186 lines
4.1 KiB
TypeScript
186 lines
4.1 KiB
TypeScript
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<number>(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<number>(options.paddingBytes).fill(0))
|
|
: [];
|
|
const animation = options.animationChunk
|
|
? riffChunk("ANIM", new Array<number>(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];
|
|
}
|