挑色号这一步,得先摊开算一遍
拼豆图纸是把一张图缩成 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.834 | 441.673 | 1.732 |
| 纯绿→黑 | 510 | 255 | 2(G 轴恒 4,√4) |
| 纯红→黑 | 403.033 | 255 | 1.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),平局只落在格点缝隙。
开抖动不是补几个格,是整张重算
图 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 个挖空的白底又被填满。
两套色卡的偏差账要分开算
图 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:图纸到手后定格数、买料那套账呢?
本篇只算挑色号与抖动这一层,图纸使用与买料口径我另有一篇 拼豆图纸的使用笔记 讲过,这里不重复。