vue js 图像标注 --- canvas 实现

图像标注

        • 需求
        • 打点式实现思路
        • 矩形框实现思路
        • 右键弹出删除和添加备注菜单
        • 删除选中内容
        • 更新备注

项目中碰到一个需要对图像标注的功能,查了好几个插件,但是感觉用起来有点颇为复杂而且和自己需要的功能不完全一致,于是就自己用canvas写了一个简单的:

vue js 图像标注 --- canvas 实现_第1张图片

需求
  • 两种标注方式,一种是打点式标注,一种是矩形框标注
  • 打点式:单击图像打点,把每一个点用线连接起来,最后双击结束,形成一个闭环,并把所有坐标点记录下来传给后端
  • 矩形框:和其他标注工具一样,鼠标随意拖动,形成一个矩形框,并把四个角的坐标点记录下来传给后端
  • 在绘制的内容上右键高亮选中该内容并弹出菜单,可以删除,可以更新备注
  • 使用打点式标注,鼠标变画笔;使用矩形框标注,鼠标变十字
打点式实现思路
  • 创建canvas 并绘制和图片一样大的背景
  • 使用click事件获取点击的坐标,用fillRect方法绘制一个直径4像素的小矩形,作为点(也可以做圆形)
  • 把每次点击的坐标存储起来,点下一个坐标时,使用moveTo,lineTo方法连线
  • 双击时把最后一个坐标和第一个连起来

注意: 双击事件会触发两次单击事件,需要处理一下,网上找的方法:

	click(){
		clearTimeout(timer)
		timer = setTimeout(()=>{
			// xxx
		},200)
	}
	dbclick(){
		clearTimeout(timer)
		// xxxx
	}

打点式标注部分代码

this.ctx.drawImage(this.image,0,0,this.w,this.h)

// 绘制矩形小点
this.ctx.fillStyle="#FF0000";
this.ctx.fillRect(e.offsetX - 2, e.offsetY - 2, 4, 4);

//连线  tempPointArr是储存坐标点的数组,双击形成闭环后清空
this.ctx.beginPath()
this.ctx.strokeStyle= '#FF0000'
this.ctx.moveTo(this.tempPointArr[this.tempPointArr.length - 2].x,this.tempPointArr[this.tempPointArr.length - 2].y);
this.ctx.lineTo(this.tempPointArr[this.tempPointArr.length - 1].x,this.tempPointArr[this.tempPointArr.length - 1].y);
this.ctx.stroke();
矩形框实现思路
  • 使用mousedown mousemove mouseup 来完成
  • mousedown事件获取开始的坐标
  • mousemove事件使用strokeRect画矩形,但要注意先清空,再画新的,否则会导致有很多矩形框
  • mouseup事件清空开始坐标,完成一个矩形框的绘制

鼠标按下时记录当前canvas 内容,在每一次鼠标拖动时先还原之前干净的canvas内容,再画新的矩形,达到先清空再画新矩形的目的

重点代码:



mousedown(){
	this.imgData = this.ctx.getImageData(0,0,this.w,this.h)
}
mousemove(){
	this.ctx.putImageData(this.imgData,0,0)
}

矩形框标注部分代码

mousedown(){
	this.startPoint = {
      x:e.offsetX,
      y:e.offsetY
    }
    this.imgData = this.ctx.getImageData(0,0,this.w,this.h)
}

mousemove(){
	this.ctx.putImageData(this.imgData,0,0)
    this.ctx.beginPath()
    this.ctx.fillStyle = "rgba(255,0,0,0.1)";
    this.ctx.strokeStyle = '#FF0000';
    this.ctx.strokeRect(this.startPoint.x,this.startPoint.y,e.offsetX - this.startPoint.x,e.offsetY - this.startPoint.y);
}

mouseup(){
	this.startPoint = null
}
右键弹出删除和添加备注菜单

在有内容的地方才弹出,没有内容的地方不弹
主要使用坐标点去判断在不在某一个范围内,之前把每一个绘制的内容都存储成数组

思路:右键的坐标点必须满足大于某个x点,小于另一个x点,大于某个y点,小于另一个y点,则表示在这个内容区域内

部分代码

const index = this.pointData.findIndex((item)=>{
			    let leftX = false
			    let rightX = false
			    let topY = false
			    let bottomY = false
			    item.point.forEach(p=>{
			      if(e.offsetX > p.x){
			        leftX = true
			      }
			      if(e.offsetX < p.x){
			        rightX = true
			      }
			      if(e.offsetY < p.y){
			        bottomY = true
			      }
			      if(e.offsetY > p.y){
			        topY = true
			      }
			    })
			    return leftX && rightX && topY && bottomY
			  })
if(index > -1){
	/**
		xxxxxx
	*/
	this.fillRect(this.pointData[index].point) //给这个区域填充背景色,表示高亮
}
删除选中内容

思路:使用橡皮擦模拟实现删除效果,canvas下面要有多一张背景图,否则的话会直接擦成白色;
使用完后再使用其他功能切记要把globalCompositeOperation属性从destination-out改回默认值 source-over

部分代码

this.ctx.globalCompositeOperation = 'destination-out'  //重点
/** 
	...重新绘制一次要删除的内容
*/
this.ctx.globalCompositeOperation = 'source-over'
更新备注

使用fillText方法添加文本
在添加新的文本前先清除掉之前写的文本,主要使用clearRect方法

部分代码

let textW = this.ctx.measureText(this.textObj[this.selectedIndex]).width
this.ctx.clearRect(this.pointData[this.selectedIndex].point[0].x + 5,this.pointData[this.selectedIndex].point[0].y - 8 - 12 ,textW,13)
this.ctx.font="12px Arial"
this.ctx.fillStyle ='#FF0000'
this.ctx.fillText(this.note,this.pointData[this.selectedIndex].point[0].x + 5,this.pointData[this.selectedIndex].point[0].y - 8)

大致思路到此为止

多次使用geiImageData putImageData 来实现保存某一步的内容,再显示;
也可以利用这个实现撤销的功能

如果需要完整的代码可以去git 上下载一下,也希望有大佬可以帮忙优化一下代码,感谢~~~
最后附上地址:

https://gitee.com/cuijinrong/project-gather/blob/master/src/components/marker.vue

你可能感兴趣的:(vue组件,html5,javascript)