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>

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
实现 Canvas 上的图片标注、缩放、移动、保存历史状态、橡皮擦等功能,可以通过以下步骤来完成: 1. 加载图片。使用 JavaScript 中的 Image 对象加载图片,并在图片加载完成后将其绘制Canvas 上。 ```javascript const img = new Image(); // 创建 Image 对象 img.onload = function() { ctx.drawImage(img, 0, 0); // 将图片绘制到画布上 }; img.src = 'image.jpg'; // 设置图片路径 ``` 2. 实现标注功能。通过鼠标事件监听用户的操作,使用 Canvas 的 API 绘制标注。例如,监听鼠标点击事件,在点击位置绘制一个圆形。 ```javascript canvas.addEventListener('mousedown', function(e) { ctx.beginPath(); ctx.arc(e.offsetX, e.offsetY, 5, 0, 2 * Math.PI); ctx.fill(); }); ``` 3. 实现缩放和移动功能。通过鼠标滚轮事件监听用户的操作,使用 Canvas 的 API 实现缩放和移动。例如,监听鼠标滚轮事件,在滚轮滚动时根据滚动方向调整画布的缩放比例。 ```javascript canvas.addEventListener('wheel', function(e) { const delta = e.deltaY > 0 ? 0.1 : -0.1; const scale = Math.max(0.1, Math.min(10, currentScale + delta)); ctx.scale(scale, scale); currentScale = scale; }); ``` 4. 实现保存历史状态功能。使用 JavaScript 中的数组来保存历史状态,每当用户进行操作时,将当前状态保存到数组中。撤销操作时,从数组中取出上一个状态并恢复到画布上。 ```javascript const states = []; // 保存历史状态的数组 function saveState() { states.push(canvas.toDataURL()); // 保存当前状态 } function undo() { if (states.length > 0) { const img = new Image(); img.onload = function() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 清空画布 ctx.drawImage(img, 0, 0); // 绘制上一个状态 }; img.src = states.pop(); // 取出上一个状态 } } ``` 5. 实现橡皮擦功能。通过监听鼠标事件,在鼠标位置绘制一个与背景相同的矩形,来模拟橡皮擦的效果。 ```javascript canvas.addEventListener('mousemove', function(e) { if (erasing) { ctx.fillStyle = '#ffffff'; // 设置橡皮擦颜色为白色 ctx.fillRect(e.offsetX - 5, e.offsetY - 5, 10, 10); // 绘制矩形 } }); ``` 完整示例代码如下: ```html <!DOCTYPE html> <html> <body> <canvas id="canvas" width="600" height="400"></canvas> <button onclick="undo()">撤销</button> <button onclick="erasing = !erasing">橡皮擦</button> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); let currentScale = 1; let erasing = false; const states = []; const img = new Image(); img.onload = function() { ctx.drawImage(img, 0, 0); saveState(); }; img.src = 'image.jpg'; canvas.addEventListener('mousedown', function(e) { ctx.beginPath(); ctx.arc(e.offsetX, e.offsetY, 5, 0, 2 * Math.PI); ctx.fill(); saveState(); }); canvas.addEventListener('mousemove', function(e) { if (erasing) { ctx.fillStyle = '#ffffff'; ctx.fillRect(e.offsetX - 5, e.offsetY - 5, 10, 10); } }); canvas.addEventListener('wheel', function(e) { const delta = e.deltaY > 0 ? 0.1 : -0.1; const scale = Math.max(0.1, Math.min(10, currentScale + delta)); ctx.scale(scale, scale); currentScale = scale; }); function saveState() { states.push(canvas.toDataURL()); } function undo() { if (states.length > 1) { const img = new Image(); img.onload = function() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0); }; img.src = states[states.length - 2]; states.pop(); } } </script> </body> </html> ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农六六

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值