Canvas 实现可交互抠图并让 Midjourney 局部重绘,主要涉及以下步骤:
- 初始化画布:使用 fabric.js 初始化画布,并设置相关属性,如是否开启互动、框选区域背景色等。
var canvas = new fabric.Canvas('.fabric-canvas', { interactive: true,});canvas.selection = true;canvas.selectionColor = 'rgba(255, 255, 255, 0.7)';canvas.selectionFullyContained = true;- 设置画布宽高及宽高比:确保底图宽高及宽高比与画布相同,以便准确框选底图区域。
fabric.Image.fromURL('图片地址 url', (img) => { canvas.setHeight(400); canvas.setWidth(400 * img.width / img.height); canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas), { left: 0, top: 0, originX: 'left', originY: 'top', scaleX: canvas.width / img.width, scaleY: canvas.height / img.height, crossOrigin: 'anonymous', });}, { crossOrigin: 'anonymous',});- 框选区域:实现矩形和多边形框选功能,并处理相关交互逻辑。
var startPoint = null;var endPoint = null;var tempPolygon = null;var isDraw = true;var mode = 'square';var point = [];function clearTempPolygon() { tempPolygon && canvas.remove(tempPolygon);}canvas.on('mouse:out', () => { startPoint = null;});canvas.on('mouse:down', (point) => { isDraw = true; startPoint = new fabric.Point(point.pointer.x, point.pointer.y);});canvas.on('mouse:up', (point) => { clearTempPolygon(); endPoint = new fabric.Point(point.pointer.x, point.pointer.y); if (startPoint && endPoint.eq(startPoint)) return; if (mode === 'square' && startPoint && isDraw) { const rect = new fabric.Rect({ left: startPoint.x, top: startPoint.y, width: endPoint.x - startPoint.x, height: endPoint.y - startPoint.y, fill: 'rgba(255, 255, 255, 0.7)', selectable: true, }); canvas.add(rect); } else if (mode === 'point') { if (endPoint || endPoint.x < 0 || endPoint.x > canvas.width || endPoint.y < 0 || endPoint.y > canvas.height || points.length < 3) return; startPoint = null; const polygon = new fabric.Polygon([ ...points ], { fill: 'rgba(255, 255, 255, 0.7)', selectable: true, }); canvas.add(polygon); } points = [];});canvas.on('mouse:move', (point) => { if (isDraw) return; points.push(new fabric.Point(point.pointer.x, point.pointer.y)); clearTempPolygon(); if (mode === 'point' && startPoint) { tempPolygon = new fabric.Polygon([ ...points ], { fill: 'rgba(255, 255, 255, 0.7)', selectable: false, }); canvas.add(tempPolygon); }});- 监听绘制对象操作:处理绘制对象的旋转、移动、缩放和倾斜等操作。
canvas.on('object:modified', () => { isDraw = false;});canvas.on('object:rotating', () => { isDraw = false;});canvas.on('object:scaling', () => { isDraw = false;});canvas.on('object:moving', () => { isDraw = false;});canvas.on('object:skewing', () => { isDraw = false;});- 撤销操作:使用栈的数据结构记录操作数据,实现撤销功能。
export class CanvasSnapShot { private canvasSnapShot: fabric.Object[][] = []; get() { return this.canvasSnapShot; } getLast() { if (count() < 1) return []; return this.canvasSnapShot[count() - 1] ?? []; } push(val: fabric.Object[]) { this.canvasSnapShot.push(val); } pop() { return this.canvasSnapShot.pop(); } count() { return this.canvasSnapShot.length; }}var canvasSnapShot = new CanvasSnapShot();function updateObjects() { canvasSnapShot.push(canvas.getObjects().map((obj: fabric.Object) => { return new fabric.Object(obj); }));}function undo() { canvasSnapShot.pop(); canvas.remove(...canvas.getObjects()); canvas.add(...canvasSnapShot.getLast().map((obj: fabric.Object) => new fabric.Object(obj))); canvas.renderAll();}- 生成 base64 图:克隆画布并生成符合 Midjourney 要求的 base64 图。
var mask_base64 = '';function createBase64() { canvas.clone((res: any) => { res.setBackgroundImage(undefined); res.setBackgroundColor('rgba(0, 0, 0, 1)'); res._objects = res.getObjects().map((obj: any) => { obj.set('fill', 'rgba(255, 255, 255, 1)'); return obj; }); res.renderAll(); mask_base64 = res.toDataURL({ format: 'jpeg', }); }, [ 'width', 'height' ]);}- 清除画布:在组件或页面销毁时清除画布元素和事件监听器。
canvas.dispose();通过以上步骤,可以实现 Canvas 的可交互抠图功能,并与 Midjourney 的局部重绘功能结合。