cross-image 0.1.2 → 0.1.4

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 (82) hide show
  1. package/README.md +35 -520
  2. package/esm/src/formats/ascii.d.ts +11 -0
  3. package/esm/src/formats/ascii.js +14 -0
  4. package/esm/src/formats/bmp.d.ts +17 -0
  5. package/esm/src/formats/bmp.js +20 -0
  6. package/esm/src/formats/gif.d.ts +21 -0
  7. package/esm/src/formats/gif.js +26 -0
  8. package/esm/src/formats/jpeg.d.ts +17 -0
  9. package/esm/src/formats/jpeg.js +20 -0
  10. package/esm/src/formats/png.d.ts +17 -0
  11. package/esm/src/formats/png.js +20 -0
  12. package/esm/src/formats/raw.d.ts +17 -0
  13. package/esm/src/formats/raw.js +20 -0
  14. package/esm/src/formats/tiff.d.ts +16 -0
  15. package/esm/src/formats/tiff.js +37 -12
  16. package/esm/src/formats/webp.d.ts +18 -0
  17. package/esm/src/formats/webp.js +21 -0
  18. package/esm/src/image.js +5 -0
  19. package/esm/src/utils/security.d.ts +28 -0
  20. package/esm/src/utils/security.js +48 -0
  21. package/esm/src/utils/webp_decoder.js +3 -0
  22. package/package.json +1 -4
  23. package/script/src/formats/ascii.d.ts +11 -0
  24. package/script/src/formats/ascii.js +14 -0
  25. package/script/src/formats/bmp.d.ts +17 -0
  26. package/script/src/formats/bmp.js +20 -0
  27. package/script/src/formats/gif.d.ts +21 -0
  28. package/script/src/formats/gif.js +26 -0
  29. package/script/src/formats/jpeg.d.ts +17 -0
  30. package/script/src/formats/jpeg.js +20 -0
  31. package/script/src/formats/png.d.ts +17 -0
  32. package/script/src/formats/png.js +20 -0
  33. package/script/src/formats/raw.d.ts +17 -0
  34. package/script/src/formats/raw.js +20 -0
  35. package/script/src/formats/tiff.d.ts +16 -0
  36. package/script/src/formats/tiff.js +37 -12
  37. package/script/src/formats/webp.d.ts +18 -0
  38. package/script/src/formats/webp.js +21 -0
  39. package/script/src/image.js +5 -0
  40. package/script/src/utils/security.d.ts +28 -0
  41. package/script/src/utils/security.js +53 -0
  42. package/script/src/utils/webp_decoder.js +3 -0
  43. package/esm/mod.d.ts.map +0 -1
  44. package/esm/src/formats/ascii.d.ts.map +0 -1
  45. package/esm/src/formats/bmp.d.ts.map +0 -1
  46. package/esm/src/formats/gif.d.ts.map +0 -1
  47. package/esm/src/formats/jpeg.d.ts.map +0 -1
  48. package/esm/src/formats/png.d.ts.map +0 -1
  49. package/esm/src/formats/raw.d.ts.map +0 -1
  50. package/esm/src/formats/tiff.d.ts.map +0 -1
  51. package/esm/src/formats/webp.d.ts.map +0 -1
  52. package/esm/src/image.d.ts.map +0 -1
  53. package/esm/src/types.d.ts.map +0 -1
  54. package/esm/src/utils/gif_decoder.d.ts.map +0 -1
  55. package/esm/src/utils/gif_encoder.d.ts.map +0 -1
  56. package/esm/src/utils/jpeg_decoder.d.ts.map +0 -1
  57. package/esm/src/utils/jpeg_encoder.d.ts.map +0 -1
  58. package/esm/src/utils/lzw.d.ts.map +0 -1
  59. package/esm/src/utils/resize.d.ts.map +0 -1
  60. package/esm/src/utils/tiff_lzw.d.ts.map +0 -1
  61. package/esm/src/utils/webp_decoder.d.ts.map +0 -1
  62. package/esm/src/utils/webp_encoder.d.ts.map +0 -1
  63. package/script/mod.d.ts.map +0 -1
  64. package/script/src/formats/ascii.d.ts.map +0 -1
  65. package/script/src/formats/bmp.d.ts.map +0 -1
  66. package/script/src/formats/gif.d.ts.map +0 -1
  67. package/script/src/formats/jpeg.d.ts.map +0 -1
  68. package/script/src/formats/png.d.ts.map +0 -1
  69. package/script/src/formats/raw.d.ts.map +0 -1
  70. package/script/src/formats/tiff.d.ts.map +0 -1
  71. package/script/src/formats/webp.d.ts.map +0 -1
  72. package/script/src/image.d.ts.map +0 -1
  73. package/script/src/types.d.ts.map +0 -1
  74. package/script/src/utils/gif_decoder.d.ts.map +0 -1
  75. package/script/src/utils/gif_encoder.d.ts.map +0 -1
  76. package/script/src/utils/jpeg_decoder.d.ts.map +0 -1
  77. package/script/src/utils/jpeg_encoder.d.ts.map +0 -1
  78. package/script/src/utils/lzw.d.ts.map +0 -1
  79. package/script/src/utils/resize.d.ts.map +0 -1
  80. package/script/src/utils/tiff_lzw.d.ts.map +0 -1
  81. package/script/src/utils/webp_decoder.d.ts.map +0 -1
  82. package/script/src/utils/webp_encoder.d.ts.map +0 -1
@@ -1,3 +1,4 @@
1
+ import { validateImageDimensions } from "../utils/security.js";
1
2
  // Constants for unit conversions
2
3
  const INCHES_PER_METER = 39.3701;
3
4
  /**
@@ -6,12 +7,14 @@ const INCHES_PER_METER = 39.3701;
6
7
  */
7
8
  export class BMPFormat {
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: "bmp"
14
16
  });
17
+ /** MIME type for BMP images */
15
18
  Object.defineProperty(this, "mimeType", {
16
19
  enumerable: true,
17
20
  configurable: true,
@@ -19,11 +22,21 @@ export class BMPFormat {
19
22
  value: "image/bmp"
20
23
  });
21
24
  }
25
+ /**
26
+ * Check if the given data is a BMP image
27
+ * @param data Raw image data to check
28
+ * @returns true if data has BMP signature
29
+ */
22
30
  canDecode(data) {
23
31
  // BMP signature: 'BM' (0x42 0x4D)
24
32
  return data.length >= 2 &&
25
33
  data[0] === 0x42 && data[1] === 0x4d;
26
34
  }
35
+ /**
36
+ * Decode BMP image data to RGBA
37
+ * @param data Raw BMP image data
38
+ * @returns Decoded image data with RGBA pixels
39
+ */
27
40
  decode(data) {
28
41
  if (!this.canDecode(data)) {
29
42
  throw new Error("Invalid BMP signature");
@@ -61,6 +74,8 @@ export class BMPFormat {
61
74
  // Handle negative height (top-down bitmap)
62
75
  const isTopDown = height < 0;
63
76
  const absHeight = Math.abs(height);
77
+ // Validate dimensions for security (prevent integer overflow and heap exhaustion)
78
+ validateImageDimensions(width, absHeight);
64
79
  // Only support uncompressed BMPs for now
65
80
  if (compression !== 0) {
66
81
  throw new Error(`Compressed BMP not supported (compression type: ${compression})`);
@@ -94,6 +109,11 @@ export class BMPFormat {
94
109
  metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
95
110
  });
96
111
  }
112
+ /**
113
+ * Encode RGBA image data to BMP format
114
+ * @param imageData Image data to encode
115
+ * @returns Encoded BMP image bytes
116
+ */
97
117
  encode(imageData) {
98
118
  const { width, height, data, metadata } = imageData;
99
119
  // Calculate sizes
@@ -12,12 +12,33 @@ import type { ImageData, ImageFormat, MultiFrameImageData } from "../types.js";
12
12
  * - Falls back to runtime APIs when pure-JS fails
13
13
  */
14
14
  export declare class GIFFormat implements ImageFormat {
15
+ /** Format name identifier */
15
16
  readonly name = "gif";
17
+ /** MIME type for GIF images */
16
18
  readonly mimeType = "image/gif";
19
+ /**
20
+ * Check if this format supports multiple frames (animations)
21
+ * @returns true for GIF format
22
+ */
17
23
  supportsMultipleFrames(): boolean;
24
+ /**
25
+ * Check if the given data is a GIF image
26
+ * @param data Raw image data to check
27
+ * @returns true if data has GIF signature
28
+ */
18
29
  canDecode(data: Uint8Array): boolean;
30
+ /**
31
+ * Decode GIF image data to RGBA (first frame only)
32
+ * @param data Raw GIF image data
33
+ * @returns Decoded image data with RGBA pixels of first frame
34
+ */
19
35
  decode(data: Uint8Array): Promise<ImageData>;
20
36
  private extractMetadata;
37
+ /**
38
+ * Encode RGBA image data to GIF format (single frame)
39
+ * @param imageData Image data to encode
40
+ * @returns Encoded GIF image bytes
41
+ */
21
42
  encode(imageData: ImageData): Promise<Uint8Array>;
22
43
  /**
23
44
  * Decode all frames from an animated GIF
@@ -1,5 +1,6 @@
1
1
  import { GIFDecoder } from "../utils/gif_decoder.js";
2
2
  import { GIFEncoder } from "../utils/gif_encoder.js";
3
+ import { validateImageDimensions } from "../utils/security.js";
3
4
  /**
4
5
  * GIF format handler
5
6
  * Now includes pure-JS implementation with custom LZW compression/decompression
@@ -14,12 +15,14 @@ import { GIFEncoder } from "../utils/gif_encoder.js";
14
15
  */
15
16
  export class GIFFormat {
16
17
  constructor() {
18
+ /** Format name identifier */
17
19
  Object.defineProperty(this, "name", {
18
20
  enumerable: true,
19
21
  configurable: true,
20
22
  writable: true,
21
23
  value: "gif"
22
24
  });
25
+ /** MIME type for GIF images */
23
26
  Object.defineProperty(this, "mimeType", {
24
27
  enumerable: true,
25
28
  configurable: true,
@@ -27,9 +30,18 @@ export class GIFFormat {
27
30
  value: "image/gif"
28
31
  });
29
32
  }
33
+ /**
34
+ * Check if this format supports multiple frames (animations)
35
+ * @returns true for GIF format
36
+ */
30
37
  supportsMultipleFrames() {
31
38
  return true;
32
39
  }
40
+ /**
41
+ * Check if the given data is a GIF image
42
+ * @param data Raw image data to check
43
+ * @returns true if data has GIF signature
44
+ */
33
45
  canDecode(data) {
34
46
  // GIF signature: "GIF87a" or "GIF89a"
35
47
  return data.length >= 6 &&
@@ -38,6 +50,11 @@ export class GIFFormat {
38
50
  (data[4] === 0x37 || data[4] === 0x39) && // "7" or "9"
39
51
  data[5] === 0x61; // "a"
40
52
  }
53
+ /**
54
+ * Decode GIF image data to RGBA (first frame only)
55
+ * @param data Raw GIF image data
56
+ * @returns Decoded image data with RGBA pixels of first frame
57
+ */
41
58
  async decode(data) {
42
59
  if (!this.canDecode(data)) {
43
60
  throw new Error("Invalid GIF signature");
@@ -46,6 +63,8 @@ export class GIFFormat {
46
63
  try {
47
64
  const decoder = new GIFDecoder(data);
48
65
  const result = decoder.decode();
66
+ // Validate dimensions for security (prevent integer overflow and heap exhaustion)
67
+ validateImageDimensions(result.width, result.height);
49
68
  // Extract metadata from comment extensions
50
69
  const metadata = this.extractMetadata(data);
51
70
  return {
@@ -62,6 +81,8 @@ export class GIFFormat {
62
81
  const width = this.readUint16LE(data, pos);
63
82
  pos += 2;
64
83
  const height = this.readUint16LE(data, pos);
84
+ // Validate dimensions for security (prevent integer overflow and heap exhaustion)
85
+ validateImageDimensions(width, height);
65
86
  const rgba = await this.decodeUsingRuntime(data, width, height);
66
87
  const metadata = this.extractMetadata(data);
67
88
  return {
@@ -124,6 +145,11 @@ export class GIFFormat {
124
145
  }
125
146
  return metadata;
126
147
  }
148
+ /**
149
+ * Encode RGBA image data to GIF format (single frame)
150
+ * @param imageData Image data to encode
151
+ * @returns Encoded GIF image bytes
152
+ */
127
153
  async encode(imageData) {
128
154
  const { width, height, data, metadata } = imageData;
129
155
  // Try pure-JS encoder first
@@ -4,10 +4,27 @@ import type { ImageData, ImageFormat } from "../types.js";
4
4
  * Implements a basic JPEG decoder and encoder
5
5
  */
6
6
  export declare class JPEGFormat implements ImageFormat {
7
+ /** Format name identifier */
7
8
  readonly name = "jpeg";
9
+ /** MIME type for JPEG images */
8
10
  readonly mimeType = "image/jpeg";
11
+ /**
12
+ * Check if the given data is a JPEG image
13
+ * @param data Raw image data to check
14
+ * @returns true if data has JPEG signature
15
+ */
9
16
  canDecode(data: Uint8Array): boolean;
17
+ /**
18
+ * Decode JPEG image data to RGBA
19
+ * @param data Raw JPEG 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 JPEG format
25
+ * @param imageData Image data to encode
26
+ * @returns Encoded JPEG image bytes
27
+ */
11
28
  encode(imageData: ImageData): Promise<Uint8Array>;
12
29
  private injectMetadata;
13
30
  private decodeUsingRuntime;
@@ -1,3 +1,4 @@
1
+ import { validateImageDimensions } from "../utils/security.js";
1
2
  // Constants for unit conversions
2
3
  const CM_PER_INCH = 2.54;
3
4
  /**
@@ -6,12 +7,14 @@ const CM_PER_INCH = 2.54;
6
7
  */
7
8
  export class JPEGFormat {
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: "jpeg"
14
16
  });
17
+ /** MIME type for JPEG images */
15
18
  Object.defineProperty(this, "mimeType", {
16
19
  enumerable: true,
17
20
  configurable: true,
@@ -19,11 +22,21 @@ export class JPEGFormat {
19
22
  value: "image/jpeg"
20
23
  });
21
24
  }
25
+ /**
26
+ * Check if the given data is a JPEG image
27
+ * @param data Raw image data to check
28
+ * @returns true if data has JPEG signature
29
+ */
22
30
  canDecode(data) {
23
31
  // JPEG signature: FF D8 FF
24
32
  return data.length >= 3 &&
25
33
  data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff;
26
34
  }
35
+ /**
36
+ * Decode JPEG image data to RGBA
37
+ * @param data Raw JPEG image data
38
+ * @returns Decoded image data with RGBA pixels
39
+ */
27
40
  async decode(data) {
28
41
  if (!this.canDecode(data)) {
29
42
  throw new Error("Invalid JPEG signature");
@@ -78,6 +91,8 @@ export class JPEGFormat {
78
91
  if (width === 0 || height === 0) {
79
92
  throw new Error("Could not determine JPEG dimensions");
80
93
  }
94
+ // Validate dimensions for security (prevent integer overflow and heap exhaustion)
95
+ validateImageDimensions(width, height);
81
96
  // For a pure JS implementation, we'd need to implement full JPEG decoding
82
97
  // which is very complex. Instead, we'll use the browser/runtime's decoder.
83
98
  const rgba = await this.decodeUsingRuntime(data, width, height);
@@ -88,6 +103,11 @@ export class JPEGFormat {
88
103
  metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
89
104
  };
90
105
  }
106
+ /**
107
+ * Encode RGBA image data to JPEG format
108
+ * @param imageData Image data to encode
109
+ * @returns Encoded JPEG image bytes
110
+ */
91
111
  async encode(imageData) {
92
112
  const { width, height, data, metadata } = imageData;
93
113
  // Try to use runtime encoding if available (better quality)
@@ -4,10 +4,27 @@ import type { ImageData, ImageFormat } from "../types.js";
4
4
  * Implements a pure JavaScript PNG decoder and encoder
5
5
  */
6
6
  export declare class PNGFormat implements ImageFormat {
7
+ /** Format name identifier */
7
8
  readonly name = "png";
9
+ /** MIME type for PNG images */
8
10
  readonly mimeType = "image/png";
11
+ /**
12
+ * Check if the given data is a PNG image
13
+ * @param data Raw image data to check
14
+ * @returns true if data has PNG signature
15
+ */
9
16
  canDecode(data: Uint8Array): boolean;
17
+ /**
18
+ * Decode PNG image data to RGBA
19
+ * @param data Raw PNG 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 PNG format
25
+ * @param imageData Image data to encode
26
+ * @returns Encoded PNG image bytes
27
+ */
11
28
  encode(imageData: ImageData): Promise<Uint8Array>;
12
29
  private readUint32;
13
30
  private writeUint32;
@@ -1,3 +1,4 @@
1
+ import { validateImageDimensions } from "../utils/security.js";
1
2
  // Constants for unit conversions
2
3
  const INCHES_PER_METER = 39.3701;
3
4
  /**
@@ -6,12 +7,14 @@ const INCHES_PER_METER = 39.3701;
6
7
  */
7
8
  export class PNGFormat {
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: "png"
14
16
  });
17
+ /** MIME type for PNG images */
15
18
  Object.defineProperty(this, "mimeType", {
16
19
  enumerable: true,
17
20
  configurable: true,
@@ -19,6 +22,11 @@ export class PNGFormat {
19
22
  value: "image/png"
20
23
  });
21
24
  }
25
+ /**
26
+ * Check if the given data is a PNG image
27
+ * @param data Raw image data to check
28
+ * @returns true if data has PNG signature
29
+ */
22
30
  canDecode(data) {
23
31
  // PNG signature: 137 80 78 71 13 10 26 10
24
32
  return data.length >= 8 &&
@@ -27,6 +35,11 @@ export class PNGFormat {
27
35
  data[4] === 13 && data[5] === 10 &&
28
36
  data[6] === 26 && data[7] === 10;
29
37
  }
38
+ /**
39
+ * Decode PNG image data to RGBA
40
+ * @param data Raw PNG 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 PNG signature");
@@ -79,6 +92,8 @@ export class PNGFormat {
79
92
  if (width === 0 || height === 0) {
80
93
  throw new Error("Invalid PNG: missing IHDR chunk");
81
94
  }
95
+ // Validate dimensions for security (prevent integer overflow and heap exhaustion)
96
+ validateImageDimensions(width, height);
82
97
  // Concatenate IDAT chunks
83
98
  const idatData = this.concatenateChunks(chunks);
84
99
  // Decompress data
@@ -92,6 +107,11 @@ export class PNGFormat {
92
107
  metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
93
108
  };
94
109
  }
110
+ /**
111
+ * Encode RGBA image data to PNG format
112
+ * @param imageData Image data to encode
113
+ * @returns Encoded PNG image bytes
114
+ */
95
115
  async encode(imageData) {
96
116
  const { width, height, data, metadata } = imageData;
97
117
  // Prepare IHDR chunk
@@ -10,12 +10,29 @@ import type { ImageData, ImageFormat } from "../types.js";
10
10
  * - RGBA pixel data (width * height * 4 bytes)
11
11
  */
12
12
  export declare class RAWFormat implements ImageFormat {
13
+ /** Format name identifier */
13
14
  readonly name = "raw";
15
+ /** MIME type for RAW images */
14
16
  readonly mimeType = "image/raw";
15
17
  private readonly MAGIC_BYTES;
16
18
  private readonly HEADER_SIZE;
19
+ /**
20
+ * Check if the given data is a RAW image
21
+ * @param data Raw image data to check
22
+ * @returns true if data has RAW signature
23
+ */
17
24
  canDecode(data: Uint8Array): boolean;
25
+ /**
26
+ * Decode RAW image data to RGBA
27
+ * @param data Raw RAW image data
28
+ * @returns Decoded image data with RGBA pixels
29
+ */
18
30
  decode(data: Uint8Array): Promise<ImageData>;
31
+ /**
32
+ * Encode RGBA image data to RAW format
33
+ * @param imageData Image data to encode
34
+ * @returns Encoded RAW image bytes
35
+ */
19
36
  encode(imageData: ImageData): Promise<Uint8Array>;
20
37
  private readUint32;
21
38
  private writeUint32;
@@ -1,3 +1,4 @@
1
+ import { validateImageDimensions } from "../utils/security.js";
1
2
  /**
2
3
  * RAW format handler
3
4
  * Implements a simple uncompressed RGBA format with a minimal header
@@ -10,12 +11,14 @@
10
11
  */
11
12
  export class RAWFormat {
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: "raw"
18
20
  });
21
+ /** MIME type for RAW images */
19
22
  Object.defineProperty(this, "mimeType", {
20
23
  enumerable: true,
21
24
  configurable: true,
@@ -35,6 +38,11 @@ export class RAWFormat {
35
38
  value: 12
36
39
  }); // 4 bytes magic + 4 bytes width + 4 bytes height
37
40
  }
41
+ /**
42
+ * Check if the given data is a RAW image
43
+ * @param data Raw image data to check
44
+ * @returns true if data has RAW signature
45
+ */
38
46
  canDecode(data) {
39
47
  // Check if data has at least header size and matches magic bytes
40
48
  if (data.length < this.HEADER_SIZE) {
@@ -45,6 +53,11 @@ export class RAWFormat {
45
53
  data[2] === this.MAGIC_BYTES[2] &&
46
54
  data[3] === this.MAGIC_BYTES[3];
47
55
  }
56
+ /**
57
+ * Decode RAW image data to RGBA
58
+ * @param data Raw RAW image data
59
+ * @returns Decoded image data with RGBA pixels
60
+ */
48
61
  decode(data) {
49
62
  if (!this.canDecode(data)) {
50
63
  throw new Error("Invalid RAW signature");
@@ -56,6 +69,8 @@ export class RAWFormat {
56
69
  if (width <= 0 || height <= 0) {
57
70
  throw new Error(`Invalid RAW dimensions: ${width}x${height}`);
58
71
  }
72
+ // Validate dimensions for security (prevent integer overflow and heap exhaustion)
73
+ validateImageDimensions(width, height);
59
74
  const expectedDataLength = width * height * 4;
60
75
  const actualDataLength = data.length - this.HEADER_SIZE;
61
76
  if (actualDataLength !== expectedDataLength) {
@@ -66,6 +81,11 @@ export class RAWFormat {
66
81
  pixelData.set(data.subarray(this.HEADER_SIZE));
67
82
  return Promise.resolve({ width, height, data: pixelData });
68
83
  }
84
+ /**
85
+ * Encode RGBA image data to RAW format
86
+ * @param imageData Image data to encode
87
+ * @returns Encoded RAW image bytes
88
+ */
69
89
  encode(imageData) {
70
90
  const { width, height, data } = imageData;
71
91
  // Validate input
@@ -14,10 +14,26 @@ export interface TIFFEncodeOptions {
14
14
  * Supports multi-page TIFF files.
15
15
  */
16
16
  export declare class TIFFFormat implements ImageFormat {
17
+ /** Format name identifier */
17
18
  readonly name = "tiff";
19
+ /** MIME type for TIFF images */
18
20
  readonly mimeType = "image/tiff";
21
+ /**
22
+ * Check if this format supports multiple frames (pages)
23
+ * @returns true for TIFF format
24
+ */
19
25
  supportsMultipleFrames(): boolean;
26
+ /**
27
+ * Check if the given data is a TIFF image
28
+ * @param data Raw image data to check
29
+ * @returns true if data has TIFF signature
30
+ */
20
31
  canDecode(data: Uint8Array): boolean;
32
+ /**
33
+ * Decode TIFF image data to RGBA (first page only)
34
+ * @param data Raw TIFF image data
35
+ * @returns Decoded image data with RGBA pixels of first page
36
+ */
21
37
  decode(data: Uint8Array): Promise<ImageData>;
22
38
  encode(imageData: ImageData, options?: unknown): Promise<Uint8Array>;
23
39
  /**
@@ -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;