@levischuck/tiny-png 0.1.0 → 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/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { scanChunk, iterateChunks, findChunk, readIHDR, validatePngSignature, readPngIHDR, type PngChunk, type ColorType, type InterlaceMethod, type IHDRData, } from './reader.ts';
1
2
  /**
2
3
  * Generate an indexed-color PNG image, up to 256 colors are supported
3
4
  *
package/dist/index.js CHANGED
@@ -1,87 +1,215 @@
1
- function V(...n) {
2
- const t = new Uint8Array(
3
- n.reduceRight((r, o) => r + o.length, 0)
1
+ function k(...t) {
2
+ const e = new Uint8Array(
3
+ t.reduceRight((r, o) => r + o.length, 0)
4
4
  );
5
- let e = 0;
6
- for (const r of n)
7
- t.set(r, e), e += r.length;
8
- return t;
9
- }
10
- async function k(n) {
11
- const t = new CompressionStream("deflate"), e = t.writable.getWriter();
12
- e.write(n), e.close();
13
- const r = t.readable.getReader(), o = [];
14
- let u = 0;
5
+ let n = 0;
6
+ for (const r of t)
7
+ e.set(r, n), n += r.length;
8
+ return e;
9
+ }
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: l, value: a } = await r.read();
17
- if (l) break;
18
- o.push(a), u += a.length;
16
+ const { done: u, value: c } = await r.read();
17
+ if (u) break;
18
+ o.push(c), i += c.length;
19
19
  }
20
- const i = new Uint8Array(u);
20
+ const s = new Uint8Array(i);
21
21
  let f = 0;
22
- for (const l of o)
23
- i.set(l, f), f += l.length;
24
- return i;
22
+ for (const u of o)
23
+ s.set(u, f), f += u.length;
24
+ return s;
25
25
  }
26
- const d = new Uint32Array(256);
27
- for (let n = 0; n < 256; n++) {
28
- let t = n;
29
- for (let e = 0; e < 8; e++)
30
- t = t & 1 ? 3988292384 ^ t >>> 1 : t >>> 1;
31
- d[n] = t;
32
- }
33
- function x(n) {
34
- let t = 4294967295;
35
- for (let e = 0; e < n.length; e++)
36
- t = d[(t ^ n[e]) & 255] ^ t >>> 8;
37
- return (t ^ 4294967295) >>> 0;
38
- }
39
- const E = new TextEncoder();
40
- function p(n, t) {
41
- const e = new Uint8Array(8 + t.length + 4), r = new DataView(e.buffer);
42
- r.setUint32(0, t.length), e.set(E.encode(n), 4), e.set(t, 8);
43
- const o = x(e.subarray(4, e.length - 4));
44
- return r.setUint32(e.length - 4, o), e;
45
- }
46
- function R(n) {
47
- return n <= 1 ? 1 : n <= 3 ? 2 : n <= 15 ? 4 : 8;
48
- }
49
- function m(n, t, e, r) {
50
- const o = 8 / r, i = Math.ceil(t / o) + 1, f = new Uint8Array(i * e), l = new DataView(f.buffer);
51
- for (let a = 0; a < e; a++) {
52
- l.setUint8(a * i, 0);
53
- for (let s = 0; s < t; s++) {
54
- const g = n.getUint8(a * t + s), w = a * i + 1 + Math.floor(s / o), U = (o - 1 - s % o) * r, y = l.getUint8(w);
55
- l.setUint8(w, y | g << U);
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
+ e = e & 1 ? 3988292384 ^ e >>> 1 : e >>> 1;
31
+ b[t] = e;
32
+ }
33
+ function $(t) {
34
+ let e = 4294967295;
35
+ for (let n = 0; n < t.length; n++)
36
+ e = b[(e ^ t[n]) & 255] ^ e >>> 8;
37
+ return (e ^ 4294967295) >>> 0;
38
+ }
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
56
  }
57
57
  }
58
58
  return f;
59
59
  }
60
- async function P(n, t, e, r) {
61
- const o = new DataView(n.buffer), u = o.byteLength;
62
- if (u === 0)
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;
131
+ }
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)
147
+ throw new Error(
148
+ `Invalid compression method: ${i}. Only 0 (deflate) is supported.`
149
+ );
150
+ if (s !== 0)
151
+ throw new Error(
152
+ `Invalid filter method: ${s}. Only 0 (adaptive) is supported.`
153
+ );
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)
63
185
  throw new Error("Received empty input");
64
- if (u !== t * e)
186
+ if (i !== e * n)
65
187
  throw new Error(
66
- `Input does not match dimensions ${t}x${e}. Only ${u} bytes were given when ${t * e} are expected!`
188
+ `Input does not match dimensions ${e}x${n}. Only ${i} bytes were given when ${e * n} are expected!`
67
189
  );
68
- let i = 0;
69
- for (let c = 0; c < u; c++) {
70
- const h = o.getUint8(c);
71
- h > i && (i = h);
190
+ let s = 0;
191
+ for (let l = 0; l < i; l++) {
192
+ const p = o.getUint8(l);
193
+ p > s && (s = p);
72
194
  }
73
- if (r.length <= i)
195
+ if (r.length <= s)
74
196
  throw new Error(
75
- `Color palette does not have enough colors (${i + 1}). Only ${r.length} were given!`
197
+ `Color palette does not have enough colors (${s + 1}). Only ${r.length} were given!`
76
198
  );
77
- const f = R(i), l = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), a = new Uint8Array(13), s = new DataView(a.buffer);
78
- s.setUint32(0, t), s.setUint32(4, e), s.setUint8(8, f), s.setUint8(9, 3), s.setUint8(10, 0), s.setUint8(11, 0), s.setUint8(12, 0);
79
- const g = p("IHDR", a), w = new Uint8Array(r.length * 3), U = new DataView(w.buffer);
80
- for (let c = 0; c <= i; c++)
81
- U.setUint8(c * 3, r[c][0]), U.setUint8(c * 3 + 1, r[c][1]), U.setUint8(c * 3 + 2, r[c][2]);
82
- const y = p("PLTE", w), b = m(o, t, e, f), D = p("IDAT", await k(b)), A = p("IEND", new Uint8Array(0));
83
- return V(l, g, y, D, A);
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);
84
206
  }
85
207
  export {
86
- P as indexedPng
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
87
215
  };
@@ -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;
@@ -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>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@levischuck/tiny-png",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",