| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 |
- "use strict";
- /** Verify rendered icons: decode PNG (8-bit RGBA, non-interlaced) and check
- * color composition + whale centering. Usage: node tools/check-icon.js a.png */
- const fs = require("node:fs");
- const zlib = require("node:zlib");
- function decodePng(buf) {
- if (buf.readUInt32BE(0) !== 0x89504e47) throw new Error("not a PNG");
- let pos = 8;
- let width = 0;
- let height = 0;
- let bitDepth = 0;
- let colorType = 0;
- const 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") {
- width = data.readUInt32BE(0);
- height = data.readUInt32BE(4);
- bitDepth = data[8];
- colorType = data[9];
- } else if (type === "IDAT") idat.push(data);
- else if (type === "IEND") break;
- pos += 12 + len;
- }
- if (bitDepth !== 8 || colorType !== 6) throw new Error(`unsupported PNG: depth=${bitDepth} color=${colorType}`);
- const raw = zlib.inflateSync(Buffer.concat(idat));
- const stride = width * 4;
- const out = Buffer.alloc(width * height * 4);
- const paeth = (a, b, c) => {
- const p = a + b - c;
- const pa = Math.abs(p - a);
- const pb = Math.abs(p - b);
- const pc = Math.abs(p - c);
- return pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
- };
- for (let y = 0; y < height; y++) {
- const filter = raw[y * (stride + 1)];
- const row = raw.subarray(y * (stride + 1) + 1, (y + 1) * (stride + 1));
- const prev = y > 0 ? out.subarray((y - 1) * stride, y * stride) : null;
- for (let x = 0; x < stride; x++) {
- const i = y * stride + x;
- const left = x >= 4 ? out[i - 4] : 0;
- const up = prev ? prev[x] : 0;
- const ul = prev && x >= 4 ? prev[x - 4] : 0;
- let v = row[x];
- if (filter === 1) v = (v + left) & 255;
- else if (filter === 2) v = (v + up) & 255;
- else if (filter === 3) v = (v + ((left + up) >> 1)) & 255;
- else if (filter === 4) v = (v + paeth(left, up, ul)) & 255;
- out[i] = v;
- }
- }
- return { width, height, pixels: out };
- }
- const file = process.argv[2];
- const { width, height, pixels } = decodePng(fs.readFileSync(file));
- let transparent = 0;
- let white = 0;
- let blue = 0;
- let black = 0;
- let other = 0;
- let minX = Infinity;
- let maxX = -Infinity;
- let minY = Infinity;
- let maxY = -Infinity;
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- const i = (y * width + x) * 4;
- const r = pixels[i];
- const g = pixels[i + 1];
- const b = pixels[i + 2];
- const a = pixels[i + 3];
- if (a < 16) { transparent++; continue; }
- if (r > 220 && g > 220 && b > 220) {
- white++;
- if (x < minX) minX = x;
- if (x > maxX) maxX = x;
- if (y < minY) minY = y;
- if (y > maxY) maxY = y;
- } else if (b > 180 && b > r + 40 && g > r) blue++;
- else if (r < 60 && g < 60 && b < 60) black++;
- else other++;
- }
- }
- const total = width * height;
- const pct = (n) => `${((n / total) * 100).toFixed(1)}%`;
- console.log(`${file}: ${width}×${height}`);
- console.log(` transparent=${pct(transparent)} white=${pct(white)} blue=${pct(blue)} black=${pct(black)} other=${pct(other)}`);
- if (white > 0) {
- const cx = (minX + maxX) / 2 / width;
- const cy = (minY + maxY) / 2 / height;
- console.log(` white mass bbox: x ${minX}..${maxX}, y ${minY}..${maxY}; center=(${cx.toFixed(2)}, ${cy.toFixed(2)}) of canvas`);
- }
|