一张图片,两层油墨:用华为云码道做个双色套印小工具

发布时间:2026/10/12 6:46:26
一张图片,两层油墨:用华为云码道做个双色套印小工具
杯子的右侧暗一些球体的边缘也暗一些。把这些明暗换成红色和蓝色的小圆点原本普通的灰阶静物就有了另一种画面感。这次我想做一个能处理自己图片的小工具。选一张图把它拆成两张墨色网点版叠到同一张纸上再让其中一版稍微偏移。最后留下的图片可以拿去做文章配图或封面素材。我用华为云码道 CodeArts 代码智能体开发了「双色套印铺」。下面这张是实际运行页面左边拆开看 A、B 两版中间看合成结果右边调整参数。当前用的是程序生成的杯子、圆球和枝叶样图并没有上传照片。项目使用 Vue 3、TypeScript、Vite 和原生 Canvas 2D没有接文生图接口也不需要后台生成图片。源码已经放到 AtomGitcannonjinx / duo-print-studio。我先把两张色版摆到成品旁边同好们可以先看左侧的小样。A 版是珊瑚红杯身和球体都有较完整的明暗B 版是靛蓝内容少一些主要集中在杯口、杯身侧面和球体边缘这些较暗的位置。两张版各自保留的内容并不相同。我把它们一直放在成品旁边是想让操作有个参照。右侧有「A 版参与合成」「B 版参与合成」两个开关关闭某一版以后合成时就跳过它但左边仍保留该版的小样。这样我们能分清楚“这张版没有内容”和“这张版暂时没有参与合成”。首版只提供两套墨色珊瑚红与靛蓝、赭黄与深墨。网点间距也只有 6、10、16 三档。我没有继续加色彩曲线、图层混合面板和几十种滤镜想先把这几个参数做得容易理解。其中间距的单位是最终成品的像素。页面缩小以后点看着更小但不会因为浏览器窗口变窄就重新生成一批更密的点。图案的尺度应当由我们选的参数决定。我给码道的要求先把一张图片处理好我提交给码道的要求开头就写了「分版、叠印、错位、保存」。用户上传自己的图片或者先玩内置样图调整后保存一张 PNG。没有账户系统也没有素材商店。这里我特意写清了几件小事打开页面就要有样图图片要完整放进纸面不能为了铺满画面裁掉边缘A、B 两版得各有真实内容下载只保留成品不把网页控件一起带走。这些要求比一句“做得复古一点”具体。比如“完整放图”落到实现上就是等比缩放并留白“看懂两张版”就需要单独的色版预览而不仅是中间一张合成图。接下来码道把开发拆成项目骨架、纯函数算法、内置样图、图片输入输出、Vue 组件和状态管理等任务。初始化阶段包含package.json、Vite、TypeScript 和 Vitest 配置算法则分成fit-image、grayscale、halftone、compose几个模块。我觉得这种拆法对后续检查很有用。成品颜色不对就看分版和合成图片被裁掉就查缩放点阵方向不对就找网格坐标。我们不必在一个塞满所有逻辑的页面文件里来回翻。浅色交给一版暗部再加另一版要解释这张图怎么来的先不用讨论复杂的印刷知识。程序把图像变成一组灰度深浅值白色记为 0黑色记为 1中间的灰色落在两者之间。处理透明图片时程序先把图片合成到白底再读取像素。否则一个完全透明、RGB 却为黑的像素就可能被误当成需要印很多墨的暗部。这里的白底只用于取样最终成品仍然使用浅纸色背景。随后每个采样单元用 4×4 个固定采样点估计平均深浅再计算两张色版各自的网点强度。下面是仓库src/core/halftone.ts中的实际函数exportfunctionintensityA(depth:number):number{returnclamp01(2*depth)}exportfunctionintensityB(depth:number):number{returnclamp01(2*depth-1)}exportfunctionradiusFromIntensity(intensity:number,spacing:number):number{if(intensity0)return0return(spacing/2)*Math.sqrt(intensity)}clamp01把结果限制在 01。A 版先随深浅增加强度达到上限后不再增加B 版要等深浅超过 0.5 才开始出点。所以在浅灰区域我们主要看到珊瑚红暗部才逐渐加入靛蓝。拿五个输入代入可以把规则看得更清楚。下表是函数的计算关系不是性能实测仓库的grayscale.test.ts也用这五组值检查了两版强度。灰度深浅A 版强度B 版强度0000.250.500.5100.7510.5111有一点容易说错这里的「强度 1」不等于墨色已经铺满整个方格。圆点半径最大为间距的一半一个圆占对应方格的面积最多是π/4约 78.5%。因此纯黑输入会得到两张最大网点版并不要求结果成为没有空隙的实心黑块。灰度计算使用的是 sRGB 通道加权近似叠印也在 RGB 画布中完成。我把它当作一种屏幕图像风格没有把它叫作印刷级色彩模拟。真实油墨、纸张吸收和印前色彩管理并不在这个小项目的范围里。转动的是网格挪动的是整张色版左侧标签写着 A 版方向 0°、B 版方向 45°。这个 45° 很关键但它不表示把杯子也旋转了。程序改变的是取样网格的方向。每个网格点仍然回到原图对应的位置取样所以杯子保持原来的朝向只有网点的排列方向不同。仓库中的坐标变换是exportfunctiongridToCanvas(u:number,v:number,theta:number):{x:number;y:number}{constcosMath.cos(theta)constsinMath.sin(theta)return{x:CENTER_Xu*cos-v*sin,y:CENTER_Yu*sinv*cos,}}等两张透明色版画好以后才轮到「错位量」生效。A 版保持原位B 版整体移动水平位移范围是 012 px垂直位移固定为水平的一半。比如把水平设成 8垂直就为 4设成 7垂直就是 3.5不会取整成 4。src/core/compose.ts里对应的合成片段很短ctx.globalCompositeOperationmultiplyif(options.enableAplateACanvas){ctx.drawImage(plateACanvas,0,0)}if(options.enableBplateBCanvas){constoyoffsetToY(options.offsetX)ctx.drawImage(plateBCanvas,options.offsetX,oy)}ctx.globalCompositeOperationsource-over执行这段代码之前画布已经铺上纸色。multiply会让重叠区域变暗具体合成语义可以对照 MDN 的 Canvas 合成模式说明。最后恢复source-over避免这次设置影响后续绘制。那么为什么要先生成色版再移动它因为我想看到的是整张版的偏移。如果移动滑杆时又去原图的另一块位置取样版上的内容也跟着变了就很难观察同一组轮廓错开了多少。「重新对齐」只把位移清零不重置墨色、图片或间距。读者朋友们自己运行时可以保持其他参数不变只比较 0 和 8 的差别再单独改网点粗细。这样更容易分辨眼前的变化来自哪里。当前页面还多了一个「演示错位动画」按钮仓库后续提交5ae4846增加了这个入口。它通过改变 B 版位移演示错位结束时恢复原来的参数这是网页内的演示不是 GIF 或视频导出。前面的运行截图处于水平、垂直都为 0 的状态没有展示偏移 8 的画面。下载时先留住当前那张纸我给输出定的是 1200×1500纸面四周预留 80 px图片在内部区域等比完整放置。横图与竖图会留下不同的空白但不会为了填满 4:5 画布裁掉主体。留白也给网点边缘和轻微错位留出了空间。主预览与导出使用同一张固定尺寸母版。保存时src/core/export-png.ts先建立另一张同尺寸画布把母版复制过去再编码 PNGexportfunctionsnapshotCanvas(source:HTMLCanvasElement):HTMLCanvasElement{constcanvasdocument.createElement(canvas)canvas.widthOUTPUT_WIDTHcanvas.heightOUTPUT_HEIGHTconstctxcanvas.getContext(2d)if(!ctx)thrownewExportPngError(无法创建导出画布,encode-failed)ctx.drawImage(source,0,0)returncanvas}这样编码拿到的是一份已经复制好的画面不会一边编码一边又被滑杆改动。当前控制器还有一个行为需要说明如果编码期间换成了另一张图片它会取消旧图片的这次下载只调整同一张图的参数则仍使用点击时的快照与文件名参数。这部分代码提供了 PNG 编码超时、空结果和异常处理。输出目标是纸面与启用的色版不包含标题、按钮和左右小样。不过代码里定义了 1200×1500还不能替代下载后重新打开文件的检查。这项真实文件验证仍需补做。78 项测试通过之后还要看哪些地方码道的执行截图记录了 Vitest 测试、类型检查和生产构建。交付汇总报告 6 个测试文件、78 项测试通过仓库docs/verification.md也保留了相同记录。这些检查主要覆盖灰度与透明度、分版公式、网格坐标、等比缩放、合成调用和文件格式判断。它们能帮我们确认规则有没有写错但测试环境中的 Canvas 使用了模拟上下文合成测试检查的是调用顺序和参数不是真实输出的每个像素。后面的实际运行图补上了桌面样图这一环杯子、球体、枝叶能显示A/B 两版与成品能同屏查看。但真实照片上传、带方向信息的 JPEG、快速切图、手机布局以及 PNG 下载后的尺寸与内容还需要各自验证。我不会把“78 项通过”扩大成“所有浏览器和图片都测过”。本文核对了开发截图与仓库代码没有另行重跑测试。后续新增的演示按钮也不能直接沿用初版测试记录当作它的验证结果。把代码和记录一起留下开发会话里还保留了创建 AtomGit 公开仓库、添加远程并推送的过程仓库名为duo-print-studio。同学们要在本地运行可以克隆仓库后执行gitclone https://atomgit.com/cannonjinx/duo-print-studio.gitcdduo-print-studionpminstallnpmrun dev开发服务地址以终端实际输出为准。想继续看实现可以先读halftone.ts和compose.ts想核对检查范围再看docs/verification.md。我暂时把这个工具停在两套墨色、三档网点和一个偏移方向。几项设置放在一起已经足够看清两张色版怎样组成一张图。AtomGit · 双色套印铺 https://atomgit.com/cannonjinx/duo-print-studio。