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.
- package/README.md +35 -520
- package/esm/src/formats/ascii.d.ts +11 -0
- package/esm/src/formats/ascii.js +14 -0
- package/esm/src/formats/bmp.d.ts +17 -0
- package/esm/src/formats/bmp.js +20 -0
- package/esm/src/formats/gif.d.ts +21 -0
- package/esm/src/formats/gif.js +26 -0
- package/esm/src/formats/jpeg.d.ts +17 -0
- package/esm/src/formats/jpeg.js +20 -0
- package/esm/src/formats/png.d.ts +17 -0
- package/esm/src/formats/png.js +20 -0
- package/esm/src/formats/raw.d.ts +17 -0
- package/esm/src/formats/raw.js +20 -0
- package/esm/src/formats/tiff.d.ts +16 -0
- package/esm/src/formats/tiff.js +37 -12
- package/esm/src/formats/webp.d.ts +18 -0
- package/esm/src/formats/webp.js +21 -0
- package/esm/src/image.js +5 -0
- package/esm/src/utils/security.d.ts +28 -0
- package/esm/src/utils/security.js +48 -0
- package/esm/src/utils/webp_decoder.js +3 -0
- package/package.json +1 -4
- package/script/src/formats/ascii.d.ts +11 -0
- package/script/src/formats/ascii.js +14 -0
- package/script/src/formats/bmp.d.ts +17 -0
- package/script/src/formats/bmp.js +20 -0
- package/script/src/formats/gif.d.ts +21 -0
- package/script/src/formats/gif.js +26 -0
- package/script/src/formats/jpeg.d.ts +17 -0
- package/script/src/formats/jpeg.js +20 -0
- package/script/src/formats/png.d.ts +17 -0
- package/script/src/formats/png.js +20 -0
- package/script/src/formats/raw.d.ts +17 -0
- package/script/src/formats/raw.js +20 -0
- package/script/src/formats/tiff.d.ts +16 -0
- package/script/src/formats/tiff.js +37 -12
- package/script/src/formats/webp.d.ts +18 -0
- package/script/src/formats/webp.js +21 -0
- package/script/src/image.js +5 -0
- package/script/src/utils/security.d.ts +28 -0
- package/script/src/utils/security.js +53 -0
- package/script/src/utils/webp_decoder.js +3 -0
- package/esm/mod.d.ts.map +0 -1
- package/esm/src/formats/ascii.d.ts.map +0 -1
- package/esm/src/formats/bmp.d.ts.map +0 -1
- package/esm/src/formats/gif.d.ts.map +0 -1
- package/esm/src/formats/jpeg.d.ts.map +0 -1
- package/esm/src/formats/png.d.ts.map +0 -1
- package/esm/src/formats/raw.d.ts.map +0 -1
- package/esm/src/formats/tiff.d.ts.map +0 -1
- package/esm/src/formats/webp.d.ts.map +0 -1
- package/esm/src/image.d.ts.map +0 -1
- package/esm/src/types.d.ts.map +0 -1
- package/esm/src/utils/gif_decoder.d.ts.map +0 -1
- package/esm/src/utils/gif_encoder.d.ts.map +0 -1
- package/esm/src/utils/jpeg_decoder.d.ts.map +0 -1
- package/esm/src/utils/jpeg_encoder.d.ts.map +0 -1
- package/esm/src/utils/lzw.d.ts.map +0 -1
- package/esm/src/utils/resize.d.ts.map +0 -1
- package/esm/src/utils/tiff_lzw.d.ts.map +0 -1
- package/esm/src/utils/webp_decoder.d.ts.map +0 -1
- package/esm/src/utils/webp_encoder.d.ts.map +0 -1
- package/script/mod.d.ts.map +0 -1
- package/script/src/formats/ascii.d.ts.map +0 -1
- package/script/src/formats/bmp.d.ts.map +0 -1
- package/script/src/formats/gif.d.ts.map +0 -1
- package/script/src/formats/jpeg.d.ts.map +0 -1
- package/script/src/formats/png.d.ts.map +0 -1
- package/script/src/formats/raw.d.ts.map +0 -1
- package/script/src/formats/tiff.d.ts.map +0 -1
- package/script/src/formats/webp.d.ts.map +0 -1
- package/script/src/image.d.ts.map +0 -1
- package/script/src/types.d.ts.map +0 -1
- package/script/src/utils/gif_decoder.d.ts.map +0 -1
- package/script/src/utils/gif_encoder.d.ts.map +0 -1
- package/script/src/utils/jpeg_decoder.d.ts.map +0 -1
- package/script/src/utils/jpeg_encoder.d.ts.map +0 -1
- package/script/src/utils/lzw.d.ts.map +0 -1
- package/script/src/utils/resize.d.ts.map +0 -1
- package/script/src/utils/tiff_lzw.d.ts.map +0 -1
- package/script/src/utils/webp_decoder.d.ts.map +0 -1
- package/script/src/utils/webp_encoder.d.ts.map +0 -1
package/esm/src/formats/bmp.js
CHANGED
|
@@ -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
|
package/esm/src/formats/gif.d.ts
CHANGED
|
@@ -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
|
package/esm/src/formats/gif.js
CHANGED
|
@@ -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;
|
package/esm/src/formats/jpeg.js
CHANGED
|
@@ -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)
|
package/esm/src/formats/png.d.ts
CHANGED
|
@@ -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;
|
package/esm/src/formats/png.js
CHANGED
|
@@ -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
|
package/esm/src/formats/raw.d.ts
CHANGED
|
@@ -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;
|
package/esm/src/formats/raw.js
CHANGED
|
@@ -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
|
/**
|
package/esm/src/formats/tiff.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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;
|