/** * Renders the wordmark at several widths and thresholds. * * npm run brand:variants * * The shop logo is a Didone: its thin strokes are a fraction of the thick * ones. Downscaling averages a hairline into grey, and a threshold then has to * decide whether that grey is ink. Too strict and the diagonal of the A * disappears; too loose and the whole wordmark turns to mud. The only way to * choose is to look at the result thresholded, magnified. */ import { mkdir, readFile, writeFile } from 'node:fs/promises'; import path from 'node:path'; import sharp from 'sharp'; import { encodePng, toBitmap } from '../lib/screen/encode'; const WIDTHS = [260, 320, 400]; const THRESHOLDS = [127, 165, 195]; async function main(): Promise { const out = path.join(process.cwd(), '.preview', 'logo'); await mkdir(out, { recursive: true }); const source = await readFile(path.join(process.cwd(), 'public', 'brand', 'logo.png')); const band = await findWordmarkBand(source); const rows: string[] = []; for (const width of WIDTHS) { for (const threshold of THRESHOLDS) { const raw = await sharp(source) .extract(band) .resize({ width }) .flatten({ background: '#ffffff' }) .ensureAlpha() .raw() .toBuffer({ resolveWithObject: true }); const bitmap = toBitmap(raw.data, raw.info.width, raw.info.height, { threshold }); const name = `w${width}-s${threshold}`; await writeFile(path.join(out, `${name}.png`), encodePng(bitmap)); // Magnified to the same apparent size, so the variants compare fairly. await sharp(path.join(out, `${name}.png`)) .resize({ width: 1040, kernel: 'nearest' }) .toFile(path.join(out, `${name}-zoom.png`)); rows.push(`${name} ${raw.info.width}x${raw.info.height}`); } } console.log(rows.join('\n')); } async function findWordmarkBand(source: Buffer) { const { data, info } = await sharp(source) .flatten({ background: '#ffffff' }) .greyscale() .raw() .toBuffer({ resolveWithObject: true }); let top: number | null = null; let bottom = 0; for (let y = 0; y < info.height; y += 1) { let ink = 0; for (let x = 0; x < info.width; x += 1) { if ((data[y * info.width + x] ?? 255) < 128) { ink += 1; } } if (ink > 0) { if (top === null) { top = y; } bottom = y; } else if (top !== null) { break; } } const start = Math.max(0, (top ?? 0) - 2); return { left: 0, top: start, width: info.width, height: bottom + 2 - start + 1 }; } main().catch((error: unknown) => { console.error(error); process.exitCode = 1; });