杂记 › 生活随笔 › 拼豆图纸的色号是怎么挑出来的:开一次抖动,1024 格里有 150 格被改写

拼豆图纸的色号是怎么挑出来的:开一次抖动,1024 格里有 150 格被改写

挑色号用的是 redmean 加权距离而非欧氏 RGB,连三角不等式都不满足;平局只按色卡声明顺序定;开抖动是整张重算,1024 格里 150 格被改写、逐格总偏差涨 +32.16%。

挑色号这一步,得先摊开算一遍

拼豆图纸是把一张图缩成 m×n 的格子、每格填一颗固定色号的豆子,决定成品的是「给定一个 RGB 挑哪个色号」这一步。把它摊开会发现:挑色号用的不是直觉里的欧氏 RGB 色差,平局归属只由色卡声明顺序决定,勾上「抖动算法」也不是补几个格、而是整张重算。

我把 拼豆图案生成 模板里量化那几行按行切出来,在 Node v24.19.0 逐字驱动 20 组输入色,再用 headless Chrome 跑通 handleImage→generatePattern。模板本体 1109 行 / 56659 B,内置 4 套色卡,本篇取 classic 48 色与 mini 24 色两组对算。

色卡距离是 redmean 加权,不是欧氏 RGB

第 383–384 行 rmean=(r1+r2)/2,R 轴权重 2+rmean/256、G 轴恒 4、B 轴 2+(255-rmean)/256,R/B 两个权重都在 2–3 之间随 rmean 滑动,跟欧氏 RGB 不是一条度量。同一对色的距离被放大成这样:

色对redmean欧氏值比值
白↔黑764.834441.6731.732
纯绿→黑5102552(G 轴恒 4,√4)
纯红→黑403.0332551.581

20 组输入色在 classic 48 色下去重命中 13/20 种、距离相加=1173.722、均值 58.686。它还违反三角不等式:d(A,B)+d(B,C)=394.312 竟小于 d(A,C)=395.044,多出 0.733——这条函数只保序不保距离,「最近色」的距离不能当真实色差读。

平局取谁,只由色卡声明顺序决定

第 392 行是严格的 if (d<minDist),相等时保留索引更小的那个:rgb(160,160,160) 让 C-39 浅灰与 C-40 灰同距 95.979,返回 idx=38 的 C-39。取 5 组浮点精确相等的查询,把色卡声明顺序反转再问同一色,5/5 全改吐另一个色号,归属只跟着遍历顺序走;4 套卡无重复 hex(uniqHex=48/56/44/24),平局只落在格点缝隙。

开抖动不是补几个格,是整张重算

指标 关抖动 开抖动 总偏差 21968.638 29033.911 平均 21.454 28.353 最大 143.36 267.144 过百格 57 103

图 1:pp_swatches 32×32 classic 开抖动的逐格偏差账,每行按自身取值等比缩放;改写 150/1024 格、用色 23→28。

这张色块图关抖动时是 1024 格、用色 23 种;勾上抖动后,第 640–643 行对每个 alpha≥128 的格重跑 nearestColorIndex 并覆写整张 beadGrid,150/1024 格被改写、用色涨到 28、逐格总偏差 +32.16%。8/8 组真图开抖动后总偏差一律上升,涨幅 +25.08%~+33.71%。文案「色彩过渡更自然」讲的是视觉梯度,不是逐格偏差变小。

两个开关还会打架:单开「移除白色背景」时 32×32 有 empty=502/1024,与抖动同勾后 empty 回到 0、dither-refilled-empty-cells=502——第 640 行的抖动分支只重判 alpha<128、不看 bgRemove,502 个挖空的白底又被填满。

两套色卡的偏差账要分开算

指标 classic mini 距离合计 1173.722 1466.662 平均距离 58.686 73.333 色块均差 21.454 39.95 照片均差 89.599 122.518

图 2:同一批输入色、同一张图在 classic 48 色与 mini 24 色下的偏差对照;均值涨 +24.96%、命中不同 15/20。

同样 20 组输入色换到 mini 24 色卡,15/20 组落到不同 hex、距离相加 1466.662、均值 73.333,比 classic 的 58.686 高 +24.96%。最扎眼的是 rgb(128,128,128):在 classic 上是 d=0 的 C-40 灰,到 mini 被投给 d=146.12 的 M-19 土黄。色卡从 48 缩到 24,照片用色从 11 掉到 9,平均偏差却涨到 122.518。

图纸像素只由格数决定,字节跟内容走

导出画布每格固定 20 px,无 DPI 或倍图档位:32×32 出 640×802 / 58794 B、200×200 出 4000×4132 / 497624 B,4000 px 就是 200×20 的结果;图例高度用 legendPad=20 公式反推 4 组 IHDR 全命中。开关动字节不动尺寸:关掉每格色号标注从 58794 B 掉到 40761 B、省 18033 B;裁出界的全空图案仍能导出 640×712 / 20914 B 的合法 PNG。

清单占比走 toFixed(1),合计不到 100%

占比列是第 786 行 (cnt/total*100).toFixed(1):32×32 的 11 行相加=99.9%、200×200 的 12 行也=99.9%;最小行 12 C-14 紫红 3 0.0% 是 3/40000=0.0075% 下溢,不代表这颗豆不存在。TXT 无 BOM,32×32 清单 439 B、200×200 的 480 B、全空图案 193 B。

失败路径多半静默,越界 clamp 不回写

入口第 471 行 file.type.match(/image\//) 只认 MIME 前缀 image/:text/plain 弹「请上传图片文件」,corrupt.png(72 B) 解码失败却 toasts=[]、停在空态。第 488 行的 autoFitSize 能按宽高比换算格数,但全文只命中定义行、无调用点,1200×900 上传后仍停在默认 32×32。

格数靠第 543–544 行的 Math.max(4, Math.min(200, parseInt(value)||32)) 夹:999 变 200×200=40000 格、输入框却仍写 999,setSize(300,300) 不走 clamp、gridW=300;边缘量化把每通道压到 16 级、key 空间 16³=4096,白底判据 r/g/b 全大于 230、即实际阈值 231。

几个常被问到的点

Q:挑的是「色差最小」那个吗?

对模板自己的 redmean 函数是,但它是加权距离、不是欧氏 RGB,连三角不等式都不满足。

Q:勾了「抖动算法」颜色会更准吗?

不会。8/8 组开抖动后逐格总偏差都上升(+25.08%~+33.71%)、150/1024 格被改写,关抖动才有「每格都最接近」这层保证。

Q:图纸到手后定格数、买料那套账呢?

本篇只算挑色号与抖动这一层,图纸使用与买料口径我另有一篇 拼豆图纸的使用笔记 讲过,这里不重复。

📎 相关文章

同一串 23 字节的内容切换 4 个纠错等级:这一页自己的二维码编码器走到了哪一步 2026-10-30 帧间隔填 137 ms,成品每帧其实停 140 ms:tick 量化、4096 页填充与跟着第一张走的画布 2026-10-30 横图裁掉 25%、竖图裁掉 43.75%:7 个拼图模板逐格算一笔几何账 2026-10-28 浅色底上的白字水印设成 5% 等于没加:导出 4782 字节与底图一字节不差 2026-10-28 1 月 31 日加一个月落在哪天?这页只给天数,28 天与 29 天差 86400 秒 2026-10-28