cross-image 0.2.0 → 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 (48) hide show
  1. package/README.md +22 -16
  2. package/esm/mod.d.ts +3 -1
  3. package/esm/mod.js +3 -1
  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.js +4 -4
  9. package/esm/src/formats/gif.d.ts +0 -2
  10. package/esm/src/formats/gif.js +10 -16
  11. package/esm/src/formats/ico.d.ts +41 -0
  12. package/esm/src/formats/ico.js +214 -0
  13. package/esm/src/formats/pcx.js +1 -1
  14. package/esm/src/formats/png.d.ts +2 -21
  15. package/esm/src/formats/png.js +5 -429
  16. package/esm/src/formats/png_base.d.ts +108 -0
  17. package/esm/src/formats/png_base.js +487 -0
  18. package/esm/src/formats/webp.d.ts +0 -1
  19. package/esm/src/formats/webp.js +4 -7
  20. package/esm/src/image.js +4 -0
  21. package/esm/src/utils/byte_utils.d.ts +30 -0
  22. package/esm/src/utils/byte_utils.js +50 -0
  23. package/esm/src/utils/gif_encoder.d.ts +3 -2
  24. package/esm/src/utils/gif_encoder.js +115 -48
  25. package/package.json +1 -1
  26. package/script/mod.d.ts +3 -1
  27. package/script/mod.js +6 -2
  28. package/script/src/formats/apng.d.ts +50 -0
  29. package/script/src/formats/apng.js +368 -0
  30. package/script/src/formats/bmp.d.ts +0 -6
  31. package/script/src/formats/bmp.js +24 -47
  32. package/script/src/formats/dng.js +4 -4
  33. package/script/src/formats/gif.d.ts +0 -2
  34. package/script/src/formats/gif.js +10 -16
  35. package/script/src/formats/ico.d.ts +41 -0
  36. package/script/src/formats/ico.js +218 -0
  37. package/script/src/formats/pcx.js +1 -1
  38. package/script/src/formats/png.d.ts +2 -21
  39. package/script/src/formats/png.js +5 -429
  40. package/script/src/formats/png_base.d.ts +108 -0
  41. package/script/src/formats/png_base.js +491 -0
  42. package/script/src/formats/webp.d.ts +0 -1
  43. package/script/src/formats/webp.js +4 -7
  44. package/script/src/image.js +4 -0
  45. package/script/src/utils/byte_utils.d.ts +30 -0
  46. package/script/src/utils/byte_utils.js +58 -0
  47. package/script/src/utils/gif_encoder.d.ts +3 -2
  48. package/script/src/utils/gif_encoder.js +115 -48
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ICOFormat = void 0;
4
+ const security_js_1 = require("../utils/security.js");
5
+ const png_js_1 = require("./png.js");
6
+ const byte_utils_js_1 = require("../utils/byte_utils.js");
7
+ /**
8
+ * ICO format handler
9
+ * Implements a pure JavaScript ICO (Windows Icon) decoder and encoder
10
+ *
11
+ * ICO files can contain multiple images at different sizes.
12
+ * This implementation decodes the largest image and encodes as a single-image ICO.
13
+ */
14
+ class ICOFormat {
15
+ constructor() {
16
+ /** Format name identifier */
17
+ Object.defineProperty(this, "name", {
18
+ enumerable: true,
19
+ configurable: true,
20
+ writable: true,
21
+ value: "ico"
22
+ });
23
+ /** MIME type for ICO images */
24
+ Object.defineProperty(this, "mimeType", {
25
+ enumerable: true,
26
+ configurable: true,
27
+ writable: true,
28
+ value: "image/x-icon"
29
+ });
30
+ Object.defineProperty(this, "pngFormat", {
31
+ enumerable: true,
32
+ configurable: true,
33
+ writable: true,
34
+ value: new png_js_1.PNGFormat()
35
+ });
36
+ }
37
+ /**
38
+ * Check if the given data is an ICO image
39
+ * @param data Raw image data to check
40
+ * @returns true if data has ICO/CUR signature
41
+ */
42
+ canDecode(data) {
43
+ // ICO signature: reserved=0, type=1 (icon) or 2 (cursor)
44
+ return data.length >= 6 &&
45
+ data[0] === 0 && data[1] === 0 && // Reserved
46
+ (data[2] === 1 || data[2] === 2) && data[3] === 0 && // Type = 1 (icon) or 2 (cursor)
47
+ data[4] !== 0; // Count > 0
48
+ }
49
+ /**
50
+ * Decode ICO image data to RGBA
51
+ * Selects and decodes the largest image in the ICO file
52
+ * @param data Raw ICO image data
53
+ * @returns Decoded image data with RGBA pixels
54
+ */
55
+ async decode(data) {
56
+ if (!this.canDecode(data)) {
57
+ throw new Error("Invalid ICO signature");
58
+ }
59
+ // Read ICONDIR header
60
+ const count = (0, byte_utils_js_1.readUint16LE)(data, 4);
61
+ if (count === 0) {
62
+ throw new Error("ICO file contains no images");
63
+ }
64
+ // Read all ICONDIRENTRY structures (16 bytes each, starting at offset 6)
65
+ const entries = [];
66
+ for (let i = 0; i < count; i++) {
67
+ const entryOffset = 6 + i * 16;
68
+ if (entryOffset + 16 > data.length) {
69
+ throw new Error("Invalid ICO file: entry data out of bounds");
70
+ }
71
+ let width = data[entryOffset]; // 0 means 256
72
+ let height = data[entryOffset + 1]; // 0 means 256
73
+ // Width/height of 0 means 256 pixels
74
+ if (width === 0)
75
+ width = 256;
76
+ if (height === 0)
77
+ height = 256;
78
+ const size = (0, byte_utils_js_1.readUint32LE)(data, entryOffset + 8);
79
+ const offset = (0, byte_utils_js_1.readUint32LE)(data, entryOffset + 12);
80
+ entries.push({ width, height, size, offset });
81
+ }
82
+ // Find the largest image (by area)
83
+ let largestEntry = entries[0];
84
+ let largestArea = largestEntry.width * largestEntry.height;
85
+ for (const entry of entries) {
86
+ const area = entry.width * entry.height;
87
+ if (area > largestArea) {
88
+ largestEntry = entry;
89
+ largestArea = area;
90
+ }
91
+ }
92
+ // Extract image data
93
+ const imageStart = largestEntry.offset;
94
+ const imageEnd = imageStart + largestEntry.size;
95
+ if (imageEnd > data.length) {
96
+ throw new Error("Invalid ICO file: image data out of bounds");
97
+ }
98
+ const imageData = data.slice(imageStart, imageEnd);
99
+ // Check if it's a PNG (starts with PNG signature)
100
+ if (imageData.length >= 8 &&
101
+ imageData[0] === 0x89 &&
102
+ imageData[1] === 0x50 &&
103
+ imageData[2] === 0x4e &&
104
+ imageData[3] === 0x47) {
105
+ // It's a PNG, decode it
106
+ return await this.pngFormat.decode(imageData);
107
+ }
108
+ // Otherwise, it's a BMP without the file header (DIB format)
109
+ return this.decodeDIB(imageData);
110
+ }
111
+ /**
112
+ * Decode a DIB (Device Independent Bitmap) format
113
+ * This is a BMP without the 14-byte file header
114
+ */
115
+ decodeDIB(data) {
116
+ // Read DIB header
117
+ const dibHeaderSize = (0, byte_utils_js_1.readUint32LE)(data, 0);
118
+ if (dibHeaderSize < 40) {
119
+ throw new Error("Unsupported DIB header size");
120
+ }
121
+ const width = (0, byte_utils_js_1.readInt32LE)(data, 4);
122
+ const height = (0, byte_utils_js_1.readInt32LE)(data, 8);
123
+ const bitDepth = (0, byte_utils_js_1.readUint16LE)(data, 14);
124
+ const compression = (0, byte_utils_js_1.readUint32LE)(data, 16);
125
+ // Validate dimensions
126
+ (0, security_js_1.validateImageDimensions)(width, Math.abs(height) / 2); // DIB height includes both XOR and AND mask data
127
+ // ICO files store height as 2x actual height (for AND mask)
128
+ const actualHeight = Math.abs(height) / 2;
129
+ // Only support uncompressed DIBs
130
+ if (compression !== 0) {
131
+ throw new Error(`Compressed DIB not supported (compression: ${compression})`);
132
+ }
133
+ // Support common bit depths
134
+ if (bitDepth !== 24 && bitDepth !== 32) {
135
+ throw new Error(`Unsupported bit depth: ${bitDepth}`);
136
+ }
137
+ // Calculate data offset (after header and optional color table)
138
+ const dataOffset = dibHeaderSize;
139
+ // Calculate row size (must be multiple of 4 bytes)
140
+ const bytesPerPixel = bitDepth / 8;
141
+ const rowSize = Math.floor((bitDepth * width + 31) / 32) * 4;
142
+ // Read XOR mask (color data)
143
+ const rgba = new Uint8Array(width * actualHeight * 4);
144
+ for (let y = 0; y < actualHeight; y++) {
145
+ // DIB data is stored bottom-to-top
146
+ const rowIndex = actualHeight - 1 - y;
147
+ const rowOffset = dataOffset + y * rowSize;
148
+ for (let x = 0; x < width; x++) {
149
+ const pixelOffset = rowOffset + x * bytesPerPixel;
150
+ const outIndex = (rowIndex * width + x) * 4;
151
+ // DIB stores pixels as BGR(A)
152
+ rgba[outIndex] = data[pixelOffset + 2]; // R
153
+ rgba[outIndex + 1] = data[pixelOffset + 1]; // G
154
+ rgba[outIndex + 2] = data[pixelOffset]; // B
155
+ rgba[outIndex + 3] = bitDepth === 32 ? data[pixelOffset + 3] : 255; // A
156
+ }
157
+ }
158
+ // Read AND mask if present (for transparency in 24-bit images)
159
+ if (bitDepth === 24) {
160
+ const andMaskOffset = dataOffset + rowSize * actualHeight;
161
+ const andMaskRowSize = Math.floor((width + 31) / 32) * 4;
162
+ for (let y = 0; y < actualHeight; y++) {
163
+ const rowIndex = actualHeight - 1 - y;
164
+ const rowOffset = andMaskOffset + y * andMaskRowSize;
165
+ for (let x = 0; x < width; x++) {
166
+ const byteOffset = rowOffset + Math.floor(x / 8);
167
+ const bitOffset = 7 - (x % 8);
168
+ const isTransparent = (data[byteOffset] & (1 << bitOffset)) !== 0;
169
+ if (isTransparent) {
170
+ const outIndex = (rowIndex * width + x) * 4 + 3;
171
+ rgba[outIndex] = 0; // Set alpha to 0 for transparent pixels
172
+ }
173
+ }
174
+ }
175
+ }
176
+ return Promise.resolve({
177
+ width,
178
+ height: actualHeight,
179
+ data: rgba,
180
+ });
181
+ }
182
+ /**
183
+ * Encode RGBA image data to ICO format
184
+ * Creates an ICO file with a single PNG-encoded image
185
+ * @param imageData Image data to encode
186
+ * @returns Encoded ICO image bytes
187
+ */
188
+ async encode(imageData) {
189
+ const { width, height } = imageData;
190
+ // Encode the image as PNG
191
+ const pngData = await this.pngFormat.encode(imageData);
192
+ // Create ICO file structure
193
+ // ICONDIR (6 bytes) + ICONDIRENTRY (16 bytes) + PNG data
194
+ const icoSize = 6 + 16 + pngData.length;
195
+ const result = new Uint8Array(icoSize);
196
+ // Write ICONDIR header
197
+ result[0] = 0; // Reserved
198
+ result[1] = 0; // Reserved
199
+ result[2] = 1; // Type = 1 (icon)
200
+ result[3] = 0; // Type high byte
201
+ result[4] = 1; // Count = 1
202
+ result[5] = 0; // Count high byte
203
+ // Write ICONDIRENTRY
204
+ const entryOffset = 6;
205
+ result[entryOffset] = width >= 256 ? 0 : width; // Width (0 = 256)
206
+ result[entryOffset + 1] = height >= 256 ? 0 : height; // Height (0 = 256)
207
+ result[entryOffset + 2] = 0; // Color count (0 = no palette)
208
+ result[entryOffset + 3] = 0; // Reserved
209
+ (0, byte_utils_js_1.writeUint16LE)(result, entryOffset + 4, 1); // Color planes
210
+ (0, byte_utils_js_1.writeUint16LE)(result, entryOffset + 6, 32); // Bits per pixel
211
+ (0, byte_utils_js_1.writeUint32LE)(result, entryOffset + 8, pngData.length); // Image size
212
+ (0, byte_utils_js_1.writeUint32LE)(result, entryOffset + 12, 22); // Image offset (6 + 16)
213
+ // Write PNG data
214
+ result.set(pngData, 22);
215
+ return result;
216
+ }
217
+ }
218
+ exports.ICOFormat = ICOFormat;
@@ -158,7 +158,7 @@ class PCXFormat {
158
158
  view.setUint16(66, width + (width % 2), true); // BytesPerLine (must be even)
159
159
  view.setUint16(68, 1, true); // PaletteInfo (Color/BW)
160
160
  const bytesPerLine = width + (width % 2);
161
- const scanlineLength = bytesPerLine * 3;
161
+ const _scanlineLength = bytesPerLine * 3;
162
162
  const rleData = [];
163
163
  // Helper to write RLE
164
164
  const writeRLE = (byte, count) => {
@@ -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