Files
postgresqlfpk/scripts/generate-fnos-icons.mjs

132 lines
4.6 KiB
JavaScript

import { deflateSync } from "node:zlib";
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
const [packageRoot, uiImageRoot] = process.argv.slice(2);
if (!packageRoot || !uiImageRoot) {
throw new Error("usage: node generate-fnos-icons.mjs package-root ui-image-root");
}
const crcTable = new Uint32Array(256);
for (let index = 0; index < 256; index++) {
let value = index;
for (let bit = 0; bit < 8; bit++) {
value = (value & 1) !== 0 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
}
crcTable[index] = value >>> 0;
}
function crc32(buffer) {
let value = 0xffffffff;
for (const byte of buffer) {
value = crcTable[(value ^ byte) & 0xff] ^ (value >>> 8);
}
return (value ^ 0xffffffff) >>> 0;
}
function chunk(type, data) {
const typeBuffer = Buffer.from(type, "ascii");
const length = Buffer.alloc(4);
length.writeUInt32BE(data.length);
const checksum = Buffer.alloc(4);
checksum.writeUInt32BE(crc32(Buffer.concat([typeBuffer, data])));
return Buffer.concat([length, typeBuffer, data, checksum]);
}
function roundedRectangleDistance(x, y, left, top, right, bottom, radius) {
const centerX = (left + right) / 2;
const centerY = (top + bottom) / 2;
const halfWidth = (right - left) / 2 - radius;
const halfHeight = (bottom - top) / 2 - radius;
const dx = Math.max(Math.abs(x - centerX) - halfWidth, 0);
const dy = Math.max(Math.abs(y - centerY) - halfHeight, 0);
return Math.hypot(dx, dy) - radius;
}
function render(size) {
const pixels = Buffer.alloc(size * size * 4);
const samples = 3;
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const rgba = [0, 0, 0, 0];
for (let sy = 0; sy < samples; sy++) {
for (let sx = 0; sx < samples; sx++) {
const px = x + (sx + 0.5) / samples;
const py = y + (sy + 0.5) / samples;
const scale = size / 256;
let color = [0, 0, 0, 0];
const background = roundedRectangleDistance(px, py, 12 * scale, 12 * scale, 244 * scale, 244 * scale, 50 * scale);
if (background <= 0) {
const mix = Math.min(1, Math.max(0, (px + py) / (512 * scale)));
color = [Math.round(27 + 33 * mix), Math.round(94 + 66 * mix), Math.round(180 + 49 * mix), 255];
}
const body = roundedRectangleDistance(px, py, 51 * scale, 75 * scale, 190 * scale, 185 * scale, 22 * scale);
if (body <= 0) {
color = [245, 249, 255, 255];
}
const lensDistance = Math.hypot(px - 120 * scale, py - 130 * scale);
if (lensDistance <= 36 * scale) {
color = lensDistance <= 23 * scale ? [42, 109, 196, 255] : [128, 185, 239, 255];
}
const viewfinder = roundedRectangleDistance(px, py, 73 * scale, 56 * scale, 122 * scale, 86 * scale, 9 * scale);
if (viewfinder <= 0) {
color = [232, 241, 253, 255];
}
if (px >= 190 * scale && px <= 222 * scale && py >= 101 * scale && py <= 159 * scale) {
const edge = Math.abs(py - 130 * scale) / (29 * scale);
const leftEdge = 190 * scale + edge * 16 * scale;
if (px >= leftEdge) {
color = [237, 244, 253, 255];
}
}
const statusDistance = Math.hypot(px - 165 * scale, py - 98 * scale);
if (statusDistance <= 9 * scale) {
color = [244, 85, 93, 255];
}
for (let channel = 0; channel < 4; channel++) {
rgba[channel] += color[channel];
}
}
}
const offset = (y * size + x) * 4;
for (let channel = 0; channel < 4; channel++) {
pixels[offset + channel] = Math.round(rgba[channel] / (samples * samples));
}
}
}
const scanlines = Buffer.alloc((size * 4 + 1) * size);
for (let row = 0; row < size; row++) {
const target = row * (size * 4 + 1);
scanlines[target] = 0;
pixels.copy(scanlines, target + 1, row * size * 4, (row + 1) * size * 4);
}
const header = Buffer.alloc(13);
header.writeUInt32BE(size, 0);
header.writeUInt32BE(size, 4);
header[8] = 8;
header[9] = 6;
return Buffer.concat([
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
chunk("IHDR", header),
chunk("IDAT", deflateSync(scanlines, { level: 9 })),
chunk("IEND", Buffer.alloc(0))
]);
}
mkdirSync(packageRoot, { recursive: true });
mkdirSync(uiImageRoot, { recursive: true });
const icon64 = render(64);
const icon256 = render(256);
writeFileSync(join(packageRoot, "ICON.PNG"), icon64);
writeFileSync(join(packageRoot, "ICON_256.PNG"), icon256);
writeFileSync(join(uiImageRoot, "icon_64.png"), icon64);
writeFileSync(join(uiImageRoot, "icon_256.png"), icon256);