ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从照片到材料清单:我用华为云码道开发「拼豆专家」

从照片到材料清单:我用华为云码道开发「拼豆专家」

一键开通华为云码道 CodeArts 代码智能体:https://developer.huaweicloud.com/codeartsco.html?source=dmzntgwatomgit1&sourcead=dmzntgwatomgithd

把一张照片变成像素风图片,和把它变成一张能照着制作的拼豆图纸,我觉得是两件事。

前者主要看画面,后者还得回答一些具体问题:一共多少格?每种颜色要准备多少颗?透明背景是不是留空?如果增加细节,材料量会增加多少?

这次,我想用华为云码道 CodeArts 代码智能体做一个偏实用的小工具,名字叫「拼豆专家」。输入一张本地照片或插画,裁切主体、选择网格密度和颜色上限,再生成拼豆数据。三维预览负责帮助我们理解颗粒外观,平面图纸和材料清单负责辅助制作。

我没有把它做成素材商城,也没有加入账号和云端作品库。原图处理放在浏览器里,先把“自己的图片怎么变成图纸”这条流程做清楚。

我给码道的需求,先从用豆数量想起

最初的需求比较完整:选择照片、裁切、限色、生成网格、查看拼豆和图纸,再导出材料清单与项目文件。我还计划加入画笔、取色、擦除和撤销,允许用户修正局部颜色。

这里我专门写了一个约束:三维拼豆、平面图纸和材料统计必须使用同一份网格。不能预览是一套颜色,下载后又换成另一套,更不能为了让界面好看,先写几个示例数量放在材料栏里。

技术方案也按这个思路拆开:Vue 3 管理页面与状态,Canvas 2D 绘制图纸,Three.js 表达拼豆颗粒,图像采样和颜色处理放到 Web Worker 中。开发、类型检查和测试则使用 Vite、TypeScript 与 Vitest。

码道的任务记录里,最先写的是types.ts、constants.ts、colors.ts和grid.ts,随后才继续统计、历史记录、分页和尺寸等模块。

对这个项目来说,先把网格定义清楚很有必要。每个格子究竟是空白,还是某一种颜色的豆子,后面的所有功能都得围绕这个答案工作。

网格加密,增加的不只是画面细节

项目配置中提供了 64、96、128、160 四档方形网格,默认使用 64 × 64。颜色上限是 8、16、24、32,默认上限为 24 色。

同好们可以先算一笔简单的账。下面是四档网格的容量,直接按边长相乘得到,不是某张照片的实际用豆统计:

网格规格总格数相对 64 × 64 的容量
64 × 644,0961 倍
96 × 969,2162.25 倍
128 × 12816,3844 倍
160 × 16025,6006.25 倍

如果所有格子都放豆子,160 × 160 就对应 25,600 颗;有透明留空时,实际数量会减少。所以我希望用户选择高密度前,先看清材料量,而不只是觉得“更大就更精细”。至于某一档需要处理多久、浏览器是否流畅,还得另外测,不能从格数直接推断。

颜色上限也不等于实际使用颜色数。选了 24 色,只是允许算法最多使用 24 种代表色。如果原图本身颜色很少,结果不应该为了凑数硬塞进 24 种。

这两个参数分别影响空间细节和颜色区分。先选一个能认出主体的网格,再调整颜色数量,比一开始把所有参数都拉满,更容易看出每一步改变了什么。

照片怎么变成一格一格的颜色

当前源码把这件事分成采样、整理调色板和映射网格几个步骤。

先把裁切区域划分成网格。一个格子通常覆盖原图中的多个像素,程序按覆盖面积计算平均颜色,而不是随便取其中一个像素。sampling.ts里先把 sRGB 通道值转换到线性空间,再参与平均;透明度则作为权重一起处理。

保留透明背景时,覆盖率低于设定阈值的格子会被标为空白。选择合成背景时,再把透明部分与指定底色合成。这里没有接自动抠图模型,透明处理也不等于能识别并删除普通照片的背景。

接下来,quantization.ts按颜色分布做加权切分,得到受颜色上限约束的调色板,再在 OKLab 空间里为每个非空格寻找距离最近的调色板颜色。最后清理没有使用的颜色,生成当前项目的色号。

这条处理流程放在image.worker.ts中,处理阶段会把采样、整理颜色和映射进度发回页面。它提供了把图像计算与界面操作分开的实现方式,但高密度图片的实际响应时间仍要通过运行验证。

开发过程中,这里还修过一次很具体的数据读取问题。码道交付记录说明,sampleGrid最初错误地解构了宽高,后来改为从settings.size取得方形网格边长。当前src/domain/sampling.ts中对应的代码是:

const{size,crop,transparency,alphaThreshold,background}=settingsconstwidth=sizeconstheight=sizeconsttotal=width*height

这几行不复杂,却决定了后面分配多少个采样位置。界面选择的是一个size,计算函数就得按这份数据约定读取,不能在另一个模块里想当然地改成width和height。

材料表里的每一个数字,都要能对回网格

拼豆数据用Uint8Array保存。当前实现里,0 表示空格,1~32 表示调色板索引。材料统计遍历这些格子,累计各个颜色出现的次数,再算出已填充数量、空格数和实际使用颜色数。

我希望这里的数量能互相核对。源码src/domain/statistics.ts中保留了这样一个检查函数:

exportfunctionverifyStatisticsInvariants(stats:GridStatistics):string|null{if(stats.capacity!==stats.filled+stats.empty)return'capacity != filled + empty'letsum=0constseen=newSet<number>()for(constmofstats.materials){if(seen.has(m.index))return`重复色号${m.code}`seen.add(m.index)sum+=m.count}if(sum!==stats.filled)return'sum(materials.count) != filled'returnnull}

它检查两笔总账:已填充加空白应当等于网格容量,各颜色的数量合计应当等于已填充格数,同时不能出现重复的颜色索引。这样的检查不能证明图案好看,却能帮助我们发现统计数据对不上的问题。

材料 CSV 的实现同样从项目网格重新计算数量,输出项目名称、色号、颜色名称、HEX 和数量。这里还做了字段转义与公式前缀防护,避免用户填写的项目名被表格软件当成公式处理。

读者朋友们需要注意,项目中的C01、C02是自动生成的内部色号,不是某个拼豆品牌的商品编号。材料栏源码也保留了“自定义色号,非品牌色卡”的提示。真正购买材料时,仍要对照自己使用的实物色卡;目前不能把这张清单当成已经匹配品牌商品的采购单。

先把整张图纸看清楚,编辑功能可以往后放

最初,我把局部编辑也放进了首版。后来实际使用时,我向码道反馈了两个问题:真实拼豆预览无法显示,图纸也有问题。于是,我补充了一句更明确的要求:“不需要编辑,直接展示整张图片的图纸即可。”

做工具时很容易遇到这种情况:开始觉得画笔、吸管、撤销都应该有,真正打开以后,最先需要的却是把结果完整看见。如果主流程还看不清,增加工具按钮也帮不上忙。

因此,这轮调整的验收目标先收紧为两项:拼豆预览能正常显示,制作图纸能够完整呈现。现有修复截图记录的是排查过程,没有展示完成结果;当前可读取的仓库初版也仍保留编辑代码,所以这里不把“取消编辑”写成已经完成的修改。

预览不显示的原因,出在初始化顺序上。按修复报告的说明,BeadRenderer.rebuild()把this.beadMesh = mesh放在了填充实例的循环之后,而循环调用的appendInstance()恰好需要读取this.beadMesh。这时它还是null,所以 4,096 颗豆子的实例都被跳过了。把赋值移到循环前,才有了可以填充的目标网格。

这个问题让我印象比较深:材料栏里已经有数量,不代表场景里就一定有对应的物体。数据算出来了,还要确认它真正进入渲染对象。

图纸部分则按这份修复说明改为只读导航,移除了图纸视口的画笔、取色和擦除交互,保留缩放、平移与适应画布,并在切换视图时自动执行fit和重绘。这里简化的是图纸视口;上面的运行图停在“真实拼豆”页,顶部仍能看到编辑工具入口,并不是整个应用都取消了编辑。

三维部分的设计目标,是让每颗豆子具有短圆柱外形和中间的孔,放在规则网格上,而不是把像素方块简单换成小球。实现通过轮廓生成颗粒几何,再使用实例化网格安排位置与颜色。补充的运行图中,中央已经能看到由颗粒组成的完整人物图案,原图中的浅蓝背景和红色衣服也能对应上。

平面图纸则服务于另一件事:看行列、辨认色号。它不需要立体阴影。把这两种视图分开,才能避免“预览看着漂亮,照着做却找不到格子”的情况。

81 项测试通过,之后仍然出现了页面问题

码道的首轮交付截图记录了下面这些结果:

检查项当时的交付报告
npm test81 项通过,涉及 7 个测试文件
npm run typecheck0 个错误
npm run build构建成功,本次耗时 3.65 秒

3.65 秒是这一次构建的耗时,不是整个项目的开发时间,也不是照片转拼豆的处理时间。这些数字来自码道的交付记录,没有当作一轮独立复测结果。

同一份记录还列出了补齐downloadSafety.ts、修正采样尺寸读取、处理 TypeScript 类型问题和测试断言等工作。仓库测试覆盖了颜色转换、网格索引、采样、限色、分页、项目校验和导出等模块。

但这些检查之后,我依然遇到了真实拼豆没有显示的问题。对这个项目来说,函数输入输出正确、类型检查通过、构建成功,只覆盖了验收的一部分。图片能否生成结果、两个视图能否显示、图纸是否完整,还需要在浏览器中逐项确认。

这也影响了我后面的开发顺序:先完成从图片到可查看图纸的流程,再决定哪些编辑功能值得保留。材料清单、图纸与预览能对应上,比首版多几个工具更有用。

项目仓库:dragonbady / bead-expert。采样、颜色映射、材料统计与导出代码都在其中。对于这个手工小工具,我想先交付的是一张能读懂、数量能核对的图纸,再继续打磨颗粒质感和编辑体验。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进