UNPKG

image-label-ts

Version:

基于ts的前端图片标注组件,不依赖vue和react

818 lines (810 loc) 33 kB
import { SVG } from '@svgdotjs/svg.js'; import '@svgdotjs/svg.panzoom.js'; import '@svgdotjs/svg.draggable.js'; import '@svgdotjs/svg.topoly.js'; import { merge } from 'lodash-es'; var LayerTypeEnum; (function (LayerTypeEnum) { LayerTypeEnum["polygon"] = "polygon"; // rect = 'rect', })(LayerTypeEnum || (LayerTypeEnum = {})); class Layer { containerCanvas; type; dom; name; id; constructor() { } } class LayerCanvas { constructor() { } } var img = "data:image/png;base64,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"; class SelectLayer { containerCanvas; layer; selectDom; cloneDom; constructor(layer) { this.layer = layer; this.containerCanvas = layer.containerCanvas; this.removeSelect(); this.selectDom = this.containerCanvas.canvas.group().size().attr({ id: 'select' }); this.cloneDom = this.layer.dom .clone() .attr({ stroke: this.layer.style.color, fill: this.layer.style.selectFillColor, name: 'select-polygon', 'stroke-dasharray': '10,10', }) .on('click', function (e) { e.stopPropagation(); }) .addTo(this.containerCanvas.canvas.findOne('#select')); if (this.layer.able.remove) { this.createCloseBtn(); } if (this.layer.able.drag) { this.selectDom.draggable().on('dragstart', () => { // 拖动遮罩区域时,隐藏选中图形 layer.dom.hide(); }); this.createLayerDraggableCircle(); this.selectDom.on('dragend', this.layerMaskDragendHandler.bind(this)); } } static init(layer) { return new SelectLayer(layer); } removeSelect() { this.containerCanvas.canvas.find('#select').remove(); } removeLayer() { this.removeSelect(); this.layer.dom.remove(); this.containerCanvas.event.remove && this.containerCanvas.event.remove(this.layer); } createCloseBtn() { let minIndex = 0; let left = 5; let top = -40; const points = this.layer.dom.plot(); points.forEach((item, index) => { if (item[1] < points[minIndex][1]) { minIndex = index; } }); if (points[minIndex][0] + 45 > this.containerCanvas.width) { left = -45; } if (points[minIndex][1] - 40 < 0) { top = 0; } if (this.selectDom.closeBtn) { this.selectDom.closeBtn.remove(); } this.selectDom.closeBtn = this.containerCanvas.canvas .image(new URL(img, import.meta.url).href) .size(40, 40) .attr({ x: points[minIndex][0] + left, y: points[minIndex][1] + top, style: 'cursor: pointer', }) .on('click', () => { this.removeLayer(); this.containerCanvas.removeLayerByLayer(this.layer); }) .addTo(this.containerCanvas.canvas.findOne('#select')); } createLayerDraggableCircle() { const points = [...this.cloneDom.array()]; points.pop(); points.map(([cx, cy], index) => { const circle = this.selectDom.circle(); return circle .radius(this.layer.style.circleRadius / this.containerCanvas.root.zoomNum) .attr({ cx, cy, fill: this.layer.style.color, index, }) .draggable() .on('click', (e) => { e.stopPropagation(); }) .on('dragstart', () => { this.layer.dom.hide(); }) .on('dragmove', this.layerMaskCircleDragmoveHandler.bind(this, circle)) .on('dragend', this.layerMaskDragendCircleHandler.bind(this)); }); } layerMaskCircleDragmoveHandler(circle) { const { cx, cy, index } = circle.attr(['cx', 'cy', 'index']); const pointsArray = this.cloneDom.array(); pointsArray[index] = [cx, cy]; if (index === 0) { pointsArray[pointsArray.length - 1] = [cx, cy]; } this.cloneDom.plot(pointsArray.join().replace(/,/g, ' ')); } layerMaskDragendHandler() { this.layer.dom._array = this.cloneDom.array(); this.layer.dom.plot(this.cloneDom.array().join().replace(/,/g, ' ')).show(); this.dragEnd(); } dragEnd() { // 多边形 if (this.layer.type === LayerTypeEnum.polygon) { const points = Polygon.transformSVGPointsToOrigin({ points: this.cloneDom.array(), scale: this.containerCanvas.transform.scale, offsetX: this.containerCanvas.transform.offsetX, offsetY: this.containerCanvas.transform.offsetY, }); this.layer.setOption({ points: points, }); } if (this.containerCanvas.event.dragEnd) { this.containerCanvas.event.dragEnd(this.layer); } } layerMaskDragendCircleHandler() { this.layer.dom.plot(this.cloneDom.array().join().replace(/,/g, ' ')).show(); this.selectDom.closeBtn.remove(); this.selectDom.closeBtn = null; this.createCloseBtn(); this.dragEnd(); } } const defaultIPolygonOption = { name: '', points: [], style: { color: '#E1600A', fillColor: 'rgba(0, 0, 0, 0)', selectFillColor: 'rgba(0, 0, 0, 0)', strokeWidth: 2, circleRadius: 4, }, able: { click: false, drag: true, remove: true, }, }; class Polygon extends Layer { points; style; able; canvasOnce; constructor(canvas, option) { super(); this.containerCanvas = canvas; const { x, y, width, height } = this.containerCanvas.labelImage.attr([ 'x', 'y', 'width', 'height', ]); this.canvasOnce = this.containerCanvas.canvas .group() .attr({ id: 'polygonCanvas-polygon-once', width, height }); this.canvasOnce.rect(width, height).attr({ x, y }); this.setOption(option); if (!this.id) { this.id = Math.floor(Math.random() * 1e10).toString(); } this.draw(); } static init(canvas, option) { return new Polygon(canvas, option); } static transformOriginPointsToSVG(params) { const { points, scale, offsetX, offsetY } = params; return points.map((item) => [item.x / scale + offsetX, item.y / scale + offsetY]); } static transformSVGPointsToOrigin(params) { const { points, scale, offsetX, offsetY } = params; const res = points.map((item) => ({ x: Math.round((item[0] - offsetX) * scale), y: Math.round((item[1] - offsetY) * scale), })); res.pop(); return res; } transformOriginPointsToSVG(points) { const { scale = 1, offsetX = 0, offsetY = 0 } = this.containerCanvas.transform; return points.map((item) => [item.x / scale + offsetX, item.y / scale + offsetY]); } transformSVGPointsToOrigin(points) { const { scale = 1, offsetX = 0, offsetY = 0 } = this.containerCanvas.transform; const res = points.map((item) => ({ x: Math.round((item[0] - offsetX) * scale), y: Math.round((item[1] - offsetY) * scale), })); res.pop(); return res; } remove() { this.dom.remove(); } select() { this.containerCanvas.event.select(this); this.containerCanvas.selectLayer = SelectLayer.init(this); } // todo 此api出错率高,可做单元测试 setOption(option) { this.type = LayerTypeEnum.polygon; const _option = merge({}, defaultIPolygonOption, this, JSON.parse(JSON.stringify(option))); // 深度合并 Object.keys(_option).forEach((key) => { this[key] = option[key] ? _option[key] : this[key] || defaultIPolygonOption[key]; }); } draw() { const pointsFormat = this.transformOriginPointsToSVG(this.points).map((item) => { return ['L', item[0], item[1]]; }); pointsFormat.unshift([ 'M', pointsFormat[pointsFormat.length - 1][1], pointsFormat[pointsFormat.length - 1][2], ]); const polyPath = this.containerCanvas.canvas .path() .plot(['M', pointsFormat[0][1], pointsFormat[0][2]].join().replace(/,/g, ' ')) .attr({ stroke: this.style.color, fill: 'none', 'stroke-width': this.style.strokeWidth / this.containerCanvas.root.zoomNum, }); try { this.dom = polyPath .plot(pointsFormat.join().replace(/,/g, ' ')) .toPoly() .attr({ color: this.style.color, stroke: this.style.color, 'stroke-width': this.style.strokeWidth / this.containerCanvas.root.zoomNum, fill: this.style.fillColor, type: 'polygon', }) .addTo(this.containerCanvas.canvas); } catch (e) { console.error('draw error', e); this.canvasOnce.remove(); this.canvasOnce = null; } this.dom.attr({ name: this.name }); if (this.able.click) { this.dom.on('click', (e) => { this.select(); e.stopPropagation(); }); } this.canvasOnce.remove(); this.canvasOnce = null; return this; } // todo reDraw(option) { this.setOption(option); // ... } } class PolygonCanvas extends LayerCanvas { containerCanvas; polygonCanvas; tempPath; // 描述手动绘图的dom对象 polyPath; // 描述手动绘图的dom对象 closePoint; // 手动绘图时首尾连通的那个点 constructor(canvas) { super(); this.containerCanvas = canvas; const { x, y, width, height } = this.containerCanvas.labelImage.attr([ 'x', 'y', 'width', 'height', ]); this.polygonCanvas = this.containerCanvas.canvas .group() .attr({ id: 'drawLayer', width, height }); this.polygonCanvas .rect(width, height) .attr({ x, y, 'fill-opacity': 0 }) .on('mousemove', this.mouseMoveFollow.bind(this)); this.polygonCanvas.pathPoints = []; this.tempPath = null; this.polygonCanvas.on('mousemove', this.drawTempPath.bind(this)); this.polygonCanvas.on('mousemove', this.polygonCountIsClose.bind(this)); this.polygonCanvas.on('click', this.drawPolyPathHandler.bind(this)); } static init(canvas) { return new PolygonCanvas(canvas); } drawTempPath(e) { if (this.polygonCanvas.pathPoints.length > 0) { const [x, y] = this.currentNodeMovePosition(e); const tempPathArray = this.polyPath.array(); tempPathArray[this.polygonCanvas.pathPoints.length] = ['L', x, y]; if (!this.tempPath) { this.tempPath = this.polygonCanvas .path() .plot(tempPathArray.join().replace(/,/g, ' ')) .attr({ stroke: this.containerCanvas.style.color, fill: this.containerCanvas.style.fillColor, 'fill-opacity': 0.6, 'stroke-width': this.containerCanvas.style.strokeWidth / this.containerCanvas.root.zoomNum, 'stroke-dasharray': '10,10', }); } else { this.tempPath.plot(tempPathArray.join().replace(/,/g, ' ')); } } } // 点击回调事件,绘点 drawPolyPathHandler(e) { const [x, y] = this.currentNodeMovePosition(e); if (this.polygonCanvas.pathPoints.length === 0) { this.polygonCanvas.pathPoints.push(this.polygonCanvas .circle() .radius(this.containerCanvas.style.circleRadius / this.containerCanvas.root.zoomNum) .attr({ cx: x, cy: y, fill: this.containerCanvas.style.color, id: 'begin' })); this.polyPath = this.polygonCanvas .path() .plot(['M', x, y].join().replace(/,/g, ' ')) .attr({ stroke: this.containerCanvas.style.color, fill: 'none', 'stroke-width': this.containerCanvas.style.strokeWidth / this.containerCanvas.root.zoomNum, }); } else { this.polygonCanvas.pathPoints.push(this.polygonCanvas .circle() .radius(this.containerCanvas.style.circleRadius / this.containerCanvas.root.zoomNum) .attr({ cx: x, cy: y, fill: this.containerCanvas.style.color })); // pathArray 为构成图形的点位 const pathArray = this.polyPath.array(); pathArray.push(['L', x, y]); this.polyPath.plot(pathArray.join().replace(/,/g, ' ')); } } // 检测鼠标是否移动到了多边形的第一个点(是否形成封闭多边形) polygonCountIsClose(e) { if (this.polygonCanvas.pathPoints.length > 2) { const [x, y] = this.currentNodeMovePosition(e); const { cx, cy } = this.polygonCanvas.pathPoints[0].attr(['cx', 'cy']); const a = Math.abs(cx - x); const b = Math.abs(cy - y); if (Math.sqrt(a * a + b * b) < 15 / this.containerCanvas.root.zoomNum) { if (!this.closePoint) { this.closePoint = this.polygonCanvas .circle() .radius(15 / this.containerCanvas.root.zoomNum) .attr({ cx, cy, fill: this.containerCanvas.style.color, 'fill-opacity': 0.5, }) .on('click', (e) => { this.pathToPolygon(); e.stopPropagation(); }); } this.polygonCanvas.followCircle && this.polygonCanvas.followCircle.hide(); this.closePoint?.show(); const tempPathArray = this.polyPath.array(); tempPathArray[this.polygonCanvas.pathPoints.length] = ['L', cx, cy]; this.tempPath.plot(tempPathArray.join().replace(/,/g, ' ')); } else { if (this.closePoint) { this.closePoint.remove(); this.closePoint = null; } this.polygonCanvas.followCircle && this.polygonCanvas.followCircle.show(); } } } pathToPolygon() { const points = this.polyPath.array().map((item) => { return [item[1], item[2]]; }); const formatPoints = Polygon.transformSVGPointsToOrigin({ points, scale: this.containerCanvas.transform.scale, offsetX: this.containerCanvas.transform.offsetX, offsetY: this.containerCanvas.transform.offsetY, }); this.containerCanvas.drawLayer(LayerTypeEnum.polygon, { name: this.containerCanvas.layerName, points: formatPoints, style: { color: this.containerCanvas.style.color, fillColor: this.containerCanvas.style.fillColor, selectFillColor: this.containerCanvas.style.selectFillColor, strokeWidth: this.containerCanvas.style.strokeWidth, circleRadius: this.containerCanvas.style.circleRadius, }, able: { click: true, drag: true, remove: true, }, }); this.remove(); this.containerCanvas.drawDone && this.containerCanvas.drawDone(); } remove() { if (this.containerCanvas.canvas.findOne('#drawLayer')) { this.containerCanvas.canvas.findOne('#drawLayer').remove(); } this.polygonCanvas = null; this.tempPath = null; this.polyPath = null; this.closePoint = null; } currentNodeMovePosition(e) { const bgNode = this.containerCanvas.root.findOne('#labelImage-background').node; const { x, y } = bgNode === null ? { x: 0, y: 0 } : bgNode.getClientRects()[0]; return [ (1 / this.containerCanvas.root.zoomNum) * (e.clientX - x + this.containerCanvas.transform.offsetX), (1 / this.containerCanvas.root.zoomNum) * (e.clientY - y + this.containerCanvas.transform.offsetY), ]; } mouseMoveFollow(e) { const [cx, cy] = this.currentNodeMovePosition(e); this.containerCanvas.root.on('mousemove', this.removeFollower.bind(this)); const polygonCanvas = this.polygonCanvas; if (!polygonCanvas.followCircle) { polygonCanvas.followCircle = polygonCanvas .circle() .radius(this.containerCanvas.style.circleRadius / this.containerCanvas.root.zoomNum) .attr({ cx, cy, fill: this.containerCanvas.style.color }) .on('mousemove', (e) => { const [x, y] = this.currentNodeMovePosition(e); polygonCanvas.followCircle.attr({ cx: x, cy: y }); }); } else { polygonCanvas.followCircle.attr({ cx, cy }); } } removeFollower(e) { if (this.containerCanvas.root) { const polygonCanvas = this.polygonCanvas; const [cx, cy] = this.currentNodeMovePosition(e); const { width, height } = polygonCanvas.attr(['width', 'height']); if (cx > width || cy > height || cx < 0 || cy < 0) { if (polygonCanvas.followCircle) { polygonCanvas.followCircle.remove(); polygonCanvas.followCircle = null; this.containerCanvas.root.off('mousemove'); } } } } } const loadImage = (url) => { return new Promise((res) => { const image = new Image(); image.onload = () => { res(image); }; image.src = url; }); }; var ModeEnum; (function (ModeEnum) { ModeEnum["default"] = "default"; ModeEnum["drag"] = "drag"; ModeEnum["poly"] = "poly"; })(ModeEnum || (ModeEnum = {})); const defaultContainerCanvasOption = { width: 0, height: 0, zoom: { zoomFactor: 0.5, zoomMin: 0.5, zoomMax: 20, }, style: { color: '#E1600A', fillColor: 'rgba(0, 0, 0, 0)', selectFillColor: 'rgba(0, 0, 0, 0)', strokeWidth: 2, circleRadius: 4, }, layerName: 'default-layer', mode: ModeEnum.default, event: { draw: () => { }, select: () => { }, remove: () => { }, dragEnd: () => { }, clickCanvas: () => { }, beforeLoadImage: () => { }, loadedImage: () => { }, }, transform: { scale: 1, fixedOffset: false, offsetX: 0, offsetY: 0, }, }; class ContainerCanvas { root; canvas; layerCanvas; // layer画布, 手动画图时使用 layers = []; // 标注的图形 labelImage; // 标注的图 selectLayer; polygonCanvas; width; height; zoom; style; layerName; mode; event; transform; imageUrl = ''; drawImageWidth = 0; drawImageHeight = 0; constructor(el, option) { this.setOption(option); this.root = SVG().addTo(el).attr({ style: 'vertical-align: bottom' }); this.root.zoomNum = 1; this.canvas = this.root.group().attr({ id: 'canvas' }); this.root.size(this.width, this.height).viewbox(0, 0, this.width, this.height); if (this.mode !== ModeEnum.default) { this.root = this.root.panZoom(this.zoom); } this.root.on('zoom', (lvl) => { this.root.zoomNum = lvl.detail.level; this.canvas.find('path').forEach((path) => { path.attr({ 'stroke-width': this.style.strokeWidth / lvl.detail.level, }); }); this.canvas.find('polyline').forEach((polyline) => { polyline.attr({ 'stroke-width': this.style.strokeWidth / lvl.detail.level, }); }); this.canvas.find('circle').forEach((circle) => { circle.radius(this.style.circleRadius / lvl.detail.level); }); }); this.canvas.on('click', () => { this.selectLayer && this.selectLayer.removeSelect(); this.selectLayer = undefined; this.event.clickCanvas && this.event.clickCanvas(); }); } static init(el, option) { return new ContainerCanvas(el, option); } setOption(option) { const _option = merge({}, defaultContainerCanvasOption, option); Object.keys(_option).forEach((key) => { // this[key] = _option[key]; this[key] = this[key] || _option[key]; }); } setMode(mode) { this.mode = mode; switch (mode) { case ModeEnum.drag: this.polygonCanvas && this.polygonCanvas.remove(); this.polygonCanvas = null; break; case ModeEnum.poly: // 创建layer画布 this.polygonCanvas = PolygonCanvas.init(this); break; case ModeEnum.default: break; } } toggleMode(mode1, mode2) { if (this.mode === mode1) { this.setMode(mode2); } else { this.setMode(mode1); } } // 清除整个画布 clear() { this.canvas.children().forEach((child) => { child.remove(); }); this.layers = []; } // 获取所有的图形 (ContainerCanvas实例是个ref时, containerCanvas.layers也会变成响应式) getLayers(type) { return this.layers.filter((layer) => layer.type === type); } // 获取所有的图形 getLayersByName(type, name) { return this.getLayers(type).filter((layer) => layer.name === name); } // 根据多边形删除多边形 removeLayerByLayer(layer) { layer.remove(); for (let i = 0; i < this.layers.length; i++) { if (this.layers[i] === layer) { this.layers.splice(i, 1); return; } } } // 根据多边形删除多边形 removeLayerById(id) { const layer = this.layers.find(item => item.id === id); if (layer) { layer.remove(); const index = this.layers.indexOf(layer); this.layers.splice(index, 1); } } drawDone() { this.root.off('mousemove'); this.setMode(ModeEnum.drag); this.event.draw && this.event.draw(this.layers[this.layers.length - 1]); } loadImageStack = []; loadImagePool = (imageUrl, fn) => { return new Promise((resolve) => { const p = loadImage(imageUrl); this.loadImageStack.push(p); p.then((image) => { if (this.loadImageStack[this.loadImageStack.length - 1] === p) { fn.call(this, image); } this.loadImageStack.splice(0, this.loadImageStack.indexOf(p)); resolve(null); }); }); }; // 加载图片 async loadImage(imageUrl, imageWidth, imageHeight) { this.imageUrl = imageUrl; if (this.mode !== ModeEnum.default) { this.setMode(ModeEnum.drag); } this.root.zoom(1); this.root.zoomNum = 1; if (this.mode !== ModeEnum.default) { this.root = this.root.panZoom(this.zoom); } this.root.viewbox(0, 0, this.width, this.height); this.clear(); this.layers = []; if (!imageUrl) { return; } this.event.beforeLoadImage && this.event.beforeLoadImage(); await this.loadImagePool(imageUrl, (image) => { this.event.loadedImage && this.event.loadedImage(); imageWidth = imageWidth || image.width; imageHeight = imageHeight || image.height; this.transform.scale = Math.max(imageWidth / this.width, imageHeight / this.height); this.drawImageWidth = imageWidth / this.transform.scale; this.drawImageHeight = imageHeight / this.transform.scale; if (!this.transform.fixedOffset) { this.transform.offsetX = (this.width - this.drawImageWidth) / 2; this.transform.offsetY = (this.height - this.drawImageHeight) / 2; } this._loadImage(imageUrl).size(this.drawImageWidth, this.drawImageHeight).attr({ x: this.transform.offsetX, y: this.transform.offsetY, }); }); } _loadImage(imageUrl) { const image = this.canvas.findOne('#labelImage-background'); if (image) { image.load(imageUrl); return image; } else { this.labelImage = this.canvas.image(imageUrl).attr({ id: 'labelImage-background' }); return this.labelImage; } } // 画蒙层 drawMask(color) { this.canvas .rect(this.drawImageWidth, this.drawImageHeight) .fill(color) .attr({ x: this.transform.offsetX, y: this.transform.offsetY }); } // 画一个图片的多边形可使区域 drawPolygonImages(points, imageUrl = this.imageUrl) { if (!points || !points.length) return; const pattern = this.root .defs() .pattern() .attr({ x: this.transform.offsetX, y: this.transform.offsetY }); pattern.image(imageUrl).size(this.drawImageWidth, this.drawImageHeight); points.forEach(point => { const _points = Polygon.transformOriginPointsToSVG({ points: point, scale: this.transform.scale, offsetX: this.transform.offsetX, offsetY: this.transform.offsetY, }); const str = _points.map((item) => `${item[0]},${item[1]}`).join(' '); this.canvas.polygon(str).fill(pattern); }); } // 绘制一个图形 drawLayer(type, option) { if (type === LayerTypeEnum.polygon) { try { this.layers.push(Polygon.init(this, option)); } catch (e) { console.error('drawLayer error', e); } } } // 绘制多个图形 drawLayers(type, options) { options.forEach((option) => { option.points.forEach((point) => { this.drawLayer(type, { ...option, points: point, }); }); }); } hideLayers(type) { this.getLayers(type).forEach((item) => { item.dom.style.display = 'none'; }); } showLayers(type) { this.getLayers(type).forEach((item) => { item.dom.style.display = 'block'; }); } hideLayersByName(type, name) { this.getLayersByName(type, name).forEach((item) => { item.dom.node.style.display = 'none'; }); } showLayersByName(type, name) { this.getLayersByName(type, name).forEach((item) => { item.dom.node.style.display = 'block'; }); } // 切换图片并且绘制多边形 async loadImageAndDrawLayers(imageUrl, options) { await this.loadImage(imageUrl); (options || []).forEach((option) => { if (option.type === LayerTypeEnum.polygon) { this.drawLayers(option.type, option.option); } }); } } export { ContainerCanvas, Layer, LayerCanvas, LayerTypeEnum, ModeEnum, Polygon, PolygonCanvas, SelectLayer, defaultContainerCanvasOption }; //# sourceMappingURL=index.js.map