// A screenshot that failed, as a check's output: WebP, small enough for // the runner to keep (at most 1,000,000 bytes, the lexicon's maxSize). // // node harness/shots/outputs.mjs before|after|difference // // Before and after are lossy, at quality 75 and the shot's own scale: at // 2× the text is as crisp as the PNG's, at a third of its size or less. The // difference is lossless, so the pixels it marks stay exact. One still too // big is tried smaller (before and after at quality 50, then at half the // scale), keeping the smallest; one still over is written anyway, and // said, as the runner leaves it out. Any failure exits non-zero, having // written nothing, and linux.sh keeps the PNG instead. import { readFile, writeFile } from 'node:fs/promises' import { basename } from 'node:path' const [png, out, kind] = process.argv.slice(2) const most = 1_000_000 // WebP's largest side, in pixels. const side = 16383 const tries = kind === 'difference' ? // Lossless is smallest for a difference's faded page, even against // lossy at half the scale, but a change of a different kind may not be. [ { lossless: true }, { quality: 50, half: true, note: 'lossy, at half the scale: the pixels it marks are blurred', }, ] : [ { quality: 75 }, { quality: 50, note: 'at quality 50' }, { quality: 75, half: true, note: 'at half the scale' }, ] const name = basename(out) + '.webp' process.on('uncaughtException', (e) => { console.log(`==> ${name}: none, as ${e.message}`) process.exit(1) }) // Imported here, so its absence is said as any other failure is. const { default: sharp } = await import('sharp') const input = await readFile(png) const { width, height } = await sharp(input).metadata() let smallest for (const t of tries) { const scale = t.half ? 0.5 : 1 if (Math.max(width, height) * scale > side) continue let image = sharp(input) if (t.half) image = image.resize({ width: Math.round(width / 2) }) const options = t.lossless ? { lossless: true } : { quality: t.quality, smartSubsample: true } const bytes = await image.webp(options).toBuffer() if (!smallest || bytes.length < smallest.bytes.length) smallest = { bytes, t } if (bytes.length <= most) break } if (!smallest) throw new Error(`${width}×${height} is too big for WebP, even at half the scale`) await writeFile(out + '.webp', smallest.bytes) const kb = (n) => `${Math.round(n / 1000)} kB` const how = smallest.t.note ? `, ${smallest.t.note}` : '' console.log(`==> ${name}: ${kb(smallest.bytes.length)}, from a ${kb(input.length)} PNG${how}`) if (smallest.bytes.length > most) console.log(`==> ${name}: still over ${most} bytes, so the runner will not keep it`)