
把圖片交給 AI請(qǐng)它總結(jié)“這是什么配色”會(huì)得到很有氣氛的詞??扇绻罄m(xù)要生成主題變量仍需要一個(gè)穩(wěn)定的數(shù)值底座到底哪些顏色出現(xiàn)了多少次我會(huì)先從像素提取少量代表色再讓模型幫忙命名與討論用途。八叉樹(shù)要合并顏色不能合并掉像素八叉樹(shù)把 RGB 每一位組合成 0 到 7 的分支。每插入一個(gè)像素沿途節(jié)點(diǎn)都累加數(shù)量與通道總和。減少顏色時(shí)折疊一個(gè)節(jié)點(diǎn)的子樹(shù)用該節(jié)點(diǎn)累計(jì)的均值作為代表色。無(wú)論樹(shù)怎樣折疊輸出色桶的數(shù)量總和都應(yīng)該等于輸入像素?cái)?shù)。這個(gè)不變量很重要。若把根節(jié)點(diǎn)或只剩一個(gè)孩子的節(jié)點(diǎn)排除在歸并之外某些實(shí)現(xiàn)會(huì)把未統(tǒng)計(jì)的內(nèi)部節(jié)點(diǎn)當(dāng)成葉子返回一張看似正常、實(shí)際上丟了像素的調(diào)色板。五個(gè)測(cè)試像素——黑、白、紅、白、黑——足夠暴露這類(lèi)問(wèn)題。明確“最多”不承諾“恰好”下面每次折疊最深層的一個(gè)節(jié)點(diǎn)優(yōu)先處理樣本少的節(jié)點(diǎn)。它是教學(xué)用的八叉樹(shù)量化不是最小感知誤差算法一次折疊可能跨過(guò)目標(biāo)數(shù)量所以maxColors2可能只剩一個(gè)顏色。若產(chǎn)品必須恰好給出兩色需要另一種拆分或聚類(lèi)策略不能偷偷復(fù)制色桶湊數(shù)。Nano Banana 2.1 的圖像編輯說(shuō)明適合了解后續(xù)參考圖工作流但提取的 RGB 也不能保證生成圖嚴(yán)格遵守。應(yīng)把色值、占比、命名提案分開(kāi)讓模型解釋色彩關(guān)系不替代原始統(tǒng)計(jì)。運(yùn)行node palette.js驗(yàn)證像素總數(shù)守恒、色數(shù)上限、單色輸入和重復(fù)像素加權(quán)。示例直接使用 RGB 數(shù)組未測(cè)試圖片解碼、透明合成、瀏覽器或真實(shí)模型顏色距離也沒(méi)有做感知空間校準(zhǔn)。保留這份賬本創(chuàng)意建議才有可以回頭核對(duì)的依據(jù)。可獨(dú)立運(yùn)行的核心例子function palette(pixels,maxColors) { if(!Number.isInteger(maxColors)||maxColors1||!pixels.length)throw Error(數(shù)量無(wú)效); const nodedepth({depth,count:0,sum:[0,0,0],children:new Map()}); const rootnode(0); for(const rgb of pixels) { if(rgb.length!3||rgb.some(v!Number.isInteger(v)||v0||v255))throw Error(RGB無(wú)效); let nroot; for(let depth0;depth8;depth) { n.count;rgb.forEach((v,i)n.sum[i]v); if(depth8)break; const bit7-depth,k((rgb[0]bit1)2)|((rgb[1]bit1)1)|(rgb[2]bit1); if(!n.children.has(k))n.children.set(k,node(depth1)); nn.children.get(k); } } const leavesnn.children.size?[...n.children.values()].flatMap(leaves):[n]; while(leaves(root).lengthmaxColors) { const candidates[]; function visit(n) { if(!n.children.size)return; if([...n.children.values()].every(c!c.children.size))candidates.push(n); else n.children.forEach(visit); } visit(root); candidates.sort((a,b)b.depth-a.depth||a.count-b.count); if(!candidates.length)throw Error(歸并無(wú)進(jìn)展); candidates[0].children.clear(); } return leaves(root).map(n({rgb:n.sum.map(vMath.round(v/n.count)),count:n.count})); } const px[[0,0,0],[255,255,255],[255,0,0],[255,255,255],[0,0,0]]; const outpalette(px,2); if(out.length2||out.reduce((s,c)sc.count,0)!5)throw Error(像素丟失); const onepalette([[12,34,56],[12,34,56]],8); if(one.length!1||one[0].count!2||one[0].rgb.join(,)!12,34,56)throw Error(單色失敗); const weightedpalette([[0,0,0],[0,0,0],[255,255,255]],1)[0]; if(weighted.rgb[0]!85||weighted.count!3)throw Error(加權(quán)錯(cuò)誤); console.log(JSON.stringify(out));