Bootstrap

canvas实现图片标注,绘制区域

使用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>

;