如果一个隐私工具的第一步就是“先把敏感截图上传到我们的服务器”,用户很快就会对它失去信任。浏览器端打码的价值就在这里,它把识别、编辑和导出都尽量留在客户端。
这也是 文档打码工具、截图打码工具 和 图片打码 这类工具更合理的实现方向。真正有意思的地方不只是“它运行在浏览器里”,而是怎样把浏览器端编辑做成一个真实产品,而不是一个玩具级 canvas 演示。
为什么要坚持客户端流程
- 敏感图片不需要为了简单清理就离开设备。
- 编辑完成后可以立即导出。
- 手动编辑和自动检测可以组合使用,不必每次都等待服务端往返。
这并不代表实现会更简单,只是把复杂度转移到了渲染、内存处理和导出流程里。
第 1 步:把源图像素和可视视口分开
这个项目里的编辑器做法,是把原始图片尺寸当成唯一真实坐标系,再把界面里的可视 canvas 缩放到适合交互的大小。
this.canvas = new Canvas(el, {
width: clientWidth,
height: clientHeight,
backgroundColor: "#f2f4f8",
objectCaching: true,
controlsAboveOverlay: true,
preserveObjectStacking: true,
});然后把原图作为底层基础图像加入画布,再根据视口尺寸做适配缩放。
this.image = new FabricImage(image, {
left: image.width / 2,
top: image.height / 2,
selectable: false,
evented: false,
});这层拆分很关键,因为用户需要一个舒服的编辑视口,但最终导出的打码区域必须准确落在原始像素坐标上。
第 2 步:把编辑结果当成叠加层,而不是直接改原图
一个实用的打码编辑器,不应该每做一步操作就立刻把改动烘焙进位图。更稳妥的方式是把所有编辑对象都作为可编辑叠加层放在源图之上。
在这个代码里,手动打码框是普通的 Fabric 对象,而模糊和像素化则是引用不同渲染源的 effect patch。这样可以做到:
- 插入后仍然可以移动和缩放打码框
- 调整模糊强度时不用重建整个编辑器
- 清理自动生成的区域时不影响手动编辑结果
最后这一点对隐私工具尤其重要,因为自动检测必须是可替换、可复查的。
第 3 步:导出时重新从源图构建结果
用户点击保存时,真正可靠的实现不是直接把当前视口截图导出,而是按原始图像尺寸重新构建一个干净的导出 canvas,先放底图,再把所有叠加层重新铺上去。
const exportCanvas = new StaticCanvas(util.createCanvasElement(), {
width: this.sourceImageElement.width,
height: this.sourceImageElement.height,
});然后再把每个叠加对象克隆或重建到这个导出 canvas 上,最后生成结果文件。
const dataUrl = exportCanvas.toDataURL({
format: normalizeImageFormat(format) as ImageFormat,
quality: 1,
multiplier: 1,
});这就是“真正的编辑器”和“把编辑界面截图保存”之间的区别。
第 4 步:让下载留在本地
当导出的 data URL 生成后,应用可以直接在浏览器里触发下载。
const anchor = document.createElement("a");
anchor.href = dataUrl;
anchor.download = fileName;
anchor.click();这个流程里不需要强制经过服务端。对于隐私工具来说,这条边界本身往往比额外加一个云端 API 更有价值。
第 5 步:让自动检测成为“可编辑建议”,而不是“神秘结果”
浏览器端不代表只能手动操作。这个项目把手动标记和自动检测结合在一起,但自动生成的区域并不会变成不可见的黑盒判断,而是作为可编辑叠加层插入进画布。
这样 OCR 或条码检测给出的只是建议区域,用户仍然可以决定每个区域该保留、移动,还是直接删除。
浏览器端打码真正困难的地方
困难通常不在“怎么画一个矩形”,而在这些产品边界问题:
- 视口缩放和平移时,如何始终保留原始坐标
- 如何避免自动检测状态过期
- 导出时如何准确重建 effect patch
- 大图情况下如何保持足够流畅
- 编辑器关闭时如何清理 worker 和 canvas 资源
这些问题一旦要做成真实产品,就会马上出现。
隐私产品的一条判断标准
如果产品对外承诺的是“隐私安全编辑”,那架构也应该体现这个承诺。客户端打码不只是部署方式,它本身就是产品声明的一部分。
最好的版本不是“不惜一切代价所有东西都必须本地执行”,而是“敏感编辑默认留在本地,导出路径明确、可复查、可理解”。
