cross-image 0.2.0 → 0.2.1

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.
Files changed (48) hide show
  1. package/README.md +22 -16
  2. package/esm/mod.d.ts +3 -1
  3. package/esm/mod.js +3 -1
  4. package/esm/src/formats/apng.d.ts +50 -0
  5. package/esm/src/formats/apng.js +364 -0
  6. package/esm/src/formats/bmp.d.ts +0 -6
  7. package/esm/src/formats/bmp.js +24 -47
  8. package/esm/src/formats/dng.js +4 -4
  9. package/esm/src/formats/gif.d.ts +0 -2
  10. package/esm/src/formats/gif.js +10 -16
  11. package/esm/src/formats/ico.d.ts +41 -0
  12. package/esm/src/formats/ico.js +214 -0
  13. package/esm/src/formats/pcx.js +1 -1
  14. package/esm/src/formats/png.d.ts +2 -21
  15. package/esm/src/formats/png.js +5 -429
  16. package/esm/src/formats/png_base.d.ts +108 -0
  17. package/esm/src/formats/png_base.js +487 -0
  18. package/esm/src/formats/webp.d.ts +0 -1
  19. package/esm/src/formats/webp.js +4 -7
  20. package/esm/src/image.js +4 -0
  21. package/esm/src/utils/byte_utils.d.ts +30 -0
  22. package/esm/src/utils/byte_utils.js +50 -0
  23. package/esm/src/utils/gif_encoder.d.ts +3 -2
  24. package/esm/src/utils/gif_encoder.js +115 -48
  25. package/package.json +1 -1
  26. package/script/mod.d.ts +3 -1
  27. package/script/mod.js +6 -2
  28. package/script/src/formats/apng.d.ts +50 -0
  29. package/script/src/formats/apng.js +368 -0
  30. package/script/src/formats/bmp.d.ts +0 -6
  31. package/script/src/formats/bmp.js +24 -47
  32. package/script/src/formats/dng.js +4 -4
  33. package/script/src/formats/gif.d.ts +0 -2
  34. package/script/src/formats/gif.js +10 -16
  35. package/script/src/formats/ico.d.ts +41 -0
  36. package/script/src/formats/ico.js +218 -0
  37. package/script/src/formats/pcx.js +1 -1
  38. package/script/src/formats/png.d.ts +2 -21
  39. package/script/src/formats/png.js +5 -429
  40. package/script/src/formats/png_base.d.ts +108 -0
  41. package/script/src/formats/png_base.js +491 -0
  42. package/script/src/formats/webp.d.ts +0 -1
  43. package/script/src/formats/webp.js +4 -7
  44. package/script/src/image.js +4 -0
  45. package/script/src/utils/byte_utils.d.ts +30 -0
  46. package/script/src/utils/byte_utils.js +58 -0
  47. package/script/src/utils/gif_encoder.d.ts +3 -2
  48. package/script/src/utils/gif_encoder.js +115 -48
@@ -20,15 +20,20 @@ class GIFEncoder {
20
20
  writable: true,
21
21
  value: void 0
22
22
  });
23
- Object.defineProperty(this, "data", {
23
+ Object.defineProperty(this, "frames", {
24
24
  enumerable: true,
25
25
  configurable: true,
26
26
  writable: true,
27
- value: void 0
27
+ value: []
28
28
  });
29
29
  this.width = width;
30
30
  this.height = height;
31
- this.data = data;
31
+ if (data) {
32
+ this.addFrame(data);
33
+ }
34
+ }
35
+ addFrame(data, delay = 0) {
36
+ this.frames.push({ data, delay });
32
37
  }
33
38
  writeBytes(output, bytes) {
34
39
  output.push(...bytes);
@@ -57,7 +62,7 @@ class GIFEncoder {
57
62
  /**
58
63
  * Quantize RGBA image to 256 colors using median cut algorithm
59
64
  */
60
- quantize() {
65
+ quantize(data) {
61
66
  // Simple quantization: collect unique colors and build palette
62
67
  const colorMap = new Map();
63
68
  const colors = [];
@@ -68,10 +73,10 @@ class GIFEncoder {
68
73
  const rgStep = 255 / RG_LEVELS; // Step size for R/G quantization
69
74
  const bStep = 255 / B_LEVELS; // Step size for B quantization
70
75
  // Collect unique colors
71
- for (let i = 0; i < this.data.length; i += 4) {
72
- const r = this.data[i];
73
- const g = this.data[i + 1];
74
- const b = this.data[i + 2];
76
+ for (let i = 0; i < data.length; i += 4) {
77
+ const r = data[i];
78
+ const g = data[i + 1];
79
+ const b = data[i + 2];
75
80
  const key = `${r},${g},${b}`;
76
81
  if (!colorMap.has(key) && colors.length < 256) {
77
82
  colorMap.set(key, colors.length);
@@ -86,12 +91,12 @@ class GIFEncoder {
86
91
  colorMap.clear();
87
92
  colors.length = 0;
88
93
  useColorReduction = true;
89
- for (let i = 0; i < this.data.length; i += 4) {
94
+ for (let i = 0; i < data.length; i += 4) {
90
95
  // Reduce color depth: 3 bits for R/G channels, 2 bits for B channel
91
96
  // This gives us 8 bits total = 256 possible colors
92
- const r = this.quantizeChannel(this.data[i], RG_LEVELS, rgStep);
93
- const g = this.quantizeChannel(this.data[i + 1], RG_LEVELS, rgStep);
94
- const b = this.quantizeChannel(this.data[i + 2], B_LEVELS, bStep);
97
+ const r = this.quantizeChannel(data[i], RG_LEVELS, rgStep);
98
+ const g = this.quantizeChannel(data[i + 1], RG_LEVELS, rgStep);
99
+ const b = this.quantizeChannel(data[i + 2], B_LEVELS, bStep);
95
100
  const key = `${r},${g},${b}`;
96
101
  if (!colorMap.has(key)) {
97
102
  if (colors.length < 256) {
@@ -115,10 +120,10 @@ class GIFEncoder {
115
120
  }
116
121
  // Create indexed data
117
122
  const indexed = new Uint8Array(this.width * this.height);
118
- for (let i = 0, j = 0; i < this.data.length; i += 4, j++) {
119
- let r = this.data[i];
120
- let g = this.data[i + 1];
121
- let b = this.data[i + 2];
123
+ for (let i = 0, j = 0; i < data.length; i += 4, j++) {
124
+ let r = data[i];
125
+ let g = data[i + 1];
126
+ let b = data[i + 2];
122
127
  // Apply color reduction if it was used for building the palette
123
128
  if (useColorReduction) {
124
129
  r = this.quantizeChannel(r, RG_LEVELS, rgStep);
@@ -164,10 +169,14 @@ class GIFEncoder {
164
169
  return Math.max(2, bits);
165
170
  }
166
171
  encode() {
172
+ if (this.frames.length === 0) {
173
+ throw new Error("No frames to encode");
174
+ }
167
175
  const output = [];
168
- // Quantize image
169
- const { palette, indexed } = this.quantize();
170
- const paletteSize = palette.length / 3;
176
+ // Quantize first frame for Global Color Table
177
+ const firstFrame = this.frames[0];
178
+ const { palette: globalPalette, indexed: firstIndexed } = this.quantize(firstFrame.data);
179
+ const paletteSize = globalPalette.length / 3;
171
180
  const bitsPerColor = this.getBitsPerColor(paletteSize);
172
181
  // Header
173
182
  this.writeString(output, "GIF89a");
@@ -190,38 +199,96 @@ class GIFEncoder {
190
199
  // So we need to write that many colors, padding if necessary
191
200
  const gctSize = 1 << bitsPerColor;
192
201
  const paddedPalette = new Uint8Array(gctSize * 3);
193
- paddedPalette.set(palette);
202
+ paddedPalette.set(globalPalette);
194
203
  this.writeBytes(output, paddedPalette);
195
- // Image Descriptor
196
- output.push(0x2c); // Image Separator
197
- // Image position and dimensions
198
- this.writeUint16LE(output, 0); // Left
199
- this.writeUint16LE(output, 0); // Top
200
- this.writeUint16LE(output, this.width);
201
- this.writeUint16LE(output, this.height);
202
- // Packed field:
203
- // - Local Color Table Flag (1 bit): 0
204
- // - Interlace Flag (1 bit): 0
205
- // - Sort Flag (1 bit): 0
206
- // - Reserved (2 bits): 0
207
- // - Size of Local Color Table (3 bits): 0
208
- output.push(0);
209
- // LZW Minimum Code Size
210
- const minCodeSize = Math.max(2, bitsPerColor);
211
- output.push(minCodeSize);
212
- // Compress image data with LZW
213
- const encoder = new lzw_js_1.LZWEncoder(minCodeSize);
214
- const compressed = encoder.compress(indexed);
215
- // Write compressed data in sub-blocks (max 255 bytes per block)
216
- for (let i = 0; i < compressed.length; i += 255) {
217
- const blockSize = Math.min(255, compressed.length - i);
218
- output.push(blockSize);
219
- for (let j = 0; j < blockSize; j++) {
220
- output.push(compressed[i + j]);
204
+ // Netscape Application Extension (Looping)
205
+ if (this.frames.length > 1) {
206
+ output.push(0x21); // Extension Introducer
207
+ output.push(0xff); // Application Extension Label
208
+ output.push(11); // Block Size
209
+ this.writeString(output, "NETSCAPE2.0");
210
+ output.push(3); // Sub-block Size
211
+ output.push(1); // Loop Indicator (1 = loop)
212
+ this.writeUint16LE(output, 0); // Loop Count (0 = infinite)
213
+ output.push(0); // Block Terminator
214
+ }
215
+ // Encode frames
216
+ for (let i = 0; i < this.frames.length; i++) {
217
+ const frame = this.frames[i];
218
+ let indexed;
219
+ let useLocalPalette = false;
220
+ let localPalette = null;
221
+ let localBitsPerColor = bitsPerColor;
222
+ if (i === 0) {
223
+ indexed = firstIndexed;
224
+ }
225
+ else {
226
+ // Quantize subsequent frames
227
+ // For simplicity, we use a Local Color Table for each frame to ensure colors are correct
228
+ const result = this.quantize(frame.data);
229
+ indexed = result.indexed;
230
+ localPalette = result.palette;
231
+ useLocalPalette = true;
232
+ const localPaletteSize = localPalette.length / 3;
233
+ localBitsPerColor = this.getBitsPerColor(localPaletteSize);
221
234
  }
235
+ // Graphic Control Extension
236
+ output.push(0x21); // Extension Introducer
237
+ output.push(0xf9); // Graphic Control Label
238
+ output.push(4); // Byte Size
239
+ // Packed Field
240
+ // Reserved (3 bits)
241
+ // Disposal Method (3 bits): 2 (Restore to background) - usually safe for animation
242
+ // User Input Flag (1 bit): 0
243
+ // Transparent Color Flag (1 bit): 0
244
+ output.push(0x08); // Disposal method 2 (Restore to background)
245
+ // Delay Time (1/100ths of a second)
246
+ // Default to 10 (100ms) if not specified
247
+ const delay = frame.delay > 0 ? Math.round(frame.delay / 10) : 10;
248
+ this.writeUint16LE(output, delay);
249
+ // Transparent Color Index
250
+ output.push(0);
251
+ output.push(0); // Block Terminator
252
+ // Image Descriptor
253
+ output.push(0x2c); // Image Separator
254
+ this.writeUint16LE(output, 0); // Left
255
+ this.writeUint16LE(output, 0); // Top
256
+ this.writeUint16LE(output, this.width);
257
+ this.writeUint16LE(output, this.height);
258
+ // Packed Field
259
+ if (useLocalPalette && localPalette) {
260
+ // LCT Flag: 1
261
+ // Interlace: 0
262
+ // Sort: 0
263
+ // Reserved: 0
264
+ // Size of LCT: localBitsPerColor - 1
265
+ const lctPacked = 0x80 | (localBitsPerColor - 1);
266
+ output.push(lctPacked);
267
+ // Write Local Color Table
268
+ const lctSize = 1 << localBitsPerColor;
269
+ const paddedLct = new Uint8Array(lctSize * 3);
270
+ paddedLct.set(localPalette);
271
+ this.writeBytes(output, paddedLct);
272
+ }
273
+ else {
274
+ output.push(0); // No LCT
275
+ }
276
+ // LZW Minimum Code Size
277
+ const minCodeSize = Math.max(2, useLocalPalette ? localBitsPerColor : bitsPerColor);
278
+ output.push(minCodeSize);
279
+ // Compress image data with LZW
280
+ const encoder = new lzw_js_1.LZWEncoder(minCodeSize);
281
+ const compressed = encoder.compress(indexed);
282
+ // Write compressed data in sub-blocks (max 255 bytes per block)
283
+ for (let k = 0; k < compressed.length; k += 255) {
284
+ const blockSize = Math.min(255, compressed.length - k);
285
+ output.push(blockSize);
286
+ for (let j = 0; j < blockSize; j++) {
287
+ output.push(compressed[k + j]);
288
+ }
289
+ }
290
+ output.push(0); // Block Terminator
222
291
  }
223
- // Block Terminator
224
- output.push(0);
225
292
  // Trailer
226
293
  output.push(0x3b);
227
294
  return new Uint8Array(output);