export const VALID_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="; export function pngFixture( width: number, height: number, options: { readonly animated?: boolean; readonly exif?: boolean; readonly text?: boolean; readonly paddingBytes?: number; } = {}, ): Uint8Array { const chunks: number[][] = [ chunk("IHDR", [...u32be(width), ...u32be(height), 8, 6, 0, 0, 0]), ]; if (options.paddingBytes) { chunks.push(chunk("ruSt", new Array(options.paddingBytes).fill(0))); } if (options.animated) chunks.push(chunk("acTL", [...u32be(2), ...u32be(0)])); if (options.exif) chunks.push(chunk("eXIf", tiffOrientation(6))); if (options.text) chunks.push(chunk("tEXt", asciiBytes("Comment\0fixture"))); chunks.push(chunk("IDAT", [])); return Uint8Array.from([ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, ...chunks.flat(), ]); } export function jpegFixture( width: number, height: number, options: { readonly orientation?: number; readonly multiPicture?: boolean; } = {}, ): Uint8Array { const segments: number[][] = []; if (options.orientation) { segments.push( jpegSegment(0xe1, [ ...asciiBytes("Exif\0\0"), ...tiffOrientation(options.orientation), ]), ); } if (options.multiPicture) segments.push(jpegSegment(0xe2, asciiBytes("MPF\0fixture"))); segments.push( jpegSegment(0xc0, [ 8, ...u16be(height), ...u16be(width), 3, 1, 0x11, 0, 2, 0x11, 0, 3, 0x11, 0, ]), ); return Uint8Array.from([0xff, 0xd8, ...segments.flat(), 0xff, 0xd9]); } export function webpFixture( width: number, height: number, options: { readonly animated?: boolean; readonly alpha?: boolean; readonly metadata?: boolean; readonly animationChunk?: boolean; readonly paddingBytes?: number; } = {}, ): Uint8Array { const flags = (options.animated ? 0x02 : 0) | (options.alpha ? 0x10 : 0) | (options.metadata ? 0x2c : 0); const payload = [flags, 0, 0, 0, ...u24le(width - 1), ...u24le(height - 1)]; const vp8x = riffChunk("VP8X", payload); const padding = options.paddingBytes ? riffChunk("JUNK", new Array(options.paddingBytes).fill(0)) : []; const animation = options.animationChunk ? riffChunk("ANIM", new Array(6).fill(0)) : []; const chunks = [...vp8x, ...padding, ...animation]; const size = 4 + chunks.length; return Uint8Array.from([ ...asciiBytes("RIFF"), ...u32le(size), ...asciiBytes("WEBP"), ...chunks, ]); } function jpegSegment(marker: number, data: readonly number[]): number[] { return [0xff, marker, ...u16be(data.length + 2), ...data]; } function chunk(type: string, data: readonly number[]): number[] { return [...u32be(data.length), ...asciiBytes(type), ...data, 0, 0, 0, 0]; } function riffChunk(type: string, data: readonly number[]): number[] { return [ ...asciiBytes(type), ...u32le(data.length), ...data, ...(data.length % 2 ? [0] : []), ]; } function tiffOrientation(orientation: number): number[] { return [ 0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x12, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, orientation, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; } function asciiBytes(value: string): number[] { return Array.from(value, (character) => character.charCodeAt(0)); } function u16be(value: number): number[] { return [(value >>> 8) & 0xff, value & 0xff]; } function u32be(value: number): number[] { return [ (value >>> 24) & 0xff, (value >>> 16) & 0xff, (value >>> 8) & 0xff, value & 0xff, ]; } function u32le(value: number): number[] { return [ value & 0xff, (value >>> 8) & 0xff, (value >>> 16) & 0xff, (value >>> 24) & 0xff, ]; } function u24le(value: number): number[] { return [value & 0xff, (value >>> 8) & 0xff, (value >>> 16) & 0xff]; }