@@ -0,0 +1,63 @@
|
||||
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));
|
||||
Reference in New Issue
Block a user