@levischuck/tiny-png 0.0.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bytes.d.ts +1 -1
- package/dist/index.d.ts +9 -2
- package/dist/index.js +194 -58
- package/dist/reader.d.ts +101 -0
- package/dist/writer.d.ts +15 -0
- package/package.json +1 -1
package/dist/bytes.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function concat(...bytes: Uint8Array[]): Uint8Array
|
|
1
|
+
export declare function concat(...bytes: Uint8Array[]): Uint8Array<ArrayBuffer>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
export { scanChunk, iterateChunks, findChunk, readIHDR, validatePngSignature, readPngIHDR, type PngChunk, type ColorType, type InterlaceMethod, type IHDRData, } from './reader.ts';
|
|
1
2
|
/**
|
|
2
|
-
* Generate an indexed-color PNG image, up to
|
|
3
|
+
* Generate an indexed-color PNG image, up to 256 colors are supported
|
|
4
|
+
*
|
|
5
|
+
* Automatically selects the optimal bit depth based on the number of colors used:
|
|
6
|
+
* - 1-bit for 2 or fewer colors
|
|
7
|
+
* - 2-bit for 4 or fewer colors
|
|
8
|
+
* - 4-bit for 16 or fewer colors
|
|
9
|
+
* - 8-bit for up to 256 colors
|
|
3
10
|
*
|
|
4
11
|
* @param input - Pixel data as a Uint8Array where each byte is a palette index
|
|
5
12
|
* @param width - Image width in pixels
|
|
@@ -10,4 +17,4 @@
|
|
|
10
17
|
* @throws Error if the input does not match the dimensions.
|
|
11
18
|
* @throws Error if the input is empty.
|
|
12
19
|
*/
|
|
13
|
-
export declare function indexedPng(input: Uint8Array, width: number, height: number, colors: [number, number, number][]): Promise<Uint8Array
|
|
20
|
+
export declare function indexedPng(input: Uint8Array, width: number, height: number, colors: [number, number, number][]): Promise<Uint8Array<ArrayBuffer>>;
|
package/dist/index.js
CHANGED
|
@@ -1,79 +1,215 @@
|
|
|
1
|
-
function
|
|
1
|
+
function k(...t) {
|
|
2
2
|
const e = new Uint8Array(
|
|
3
|
-
|
|
3
|
+
t.reduceRight((r, o) => r + o.length, 0)
|
|
4
4
|
);
|
|
5
|
-
let
|
|
6
|
-
for (const
|
|
7
|
-
e.set(
|
|
5
|
+
let n = 0;
|
|
6
|
+
for (const r of t)
|
|
7
|
+
e.set(r, n), n += r.length;
|
|
8
8
|
return e;
|
|
9
9
|
}
|
|
10
|
-
async function
|
|
11
|
-
const e = new CompressionStream("deflate"),
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
let
|
|
10
|
+
async function I(t) {
|
|
11
|
+
const e = new CompressionStream("deflate"), n = e.writable.getWriter();
|
|
12
|
+
n.write(t), n.close();
|
|
13
|
+
const r = e.readable.getReader(), o = [];
|
|
14
|
+
let i = 0;
|
|
15
15
|
for (; ; ) {
|
|
16
|
-
const { done:
|
|
17
|
-
if (
|
|
18
|
-
|
|
16
|
+
const { done: u, value: c } = await r.read();
|
|
17
|
+
if (u) break;
|
|
18
|
+
o.push(c), i += c.length;
|
|
19
19
|
}
|
|
20
|
-
const
|
|
21
|
-
let
|
|
22
|
-
for (const
|
|
23
|
-
|
|
24
|
-
return
|
|
25
|
-
}
|
|
26
|
-
const
|
|
27
|
-
for (let
|
|
28
|
-
let e =
|
|
29
|
-
for (let
|
|
20
|
+
const s = new Uint8Array(i);
|
|
21
|
+
let f = 0;
|
|
22
|
+
for (const u of o)
|
|
23
|
+
s.set(u, f), f += u.length;
|
|
24
|
+
return s;
|
|
25
|
+
}
|
|
26
|
+
const b = new Uint32Array(256);
|
|
27
|
+
for (let t = 0; t < 256; t++) {
|
|
28
|
+
let e = t;
|
|
29
|
+
for (let n = 0; n < 8; n++)
|
|
30
30
|
e = e & 1 ? 3988292384 ^ e >>> 1 : e >>> 1;
|
|
31
|
-
|
|
31
|
+
b[t] = e;
|
|
32
32
|
}
|
|
33
|
-
function
|
|
33
|
+
function $(t) {
|
|
34
34
|
let e = 4294967295;
|
|
35
|
-
for (let
|
|
36
|
-
e =
|
|
35
|
+
for (let n = 0; n < t.length; n++)
|
|
36
|
+
e = b[(e ^ t[n]) & 255] ^ e >>> 8;
|
|
37
37
|
return (e ^ 4294967295) >>> 0;
|
|
38
38
|
}
|
|
39
|
-
const
|
|
40
|
-
function
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
const m = new TextEncoder();
|
|
40
|
+
function d(t, e) {
|
|
41
|
+
const n = new Uint8Array(8 + e.length + 4), r = new DataView(n.buffer);
|
|
42
|
+
r.setUint32(0, e.length), n.set(m.encode(t), 4), n.set(e, 8);
|
|
43
|
+
const o = $(n.subarray(4, n.length - 4));
|
|
44
|
+
return r.setUint32(n.length - 4, o), n;
|
|
45
|
+
}
|
|
46
|
+
function A(t) {
|
|
47
|
+
return t <= 1 ? 1 : t <= 3 ? 2 : t <= 15 ? 4 : 8;
|
|
48
|
+
}
|
|
49
|
+
function R(t, e, n, r) {
|
|
50
|
+
const o = 8 / r, s = Math.ceil(e / o) + 1, f = new Uint8Array(s * n), u = new DataView(f.buffer);
|
|
51
|
+
for (let c = 0; c < n; c++) {
|
|
52
|
+
u.setUint8(c * s, 0);
|
|
53
|
+
for (let a = 0; a < e; a++) {
|
|
54
|
+
const y = t.getUint8(c * e + a), h = c * s + 1 + Math.floor(a / o), w = (o - 1 - a % o) * r, g = u.getUint8(h);
|
|
55
|
+
u.setUint8(h, g | y << w);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return f;
|
|
59
|
+
}
|
|
60
|
+
const C = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), L = new TextDecoder();
|
|
61
|
+
function x(t) {
|
|
62
|
+
switch (t) {
|
|
63
|
+
case 0:
|
|
64
|
+
return "greyscale";
|
|
65
|
+
case 2:
|
|
66
|
+
return "truecolor";
|
|
67
|
+
case 3:
|
|
68
|
+
return "indexed";
|
|
69
|
+
case 4:
|
|
70
|
+
return "greyscale-alpha";
|
|
71
|
+
case 6:
|
|
72
|
+
return "truecolor-alpha";
|
|
73
|
+
default:
|
|
74
|
+
throw new Error(`Invalid color type: ${t}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function P(t) {
|
|
78
|
+
switch (t) {
|
|
79
|
+
case 0:
|
|
80
|
+
return "none";
|
|
81
|
+
case 1:
|
|
82
|
+
return "adam7";
|
|
83
|
+
default:
|
|
84
|
+
throw new Error(`Invalid interlace method: ${t}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function V(t, e) {
|
|
88
|
+
const r = {
|
|
89
|
+
0: [1, 2, 4, 8, 16],
|
|
90
|
+
// Greyscale
|
|
91
|
+
2: [8, 16],
|
|
92
|
+
// Truecolor
|
|
93
|
+
3: [1, 2, 4, 8],
|
|
94
|
+
// Indexed
|
|
95
|
+
4: [8, 16],
|
|
96
|
+
// Greyscale with alpha
|
|
97
|
+
6: [8, 16]
|
|
98
|
+
// Truecolor with alpha
|
|
99
|
+
}[e];
|
|
100
|
+
if (!r)
|
|
101
|
+
throw new Error(`Invalid color type: ${e}`);
|
|
102
|
+
if (!r.includes(t))
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Invalid bit depth ${t} for color type ${e}. Allowed: ${r.join(", ")}`
|
|
105
|
+
);
|
|
106
|
+
return !0;
|
|
107
|
+
}
|
|
108
|
+
function v(t, e) {
|
|
109
|
+
if (e + 12 > t.byteLength)
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Chunk at offset ${e} extends beyond data (need at least 12 bytes for header)`
|
|
112
|
+
);
|
|
113
|
+
const n = t.getUint32(e, !1), r = new Uint8Array(t.buffer, t.byteOffset + e + 4, 4), o = L.decode(r), i = 8 + n + 4;
|
|
114
|
+
if (e + i > t.byteLength)
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Chunk "${o}" at offset ${e} extends beyond data (chunk size: ${i}, available: ${t.byteLength - e})`
|
|
117
|
+
);
|
|
118
|
+
const s = new DataView(t.buffer, t.byteOffset + e + 8, n);
|
|
119
|
+
return {
|
|
120
|
+
type: o,
|
|
121
|
+
data: s,
|
|
122
|
+
offset: e,
|
|
123
|
+
totalSize: i
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function* O(t, e = 0) {
|
|
127
|
+
let n = e;
|
|
128
|
+
for (; n < t.byteLength; ) {
|
|
129
|
+
const r = v(t, n);
|
|
130
|
+
yield r, n += r.totalSize;
|
|
52
131
|
}
|
|
53
|
-
|
|
132
|
+
}
|
|
133
|
+
function M(t, e, n, r = 0) {
|
|
134
|
+
for (const o of O(t, r))
|
|
135
|
+
if (o.type === e)
|
|
136
|
+
return n(o.data);
|
|
137
|
+
}
|
|
138
|
+
function N(t) {
|
|
139
|
+
if (t.byteLength !== 13)
|
|
140
|
+
throw new Error(`IHDR chunk must be 13 bytes, got ${t.byteLength}`);
|
|
141
|
+
const e = t.getUint32(0, !1), n = t.getUint32(4, !1), r = t.getUint8(8), o = t.getUint8(9), i = t.getUint8(10), s = t.getUint8(11), f = t.getUint8(12);
|
|
142
|
+
if (e === 0)
|
|
143
|
+
throw new Error("IHDR width cannot be zero");
|
|
144
|
+
if (n === 0)
|
|
145
|
+
throw new Error("IHDR height cannot be zero");
|
|
146
|
+
if (i !== 0)
|
|
54
147
|
throw new Error(
|
|
55
|
-
`
|
|
148
|
+
`Invalid compression method: ${i}. Only 0 (deflate) is supported.`
|
|
56
149
|
);
|
|
57
|
-
if (
|
|
150
|
+
if (s !== 0)
|
|
58
151
|
throw new Error(
|
|
59
|
-
`
|
|
152
|
+
`Invalid filter method: ${s}. Only 0 (adaptive) is supported.`
|
|
60
153
|
);
|
|
61
|
-
|
|
154
|
+
V(r, o);
|
|
155
|
+
const u = x(o), c = P(f);
|
|
156
|
+
return {
|
|
157
|
+
width: e,
|
|
158
|
+
height: n,
|
|
159
|
+
bitDepth: r,
|
|
160
|
+
colorType: u,
|
|
161
|
+
compressionMethod: "deflate",
|
|
162
|
+
filterMethod: "adaptive",
|
|
163
|
+
interlaceMethod: c
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function T(t) {
|
|
167
|
+
if (t.byteLength < 8)
|
|
168
|
+
throw new Error("Data too short to contain PNG signature");
|
|
169
|
+
for (let e = 0; e < 8; e++)
|
|
170
|
+
if (t.getUint8(e) !== C[e])
|
|
171
|
+
throw new Error("Invalid PNG signature");
|
|
172
|
+
return !0;
|
|
173
|
+
}
|
|
174
|
+
function H(t) {
|
|
175
|
+
const e = t instanceof Uint8Array ? t.buffer : t, n = t instanceof Uint8Array ? t.byteOffset : 0, r = (t instanceof Uint8Array, t.byteLength), o = new DataView(e, n, r);
|
|
176
|
+
T(o);
|
|
177
|
+
const i = M(o, "IHDR", N, 8);
|
|
178
|
+
if (i === void 0)
|
|
179
|
+
throw new Error("IHDR chunk not found in PNG");
|
|
180
|
+
return i;
|
|
181
|
+
}
|
|
182
|
+
async function z(t, e, n, r) {
|
|
183
|
+
const o = new DataView(t.buffer), i = o.byteLength;
|
|
184
|
+
if (i === 0)
|
|
62
185
|
throw new Error("Received empty input");
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
for (let f = 0; f < e; f++)
|
|
72
|
-
b.setUint8(n * d + 1 + f, i.getUint8(n * e + f));
|
|
186
|
+
if (i !== e * n)
|
|
187
|
+
throw new Error(
|
|
188
|
+
`Input does not match dimensions ${e}x${n}. Only ${i} bytes were given when ${e * n} are expected!`
|
|
189
|
+
);
|
|
190
|
+
let s = 0;
|
|
191
|
+
for (let l = 0; l < i; l++) {
|
|
192
|
+
const p = o.getUint8(l);
|
|
193
|
+
p > s && (s = p);
|
|
73
194
|
}
|
|
74
|
-
|
|
75
|
-
|
|
195
|
+
if (r.length <= s)
|
|
196
|
+
throw new Error(
|
|
197
|
+
`Color palette does not have enough colors (${s + 1}). Only ${r.length} were given!`
|
|
198
|
+
);
|
|
199
|
+
const f = A(s), u = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), c = new Uint8Array(13), a = new DataView(c.buffer);
|
|
200
|
+
a.setUint32(0, e), a.setUint32(4, n), a.setUint8(8, f), a.setUint8(9, 3), a.setUint8(10, 0), a.setUint8(11, 0), a.setUint8(12, 0);
|
|
201
|
+
const y = d("IHDR", c), h = new Uint8Array(r.length * 3), w = new DataView(h.buffer);
|
|
202
|
+
for (let l = 0; l <= s; l++)
|
|
203
|
+
w.setUint8(l * 3, r[l][0]), w.setUint8(l * 3 + 1, r[l][1]), w.setUint8(l * 3 + 2, r[l][2]);
|
|
204
|
+
const g = d("PLTE", h), U = R(o, e, n, f), D = d("IDAT", await I(U)), E = d("IEND", new Uint8Array(0));
|
|
205
|
+
return k(u, y, g, D, E);
|
|
76
206
|
}
|
|
77
207
|
export {
|
|
78
|
-
|
|
208
|
+
M as findChunk,
|
|
209
|
+
z as indexedPng,
|
|
210
|
+
O as iterateChunks,
|
|
211
|
+
N as readIHDR,
|
|
212
|
+
H as readPngIHDR,
|
|
213
|
+
v as scanChunk,
|
|
214
|
+
T as validatePngSignature
|
|
79
215
|
};
|
package/dist/reader.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PNG chunk reading and parsing utilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Represents a PNG chunk with its metadata and data
|
|
6
|
+
*/
|
|
7
|
+
export interface PngChunk {
|
|
8
|
+
/** 4-character chunk type name (e.g., "IHDR", "IDAT", "IEND") */
|
|
9
|
+
type: string;
|
|
10
|
+
/** Chunk data (excluding length, type, and CRC) */
|
|
11
|
+
data: DataView;
|
|
12
|
+
/** Offset of the chunk in the original data (points to length field) */
|
|
13
|
+
offset: number;
|
|
14
|
+
/** Total chunk size including length, type, data, and CRC */
|
|
15
|
+
totalSize: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Color type strings for PNG images
|
|
19
|
+
*/
|
|
20
|
+
export type ColorType = "greyscale" | "truecolor" | "indexed" | "greyscale-alpha" | "truecolor-alpha";
|
|
21
|
+
/**
|
|
22
|
+
* Interlace method strings
|
|
23
|
+
*/
|
|
24
|
+
export type InterlaceMethod = "none" | "adam7";
|
|
25
|
+
/**
|
|
26
|
+
* Bits per sample or palette index (1, 2, 4, 8, or 16)
|
|
27
|
+
*/
|
|
28
|
+
export type BitDepth = 1 | 2 | 4 | 8 | 16;
|
|
29
|
+
/**
|
|
30
|
+
* Parsed IHDR (Image Header) chunk data
|
|
31
|
+
*/
|
|
32
|
+
export interface IHDRData {
|
|
33
|
+
/** Image width in pixels */
|
|
34
|
+
width: number;
|
|
35
|
+
/** Image height in pixels */
|
|
36
|
+
height: number;
|
|
37
|
+
/** Bits per sample or palette index (1, 2, 4, 8, or 16) */
|
|
38
|
+
bitDepth: BitDepth;
|
|
39
|
+
/** Color type of the image */
|
|
40
|
+
colorType: ColorType;
|
|
41
|
+
/** Compression method (always 'deflate' for valid PNGs) */
|
|
42
|
+
compressionMethod: "deflate";
|
|
43
|
+
/** Filter method (always 'adaptive' for valid PNGs) */
|
|
44
|
+
filterMethod: "adaptive";
|
|
45
|
+
/** Interlace method */
|
|
46
|
+
interlaceMethod: InterlaceMethod;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Scans a PNG chunk at the given offset in a DataView
|
|
50
|
+
*
|
|
51
|
+
* @param view - DataView of the PNG data
|
|
52
|
+
* @param offset - Byte offset where the chunk starts (at the length field)
|
|
53
|
+
* @returns The parsed chunk information
|
|
54
|
+
* @throws Error if the offset is out of bounds or chunk is malformed
|
|
55
|
+
*/
|
|
56
|
+
export declare function scanChunk(view: DataView, offset: number): PngChunk;
|
|
57
|
+
/**
|
|
58
|
+
* Generator that iterates over all chunks in a PNG DataView
|
|
59
|
+
*
|
|
60
|
+
* @param view - DataView of the PNG data (should start after PNG signature)
|
|
61
|
+
* @param startOffset - Byte offset to start scanning from (default: 0, assumes signature already skipped)
|
|
62
|
+
* @yields Each chunk in sequence
|
|
63
|
+
*/
|
|
64
|
+
export declare function iterateChunks(view: DataView, startOffset?: number): Generator<PngChunk, void, unknown>;
|
|
65
|
+
/**
|
|
66
|
+
* Finds a chunk by its type name and calls a reader function on it
|
|
67
|
+
*
|
|
68
|
+
* @param view - DataView of the PNG data (after signature)
|
|
69
|
+
* @param chunkType - The 4-character chunk type to find (e.g., "IHDR")
|
|
70
|
+
* @param reader - Function to call with the chunk's data DataView
|
|
71
|
+
* @param startOffset - Byte offset to start scanning from (default: 0)
|
|
72
|
+
* @returns The result of the reader function, or undefined if chunk not found
|
|
73
|
+
*/
|
|
74
|
+
export declare function findChunk<T>(view: DataView, chunkType: string, reader: (data: DataView) => T, startOffset?: number): T | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Reads and parses an IHDR chunk from its data
|
|
77
|
+
*
|
|
78
|
+
* @param data - DataView of the IHDR chunk data (13 bytes)
|
|
79
|
+
* @returns Parsed IHDR data with human-readable type strings
|
|
80
|
+
* @throws Error if the IHDR data is invalid
|
|
81
|
+
*/
|
|
82
|
+
export declare function readIHDR(data: DataView): IHDRData;
|
|
83
|
+
/**
|
|
84
|
+
* Validates that the data starts with a valid PNG signature
|
|
85
|
+
*
|
|
86
|
+
* @param view - DataView of the data to check
|
|
87
|
+
* @returns true if the signature is valid
|
|
88
|
+
* @throws Error if the signature is invalid or data is too short
|
|
89
|
+
*/
|
|
90
|
+
export declare function validatePngSignature(view: DataView): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Reads a PNG and extracts the parsed IHDR data
|
|
93
|
+
*
|
|
94
|
+
* This is a convenience function that combines signature validation,
|
|
95
|
+
* chunk scanning, and IHDR parsing.
|
|
96
|
+
*
|
|
97
|
+
* @param data - PNG file data as Uint8Array or ArrayBuffer
|
|
98
|
+
* @returns Parsed IHDR data with human-readable type strings
|
|
99
|
+
* @throws Error if the PNG is invalid or IHDR chunk is not found
|
|
100
|
+
*/
|
|
101
|
+
export declare function readPngIHDR(data: Uint8Array | ArrayBuffer): IHDRData;
|
package/dist/writer.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate the optimal bit depth for indexed PNG based on the maximum color index
|
|
3
|
+
* @param maxIndex - The highest palette index used in the image
|
|
4
|
+
* @returns The bit depth (1, 2, 4, or 8)
|
|
5
|
+
*/
|
|
6
|
+
export declare function calculateBitDepth(maxIndex: number): 1 | 2 | 4 | 8;
|
|
7
|
+
/**
|
|
8
|
+
* Pack pixel indices into bytes according to the specified bit depth
|
|
9
|
+
* @param inputView - DataView of original pixel data (1 byte per pixel)
|
|
10
|
+
* @param width - Image width in pixels
|
|
11
|
+
* @param height - Image height in pixels
|
|
12
|
+
* @param bitDepth - Target bit depth (1, 2, 4, or 8)
|
|
13
|
+
* @returns Packed image data with filter bytes for each row
|
|
14
|
+
*/
|
|
15
|
+
export declare function packPixelData(inputView: DataView, width: number, height: number, bitDepth: 1 | 2 | 4 | 8): Uint8Array<ArrayBuffer>;
|