import sharp from 'sharp'; export type ImageFacts = { width: number | null; height: number | null; hasAlpha: boolean | null; opaque: boolean | null; }; const UNKNOWN: ImageFacts = { width: null, height: null, hasAlpha: null, opaque: null }; /** * Measure the file that was actually written. * * Read from disk rather than echoed from the request, because the request is what was asked for and * this is what arrived — and those are precisely the two things that come apart. The caller is an * agent that cannot look at the image, so anything it is not told, it cannot know. * * `hasAlpha` and `opaque` answer different questions and the gap between them is the point: * * hasAlpha: false no alpha channel at all * hasAlpha: true, opaque: false transparency present and used * hasAlpha: true, opaque: true an alpha channel the model never used * * The third is the failure worth catching. `background: "transparent"` is accepted, the job succeeds, * and every pixel comes back fully opaque — which for an app icon is the difference between usable and * a black square, and is invisible from both ends without this field. * * Never throws. A generation that produced real bytes must not be recorded as failed because the * measurement of it did; nulls say "not known" and the image is still there. */ export async function inspectImage(path: string): Promise { try { const image = sharp(path); const metadata = await image.metadata(); // stats() decodes the pixels — the only way to answer "is anything actually transparent", since // the header can only say whether a channel exists. Skipped when there is no alpha channel at // all, where the answer is already known and the decode would be waste. const opaque = metadata.hasAlpha ? (await image.stats()).isOpaque : true; return { width: metadata.width ?? null, height: metadata.height ?? null, hasAlpha: metadata.hasAlpha ?? null, opaque, }; } catch (error) { console.error(`[inspect] could not read ${path}:`, error instanceof Error ? error.message : error); return UNKNOWN; } }