137 lines
3.7 KiB
JavaScript
137 lines
3.7 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const { PNG } = require('pngjs');
|
|
|
|
const SIZE = 600;
|
|
|
|
function hexToRgb(hex) {
|
|
const clean = hex.replace('#', '');
|
|
return {
|
|
r: parseInt(clean.substring(0, 2), 16),
|
|
g: parseInt(clean.substring(2, 4), 16),
|
|
b: parseInt(clean.substring(4, 6), 16),
|
|
};
|
|
}
|
|
|
|
function setPixel(png, x, y, color) {
|
|
if (x < 0 || y < 0 || x >= png.width || y >= png.height) return;
|
|
const idx = (png.width * y + x) << 2;
|
|
png.data[idx] = color.r;
|
|
png.data[idx + 1] = color.g;
|
|
png.data[idx + 2] = color.b;
|
|
png.data[idx + 3] = 255;
|
|
}
|
|
|
|
function fillRect(png, x0, y0, x1, y1, color) {
|
|
const sx = Math.max(0, Math.floor(x0));
|
|
const sy = Math.max(0, Math.floor(y0));
|
|
const ex = Math.min(png.width - 1, Math.ceil(x1));
|
|
const ey = Math.min(png.height - 1, Math.ceil(y1));
|
|
for (let y = sy; y <= ey; y++) {
|
|
for (let x = sx; x <= ex; x++) setPixel(png, x, y, color);
|
|
}
|
|
}
|
|
|
|
function fillCircle(png, cx, cy, r, color) {
|
|
const rr = r * r;
|
|
const x0 = Math.floor(cx - r);
|
|
const x1 = Math.ceil(cx + r);
|
|
const y0 = Math.floor(cy - r);
|
|
const y1 = Math.ceil(cy + r);
|
|
for (let y = y0; y <= y1; y++) {
|
|
for (let x = x0; x <= x1; x++) {
|
|
const dx = x - cx;
|
|
const dy = y - cy;
|
|
if (dx * dx + dy * dy <= rr) setPixel(png, x, y, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
function fillEllipse(png, cx, cy, rx, ry, color) {
|
|
const rx2 = rx * rx;
|
|
const ry2 = ry * ry;
|
|
const x0 = Math.floor(cx - rx);
|
|
const x1 = Math.ceil(cx + rx);
|
|
const y0 = Math.floor(cy - ry);
|
|
const y1 = Math.ceil(cy + ry);
|
|
for (let y = y0; y <= y1; y++) {
|
|
for (let x = x0; x <= x1; x++) {
|
|
const dx = x - cx;
|
|
const dy = y - cy;
|
|
if ((dx * dx) / rx2 + (dy * dy) / ry2 <= 1) setPixel(png, x, y, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
function drawThickLine(png, x0, y0, x1, y1, thickness, color) {
|
|
const steps = Math.max(Math.abs(x1 - x0), Math.abs(y1 - y0));
|
|
for (let i = 0; i <= steps; i++) {
|
|
const t = i / (steps || 1);
|
|
const x = Math.round(x0 + (x1 - x0) * t);
|
|
const y = Math.round(y0 + (y1 - y0) * t);
|
|
fillCircle(png, x, y, thickness / 2, color);
|
|
}
|
|
}
|
|
|
|
function drawLogo(filename, theme) {
|
|
const png = new PNG({ width: SIZE, height: SIZE });
|
|
|
|
const bg = hexToRgb(theme.bg);
|
|
const card = hexToRgb(theme.card);
|
|
const bowl = hexToRgb(theme.bowl);
|
|
const accent = hexToRgb(theme.accent);
|
|
|
|
fillRect(png, 0, 0, SIZE - 1, SIZE - 1, bg);
|
|
|
|
// Card background shape
|
|
fillRect(png, 80, 80, 520, 520, card);
|
|
fillCircle(png, 80, 80, 70, card);
|
|
fillCircle(png, 520, 80, 70, card);
|
|
fillCircle(png, 80, 520, 70, card);
|
|
fillCircle(png, 520, 520, 70, card);
|
|
|
|
// Bowl
|
|
fillEllipse(png, 300, 350, 165, 90, bowl);
|
|
fillRect(png, 135, 350, 465, 430, bowl);
|
|
fillEllipse(png, 300, 430, 165, 45, bowl);
|
|
|
|
// Bowl top cut
|
|
fillEllipse(png, 300, 340, 175, 68, card);
|
|
fillEllipse(png, 300, 346, 175, 4, accent);
|
|
|
|
// Noodles
|
|
fillEllipse(png, 250, 372, 50, 12, accent);
|
|
fillEllipse(png, 300, 374, 55, 13, accent);
|
|
fillEllipse(png, 350, 372, 50, 12, accent);
|
|
|
|
// Steam
|
|
fillEllipse(png, 245, 255, 14, 32, accent);
|
|
fillEllipse(png, 300, 235, 16, 38, accent);
|
|
fillEllipse(png, 355, 255, 14, 32, accent);
|
|
|
|
// Chopsticks
|
|
drawThickLine(png, 360, 205, 485, 330, 11, accent);
|
|
drawThickLine(png, 390, 188, 515, 313, 11, accent);
|
|
|
|
const outPath = path.join(__dirname, filename);
|
|
fs.writeFileSync(outPath, PNG.sync.write(png));
|
|
return outPath;
|
|
}
|
|
|
|
const light = drawLogo('lunch-pick-logo-light-600.png', {
|
|
bg: '#F8FAFC',
|
|
card: '#E0F2FE',
|
|
bowl: '#0EA5E9',
|
|
accent: '#0C4A6E',
|
|
});
|
|
|
|
const dark = drawLogo('lunch-pick-logo-dark-600.png', {
|
|
bg: '#0B1220',
|
|
card: '#13233C',
|
|
bowl: '#38BDF8',
|
|
accent: '#E2E8F0',
|
|
});
|
|
|
|
console.log(light);
|
|
console.log(dark);
|