雜記 › 生活隨筆 › 拼豆圖紙的色號是怎麼挑出來的:開一次抖動,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 28 天還是 29 天:1 月 31 日加一個月要自己換算 2026-10-28