103 lines
3.8 KiB
JavaScript
103 lines
3.8 KiB
JavaScript
#!/usr/bin/env node
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const zlib = require('zlib');
|
|
|
|
const root = path.resolve(__dirname, '..');
|
|
const source = fs.readFileSync(path.join(root, 'shared', 'icons', 'verstak.svg'), 'utf8');
|
|
|
|
assert.match(source, /fill="#1c2f4a"/i, 'icon source must define the Verstak navy');
|
|
assert.match(source, /fill="#72d2b3"/i, 'icon source must define the Verstak mint');
|
|
|
|
function paeth(left, up, upperLeft) {
|
|
const estimate = left + up - upperLeft;
|
|
const leftDistance = Math.abs(estimate - left);
|
|
const upDistance = Math.abs(estimate - up);
|
|
const upperLeftDistance = Math.abs(estimate - upperLeft);
|
|
if (leftDistance <= upDistance && leftDistance <= upperLeftDistance) return left;
|
|
if (upDistance <= upperLeftDistance) return up;
|
|
return upperLeft;
|
|
}
|
|
|
|
function pngColors(file) {
|
|
const png = fs.readFileSync(file);
|
|
assert.deepStrictEqual(png.subarray(0, 8), Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]), `${file} must be PNG`);
|
|
|
|
let offset = 8;
|
|
let width;
|
|
let height;
|
|
let bitDepth;
|
|
let colorType;
|
|
let palette;
|
|
const idat = [];
|
|
while (offset < png.length) {
|
|
const length = png.readUInt32BE(offset);
|
|
const type = png.toString('ascii', offset + 4, offset + 8);
|
|
const data = png.subarray(offset + 8, offset + 8 + length);
|
|
offset += length + 12;
|
|
if (type === 'IHDR') {
|
|
width = data.readUInt32BE(0);
|
|
height = data.readUInt32BE(4);
|
|
bitDepth = data[8];
|
|
colorType = data[9];
|
|
} else if (type === 'PLTE') {
|
|
palette = data;
|
|
} else if (type === 'IDAT') {
|
|
idat.push(data);
|
|
}
|
|
}
|
|
|
|
assert.equal(bitDepth, 8, `${file} must use 8-bit samples`);
|
|
assert.ok([2, 3, 6].includes(colorType), `${file} must use RGB, palette, or RGBA pixels`);
|
|
const bytesPerPixel = colorType === 6 ? 4 : colorType === 2 ? 3 : 1;
|
|
const rowSize = width * bytesPerPixel;
|
|
const decoded = zlib.inflateSync(Buffer.concat(idat));
|
|
const colors = new Set();
|
|
let readOffset = 0;
|
|
let previous = Buffer.alloc(rowSize);
|
|
|
|
for (let y = 0; y < height; y += 1) {
|
|
const filter = decoded[readOffset++];
|
|
const encoded = decoded.subarray(readOffset, readOffset + rowSize);
|
|
readOffset += rowSize;
|
|
const row = Buffer.alloc(rowSize);
|
|
for (let x = 0; x < rowSize; x += 1) {
|
|
const left = x >= bytesPerPixel ? row[x - bytesPerPixel] : 0;
|
|
const up = previous[x];
|
|
const upperLeft = x >= bytesPerPixel ? previous[x - bytesPerPixel] : 0;
|
|
if (filter === 0) row[x] = encoded[x];
|
|
else if (filter === 1) row[x] = (encoded[x] + left) & 255;
|
|
else if (filter === 2) row[x] = (encoded[x] + up) & 255;
|
|
else if (filter === 3) row[x] = (encoded[x] + Math.floor((left + up) / 2)) & 255;
|
|
else if (filter === 4) row[x] = (encoded[x] + paeth(left, up, upperLeft)) & 255;
|
|
else assert.fail(`${file} has unsupported PNG filter ${filter}`);
|
|
}
|
|
for (let x = 0; x < width; x += 1) {
|
|
if (colorType === 3) {
|
|
const paletteOffset = row[x] * 3;
|
|
colors.add(palette.subarray(paletteOffset, paletteOffset + 3).toString('hex'));
|
|
} else {
|
|
const pixelOffset = x * bytesPerPixel;
|
|
colors.add(row.subarray(pixelOffset, pixelOffset + 3).toString('hex'));
|
|
}
|
|
}
|
|
previous = row;
|
|
}
|
|
return { width, height, colors };
|
|
}
|
|
|
|
for (const [name, size, needsMint] of [
|
|
['icon16.png', 16, false],
|
|
['icon48.png', 48, true],
|
|
['icon128.png', 128, true],
|
|
]) {
|
|
const icon = pngColors(path.join(root, 'shared', 'icons', name));
|
|
assert.equal(icon.width, size, `${name} width`);
|
|
assert.equal(icon.height, size, `${name} height`);
|
|
assert.ok(icon.colors.has('1c2f4a'), `${name} must contain the Verstak navy`);
|
|
if (needsMint) assert.ok(icon.colors.has('72d2b3'), `${name} must contain the Verstak mint`);
|
|
}
|
|
|
|
console.log('browser extension icon tests passed');
|