同版式截图批量打码:用华为云码道做一个本地小工具!

文章目录

码道

客服把订单截图转给技术支持核对异常,是件高频又琐碎的事。商品、金额、订单状态得留着,收件人的姓名、电话、地址不能一起发出去。一张一张用微信打码,图少的时候没问题,但同一天收到几十张格式一样的订单截图时,每张都要在相同位置画几个黑框,重复操作很磨人。

我这次用华为云码道做了个叫「遮一下」的小工具,专门处理这件事。它是一个下载到电脑上就能打开的网页,不用装软件、不用登录。一次导入多张同版式截图,在第一张上框住要遮的位置,存成模板,一键应用到全部,翻看一遍确认没漏,再批量导出。图片从头到尾只在浏览器里处理,不上传任何服务器。

开发用的是桌面版码道。我把需求发过去,它生成了页面、图片处理代码和批量逻辑,也写了 README 和 MIT 开源协议。中间碰到示例图片没直接生成的小问题,用浏览器绘图代码补上了。最后我拿三张同版式的虚构订单实测:在第一张上圈好姓名、手机、地址、卡号四个位置,存为模板后应用到三张,逐张翻看遮挡位置一致,全部导出成功。

公开仓库:遮一下 · 本地截图打码工具(AtomGit)

完整仓库地址(可复制):https://atomgit.com/qq_40202349/local-redact

一、先聊聊这次做的截图打码工具

1.1 用在什么场景

客服转单是最典型的场景。订单异常时,客服把截图发给技术支持,有时还要抄送给财务或归档。同一系统的订单页面版式固定,每张截图里收货人、手机号、地址、银行卡号的位置完全一样。要遮的就是这几行,但每张都要手动框选一遍。

「遮一下」针对这个场景做了批量打码:一次导入多张同版式截图,在第一张上框选要遮的位置,保存为模板,一键应用到全部图片,翻看检查后批量导出 PNG。写操作教程、公开报错截图时如果也有同版式批量处理的需求,同样适用。

1.2 这一版包含哪些功能

功能 当前实现
导入图片 选择或拖入 PNG、JPEG、WebP,支持多选,包含类型检查和读取失败提示
矩形遮挡 用不透明黑色矩形覆盖选区,可以连续添加多处
修改选区记录 撤销上一步,或清空当前图片的全部遮挡
批量打码 在第一张上框选 → 存为模板 → 应用到全部 → 翻看检查
导出图片 导出当前一张,或全部导出;按原图尺寸合成 PNG
示例与交付 内置 3 张同版式虚构订单示例,附 README 和 MIT 协议

1.3 这次重点检查什么

我主要检查三件事:一是页面能否正常加载多张示例并翻看,二是在第一张上框选后存为模板、应用到全部,其他图片的遮挡位置是否一致,三是批量导出能否为每张图片生成独立的 PNG。"本地处理"则沿图片读取和下载代码核对,确认没有图片上传接口。

后面的效果验证会按这几个目标逐项看结果。

1.4 当前版本的范围

现在一次处理一组同版式截图,遮哪里由人在第一张上决定,没有自动识别。模板里的遮挡位置按原图坐标保存,应用到其他图片时会裁剪到各自边界------同版式时通常不变,尺寸不同时可能需要单独调整。导出后仍要检查是否漏遮。桌面浏览器的完整流程已有运行记录,移动端和多浏览器回归还没测完。

二、环境与选型:桌面版码道,原生网页运行

这次开发在 Windows 上进行,使用桌面版华为云码道。

项目 本次使用方式
开发工具 桌面版华为云码道,任务名为「本地截图打码工具开发」
本地项目 C:\Users\86155\Desktop\AI征文项目\截图打码工具
实现方式 原生 HTML、CSS、JavaScript
运行方式 下载源码后,双击 index.html,由浏览器打开
源码托管 AtomGit 公开仓库 qq_40202349/local-redact

文件选择、图片解码、Canvas 绘制和下载都可以在浏览器里完成,所以这一版没有引入前端框架,也不需要启动后端。运行主体是 index.htmlstyle.cssscript.js,拿到源码就能试。

我的 AtomGit 账号为 qq_40202349,对应仓库是 local-redact。在 AtomGit 工作台里,可以同时看到该账号、华为云码道 Agent 入口和关联仓库。

三、开发(一):把需求和验收方法一起交给码道

开始前,我把保存目录、功能范围和检查动作写在同一份指令里。除了单张打码的基本功能,还特别说明了批量需求:一次导入多张同版式截图,在第一张上框选后可以保存为模板,一键应用到全部,再批量导出。

下面是当时交给码道的指令,仅调整了分行:

text 复制代码
请开发「遮一下------本地截图打码工具」,并实际创建项目文件。

项目根目录:
C:\Users\86155\Desktop\AI征文项目\截图打码工具

所有源码、文档和测试文件都保存在上述目录。
目录不存在则创建,保留其他项目的已有文件。

功能:
1. 导入 PNG、JPEG、WebP 图片,支持一次多选。
2. 在图片上拖动框选,用完全不透明的黑色矩形遮挡选区。
3. 支持撤销上一步、清空当前图片的遮挡。
4. 批量打码:在第一张上框选后存为模板,一键应用到全部图片。
5. 翻看上一张/下一张,逐张检查遮挡效果。
6. 导出当前一张,或全部导出;按原图分辨率合成 PNG,遮挡必须真正写入。
7. 提供多张同版式虚构信息示例图片,方便演示批量打码。

实现要求:
- 使用原生 HTML、CSS、JavaScript,无后端、无外部依赖。
- 双击 index.html 即可运行。
- 图片只在浏览器内存中处理,不上传。
- 正确处理图片缩放、反向框选和边界限制。
- 对文件类型错误、图片损坏等情况给出提示。
- 界面简洁,适配手机;使用暖灰背景、深色文字和少量青柠色强调。

同时创建:
- README.md:应用介绍、运行方法、使用步骤、隐私说明。
- LICENSE:MIT 开源协议。

完成后检查:
导入多张图片 → 在第一张上框选遮挡 → 存为模板 → 应用到全部 → 翻看检查 → 全部导出。
确认每张导出的图片都包含遮挡,尺寸与原图一致。

请实际写入文件,不要只在聊天中展示代码。
最后报告保存路径、文件清单、运行方法和验证结果。
没有实际验证的部分请明确说明。

末尾还要求码道说明没有验证的部分。这样拿到完成报告后,可以知道哪些已经检查,哪些还需要自己到浏览器里操作。

四、开发(二):检查生成文件和完成报告

码道最后交付了六个文件:

text 复制代码
截图打码工具/
├── index.html             页面结构、工具栏和批量栏
├── style.css              页面样式与小屏布局
├── script.js              图片加载、框选、模板、批量导出
├── generate-sample.html   示例图片生成器(3 张同版式订单)
├── README.md              运行方法、操作步骤和隐私说明
└── LICENSE                MIT 开源协议

运行主体三个文件合计约 25 KiB。generate-sample.html 用来生成 3 张同版式示例图片,README 写了运行方法和批量打码步骤,LICENSE 使用 MIT 协议。

桌面版码道的登录账号是 hw83659151。展开账号面板后,账号和「本地截图打码工具开发」任务的完成报告可以同时看到:

报告列出的验证以静态检查为主,包括文件是否写入、页面元素 ID 与脚本引用能否对应,以及加载、框选、撤销、导出的主要逻辑。浏览器点击流和像素级自动化没有执行。

还有一项需要留意:目录里没有直接生成 sample.png。项目提供了浏览器内绘制示例的办法,这部分的环境报错和实现放到第八节说明。

五、开发(三):先在浏览器里把页面打开

文件生成后,我双击项目里的 index.html。页面顶部是导入、加载示例、撤销、清空、导出当前和全部导出按钮,下面一行是批量栏:上一张、计数器、下一张、存为模板、应用到全部。中间是画布,下方显示图片尺寸和操作状态。

接着点击「加载示例(3 张)」。三张虚构订单图片依次加载,计数器显示 1 / 3,状态栏提示可以在第一张上框选后存为模板。第一节的图 1 就是加载后的界面。

到这里,页面和批量示例加载已经有了实际结果。接下来先看框选、模板和导出的代码,完整的遮挡与导出效果集中放在第九节对照。

六、实现逻辑(一):图片坐标、框选和批量状态

页面现在要同时管理多张图片,每张图片各自保存原图和遮挡记录。图片数据的流向如下:

text 复制代码
导入:本地 File → 临时 Object URL → Image → images 数组
编辑:鼠标或触摸事件 → 原图坐标 → 当前图片的 masks → redraw()
模板:当前 masks → templateMasks → 应用到每张图片的 masks
导出:逐张合成 originalImage + masks → 新 Canvas → PNG Blob → 下载

6.1 多张图片和遮挡记录各自保存

script.js 中的主要状态:

状态 保存什么 用在哪里
images 图片数组,每项为 {img, name, masks} 翻看、模板应用、批量导出
currentIndex 当前显示的图片索引 切换、导出当前
templateMasks 保存的模板遮挡(原图坐标) 应用到全部
drawing 当前是否正在拖动 控制选区预览和结束操作
startPtcurrentPt 本次拖动的起点和当前位置 计算预览矩形

每张图片的 masks 是独立的数组,存的是 {x, y, w, h} 原图坐标。redraw() 每次先画当前图片的原图,再画出它的遮挡。正在拖动的选区也会显示,但要等到松开鼠标后才加入当前图片的 masks

切换图片时,showImage(index) 把画布尺寸设为新图的原始尺寸,再重绘。每张图的遮挡互不影响。

6.2 显示尺寸和原图尺寸,需要换算一次

Canvas 有内部像素尺寸,也有页面上的显示尺寸。本项目把内部尺寸设为图片的 naturalWidthnaturalHeight,CSS 通过 max-width: 100% 控制它在页面上的显示宽度。

假设原图宽 900 像素,页面中显示为 450 像素,比例就是 2。鼠标距离图片左边缘 100 个页面像素,换算到原图中应该是 200 像素。如果直接保存 100,导出时遮挡位置就会偏移。

源码通过 getBoundingClientRect() 取得显示区域。取出鼠标或触摸的 cxcy 后,使用下面的换算:

javascript 复制代码
var rect = canvas.getBoundingClientRect();
var scaleX = canvas.width / rect.width;
var scaleY = canvas.height / rect.height;

var x = (cx - rect.left) * scaleX;
var y = (cy - rect.top) * scaleY;
x = Math.max(0, Math.min(canvas.width, x));
y = Math.max(0, Math.min(canvas.height, y));

先减去画布相对视口的位置,再乘以缩放比例,得到原图坐标。后面的两行把坐标限制在画布宽高之内。保存到 masks 中的值都来自这套原图坐标,预览与导出使用同一套位置数据。

6.3 反向框选和松手提交怎么处理

用户可以从右下往左上拖动,矩形的宽高仍然需要是正数。代码用两个端点中较小的坐标作为左上角,用差值的绝对值计算宽高:

javascript 复制代码
function rectFromPoints(a, b) {
  var x = Math.min(a.x, b.x);
  var y = Math.min(a.y, b.y);
  var w = Math.abs(b.x - a.x);
  var h = Math.abs(b.y - a.y);
  return { x: x, y: y, w: w, h: h };
}

源码只接收宽、高都大于 1 个原图像素的矩形。鼠标的 mousemovemouseup 监听在 window 上,因此鼠标移出画布但仍在当前页面时,也能继续更新并结束选区。

七、实现逻辑(二):模板、批量导出和本地处理

7.1 存为模板,再应用到全部

模板的本质是把当前图片的遮挡位置复制一份,保存到 templateMasks。应用时,遍历每张图片,把模板里的每个矩形裁剪到该图的边界后写入它的 masks

javascript 复制代码
function applyTemplateToAll() {
  if (!templateMasks || images.length === 0) return;
  for (var i = 0; i < images.length; i++) {
    var entry = images[i];
    var w = entry.img.naturalWidth;
    var h = entry.img.naturalHeight;
    var copied = [];
    for (var j = 0; j < templateMasks.length; j++) {
      var r = clampRectTo(templateMasks[j], w, h);
      if (r.w > 0 && r.h > 0) copied.push(r);
    }
    entry.masks = copied;
  }
  redraw();
  updateButtons();
}

同版式截图尺寸一致,裁剪后位置不变。如果某张图尺寸不同,clampRectTo 会把超出边界的部分裁掉,不会画到图片外面。

7.2 导出时重新合成图片

单张导出和批量导出共用同一个合成函数。新建一张离屏 Canvas,宽高取自原图,先画原图,再叠加遮挡:

javascript 复制代码
function composePNG(entry, callback) {
  var out = document.createElement("canvas");
  out.width = entry.img.naturalWidth;
  out.height = entry.img.naturalHeight;
  var octx = out.getContext("2d");
  octx.drawImage(entry.img, 0, 0, out.width, out.height);
  octx.fillStyle = "#000000";
  for (var i = 0; i < entry.masks.length; i++) {
    var m = entry.masks[i];
    octx.fillRect(m.x, m.y, m.w, m.h);
  }
  out.toBlob(function (blob) { callback(blob, out.width, out.height); }, "image/png");
}

批量导出时依次合成每张图片,相邻下载间隔 350ms,避免浏览器拦截连续下载。文件名包含原始文件名和序号,方便对应。

7.3 文件读取与输入检查

"本地处理"也要看代码。导入时,文件来自用户选择的 File 对象,或者拖拽事件中的文件列表。程序使用 URL.createObjectURL(file) 创建临时引用,让 Image 读取,加载成功或失败后都会释放引用。导出时,图片经 Canvas 合成,再由浏览器下载。

当前源码没有图片上传接口,也没有引入远程第三方库或统计脚本。处理图片所需的逻辑都在本地脚本中。

检查点 当前源码的处理 作用
文件格式 检查 PNG、JPEG、WebP 的 MIME 或扩展名 先筛掉明显不支持的输入
图片读取失败 img.onerror 提示读取失败;加载时检查自然宽高 处理损坏图片和解码失败
文件名与状态文案 通过 textContent 写入页面 按文本显示,不把文件名解析为 HTML
重复选择同一文件 处理选择事件后清空文件输入值 可以再次选择同一组图片
空状态操作 未加载图片时禁用导出;没有遮挡时禁用撤销和清空 让按钮状态对应当前数据

八、踩坑:示例图和 README 各卡了一次

8.1 sample.png 没直接生成,改在浏览器里画

生成过程遇到的实际问题,是 Agent 没有直接把二进制 sample.png 写出来。完成报告中提到了运行环境限制:

text 复制代码
Sandbox exe is not available

报告说明,当时无法通过命令环境生成 PNG,也没有执行浏览器点击流和像素级自动化测试。项目改用浏览器生成示例:工具里可以即时绘图,独立的 generate-sample.html 页面则可以下载示例文件。

报告列出了已生成文件、功能实现、静态检查结果,以及没有自动执行的验证项目。

点击「加载示例」后,程序在浏览器中创建 3 个 900 × 560 的 Canvas,分别画上不同客户信息的虚构订单,版式完全一致。这样即使没有独立的 PNG 文件也可以演示批量打码。

8.2 上传 README 时遇到同名冲突

最后通过网页上传六个文件时,服务端返回 HTTP 400。响应里有两行报错:

text 复制代码
Path or parent path already exist
README.md already exist.

仓库已经有一个 README.md,这次批量上传无法再创建同名文件。我把 README 从上传列表移除,先提交其余五个文件,再进入仓库已有 README 的编辑页面,写入本地内容并提交。

上传后,重新下载仓库,对六个文件逐一计算 SHA256,与本地项目对照。为了比较原始字节,检出时关闭自动换行转换。下面是匿名克隆命令,运行前确认当前目录没有同名的 local-redact 文件夹:

powershell 复制代码
git -c credential.helper= -c core.autocrlf=false clone --depth 1 https://atomgit.com/qq_40202349/local-redact.git local-redact

本次校验结果对应提交 067d7279fcbcb8b47a81d6eb22c1ca91ca896036

文件 本地文件字节数 下载版本 SHA256 对照
index.html 2,493 一致
style.css 4,072 一致
script.js 18,932 一致
generate-sample.html 4,221 一致
README.md 4,511 一致
LICENSE 1,094 一致

六个文件的哈希全部一致,公开仓库中的源码、README 和 MIT 协议与本地版本相符。匿名克隆也验证了未登录时可以获取源码。

对试用者来说,直接在仓库页面下载 ZIP、解压、双击 index.html 即可。想先熟悉操作,可以点击「加载示例(3 张)」;也可以通过「导入图片」选择自己的 PNG、JPEG 或 WebP 文件。

九、效果验证:从框选到批量导出

这次实际操作使用内置的 3 张同版式订单示例,每张 900 × 560。下面对照开头列出的检查目标,看页面和导出文件各自的结果。

9.1 在第一张上框选并存为模板

在第一张示例图片上拖动框选,遮住收货人、手机号码、收货地址、银行卡号四行。松开鼠标后,选区成为黑色矩形,状态栏提示当前图片共有 4 处遮挡。接着点「存为模板」,模板状态显示「模板已保存:4 处」。

在第一张虚构订单上框选姓名、手机、地址、卡号四行,存为模板。批量栏显示「1 / 3」和「模板已保存:4 处」。

9.2 应用到全部,翻看其他图片

点「应用到全部」,模板中的 4 处遮挡一次性套用到 3 张图片。翻到第二张,状态栏显示「共有 4 处遮挡」,遮挡位置和第一张完全一致------姓名、手机、地址、卡号都被遮住了。

第二张虚构订单自动套用模板后,遮挡位置与第一张一致。客户信息不同(李芳华 / 139-0000-5678),但被遮住的位置相同。

第三张同样如此。如果某张图需要补遮或调整,可以单独框选或撤销,不影响其他图片。

9.3 批量导出

确认三张图片的遮挡都正确后,点「全部导出」。工具依次合成每张图片,状态栏显示导出进度。完成后提示「共 3 张,全部成功」。下载目录里出现 3 个 PNG 文件,文件名包含原始图片名和序号。

批量导出完成,3 张图片全部成功导出。

9.4 本次验证范围

本次手动运行和源码核对分别做到以下范围:

项目 检查方式 本次记录
页面运行 桌面浏览器打开本地页面 页面、工具栏和批量栏正常显示
批量示例 点击加载示例(3 张) 生成并加载 3 张 900 × 560 同版式图片
框选与模板 在第一张上框选 4 处,存为模板 模板保存成功,显示 4 处
模板应用 点应用到全部,翻看第二、三张 遮挡位置一致,各 4 处
批量导出 点全部导出 3 张 PNG 全部生成,文件名含序号
撤销与单张导出 撤销一处后导出当前 撤销后剩余 3 处,单张导出成功
移动端、多浏览器与像素级自动化 未执行完整验证 后续补充

十、使用判断:码道帮到了哪里,工具有什么用

10.1 从需求到可运行文件

这次码道把页面、脚本和配套文档一起生成了。批量打码的需求------多图管理、模板保存、一键应用、批量导出------都能在代码中找到对应实现。示例文件没有直接生成时,浏览器绘图的备用方式也让项目能够继续试用。

对这类范围明确的小项目,Agent 的帮助很具体:需求交出去之后,拿到的是可以打开的项目文件,再沿着函数检查实现。页面、脚本和 README 在同一次任务里交付,功能说明也能对照着代码看。

不过,完成报告里没有运行过的测试,仍要补做。这次我还要打开页面操作、翻看每张图片的遮挡、查看下载的 PNG,再核对公开仓库与本地文件是否一致。这些工作完成后,才有文章里的运行截图和文件校验结果。

10.2 工具的价值放在具体工作里看

这个工具的用处不在"能画黑框"------很多软件都能做。它的价值在于:面对一组同版式截图,把"每张都要重复框选"变成"只框一次,其余自动套用"。客服一天处理几十张订单截图时,省下来的是大量重复操作,而不是单张打码这一步本身。

再加上图片全程在本地处理,不经过服务器,对涉及客户信息的场景来说,这条数据处理路径本身就减少了一道外发风险。团队可以把静态文件分发下去试用,不用额外维护图片上传和存储服务。

如果继续做企业定制,我会考虑把编辑入口接到客服工单的附件窗口:员工选好截图后就地遮挡,再提交到原有附件流程。批量打码、模板管理和内部部署都需要结合具体系统适配,这些工作才可能形成收费服务。

十一、不足与下一步

这次先把一个可以下载运行的批量版本做了出来,后面还有几项明确的工作:

  1. 补大图的输入限制。 当前没有文件大小或像素总量上限,图片过大会增加解码和 Canvas 的内存占用。需要用不同尺寸的图片测试,再决定限制和提示方式。
  2. 补交互回归。 撤销、清空、反向框选已经核对过源码,还要逐项操作,尤其是缩放后框选、撤销后再次导出,以及触摸结束和取消。
  3. 扩大实际测试范围。 目前留下的是桌面浏览器操作内置示例的记录,还需要用不同格式的本地 PNG、JPEG、WebP 文件检查导入,并补移动端、多浏览器和像素级验证。
  4. 考虑模板的持久化。 当前模板存在内存里,刷新页面就丢了。如果客服每天处理同版式截图,可以把模板存到 localStorage,下次打开直接复用。

下一步我会先把这些测试补齐。自动识别敏感字段、工单集成和企业试点,可以等基础操作稳定、有人实际试用之后,再按需求决定。

资料与源码

相关推荐
qq_199886871 小时前
第8板块·第1节:主机与设备内存管理基础与统一内存
c++·人工智能·gpu算力·cuda
麻花地1 小时前
Jev 模型深度解读:不生成文字的 System One 决策模型
人工智能·深度学习
SelectDB技术团队1 小时前
StarRocks 迁移至 Apache Doris 完整指南:三步完成结构、数据与业务平滑切换
数据库·人工智能·sql·clickhouse·apache doris·selectdb·湖仓架构升级
ysu_03141 小时前
【2026】AI Agent 工程化落地六大挑战:从路径坍缩到成本失控
人工智能·ai·rag·ai agent·大模型应用·langgraph·agent工程化
aneasystone本尊1 小时前
学习大模型推理的显存优化:PagedAttention 与前缀缓存
人工智能
VIP_CQCRE1 小时前
Visual Studio 也能丝滑接入大模型:用 Ace Data Cloud + LMLocal 打造 AI 编程体验
ai·大模型·visual studio·ace data cloud·lmlocal
daxiangxm1 小时前
【趣味休息】“围住小猫在线玩”-“困住小猫”,又回来了
数据结构·人工智能·vscode·github·php
zhangfeng11331 小时前
Git 里一个分支下可以有任意多个版本
人工智能·ai编程
子非鱼eva1 小时前
昇腾开源仓Issue分析解答-Ascend精选(十六)·2026-09增量补采
人工智能·ai·gitcode