天天看點

vue js 圖像标注 --- canvas 實作

圖像标注

        • 需求
        • 打點式實作思路
        • 矩形框實作思路
        • 右鍵彈出删除和添加備注菜單
        • 删除選中内容
        • 更新備注

項目中碰到一個需要對圖像标注的功能,查了好幾個插件,但是感覺用起來有點頗為複雜而且和自己需要的功能不完全一緻,于是就自己用canvas寫了一個簡單的:

vue js 圖像标注 --- canvas 實作

需求

  • 兩種标注方式,一種是打點式标注,一種是矩形框标注
  • 打點式:單擊圖像打點,把每一個點用線連接配接起來,最後輕按兩下結束,形成一個閉環,并把所有坐标點記錄下來傳給後端
  • 矩形框:和其他标注工具一樣,滑鼠随意拖動,形成一個矩形框,并把四個角的坐标點記錄下來傳給後端
  • 在繪制的内容上右鍵高亮選中該内容并彈出菜單,可以删除,可以更新備注
  • 使用打點式标注,滑鼠變畫筆;使用矩形框标注,滑鼠變十字

打點式實作思路

  • 建立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

繼續閱讀