Release Image Tools 0.1.0

This commit is contained in:
2026-09-01 02:47:11 +02:00
commit 83926457b2
76 changed files with 11815 additions and 0 deletions
+185
View File
@@ -0,0 +1,185 @@
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];
}