Something went wrong. Try again.
Web frontend and supporting services for lance.blue
Something went wrong. Try again.
3.8 kB · 112 lines
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113/** * A generator that renders one flat colour is the failure this file exists to * catch, because nothing else reports it. The image is valid, the canvas is * painted, no exception is thrown, and the only symptom is a swatch in the * picker that looks like the Solid pattern. * * Reaction diffusion shipped that way. Gray-Scott only makes patterns along a * narrow diagonal band of the feed/kill plane, it drew the two rates * independently and mostly missed the band, and it seeded a fixed number of * nucleation sites regardless of image area, which flooded the half-size * thumbnail even when the rates were good. Both failures are silent, and both * depend on the seed and the sliders, so a spot check of one render would not * have found either. * * The sweep is therefore over parameters rather than over one image: every * generator, at both the sizes the editor actually renders, across the corners * and middle of the slider space and a spread of seeds. Solid is excluded by * name — one colour is what it is for. * * Run with `npm test`. */import { test } from "node:test";import assert from "node:assert/strict";import { mkdtemp, rm } from "node:fs/promises";import { tmpdir } from "node:os";import { join } from "node:path";import { execFile } from "node:child_process";import { promisify } from "node:util";
const run = promisify(execFile);const web = join(new URL(".", import.meta.url).pathname, "..");
const dir = await mkdtemp(join(tmpdir(), "camo-patterns-"));const bundle = join(dir, "fixture.mjs");await run( join(web, "node_modules/.bin/rolldown"), [ join(web, "scripts/camo-patterns.fixture.ts"), "--format", "esm", "--platform", "node", "--file", bundle, ], { cwd: web },);
const { GENERATORS } = await import(bundle);
/** Node has no ImageData, and a generator only ever touches these three. */const image = (width, height) => ({ width, height, data: new Uint8ClampedArray(width * height * 4),});
/** The camo, and the half-size thumbnail the picker draws beside each name. */const SIZES = [ [84, 72], [42, 36],];
/** Four distinct greys: a generator that bands is visible, a flat one is not. */const PALETTE = [ [0, 0, 0], [85, 85, 85], [170, 170, 170], [255, 255, 255],];
const SEEDS = [1, 20260807, 3189639203];const SLIDER = [0, 0.5, 1];
/** Distinct colours in the render, and the share held by the commonest one. */function spread(img) { const counts = new Map(); for (let i = 0; i < img.data.length; i += 4) { const key = (img.data[i] << 16) | (img.data[i + 1] << 8) | img.data[i + 2]; counts.set(key, (counts.get(key) ?? 0) + 1); } const pixels = img.width * img.height; return { colours: counts.size, top: Math.max(...counts.values()) / pixels };}
for (const gen of GENERATORS) { if (gen.id === "solid") continue; test(`${gen.name} renders more than one colour`, () => { for (const [w, h] of SIZES) { for (const seed of SEEDS) { for (const scale of SLIDER) { for (const detail of SLIDER) { const params = { seed, scale, contrast: 0.6, detail, angle: 0 }; const img = image(w, h); gen.render(img, PALETTE, params); const { colours, top } = spread(img); const where = `${gen.id} ${w}x${h} ${JSON.stringify(params)}`; assert.ok(colours > 1, `${where}: one flat colour`); // Two colours where one covers all but a few pixels is the same // failure wearing a disguise, so the dominant colour is capped. assert.ok( top < 0.97, `${where}: ${Math.round(top * 100)}% one colour`, ); } } } } });}
test.after(() => rm(dir, { recursive: true, force: true }));