使用canvas绘制通过多边形标注区域
AI视频项目中需要分析图片,需要前台绘制区域,后端获取坐标然后识别图像,通过canvas
获取点然后连线绘图
HEML代码段
<template>
<!-- 组件模板开始 -->
<div class="w100 h100 wrap">
<div
class="imgwrap"
:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
>
<slot></slot>
</div>
<canvas ref="canvas" :width="canvasWidth" :height="canvasHeight"></canvas>
</div>
</template>
CSS代码段
<style scoped lang="scss">
.wrap {
position: relative;
}
.imgwrap {
position: absolute;
top: 0;
left: 0;
div {
height: 100%;
width: 100%;
}
img {
height: 100%;
width: 100%;
}
}
canvas {
border: 1px solid #fff;
position: absolute;
top: 0;
left: 0;
}
</style>
script代码段
<script>
// 组件脚本开始
export default {
name: "draw", // 组件名称
props: {
// 传入的值
value: {
type: Array,
default: () => {
return [];
},
},
canvasWidth: {
type: Number,
default: 402,
},
canvasHeight: {
type: Number,
default: 302,
},
// 开启绘制和绘制微调
DrawingValue: {
type: Boolean,
default: false,
},
// 控制一个图形可以包含点的数量
count: {
type: Number,
default: 4,
},
//清除绘制
clear: {
type: Boolean,
default: false,
},
// 控制是否可以拖拽
isDraggable: {
type: Boolean,
default: true,
},
//控制第几个图形
index: {
type: [Number, String],
default: 0,
},
},
data() {
return {
canvasWidths: undefined,
canvasHeights: undefined,
imageSrc: "your_image_url_here", // 图像的URL地址
context: null, // 画布上下文
points: [], // 临时存储当前绘制的点
shapes: [], // 已保存的形状
isDragging: false, // 是否正在拖拽
draggingIndex: -1, // 当前拖拽的点
draggingShapeIndex: -1, // 当前拖拽的形状
Drawing: false, // 是否开启绘制
};
},
methods: {
// 绘制线段
drawLine(points, index) {
if (index > 0) {
// 如果点的数量大于1,就绘制线段,等于1就绘制点不需要连线
if (this.count > 1) {
this.context.beginPath();
this.context.moveTo(points[index - 1].X, points[index - 1].Y);
this.context.lineTo(points[index].X, points[index].Y);
this.context.strokeStyle = "red";
this.context.lineWidth = 1;
this.context.stroke();
} else if (this.count == 1) {
this.context.beginPath();
this.context.arc(
points[index].X,
points[index].Y,
2,
0,
2 * Math.PI,
false
);
this.context.fillStyle = "red";
this.context.fill();
this.context.lineWidth = 1;
this.context.strokeStyle = "red";
this.context.stroke();
}
}
},
// 处理点击事件,用于添加新点
// 处理点击事件,用于添加新点
handleCanvasClick(event) {
// 如果当前是在拖动状态,则不应当添加新的点
if (this.isDragging) return;
const rect = this.$refs.canvas.getBoundingClientRect();
const X = event.clientX - rect.left;
const Y = event.clientY - rect.top;
// 检查当前点是否在画布中已有的点附近,因为我们不想要重复的点
const isDuplicate = this.points.some((point) => {
return Math.abs(point.X - X) < 5 && Math.abs(point.Y - Y) < 5;
});
// 如果我们当前没有在绘制或者点击位置是一个重复的点,则不做任何操作
if (!this.Drawing || isDuplicate) return;
// 将新点击的点添加到点集合中
this.points.push({ X, Y });
// 每次添加点后都重绘画布
this.redraw();
// 如果点的数量达到了允许的最大数量,保存形状并重置点集合
if (this.points.length === this.count) {
// 注意这里用的是 === 检查
this.shapes[this.index] = [...this.points];
this.points = []; // 重置点集合,为下一个图形做准备
this.redraw(); // 重置并重绘画布
//派发事件告诉父组件绘制完毕
this.$emit("drawn", this.shapes);
}
},
// 处理鼠标按下事件
handleMouseDown(event) {
// 如果开启绘制
const rect = this.$refs.canvas.getBoundingClientRect();
const X = event.clientX - rect.left;
const Y = event.clientY - rect.top;
if (this.isDragging) {
// 找到点击的形状
for (let s = 0; s < this.shapes.length; s++) {
this.draggingShapeIndex = s;
this.draggingIndex = this.shapes[s].findIndex(
(point) =>
Math.abs(point.X - X) < 5 &&
Math.abs(point.Y - Y) < 5 &&
s === this.index
);
if (this.draggingIndex !== -1) {
this.points = [...this.shapes[s]];
return;
}
}
}
},
// 处理鼠标移动
handleMouseMove(event) {
if (this.isDragging) {
const rect = this.$refs.canvas.getBoundingClientRect();
const X = event.clientX - rect.left;
const Y = event.clientY - rect.top;
this.points[this.draggingIndex].X = X;
this.points[this.draggingIndex].Y = Y;
//派发事件告诉父组件绘制完毕
this.$emit("drawn", this.shapes);
this.redraw();
}
},
// 处理鼠标抬起
handleMouseUp(event) {
if (this.isDragging && this.draggingIndex !== -1) {
// 注意这里要创建 this.points 的副本复制到 shapes 数组中
this.shapes[this.draggingShapeIndex] = [...this.points];
this.points = [];
this.draggingIndex = -1;
this.draggingShapeIndex = -1;
this.redraw();
}
},
// 绘制点
drawPoint(X, Y) {
this.context.beginPath();
this.context.arc(X, Y, 2, 0, 2 * Math.PI, false);
this.context.fillStyle = "red";
this.context.fill();
this.context.lineWidth = 1;
this.context.strokeStyle = "red";
this.context.stroke();
},
// 重新绘制画布
// 重新绘制画布
// 重新绘制画布(更新方法,添加了判断并传递isCurrentIndex到绘制方法)
redraw() {
this.context.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
// 重新绘制所有保存的图形
this.shapes.forEach((points, shapeIndex) => {
const isCurrentIndex = shapeIndex === this.index;
this.drawPolygon(points, isCurrentIndex);
points.forEach((point, index) => {
this.drawPoint(point.X, point.Y, isCurrentIndex);
this.drawLine(points, index, isCurrentIndex); // line color change needs to be implemented in drawLine
});
});
// 绘制当前正在创建的图形
this.points.forEach((point, index) => {
this.drawPoint(point.X, point.Y, false);
if (index > 0) {
this.drawLine(this.points, index, false); // assuming this is the temporary shape and uses default red color
}
});
},
// 绘制多边形(更新方法,添加了根据index改变颜色功能)
drawPolygon(points, isCurrentIndex) {
if (points.length) {
if (this.count > 1 || this.count === 1) {
this.context.beginPath();
this.context.moveTo(points[0].X, points[0].Y);
}
for (let i = 1; i < points.length; i++) {
this.context.lineTo(points[i].X, points[i].Y);
}
if (points.length > 2) {
this.context.closePath();
}
this.context.strokeStyle = isCurrentIndex ? "blue" : "red";
this.context.lineWidth = isCurrentIndex ? 2 : 1;
this.context.stroke();
if (this.count === 1) {
this.context.fillStyle = isCurrentIndex ? "blue" : "red";
this.context.fill();
}
}
},
// 动画方法,用于拖拽时重新绘制画布
animate() {
if (this.isDragging) {
this.redraw();
requestAnimationFrame(this.animate);
}
},
//清除绘制
handleClear() {
if (this.points.length > 0) {
this.points = [];
} else {
this.shapes[this.index] = [];
}
this.redraw();
//派发事件告诉父组件绘制完毕
this.$emit("drawn", this.shapes);
},
},
mounted() {
// 获取画布上下文
this.context = this.$refs.canvas.getContext("2d");
// 添加事件监听器
this.$refs.canvas.addEventListener("click", this.handleCanvasClick);
this.$refs.canvas.addEventListener("mousedown", this.handleMouseDown);
this.$refs.canvas.addEventListener("mousemove", this.handleMouseMove);
this.$refs.canvas.addEventListener("mouseup", this.handleMouseUp);
this.$refs.canvas.addEventListener("mouseleave", this.handleMouseUp);
// 绑定动画方法的上下文
this.animate = this.animate.bind(this);
},
watch: {
// 监听canvasWidth变化
canvasWidth: {
immediate: true,
handler(newValue, oldValue) {
this.canvasWidths = newValue;
},
},
canvasHeight: {
immediate: true,
handler(newValue, oldValue) {
this.canvasHeights = newValue;
},
},
value: {
immediate: true,
handler(newValue, oldValue) {
setTimeout(() => {
this.shapes.push(...JSON.parse(JSON.stringify(newValue))); // 添加新的图形而不是替换全部图形
this.redraw(); // 重新绘制图形
}, 200);
},
},
// 更新方法,添加了判断并传递isCurrentIndex到绘制方法
index: {
handler(newValue, oldValue) {
this.redraw();
},
},
//清除绘制
clear: {
handler(newValue, oldValue) {
//清除绘制
this.handleClear();
},
},
// 开启绘制
DrawingValue: {
immediate: true,
deep: true,
handler(newValue, oldValue) {
this.Drawing = newValue;
},
},
// 开启绘制微调
isDraggable: {
immediate: true,
handler(newValue, oldValue) {
this.isDragging = newValue;
},
},
},
};
// 组件脚本结束
</script>