cross-image 0.2.4 → 0.4.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 (117) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +615 -333
  3. package/esm/mod.d.ts +6 -4
  4. package/esm/mod.js +4 -2
  5. package/esm/src/formats/apng.d.ts +7 -5
  6. package/esm/src/formats/apng.js +15 -10
  7. package/esm/src/formats/ascii.d.ts +3 -3
  8. package/esm/src/formats/ascii.js +1 -1
  9. package/esm/src/formats/avif.d.ts +3 -3
  10. package/esm/src/formats/avif.js +17 -7
  11. package/esm/src/formats/bmp.d.ts +3 -3
  12. package/esm/src/formats/bmp.js +2 -2
  13. package/esm/src/formats/dng.d.ts +1 -1
  14. package/esm/src/formats/dng.js +1 -1
  15. package/esm/src/formats/gif.d.ts +5 -5
  16. package/esm/src/formats/gif.js +17 -13
  17. package/esm/src/formats/heic.d.ts +3 -3
  18. package/esm/src/formats/heic.js +17 -7
  19. package/esm/src/formats/ico.d.ts +3 -3
  20. package/esm/src/formats/ico.js +4 -4
  21. package/esm/src/formats/jpeg.d.ts +3 -3
  22. package/esm/src/formats/jpeg.js +23 -11
  23. package/esm/src/formats/pam.d.ts +3 -3
  24. package/esm/src/formats/pam.js +2 -2
  25. package/esm/src/formats/pcx.d.ts +3 -3
  26. package/esm/src/formats/pcx.js +2 -2
  27. package/esm/src/formats/png.d.ts +4 -3
  28. package/esm/src/formats/png.js +9 -3
  29. package/esm/src/formats/png_base.d.ts +42 -1
  30. package/esm/src/formats/png_base.js +200 -10
  31. package/esm/src/formats/ppm.d.ts +3 -3
  32. package/esm/src/formats/ppm.js +2 -2
  33. package/esm/src/formats/tiff.d.ts +7 -18
  34. package/esm/src/formats/tiff.js +162 -27
  35. package/esm/src/formats/webp.d.ts +3 -3
  36. package/esm/src/formats/webp.js +11 -8
  37. package/esm/src/image.d.ts +26 -3
  38. package/esm/src/image.js +66 -22
  39. package/esm/src/types.d.ts +122 -4
  40. package/esm/src/utils/base64.d.ts +32 -0
  41. package/esm/src/utils/base64.js +173 -0
  42. package/esm/src/utils/gif_decoder.d.ts +4 -1
  43. package/esm/src/utils/gif_decoder.js +91 -65
  44. package/esm/src/utils/gif_encoder.d.ts +3 -1
  45. package/esm/src/utils/gif_encoder.js +4 -2
  46. package/esm/src/utils/image_processing.d.ts +31 -0
  47. package/esm/src/utils/image_processing.js +232 -70
  48. package/esm/src/utils/jpeg_decoder.d.ts +17 -4
  49. package/esm/src/utils/jpeg_decoder.js +448 -83
  50. package/esm/src/utils/jpeg_encoder.d.ts +15 -1
  51. package/esm/src/utils/jpeg_encoder.js +263 -24
  52. package/esm/src/utils/resize.js +51 -20
  53. package/esm/src/utils/tiff_deflate.d.ts +18 -0
  54. package/esm/src/utils/tiff_deflate.js +27 -0
  55. package/esm/src/utils/tiff_packbits.d.ts +24 -0
  56. package/esm/src/utils/tiff_packbits.js +90 -0
  57. package/esm/src/utils/webp_decoder.d.ts +3 -1
  58. package/esm/src/utils/webp_decoder.js +144 -63
  59. package/esm/src/utils/webp_encoder.js +5 -11
  60. package/package.json +1 -1
  61. package/script/mod.d.ts +6 -4
  62. package/script/mod.js +13 -3
  63. package/script/src/formats/apng.d.ts +7 -5
  64. package/script/src/formats/apng.js +15 -10
  65. package/script/src/formats/ascii.d.ts +3 -3
  66. package/script/src/formats/ascii.js +1 -1
  67. package/script/src/formats/avif.d.ts +3 -3
  68. package/script/src/formats/avif.js +17 -7
  69. package/script/src/formats/bmp.d.ts +3 -3
  70. package/script/src/formats/bmp.js +2 -2
  71. package/script/src/formats/dng.d.ts +1 -1
  72. package/script/src/formats/dng.js +1 -1
  73. package/script/src/formats/gif.d.ts +5 -5
  74. package/script/src/formats/gif.js +17 -13
  75. package/script/src/formats/heic.d.ts +3 -3
  76. package/script/src/formats/heic.js +17 -7
  77. package/script/src/formats/ico.d.ts +3 -3
  78. package/script/src/formats/ico.js +4 -4
  79. package/script/src/formats/jpeg.d.ts +3 -3
  80. package/script/src/formats/jpeg.js +23 -11
  81. package/script/src/formats/pam.d.ts +3 -3
  82. package/script/src/formats/pam.js +2 -2
  83. package/script/src/formats/pcx.d.ts +3 -3
  84. package/script/src/formats/pcx.js +2 -2
  85. package/script/src/formats/png.d.ts +4 -3
  86. package/script/src/formats/png.js +9 -3
  87. package/script/src/formats/png_base.d.ts +42 -1
  88. package/script/src/formats/png_base.js +200 -10
  89. package/script/src/formats/ppm.d.ts +3 -3
  90. package/script/src/formats/ppm.js +2 -2
  91. package/script/src/formats/tiff.d.ts +7 -18
  92. package/script/src/formats/tiff.js +162 -27
  93. package/script/src/formats/webp.d.ts +3 -3
  94. package/script/src/formats/webp.js +11 -8
  95. package/script/src/image.d.ts +26 -3
  96. package/script/src/image.js +64 -20
  97. package/script/src/types.d.ts +122 -4
  98. package/script/src/utils/base64.d.ts +32 -0
  99. package/script/src/utils/base64.js +179 -0
  100. package/script/src/utils/gif_decoder.d.ts +4 -1
  101. package/script/src/utils/gif_decoder.js +91 -65
  102. package/script/src/utils/gif_encoder.d.ts +3 -1
  103. package/script/src/utils/gif_encoder.js +4 -2
  104. package/script/src/utils/image_processing.d.ts +31 -0
  105. package/script/src/utils/image_processing.js +236 -70
  106. package/script/src/utils/jpeg_decoder.d.ts +17 -4
  107. package/script/src/utils/jpeg_decoder.js +448 -83
  108. package/script/src/utils/jpeg_encoder.d.ts +15 -1
  109. package/script/src/utils/jpeg_encoder.js +263 -24
  110. package/script/src/utils/resize.js +51 -20
  111. package/script/src/utils/tiff_deflate.d.ts +18 -0
  112. package/script/src/utils/tiff_deflate.js +31 -0
  113. package/script/src/utils/tiff_packbits.d.ts +24 -0
  114. package/script/src/utils/tiff_packbits.js +94 -0
  115. package/script/src/utils/webp_decoder.d.ts +3 -1
  116. package/script/src/utils/webp_decoder.js +144 -63
  117. package/script/src/utils/webp_encoder.js +5 -11
@@ -43,7 +43,48 @@ export declare abstract class PNGBase {
43
43
  /**
44
44
  * Filter PNG data for encoding (using filter type 0 - None)
45
45
  */
46
- protected filterData(data: Uint8Array, width: number, height: number): Uint8Array;
46
+ /**
47
+ * Apply PNG filter to image data based on compression level
48
+ * @param data Raw RGBA pixel data
49
+ * @param width Image width
50
+ * @param height Image height
51
+ * @param compressionLevel Compression level (0-9, default 6)
52
+ * @returns Filtered data with filter type byte per scanline
53
+ */
54
+ protected filterData(data: Uint8Array, width: number, height: number, compressionLevel?: number): Uint8Array;
55
+ /**
56
+ * Apply filter type 0 (None) - no filtering
57
+ */
58
+ private applyNoFilter;
59
+ /**
60
+ * Apply filter type 1 (Sub) - subtract left pixel
61
+ */
62
+ private applySubFilter;
63
+ /**
64
+ * Apply filter type 2 (Up) - subtract above pixel
65
+ */
66
+ private applyUpFilter;
67
+ /**
68
+ * Apply filter type 3 (Average) - subtract average of left and above
69
+ */
70
+ private applyAverageFilter;
71
+ /**
72
+ * Apply filter type 4 (Paeth) - Paeth predictor
73
+ */
74
+ private applyPaethFilter;
75
+ /**
76
+ * Calculate sum of absolute differences for a filtered scanline
77
+ * Lower values indicate better compression potential
78
+ */
79
+ private calculateFilterScore;
80
+ /**
81
+ * Apply adaptive filtering - choose best filter per scanline
82
+ */
83
+ private applyAdaptiveFilter;
84
+ /**
85
+ * Filter a single scanline with specified filter type
86
+ */
87
+ private filterScanline;
47
88
  /**
48
89
  * Get bytes per pixel for a given color type and bit depth
49
90
  */
@@ -108,9 +108,7 @@ class PNGBase {
108
108
  for (let x = 0; x < scanline.length; x++) {
109
109
  const left = x >= bytesPerPixel ? scanline[x - bytesPerPixel] : 0;
110
110
  const above = prevLine ? prevLine[x] : 0;
111
- const upperLeft = (x >= bytesPerPixel && prevLine)
112
- ? prevLine[x - bytesPerPixel]
113
- : 0;
111
+ const upperLeft = (x >= bytesPerPixel && prevLine) ? prevLine[x - bytesPerPixel] : 0;
114
112
  switch (filterType) {
115
113
  case 0: // None
116
114
  break;
@@ -124,8 +122,7 @@ class PNGBase {
124
122
  scanline[x] = (scanline[x] + Math.floor((left + above) / 2)) & 0xff;
125
123
  break;
126
124
  case 4: // Paeth
127
- scanline[x] =
128
- (scanline[x] + this.paethPredictor(left, above, upperLeft)) & 0xff;
125
+ scanline[x] = (scanline[x] + this.paethPredictor(left, above, upperLeft)) & 0xff;
129
126
  break;
130
127
  }
131
128
  }
@@ -147,18 +144,211 @@ class PNGBase {
147
144
  /**
148
145
  * Filter PNG data for encoding (using filter type 0 - None)
149
146
  */
150
- filterData(data, width, height) {
151
- // Use filter type 0 (None) for simplicity
152
- const filtered = new Uint8Array(height * (1 + width * 4));
147
+ /**
148
+ * Apply PNG filter to image data based on compression level
149
+ * @param data Raw RGBA pixel data
150
+ * @param width Image width
151
+ * @param height Image height
152
+ * @param compressionLevel Compression level (0-9, default 6)
153
+ * @returns Filtered data with filter type byte per scanline
154
+ */
155
+ filterData(data, width, height, compressionLevel = 6) {
156
+ // Choose filtering strategy based on compression level
157
+ if (compressionLevel <= 2) {
158
+ // Fast: No filtering
159
+ return this.applyNoFilter(data, width, height);
160
+ }
161
+ else if (compressionLevel <= 6) {
162
+ // Balanced: Sub filter
163
+ return this.applySubFilter(data, width, height);
164
+ }
165
+ else {
166
+ // Best: Adaptive filtering (choose best filter per scanline)
167
+ return this.applyAdaptiveFilter(data, width, height);
168
+ }
169
+ }
170
+ /**
171
+ * Apply filter type 0 (None) - no filtering
172
+ */
173
+ applyNoFilter(data, width, height) {
174
+ const bytesPerScanline = width * 4;
175
+ const filtered = new Uint8Array(height * (1 + bytesPerScanline));
153
176
  let pos = 0;
154
177
  for (let y = 0; y < height; y++) {
155
178
  filtered[pos++] = 0; // Filter type: None
156
- for (let x = 0; x < width * 4; x++) {
157
- filtered[pos++] = data[y * width * 4 + x];
179
+ const scanlineStart = y * bytesPerScanline;
180
+ for (let x = 0; x < bytesPerScanline; x++) {
181
+ filtered[pos++] = data[scanlineStart + x];
182
+ }
183
+ }
184
+ return filtered;
185
+ }
186
+ /**
187
+ * Apply filter type 1 (Sub) - subtract left pixel
188
+ */
189
+ applySubFilter(data, width, height) {
190
+ const bytesPerScanline = width * 4;
191
+ const filtered = new Uint8Array(height * (1 + bytesPerScanline));
192
+ let pos = 0;
193
+ for (let y = 0; y < height; y++) {
194
+ filtered[pos++] = 1; // Filter type: Sub
195
+ const scanlineStart = y * bytesPerScanline;
196
+ for (let x = 0; x < bytesPerScanline; x++) {
197
+ const current = data[scanlineStart + x];
198
+ const left = x >= 4 ? data[scanlineStart + x - 4] : 0;
199
+ filtered[pos++] = (current - left) & 0xff;
200
+ }
201
+ }
202
+ return filtered;
203
+ }
204
+ /**
205
+ * Apply filter type 2 (Up) - subtract above pixel
206
+ */
207
+ applyUpFilter(data, width, height) {
208
+ const bytesPerScanline = width * 4;
209
+ const filtered = new Uint8Array(height * (1 + bytesPerScanline));
210
+ let pos = 0;
211
+ for (let y = 0; y < height; y++) {
212
+ filtered[pos++] = 2; // Filter type: Up
213
+ const scanlineStart = y * bytesPerScanline;
214
+ const prevScanlineStart = (y - 1) * bytesPerScanline;
215
+ for (let x = 0; x < bytesPerScanline; x++) {
216
+ const current = data[scanlineStart + x];
217
+ const up = y > 0 ? data[prevScanlineStart + x] : 0;
218
+ filtered[pos++] = (current - up) & 0xff;
219
+ }
220
+ }
221
+ return filtered;
222
+ }
223
+ /**
224
+ * Apply filter type 3 (Average) - subtract average of left and above
225
+ */
226
+ applyAverageFilter(data, width, height) {
227
+ const bytesPerScanline = width * 4;
228
+ const filtered = new Uint8Array(height * (1 + bytesPerScanline));
229
+ let pos = 0;
230
+ for (let y = 0; y < height; y++) {
231
+ filtered[pos++] = 3; // Filter type: Average
232
+ const scanlineStart = y * bytesPerScanline;
233
+ const prevScanlineStart = (y - 1) * bytesPerScanline;
234
+ for (let x = 0; x < bytesPerScanline; x++) {
235
+ const current = data[scanlineStart + x];
236
+ const left = x >= 4 ? data[scanlineStart + x - 4] : 0;
237
+ const up = y > 0 ? data[prevScanlineStart + x] : 0;
238
+ const avg = Math.floor((left + up) / 2);
239
+ filtered[pos++] = (current - avg) & 0xff;
158
240
  }
159
241
  }
160
242
  return filtered;
161
243
  }
244
+ /**
245
+ * Apply filter type 4 (Paeth) - Paeth predictor
246
+ */
247
+ applyPaethFilter(data, width, height) {
248
+ const bytesPerScanline = width * 4;
249
+ const filtered = new Uint8Array(height * (1 + bytesPerScanline));
250
+ let pos = 0;
251
+ for (let y = 0; y < height; y++) {
252
+ filtered[pos++] = 4; // Filter type: Paeth
253
+ const scanlineStart = y * bytesPerScanline;
254
+ const prevScanlineStart = (y - 1) * bytesPerScanline;
255
+ for (let x = 0; x < bytesPerScanline; x++) {
256
+ const current = data[scanlineStart + x];
257
+ const left = x >= 4 ? data[scanlineStart + x - 4] : 0;
258
+ const up = y > 0 ? data[prevScanlineStart + x] : 0;
259
+ const upLeft = (y > 0 && x >= 4) ? data[prevScanlineStart + x - 4] : 0;
260
+ const paeth = this.paethPredictor(left, up, upLeft);
261
+ filtered[pos++] = (current - paeth) & 0xff;
262
+ }
263
+ }
264
+ return filtered;
265
+ }
266
+ /**
267
+ * Calculate sum of absolute differences for a filtered scanline
268
+ * Lower values indicate better compression potential
269
+ */
270
+ calculateFilterScore(filtered) {
271
+ let sum = 0;
272
+ for (let i = 1; i < filtered.length; i++) {
273
+ const byte = filtered[i];
274
+ // Penalize larger absolute values
275
+ sum += byte < 128 ? byte : (256 - byte);
276
+ }
277
+ return sum;
278
+ }
279
+ /**
280
+ * Apply adaptive filtering - choose best filter per scanline
281
+ */
282
+ applyAdaptiveFilter(data, width, height) {
283
+ const bytesPerScanline = width * 4;
284
+ const filtered = new Uint8Array(height * (1 + bytesPerScanline));
285
+ let outPos = 0;
286
+ // Try each filter type and choose the best for each scanline
287
+ const filters = [
288
+ (y) => this.filterScanline(data, y, width, 0), // None
289
+ (y) => this.filterScanline(data, y, width, 1), // Sub
290
+ (y) => this.filterScanline(data, y, width, 2), // Up
291
+ (y) => this.filterScanline(data, y, width, 3), // Average
292
+ (y) => this.filterScanline(data, y, width, 4), // Paeth
293
+ ];
294
+ for (let y = 0; y < height; y++) {
295
+ let bestFilter = null;
296
+ let bestScore = Infinity;
297
+ // Try each filter type
298
+ for (const filterFn of filters) {
299
+ const result = filterFn(y);
300
+ const score = this.calculateFilterScore(result);
301
+ if (score < bestScore) {
302
+ bestScore = score;
303
+ bestFilter = result;
304
+ }
305
+ }
306
+ // Copy best filter result
307
+ if (bestFilter) {
308
+ filtered.set(bestFilter, outPos);
309
+ outPos += bestFilter.length;
310
+ }
311
+ }
312
+ return filtered;
313
+ }
314
+ /**
315
+ * Filter a single scanline with specified filter type
316
+ */
317
+ filterScanline(data, y, width, filterType) {
318
+ const bytesPerScanline = width * 4;
319
+ const result = new Uint8Array(1 + bytesPerScanline);
320
+ result[0] = filterType;
321
+ const scanlineStart = y * bytesPerScanline;
322
+ const prevScanlineStart = (y - 1) * bytesPerScanline;
323
+ for (let x = 0; x < bytesPerScanline; x++) {
324
+ const current = data[scanlineStart + x];
325
+ const left = x >= 4 ? data[scanlineStart + x - 4] : 0;
326
+ const up = y > 0 ? data[prevScanlineStart + x] : 0;
327
+ const upLeft = (y > 0 && x >= 4) ? data[prevScanlineStart + x - 4] : 0;
328
+ let filtered;
329
+ switch (filterType) {
330
+ case 0: // None
331
+ filtered = current;
332
+ break;
333
+ case 1: // Sub
334
+ filtered = (current - left) & 0xff;
335
+ break;
336
+ case 2: // Up
337
+ filtered = (current - up) & 0xff;
338
+ break;
339
+ case 3: // Average
340
+ filtered = (current - Math.floor((left + up) / 2)) & 0xff;
341
+ break;
342
+ case 4: // Paeth
343
+ filtered = (current - this.paethPredictor(left, up, upLeft)) & 0xff;
344
+ break;
345
+ default:
346
+ filtered = current;
347
+ }
348
+ result[x + 1] = filtered;
349
+ }
350
+ return result;
351
+ }
162
352
  /**
163
353
  * Get bytes per pixel for a given color type and bit depth
164
354
  */
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
1
+ import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata } from "../types.js";
2
2
  /**
3
3
  * PPM format handler
4
4
  * Implements the Netpbm PPM (Portable PixMap) format.
@@ -34,14 +34,14 @@ export declare class PPMFormat implements ImageFormat {
34
34
  * @param data Raw PPM image data
35
35
  * @returns Decoded image data with RGBA pixels
36
36
  */
37
- decode(data: Uint8Array): Promise<ImageData>;
37
+ decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
38
38
  /**
39
39
  * Encode RGBA image data to PPM format (P6 binary)
40
40
  * Note: Alpha channel is ignored as PPM doesn't support transparency
41
41
  * @param imageData Image data to encode
42
42
  * @returns Encoded PPM image bytes
43
43
  */
44
- encode(imageData: ImageData): Promise<Uint8Array>;
44
+ encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
45
45
  /**
46
46
  * Check if a byte is whitespace (space, tab, CR, LF)
47
47
  */
@@ -59,7 +59,7 @@ class PPMFormat {
59
59
  * @param data Raw PPM image data
60
60
  * @returns Decoded image data with RGBA pixels
61
61
  */
62
- decode(data) {
62
+ decode(data, _options) {
63
63
  if (!this.canDecode(data)) {
64
64
  throw new Error("Invalid PPM signature");
65
65
  }
@@ -211,7 +211,7 @@ class PPMFormat {
211
211
  * @param imageData Image data to encode
212
212
  * @returns Encoded PPM image bytes
213
213
  */
214
- encode(imageData) {
214
+ encode(imageData, _options) {
215
215
  const { width, height, data } = imageData;
216
216
  // Validate input
217
217
  if (data.length !== width * height * 4) {
@@ -1,20 +1,9 @@
1
- import type { ImageData, ImageFormat, ImageMetadata, MultiFrameImageData } from "../types.js";
2
- /**
3
- * Options for TIFF encoding
4
- */
5
- export interface TIFFEncodeOptions {
6
- /** Compression method: "none" for uncompressed (default), "lzw" for LZW compression */
7
- compression?: "none" | "lzw";
8
- /** Encode as grayscale instead of RGB/RGBA */
9
- grayscale?: boolean;
10
- /** Encode as RGB without alpha channel (ignored if grayscale is true) */
11
- rgb?: boolean;
12
- }
1
+ import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, MultiFrameImageData } from "../types.js";
13
2
  /**
14
3
  * TIFF format handler
15
- * Implements pure-JS TIFF decoder for uncompressed and LZW-compressed RGB/RGBA images
16
- * and encoder for uncompressed and LZW-compressed RGBA TIFFs. Falls back to ImageDecoder
17
- * for other compressed TIFFs (JPEG, PackBits, etc.)
4
+ * Implements pure-JS TIFF decoder for uncompressed, LZW, PackBits, and Deflate-compressed RGB/RGBA images
5
+ * and encoder for uncompressed, LZW, PackBits, and Deflate-compressed RGBA TIFFs. Falls back to ImageDecoder
6
+ * for JPEG-compressed TIFFs.
18
7
  * Supports multi-page TIFF files.
19
8
  */
20
9
  export declare class TIFFFormat implements ImageFormat {
@@ -38,12 +27,12 @@ export declare class TIFFFormat implements ImageFormat {
38
27
  * @param data Raw TIFF image data
39
28
  * @returns Decoded image data with RGBA pixels of first page
40
29
  */
41
- decode(data: Uint8Array): Promise<ImageData>;
30
+ decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
42
31
  encode(imageData: ImageData, options?: unknown): Promise<Uint8Array>;
43
32
  /**
44
33
  * Decode all pages from a multi-page TIFF
45
34
  */
46
- decodeFrames(data: Uint8Array): Promise<MultiFrameImageData>;
35
+ decodeFrames(data: Uint8Array, _options?: ImageDecoderOptions): Promise<MultiFrameImageData>;
47
36
  /**
48
37
  * Encode multi-page TIFF
49
38
  */
@@ -65,7 +54,7 @@ export declare class TIFFFormat implements ImageFormat {
65
54
  private decodeUsingRuntime;
66
55
  private readString;
67
56
  /**
68
- * Pure JavaScript TIFF decoder for uncompressed and LZW-compressed RGB/RGBA images
57
+ * Pure JavaScript TIFF decoder for uncompressed, LZW, PackBits, and Deflate-compressed RGB/RGBA images
69
58
  * Returns null if the TIFF uses unsupported features
70
59
  */
71
60
  private decodePureJS;