🍔 fabric如何实现辅助选区捏

功能概述

基本流程:以不同的颜色分区作为依据,解析图片数据,帮助辅助选区

最终效果:

1.1 核心功能

  • 图像追踪:将位图选区图片转换为 SVG 矢量路径

  • 路径解析:按颜色分组解析 SVG 路径数据

  • 交互选择:支持鼠标点击、悬停等交互操作

1.2 技术栈

  • Fabric.js:Canvas 图形库,用于路径渲染和交互

  • imageTracer.js:图像追踪库,用于位图转矢量


核心实现流程

主函数:test()

test() 函数是选区识别与交互功能的核心入口,完整流程如下:

初始化画布状态

ini 复制代码
selectionLoading.value = true // 显示加载提示
fabricCanvas.skipTargetFind = false // 启用对象检测
fabricCanvas.isDrawingMode = false // 禁用绘制模式
fabricCanvas.renderAll() // 重新渲染

关键点

  • skipTargetFind: false 允许鼠标事件被路径对象捕获
  • isDrawingMode: false 确保不会触发自由绘制

加载选区图片

此处需要一张以不同颜色区分选区的图片,后续将基于这些颜色进行区域识别:

ini 复制代码
const img = new Image()
img.crossOrigin = 'anonymous' // 跨域支持
img.src = props.selectionUrl

await new Promise((resolve) => {
  img.onload = resolve
})

提取选区图片像素数据

ini 复制代码
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = img.width
canvas.height = img.height
ctx.drawImage(img, 0, 0)

创建临时 Canvas 获取图片的像素数据

将图片canvas的数据导入进 imageTracer;将其转化为svg,

arduino 复制代码
const svgString = imageTracer.imageDataToSVG(ctx?.getImageData(0, 0, canvas.width, canvas.height), {
  ltres: 1, // 直线阈值
  qtres: 1, // 二次曲线阈值
  pathomit: 8, // 路径简化阈值
})

参数说明

  • ltres:控制直线检测的精度,值越小越精确
  • qtres:控制曲线检测的精度
  • pathomit:路径简化参数,值越大简化程度越高

SVG 格式的字符串,包含多个 <path> 元素。这些path元素记录了我们需要的区域信息,比方说在这张图的那张位置,边界是如何巴拉巴拉。

<path> 元素提供了精确的区域信息,将其导入fabric的path中,利用fabric的交互监听,可以帮助进行处理

解析 SVG 并按颜色分组

typescript 复制代码
const parser = new DOMParser()
const svgDoc = parser.parseFromString(svgString, 'image/svg+xml')
const pathGroupByColor = new Map<string, string>()

svgDoc.querySelectorAll('path').forEach((path) => {
  const color = path.getAttribute('fill') || '#000000'
  // 过滤黑色背景
  if (color === 'rgb(0,0,0)' || color === '#000000') {
    return
  }
  const pathData = path.getAttribute('d') || ''
  const oriVal = pathGroupByColor.get(color) || ''
  pathGroupByColor.set(color, oriVal + ' ' + pathData)
})

处理逻辑

  1. 使用 DOMParser 解析 SVG 字符串为 DOM 对象
  2. 遍历所有 <path> 元素
  3. fill 颜色属性分组,相同颜色的路径数据合并
  4. 过滤掉黑色背景(#000000rgb(0,0,0)

为什么按颜色分组

  • 图像追踪可能产生多个不同颜色的路径区域
  • 相同颜色的路径通常属于同一个选区区域
  • 合并后可以减少路径对象数量,提高性能

创建可交互的 Fabric Path 对象

php 复制代码
pathGroupByColor.forEach((pathData) => {
 const fabricPath = new fabric.Path(pathData, {
  fill: 'transparent',
  opacity: 0.01,
  stroke: 'transparent',
  strokeWidth: 1,
    perPixelTargetFind: true, // 像素级精确检测
  })

  // 绑定交互事件...
  selectionPaths.value.push(fabricPath)
})

关键配置

  • perPixelTargetFind: true:启用像素级命中检测,提高点击精度

必须使用opacity: 0.01,因为此处是使用perPixelTargetFind: true 进行像素检测,如果说完全透明+无背景,fabric无法监测这个path的交互事件

绑定鼠标交互事件

dart 复制代码
const initPtah = {
  fill: 'white',
  opacity: 0.01,
  stroke: 'transparent',
  strokeWidth: 1,
};
const selectedPath = {
  fill: pencilBrushColor.value,
  globalCompositeOperation: 'xor',
  stroke: pencilBrushColor.value,
  opacity: 1,
};
// 点击
fabricPath.on('mousedown', (e: any) => {
  const opacity = fabricPath.get('opacity')
  const pathOption = opacity === 1 ? initPtah : selectedPath

  fabricPath.set({
    ...pathOption,
  })
  fabricCanvas.renderAll()
})

// hover:
fabricPath.on('mouseover', (e: any) => {
  const opacity = fabricPath.get('opacity')
  if (opacity === 1) return // 已选中的不处理

  fabricPath.set({
    ...selectedPath,
    opacity: 0.75, // 半透明高亮
  })
  fabricCanvas.renderAll()
})

// leave
fabricPath.on('mouseout', (e: any) => {
  const opacity = fabricPath.get('opacity')
  if (opacity === 1) return // 已选中的保持选中状态

  fabricPath.set({
    ...initPtah, // 恢复初始透明状态
  })
  fabricCanvas.renderAll()
})

组合路径并适配画布

arduino 复制代码
const scaleX = canvasWidth.value / img.width!
const scaleY = canvasHeight.value / img.height!
const scale = Math.min(scaleX, scaleY) // 保持宽高比
const group = new fabric.Group(selectionPaths.value, {
  evented: true, // 启用事件
  hasControls: false, // 隐藏控制点
  subTargetCheck: true, // 子对象检测
})

group.set({
  scaleX: scale,
  scaleY: scale,
  left: (canvasWidth.value - img.width! * scale) / 2, // 水平居中
  top: (canvasHeight.value - img.height! * scale) / 2, // 垂直居中
  lockMovementX: true, // 锁定水平移动
  lockMovementY: true, // 锁定垂直移动
})

缩放计算

  • 计算水平和垂直方向的缩放比例
  • 取较小值确保图片完整显示且保持宽高比

Group 配置

  • evented: true:允许接收鼠标事件
  • subTargetCheck: true:检测子对象(路径),确保点击路径时能触发事件
  • hasControls: false:隐藏变换控制点,防止用户误操作
  • lockMovementX/Y: true:锁定位置,防止拖动
相关推荐
程序员清洒1 小时前
Flutter for OpenHarmony:GridView — 网格布局实现
android·前端·学习·flutter·华为
VX:Fegn08951 小时前
计算机毕业设计|基于ssm + vue超市管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·课程设计
0思必得02 小时前
[Web自动化] 反爬虫
前端·爬虫·python·selenium·自动化
LawrenceLan2 小时前
Flutter 零基础入门(二十六):StatefulWidget 与状态更新 setState
开发语言·前端·flutter·dart
秋秋小事2 小时前
TypeScript 模版字面量与类型操作
前端·typescript
2401_892000523 小时前
Flutter for OpenHarmony 猫咪管家App实战 - 添加提醒实现
前端·javascript·flutter
Yolanda943 小时前
【项目经验】vue h5移动端禁止缩放
前端·javascript·vue.js
广州华水科技4 小时前
单北斗GNSS形变监测一体机在基础设施安全中的应用与技术优势
前端
EndingCoder4 小时前
案例研究:从 JavaScript 迁移到 TypeScript
开发语言·前端·javascript·性能优化·typescript
阿珊和她的猫5 小时前
React 路由:构建单页面应用的导航系统
前端·react.js·状态模式