cross-image 0.1.2 → 0.1.5

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 (88) hide show
  1. package/README.md +143 -606
  2. package/esm/mod.d.ts +5 -5
  3. package/esm/mod.js +5 -5
  4. package/esm/src/formats/ascii.d.ts +11 -0
  5. package/esm/src/formats/ascii.js +14 -0
  6. package/esm/src/formats/bmp.d.ts +17 -0
  7. package/esm/src/formats/bmp.js +20 -0
  8. package/esm/src/formats/gif.d.ts +21 -0
  9. package/esm/src/formats/gif.js +26 -0
  10. package/esm/src/formats/jpeg.d.ts +17 -0
  11. package/esm/src/formats/jpeg.js +20 -0
  12. package/esm/src/formats/png.d.ts +17 -0
  13. package/esm/src/formats/png.js +20 -0
  14. package/esm/src/formats/raw.d.ts +17 -0
  15. package/esm/src/formats/raw.js +20 -0
  16. package/esm/src/formats/tiff.d.ts +16 -0
  17. package/esm/src/formats/tiff.js +37 -12
  18. package/esm/src/formats/webp.d.ts +18 -0
  19. package/esm/src/formats/webp.js +21 -0
  20. package/esm/src/image.d.ts +34 -1
  21. package/esm/src/image.js +55 -9
  22. package/esm/src/utils/security.d.ts +28 -0
  23. package/esm/src/utils/security.js +48 -0
  24. package/esm/src/utils/webp_decoder.js +3 -0
  25. package/package.json +1 -4
  26. package/script/mod.d.ts +5 -5
  27. package/script/mod.js +5 -5
  28. package/script/src/formats/ascii.d.ts +11 -0
  29. package/script/src/formats/ascii.js +14 -0
  30. package/script/src/formats/bmp.d.ts +17 -0
  31. package/script/src/formats/bmp.js +20 -0
  32. package/script/src/formats/gif.d.ts +21 -0
  33. package/script/src/formats/gif.js +26 -0
  34. package/script/src/formats/jpeg.d.ts +17 -0
  35. package/script/src/formats/jpeg.js +20 -0
  36. package/script/src/formats/png.d.ts +17 -0
  37. package/script/src/formats/png.js +20 -0
  38. package/script/src/formats/raw.d.ts +17 -0
  39. package/script/src/formats/raw.js +20 -0
  40. package/script/src/formats/tiff.d.ts +16 -0
  41. package/script/src/formats/tiff.js +37 -12
  42. package/script/src/formats/webp.d.ts +18 -0
  43. package/script/src/formats/webp.js +21 -0
  44. package/script/src/image.d.ts +34 -1
  45. package/script/src/image.js +55 -9
  46. package/script/src/utils/security.d.ts +28 -0
  47. package/script/src/utils/security.js +53 -0
  48. package/script/src/utils/webp_decoder.js +3 -0
  49. package/esm/mod.d.ts.map +0 -1
  50. package/esm/src/formats/ascii.d.ts.map +0 -1
  51. package/esm/src/formats/bmp.d.ts.map +0 -1
  52. package/esm/src/formats/gif.d.ts.map +0 -1
  53. package/esm/src/formats/jpeg.d.ts.map +0 -1
  54. package/esm/src/formats/png.d.ts.map +0 -1
  55. package/esm/src/formats/raw.d.ts.map +0 -1
  56. package/esm/src/formats/tiff.d.ts.map +0 -1
  57. package/esm/src/formats/webp.d.ts.map +0 -1
  58. package/esm/src/image.d.ts.map +0 -1
  59. package/esm/src/types.d.ts.map +0 -1
  60. package/esm/src/utils/gif_decoder.d.ts.map +0 -1
  61. package/esm/src/utils/gif_encoder.d.ts.map +0 -1
  62. package/esm/src/utils/jpeg_decoder.d.ts.map +0 -1
  63. package/esm/src/utils/jpeg_encoder.d.ts.map +0 -1
  64. package/esm/src/utils/lzw.d.ts.map +0 -1
  65. package/esm/src/utils/resize.d.ts.map +0 -1
  66. package/esm/src/utils/tiff_lzw.d.ts.map +0 -1
  67. package/esm/src/utils/webp_decoder.d.ts.map +0 -1
  68. package/esm/src/utils/webp_encoder.d.ts.map +0 -1
  69. package/script/mod.d.ts.map +0 -1
  70. package/script/src/formats/ascii.d.ts.map +0 -1
  71. package/script/src/formats/bmp.d.ts.map +0 -1
  72. package/script/src/formats/gif.d.ts.map +0 -1
  73. package/script/src/formats/jpeg.d.ts.map +0 -1
  74. package/script/src/formats/png.d.ts.map +0 -1
  75. package/script/src/formats/raw.d.ts.map +0 -1
  76. package/script/src/formats/tiff.d.ts.map +0 -1
  77. package/script/src/formats/webp.d.ts.map +0 -1
  78. package/script/src/image.d.ts.map +0 -1
  79. package/script/src/types.d.ts.map +0 -1
  80. package/script/src/utils/gif_decoder.d.ts.map +0 -1
  81. package/script/src/utils/gif_encoder.d.ts.map +0 -1
  82. package/script/src/utils/jpeg_decoder.d.ts.map +0 -1
  83. package/script/src/utils/jpeg_encoder.d.ts.map +0 -1
  84. package/script/src/utils/lzw.d.ts.map +0 -1
  85. package/script/src/utils/resize.d.ts.map +0 -1
  86. package/script/src/utils/tiff_lzw.d.ts.map +0 -1
  87. package/script/src/utils/webp_decoder.d.ts.map +0 -1
  88. package/script/src/utils/webp_encoder.d.ts.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import { TIFFLZWDecoder, TIFFLZWEncoder } from "../utils/tiff_lzw.js";
2
+ import { validateImageDimensions } from "../utils/security.js";
2
3
  // Constants for unit conversions
3
4
  const DEFAULT_DPI = 72;
4
5
  /**
@@ -10,12 +11,14 @@ const DEFAULT_DPI = 72;
10
11
  */
11
12
  export class TIFFFormat {
12
13
  constructor() {
14
+ /** Format name identifier */
13
15
  Object.defineProperty(this, "name", {
14
16
  enumerable: true,
15
17
  configurable: true,
16
18
  writable: true,
17
19
  value: "tiff"
18
20
  });
21
+ /** MIME type for TIFF images */
19
22
  Object.defineProperty(this, "mimeType", {
20
23
  enumerable: true,
21
24
  configurable: true,
@@ -23,9 +26,18 @@ export class TIFFFormat {
23
26
  value: "image/tiff"
24
27
  });
25
28
  }
29
+ /**
30
+ * Check if this format supports multiple frames (pages)
31
+ * @returns true for TIFF format
32
+ */
26
33
  supportsMultipleFrames() {
27
34
  return true;
28
35
  }
36
+ /**
37
+ * Check if the given data is a TIFF image
38
+ * @param data Raw image data to check
39
+ * @returns true if data has TIFF signature
40
+ */
29
41
  canDecode(data) {
30
42
  // TIFF signature: "II" (little-endian) or "MM" (big-endian) followed by 42
31
43
  return data.length >= 4 &&
@@ -35,6 +47,11 @@ export class TIFFFormat {
35
47
  data[3] === 0x2a) // "MM\0*"
36
48
  );
37
49
  }
50
+ /**
51
+ * Decode TIFF image data to RGBA (first page only)
52
+ * @param data Raw TIFF image data
53
+ * @returns Decoded image data with RGBA pixels of first page
54
+ */
38
55
  async decode(data) {
39
56
  if (!this.canDecode(data)) {
40
57
  throw new Error("Invalid TIFF signature");
@@ -144,7 +161,7 @@ export class TIFFFormat {
144
161
  // IFD (Image File Directory)
145
162
  const ifdStart = result.length;
146
163
  // Count number of entries (including metadata)
147
- let numEntries = 11; // Base entries
164
+ let numEntries = 12; // Base entries (including ExtraSamples)
148
165
  if (metadata?.description)
149
166
  numEntries++;
150
167
  if (metadata?.author)
@@ -184,6 +201,8 @@ export class TIFFFormat {
184
201
  const yResOffset = dataOffset;
185
202
  this.writeIFDEntry(result, 0x011b, 5, 1, yResOffset);
186
203
  dataOffset += 8;
204
+ // ExtraSamples (0x0152) - 2 = unassociated alpha
205
+ this.writeIFDEntry(result, 0x0152, 3, 1, 2);
187
206
  // Optional metadata entries
188
207
  if (metadata?.description) {
189
208
  const descBytes = new TextEncoder().encode(metadata.description + "\0");
@@ -210,6 +229,11 @@ export class TIFFFormat {
210
229
  // Next IFD offset (0 = no more IFDs)
211
230
  this.writeUint32LE(result, 0);
212
231
  // Write variable-length data
232
+ // BitsPerSample values (must be written first to match offset calculation)
233
+ this.writeUint16LE(result, 8);
234
+ this.writeUint16LE(result, 8);
235
+ this.writeUint16LE(result, 8);
236
+ this.writeUint16LE(result, 8);
213
237
  // XResolution value (rational)
214
238
  const dpiX = metadata?.dpiX ?? DEFAULT_DPI;
215
239
  this.writeUint32LE(result, dpiX);
@@ -218,11 +242,6 @@ export class TIFFFormat {
218
242
  const dpiY = metadata?.dpiY ?? DEFAULT_DPI;
219
243
  this.writeUint32LE(result, dpiY);
220
244
  this.writeUint32LE(result, 1);
221
- // BitsPerSample values
222
- this.writeUint16LE(result, 8);
223
- this.writeUint16LE(result, 8);
224
- this.writeUint16LE(result, 8);
225
- this.writeUint16LE(result, 8);
226
245
  // Write metadata strings
227
246
  if (metadata?.description) {
228
247
  const descBytes = new TextEncoder().encode(metadata.description + "\0");
@@ -358,7 +377,7 @@ export class TIFFFormat {
358
377
  ifdOffsets.push(currentOffset);
359
378
  const ifdStart = result.length;
360
379
  // Count number of entries (including metadata only for first page)
361
- let numEntries = 11;
380
+ let numEntries = 12; // Base entries (including ExtraSamples)
362
381
  if (i === 0 && imageData.metadata) {
363
382
  if (imageData.metadata.description)
364
383
  numEntries++;
@@ -402,6 +421,8 @@ export class TIFFFormat {
402
421
  const yResOffset = dataOffset;
403
422
  this.writeIFDEntry(result, 0x011b, 5, 1, yResOffset);
404
423
  dataOffset += 8;
424
+ // ExtraSamples (0x0152) - 2 = unassociated alpha
425
+ this.writeIFDEntry(result, 0x0152, 3, 1, 2);
405
426
  // Metadata (only for first page)
406
427
  if (i === 0 && imageData.metadata) {
407
428
  if (imageData.metadata.description) {
@@ -432,6 +453,11 @@ export class TIFFFormat {
432
453
  this.writeUint32LE(result, nextIFDOffset);
433
454
  currentOffset = dataOffset;
434
455
  // Write variable-length data
456
+ // BitsPerSample values (must be written first to match offset calculation)
457
+ this.writeUint16LE(result, 8);
458
+ this.writeUint16LE(result, 8);
459
+ this.writeUint16LE(result, 8);
460
+ this.writeUint16LE(result, 8);
435
461
  // XResolution value (rational)
436
462
  const dpiX = (i === 0 && imageData.metadata?.dpiX) ||
437
463
  DEFAULT_DPI;
@@ -442,11 +468,6 @@ export class TIFFFormat {
442
468
  DEFAULT_DPI;
443
469
  this.writeUint32LE(result, dpiY);
444
470
  this.writeUint32LE(result, 1);
445
- // BitsPerSample values
446
- this.writeUint16LE(result, 8);
447
- this.writeUint16LE(result, 8);
448
- this.writeUint16LE(result, 8);
449
- this.writeUint16LE(result, 8);
450
471
  // Write metadata strings (only for first page)
451
472
  if (i === 0 && imageData.metadata) {
452
473
  if (imageData.metadata.description) {
@@ -707,6 +728,8 @@ export class TIFFFormat {
707
728
  // Uncompressed
708
729
  pixelData = data.slice(stripOffset, stripOffset + stripByteCount);
709
730
  }
731
+ // Validate dimensions for security (prevent integer overflow and heap exhaustion)
732
+ validateImageDimensions(width, height);
710
733
  // Convert to RGBA
711
734
  const rgba = new Uint8Array(width * height * 4);
712
735
  let srcPos = 0;
@@ -770,6 +793,8 @@ export class TIFFFormat {
770
793
  // Uncompressed
771
794
  pixelData = data.slice(stripOffset, stripOffset + stripByteCount);
772
795
  }
796
+ // Validate dimensions for security (prevent integer overflow and heap exhaustion)
797
+ validateImageDimensions(width, height);
773
798
  // Convert to RGBA
774
799
  const rgba = new Uint8Array(width * height * 4);
775
800
  let srcPos = 0;
@@ -4,10 +4,28 @@ import type { ImageData, ImageFormat, WebPEncodeOptions } from "../types.js";
4
4
  * Implements a basic WebP decoder and encoder
5
5
  */
6
6
  export declare class WebPFormat implements ImageFormat {
7
+ /** Format name identifier */
7
8
  readonly name = "webp";
9
+ /** MIME type for WebP images */
8
10
  readonly mimeType = "image/webp";
11
+ /**
12
+ * Check if the given data is a WebP image
13
+ * @param data Raw image data to check
14
+ * @returns true if data has WebP signature
15
+ */
9
16
  canDecode(data: Uint8Array): boolean;
17
+ /**
18
+ * Decode WebP image data to RGBA
19
+ * @param data Raw WebP image data
20
+ * @returns Decoded image data with RGBA pixels
21
+ */
10
22
  decode(data: Uint8Array): Promise<ImageData>;
23
+ /**
24
+ * Encode RGBA image data to WebP format
25
+ * @param imageData Image data to encode
26
+ * @param options Optional WebP encoding options
27
+ * @returns Encoded WebP image bytes
28
+ */
11
29
  encode(imageData: ImageData, options?: WebPEncodeOptions): Promise<Uint8Array>;
12
30
  private readUint32LE;
13
31
  private readUint24LE;
@@ -1,3 +1,4 @@
1
+ import { validateImageDimensions } from "../utils/security.js";
1
2
  // Default quality for WebP encoding when not specified
2
3
  const DEFAULT_WEBP_QUALITY = 90;
3
4
  /**
@@ -6,12 +7,14 @@ const DEFAULT_WEBP_QUALITY = 90;
6
7
  */
7
8
  export class WebPFormat {
8
9
  constructor() {
10
+ /** Format name identifier */
9
11
  Object.defineProperty(this, "name", {
10
12
  enumerable: true,
11
13
  configurable: true,
12
14
  writable: true,
13
15
  value: "webp"
14
16
  });
17
+ /** MIME type for WebP images */
15
18
  Object.defineProperty(this, "mimeType", {
16
19
  enumerable: true,
17
20
  configurable: true,
@@ -19,6 +22,11 @@ export class WebPFormat {
19
22
  value: "image/webp"
20
23
  });
21
24
  }
25
+ /**
26
+ * Check if the given data is a WebP image
27
+ * @param data Raw image data to check
28
+ * @returns true if data has WebP signature
29
+ */
22
30
  canDecode(data) {
23
31
  // WebP signature: "RIFF" + size + "WEBP"
24
32
  return data.length >= 12 &&
@@ -27,6 +35,11 @@ export class WebPFormat {
27
35
  data[8] === 0x57 && data[9] === 0x45 && // "WE"
28
36
  data[10] === 0x42 && data[11] === 0x50; // "BP"
29
37
  }
38
+ /**
39
+ * Decode WebP image data to RGBA
40
+ * @param data Raw WebP image data
41
+ * @returns Decoded image data with RGBA pixels
42
+ */
30
43
  async decode(data) {
31
44
  if (!this.canDecode(data)) {
32
45
  throw new Error("Invalid WebP signature");
@@ -89,6 +102,8 @@ export class WebPFormat {
89
102
  if (width === 0 || height === 0) {
90
103
  throw new Error("Could not determine WebP dimensions");
91
104
  }
105
+ // Validate dimensions for security (prevent integer overflow and heap exhaustion)
106
+ validateImageDimensions(width, height);
92
107
  // For a pure JS implementation, we'd need to implement full WebP decoding
93
108
  // which is very complex. Instead, we'll use the browser/runtime's decoder.
94
109
  const rgba = await this.decodeUsingRuntime(data, width, height);
@@ -99,6 +114,12 @@ export class WebPFormat {
99
114
  metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
100
115
  };
101
116
  }
117
+ /**
118
+ * Encode RGBA image data to WebP format
119
+ * @param imageData Image data to encode
120
+ * @param options Optional WebP encoding options
121
+ * @returns Encoded WebP image bytes
122
+ */
102
123
  async encode(imageData, options) {
103
124
  const { width, height, data, metadata } = imageData;
104
125
  const quality = options?.quality ?? DEFAULT_WEBP_QUALITY;
@@ -72,24 +72,49 @@ export declare class Image {
72
72
  * Get all registered formats
73
73
  */
74
74
  static getFormats(): readonly ImageFormat[];
75
+ /**
76
+ * Decode an image from bytes
77
+ * @param data Raw image data
78
+ * @param format Optional format hint (e.g., "png", "jpeg", "webp")
79
+ * @returns Image instance
80
+ */
81
+ static decode(data: Uint8Array, format?: string): Promise<Image>;
75
82
  /**
76
83
  * Read an image from bytes
84
+ * @deprecated Use `decode()` instead. This method will be removed in a future version.
77
85
  * @param data Raw image data
78
86
  * @param format Optional format hint (e.g., "png", "jpeg", "webp")
79
87
  * @returns Image instance
80
88
  */
81
89
  static read(data: Uint8Array, format?: string): Promise<Image>;
90
+ /**
91
+ * Decode all frames from a multi-frame image (GIF animation, multi-page TIFF)
92
+ * @param data Raw image data
93
+ * @param format Optional format hint (e.g., "gif", "tiff")
94
+ * @returns MultiFrameImageData with all frames
95
+ */
96
+ static decodeFrames(data: Uint8Array, format?: string): Promise<MultiFrameImageData>;
82
97
  /**
83
98
  * Read all frames from a multi-frame image (GIF animation, multi-page TIFF)
99
+ * @deprecated Use `decodeFrames()` instead. This method will be removed in a future version.
84
100
  * @param data Raw image data
85
101
  * @param format Optional format hint (e.g., "gif", "tiff")
86
102
  * @returns MultiFrameImageData with all frames
87
103
  */
88
104
  static readFrames(data: Uint8Array, format?: string): Promise<MultiFrameImageData>;
105
+ /**
106
+ * Encode multi-frame image data to bytes in the specified format
107
+ * @param format Format name (e.g., "gif", "tiff")
108
+ * @param imageData Multi-frame image data to encode
109
+ * @param options Optional format-specific encoding options
110
+ * @returns Encoded image bytes
111
+ */
112
+ static encodeFrames(format: string, imageData: MultiFrameImageData, options?: unknown): Promise<Uint8Array>;
89
113
  /**
90
114
  * Save multi-frame image data to bytes in the specified format
115
+ * @deprecated Use `encodeFrames()` instead. This method will be removed in a future version.
91
116
  * @param format Format name (e.g., "gif", "tiff")
92
- * @param imageData Multi-frame image data to save
117
+ * @param imageData Multi-frame image data to encode
93
118
  * @param options Optional format-specific encoding options
94
119
  * @returns Encoded image bytes
95
120
  */
@@ -108,8 +133,16 @@ export declare class Image {
108
133
  * @returns This image instance for chaining
109
134
  */
110
135
  resize(options: ResizeOptions): this;
136
+ /**
137
+ * Encode the image to bytes in the specified format
138
+ * @param format Format name (e.g., "png", "jpeg", "webp", "ascii")
139
+ * @param options Optional format-specific encoding options
140
+ * @returns Encoded image bytes
141
+ */
142
+ encode(format: string, options?: unknown): Promise<Uint8Array>;
111
143
  /**
112
144
  * Save the image to bytes in the specified format
145
+ * @deprecated Use `encode()` instead. This method will be removed in a future version.
113
146
  * @param format Format name (e.g., "png", "jpeg", "webp", "ascii")
114
147
  * @param options Optional format-specific encoding options
115
148
  * @returns Encoded image bytes
package/esm/src/image.js CHANGED
@@ -7,6 +7,7 @@ import { TIFFFormat } from "./formats/tiff.js";
7
7
  import { BMPFormat } from "./formats/bmp.js";
8
8
  import { RAWFormat } from "./formats/raw.js";
9
9
  import { ASCIIFormat } from "./formats/ascii.js";
10
+ import { validateImageDimensions } from "./utils/security.js";
10
11
  /**
11
12
  * Main Image class for reading, manipulating, and saving images
12
13
  */
@@ -150,12 +151,12 @@ export class Image {
150
151
  return Image.formats;
151
152
  }
152
153
  /**
153
- * Read an image from bytes
154
+ * Decode an image from bytes
154
155
  * @param data Raw image data
155
156
  * @param format Optional format hint (e.g., "png", "jpeg", "webp")
156
157
  * @returns Image instance
157
158
  */
158
- static async read(data, format) {
159
+ static async decode(data, format) {
159
160
  const image = new Image();
160
161
  // Try specified format first
161
162
  if (format) {
@@ -175,12 +176,22 @@ export class Image {
175
176
  throw new Error("Unsupported or unrecognized image format");
176
177
  }
177
178
  /**
178
- * Read all frames from a multi-frame image (GIF animation, multi-page TIFF)
179
+ * Read an image from bytes
180
+ * @deprecated Use `decode()` instead. This method will be removed in a future version.
181
+ * @param data Raw image data
182
+ * @param format Optional format hint (e.g., "png", "jpeg", "webp")
183
+ * @returns Image instance
184
+ */
185
+ static read(data, format) {
186
+ return Image.decode(data, format);
187
+ }
188
+ /**
189
+ * Decode all frames from a multi-frame image (GIF animation, multi-page TIFF)
179
190
  * @param data Raw image data
180
191
  * @param format Optional format hint (e.g., "gif", "tiff")
181
192
  * @returns MultiFrameImageData with all frames
182
193
  */
183
- static async readFrames(data, format) {
194
+ static async decodeFrames(data, format) {
184
195
  // Try specified format first
185
196
  if (format) {
186
197
  const handler = Image.formats.find((f) => f.name === format);
@@ -197,13 +208,23 @@ export class Image {
197
208
  throw new Error("Unsupported or unrecognized multi-frame image format");
198
209
  }
199
210
  /**
200
- * Save multi-frame image data to bytes in the specified format
211
+ * Read all frames from a multi-frame image (GIF animation, multi-page TIFF)
212
+ * @deprecated Use `decodeFrames()` instead. This method will be removed in a future version.
213
+ * @param data Raw image data
214
+ * @param format Optional format hint (e.g., "gif", "tiff")
215
+ * @returns MultiFrameImageData with all frames
216
+ */
217
+ static readFrames(data, format) {
218
+ return Image.decodeFrames(data, format);
219
+ }
220
+ /**
221
+ * Encode multi-frame image data to bytes in the specified format
201
222
  * @param format Format name (e.g., "gif", "tiff")
202
- * @param imageData Multi-frame image data to save
223
+ * @param imageData Multi-frame image data to encode
203
224
  * @param options Optional format-specific encoding options
204
225
  * @returns Encoded image bytes
205
226
  */
206
- static async saveFrames(format, imageData, options) {
227
+ static async encodeFrames(format, imageData, options) {
207
228
  const handler = Image.formats.find((f) => f.name === format);
208
229
  if (!handler) {
209
230
  throw new Error(`Unsupported format: ${format}`);
@@ -213,6 +234,17 @@ export class Image {
213
234
  }
214
235
  return await handler.encodeFrames(imageData, options);
215
236
  }
237
+ /**
238
+ * Save multi-frame image data to bytes in the specified format
239
+ * @deprecated Use `encodeFrames()` instead. This method will be removed in a future version.
240
+ * @param format Format name (e.g., "gif", "tiff")
241
+ * @param imageData Multi-frame image data to encode
242
+ * @param options Optional format-specific encoding options
243
+ * @returns Encoded image bytes
244
+ */
245
+ static saveFrames(format, imageData, options) {
246
+ return Image.encodeFrames(format, imageData, options);
247
+ }
216
248
  /**
217
249
  * Create an image from raw RGBA data
218
250
  * @param width Image width
@@ -221,6 +253,8 @@ export class Image {
221
253
  * @returns Image instance
222
254
  */
223
255
  static fromRGBA(width, height, data) {
256
+ // Validate dimensions for security (prevent integer overflow and heap exhaustion)
257
+ validateImageDimensions(width, height);
224
258
  if (data.length !== width * height * 4) {
225
259
  throw new Error(`Data length mismatch: expected ${width * height * 4}, got ${data.length}`);
226
260
  }
@@ -237,6 +271,8 @@ export class Image {
237
271
  if (!this.imageData)
238
272
  throw new Error("No image loaded");
239
273
  const { width, height, method = "bilinear" } = options;
274
+ // Validate new dimensions for security (prevent integer overflow and heap exhaustion)
275
+ validateImageDimensions(width, height);
240
276
  const { data: srcData, width: srcWidth, height: srcHeight } = this.imageData;
241
277
  let resizedData;
242
278
  if (method === "nearest") {
@@ -265,12 +301,12 @@ export class Image {
265
301
  return this;
266
302
  }
267
303
  /**
268
- * Save the image to bytes in the specified format
304
+ * Encode the image to bytes in the specified format
269
305
  * @param format Format name (e.g., "png", "jpeg", "webp", "ascii")
270
306
  * @param options Optional format-specific encoding options
271
307
  * @returns Encoded image bytes
272
308
  */
273
- async save(format, options) {
309
+ async encode(format, options) {
274
310
  if (!this.imageData)
275
311
  throw new Error("No image loaded");
276
312
  const handler = Image.formats.find((f) => f.name === format);
@@ -279,6 +315,16 @@ export class Image {
279
315
  }
280
316
  return await handler.encode(this.imageData, options);
281
317
  }
318
+ /**
319
+ * Save the image to bytes in the specified format
320
+ * @deprecated Use `encode()` instead. This method will be removed in a future version.
321
+ * @param format Format name (e.g., "png", "jpeg", "webp", "ascii")
322
+ * @param options Optional format-specific encoding options
323
+ * @returns Encoded image bytes
324
+ */
325
+ save(format, options) {
326
+ return this.encode(format, options);
327
+ }
282
328
  /**
283
329
  * Clone this image
284
330
  * @returns New image instance with copied data and metadata
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Security utilities for image processing
3
+ * Prevents integer overflow, heap exhaustion, and decompression bombs
4
+ */
5
+ /**
6
+ * Maximum safe image dimensions
7
+ * These limits prevent integer overflow and heap exhaustion attacks
8
+ */
9
+ export declare const MAX_IMAGE_DIMENSION = 65535;
10
+ export declare const MAX_IMAGE_PIXELS = 178956970;
11
+ /**
12
+ * Validates image dimensions to prevent security vulnerabilities
13
+ *
14
+ * @param width Image width in pixels
15
+ * @param height Image height in pixels
16
+ * @throws Error if dimensions are invalid or unsafe
17
+ */
18
+ export declare function validateImageDimensions(width: number, height: number): void;
19
+ /**
20
+ * Safely calculates buffer size for RGBA image data
21
+ *
22
+ * @param width Image width in pixels
23
+ * @param height Image height in pixels
24
+ * @returns Buffer size in bytes (width * height * 4)
25
+ * @throws Error if calculation would overflow
26
+ */
27
+ export declare function calculateBufferSize(width: number, height: number): number;
28
+ //# sourceMappingURL=security.d.ts.map
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Security utilities for image processing
3
+ * Prevents integer overflow, heap exhaustion, and decompression bombs
4
+ */
5
+ /**
6
+ * Maximum safe image dimensions
7
+ * These limits prevent integer overflow and heap exhaustion attacks
8
+ */
9
+ export const MAX_IMAGE_DIMENSION = 65535; // 2^16 - 1 (reasonable for most use cases)
10
+ export const MAX_IMAGE_PIXELS = 178956970; // ~179 megapixels (fits safely in memory)
11
+ /**
12
+ * Validates image dimensions to prevent security vulnerabilities
13
+ *
14
+ * @param width Image width in pixels
15
+ * @param height Image height in pixels
16
+ * @throws Error if dimensions are invalid or unsafe
17
+ */
18
+ export function validateImageDimensions(width, height) {
19
+ // Check for negative or zero dimensions
20
+ if (width <= 0 || height <= 0) {
21
+ throw new Error(`Invalid image dimensions: ${width}x${height} (dimensions must be positive)`);
22
+ }
23
+ // Check if dimensions are integers
24
+ if (!Number.isInteger(width) || !Number.isInteger(height)) {
25
+ throw new Error(`Invalid image dimensions: ${width}x${height} (dimensions must be integers)`);
26
+ }
27
+ // Check individual dimension limits
28
+ if (width > MAX_IMAGE_DIMENSION || height > MAX_IMAGE_DIMENSION) {
29
+ throw new Error(`Image dimensions too large: ${width}x${height} (maximum ${MAX_IMAGE_DIMENSION}x${MAX_IMAGE_DIMENSION})`);
30
+ }
31
+ // Check total pixel count to prevent excessive memory allocation
32
+ const pixelCount = width * height;
33
+ if (pixelCount > MAX_IMAGE_PIXELS) {
34
+ throw new Error(`Image size too large: ${width}x${height} (${pixelCount} pixels exceeds maximum ${MAX_IMAGE_PIXELS})`);
35
+ }
36
+ }
37
+ /**
38
+ * Safely calculates buffer size for RGBA image data
39
+ *
40
+ * @param width Image width in pixels
41
+ * @param height Image height in pixels
42
+ * @returns Buffer size in bytes (width * height * 4)
43
+ * @throws Error if calculation would overflow
44
+ */
45
+ export function calculateBufferSize(width, height) {
46
+ validateImageDimensions(width, height);
47
+ return width * height * 4;
48
+ }
@@ -19,6 +19,7 @@
19
19
  * @see https://developers.google.com/speed/webp/docs/riff_container
20
20
  * @see https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification
21
21
  */
22
+ import { validateImageDimensions } from "./security.js";
22
23
  // Helper to read little-endian values
23
24
  function readUint24LE(data, offset) {
24
25
  return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16);
@@ -252,6 +253,8 @@ export class WebPDecoder {
252
253
  }
253
254
  // Read Huffman codes
254
255
  const huffmanTables = this.readHuffmanCodes(reader, useColorCache, colorCacheBits);
256
+ // Validate dimensions for security (prevent integer overflow and heap exhaustion)
257
+ validateImageDimensions(width, height);
255
258
  // Decode the image using Huffman codes
256
259
  const pixelData = new Uint8Array(width * height * 4);
257
260
  let pixelIndex = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cross-image",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "description": "A pure JavaScript, dependency-free, cross-runtime image processing library for Deno, Node.js, and Bun.",
5
5
  "keywords": [
6
6
  "image",
@@ -34,8 +34,5 @@
34
34
  }
35
35
  },
36
36
  "scripts": {},
37
- "devDependencies": {
38
- "@types/node": "^20.9.0"
39
- },
40
37
  "_generatedBy": "dnt@dev"
41
38
  }
package/script/mod.d.ts CHANGED
@@ -2,21 +2,21 @@
2
2
  * @module @cross/image
3
3
  *
4
4
  * A pure JavaScript, dependency-free, cross-runtime image processing library.
5
- * Supports reading, resizing, and saving common image formats (PNG, JPEG, WebP, GIF, TIFF, BMP, RAW).
5
+ * Supports decoding, resizing, and encoding common image formats (PNG, JPEG, WebP, GIF, TIFF, BMP, RAW).
6
6
  *
7
7
  * @example
8
8
  * ```ts
9
9
  * import { Image } from "@cross/image";
10
10
  *
11
- * // Read an image
11
+ * // Decode an image
12
12
  * const data = await Deno.readFile("input.png");
13
- * const image = await Image.read(data);
13
+ * const image = await Image.decode(data);
14
14
  *
15
15
  * // Resize it
16
16
  * image.resize({ width: 200, height: 200 });
17
17
  *
18
- * // Save as different format
19
- * const output = await image.save("jpeg");
18
+ * // Encode as different format
19
+ * const output = await image.encode("jpeg");
20
20
  * await Deno.writeFile("output.jpg", output);
21
21
  * ```
22
22
  */
package/script/mod.js CHANGED
@@ -3,21 +3,21 @@
3
3
  * @module @cross/image
4
4
  *
5
5
  * A pure JavaScript, dependency-free, cross-runtime image processing library.
6
- * Supports reading, resizing, and saving common image formats (PNG, JPEG, WebP, GIF, TIFF, BMP, RAW).
6
+ * Supports decoding, resizing, and encoding common image formats (PNG, JPEG, WebP, GIF, TIFF, BMP, RAW).
7
7
  *
8
8
  * @example
9
9
  * ```ts
10
10
  * import { Image } from "@cross/image";
11
11
  *
12
- * // Read an image
12
+ * // Decode an image
13
13
  * const data = await Deno.readFile("input.png");
14
- * const image = await Image.read(data);
14
+ * const image = await Image.decode(data);
15
15
  *
16
16
  * // Resize it
17
17
  * image.resize({ width: 200, height: 200 });
18
18
  *
19
- * // Save as different format
20
- * const output = await image.save("jpeg");
19
+ * // Encode as different format
20
+ * const output = await image.encode("jpeg");
21
21
  * await Deno.writeFile("output.jpg", output);
22
22
  * ```
23
23
  */
@@ -17,7 +17,18 @@ export declare class ASCIIFormat implements ImageFormat {
17
17
  private readonly MAGIC_BYTES;
18
18
  private readonly CHARSETS;
19
19
  canDecode(data: Uint8Array): boolean;
20
+ /**
21
+ * Decode ASCII art to a basic grayscale RGBA image
22
+ * @param data Raw ASCII art data
23
+ * @returns Decoded image data with grayscale RGBA pixels
24
+ */
20
25
  decode(data: Uint8Array): Promise<ImageData>;
26
+ /**
27
+ * Encode RGBA image data to ASCII art
28
+ * @param imageData Image data to encode
29
+ * @param options Optional ASCII encoding options
30
+ * @returns Encoded ASCII art as UTF-8 bytes
31
+ */
21
32
  encode(imageData: ImageData, options?: ASCIIOptions): Promise<Uint8Array>;
22
33
  /**
23
34
  * Parse options from the options line