cross-image 0.2.3 → 0.4.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.
Files changed (105) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +292 -74
  3. package/esm/mod.d.ts +6 -4
  4. package/esm/mod.js +4 -2
  5. package/esm/src/formats/apng.d.ts +17 -5
  6. package/esm/src/formats/apng.js +104 -9
  7. package/esm/src/formats/ascii.d.ts +13 -3
  8. package/esm/src/formats/ascii.js +25 -1
  9. package/esm/src/formats/avif.d.ts +96 -0
  10. package/esm/src/formats/avif.js +607 -0
  11. package/esm/src/formats/bmp.d.ts +13 -3
  12. package/esm/src/formats/bmp.js +75 -2
  13. package/esm/src/formats/dng.d.ts +14 -2
  14. package/esm/src/formats/dng.js +27 -5
  15. package/esm/src/formats/gif.d.ts +18 -5
  16. package/esm/src/formats/gif.js +160 -14
  17. package/esm/src/formats/heic.d.ts +96 -0
  18. package/esm/src/formats/heic.js +608 -0
  19. package/esm/src/formats/ico.d.ts +13 -3
  20. package/esm/src/formats/ico.js +32 -4
  21. package/esm/src/formats/jpeg.d.ts +10 -3
  22. package/esm/src/formats/jpeg.js +99 -11
  23. package/esm/src/formats/pam.d.ts +13 -3
  24. package/esm/src/formats/pam.js +68 -2
  25. package/esm/src/formats/pcx.d.ts +13 -3
  26. package/esm/src/formats/pcx.js +47 -2
  27. package/esm/src/formats/png.d.ts +15 -3
  28. package/esm/src/formats/png.js +89 -2
  29. package/esm/src/formats/png_base.js +2 -5
  30. package/esm/src/formats/ppm.d.ts +13 -3
  31. package/esm/src/formats/ppm.js +36 -2
  32. package/esm/src/formats/tiff.d.ts +14 -18
  33. package/esm/src/formats/tiff.js +219 -20
  34. package/esm/src/formats/webp.d.ts +10 -3
  35. package/esm/src/formats/webp.js +103 -8
  36. package/esm/src/image.d.ts +20 -3
  37. package/esm/src/image.js +65 -21
  38. package/esm/src/types.d.ts +74 -4
  39. package/esm/src/utils/gif_decoder.d.ts +4 -1
  40. package/esm/src/utils/gif_decoder.js +91 -65
  41. package/esm/src/utils/image_processing.js +144 -70
  42. package/esm/src/utils/jpeg_decoder.d.ts +17 -4
  43. package/esm/src/utils/jpeg_decoder.js +448 -83
  44. package/esm/src/utils/jpeg_encoder.d.ts +15 -1
  45. package/esm/src/utils/jpeg_encoder.js +263 -24
  46. package/esm/src/utils/resize.js +51 -20
  47. package/esm/src/utils/tiff_deflate.d.ts +18 -0
  48. package/esm/src/utils/tiff_deflate.js +27 -0
  49. package/esm/src/utils/tiff_packbits.d.ts +24 -0
  50. package/esm/src/utils/tiff_packbits.js +90 -0
  51. package/esm/src/utils/webp_decoder.d.ts +3 -1
  52. package/esm/src/utils/webp_decoder.js +144 -63
  53. package/esm/src/utils/webp_encoder.js +5 -11
  54. package/package.json +18 -1
  55. package/script/mod.d.ts +6 -4
  56. package/script/mod.js +7 -3
  57. package/script/src/formats/apng.d.ts +17 -5
  58. package/script/src/formats/apng.js +104 -9
  59. package/script/src/formats/ascii.d.ts +13 -3
  60. package/script/src/formats/ascii.js +25 -1
  61. package/script/src/formats/avif.d.ts +96 -0
  62. package/script/src/formats/avif.js +611 -0
  63. package/script/src/formats/bmp.d.ts +13 -3
  64. package/script/src/formats/bmp.js +75 -2
  65. package/script/src/formats/dng.d.ts +14 -2
  66. package/script/src/formats/dng.js +27 -5
  67. package/script/src/formats/gif.d.ts +18 -5
  68. package/script/src/formats/gif.js +160 -14
  69. package/script/src/formats/heic.d.ts +96 -0
  70. package/script/src/formats/heic.js +612 -0
  71. package/script/src/formats/ico.d.ts +13 -3
  72. package/script/src/formats/ico.js +32 -4
  73. package/script/src/formats/jpeg.d.ts +10 -3
  74. package/script/src/formats/jpeg.js +99 -11
  75. package/script/src/formats/pam.d.ts +13 -3
  76. package/script/src/formats/pam.js +68 -2
  77. package/script/src/formats/pcx.d.ts +13 -3
  78. package/script/src/formats/pcx.js +47 -2
  79. package/script/src/formats/png.d.ts +15 -3
  80. package/script/src/formats/png.js +89 -2
  81. package/script/src/formats/png_base.js +2 -5
  82. package/script/src/formats/ppm.d.ts +13 -3
  83. package/script/src/formats/ppm.js +36 -2
  84. package/script/src/formats/tiff.d.ts +14 -18
  85. package/script/src/formats/tiff.js +219 -20
  86. package/script/src/formats/webp.d.ts +10 -3
  87. package/script/src/formats/webp.js +103 -8
  88. package/script/src/image.d.ts +20 -3
  89. package/script/src/image.js +64 -20
  90. package/script/src/types.d.ts +74 -4
  91. package/script/src/utils/gif_decoder.d.ts +4 -1
  92. package/script/src/utils/gif_decoder.js +91 -65
  93. package/script/src/utils/image_processing.js +144 -70
  94. package/script/src/utils/jpeg_decoder.d.ts +17 -4
  95. package/script/src/utils/jpeg_decoder.js +448 -83
  96. package/script/src/utils/jpeg_encoder.d.ts +15 -1
  97. package/script/src/utils/jpeg_encoder.js +263 -24
  98. package/script/src/utils/resize.js +51 -20
  99. package/script/src/utils/tiff_deflate.d.ts +18 -0
  100. package/script/src/utils/tiff_deflate.js +31 -0
  101. package/script/src/utils/tiff_packbits.d.ts +24 -0
  102. package/script/src/utils/tiff_packbits.js +94 -0
  103. package/script/src/utils/webp_decoder.d.ts +3 -1
  104. package/script/src/utils/webp_decoder.js +144 -63
  105. package/script/src/utils/webp_encoder.js +5 -11
@@ -52,7 +52,7 @@ class ICOFormat {
52
52
  * @param data Raw ICO image data
53
53
  * @returns Decoded image data with RGBA pixels
54
54
  */
55
- async decode(data) {
55
+ async decode(data, options) {
56
56
  if (!this.canDecode(data)) {
57
57
  throw new Error("Invalid ICO signature");
58
58
  }
@@ -103,7 +103,7 @@ class ICOFormat {
103
103
  imageData[2] === 0x4e &&
104
104
  imageData[3] === 0x47) {
105
105
  // It's a PNG, decode it
106
- return await this.pngFormat.decode(imageData);
106
+ return await this.pngFormat.decode(imageData, options);
107
107
  }
108
108
  // Otherwise, it's a BMP without the file header (DIB format)
109
109
  return this.decodeDIB(imageData);
@@ -185,10 +185,10 @@ class ICOFormat {
185
185
  * @param imageData Image data to encode
186
186
  * @returns Encoded ICO image bytes
187
187
  */
188
- async encode(imageData) {
188
+ async encode(imageData, _options) {
189
189
  const { width, height } = imageData;
190
190
  // Encode the image as PNG
191
- const pngData = await this.pngFormat.encode(imageData);
191
+ const pngData = await this.pngFormat.encode(imageData, undefined);
192
192
  // Create ICO file structure
193
193
  // ICONDIR (6 bytes) + ICONDIRENTRY (16 bytes) + PNG data
194
194
  const icoSize = 6 + 16 + pngData.length;
@@ -214,5 +214,33 @@ class ICOFormat {
214
214
  result.set(pngData, 22);
215
215
  return result;
216
216
  }
217
+ /**
218
+ * Get the list of metadata fields supported by ICO format
219
+ */
220
+ getSupportedMetadata() {
221
+ return [
222
+ "frameCount", // Number of images in ICO file
223
+ ];
224
+ }
225
+ /**
226
+ * Extract metadata from ICO data without fully decoding the pixel data
227
+ * @param data Raw ICO data
228
+ * @returns Extracted metadata or undefined
229
+ */
230
+ extractMetadata(data) {
231
+ if (!this.canDecode(data)) {
232
+ return Promise.resolve(undefined);
233
+ }
234
+ // ICO files can contain multiple images, count them
235
+ const count = (0, byte_utils_js_1.readUint16LE)(data, 4);
236
+ const metadata = {
237
+ format: "ico",
238
+ compression: "none", // ICO typically contains uncompressed PNG or BMP
239
+ frameCount: count,
240
+ bitDepth: 32, // Most modern ICOs use 32-bit RGBA
241
+ colorType: "rgba",
242
+ };
243
+ return Promise.resolve(metadata);
244
+ }
217
245
  }
218
246
  exports.ICOFormat = ICOFormat;
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
1
+ import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata, JPEGEncoderOptions } from "../types.js";
2
2
  /**
3
3
  * JPEG format handler
4
4
  * Implements a basic JPEG decoder and encoder
@@ -19,13 +19,13 @@ export declare class JPEGFormat implements ImageFormat {
19
19
  * @param data Raw JPEG image data
20
20
  * @returns Decoded image data with RGBA pixels
21
21
  */
22
- decode(data: Uint8Array): Promise<ImageData>;
22
+ decode(data: Uint8Array, settings?: ImageDecoderOptions): Promise<ImageData>;
23
23
  /**
24
24
  * Encode RGBA image data to JPEG format
25
25
  * @param imageData Image data to encode
26
26
  * @returns Encoded JPEG image bytes
27
27
  */
28
- encode(imageData: ImageData): Promise<Uint8Array>;
28
+ encode(imageData: ImageData, options?: JPEGEncoderOptions): Promise<Uint8Array>;
29
29
  private injectMetadata;
30
30
  private decodeUsingRuntime;
31
31
  private parseJFIF;
@@ -42,5 +42,12 @@ export declare class JPEGFormat implements ImageFormat {
42
42
  * Get the list of metadata fields supported by JPEG format
43
43
  */
44
44
  getSupportedMetadata(): Array<keyof ImageMetadata>;
45
+ /**
46
+ * Extract metadata from JPEG data without fully decoding the pixel data
47
+ * This quickly parses JFIF and EXIF markers to extract metadata
48
+ * @param data Raw JPEG data
49
+ * @returns Extracted metadata or undefined
50
+ */
51
+ extractMetadata(data: Uint8Array): Promise<ImageMetadata | undefined>;
45
52
  }
46
53
  //# sourceMappingURL=jpeg.d.ts.map
@@ -73,7 +73,7 @@ class JPEGFormat {
73
73
  * @param data Raw JPEG image data
74
74
  * @returns Decoded image data with RGBA pixels
75
75
  */
76
- async decode(data) {
76
+ async decode(data, settings) {
77
77
  if (!this.canDecode(data)) {
78
78
  throw new Error("Invalid JPEG signature");
79
79
  }
@@ -131,7 +131,7 @@ class JPEGFormat {
131
131
  (0, security_js_1.validateImageDimensions)(width, height);
132
132
  // For a pure JS implementation, we'd need to implement full JPEG decoding
133
133
  // which is very complex. Instead, we'll use the browser/runtime's decoder.
134
- const rgba = await this.decodeUsingRuntime(data, width, height);
134
+ const rgba = await this.decodeUsingRuntime(data, width, height, settings);
135
135
  return {
136
136
  width,
137
137
  height,
@@ -144,10 +144,13 @@ class JPEGFormat {
144
144
  * @param imageData Image data to encode
145
145
  * @returns Encoded JPEG image bytes
146
146
  */
147
- async encode(imageData) {
147
+ async encode(imageData, options) {
148
148
  const { width, height, data, metadata } = imageData;
149
+ const requestedQuality = options?.quality;
150
+ const requestedProgressive = options?.progressive;
149
151
  // Try to use runtime encoding if available (better quality)
150
- if (typeof OffscreenCanvas !== "undefined") {
152
+ // Note: progressive output is only supported by the pure-JS encoder.
153
+ if (!requestedProgressive && typeof OffscreenCanvas !== "undefined") {
151
154
  try {
152
155
  const canvas = new OffscreenCanvas(width, height);
153
156
  const ctx = canvas.getContext("2d");
@@ -156,9 +159,12 @@ class JPEGFormat {
156
159
  const imgDataData = new Uint8ClampedArray(data);
157
160
  imgData.data.set(imgDataData);
158
161
  ctx.putImageData(imgData, 0, 0);
162
+ const quality = requestedQuality === undefined
163
+ ? 0.9
164
+ : Math.max(1, Math.min(100, requestedQuality)) / 100;
159
165
  const blob = await canvas.convertToBlob({
160
166
  type: "image/jpeg",
161
- quality: 0.9,
167
+ quality,
162
168
  });
163
169
  const arrayBuffer = await blob.arrayBuffer();
164
170
  const encoded = new Uint8Array(arrayBuffer);
@@ -177,7 +183,10 @@ class JPEGFormat {
177
183
  const { JPEGEncoder } = await Promise.resolve().then(() => __importStar(require("../utils/jpeg_encoder.js")));
178
184
  const dpiX = metadata?.dpiX ?? 72;
179
185
  const dpiY = metadata?.dpiY ?? 72;
180
- const encoder = new JPEGEncoder(85); // Quality 85
186
+ const encoder = new JPEGEncoder({
187
+ quality: requestedQuality,
188
+ progressive: requestedProgressive,
189
+ });
181
190
  const encoded = encoder.encode(width, height, data, dpiX, dpiY);
182
191
  // Add EXIF metadata if present
183
192
  if (metadata && Object.keys(metadata).length > 0) {
@@ -221,9 +230,10 @@ class JPEGFormat {
221
230
  result.set(encoded.slice(pos), pos + app1.length);
222
231
  return result;
223
232
  }
224
- async decodeUsingRuntime(data, _width, _height) {
233
+ async decodeUsingRuntime(data, _width, _height, settings) {
225
234
  // Try to use ImageDecoder API if available (Deno, modern browsers)
226
- if (typeof ImageDecoder !== "undefined") {
235
+ if (settings?.runtimeDecoding !== "never" &&
236
+ typeof ImageDecoder !== "undefined") {
227
237
  try {
228
238
  const decoder = new ImageDecoder({ data, type: "image/jpeg" });
229
239
  const result = await decoder.decode();
@@ -238,15 +248,17 @@ class JPEGFormat {
238
248
  bitmap.close();
239
249
  return new Uint8Array(imageData.data.buffer);
240
250
  }
241
- catch (error) {
251
+ catch (_error) {
242
252
  // ImageDecoder API failed, fall through to pure JS decoder
243
- console.warn("JPEG decoding with ImageDecoder failed, using pure JS decoder:", error);
244
253
  }
245
254
  }
246
255
  // Fallback to pure JavaScript decoder
247
256
  try {
248
257
  const { JPEGDecoder } = await Promise.resolve().then(() => __importStar(require("../utils/jpeg_decoder.js")));
249
- const decoder = new JPEGDecoder(data);
258
+ const decoder = new JPEGDecoder(data, {
259
+ tolerantDecoding: settings?.tolerantDecoding ?? true,
260
+ onWarning: settings?.onWarning,
261
+ });
250
262
  return decoder.decode();
251
263
  }
252
264
  catch (error) {
@@ -1096,5 +1108,81 @@ class JPEGFormat {
1096
1108
  "dpiY",
1097
1109
  ];
1098
1110
  }
1111
+ /**
1112
+ * Extract metadata from JPEG data without fully decoding the pixel data
1113
+ * This quickly parses JFIF and EXIF markers to extract metadata
1114
+ * @param data Raw JPEG data
1115
+ * @returns Extracted metadata or undefined
1116
+ */
1117
+ extractMetadata(data) {
1118
+ if (!this.canDecode(data)) {
1119
+ return Promise.resolve(undefined);
1120
+ }
1121
+ // Parse JPEG structure to extract metadata
1122
+ let pos = 2; // Skip initial FF D8
1123
+ const metadata = {
1124
+ format: "jpeg",
1125
+ compression: "dct",
1126
+ frameCount: 1,
1127
+ bitDepth: 8,
1128
+ colorType: "rgb",
1129
+ };
1130
+ let width = 0;
1131
+ let height = 0;
1132
+ while (pos < data.length - 1) {
1133
+ if (data[pos] !== 0xff) {
1134
+ pos++;
1135
+ continue;
1136
+ }
1137
+ const marker = data[pos + 1];
1138
+ pos += 2;
1139
+ // SOF markers (Start of Frame) - get dimensions for DPI calculation
1140
+ if (marker >= 0xc0 && marker <= 0xcf &&
1141
+ marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {
1142
+ const length = (data[pos] << 8) | data[pos + 1];
1143
+ // precision at pos+2
1144
+ const precision = data[pos + 2];
1145
+ if (precision && precision !== 8) {
1146
+ metadata.bitDepth = precision;
1147
+ }
1148
+ height = (data[pos + 3] << 8) | data[pos + 4];
1149
+ width = (data[pos + 5] << 8) | data[pos + 6];
1150
+ // Check number of components
1151
+ const numComponents = data[pos + 7];
1152
+ if (numComponents === 1) {
1153
+ metadata.colorType = "grayscale";
1154
+ }
1155
+ // Don't break - continue parsing for metadata
1156
+ pos += length;
1157
+ continue;
1158
+ }
1159
+ // APP0 marker (JFIF)
1160
+ if (marker === 0xe0) {
1161
+ const length = (data[pos] << 8) | data[pos + 1];
1162
+ const appData = data.slice(pos + 2, pos + length);
1163
+ this.parseJFIF(appData, metadata, width, height);
1164
+ pos += length;
1165
+ continue;
1166
+ }
1167
+ // APP1 marker (EXIF)
1168
+ if (marker === 0xe1) {
1169
+ const length = (data[pos] << 8) | data[pos + 1];
1170
+ const appData = data.slice(pos + 2, pos + length);
1171
+ this.parseEXIF(appData, metadata);
1172
+ pos += length;
1173
+ continue;
1174
+ }
1175
+ // Skip other markers
1176
+ if (marker === 0xd9 || marker === 0xda)
1177
+ break; // EOI or SOS
1178
+ if (marker >= 0xd0 && marker <= 0xd8)
1179
+ continue; // RST markers have no length
1180
+ if (marker === 0x01)
1181
+ continue; // TEM has no length
1182
+ const length = (data[pos] << 8) | data[pos + 1];
1183
+ pos += length;
1184
+ }
1185
+ return Promise.resolve(Object.keys(metadata).length > 0 ? metadata : undefined);
1186
+ }
1099
1187
  }
1100
1188
  exports.JPEGFormat = JPEGFormat;
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat } 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,12 +32,22 @@ 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
+ /**
43
+ * Get the list of metadata fields supported by PAM format
44
+ */
45
+ getSupportedMetadata(): Array<keyof ImageMetadata>;
46
+ /**
47
+ * Extract metadata from PAM data without fully decoding the pixel data
48
+ * @param data Raw PAM data
49
+ * @returns Extracted metadata or undefined
50
+ */
51
+ extractMetadata(data: Uint8Array): Promise<ImageMetadata | undefined>;
42
52
  }
43
53
  //# sourceMappingURL=pam.d.ts.map
@@ -56,7 +56,7 @@ class PAMFormat {
56
56
  * @param data Raw PAM 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 PAM signature");
62
62
  }
@@ -153,7 +153,7 @@ class PAMFormat {
153
153
  * @param imageData Image data to encode
154
154
  * @returns Encoded PAM image bytes
155
155
  */
156
- encode(imageData) {
156
+ encode(imageData, _options) {
157
157
  const { width, height, data } = imageData;
158
158
  // Validate input
159
159
  if (data.length !== width * height * 4) {
@@ -177,5 +177,71 @@ class PAMFormat {
177
177
  output.set(data, headerBytes.length);
178
178
  return Promise.resolve(output);
179
179
  }
180
+ /**
181
+ * Get the list of metadata fields supported by PAM format
182
+ */
183
+ getSupportedMetadata() {
184
+ return []; // PAM format doesn't support metadata preservation
185
+ }
186
+ /**
187
+ * Extract metadata from PAM data without fully decoding the pixel data
188
+ * @param data Raw PAM data
189
+ * @returns Extracted metadata or undefined
190
+ */
191
+ extractMetadata(data) {
192
+ if (!this.canDecode(data)) {
193
+ return Promise.resolve(undefined);
194
+ }
195
+ const metadata = {
196
+ format: "pam",
197
+ compression: "none",
198
+ frameCount: 1,
199
+ bitDepth: 8, // PAM typically uses 8 bits per channel
200
+ colorType: "rgba",
201
+ };
202
+ // Try to parse the header to get actual color type
203
+ const decoder = new TextDecoder();
204
+ const headerText = decoder.decode(data.slice(0, Math.min(200, data.length)));
205
+ // Look for TUPLTYPE to determine color type
206
+ const tupltypeMatch = headerText.match(/TUPLTYPE\s+(\S+)/);
207
+ if (tupltypeMatch) {
208
+ const tupltype = tupltypeMatch[1];
209
+ if (tupltype === "GRAYSCALE") {
210
+ metadata.colorType = "grayscale";
211
+ }
212
+ else if (tupltype === "RGB") {
213
+ metadata.colorType = "rgb";
214
+ }
215
+ else if (tupltype === "RGB_ALPHA") {
216
+ metadata.colorType = "rgba";
217
+ }
218
+ }
219
+ // Look for DEPTH to determine number of channels
220
+ const depthMatch = headerText.match(/DEPTH\s+(\d+)/);
221
+ if (depthMatch) {
222
+ const depth = parseInt(depthMatch[1]);
223
+ if (depth === 1) {
224
+ metadata.colorType = "grayscale";
225
+ }
226
+ else if (depth === 3) {
227
+ metadata.colorType = "rgb";
228
+ }
229
+ else if (depth === 4) {
230
+ metadata.colorType = "rgba";
231
+ }
232
+ }
233
+ // Look for MAXVAL to determine bit depth
234
+ const maxvalMatch = headerText.match(/MAXVAL\s+(\d+)/);
235
+ if (maxvalMatch) {
236
+ const maxval = parseInt(maxvalMatch[1]);
237
+ if (maxval === 255) {
238
+ metadata.bitDepth = 8;
239
+ }
240
+ else if (maxval === 65535) {
241
+ metadata.bitDepth = 16;
242
+ }
243
+ }
244
+ return Promise.resolve(metadata);
245
+ }
180
246
  }
181
247
  exports.PAMFormat = PAMFormat;
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat } 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,7 +7,17 @@ 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
+ /**
13
+ * Get the list of metadata fields supported by PCX format
14
+ */
15
+ getSupportedMetadata(): Array<keyof ImageMetadata>;
16
+ /**
17
+ * Extract metadata from PCX data without fully decoding the pixel data
18
+ * @param data Raw PCX data
19
+ * @returns Extracted metadata or undefined
20
+ */
21
+ extractMetadata(data: Uint8Array): Promise<ImageMetadata | undefined>;
12
22
  }
13
23
  //# sourceMappingURL=pcx.d.ts.map
@@ -31,7 +31,7 @@ class PCXFormat {
31
31
  data[1] === 5) &&
32
32
  data[2] === 1;
33
33
  }
34
- decode(data) {
34
+ decode(data, _options) {
35
35
  if (!this.canDecode(data)) {
36
36
  return Promise.reject(new Error("Invalid PCX data"));
37
37
  }
@@ -137,7 +137,7 @@ class PCXFormat {
137
137
  data: rgba,
138
138
  });
139
139
  }
140
- encode(image) {
140
+ encode(image, _options) {
141
141
  const width = image.width;
142
142
  const height = image.height;
143
143
  const data = image.data;
@@ -204,5 +204,50 @@ class PCXFormat {
204
204
  result.set(rleData, header.length);
205
205
  return Promise.resolve(result);
206
206
  }
207
+ /**
208
+ * Get the list of metadata fields supported by PCX format
209
+ */
210
+ getSupportedMetadata() {
211
+ return [
212
+ "dpiX", // DPI in header
213
+ "dpiY", // DPI in header
214
+ ];
215
+ }
216
+ /**
217
+ * Extract metadata from PCX data without fully decoding the pixel data
218
+ * @param data Raw PCX data
219
+ * @returns Extracted metadata or undefined
220
+ */
221
+ extractMetadata(data) {
222
+ if (!this.canDecode(data)) {
223
+ return Promise.resolve(undefined);
224
+ }
225
+ const metadata = {
226
+ format: "pcx",
227
+ compression: "rle",
228
+ frameCount: 1,
229
+ bitDepth: data[3], // Bits per pixel per plane
230
+ colorType: "rgb",
231
+ };
232
+ // Check number of planes to determine color type
233
+ const numPlanes = data[65];
234
+ if (numPlanes === 1) {
235
+ metadata.colorType = "indexed";
236
+ }
237
+ else if (numPlanes === 3) {
238
+ metadata.colorType = "rgb";
239
+ }
240
+ else if (numPlanes === 4) {
241
+ metadata.colorType = "rgba";
242
+ }
243
+ // DPI information
244
+ const dpiX = data[12] | (data[13] << 8);
245
+ const dpiY = data[14] | (data[15] << 8);
246
+ if (dpiX > 0)
247
+ metadata.dpiX = dpiX;
248
+ if (dpiY > 0)
249
+ metadata.dpiY = dpiY;
250
+ return Promise.resolve(metadata);
251
+ }
207
252
  }
208
253
  exports.PCXFormat = PCXFormat;
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat } from "../types.js";
1
+ import type { ImageData, ImageDecoderOptions, ImageFormat, ImageMetadata } from "../types.js";
2
2
  import { PNGBase } from "./png_base.js";
3
3
  /**
4
4
  * PNG format handler
@@ -20,12 +20,24 @@ 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
27
  * @returns Encoded PNG image bytes
28
28
  */
29
- encode(imageData: ImageData): Promise<Uint8Array>;
29
+ encode(imageData: ImageData, _options?: unknown): Promise<Uint8Array>;
30
+ /**
31
+ * Get the list of metadata fields supported by PNG format
32
+ * Delegates to PNGBase implementation
33
+ */
34
+ getSupportedMetadata(): Array<keyof ImageMetadata>;
35
+ /**
36
+ * Extract metadata from PNG data without fully decoding the pixel data
37
+ * This quickly parses PNG chunks to extract metadata
38
+ * @param data Raw PNG data
39
+ * @returns Extracted metadata or undefined
40
+ */
41
+ extractMetadata(data: Uint8Array): Promise<ImageMetadata | undefined>;
30
42
  }
31
43
  //# sourceMappingURL=png.d.ts.map
@@ -43,7 +43,7 @@ class PNGFormat extends png_base_js_1.PNGBase {
43
43
  * @param data Raw PNG image data
44
44
  * @returns Decoded image data with RGBA pixels
45
45
  */
46
- async decode(data) {
46
+ async decode(data, _options) {
47
47
  if (!this.canDecode(data)) {
48
48
  throw new Error("Invalid PNG signature");
49
49
  }
@@ -115,7 +115,7 @@ class PNGFormat extends png_base_js_1.PNGBase {
115
115
  * @param imageData Image data to encode
116
116
  * @returns Encoded PNG image bytes
117
117
  */
118
- async encode(imageData) {
118
+ async encode(imageData, _options) {
119
119
  const { width, height, data, metadata } = imageData;
120
120
  // Prepare IHDR chunk
121
121
  const ihdr = new Uint8Array(13);
@@ -140,5 +140,92 @@ class PNGFormat extends png_base_js_1.PNGBase {
140
140
  // Concatenate all chunks
141
141
  return this.concatenateArrays(chunks);
142
142
  }
143
+ /**
144
+ * Get the list of metadata fields supported by PNG format
145
+ * Delegates to PNGBase implementation
146
+ */
147
+ getSupportedMetadata() {
148
+ return super.getSupportedMetadata();
149
+ }
150
+ /**
151
+ * Extract metadata from PNG data without fully decoding the pixel data
152
+ * This quickly parses PNG chunks to extract metadata
153
+ * @param data Raw PNG data
154
+ * @returns Extracted metadata or undefined
155
+ */
156
+ extractMetadata(data) {
157
+ if (!this.canDecode(data)) {
158
+ return Promise.resolve(undefined);
159
+ }
160
+ let pos = 8; // Skip PNG signature
161
+ let width = 0;
162
+ let height = 0;
163
+ const metadata = {
164
+ format: "png",
165
+ compression: "deflate",
166
+ frameCount: 1,
167
+ };
168
+ // Parse chunks for metadata only
169
+ while (pos < data.length) {
170
+ if (pos + 8 > data.length)
171
+ break;
172
+ const length = this.readUint32(data, pos);
173
+ pos += 4;
174
+ const type = String.fromCharCode(data[pos], data[pos + 1], data[pos + 2], data[pos + 3]);
175
+ pos += 4;
176
+ if (pos + length + 4 > data.length)
177
+ break;
178
+ const chunkData = data.slice(pos, pos + length);
179
+ pos += length;
180
+ pos += 4; // Skip CRC
181
+ if (type === "IHDR") {
182
+ width = this.readUint32(chunkData, 0);
183
+ height = this.readUint32(chunkData, 4);
184
+ // Parse bit depth and color type from IHDR
185
+ if (chunkData.length >= 9) {
186
+ metadata.bitDepth = chunkData[8];
187
+ const colorTypeCode = chunkData[9];
188
+ // PNG color types: 0=grayscale, 2=rgb, 3=indexed, 4=grayscale+alpha, 6=rgba
189
+ switch (colorTypeCode) {
190
+ case 0:
191
+ metadata.colorType = "grayscale";
192
+ break;
193
+ case 2:
194
+ metadata.colorType = "rgb";
195
+ break;
196
+ case 3:
197
+ metadata.colorType = "indexed";
198
+ break;
199
+ case 4:
200
+ metadata.colorType = "grayscale-alpha";
201
+ break;
202
+ case 6:
203
+ metadata.colorType = "rgba";
204
+ break;
205
+ }
206
+ }
207
+ }
208
+ else if (type === "pHYs") {
209
+ // Physical pixel dimensions
210
+ this.parsePhysChunk(chunkData, metadata, width, height);
211
+ }
212
+ else if (type === "tEXt") {
213
+ // Text chunk
214
+ this.parseTextChunk(chunkData, metadata);
215
+ }
216
+ else if (type === "iTXt") {
217
+ // International text chunk
218
+ this.parseITxtChunk(chunkData, metadata);
219
+ }
220
+ else if (type === "eXIf") {
221
+ // EXIF chunk
222
+ this.parseExifChunk(chunkData, metadata);
223
+ }
224
+ else if (type === "IEND") {
225
+ break;
226
+ }
227
+ }
228
+ return Promise.resolve(Object.keys(metadata).length > 0 ? metadata : undefined);
229
+ }
143
230
  }
144
231
  exports.PNGFormat = PNGFormat;
@@ -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
  }