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
@@ -37,7 +37,7 @@ export class JPEGFormat {
37
37
  * @param data Raw JPEG image data
38
38
  * @returns Decoded image data with RGBA pixels
39
39
  */
40
- async decode(data) {
40
+ async decode(data, settings) {
41
41
  if (!this.canDecode(data)) {
42
42
  throw new Error("Invalid JPEG signature");
43
43
  }
@@ -95,7 +95,7 @@ export class JPEGFormat {
95
95
  validateImageDimensions(width, height);
96
96
  // For a pure JS implementation, we'd need to implement full JPEG decoding
97
97
  // which is very complex. Instead, we'll use the browser/runtime's decoder.
98
- const rgba = await this.decodeUsingRuntime(data, width, height);
98
+ const rgba = await this.decodeUsingRuntime(data, width, height, settings);
99
99
  return {
100
100
  width,
101
101
  height,
@@ -108,10 +108,13 @@ export class JPEGFormat {
108
108
  * @param imageData Image data to encode
109
109
  * @returns Encoded JPEG image bytes
110
110
  */
111
- async encode(imageData) {
111
+ async encode(imageData, options) {
112
112
  const { width, height, data, metadata } = imageData;
113
+ const requestedQuality = options?.quality;
114
+ const requestedProgressive = options?.progressive;
113
115
  // Try to use runtime encoding if available (better quality)
114
- if (typeof OffscreenCanvas !== "undefined") {
116
+ // Note: progressive output is only supported by the pure-JS encoder.
117
+ if (!requestedProgressive && typeof OffscreenCanvas !== "undefined") {
115
118
  try {
116
119
  const canvas = new OffscreenCanvas(width, height);
117
120
  const ctx = canvas.getContext("2d");
@@ -120,9 +123,12 @@ export class JPEGFormat {
120
123
  const imgDataData = new Uint8ClampedArray(data);
121
124
  imgData.data.set(imgDataData);
122
125
  ctx.putImageData(imgData, 0, 0);
126
+ const quality = requestedQuality === undefined
127
+ ? 0.9
128
+ : Math.max(1, Math.min(100, requestedQuality)) / 100;
123
129
  const blob = await canvas.convertToBlob({
124
130
  type: "image/jpeg",
125
- quality: 0.9,
131
+ quality,
126
132
  });
127
133
  const arrayBuffer = await blob.arrayBuffer();
128
134
  const encoded = new Uint8Array(arrayBuffer);
@@ -141,7 +147,10 @@ export class JPEGFormat {
141
147
  const { JPEGEncoder } = await import("../utils/jpeg_encoder.js");
142
148
  const dpiX = metadata?.dpiX ?? 72;
143
149
  const dpiY = metadata?.dpiY ?? 72;
144
- const encoder = new JPEGEncoder(85); // Quality 85
150
+ const encoder = new JPEGEncoder({
151
+ quality: requestedQuality,
152
+ progressive: requestedProgressive,
153
+ });
145
154
  const encoded = encoder.encode(width, height, data, dpiX, dpiY);
146
155
  // Add EXIF metadata if present
147
156
  if (metadata && Object.keys(metadata).length > 0) {
@@ -185,9 +194,10 @@ export class JPEGFormat {
185
194
  result.set(encoded.slice(pos), pos + app1.length);
186
195
  return result;
187
196
  }
188
- async decodeUsingRuntime(data, _width, _height) {
197
+ async decodeUsingRuntime(data, _width, _height, settings) {
189
198
  // Try to use ImageDecoder API if available (Deno, modern browsers)
190
- if (typeof ImageDecoder !== "undefined") {
199
+ if (settings?.runtimeDecoding !== "never" &&
200
+ typeof ImageDecoder !== "undefined") {
191
201
  try {
192
202
  const decoder = new ImageDecoder({ data, type: "image/jpeg" });
193
203
  const result = await decoder.decode();
@@ -202,15 +212,17 @@ export class JPEGFormat {
202
212
  bitmap.close();
203
213
  return new Uint8Array(imageData.data.buffer);
204
214
  }
205
- catch (error) {
215
+ catch (_error) {
206
216
  // ImageDecoder API failed, fall through to pure JS decoder
207
- console.warn("JPEG decoding with ImageDecoder failed, using pure JS decoder:", error);
208
217
  }
209
218
  }
210
219
  // Fallback to pure JavaScript decoder
211
220
  try {
212
221
  const { JPEGDecoder } = await import("../utils/jpeg_decoder.js");
213
- const decoder = new JPEGDecoder(data);
222
+ const decoder = new JPEGDecoder(data, {
223
+ tolerantDecoding: settings?.tolerantDecoding ?? true,
224
+ onWarning: settings?.onWarning,
225
+ });
214
226
  return decoder.decode();
215
227
  }
216
228
  catch (error) {
@@ -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
  * PAM format handler
4
4
  * Implements the Netpbm PAM (Portable Arbitrary Map) format.
@@ -32,13 +32,13 @@ export declare class PAMFormat implements ImageFormat {
32
32
  * @param data Raw PAM image data
33
33
  * @returns Decoded image data with RGBA pixels
34
34
  */
35
- decode(data: Uint8Array): Promise<ImageData>;
35
+ decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
36
36
  /**
37
37
  * Encode RGBA image data to PAM format
38
38
  * @param imageData Image data to encode
39
39
  * @returns Encoded PAM image bytes
40
40
  */
41
- encode(imageData: ImageData): Promise<Uint8Array>;
41
+ encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
42
42
  /**
43
43
  * Get the list of metadata fields supported by PAM format
44
44
  */
@@ -53,7 +53,7 @@ export class PAMFormat {
53
53
  * @param data Raw PAM image data
54
54
  * @returns Decoded image data with RGBA pixels
55
55
  */
56
- decode(data) {
56
+ decode(data, _options) {
57
57
  if (!this.canDecode(data)) {
58
58
  throw new Error("Invalid PAM signature");
59
59
  }
@@ -150,7 +150,7 @@ export class PAMFormat {
150
150
  * @param imageData Image data to encode
151
151
  * @returns Encoded PAM image bytes
152
152
  */
153
- encode(imageData) {
153
+ encode(imageData, _options) {
154
154
  const { width, height, data } = imageData;
155
155
  // Validate input
156
156
  if (data.length !== width * height * 4) {
@@ -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
  * PCX format handler
4
4
  * Implements PCX decoder and encoder
@@ -7,8 +7,8 @@ export declare class PCXFormat implements ImageFormat {
7
7
  readonly name = "pcx";
8
8
  readonly mimeType = "image/x-pcx";
9
9
  canDecode(data: Uint8Array): boolean;
10
- decode(data: Uint8Array): Promise<ImageData>;
11
- encode(image: ImageData): Promise<Uint8Array>;
10
+ decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
11
+ encode(image: ImageData, _options?: unknown): Promise<Uint8Array>;
12
12
  /**
13
13
  * Get the list of metadata fields supported by PCX format
14
14
  */
@@ -28,7 +28,7 @@ export class PCXFormat {
28
28
  data[1] === 5) &&
29
29
  data[2] === 1;
30
30
  }
31
- decode(data) {
31
+ decode(data, _options) {
32
32
  if (!this.canDecode(data)) {
33
33
  return Promise.reject(new Error("Invalid PCX data"));
34
34
  }
@@ -134,7 +134,7 @@ export class PCXFormat {
134
134
  data: rgba,
135
135
  });
136
136
  }
137
- encode(image) {
137
+ encode(image, _options) {
138
138
  const width = image.width;
139
139
  const height = image.height;
140
140
  const data = image.data;
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
1
+ import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, PNGEncoderOptions } from "../types.js";
2
2
  import { PNGBase } from "./png_base.js";
3
3
  /**
4
4
  * PNG format handler
@@ -20,13 +20,14 @@ export declare class PNGFormat extends PNGBase implements ImageFormat {
20
20
  * @param data Raw PNG image data
21
21
  * @returns Decoded image data with RGBA pixels
22
22
  */
23
- decode(data: Uint8Array): Promise<ImageData>;
23
+ decode(data: Uint8Array, _options?: ImageDecoderOptions): Promise<ImageData>;
24
24
  /**
25
25
  * Encode RGBA image data to PNG format
26
26
  * @param imageData Image data to encode
27
+ * @param options Encoding options (compressionLevel 0-9, default 6)
27
28
  * @returns Encoded PNG image bytes
28
29
  */
29
- encode(imageData: ImageData): Promise<Uint8Array>;
30
+ encode(imageData: ImageData, options?: PNGEncoderOptions): Promise<Uint8Array>;
30
31
  /**
31
32
  * Get the list of metadata fields supported by PNG format
32
33
  * Delegates to PNGBase implementation
@@ -40,7 +40,7 @@ export class PNGFormat extends PNGBase {
40
40
  * @param data Raw PNG image data
41
41
  * @returns Decoded image data with RGBA pixels
42
42
  */
43
- async decode(data) {
43
+ async decode(data, _options) {
44
44
  if (!this.canDecode(data)) {
45
45
  throw new Error("Invalid PNG signature");
46
46
  }
@@ -110,10 +110,16 @@ export class PNGFormat extends PNGBase {
110
110
  /**
111
111
  * Encode RGBA image data to PNG format
112
112
  * @param imageData Image data to encode
113
+ * @param options Encoding options (compressionLevel 0-9, default 6)
113
114
  * @returns Encoded PNG image bytes
114
115
  */
115
- async encode(imageData) {
116
+ async encode(imageData, options) {
116
117
  const { width, height, data, metadata } = imageData;
118
+ const compressionLevel = options?.compressionLevel ?? 6;
119
+ // Validate compression level
120
+ if (compressionLevel < 0 || compressionLevel > 9) {
121
+ throw new Error("Compression level must be between 0 and 9");
122
+ }
117
123
  // Prepare IHDR chunk
118
124
  const ihdr = new Uint8Array(13);
119
125
  this.writeUint32(ihdr, 0, width);
@@ -124,7 +130,7 @@ export class PNGFormat extends PNGBase {
124
130
  ihdr[11] = 0; // filter method
125
131
  ihdr[12] = 0; // interlace method
126
132
  // Filter and compress image data
127
- const filtered = this.filterData(data, width, height);
133
+ const filtered = this.filterData(data, width, height, compressionLevel);
128
134
  const compressed = await this.deflate(filtered);
129
135
  // Build PNG
130
136
  const chunks = [];
@@ -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
  */
@@ -105,9 +105,7 @@ export class PNGBase {
105
105
  for (let x = 0; x < scanline.length; x++) {
106
106
  const left = x >= bytesPerPixel ? scanline[x - bytesPerPixel] : 0;
107
107
  const above = prevLine ? prevLine[x] : 0;
108
- const upperLeft = (x >= bytesPerPixel && prevLine)
109
- ? prevLine[x - bytesPerPixel]
110
- : 0;
108
+ const upperLeft = (x >= bytesPerPixel && prevLine) ? prevLine[x - bytesPerPixel] : 0;
111
109
  switch (filterType) {
112
110
  case 0: // None
113
111
  break;
@@ -121,8 +119,7 @@ export class PNGBase {
121
119
  scanline[x] = (scanline[x] + Math.floor((left + above) / 2)) & 0xff;
122
120
  break;
123
121
  case 4: // Paeth
124
- scanline[x] =
125
- (scanline[x] + this.paethPredictor(left, above, upperLeft)) & 0xff;
122
+ scanline[x] = (scanline[x] + this.paethPredictor(left, above, upperLeft)) & 0xff;
126
123
  break;
127
124
  }
128
125
  }
@@ -144,18 +141,211 @@ export class PNGBase {
144
141
  /**
145
142
  * Filter PNG data for encoding (using filter type 0 - None)
146
143
  */
147
- filterData(data, width, height) {
148
- // Use filter type 0 (None) for simplicity
149
- const filtered = new Uint8Array(height * (1 + width * 4));
144
+ /**
145
+ * Apply PNG filter to image data based on compression level
146
+ * @param data Raw RGBA pixel data
147
+ * @param width Image width
148
+ * @param height Image height
149
+ * @param compressionLevel Compression level (0-9, default 6)
150
+ * @returns Filtered data with filter type byte per scanline
151
+ */
152
+ filterData(data, width, height, compressionLevel = 6) {
153
+ // Choose filtering strategy based on compression level
154
+ if (compressionLevel <= 2) {
155
+ // Fast: No filtering
156
+ return this.applyNoFilter(data, width, height);
157
+ }
158
+ else if (compressionLevel <= 6) {
159
+ // Balanced: Sub filter
160
+ return this.applySubFilter(data, width, height);
161
+ }
162
+ else {
163
+ // Best: Adaptive filtering (choose best filter per scanline)
164
+ return this.applyAdaptiveFilter(data, width, height);
165
+ }
166
+ }
167
+ /**
168
+ * Apply filter type 0 (None) - no filtering
169
+ */
170
+ applyNoFilter(data, width, height) {
171
+ const bytesPerScanline = width * 4;
172
+ const filtered = new Uint8Array(height * (1 + bytesPerScanline));
150
173
  let pos = 0;
151
174
  for (let y = 0; y < height; y++) {
152
175
  filtered[pos++] = 0; // Filter type: None
153
- for (let x = 0; x < width * 4; x++) {
154
- filtered[pos++] = data[y * width * 4 + x];
176
+ const scanlineStart = y * bytesPerScanline;
177
+ for (let x = 0; x < bytesPerScanline; x++) {
178
+ filtered[pos++] = data[scanlineStart + x];
179
+ }
180
+ }
181
+ return filtered;
182
+ }
183
+ /**
184
+ * Apply filter type 1 (Sub) - subtract left pixel
185
+ */
186
+ applySubFilter(data, width, height) {
187
+ const bytesPerScanline = width * 4;
188
+ const filtered = new Uint8Array(height * (1 + bytesPerScanline));
189
+ let pos = 0;
190
+ for (let y = 0; y < height; y++) {
191
+ filtered[pos++] = 1; // Filter type: Sub
192
+ const scanlineStart = y * bytesPerScanline;
193
+ for (let x = 0; x < bytesPerScanline; x++) {
194
+ const current = data[scanlineStart + x];
195
+ const left = x >= 4 ? data[scanlineStart + x - 4] : 0;
196
+ filtered[pos++] = (current - left) & 0xff;
197
+ }
198
+ }
199
+ return filtered;
200
+ }
201
+ /**
202
+ * Apply filter type 2 (Up) - subtract above pixel
203
+ */
204
+ applyUpFilter(data, width, height) {
205
+ const bytesPerScanline = width * 4;
206
+ const filtered = new Uint8Array(height * (1 + bytesPerScanline));
207
+ let pos = 0;
208
+ for (let y = 0; y < height; y++) {
209
+ filtered[pos++] = 2; // Filter type: Up
210
+ const scanlineStart = y * bytesPerScanline;
211
+ const prevScanlineStart = (y - 1) * bytesPerScanline;
212
+ for (let x = 0; x < bytesPerScanline; x++) {
213
+ const current = data[scanlineStart + x];
214
+ const up = y > 0 ? data[prevScanlineStart + x] : 0;
215
+ filtered[pos++] = (current - up) & 0xff;
216
+ }
217
+ }
218
+ return filtered;
219
+ }
220
+ /**
221
+ * Apply filter type 3 (Average) - subtract average of left and above
222
+ */
223
+ applyAverageFilter(data, width, height) {
224
+ const bytesPerScanline = width * 4;
225
+ const filtered = new Uint8Array(height * (1 + bytesPerScanline));
226
+ let pos = 0;
227
+ for (let y = 0; y < height; y++) {
228
+ filtered[pos++] = 3; // Filter type: Average
229
+ const scanlineStart = y * bytesPerScanline;
230
+ const prevScanlineStart = (y - 1) * bytesPerScanline;
231
+ for (let x = 0; x < bytesPerScanline; x++) {
232
+ const current = data[scanlineStart + x];
233
+ const left = x >= 4 ? data[scanlineStart + x - 4] : 0;
234
+ const up = y > 0 ? data[prevScanlineStart + x] : 0;
235
+ const avg = Math.floor((left + up) / 2);
236
+ filtered[pos++] = (current - avg) & 0xff;
155
237
  }
156
238
  }
157
239
  return filtered;
158
240
  }
241
+ /**
242
+ * Apply filter type 4 (Paeth) - Paeth predictor
243
+ */
244
+ applyPaethFilter(data, width, height) {
245
+ const bytesPerScanline = width * 4;
246
+ const filtered = new Uint8Array(height * (1 + bytesPerScanline));
247
+ let pos = 0;
248
+ for (let y = 0; y < height; y++) {
249
+ filtered[pos++] = 4; // Filter type: Paeth
250
+ const scanlineStart = y * bytesPerScanline;
251
+ const prevScanlineStart = (y - 1) * bytesPerScanline;
252
+ for (let x = 0; x < bytesPerScanline; x++) {
253
+ const current = data[scanlineStart + x];
254
+ const left = x >= 4 ? data[scanlineStart + x - 4] : 0;
255
+ const up = y > 0 ? data[prevScanlineStart + x] : 0;
256
+ const upLeft = (y > 0 && x >= 4) ? data[prevScanlineStart + x - 4] : 0;
257
+ const paeth = this.paethPredictor(left, up, upLeft);
258
+ filtered[pos++] = (current - paeth) & 0xff;
259
+ }
260
+ }
261
+ return filtered;
262
+ }
263
+ /**
264
+ * Calculate sum of absolute differences for a filtered scanline
265
+ * Lower values indicate better compression potential
266
+ */
267
+ calculateFilterScore(filtered) {
268
+ let sum = 0;
269
+ for (let i = 1; i < filtered.length; i++) {
270
+ const byte = filtered[i];
271
+ // Penalize larger absolute values
272
+ sum += byte < 128 ? byte : (256 - byte);
273
+ }
274
+ return sum;
275
+ }
276
+ /**
277
+ * Apply adaptive filtering - choose best filter per scanline
278
+ */
279
+ applyAdaptiveFilter(data, width, height) {
280
+ const bytesPerScanline = width * 4;
281
+ const filtered = new Uint8Array(height * (1 + bytesPerScanline));
282
+ let outPos = 0;
283
+ // Try each filter type and choose the best for each scanline
284
+ const filters = [
285
+ (y) => this.filterScanline(data, y, width, 0), // None
286
+ (y) => this.filterScanline(data, y, width, 1), // Sub
287
+ (y) => this.filterScanline(data, y, width, 2), // Up
288
+ (y) => this.filterScanline(data, y, width, 3), // Average
289
+ (y) => this.filterScanline(data, y, width, 4), // Paeth
290
+ ];
291
+ for (let y = 0; y < height; y++) {
292
+ let bestFilter = null;
293
+ let bestScore = Infinity;
294
+ // Try each filter type
295
+ for (const filterFn of filters) {
296
+ const result = filterFn(y);
297
+ const score = this.calculateFilterScore(result);
298
+ if (score < bestScore) {
299
+ bestScore = score;
300
+ bestFilter = result;
301
+ }
302
+ }
303
+ // Copy best filter result
304
+ if (bestFilter) {
305
+ filtered.set(bestFilter, outPos);
306
+ outPos += bestFilter.length;
307
+ }
308
+ }
309
+ return filtered;
310
+ }
311
+ /**
312
+ * Filter a single scanline with specified filter type
313
+ */
314
+ filterScanline(data, y, width, filterType) {
315
+ const bytesPerScanline = width * 4;
316
+ const result = new Uint8Array(1 + bytesPerScanline);
317
+ result[0] = filterType;
318
+ const scanlineStart = y * bytesPerScanline;
319
+ const prevScanlineStart = (y - 1) * bytesPerScanline;
320
+ for (let x = 0; x < bytesPerScanline; x++) {
321
+ const current = data[scanlineStart + x];
322
+ const left = x >= 4 ? data[scanlineStart + x - 4] : 0;
323
+ const up = y > 0 ? data[prevScanlineStart + x] : 0;
324
+ const upLeft = (y > 0 && x >= 4) ? data[prevScanlineStart + x - 4] : 0;
325
+ let filtered;
326
+ switch (filterType) {
327
+ case 0: // None
328
+ filtered = current;
329
+ break;
330
+ case 1: // Sub
331
+ filtered = (current - left) & 0xff;
332
+ break;
333
+ case 2: // Up
334
+ filtered = (current - up) & 0xff;
335
+ break;
336
+ case 3: // Average
337
+ filtered = (current - Math.floor((left + up) / 2)) & 0xff;
338
+ break;
339
+ case 4: // Paeth
340
+ filtered = (current - this.paethPredictor(left, up, upLeft)) & 0xff;
341
+ break;
342
+ default:
343
+ filtered = current;
344
+ }
345
+ result[x + 1] = filtered;
346
+ }
347
+ return result;
348
+ }
159
349
  /**
160
350
  * Get bytes per pixel for a given color type and bit depth
161
351
  */
@@ -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
  */
@@ -56,7 +56,7 @@ export class PPMFormat {
56
56
  * @param data Raw PPM image data
57
57
  * @returns Decoded image data with RGBA pixels
58
58
  */
59
- decode(data) {
59
+ decode(data, _options) {
60
60
  if (!this.canDecode(data)) {
61
61
  throw new Error("Invalid PPM signature");
62
62
  }
@@ -208,7 +208,7 @@ export class PPMFormat {
208
208
  * @param imageData Image data to encode
209
209
  * @returns Encoded PPM image bytes
210
210
  */
211
- encode(imageData) {
211
+ encode(imageData, _options) {
212
212
  const { width, height, data } = imageData;
213
213
  // Validate input
214
214
  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;