快速构建一个完整的图片标注功能

需求分析

页面中显示需要标注的图片,鼠标绘制矩形进行标注(考虑到实际情况,可能还有多边形、圆形等等多种形状)。最后可以拿到标注的内容位置信息、标注信息等并且回显所有标注内容。在图片上进行绘制,首先想到的是用canvas,canvas强大的功能能让我们在图片上为所欲为。但是原生的canvas API众多且繁杂,上手不易,所以最好使用成熟的canvas框架来实现。

使用ailabel

null

ailabel只看说明这个框架好像完全满足我们的需求,但是可惜源码地址已经不存在了,文档也无法访问。不过好在网上有人fork源码库,我们可以直接下载后查看文档和demo。

好,先安装依赖nmp install,然后打开demo/index.html文件,就可以看到demo了。

但是但是但是啊,项目报错了,不管是绘制什么图形,鼠标松开后都不显示图形。F12查看发现一堆报错信息,除了第一条信息外全都是没有EventEmitter导致的。看来是没有事件触发与事件监听器功能导致绘制事件无法执行。

null

通过package.json可以知道ailabel使用了events库来作为事件机制,应该是这个库没起作用。幸运的是ailabel是支持手动编译的,也就是说我们可以修改它的源码。替换一个跟events库相同功能的库tiny-mitter(我们项目中正在使用的事件发射器库)。

null

运行npm run build重新打包,更新demo的引用文件,再次运行demo。图片已显示,标注也能正常显示,控制台也没有报错信息,一切OK。

null

通过一系列验证测试基本可以认定该框架满足需求,可以使用。

ailabel确实可以满足大部分图片标注功能,但是它可定制行、可扩展性很小,而且源码库不在说明作者已经不维护,这就需求我们手动维护。

下面介绍一种完全定制化标注的基础实现。

使用fabric

fabricjs是一个基于canvas的强大的框架,提供一种类似面向对象的方法来编写canvas,在原生canvas之上提供了交互式对象模型,通过简洁的API就可以在画布上进行丰富的操作

关于fabricjs的介绍和使用请参考 fabricjs从精通到,我们重点介绍标注画框的交互。

绘制标注

fabric提供了一系列的事件帮助我们来很好的对画布进行各种操作,标注画框主要用到的是下图中的mouse:down:画笔落下;mouse:move:画框;mouse:up画笔抬起事件

null

下面以绘制矩形为例进行说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
var fabCanvas = new fabric.Canvas(id,{xxxx})
var line, isDown, origX, origY, pointer

fabCanvas.on('mouse:down', function (o) {
isDown = true
fabCanvas.selection = false
pointer = fabCanvas.getPointer(o.e)
origX = pointer.x
origY = pointer.y
pointer = fabCanvas.getPointer(o.e)
line = new fabric.Rect({
left: origX,
top: origY,
originX: 'left',
originY: 'top',
width: pointer.x - origX,
height: pointer.y - origY,
angle: 0,
fill: '#07ff11a3',
stroke: 'black',
transparentCorners: false
})
fabCanvas.add(line)
})

fabCanvas.on('mouse:move', function (o) {
if (!isDown) return
var pointer = fabCanvas.getPointer(o.e)

if (origX > pointer.x) {
line.set({ left: Math.abs(pointer.x) })
}
if (origY > pointer.y) {
line.set({ top: Math.abs(pointer.y) })
}

line.set({ width: Math.abs(origX - pointer.x) })
line.set({ height: Math.abs(origY - pointer.y) })

fabCanvas.renderAll()
})

fabCanvas.on('mouse:up', function (o) {
isDown = false
removeEvents()
})

null

其他形状如圆形、线段、多边形等实现逻辑都是大同小异,fabric都是支持对应形状的绘制的。

调整标注

如果初始化设置画布不可选,要先把画布设为可选才能选中标注

1
2
3
fabCanvas.set({
selectable: true
})

Copy

如果要修改标注的默认选中样式,可以修改标注的对应参数

1
2
3
4
5
6
7
8
9
10
11
12
13
fabCanvas.on('selection:created': (e)=>{
console.log('selection created')
e.target.set({
transparentCorners: false,
cornerColor: '#ff7a55',
cornerStrokeColor: '#ff7a55',
borderColor: 'red',
cornerSize: 12,
padding: 10,
cornerStyle: 'circle',
borderDashArray: [3, 3]
});
})

调整画框主要用到上述的object:moving:对象移动;object:modified:对象调整;

1
2
3
4
5
6
fabCanvas.on('object:modified':(e)=> {
//修改对象
console.log( 'object:modified')
console.log(e.target);
// 修改逻辑
})

选中标注

在新建形状中监听select事件,可以抛出对应事件

1
2
3
rect.on('selected',(e)=>{
this.$emit('objectSelected', e.target)
})

删除标注

直接调用fabric的remove事件

1
fabCanvas.remove(rect)

清空所有画框

1
2
3
4
5
6
7
clearAllMark(){
const objects = fabCanvas.getObjects()
for(let i in objects){
fabCanvas.remove(i)
}
this.$emit('clearAllMark')
}

放大缩小

1
fabCanvas.setZoom(10)

以上交互已经包含了绝大部分的图片交互,只是有点碎。

总结

ailabel提供了一套完整的打点,绘制矩形、多边形等各种形状的标注框架。简单易用功能完善,但是可扩展性差而且需要自行维护(作者已经删库)

fabricjs 是基于canvas类似面向对象的框架。可以绘制什么图形,可扩展和定制型强,但是需要手动监听鼠标事件自己实现绘制标注逻辑,时间成本高