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/webp.js
CHANGED
|
@@ -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;
|
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
|
*/
|
|
@@ -221,6 +222,8 @@ export class Image {
|
|
|
221
222
|
* @returns Image instance
|
|
222
223
|
*/
|
|
223
224
|
static fromRGBA(width, height, data) {
|
|
225
|
+
// Validate dimensions for security (prevent integer overflow and heap exhaustion)
|
|
226
|
+
validateImageDimensions(width, height);
|
|
224
227
|
if (data.length !== width * height * 4) {
|
|
225
228
|
throw new Error(`Data length mismatch: expected ${width * height * 4}, got ${data.length}`);
|
|
226
229
|
}
|
|
@@ -237,6 +240,8 @@ export class Image {
|
|
|
237
240
|
if (!this.imageData)
|
|
238
241
|
throw new Error("No image loaded");
|
|
239
242
|
const { width, height, method = "bilinear" } = options;
|
|
243
|
+
// Validate new dimensions for security (prevent integer overflow and heap exhaustion)
|
|
244
|
+
validateImageDimensions(width, height);
|
|
240
245
|
const { data: srcData, width: srcWidth, height: srcHeight } = this.imageData;
|
|
241
246
|
let resizedData;
|
|
242
247
|
if (method === "nearest") {
|
|
@@ -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.
|
|
3
|
+
"version": "0.1.4",
|
|
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
|
}
|
|
@@ -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
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ASCIIFormat = void 0;
|
|
4
|
+
const security_js_1 = require("../utils/security.js");
|
|
4
5
|
/**
|
|
5
6
|
* ASCII format handler
|
|
6
7
|
* Converts images to ASCII art text representation
|
|
@@ -65,6 +66,11 @@ class ASCIIFormat {
|
|
|
65
66
|
data[4] === this.MAGIC_BYTES[4] &&
|
|
66
67
|
data[5] === this.MAGIC_BYTES[5];
|
|
67
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Decode ASCII art to a basic grayscale RGBA image
|
|
71
|
+
* @param data Raw ASCII art data
|
|
72
|
+
* @returns Decoded image data with grayscale RGBA pixels
|
|
73
|
+
*/
|
|
68
74
|
decode(data) {
|
|
69
75
|
if (!this.canDecode(data)) {
|
|
70
76
|
throw new Error("Invalid ASCII art signature");
|
|
@@ -86,6 +92,8 @@ class ASCIIFormat {
|
|
|
86
92
|
// Calculate dimensions
|
|
87
93
|
const height = artLines.length;
|
|
88
94
|
const width = Math.max(...artLines.map((line) => line.length));
|
|
95
|
+
// Validate dimensions for security (prevent integer overflow and heap exhaustion)
|
|
96
|
+
(0, security_js_1.validateImageDimensions)(width, height);
|
|
89
97
|
// Convert ASCII art back to image data
|
|
90
98
|
const imageData = new Uint8Array(width * height * 4);
|
|
91
99
|
const charset = this.CHARSETS[options.charset] || this.CHARSETS.simple;
|
|
@@ -114,6 +122,12 @@ class ASCIIFormat {
|
|
|
114
122
|
}
|
|
115
123
|
return Promise.resolve({ width, height, data: imageData });
|
|
116
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Encode RGBA image data to ASCII art
|
|
127
|
+
* @param imageData Image data to encode
|
|
128
|
+
* @param options Optional ASCII encoding options
|
|
129
|
+
* @returns Encoded ASCII art as UTF-8 bytes
|
|
130
|
+
*/
|
|
117
131
|
encode(imageData, options = {}) {
|
|
118
132
|
const { width: targetWidth = 80, charset = "simple", aspectRatio = 0.5, invert = false, } = options;
|
|
119
133
|
// Get character set
|
|
@@ -4,10 +4,27 @@ import type { ImageData, ImageFormat } from "../types.js";
|
|
|
4
4
|
* Implements a pure JavaScript BMP decoder and encoder
|
|
5
5
|
*/
|
|
6
6
|
export declare class BMPFormat implements ImageFormat {
|
|
7
|
+
/** Format name identifier */
|
|
7
8
|
readonly name = "bmp";
|
|
9
|
+
/** MIME type for BMP images */
|
|
8
10
|
readonly mimeType = "image/bmp";
|
|
11
|
+
/**
|
|
12
|
+
* Check if the given data is a BMP image
|
|
13
|
+
* @param data Raw image data to check
|
|
14
|
+
* @returns true if data has BMP signature
|
|
15
|
+
*/
|
|
9
16
|
canDecode(data: Uint8Array): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Decode BMP image data to RGBA
|
|
19
|
+
* @param data Raw BMP 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 BMP format
|
|
25
|
+
* @param imageData Image data to encode
|
|
26
|
+
* @returns Encoded BMP image bytes
|
|
27
|
+
*/
|
|
11
28
|
encode(imageData: ImageData): Promise<Uint8Array>;
|
|
12
29
|
private readUint16LE;
|
|
13
30
|
private readUint32LE;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BMPFormat = void 0;
|
|
4
|
+
const security_js_1 = require("../utils/security.js");
|
|
4
5
|
// Constants for unit conversions
|
|
5
6
|
const INCHES_PER_METER = 39.3701;
|
|
6
7
|
/**
|
|
@@ -9,12 +10,14 @@ const INCHES_PER_METER = 39.3701;
|
|
|
9
10
|
*/
|
|
10
11
|
class BMPFormat {
|
|
11
12
|
constructor() {
|
|
13
|
+
/** Format name identifier */
|
|
12
14
|
Object.defineProperty(this, "name", {
|
|
13
15
|
enumerable: true,
|
|
14
16
|
configurable: true,
|
|
15
17
|
writable: true,
|
|
16
18
|
value: "bmp"
|
|
17
19
|
});
|
|
20
|
+
/** MIME type for BMP images */
|
|
18
21
|
Object.defineProperty(this, "mimeType", {
|
|
19
22
|
enumerable: true,
|
|
20
23
|
configurable: true,
|
|
@@ -22,11 +25,21 @@ class BMPFormat {
|
|
|
22
25
|
value: "image/bmp"
|
|
23
26
|
});
|
|
24
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Check if the given data is a BMP image
|
|
30
|
+
* @param data Raw image data to check
|
|
31
|
+
* @returns true if data has BMP signature
|
|
32
|
+
*/
|
|
25
33
|
canDecode(data) {
|
|
26
34
|
// BMP signature: 'BM' (0x42 0x4D)
|
|
27
35
|
return data.length >= 2 &&
|
|
28
36
|
data[0] === 0x42 && data[1] === 0x4d;
|
|
29
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Decode BMP image data to RGBA
|
|
40
|
+
* @param data Raw BMP image data
|
|
41
|
+
* @returns Decoded image data with RGBA pixels
|
|
42
|
+
*/
|
|
30
43
|
decode(data) {
|
|
31
44
|
if (!this.canDecode(data)) {
|
|
32
45
|
throw new Error("Invalid BMP signature");
|
|
@@ -64,6 +77,8 @@ class BMPFormat {
|
|
|
64
77
|
// Handle negative height (top-down bitmap)
|
|
65
78
|
const isTopDown = height < 0;
|
|
66
79
|
const absHeight = Math.abs(height);
|
|
80
|
+
// Validate dimensions for security (prevent integer overflow and heap exhaustion)
|
|
81
|
+
(0, security_js_1.validateImageDimensions)(width, absHeight);
|
|
67
82
|
// Only support uncompressed BMPs for now
|
|
68
83
|
if (compression !== 0) {
|
|
69
84
|
throw new Error(`Compressed BMP not supported (compression type: ${compression})`);
|
|
@@ -97,6 +112,11 @@ class BMPFormat {
|
|
|
97
112
|
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
98
113
|
});
|
|
99
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Encode RGBA image data to BMP format
|
|
117
|
+
* @param imageData Image data to encode
|
|
118
|
+
* @returns Encoded BMP image bytes
|
|
119
|
+
*/
|
|
100
120
|
encode(imageData) {
|
|
101
121
|
const { width, height, data, metadata } = imageData;
|
|
102
122
|
// 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
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.GIFFormat = void 0;
|
|
4
4
|
const gif_decoder_js_1 = require("../utils/gif_decoder.js");
|
|
5
5
|
const gif_encoder_js_1 = require("../utils/gif_encoder.js");
|
|
6
|
+
const security_js_1 = require("../utils/security.js");
|
|
6
7
|
/**
|
|
7
8
|
* GIF format handler
|
|
8
9
|
* Now includes pure-JS implementation with custom LZW compression/decompression
|
|
@@ -17,12 +18,14 @@ const gif_encoder_js_1 = require("../utils/gif_encoder.js");
|
|
|
17
18
|
*/
|
|
18
19
|
class GIFFormat {
|
|
19
20
|
constructor() {
|
|
21
|
+
/** Format name identifier */
|
|
20
22
|
Object.defineProperty(this, "name", {
|
|
21
23
|
enumerable: true,
|
|
22
24
|
configurable: true,
|
|
23
25
|
writable: true,
|
|
24
26
|
value: "gif"
|
|
25
27
|
});
|
|
28
|
+
/** MIME type for GIF images */
|
|
26
29
|
Object.defineProperty(this, "mimeType", {
|
|
27
30
|
enumerable: true,
|
|
28
31
|
configurable: true,
|
|
@@ -30,9 +33,18 @@ class GIFFormat {
|
|
|
30
33
|
value: "image/gif"
|
|
31
34
|
});
|
|
32
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Check if this format supports multiple frames (animations)
|
|
38
|
+
* @returns true for GIF format
|
|
39
|
+
*/
|
|
33
40
|
supportsMultipleFrames() {
|
|
34
41
|
return true;
|
|
35
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Check if the given data is a GIF image
|
|
45
|
+
* @param data Raw image data to check
|
|
46
|
+
* @returns true if data has GIF signature
|
|
47
|
+
*/
|
|
36
48
|
canDecode(data) {
|
|
37
49
|
// GIF signature: "GIF87a" or "GIF89a"
|
|
38
50
|
return data.length >= 6 &&
|
|
@@ -41,6 +53,11 @@ class GIFFormat {
|
|
|
41
53
|
(data[4] === 0x37 || data[4] === 0x39) && // "7" or "9"
|
|
42
54
|
data[5] === 0x61; // "a"
|
|
43
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Decode GIF image data to RGBA (first frame only)
|
|
58
|
+
* @param data Raw GIF image data
|
|
59
|
+
* @returns Decoded image data with RGBA pixels of first frame
|
|
60
|
+
*/
|
|
44
61
|
async decode(data) {
|
|
45
62
|
if (!this.canDecode(data)) {
|
|
46
63
|
throw new Error("Invalid GIF signature");
|
|
@@ -49,6 +66,8 @@ class GIFFormat {
|
|
|
49
66
|
try {
|
|
50
67
|
const decoder = new gif_decoder_js_1.GIFDecoder(data);
|
|
51
68
|
const result = decoder.decode();
|
|
69
|
+
// Validate dimensions for security (prevent integer overflow and heap exhaustion)
|
|
70
|
+
(0, security_js_1.validateImageDimensions)(result.width, result.height);
|
|
52
71
|
// Extract metadata from comment extensions
|
|
53
72
|
const metadata = this.extractMetadata(data);
|
|
54
73
|
return {
|
|
@@ -65,6 +84,8 @@ class GIFFormat {
|
|
|
65
84
|
const width = this.readUint16LE(data, pos);
|
|
66
85
|
pos += 2;
|
|
67
86
|
const height = this.readUint16LE(data, pos);
|
|
87
|
+
// Validate dimensions for security (prevent integer overflow and heap exhaustion)
|
|
88
|
+
(0, security_js_1.validateImageDimensions)(width, height);
|
|
68
89
|
const rgba = await this.decodeUsingRuntime(data, width, height);
|
|
69
90
|
const metadata = this.extractMetadata(data);
|
|
70
91
|
return {
|
|
@@ -127,6 +148,11 @@ class GIFFormat {
|
|
|
127
148
|
}
|
|
128
149
|
return metadata;
|
|
129
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Encode RGBA image data to GIF format (single frame)
|
|
153
|
+
* @param imageData Image data to encode
|
|
154
|
+
* @returns Encoded GIF image bytes
|
|
155
|
+
*/
|
|
130
156
|
async encode(imageData) {
|
|
131
157
|
const { width, height, data, metadata } = imageData;
|
|
132
158
|
// 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;
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.JPEGFormat = void 0;
|
|
37
|
+
const security_js_1 = require("../utils/security.js");
|
|
37
38
|
// Constants for unit conversions
|
|
38
39
|
const CM_PER_INCH = 2.54;
|
|
39
40
|
/**
|
|
@@ -42,12 +43,14 @@ const CM_PER_INCH = 2.54;
|
|
|
42
43
|
*/
|
|
43
44
|
class JPEGFormat {
|
|
44
45
|
constructor() {
|
|
46
|
+
/** Format name identifier */
|
|
45
47
|
Object.defineProperty(this, "name", {
|
|
46
48
|
enumerable: true,
|
|
47
49
|
configurable: true,
|
|
48
50
|
writable: true,
|
|
49
51
|
value: "jpeg"
|
|
50
52
|
});
|
|
53
|
+
/** MIME type for JPEG images */
|
|
51
54
|
Object.defineProperty(this, "mimeType", {
|
|
52
55
|
enumerable: true,
|
|
53
56
|
configurable: true,
|
|
@@ -55,11 +58,21 @@ class JPEGFormat {
|
|
|
55
58
|
value: "image/jpeg"
|
|
56
59
|
});
|
|
57
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Check if the given data is a JPEG image
|
|
63
|
+
* @param data Raw image data to check
|
|
64
|
+
* @returns true if data has JPEG signature
|
|
65
|
+
*/
|
|
58
66
|
canDecode(data) {
|
|
59
67
|
// JPEG signature: FF D8 FF
|
|
60
68
|
return data.length >= 3 &&
|
|
61
69
|
data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff;
|
|
62
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Decode JPEG image data to RGBA
|
|
73
|
+
* @param data Raw JPEG image data
|
|
74
|
+
* @returns Decoded image data with RGBA pixels
|
|
75
|
+
*/
|
|
63
76
|
async decode(data) {
|
|
64
77
|
if (!this.canDecode(data)) {
|
|
65
78
|
throw new Error("Invalid JPEG signature");
|
|
@@ -114,6 +127,8 @@ class JPEGFormat {
|
|
|
114
127
|
if (width === 0 || height === 0) {
|
|
115
128
|
throw new Error("Could not determine JPEG dimensions");
|
|
116
129
|
}
|
|
130
|
+
// Validate dimensions for security (prevent integer overflow and heap exhaustion)
|
|
131
|
+
(0, security_js_1.validateImageDimensions)(width, height);
|
|
117
132
|
// For a pure JS implementation, we'd need to implement full JPEG decoding
|
|
118
133
|
// which is very complex. Instead, we'll use the browser/runtime's decoder.
|
|
119
134
|
const rgba = await this.decodeUsingRuntime(data, width, height);
|
|
@@ -124,6 +139,11 @@ class JPEGFormat {
|
|
|
124
139
|
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
125
140
|
};
|
|
126
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Encode RGBA image data to JPEG format
|
|
144
|
+
* @param imageData Image data to encode
|
|
145
|
+
* @returns Encoded JPEG image bytes
|
|
146
|
+
*/
|
|
127
147
|
async encode(imageData) {
|
|
128
148
|
const { width, height, data, metadata } = imageData;
|
|
129
149
|
// 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,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PNGFormat = void 0;
|
|
4
|
+
const security_js_1 = require("../utils/security.js");
|
|
4
5
|
// Constants for unit conversions
|
|
5
6
|
const INCHES_PER_METER = 39.3701;
|
|
6
7
|
/**
|
|
@@ -9,12 +10,14 @@ const INCHES_PER_METER = 39.3701;
|
|
|
9
10
|
*/
|
|
10
11
|
class PNGFormat {
|
|
11
12
|
constructor() {
|
|
13
|
+
/** Format name identifier */
|
|
12
14
|
Object.defineProperty(this, "name", {
|
|
13
15
|
enumerable: true,
|
|
14
16
|
configurable: true,
|
|
15
17
|
writable: true,
|
|
16
18
|
value: "png"
|
|
17
19
|
});
|
|
20
|
+
/** MIME type for PNG images */
|
|
18
21
|
Object.defineProperty(this, "mimeType", {
|
|
19
22
|
enumerable: true,
|
|
20
23
|
configurable: true,
|
|
@@ -22,6 +25,11 @@ class PNGFormat {
|
|
|
22
25
|
value: "image/png"
|
|
23
26
|
});
|
|
24
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Check if the given data is a PNG image
|
|
30
|
+
* @param data Raw image data to check
|
|
31
|
+
* @returns true if data has PNG signature
|
|
32
|
+
*/
|
|
25
33
|
canDecode(data) {
|
|
26
34
|
// PNG signature: 137 80 78 71 13 10 26 10
|
|
27
35
|
return data.length >= 8 &&
|
|
@@ -30,6 +38,11 @@ class PNGFormat {
|
|
|
30
38
|
data[4] === 13 && data[5] === 10 &&
|
|
31
39
|
data[6] === 26 && data[7] === 10;
|
|
32
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Decode PNG image data to RGBA
|
|
43
|
+
* @param data Raw PNG image data
|
|
44
|
+
* @returns Decoded image data with RGBA pixels
|
|
45
|
+
*/
|
|
33
46
|
async decode(data) {
|
|
34
47
|
if (!this.canDecode(data)) {
|
|
35
48
|
throw new Error("Invalid PNG signature");
|
|
@@ -82,6 +95,8 @@ class PNGFormat {
|
|
|
82
95
|
if (width === 0 || height === 0) {
|
|
83
96
|
throw new Error("Invalid PNG: missing IHDR chunk");
|
|
84
97
|
}
|
|
98
|
+
// Validate dimensions for security (prevent integer overflow and heap exhaustion)
|
|
99
|
+
(0, security_js_1.validateImageDimensions)(width, height);
|
|
85
100
|
// Concatenate IDAT chunks
|
|
86
101
|
const idatData = this.concatenateChunks(chunks);
|
|
87
102
|
// Decompress data
|
|
@@ -95,6 +110,11 @@ class PNGFormat {
|
|
|
95
110
|
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
96
111
|
};
|
|
97
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Encode RGBA image data to PNG format
|
|
115
|
+
* @param imageData Image data to encode
|
|
116
|
+
* @returns Encoded PNG image bytes
|
|
117
|
+
*/
|
|
98
118
|
async encode(imageData) {
|
|
99
119
|
const { width, height, data, metadata } = imageData;
|
|
100
120
|
// Prepare IHDR chunk
|