import fs from "node:fs"; import zlib from "node:zlib"; const file = process.argv[2]; const buf = fs.readFileSync(file); // ---- 解析 PNG ---- let pos = 8; let W = 0, H = 0, bitDepth = 0, colorType = 0, idat = []; while (pos < buf.length) { const len = buf.readUInt32BE(pos); const type = buf.toString("ascii", pos + 4, pos + 8); const data = buf.subarray(pos + 8, pos + 8 + len); if (type === "IHDR") { W = data.readUInt32BE(0); H = data.readUInt32BE(4); bitDepth = data[8]; colorType = data[9]; } else if (type === "IDAT") idat.push(data); else if (type === "IEND") break; pos += 12 + len; } console.log(`size=${W}x${H} bitDepth=${bitDepth} colorType=${colorType}`); const raw = zlib.inflateSync(Buffer.concat(idat)); const ch = colorType === 6 ? 4 : colorType === 2 ? 3 : colorType === 0 ? 1 : 0; if (!ch || bitDepth !== 8) { console.log("unsupported format"); process.exit(0); } const stride = W * ch; const px = Buffer.alloc(H * stride); // ---- 反 filter ---- let p = 0; for (let y = 0; y < H; y++) { const ft = raw[p++]; const line = raw.subarray(p, p + stride); p += stride; const cur = px.subarray(y * stride, (y + 1) * stride); const prev = y > 0 ? px.subarray((y - 1) * stride, y * stride) : null; for (let x = 0; x < stride; x++) { const a = x >= ch ? cur[x - ch] : 0; const b = prev ? prev[x] : 0; const c = prev && x >= ch ? prev[x - ch] : 0; let v = line[x]; if (ft === 1) v += a; else if (ft === 2) v += b; else if (ft === 3) v += (a + b) >> 1; else if (ft === 4) { const pa = Math.abs(b - c), pb = Math.abs(a - c), pc = Math.abs(a + b - 2 * c); v += (pa <= pb && pa <= pc) ? a : (pb <= pc ? b : c); } cur[x] = v & 255; } } const at = (x, y) => { const o = (y * W + x) * ch; return [px[o], px[o + 1], px[o + 2]]; }; const near = (c, t, tol) => Math.abs(c[0]-t[0])<=tol && Math.abs(c[1]-t[1])<=tol && Math.abs(c[2]-t[2])<=tol; // ---- 直方图:统计出现最多的颜色 ---- const hist = new Map(); for (let y = 0; y < H; y += 2) for (let x = 0; x < W; x += 2) { const c = at(x, y); const k = `${c[0]},${c[1]},${c[2]}`; hist.set(k, (hist.get(k) || 0) + 1); } const top = [...hist.entries()].sort((a, b) => b[1] - a[1]).slice(0, 12); console.log("主色 Top12(含占比):"); for (const [k, v] of top) console.log(` #${k.split(",").map(n=>Number(n).toString(16).padStart(2,"0")).join("")} ${(v/((W/2)*(H/2))*100).toFixed(1)}% (${k})`); fs.writeFileSync(process.argv[3] || "png-px.bin", Buffer.from(px));