cross-image 0.2.0 → 0.2.2

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 (62) hide show
  1. package/README.md +41 -28
  2. package/esm/mod.d.ts +4 -1
  3. package/esm/mod.js +4 -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/ppm.d.ts +50 -0
  19. package/esm/src/formats/ppm.js +242 -0
  20. package/esm/src/formats/tiff.d.ts +4 -0
  21. package/esm/src/formats/tiff.js +163 -44
  22. package/esm/src/formats/webp.d.ts +0 -1
  23. package/esm/src/formats/webp.js +4 -7
  24. package/esm/src/image.d.ts +30 -0
  25. package/esm/src/image.js +62 -1
  26. package/esm/src/utils/byte_utils.d.ts +30 -0
  27. package/esm/src/utils/byte_utils.js +50 -0
  28. package/esm/src/utils/gif_encoder.d.ts +3 -2
  29. package/esm/src/utils/gif_encoder.js +115 -48
  30. package/esm/src/utils/image_processing.d.ts +43 -0
  31. package/esm/src/utils/image_processing.js +230 -0
  32. package/package.json +1 -1
  33. package/script/mod.d.ts +4 -1
  34. package/script/mod.js +8 -2
  35. package/script/src/formats/apng.d.ts +50 -0
  36. package/script/src/formats/apng.js +368 -0
  37. package/script/src/formats/bmp.d.ts +0 -6
  38. package/script/src/formats/bmp.js +24 -47
  39. package/script/src/formats/dng.js +4 -4
  40. package/script/src/formats/gif.d.ts +0 -2
  41. package/script/src/formats/gif.js +10 -16
  42. package/script/src/formats/ico.d.ts +41 -0
  43. package/script/src/formats/ico.js +218 -0
  44. package/script/src/formats/pcx.js +1 -1
  45. package/script/src/formats/png.d.ts +2 -21
  46. package/script/src/formats/png.js +5 -429
  47. package/script/src/formats/png_base.d.ts +108 -0
  48. package/script/src/formats/png_base.js +491 -0
  49. package/script/src/formats/ppm.d.ts +50 -0
  50. package/script/src/formats/ppm.js +246 -0
  51. package/script/src/formats/tiff.d.ts +4 -0
  52. package/script/src/formats/tiff.js +163 -44
  53. package/script/src/formats/webp.d.ts +0 -1
  54. package/script/src/formats/webp.js +4 -7
  55. package/script/src/image.d.ts +30 -0
  56. package/script/src/image.js +61 -0
  57. package/script/src/utils/byte_utils.d.ts +30 -0
  58. package/script/src/utils/byte_utils.js +58 -0
  59. package/script/src/utils/gif_encoder.d.ts +3 -2
  60. package/script/src/utils/gif_encoder.js +115 -48
  61. package/script/src/utils/image_processing.d.ts +43 -0
  62. package/script/src/utils/image_processing.js +235 -0
@@ -1,4 +1,5 @@
1
1
  import { validateImageDimensions } from "../utils/security.js";
2
+ import { readInt32LE, readUint16LE, readUint32LE, writeInt32LE, writeUint16LE, writeUint32LE, } from "../utils/byte_utils.js";
2
3
  // Constants for unit conversions
3
4
  const INCHES_PER_METER = 39.3701;
4
5
  /**
@@ -42,10 +43,10 @@ export class BMPFormat {
42
43
  throw new Error("Invalid BMP signature");
43
44
  }
44
45
  // Read BMP file header (14 bytes)
45
- const _fileSize = this.readUint32LE(data, 2);
46
- const dataOffset = this.readUint32LE(data, 10);
46
+ const _fileSize = readUint32LE(data, 2);
47
+ const dataOffset = readUint32LE(data, 10);
47
48
  // Read DIB header (at least 40 bytes for BITMAPINFOHEADER)
48
- const dibHeaderSize = this.readUint32LE(data, 14);
49
+ const dibHeaderSize = readUint32LE(data, 14);
49
50
  let width;
50
51
  let height;
51
52
  let bitDepth;
@@ -53,13 +54,13 @@ export class BMPFormat {
53
54
  const metadata = {};
54
55
  if (dibHeaderSize >= 40) {
55
56
  // BITMAPINFOHEADER or later
56
- width = this.readInt32LE(data, 18);
57
- height = this.readInt32LE(data, 22);
58
- bitDepth = this.readUint16LE(data, 28);
59
- compression = this.readUint32LE(data, 30);
57
+ width = readInt32LE(data, 18);
58
+ height = readInt32LE(data, 22);
59
+ bitDepth = readUint16LE(data, 28);
60
+ compression = readUint32LE(data, 30);
60
61
  // Read DPI information (pixels per meter)
61
- const xPixelsPerMeter = this.readInt32LE(data, 38);
62
- const yPixelsPerMeter = this.readInt32LE(data, 42);
62
+ const xPixelsPerMeter = readInt32LE(data, 38);
63
+ const yPixelsPerMeter = readInt32LE(data, 42);
63
64
  if (xPixelsPerMeter > 0 && yPixelsPerMeter > 0) {
64
65
  // Convert pixels per meter to DPI
65
66
  metadata.dpiX = Math.round(xPixelsPerMeter / INCHES_PER_METER);
@@ -134,21 +135,21 @@ export class BMPFormat {
134
135
  // BMP File Header (14 bytes)
135
136
  result[0] = 0x42; // 'B'
136
137
  result[1] = 0x4d; // 'M'
137
- this.writeUint32LE(result, 2, fileSize); // File size
138
- this.writeUint32LE(result, 6, 0); // Reserved
139
- this.writeUint32LE(result, 10, 54); // Offset to pixel data (14 + 40)
138
+ writeUint32LE(result, 2, fileSize); // File size
139
+ writeUint32LE(result, 6, 0); // Reserved
140
+ writeUint32LE(result, 10, 54); // Offset to pixel data (14 + 40)
140
141
  // DIB Header (BITMAPINFOHEADER - 40 bytes)
141
- this.writeUint32LE(result, 14, 40); // DIB header size
142
- this.writeInt32LE(result, 18, width); // Width
143
- this.writeInt32LE(result, 22, height); // Height (positive = bottom-up)
144
- this.writeUint16LE(result, 26, 1); // Planes
145
- this.writeUint16LE(result, 28, 32); // Bits per pixel
146
- this.writeUint32LE(result, 30, 0); // Compression (0 = uncompressed)
147
- this.writeUint32LE(result, 34, pixelDataSize); // Image size
148
- this.writeInt32LE(result, 38, xPixelsPerMeter); // X pixels per meter
149
- this.writeInt32LE(result, 42, yPixelsPerMeter); // Y pixels per meter
150
- this.writeUint32LE(result, 46, 0); // Colors in palette
151
- this.writeUint32LE(result, 50, 0); // Important colors
142
+ writeUint32LE(result, 14, 40); // DIB header size
143
+ writeInt32LE(result, 18, width); // Width
144
+ writeInt32LE(result, 22, height); // Height (positive = bottom-up)
145
+ writeUint16LE(result, 26, 1); // Planes
146
+ writeUint16LE(result, 28, 32); // Bits per pixel
147
+ writeUint32LE(result, 30, 0); // Compression (0 = uncompressed)
148
+ writeUint32LE(result, 34, pixelDataSize); // Image size
149
+ writeInt32LE(result, 38, xPixelsPerMeter); // X pixels per meter
150
+ writeInt32LE(result, 42, yPixelsPerMeter); // Y pixels per meter
151
+ writeUint32LE(result, 46, 0); // Colors in palette
152
+ writeUint32LE(result, 50, 0); // Important colors
152
153
  // Write pixel data (bottom-to-top, BGR(A) format)
153
154
  let offset = 54;
154
155
  for (let y = height - 1; y >= 0; y--) {
@@ -167,28 +168,4 @@ export class BMPFormat {
167
168
  }
168
169
  return Promise.resolve(result);
169
170
  }
170
- readUint16LE(data, offset) {
171
- return data[offset] | (data[offset + 1] << 8);
172
- }
173
- readUint32LE(data, offset) {
174
- return data[offset] | (data[offset + 1] << 8) |
175
- (data[offset + 2] << 16) | (data[offset + 3] << 24);
176
- }
177
- readInt32LE(data, offset) {
178
- const value = this.readUint32LE(data, offset);
179
- return value > 0x7fffffff ? value - 0x100000000 : value;
180
- }
181
- writeUint16LE(data, offset, value) {
182
- data[offset] = value & 0xff;
183
- data[offset + 1] = (value >>> 8) & 0xff;
184
- }
185
- writeUint32LE(data, offset, value) {
186
- data[offset] = value & 0xff;
187
- data[offset + 1] = (value >>> 8) & 0xff;
188
- data[offset + 2] = (value >>> 16) & 0xff;
189
- data[offset + 3] = (value >>> 24) & 0xff;
190
- }
191
- writeInt32LE(data, offset, value) {
192
- this.writeUint32LE(data, offset, value < 0 ? value + 0x100000000 : value);
193
- }
194
171
  }
@@ -78,7 +78,7 @@ export class DNGFormat extends TIFFFormat {
78
78
  * @param imageData Image data to encode
79
79
  * @returns Encoded DNG image bytes
80
80
  */
81
- async encode(imageData) {
81
+ encode(imageData) {
82
82
  const { width, height, data } = imageData;
83
83
  // We'll create a Linear DNG (demosaiced RGB)
84
84
  // This is very similar to a standard TIFF but with specific tags.
@@ -124,7 +124,7 @@ export class DNGFormat extends TIFFFormat {
124
124
  // 4. BitsPerSample (0x0102) - 8, 8, 8, 8
125
125
  this.writeIFDEntry(result, 0x0102, 3, 4, dataOffset);
126
126
  // Write the actual values later
127
- const bitsPerSampleOffset = dataOffset;
127
+ const _bitsPerSampleOffset = dataOffset;
128
128
  dataOffset += 8; // 4 * 2 bytes
129
129
  // 5. Compression (0x0103) - 1 = Uncompressed
130
130
  this.writeIFDEntry(result, 0x0103, 3, 1, 1);
@@ -161,7 +161,7 @@ export class DNGFormat extends TIFFFormat {
161
161
  const modelName = "Cross Image DNG\0";
162
162
  const modelNameBytes = new TextEncoder().encode(modelName);
163
163
  this.writeIFDEntry(result, 50708, 2, modelNameBytes.length, dataOffset);
164
- const modelNameOffset = dataOffset;
164
+ const _modelNameOffset = dataOffset;
165
165
  dataOffset += modelNameBytes.length;
166
166
  // Next IFD offset (0)
167
167
  this.writeUint32LE(result, 0);
@@ -186,6 +186,6 @@ export class DNGFormat extends TIFFFormat {
186
186
  for (let i = 0; i < modelNameBytes.length; i++) {
187
187
  result.push(modelNameBytes[i]);
188
188
  }
189
- return new Uint8Array(result);
189
+ return Promise.resolve(new Uint8Array(result));
190
190
  }
191
191
  }
@@ -46,11 +46,9 @@ export declare class GIFFormat implements ImageFormat {
46
46
  decodeFrames(data: Uint8Array): Promise<MultiFrameImageData>;
47
47
  /**
48
48
  * Encode multi-frame image data to animated GIF
49
- * Note: Currently not implemented, will encode only first frame
50
49
  */
51
50
  encodeFrames(imageData: MultiFrameImageData, _options?: unknown): Promise<Uint8Array>;
52
51
  private mapDisposalMethod;
53
- private readUint16LE;
54
52
  private decodeUsingRuntime;
55
53
  private readDataSubBlocks;
56
54
  private parseComment;
@@ -1,6 +1,7 @@
1
1
  import { GIFDecoder } from "../utils/gif_decoder.js";
2
2
  import { GIFEncoder } from "../utils/gif_encoder.js";
3
3
  import { validateImageDimensions } from "../utils/security.js";
4
+ import { readUint16LE } from "../utils/byte_utils.js";
4
5
  /**
5
6
  * GIF format handler
6
7
  * Now includes pure-JS implementation with custom LZW compression/decompression
@@ -78,9 +79,9 @@ export class GIFFormat {
78
79
  // Fall back to runtime decoder if pure-JS fails
79
80
  console.warn("Pure-JS GIF decoder failed, falling back to runtime:", error);
80
81
  let pos = 6; // Skip "GIF89a" or "GIF87a"
81
- const width = this.readUint16LE(data, pos);
82
+ const width = readUint16LE(data, pos);
82
83
  pos += 2;
83
- const height = this.readUint16LE(data, pos);
84
+ const height = readUint16LE(data, pos);
84
85
  // Validate dimensions for security (prevent integer overflow and heap exhaustion)
85
86
  validateImageDimensions(width, height);
86
87
  const rgba = await this.decodeUsingRuntime(data, width, height);
@@ -231,22 +232,18 @@ export class GIFFormat {
231
232
  }
232
233
  /**
233
234
  * Encode multi-frame image data to animated GIF
234
- * Note: Currently not implemented, will encode only first frame
235
235
  */
236
236
  encodeFrames(imageData, _options) {
237
- // For now, just encode the first frame using the existing encoder
238
- // Full multi-frame encoding would require a more complex GIFEncoder
239
237
  if (imageData.frames.length === 0) {
240
238
  throw new Error("No frames to encode");
241
239
  }
242
- const firstFrame = imageData.frames[0];
243
- const singleFrameData = {
244
- width: firstFrame.width,
245
- height: firstFrame.height,
246
- data: firstFrame.data,
247
- metadata: imageData.metadata,
248
- };
249
- return this.encode(singleFrameData);
240
+ const encoder = new GIFEncoder(imageData.width, imageData.height);
241
+ for (const frame of imageData.frames) {
242
+ // Get delay from metadata (default to 100ms if not set)
243
+ const delay = frame.frameMetadata?.delay ?? 100;
244
+ encoder.addFrame(frame.data, delay);
245
+ }
246
+ return Promise.resolve(encoder.encode());
250
247
  }
251
248
  mapDisposalMethod(disposal) {
252
249
  switch (disposal) {
@@ -261,9 +258,6 @@ export class GIFFormat {
261
258
  return "none";
262
259
  }
263
260
  }
264
- readUint16LE(data, offset) {
265
- return data[offset] | (data[offset + 1] << 8);
266
- }
267
261
  async decodeUsingRuntime(data, _width, _height) {
268
262
  // Try to use ImageDecoder API if available (Deno, modern browsers)
269
263
  if (typeof ImageDecoder !== "undefined") {
@@ -0,0 +1,41 @@
1
+ import type { ImageData, ImageFormat } from "../types.js";
2
+ /**
3
+ * ICO format handler
4
+ * Implements a pure JavaScript ICO (Windows Icon) decoder and encoder
5
+ *
6
+ * ICO files can contain multiple images at different sizes.
7
+ * This implementation decodes the largest image and encodes as a single-image ICO.
8
+ */
9
+ export declare class ICOFormat implements ImageFormat {
10
+ /** Format name identifier */
11
+ readonly name = "ico";
12
+ /** MIME type for ICO images */
13
+ readonly mimeType = "image/x-icon";
14
+ private pngFormat;
15
+ /**
16
+ * Check if the given data is an ICO image
17
+ * @param data Raw image data to check
18
+ * @returns true if data has ICO/CUR signature
19
+ */
20
+ canDecode(data: Uint8Array): boolean;
21
+ /**
22
+ * Decode ICO image data to RGBA
23
+ * Selects and decodes the largest image in the ICO file
24
+ * @param data Raw ICO image data
25
+ * @returns Decoded image data with RGBA pixels
26
+ */
27
+ decode(data: Uint8Array): Promise<ImageData>;
28
+ /**
29
+ * Decode a DIB (Device Independent Bitmap) format
30
+ * This is a BMP without the 14-byte file header
31
+ */
32
+ private decodeDIB;
33
+ /**
34
+ * Encode RGBA image data to ICO format
35
+ * Creates an ICO file with a single PNG-encoded image
36
+ * @param imageData Image data to encode
37
+ * @returns Encoded ICO image bytes
38
+ */
39
+ encode(imageData: ImageData): Promise<Uint8Array>;
40
+ }
41
+ //# sourceMappingURL=ico.d.ts.map
@@ -0,0 +1,214 @@
1
+ import { validateImageDimensions } from "../utils/security.js";
2
+ import { PNGFormat } from "./png.js";
3
+ import { readInt32LE, readUint16LE, readUint32LE, writeUint16LE, writeUint32LE, } from "../utils/byte_utils.js";
4
+ /**
5
+ * ICO format handler
6
+ * Implements a pure JavaScript ICO (Windows Icon) decoder and encoder
7
+ *
8
+ * ICO files can contain multiple images at different sizes.
9
+ * This implementation decodes the largest image and encodes as a single-image ICO.
10
+ */
11
+ export class ICOFormat {
12
+ constructor() {
13
+ /** Format name identifier */
14
+ Object.defineProperty(this, "name", {
15
+ enumerable: true,
16
+ configurable: true,
17
+ writable: true,
18
+ value: "ico"
19
+ });
20
+ /** MIME type for ICO images */
21
+ Object.defineProperty(this, "mimeType", {
22
+ enumerable: true,
23
+ configurable: true,
24
+ writable: true,
25
+ value: "image/x-icon"
26
+ });
27
+ Object.defineProperty(this, "pngFormat", {
28
+ enumerable: true,
29
+ configurable: true,
30
+ writable: true,
31
+ value: new PNGFormat()
32
+ });
33
+ }
34
+ /**
35
+ * Check if the given data is an ICO image
36
+ * @param data Raw image data to check
37
+ * @returns true if data has ICO/CUR signature
38
+ */
39
+ canDecode(data) {
40
+ // ICO signature: reserved=0, type=1 (icon) or 2 (cursor)
41
+ return data.length >= 6 &&
42
+ data[0] === 0 && data[1] === 0 && // Reserved
43
+ (data[2] === 1 || data[2] === 2) && data[3] === 0 && // Type = 1 (icon) or 2 (cursor)
44
+ data[4] !== 0; // Count > 0
45
+ }
46
+ /**
47
+ * Decode ICO image data to RGBA
48
+ * Selects and decodes the largest image in the ICO file
49
+ * @param data Raw ICO image data
50
+ * @returns Decoded image data with RGBA pixels
51
+ */
52
+ async decode(data) {
53
+ if (!this.canDecode(data)) {
54
+ throw new Error("Invalid ICO signature");
55
+ }
56
+ // Read ICONDIR header
57
+ const count = readUint16LE(data, 4);
58
+ if (count === 0) {
59
+ throw new Error("ICO file contains no images");
60
+ }
61
+ // Read all ICONDIRENTRY structures (16 bytes each, starting at offset 6)
62
+ const entries = [];
63
+ for (let i = 0; i < count; i++) {
64
+ const entryOffset = 6 + i * 16;
65
+ if (entryOffset + 16 > data.length) {
66
+ throw new Error("Invalid ICO file: entry data out of bounds");
67
+ }
68
+ let width = data[entryOffset]; // 0 means 256
69
+ let height = data[entryOffset + 1]; // 0 means 256
70
+ // Width/height of 0 means 256 pixels
71
+ if (width === 0)
72
+ width = 256;
73
+ if (height === 0)
74
+ height = 256;
75
+ const size = readUint32LE(data, entryOffset + 8);
76
+ const offset = readUint32LE(data, entryOffset + 12);
77
+ entries.push({ width, height, size, offset });
78
+ }
79
+ // Find the largest image (by area)
80
+ let largestEntry = entries[0];
81
+ let largestArea = largestEntry.width * largestEntry.height;
82
+ for (const entry of entries) {
83
+ const area = entry.width * entry.height;
84
+ if (area > largestArea) {
85
+ largestEntry = entry;
86
+ largestArea = area;
87
+ }
88
+ }
89
+ // Extract image data
90
+ const imageStart = largestEntry.offset;
91
+ const imageEnd = imageStart + largestEntry.size;
92
+ if (imageEnd > data.length) {
93
+ throw new Error("Invalid ICO file: image data out of bounds");
94
+ }
95
+ const imageData = data.slice(imageStart, imageEnd);
96
+ // Check if it's a PNG (starts with PNG signature)
97
+ if (imageData.length >= 8 &&
98
+ imageData[0] === 0x89 &&
99
+ imageData[1] === 0x50 &&
100
+ imageData[2] === 0x4e &&
101
+ imageData[3] === 0x47) {
102
+ // It's a PNG, decode it
103
+ return await this.pngFormat.decode(imageData);
104
+ }
105
+ // Otherwise, it's a BMP without the file header (DIB format)
106
+ return this.decodeDIB(imageData);
107
+ }
108
+ /**
109
+ * Decode a DIB (Device Independent Bitmap) format
110
+ * This is a BMP without the 14-byte file header
111
+ */
112
+ decodeDIB(data) {
113
+ // Read DIB header
114
+ const dibHeaderSize = readUint32LE(data, 0);
115
+ if (dibHeaderSize < 40) {
116
+ throw new Error("Unsupported DIB header size");
117
+ }
118
+ const width = readInt32LE(data, 4);
119
+ const height = readInt32LE(data, 8);
120
+ const bitDepth = readUint16LE(data, 14);
121
+ const compression = readUint32LE(data, 16);
122
+ // Validate dimensions
123
+ validateImageDimensions(width, Math.abs(height) / 2); // DIB height includes both XOR and AND mask data
124
+ // ICO files store height as 2x actual height (for AND mask)
125
+ const actualHeight = Math.abs(height) / 2;
126
+ // Only support uncompressed DIBs
127
+ if (compression !== 0) {
128
+ throw new Error(`Compressed DIB not supported (compression: ${compression})`);
129
+ }
130
+ // Support common bit depths
131
+ if (bitDepth !== 24 && bitDepth !== 32) {
132
+ throw new Error(`Unsupported bit depth: ${bitDepth}`);
133
+ }
134
+ // Calculate data offset (after header and optional color table)
135
+ const dataOffset = dibHeaderSize;
136
+ // Calculate row size (must be multiple of 4 bytes)
137
+ const bytesPerPixel = bitDepth / 8;
138
+ const rowSize = Math.floor((bitDepth * width + 31) / 32) * 4;
139
+ // Read XOR mask (color data)
140
+ const rgba = new Uint8Array(width * actualHeight * 4);
141
+ for (let y = 0; y < actualHeight; y++) {
142
+ // DIB data is stored bottom-to-top
143
+ const rowIndex = actualHeight - 1 - y;
144
+ const rowOffset = dataOffset + y * rowSize;
145
+ for (let x = 0; x < width; x++) {
146
+ const pixelOffset = rowOffset + x * bytesPerPixel;
147
+ const outIndex = (rowIndex * width + x) * 4;
148
+ // DIB stores pixels as BGR(A)
149
+ rgba[outIndex] = data[pixelOffset + 2]; // R
150
+ rgba[outIndex + 1] = data[pixelOffset + 1]; // G
151
+ rgba[outIndex + 2] = data[pixelOffset]; // B
152
+ rgba[outIndex + 3] = bitDepth === 32 ? data[pixelOffset + 3] : 255; // A
153
+ }
154
+ }
155
+ // Read AND mask if present (for transparency in 24-bit images)
156
+ if (bitDepth === 24) {
157
+ const andMaskOffset = dataOffset + rowSize * actualHeight;
158
+ const andMaskRowSize = Math.floor((width + 31) / 32) * 4;
159
+ for (let y = 0; y < actualHeight; y++) {
160
+ const rowIndex = actualHeight - 1 - y;
161
+ const rowOffset = andMaskOffset + y * andMaskRowSize;
162
+ for (let x = 0; x < width; x++) {
163
+ const byteOffset = rowOffset + Math.floor(x / 8);
164
+ const bitOffset = 7 - (x % 8);
165
+ const isTransparent = (data[byteOffset] & (1 << bitOffset)) !== 0;
166
+ if (isTransparent) {
167
+ const outIndex = (rowIndex * width + x) * 4 + 3;
168
+ rgba[outIndex] = 0; // Set alpha to 0 for transparent pixels
169
+ }
170
+ }
171
+ }
172
+ }
173
+ return Promise.resolve({
174
+ width,
175
+ height: actualHeight,
176
+ data: rgba,
177
+ });
178
+ }
179
+ /**
180
+ * Encode RGBA image data to ICO format
181
+ * Creates an ICO file with a single PNG-encoded image
182
+ * @param imageData Image data to encode
183
+ * @returns Encoded ICO image bytes
184
+ */
185
+ async encode(imageData) {
186
+ const { width, height } = imageData;
187
+ // Encode the image as PNG
188
+ const pngData = await this.pngFormat.encode(imageData);
189
+ // Create ICO file structure
190
+ // ICONDIR (6 bytes) + ICONDIRENTRY (16 bytes) + PNG data
191
+ const icoSize = 6 + 16 + pngData.length;
192
+ const result = new Uint8Array(icoSize);
193
+ // Write ICONDIR header
194
+ result[0] = 0; // Reserved
195
+ result[1] = 0; // Reserved
196
+ result[2] = 1; // Type = 1 (icon)
197
+ result[3] = 0; // Type high byte
198
+ result[4] = 1; // Count = 1
199
+ result[5] = 0; // Count high byte
200
+ // Write ICONDIRENTRY
201
+ const entryOffset = 6;
202
+ result[entryOffset] = width >= 256 ? 0 : width; // Width (0 = 256)
203
+ result[entryOffset + 1] = height >= 256 ? 0 : height; // Height (0 = 256)
204
+ result[entryOffset + 2] = 0; // Color count (0 = no palette)
205
+ result[entryOffset + 3] = 0; // Reserved
206
+ writeUint16LE(result, entryOffset + 4, 1); // Color planes
207
+ writeUint16LE(result, entryOffset + 6, 32); // Bits per pixel
208
+ writeUint32LE(result, entryOffset + 8, pngData.length); // Image size
209
+ writeUint32LE(result, entryOffset + 12, 22); // Image offset (6 + 16)
210
+ // Write PNG data
211
+ result.set(pngData, 22);
212
+ return result;
213
+ }
214
+ }
@@ -155,7 +155,7 @@ export class PCXFormat {
155
155
  view.setUint16(66, width + (width % 2), true); // BytesPerLine (must be even)
156
156
  view.setUint16(68, 1, true); // PaletteInfo (Color/BW)
157
157
  const bytesPerLine = width + (width % 2);
158
- const scanlineLength = bytesPerLine * 3;
158
+ const _scanlineLength = bytesPerLine * 3;
159
159
  const rleData = [];
160
160
  // Helper to write RLE
161
161
  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