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
@@ -49,7 +49,7 @@ export class ICOFormat {
49
49
  * @param data Raw ICO image data
50
50
  * @returns Decoded image data with RGBA pixels
51
51
  */
52
- async decode(data) {
52
+ async decode(data, options) {
53
53
  if (!this.canDecode(data)) {
54
54
  throw new Error("Invalid ICO signature");
55
55
  }
@@ -100,7 +100,7 @@ export class ICOFormat {
100
100
  imageData[2] === 0x4e &&
101
101
  imageData[3] === 0x47) {
102
102
  // It's a PNG, decode it
103
- return await this.pngFormat.decode(imageData);
103
+ return await this.pngFormat.decode(imageData, options);
104
104
  }
105
105
  // Otherwise, it's a BMP without the file header (DIB format)
106
106
  return this.decodeDIB(imageData);
@@ -182,10 +182,10 @@ export class ICOFormat {
182
182
  * @param imageData Image data to encode
183
183
  * @returns Encoded ICO image bytes
184
184
  */
185
- async encode(imageData) {
185
+ async encode(imageData, _options) {
186
186
  const { width, height } = imageData;
187
187
  // Encode the image as PNG
188
- const pngData = await this.pngFormat.encode(imageData);
188
+ const pngData = await this.pngFormat.encode(imageData, undefined);
189
189
  // Create ICO file structure
190
190
  // ICONDIR (6 bytes) + ICONDIRENTRY (16 bytes) + PNG data
191
191
  const icoSize = 6 + 16 + pngData.length;
@@ -211,4 +211,32 @@ export class ICOFormat {
211
211
  result.set(pngData, 22);
212
212
  return result;
213
213
  }
214
+ /**
215
+ * Get the list of metadata fields supported by ICO format
216
+ */
217
+ getSupportedMetadata() {
218
+ return [
219
+ "frameCount", // Number of images in ICO file
220
+ ];
221
+ }
222
+ /**
223
+ * Extract metadata from ICO data without fully decoding the pixel data
224
+ * @param data Raw ICO data
225
+ * @returns Extracted metadata or undefined
226
+ */
227
+ extractMetadata(data) {
228
+ if (!this.canDecode(data)) {
229
+ return Promise.resolve(undefined);
230
+ }
231
+ // ICO files can contain multiple images, count them
232
+ const count = readUint16LE(data, 4);
233
+ const metadata = {
234
+ format: "ico",
235
+ compression: "none", // ICO typically contains uncompressed PNG or BMP
236
+ frameCount: count,
237
+ bitDepth: 32, // Most modern ICOs use 32-bit RGBA
238
+ colorType: "rgba",
239
+ };
240
+ return Promise.resolve(metadata);
241
+ }
214
242
  }
@@ -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
@@ -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) {
@@ -1060,4 +1072,80 @@ export class JPEGFormat {
1060
1072
  "dpiY",
1061
1073
  ];
1062
1074
  }
1075
+ /**
1076
+ * Extract metadata from JPEG data without fully decoding the pixel data
1077
+ * This quickly parses JFIF and EXIF markers to extract metadata
1078
+ * @param data Raw JPEG data
1079
+ * @returns Extracted metadata or undefined
1080
+ */
1081
+ extractMetadata(data) {
1082
+ if (!this.canDecode(data)) {
1083
+ return Promise.resolve(undefined);
1084
+ }
1085
+ // Parse JPEG structure to extract metadata
1086
+ let pos = 2; // Skip initial FF D8
1087
+ const metadata = {
1088
+ format: "jpeg",
1089
+ compression: "dct",
1090
+ frameCount: 1,
1091
+ bitDepth: 8,
1092
+ colorType: "rgb",
1093
+ };
1094
+ let width = 0;
1095
+ let height = 0;
1096
+ while (pos < data.length - 1) {
1097
+ if (data[pos] !== 0xff) {
1098
+ pos++;
1099
+ continue;
1100
+ }
1101
+ const marker = data[pos + 1];
1102
+ pos += 2;
1103
+ // SOF markers (Start of Frame) - get dimensions for DPI calculation
1104
+ if (marker >= 0xc0 && marker <= 0xcf &&
1105
+ marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {
1106
+ const length = (data[pos] << 8) | data[pos + 1];
1107
+ // precision at pos+2
1108
+ const precision = data[pos + 2];
1109
+ if (precision && precision !== 8) {
1110
+ metadata.bitDepth = precision;
1111
+ }
1112
+ height = (data[pos + 3] << 8) | data[pos + 4];
1113
+ width = (data[pos + 5] << 8) | data[pos + 6];
1114
+ // Check number of components
1115
+ const numComponents = data[pos + 7];
1116
+ if (numComponents === 1) {
1117
+ metadata.colorType = "grayscale";
1118
+ }
1119
+ // Don't break - continue parsing for metadata
1120
+ pos += length;
1121
+ continue;
1122
+ }
1123
+ // APP0 marker (JFIF)
1124
+ if (marker === 0xe0) {
1125
+ const length = (data[pos] << 8) | data[pos + 1];
1126
+ const appData = data.slice(pos + 2, pos + length);
1127
+ this.parseJFIF(appData, metadata, width, height);
1128
+ pos += length;
1129
+ continue;
1130
+ }
1131
+ // APP1 marker (EXIF)
1132
+ if (marker === 0xe1) {
1133
+ const length = (data[pos] << 8) | data[pos + 1];
1134
+ const appData = data.slice(pos + 2, pos + length);
1135
+ this.parseEXIF(appData, metadata);
1136
+ pos += length;
1137
+ continue;
1138
+ }
1139
+ // Skip other markers
1140
+ if (marker === 0xd9 || marker === 0xda)
1141
+ break; // EOI or SOS
1142
+ if (marker >= 0xd0 && marker <= 0xd8)
1143
+ continue; // RST markers have no length
1144
+ if (marker === 0x01)
1145
+ continue; // TEM has no length
1146
+ const length = (data[pos] << 8) | data[pos + 1];
1147
+ pos += length;
1148
+ }
1149
+ return Promise.resolve(Object.keys(metadata).length > 0 ? metadata : undefined);
1150
+ }
1063
1151
  }
@@ -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
@@ -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) {
@@ -174,4 +174,70 @@ export class PAMFormat {
174
174
  output.set(data, headerBytes.length);
175
175
  return Promise.resolve(output);
176
176
  }
177
+ /**
178
+ * Get the list of metadata fields supported by PAM format
179
+ */
180
+ getSupportedMetadata() {
181
+ return []; // PAM format doesn't support metadata preservation
182
+ }
183
+ /**
184
+ * Extract metadata from PAM data without fully decoding the pixel data
185
+ * @param data Raw PAM data
186
+ * @returns Extracted metadata or undefined
187
+ */
188
+ extractMetadata(data) {
189
+ if (!this.canDecode(data)) {
190
+ return Promise.resolve(undefined);
191
+ }
192
+ const metadata = {
193
+ format: "pam",
194
+ compression: "none",
195
+ frameCount: 1,
196
+ bitDepth: 8, // PAM typically uses 8 bits per channel
197
+ colorType: "rgba",
198
+ };
199
+ // Try to parse the header to get actual color type
200
+ const decoder = new TextDecoder();
201
+ const headerText = decoder.decode(data.slice(0, Math.min(200, data.length)));
202
+ // Look for TUPLTYPE to determine color type
203
+ const tupltypeMatch = headerText.match(/TUPLTYPE\s+(\S+)/);
204
+ if (tupltypeMatch) {
205
+ const tupltype = tupltypeMatch[1];
206
+ if (tupltype === "GRAYSCALE") {
207
+ metadata.colorType = "grayscale";
208
+ }
209
+ else if (tupltype === "RGB") {
210
+ metadata.colorType = "rgb";
211
+ }
212
+ else if (tupltype === "RGB_ALPHA") {
213
+ metadata.colorType = "rgba";
214
+ }
215
+ }
216
+ // Look for DEPTH to determine number of channels
217
+ const depthMatch = headerText.match(/DEPTH\s+(\d+)/);
218
+ if (depthMatch) {
219
+ const depth = parseInt(depthMatch[1]);
220
+ if (depth === 1) {
221
+ metadata.colorType = "grayscale";
222
+ }
223
+ else if (depth === 3) {
224
+ metadata.colorType = "rgb";
225
+ }
226
+ else if (depth === 4) {
227
+ metadata.colorType = "rgba";
228
+ }
229
+ }
230
+ // Look for MAXVAL to determine bit depth
231
+ const maxvalMatch = headerText.match(/MAXVAL\s+(\d+)/);
232
+ if (maxvalMatch) {
233
+ const maxval = parseInt(maxvalMatch[1]);
234
+ if (maxval === 255) {
235
+ metadata.bitDepth = 8;
236
+ }
237
+ else if (maxval === 65535) {
238
+ metadata.bitDepth = 16;
239
+ }
240
+ }
241
+ return Promise.resolve(metadata);
242
+ }
177
243
  }
@@ -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
@@ -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;
@@ -201,4 +201,49 @@ export class PCXFormat {
201
201
  result.set(rleData, header.length);
202
202
  return Promise.resolve(result);
203
203
  }
204
+ /**
205
+ * Get the list of metadata fields supported by PCX format
206
+ */
207
+ getSupportedMetadata() {
208
+ return [
209
+ "dpiX", // DPI in header
210
+ "dpiY", // DPI in header
211
+ ];
212
+ }
213
+ /**
214
+ * Extract metadata from PCX data without fully decoding the pixel data
215
+ * @param data Raw PCX data
216
+ * @returns Extracted metadata or undefined
217
+ */
218
+ extractMetadata(data) {
219
+ if (!this.canDecode(data)) {
220
+ return Promise.resolve(undefined);
221
+ }
222
+ const metadata = {
223
+ format: "pcx",
224
+ compression: "rle",
225
+ frameCount: 1,
226
+ bitDepth: data[3], // Bits per pixel per plane
227
+ colorType: "rgb",
228
+ };
229
+ // Check number of planes to determine color type
230
+ const numPlanes = data[65];
231
+ if (numPlanes === 1) {
232
+ metadata.colorType = "indexed";
233
+ }
234
+ else if (numPlanes === 3) {
235
+ metadata.colorType = "rgb";
236
+ }
237
+ else if (numPlanes === 4) {
238
+ metadata.colorType = "rgba";
239
+ }
240
+ // DPI information
241
+ const dpiX = data[12] | (data[13] << 8);
242
+ const dpiY = data[14] | (data[15] << 8);
243
+ if (dpiX > 0)
244
+ metadata.dpiX = dpiX;
245
+ if (dpiY > 0)
246
+ metadata.dpiY = dpiY;
247
+ return Promise.resolve(metadata);
248
+ }
204
249
  }
@@ -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
@@ -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
  }
@@ -112,7 +112,7 @@ export class PNGFormat extends PNGBase {
112
112
  * @param imageData Image data to encode
113
113
  * @returns Encoded PNG image bytes
114
114
  */
115
- async encode(imageData) {
115
+ async encode(imageData, _options) {
116
116
  const { width, height, data, metadata } = imageData;
117
117
  // Prepare IHDR chunk
118
118
  const ihdr = new Uint8Array(13);
@@ -137,4 +137,91 @@ export class PNGFormat extends PNGBase {
137
137
  // Concatenate all chunks
138
138
  return this.concatenateArrays(chunks);
139
139
  }
140
+ /**
141
+ * Get the list of metadata fields supported by PNG format
142
+ * Delegates to PNGBase implementation
143
+ */
144
+ getSupportedMetadata() {
145
+ return super.getSupportedMetadata();
146
+ }
147
+ /**
148
+ * Extract metadata from PNG data without fully decoding the pixel data
149
+ * This quickly parses PNG chunks to extract metadata
150
+ * @param data Raw PNG data
151
+ * @returns Extracted metadata or undefined
152
+ */
153
+ extractMetadata(data) {
154
+ if (!this.canDecode(data)) {
155
+ return Promise.resolve(undefined);
156
+ }
157
+ let pos = 8; // Skip PNG signature
158
+ let width = 0;
159
+ let height = 0;
160
+ const metadata = {
161
+ format: "png",
162
+ compression: "deflate",
163
+ frameCount: 1,
164
+ };
165
+ // Parse chunks for metadata only
166
+ while (pos < data.length) {
167
+ if (pos + 8 > data.length)
168
+ break;
169
+ const length = this.readUint32(data, pos);
170
+ pos += 4;
171
+ const type = String.fromCharCode(data[pos], data[pos + 1], data[pos + 2], data[pos + 3]);
172
+ pos += 4;
173
+ if (pos + length + 4 > data.length)
174
+ break;
175
+ const chunkData = data.slice(pos, pos + length);
176
+ pos += length;
177
+ pos += 4; // Skip CRC
178
+ if (type === "IHDR") {
179
+ width = this.readUint32(chunkData, 0);
180
+ height = this.readUint32(chunkData, 4);
181
+ // Parse bit depth and color type from IHDR
182
+ if (chunkData.length >= 9) {
183
+ metadata.bitDepth = chunkData[8];
184
+ const colorTypeCode = chunkData[9];
185
+ // PNG color types: 0=grayscale, 2=rgb, 3=indexed, 4=grayscale+alpha, 6=rgba
186
+ switch (colorTypeCode) {
187
+ case 0:
188
+ metadata.colorType = "grayscale";
189
+ break;
190
+ case 2:
191
+ metadata.colorType = "rgb";
192
+ break;
193
+ case 3:
194
+ metadata.colorType = "indexed";
195
+ break;
196
+ case 4:
197
+ metadata.colorType = "grayscale-alpha";
198
+ break;
199
+ case 6:
200
+ metadata.colorType = "rgba";
201
+ break;
202
+ }
203
+ }
204
+ }
205
+ else if (type === "pHYs") {
206
+ // Physical pixel dimensions
207
+ this.parsePhysChunk(chunkData, metadata, width, height);
208
+ }
209
+ else if (type === "tEXt") {
210
+ // Text chunk
211
+ this.parseTextChunk(chunkData, metadata);
212
+ }
213
+ else if (type === "iTXt") {
214
+ // International text chunk
215
+ this.parseITxtChunk(chunkData, metadata);
216
+ }
217
+ else if (type === "eXIf") {
218
+ // EXIF chunk
219
+ this.parseExifChunk(chunkData, metadata);
220
+ }
221
+ else if (type === "IEND") {
222
+ break;
223
+ }
224
+ }
225
+ return Promise.resolve(Object.keys(metadata).length > 0 ? metadata : undefined);
226
+ }
140
227
  }
@@ -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
  }