cross-image 0.1.5 → 0.2.1

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 (72) hide show
  1. package/README.md +36 -18
  2. package/esm/mod.d.ts +30 -4
  3. package/esm/mod.js +30 -4
  4. package/esm/src/formats/apng.d.ts +50 -0
  5. package/esm/src/formats/apng.js +364 -0
  6. package/esm/src/formats/bmp.d.ts +0 -6
  7. package/esm/src/formats/bmp.js +24 -47
  8. package/esm/src/formats/dng.d.ts +27 -0
  9. package/esm/src/formats/dng.js +191 -0
  10. package/esm/src/formats/gif.d.ts +0 -2
  11. package/esm/src/formats/gif.js +10 -16
  12. package/esm/src/formats/ico.d.ts +41 -0
  13. package/esm/src/formats/ico.js +214 -0
  14. package/esm/src/formats/pam.d.ts +43 -0
  15. package/esm/src/formats/pam.js +177 -0
  16. package/esm/src/formats/pcx.d.ts +13 -0
  17. package/esm/src/formats/pcx.js +204 -0
  18. package/esm/src/formats/png.d.ts +2 -21
  19. package/esm/src/formats/png.js +5 -429
  20. package/esm/src/formats/png_base.d.ts +108 -0
  21. package/esm/src/formats/png_base.js +487 -0
  22. package/esm/src/formats/tiff.d.ts +7 -7
  23. package/esm/src/formats/webp.d.ts +0 -1
  24. package/esm/src/formats/webp.js +4 -7
  25. package/esm/src/image.d.ts +99 -0
  26. package/esm/src/image.js +204 -2
  27. package/esm/src/utils/byte_utils.d.ts +30 -0
  28. package/esm/src/utils/byte_utils.js +50 -0
  29. package/esm/src/utils/gif_encoder.d.ts +3 -2
  30. package/esm/src/utils/gif_encoder.js +115 -48
  31. package/esm/src/utils/image_processing.d.ts +91 -0
  32. package/esm/src/utils/image_processing.js +231 -0
  33. package/esm/src/utils/webp_decoder.js +47 -12
  34. package/esm/src/utils/webp_encoder.js +97 -39
  35. package/package.json +4 -1
  36. package/script/mod.d.ts +30 -4
  37. package/script/mod.js +36 -6
  38. package/script/src/formats/apng.d.ts +50 -0
  39. package/script/src/formats/apng.js +368 -0
  40. package/script/src/formats/bmp.d.ts +0 -6
  41. package/script/src/formats/bmp.js +24 -47
  42. package/script/src/formats/dng.d.ts +27 -0
  43. package/script/src/formats/dng.js +195 -0
  44. package/script/src/formats/gif.d.ts +0 -2
  45. package/script/src/formats/gif.js +10 -16
  46. package/script/src/formats/ico.d.ts +41 -0
  47. package/script/src/formats/ico.js +218 -0
  48. package/script/src/formats/pam.d.ts +43 -0
  49. package/script/src/formats/pam.js +181 -0
  50. package/script/src/formats/pcx.d.ts +13 -0
  51. package/script/src/formats/pcx.js +208 -0
  52. package/script/src/formats/png.d.ts +2 -21
  53. package/script/src/formats/png.js +5 -429
  54. package/script/src/formats/png_base.d.ts +108 -0
  55. package/script/src/formats/png_base.js +491 -0
  56. package/script/src/formats/tiff.d.ts +7 -7
  57. package/script/src/formats/webp.d.ts +0 -1
  58. package/script/src/formats/webp.js +4 -7
  59. package/script/src/image.d.ts +99 -0
  60. package/script/src/image.js +204 -2
  61. package/script/src/utils/byte_utils.d.ts +30 -0
  62. package/script/src/utils/byte_utils.js +58 -0
  63. package/script/src/utils/gif_encoder.d.ts +3 -2
  64. package/script/src/utils/gif_encoder.js +115 -48
  65. package/script/src/utils/image_processing.d.ts +91 -0
  66. package/script/src/utils/image_processing.js +242 -0
  67. package/script/src/utils/webp_decoder.js +47 -12
  68. package/script/src/utils/webp_encoder.js +97 -39
  69. package/esm/src/formats/raw.d.ts +0 -40
  70. package/esm/src/formats/raw.js +0 -118
  71. package/script/src/formats/raw.d.ts +0 -40
  72. package/script/src/formats/raw.js +0 -122
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PAMFormat = void 0;
4
+ const security_js_1 = require("../utils/security.js");
5
+ /**
6
+ * PAM format handler
7
+ * Implements the Netpbm PAM (Portable Arbitrary Map) format.
8
+ * This is a standard uncompressed format supported by GIMP and other tools.
9
+ *
10
+ * Format structure:
11
+ * - Header (text):
12
+ * P7
13
+ * WIDTH <width>
14
+ * HEIGHT <height>
15
+ * DEPTH 4
16
+ * MAXVAL 255
17
+ * TUPLTYPE RGB_ALPHA
18
+ * ENDHDR
19
+ * - Data (binary):
20
+ * RGBA pixel data (width * height * 4 bytes)
21
+ */
22
+ class PAMFormat {
23
+ constructor() {
24
+ /** Format name identifier */
25
+ Object.defineProperty(this, "name", {
26
+ enumerable: true,
27
+ configurable: true,
28
+ writable: true,
29
+ value: "pam"
30
+ });
31
+ /** MIME type for PAM images */
32
+ Object.defineProperty(this, "mimeType", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: "image/x-portable-arbitrary-map"
37
+ });
38
+ }
39
+ /**
40
+ * Check if the given data is a PAM image
41
+ * @param data Raw image data to check
42
+ * @returns true if data has PAM signature
43
+ */
44
+ canDecode(data) {
45
+ // Check if data has at least magic bytes
46
+ if (data.length < 3) {
47
+ return false;
48
+ }
49
+ // Check for P7 followed by whitespace (usually newline)
50
+ return data[0] === 0x50 && // P
51
+ data[1] === 0x37 && // 7
52
+ (data[2] === 0x0a || data[2] === 0x0d || data[2] === 0x20); // \n, \r, or space
53
+ }
54
+ /**
55
+ * Decode PAM image data to RGBA
56
+ * @param data Raw PAM image data
57
+ * @returns Decoded image data with RGBA pixels
58
+ */
59
+ decode(data) {
60
+ if (!this.canDecode(data)) {
61
+ throw new Error("Invalid PAM signature");
62
+ }
63
+ // Parse header
64
+ let offset = 0;
65
+ let width = 0;
66
+ let height = 0;
67
+ let depth = 0;
68
+ let maxval = 0;
69
+ let headerDone = false;
70
+ const decoder = new TextDecoder();
71
+ // Find end of header
72
+ // We need to read line by line.
73
+ // Since we have the full buffer, we can just scan for newlines.
74
+ let lineStart = 0;
75
+ while (!headerDone && offset < data.length) {
76
+ // Find next newline
77
+ let lineEnd = -1;
78
+ for (let i = offset; i < data.length; i++) {
79
+ if (data[i] === 0x0a) {
80
+ lineEnd = i;
81
+ break;
82
+ }
83
+ }
84
+ if (lineEnd === -1) {
85
+ throw new Error("Unexpected end of file in header");
86
+ }
87
+ const line = decoder.decode(data.subarray(lineStart, lineEnd)).trim();
88
+ offset = lineEnd + 1; // Skip newline
89
+ lineStart = offset;
90
+ // Skip comments
91
+ if (line.startsWith("#"))
92
+ continue;
93
+ if (line === "")
94
+ continue;
95
+ if (line === "P7")
96
+ continue;
97
+ if (line === "ENDHDR") {
98
+ headerDone = true;
99
+ break;
100
+ }
101
+ // Parse fields
102
+ const parts = line.split(/\s+/);
103
+ if (parts.length >= 2) {
104
+ const key = parts[0];
105
+ const value = parts[1];
106
+ switch (key) {
107
+ case "WIDTH":
108
+ width = parseInt(value, 10);
109
+ break;
110
+ case "HEIGHT":
111
+ height = parseInt(value, 10);
112
+ break;
113
+ case "DEPTH":
114
+ depth = parseInt(value, 10);
115
+ break;
116
+ case "MAXVAL":
117
+ maxval = parseInt(value, 10);
118
+ break;
119
+ case "TUPLTYPE":
120
+ // Optional check
121
+ break;
122
+ }
123
+ }
124
+ }
125
+ if (!headerDone) {
126
+ throw new Error("Invalid PAM header: missing ENDHDR");
127
+ }
128
+ // Validate dimensions
129
+ if (width <= 0 || height <= 0) {
130
+ throw new Error(`Invalid PAM dimensions: ${width}x${height}`);
131
+ }
132
+ // Validate dimensions for security
133
+ (0, security_js_1.validateImageDimensions)(width, height);
134
+ // We only support DEPTH 4 (RGBA) and MAXVAL 255 for now
135
+ if (depth !== 4) {
136
+ throw new Error(`Unsupported PAM depth: ${depth}. Only depth 4 (RGBA) is supported.`);
137
+ }
138
+ if (maxval !== 255) {
139
+ throw new Error(`Unsupported PAM maxval: ${maxval}. Only maxval 255 is supported.`);
140
+ }
141
+ const expectedDataLength = width * height * 4;
142
+ const actualDataLength = data.length - offset;
143
+ if (actualDataLength < expectedDataLength) {
144
+ throw new Error(`Invalid PAM data length: expected ${expectedDataLength}, got ${actualDataLength}`);
145
+ }
146
+ // Extract pixel data
147
+ const pixelData = new Uint8Array(expectedDataLength);
148
+ pixelData.set(data.subarray(offset, offset + expectedDataLength));
149
+ return Promise.resolve({ width, height, data: pixelData });
150
+ }
151
+ /**
152
+ * Encode RGBA image data to PAM format
153
+ * @param imageData Image data to encode
154
+ * @returns Encoded PAM image bytes
155
+ */
156
+ encode(imageData) {
157
+ const { width, height, data } = imageData;
158
+ // Validate input
159
+ if (data.length !== width * height * 4) {
160
+ throw new Error(`Data length mismatch: expected ${width * height * 4}, got ${data.length}`);
161
+ }
162
+ // Create header
163
+ const header = `P7\n` +
164
+ `WIDTH ${width}\n` +
165
+ `HEIGHT ${height}\n` +
166
+ `DEPTH 4\n` +
167
+ `MAXVAL 255\n` +
168
+ `TUPLTYPE RGB_ALPHA\n` +
169
+ `ENDHDR\n`;
170
+ const encoder = new TextEncoder();
171
+ const headerBytes = encoder.encode(header);
172
+ // Create output buffer
173
+ const output = new Uint8Array(headerBytes.length + data.length);
174
+ // Write header
175
+ output.set(headerBytes, 0);
176
+ // Write pixel data
177
+ output.set(data, headerBytes.length);
178
+ return Promise.resolve(output);
179
+ }
180
+ }
181
+ exports.PAMFormat = PAMFormat;
@@ -0,0 +1,13 @@
1
+ import type { ImageData, ImageFormat } from "../types.js";
2
+ /**
3
+ * PCX format handler
4
+ * Implements PCX decoder and encoder
5
+ */
6
+ export declare class PCXFormat implements ImageFormat {
7
+ readonly name = "pcx";
8
+ readonly mimeType = "image/x-pcx";
9
+ canDecode(data: Uint8Array): boolean;
10
+ decode(data: Uint8Array): Promise<ImageData>;
11
+ encode(image: ImageData): Promise<Uint8Array>;
12
+ }
13
+ //# sourceMappingURL=pcx.d.ts.map
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PCXFormat = void 0;
4
+ /**
5
+ * PCX format handler
6
+ * Implements PCX decoder and encoder
7
+ */
8
+ class PCXFormat {
9
+ constructor() {
10
+ Object.defineProperty(this, "name", {
11
+ enumerable: true,
12
+ configurable: true,
13
+ writable: true,
14
+ value: "pcx"
15
+ });
16
+ Object.defineProperty(this, "mimeType", {
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true,
20
+ value: "image/x-pcx"
21
+ });
22
+ }
23
+ canDecode(data) {
24
+ // PCX header check
25
+ // Byte 0: Manufacturer (must be 0x0A)
26
+ // Byte 1: Version (0, 2, 3, 4, 5)
27
+ // Byte 2: Encoding (1 = RLE)
28
+ return data.length >= 128 &&
29
+ data[0] === 0x0A &&
30
+ (data[1] === 0 || data[1] === 2 || data[1] === 3 || data[1] === 4 ||
31
+ data[1] === 5) &&
32
+ data[2] === 1;
33
+ }
34
+ decode(data) {
35
+ if (!this.canDecode(data)) {
36
+ return Promise.reject(new Error("Invalid PCX data"));
37
+ }
38
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
39
+ // Parse header
40
+ // const manufacturer = view.getUint8(0);
41
+ // const version = view.getUint8(1);
42
+ // const encoding = view.getUint8(2);
43
+ const bitsPerPixel = view.getUint8(3);
44
+ const xMin = view.getUint16(4, true);
45
+ const yMin = view.getUint16(6, true);
46
+ const xMax = view.getUint16(8, true);
47
+ const yMax = view.getUint16(10, true);
48
+ // const hDpi = view.getUint16(12, true);
49
+ // const vDpi = view.getUint16(14, true);
50
+ // const colormap = data.slice(16, 64);
51
+ // const reserved = view.getUint8(64);
52
+ const nPlanes = view.getUint8(65);
53
+ const bytesPerLine = view.getUint16(66, true);
54
+ // const paletteInfo = view.getUint16(68, true);
55
+ // const hScreenSize = view.getUint16(70, true);
56
+ // const vScreenSize = view.getUint16(72, true);
57
+ const width = xMax - xMin + 1;
58
+ const height = yMax - yMin + 1;
59
+ if (width <= 0 || height <= 0) {
60
+ return Promise.reject(new Error("Invalid PCX dimensions"));
61
+ }
62
+ // Decode RLE data
63
+ let offset = 128;
64
+ const scanlineLength = nPlanes * bytesPerLine;
65
+ const rawData = new Uint8Array(height * scanlineLength);
66
+ let ptr = 0;
67
+ // Decode all scanlines
68
+ for (let y = 0; y < height; y++) {
69
+ let x = 0;
70
+ while (x < scanlineLength) {
71
+ if (offset >= data.length)
72
+ break;
73
+ let byte = data[offset++];
74
+ let count = 1;
75
+ if ((byte & 0xC0) === 0xC0) {
76
+ count = byte & 0x3F;
77
+ if (offset >= data.length)
78
+ break;
79
+ byte = data[offset++];
80
+ }
81
+ for (let i = 0; i < count; i++) {
82
+ if (ptr < rawData.length) {
83
+ rawData[ptr++] = byte;
84
+ }
85
+ x++;
86
+ }
87
+ }
88
+ }
89
+ const rgba = new Uint8Array(width * height * 4);
90
+ if (nPlanes === 3 && bitsPerPixel === 8) {
91
+ // 24-bit RGB
92
+ for (let y = 0; y < height; y++) {
93
+ for (let x = 0; x < width; x++) {
94
+ const r = rawData[y * scanlineLength + x];
95
+ const g = rawData[y * scanlineLength + bytesPerLine + x];
96
+ const b = rawData[y * scanlineLength + 2 * bytesPerLine + x];
97
+ const idx = (y * width + x) * 4;
98
+ rgba[idx] = r;
99
+ rgba[idx + 1] = g;
100
+ rgba[idx + 2] = b;
101
+ rgba[idx + 3] = 255;
102
+ }
103
+ }
104
+ }
105
+ else if (nPlanes === 1 && bitsPerPixel === 8) {
106
+ // 8-bit palette
107
+ // Check for palette at end of file
108
+ let palette;
109
+ if (data[data.length - 769] === 0x0C) {
110
+ palette = data.slice(data.length - 768);
111
+ }
112
+ else {
113
+ // Fallback or error?
114
+ // Some old PCX might use header palette but that's only 16 colors.
115
+ // For 8bpp we expect 256 color palette at end.
116
+ return Promise.reject(new Error("Missing PCX palette"));
117
+ }
118
+ for (let y = 0; y < height; y++) {
119
+ for (let x = 0; x < width; x++) {
120
+ const colorIndex = rawData[y * scanlineLength + x];
121
+ const idx = (y * width + x) * 4;
122
+ rgba[idx] = palette[colorIndex * 3];
123
+ rgba[idx + 1] = palette[colorIndex * 3 + 1];
124
+ rgba[idx + 2] = palette[colorIndex * 3 + 2];
125
+ rgba[idx + 3] = 255;
126
+ }
127
+ }
128
+ }
129
+ else {
130
+ // Unsupported PCX format (e.g. 1-bit, 4-bit)
131
+ // For now only supporting 24-bit and 8-bit
132
+ return Promise.reject(new Error("Unsupported PCX format"));
133
+ }
134
+ return Promise.resolve({
135
+ width,
136
+ height,
137
+ data: rgba,
138
+ });
139
+ }
140
+ encode(image) {
141
+ const width = image.width;
142
+ const height = image.height;
143
+ const data = image.data;
144
+ // We will encode as 24-bit RGB (3 planes)
145
+ const header = new Uint8Array(128);
146
+ const view = new DataView(header.buffer);
147
+ view.setUint8(0, 0x0A); // Manufacturer
148
+ view.setUint8(1, 5); // Version 3.0+
149
+ view.setUint8(2, 1); // Encoding RLE
150
+ view.setUint8(3, 8); // Bits per pixel (8 per plane)
151
+ view.setUint16(4, 0, true); // XMin
152
+ view.setUint16(6, 0, true); // YMin
153
+ view.setUint16(8, width - 1, true); // XMax
154
+ view.setUint16(10, height - 1, true); // YMax
155
+ view.setUint16(12, 72, true); // HDpi
156
+ view.setUint16(14, 72, true); // VDpi
157
+ view.setUint8(65, 3); // NPlanes (3 for RGB)
158
+ view.setUint16(66, width + (width % 2), true); // BytesPerLine (must be even)
159
+ view.setUint16(68, 1, true); // PaletteInfo (Color/BW)
160
+ const bytesPerLine = width + (width % 2);
161
+ const _scanlineLength = bytesPerLine * 3;
162
+ const rleData = [];
163
+ // Helper to write RLE
164
+ const writeRLE = (byte, count) => {
165
+ if ((byte & 0xC0) === 0xC0 || count > 1) {
166
+ rleData.push(0xC0 | count);
167
+ rleData.push(byte);
168
+ }
169
+ else {
170
+ rleData.push(byte);
171
+ }
172
+ };
173
+ for (let y = 0; y < height; y++) {
174
+ // Prepare scanline planes
175
+ const lineR = new Uint8Array(bytesPerLine);
176
+ const lineG = new Uint8Array(bytesPerLine);
177
+ const lineB = new Uint8Array(bytesPerLine);
178
+ for (let x = 0; x < width; x++) {
179
+ const idx = (y * width + x) * 4;
180
+ lineR[x] = data[idx];
181
+ lineG[x] = data[idx + 1];
182
+ lineB[x] = data[idx + 2];
183
+ }
184
+ // Compress each plane
185
+ for (const plane of [lineR, lineG, lineB]) {
186
+ let currentByte = plane[0];
187
+ let runLength = 1;
188
+ for (let x = 1; x < bytesPerLine; x++) {
189
+ const byte = plane[x];
190
+ if (byte === currentByte && runLength < 63) {
191
+ runLength++;
192
+ }
193
+ else {
194
+ writeRLE(currentByte, runLength);
195
+ currentByte = byte;
196
+ runLength = 1;
197
+ }
198
+ }
199
+ writeRLE(currentByte, runLength);
200
+ }
201
+ }
202
+ const result = new Uint8Array(header.length + rleData.length);
203
+ result.set(header);
204
+ result.set(rleData, header.length);
205
+ return Promise.resolve(result);
206
+ }
207
+ }
208
+ exports.PCXFormat = PCXFormat;
@@ -1,9 +1,10 @@
1
1
  import type { ImageData, ImageFormat } from "../types.js";
2
+ import { PNGBase } from "./png_base.js";
2
3
  /**
3
4
  * PNG format handler
4
5
  * Implements a pure JavaScript PNG decoder and encoder
5
6
  */
6
- export declare class PNGFormat implements ImageFormat {
7
+ export declare class PNGFormat extends PNGBase implements ImageFormat {
7
8
  /** Format name identifier */
8
9
  readonly name = "png";
9
10
  /** MIME type for PNG images */
@@ -26,25 +27,5 @@ export declare class PNGFormat implements ImageFormat {
26
27
  * @returns Encoded PNG image bytes
27
28
  */
28
29
  encode(imageData: ImageData): Promise<Uint8Array>;
29
- private readUint32;
30
- private writeUint32;
31
- private concatenateChunks;
32
- private inflate;
33
- private deflate;
34
- private unfilterAndConvert;
35
- private unfilterScanline;
36
- private paethPredictor;
37
- private filterData;
38
- private getBytesPerPixel;
39
- private getBitsPerPixel;
40
- private createChunk;
41
- private crc32;
42
- private parsePhysChunk;
43
- private parseTextChunk;
44
- private parseITxtChunk;
45
- private parseExifChunk;
46
- private createPhysChunk;
47
- private createTextChunk;
48
- private createExifChunk;
49
30
  }
50
31
  //# sourceMappingURL=png.d.ts.map