Repository Public MIT
1/**2 * Raster image sniffing for release examples. Pure header parsing: nothing is decoded or rendered.3 * Only PNG, JPEG and WebP are recognised; SVG (scriptable) and everything else return null.4 */5export type ImageKind = "png" | "jpeg" | "webp";6export interface ImageInfo {7 kind: ImageKind;8 mime: "image/png" | "image/jpeg" | "image/webp";9 width: number;10 height: number;11}1213const PNG_SIG = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];1415/** Kind by magic bytes only (cheap; used to keep images out of the text scanner). */16export function imageKind(d: Uint8Array): ImageKind | null {17 if (d.length >= 8 && PNG_SIG.every((b, i) => d[i] === b)) return "png";18 if (d.length >= 3 && d[0] === 0xff && d[1] === 0xd8 && d[2] === 0xff) return "jpeg";19 if (d.length >= 12 && d[0] === 0x52 && d[1] === 0x49 && d[2] === 0x46 && d[3] === 0x46 && d[8] === 0x57 && d[9] === 0x45 && d[10] === 0x42 && d[11] === 0x50) return "webp";20 return null;21}2223/** Kind and pixel size, or null when the header is not a well-formed PNG, JPEG or WebP. */24export function sniffImage(data: Uint8Array): ImageInfo | null {25 const d = Buffer.isBuffer(data) ? data : Buffer.from(data.buffer, data.byteOffset, data.byteLength);26 const kind = imageKind(d);27 let width = 0;28 let height = 0;29 if (kind === "png") {30 if (d.length < 33 || d.toString("latin1", 12, 16) !== "IHDR") return null;31 width = d.readUInt32BE(16);32 height = d.readUInt32BE(20);33 } else if (kind === "jpeg") {34 let pos = 2;35 while (pos + 9 < d.length) {36 if (d[pos] !== 0xff) return null;37 const marker = d[pos + 1]!;38 if (marker === 0xff) { pos += 1; continue; }39 if (marker === 0xd8 || (marker >= 0xd0 && marker <= 0xd7) || marker === 0x01) { pos += 2; continue; }40 const len = d.readUInt16BE(pos + 2);41 if (len < 2) return null;42 // SOF0..SOF15 except DHT (C4), JPG (C8) and DAC (CC) carry the frame size.43 if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {44 height = d.readUInt16BE(pos + 5);45 width = d.readUInt16BE(pos + 7);46 break;47 }48 if (marker === 0xda || marker === 0xd9) return null;49 pos += 2 + len;50 }51 } else if (kind === "webp") {52 if (d.length < 30) return null;53 const fourcc = d.toString("latin1", 12, 16);54 if (fourcc === "VP8X") {55 width = 1 + d.readUIntLE(24, 3);56 height = 1 + d.readUIntLE(27, 3);57 } else if (fourcc === "VP8 ") {58 if (d[23] !== 0x9d || d[24] !== 0x01 || d[25] !== 0x2a) return null;59 width = d.readUInt16LE(26) & 0x3fff;60 height = d.readUInt16LE(28) & 0x3fff;61 } else if (fourcc === "VP8L") {62 if (d[20] !== 0x2f) return null;63 const bits = d.readUInt32LE(21);64 width = 1 + (bits & 0x3fff);65 height = 1 + ((bits >> 14) & 0x3fff);66 } else return null;67 } else return null;68 if (!(width > 0 && height > 0)) return null;69 return { kind, mime: kind === "png" ? "image/png" : kind === "jpeg" ? "image/jpeg" : "image/webp", width, height };70}7172const EXT_KIND: Record<string, ImageKind> = { png: "png", jpg: "jpeg", jpeg: "jpeg", webp: "webp" };73/** Image kind promised by a file extension, or null when the extension is not an accepted one. */74export function imageKindForPath(path: string): ImageKind | null {75 const ext = path.includes(".") ? path.slice(path.lastIndexOf(".") + 1).toLowerCase() : "";76 return EXT_KIND[ext] ?? null;77}78