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
package/script/mod.js CHANGED
@@ -3,7 +3,8 @@
3
3
  * @module @cross/image
4
4
  *
5
5
  * A pure JavaScript, dependency-free, cross-runtime image processing library.
6
- * Supports decoding, resizing, and encoding common image formats (PNG, JPEG, WebP, GIF, TIFF, BMP, RAW).
6
+ * Supports decoding, resizing, and encoding common image formats (PNG, APNG, JPEG, WebP, GIF, TIFF, BMP, ICO, DNG, PAM, PCX).
7
+ * Includes image processing capabilities like compositing, level adjustments, and pixel manipulation.
7
8
  *
8
9
  * @example
9
10
  * ```ts
@@ -13,20 +14,43 @@
13
14
  * const data = await Deno.readFile("input.png");
14
15
  * const image = await Image.decode(data);
15
16
  *
16
- * // Resize it
17
- * image.resize({ width: 200, height: 200 });
17
+ * // Apply image processing
18
+ * image
19
+ * .resize({ width: 200, height: 200 })
20
+ * .brightness(0.1)
21
+ * .contrast(0.2);
18
22
  *
19
23
  * // Encode as different format
20
24
  * const output = await image.encode("jpeg");
21
25
  * await Deno.writeFile("output.jpg", output);
22
26
  * ```
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * import { Image } from "@cross/image";
31
+ *
32
+ * // Create a blank canvas
33
+ * const canvas = Image.create(400, 300, 255, 255, 255);
34
+ *
35
+ * // Draw on it
36
+ * canvas.fillRect(50, 50, 100, 100, 255, 0, 0, 255);
37
+ *
38
+ * // Load and composite another image
39
+ * const overlay = await Image.decode(await Deno.readFile("logo.png"));
40
+ * canvas.composite(overlay, 10, 10, 0.8);
41
+ *
42
+ * // Save the result
43
+ * await Deno.writeFile("result.png", await canvas.encode("png"));
44
+ * ```
23
45
  */
24
46
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.ASCIIFormat = exports.RAWFormat = exports.BMPFormat = exports.TIFFFormat = exports.GIFFormat = exports.WebPFormat = exports.JPEGFormat = exports.PNGFormat = exports.Image = void 0;
47
+ exports.ASCIIFormat = exports.PCXFormat = exports.PAMFormat = exports.DNGFormat = exports.ICOFormat = exports.BMPFormat = exports.TIFFFormat = exports.GIFFormat = exports.WebPFormat = exports.JPEGFormat = exports.APNGFormat = exports.PNGFormat = exports.Image = void 0;
26
48
  var image_js_1 = require("./src/image.js");
27
49
  Object.defineProperty(exports, "Image", { enumerable: true, get: function () { return image_js_1.Image; } });
28
50
  var png_js_1 = require("./src/formats/png.js");
29
51
  Object.defineProperty(exports, "PNGFormat", { enumerable: true, get: function () { return png_js_1.PNGFormat; } });
52
+ var apng_js_1 = require("./src/formats/apng.js");
53
+ Object.defineProperty(exports, "APNGFormat", { enumerable: true, get: function () { return apng_js_1.APNGFormat; } });
30
54
  var jpeg_js_1 = require("./src/formats/jpeg.js");
31
55
  Object.defineProperty(exports, "JPEGFormat", { enumerable: true, get: function () { return jpeg_js_1.JPEGFormat; } });
32
56
  var webp_js_1 = require("./src/formats/webp.js");
@@ -37,7 +61,13 @@ var tiff_js_1 = require("./src/formats/tiff.js");
37
61
  Object.defineProperty(exports, "TIFFFormat", { enumerable: true, get: function () { return tiff_js_1.TIFFFormat; } });
38
62
  var bmp_js_1 = require("./src/formats/bmp.js");
39
63
  Object.defineProperty(exports, "BMPFormat", { enumerable: true, get: function () { return bmp_js_1.BMPFormat; } });
40
- var raw_js_1 = require("./src/formats/raw.js");
41
- Object.defineProperty(exports, "RAWFormat", { enumerable: true, get: function () { return raw_js_1.RAWFormat; } });
64
+ var ico_js_1 = require("./src/formats/ico.js");
65
+ Object.defineProperty(exports, "ICOFormat", { enumerable: true, get: function () { return ico_js_1.ICOFormat; } });
66
+ var dng_js_1 = require("./src/formats/dng.js");
67
+ Object.defineProperty(exports, "DNGFormat", { enumerable: true, get: function () { return dng_js_1.DNGFormat; } });
68
+ var pam_js_1 = require("./src/formats/pam.js");
69
+ Object.defineProperty(exports, "PAMFormat", { enumerable: true, get: function () { return pam_js_1.PAMFormat; } });
70
+ var pcx_js_1 = require("./src/formats/pcx.js");
71
+ Object.defineProperty(exports, "PCXFormat", { enumerable: true, get: function () { return pcx_js_1.PCXFormat; } });
42
72
  var ascii_js_1 = require("./src/formats/ascii.js");
43
73
  Object.defineProperty(exports, "ASCIIFormat", { enumerable: true, get: function () { return ascii_js_1.ASCIIFormat; } });
@@ -0,0 +1,50 @@
1
+ import type { ImageData, ImageFormat, MultiFrameImageData } from "../types.js";
2
+ import { PNGBase } from "./png_base.js";
3
+ /**
4
+ * APNG (Animated PNG) format handler
5
+ * Implements support for animated PNG images with multiple frames
6
+ * APNG extends PNG with animation control chunks (acTL, fcTL, fdAT)
7
+ */
8
+ export declare class APNGFormat extends PNGBase implements ImageFormat {
9
+ /** Format name identifier */
10
+ readonly name = "apng";
11
+ /** MIME type for APNG images */
12
+ readonly mimeType = "image/apng";
13
+ /**
14
+ * Check if this format supports multiple frames (animations)
15
+ * @returns true for APNG format
16
+ */
17
+ supportsMultipleFrames(): boolean;
18
+ /**
19
+ * Check if the given data is an APNG image
20
+ * @param data Raw image data to check
21
+ * @returns true if data has PNG signature and contains acTL chunk
22
+ */
23
+ canDecode(data: Uint8Array): boolean;
24
+ /**
25
+ * Decode APNG image data to RGBA (first frame only)
26
+ * @param data Raw APNG image data
27
+ * @returns Decoded image data with RGBA pixels of first frame
28
+ */
29
+ decode(data: Uint8Array): Promise<ImageData>;
30
+ /**
31
+ * Decode all frames from APNG image
32
+ * @param data Raw APNG image data
33
+ * @returns Decoded multi-frame image data
34
+ */
35
+ decodeFrames(data: Uint8Array): Promise<MultiFrameImageData>;
36
+ /**
37
+ * Encode RGBA image data to APNG format (single frame)
38
+ * @param imageData Image data to encode
39
+ * @returns Encoded APNG image bytes
40
+ */
41
+ encode(imageData: ImageData): Promise<Uint8Array>;
42
+ /**
43
+ * Encode multi-frame image data to APNG format
44
+ * @param imageData Multi-frame image data to encode
45
+ * @returns Encoded APNG image bytes
46
+ */
47
+ encodeFrames(imageData: MultiFrameImageData): Promise<Uint8Array>;
48
+ private decodeFrameData;
49
+ }
50
+ //# sourceMappingURL=apng.d.ts.map
@@ -0,0 +1,368 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.APNGFormat = void 0;
4
+ const security_js_1 = require("../utils/security.js");
5
+ const png_base_js_1 = require("./png_base.js");
6
+ /**
7
+ * APNG (Animated PNG) format handler
8
+ * Implements support for animated PNG images with multiple frames
9
+ * APNG extends PNG with animation control chunks (acTL, fcTL, fdAT)
10
+ */
11
+ class APNGFormat extends png_base_js_1.PNGBase {
12
+ constructor() {
13
+ super(...arguments);
14
+ /** Format name identifier */
15
+ Object.defineProperty(this, "name", {
16
+ enumerable: true,
17
+ configurable: true,
18
+ writable: true,
19
+ value: "apng"
20
+ });
21
+ /** MIME type for APNG images */
22
+ Object.defineProperty(this, "mimeType", {
23
+ enumerable: true,
24
+ configurable: true,
25
+ writable: true,
26
+ value: "image/apng"
27
+ });
28
+ }
29
+ /**
30
+ * Check if this format supports multiple frames (animations)
31
+ * @returns true for APNG format
32
+ */
33
+ supportsMultipleFrames() {
34
+ return true;
35
+ }
36
+ /**
37
+ * Check if the given data is an APNG image
38
+ * @param data Raw image data to check
39
+ * @returns true if data has PNG signature and contains acTL chunk
40
+ */
41
+ canDecode(data) {
42
+ // PNG signature: 137 80 78 71 13 10 26 10
43
+ if (data.length < 8 ||
44
+ data[0] !== 137 || data[1] !== 80 ||
45
+ data[2] !== 78 || data[3] !== 71 ||
46
+ data[4] !== 13 || data[5] !== 10 ||
47
+ data[6] !== 26 || data[7] !== 10) {
48
+ return false;
49
+ }
50
+ // Check for acTL (animation control) chunk to identify APNG
51
+ let pos = 8;
52
+ while (pos + 8 < data.length) {
53
+ const length = this.readUint32(data, pos);
54
+ pos += 4;
55
+ const type = String.fromCharCode(data[pos], data[pos + 1], data[pos + 2], data[pos + 3]);
56
+ pos += 4;
57
+ if (type === "acTL") {
58
+ return true;
59
+ }
60
+ pos += length + 4; // Skip chunk data and CRC
61
+ if (type === "IDAT") {
62
+ // If we hit IDAT before acTL, it's not an APNG
63
+ return false;
64
+ }
65
+ }
66
+ return false;
67
+ }
68
+ /**
69
+ * Decode APNG image data to RGBA (first frame only)
70
+ * @param data Raw APNG image data
71
+ * @returns Decoded image data with RGBA pixels of first frame
72
+ */
73
+ async decode(data) {
74
+ const frames = await this.decodeFrames(data);
75
+ const firstFrame = frames.frames[0];
76
+ return {
77
+ width: firstFrame.width,
78
+ height: firstFrame.height,
79
+ data: firstFrame.data,
80
+ metadata: frames.metadata,
81
+ };
82
+ }
83
+ /**
84
+ * Decode all frames from APNG image
85
+ * @param data Raw APNG image data
86
+ * @returns Decoded multi-frame image data
87
+ */
88
+ async decodeFrames(data) {
89
+ if (!this.canDecode(data)) {
90
+ throw new Error("Invalid APNG signature or missing acTL chunk");
91
+ }
92
+ let pos = 8; // Skip PNG signature
93
+ let width = 0;
94
+ let height = 0;
95
+ let bitDepth = 0;
96
+ let colorType = 0;
97
+ const metadata = {};
98
+ const frames = [];
99
+ // First pass: parse structure and extract metadata
100
+ const chunkList = [];
101
+ while (pos < data.length) {
102
+ const length = this.readUint32(data, pos);
103
+ pos += 4;
104
+ const type = String.fromCharCode(data[pos], data[pos + 1], data[pos + 2], data[pos + 3]);
105
+ pos += 4;
106
+ const chunkData = data.slice(pos, pos + length);
107
+ const chunkPos = pos;
108
+ pos += length;
109
+ pos += 4; // Skip CRC
110
+ chunkList.push({ type, data: chunkData, pos: chunkPos });
111
+ if (type === "IHDR") {
112
+ width = this.readUint32(chunkData, 0);
113
+ height = this.readUint32(chunkData, 4);
114
+ bitDepth = chunkData[8];
115
+ colorType = chunkData[9];
116
+ }
117
+ else if (type === "acTL") {
118
+ // Animation control chunk - we'll use frame count later if needed
119
+ // const numFrames = this.readUint32(chunkData, 0);
120
+ // const numPlays = this.readUint32(chunkData, 4);
121
+ }
122
+ else if (type === "pHYs") {
123
+ this.parsePhysChunk(chunkData, metadata, width, height);
124
+ }
125
+ else if (type === "tEXt") {
126
+ this.parseTextChunk(chunkData, metadata);
127
+ }
128
+ else if (type === "iTXt") {
129
+ this.parseITxtChunk(chunkData, metadata);
130
+ }
131
+ else if (type === "eXIf") {
132
+ this.parseExifChunk(chunkData, metadata);
133
+ }
134
+ else if (type === "IEND") {
135
+ break;
136
+ }
137
+ }
138
+ if (width === 0 || height === 0) {
139
+ throw new Error("Invalid APNG: missing IHDR chunk");
140
+ }
141
+ (0, security_js_1.validateImageDimensions)(width, height);
142
+ // Second pass: decode frames
143
+ let currentFrameControl = null;
144
+ let frameDataChunks = [];
145
+ let defaultImageChunks = [];
146
+ let hasSeenFcTL = false;
147
+ for (const chunk of chunkList) {
148
+ if (chunk.type === "fcTL") {
149
+ // If we have a previous frame to decode
150
+ if (frameDataChunks.length > 0 && currentFrameControl) {
151
+ const frameData = await this.decodeFrameData(frameDataChunks, currentFrameControl.width, currentFrameControl.height, bitDepth, colorType);
152
+ frames.push({
153
+ width: currentFrameControl.width,
154
+ height: currentFrameControl.height,
155
+ data: frameData,
156
+ frameMetadata: {
157
+ delay: currentFrameControl.delay,
158
+ disposal: currentFrameControl.disposal,
159
+ left: currentFrameControl.xOffset,
160
+ top: currentFrameControl.yOffset,
161
+ },
162
+ });
163
+ frameDataChunks = [];
164
+ }
165
+ // Parse frame control
166
+ const _fcSeq = this.readUint32(chunk.data, 0);
167
+ const fcWidth = this.readUint32(chunk.data, 4);
168
+ const fcHeight = this.readUint32(chunk.data, 8);
169
+ const fcXOffset = this.readUint32(chunk.data, 12);
170
+ const fcYOffset = this.readUint32(chunk.data, 16);
171
+ const delayNum = this.readUint16(chunk.data, 20);
172
+ const delayDen = this.readUint16(chunk.data, 22);
173
+ const disposeOp = chunk.data[24];
174
+ const blendOp = chunk.data[25];
175
+ const delay = delayDen === 0
176
+ ? delayNum * 10
177
+ : Math.round((delayNum / delayDen) * 1000);
178
+ let disposal = "none";
179
+ if (disposeOp === 1)
180
+ disposal = "background";
181
+ else if (disposeOp === 2)
182
+ disposal = "previous";
183
+ currentFrameControl = {
184
+ width: fcWidth,
185
+ height: fcHeight,
186
+ xOffset: fcXOffset,
187
+ yOffset: fcYOffset,
188
+ delay,
189
+ disposal,
190
+ blend: blendOp === 1 ? "over" : "source",
191
+ };
192
+ // If this is the first fcTL and we have default image data, use it for this frame
193
+ if (frames.length === 0 && defaultImageChunks.length > 0) {
194
+ frameDataChunks = defaultImageChunks;
195
+ defaultImageChunks = [];
196
+ }
197
+ hasSeenFcTL = true;
198
+ }
199
+ else if (chunk.type === "IDAT") {
200
+ if (!hasSeenFcTL) {
201
+ // Collect default image chunks
202
+ defaultImageChunks.push(chunk.data);
203
+ }
204
+ else if (currentFrameControl) {
205
+ // IDAT after first fcTL belongs to that frame
206
+ frameDataChunks.push(chunk.data);
207
+ }
208
+ }
209
+ else if (chunk.type === "fdAT") {
210
+ // Frame data chunk (skip sequence number)
211
+ const _frameSeq = this.readUint32(chunk.data, 0);
212
+ frameDataChunks.push(chunk.data.slice(4));
213
+ }
214
+ else if (chunk.type === "IEND") {
215
+ // Decode last frame if any
216
+ if (frameDataChunks.length > 0 && currentFrameControl) {
217
+ const frameData = await this.decodeFrameData(frameDataChunks, currentFrameControl.width, currentFrameControl.height, bitDepth, colorType);
218
+ frames.push({
219
+ width: currentFrameControl.width,
220
+ height: currentFrameControl.height,
221
+ data: frameData,
222
+ frameMetadata: {
223
+ delay: currentFrameControl.delay,
224
+ disposal: currentFrameControl.disposal,
225
+ left: currentFrameControl.xOffset,
226
+ top: currentFrameControl.yOffset,
227
+ },
228
+ });
229
+ }
230
+ else if (defaultImageChunks.length > 0) {
231
+ // Only default image, no fcTL found - treat as single frame
232
+ const frameData = await this.decodeFrameData(defaultImageChunks, width, height, bitDepth, colorType);
233
+ frames.push({
234
+ width,
235
+ height,
236
+ data: frameData,
237
+ frameMetadata: {
238
+ delay: 0,
239
+ disposal: "none",
240
+ left: 0,
241
+ top: 0,
242
+ },
243
+ });
244
+ }
245
+ break;
246
+ }
247
+ }
248
+ return {
249
+ width,
250
+ height,
251
+ frames,
252
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
253
+ };
254
+ }
255
+ /**
256
+ * Encode RGBA image data to APNG format (single frame)
257
+ * @param imageData Image data to encode
258
+ * @returns Encoded APNG image bytes
259
+ */
260
+ encode(imageData) {
261
+ // For single frame, create a multi-frame with one frame
262
+ const multiFrame = {
263
+ width: imageData.width,
264
+ height: imageData.height,
265
+ frames: [{
266
+ width: imageData.width,
267
+ height: imageData.height,
268
+ data: imageData.data,
269
+ frameMetadata: { delay: 0 },
270
+ }],
271
+ metadata: imageData.metadata,
272
+ };
273
+ return this.encodeFrames(multiFrame);
274
+ }
275
+ /**
276
+ * Encode multi-frame image data to APNG format
277
+ * @param imageData Multi-frame image data to encode
278
+ * @returns Encoded APNG image bytes
279
+ */
280
+ async encodeFrames(imageData) {
281
+ const { width, height, frames, metadata } = imageData;
282
+ if (frames.length === 0) {
283
+ throw new Error("No frames to encode");
284
+ }
285
+ // Prepare IHDR chunk
286
+ const ihdr = new Uint8Array(13);
287
+ this.writeUint32(ihdr, 0, width);
288
+ this.writeUint32(ihdr, 4, height);
289
+ ihdr[8] = 8; // bit depth
290
+ ihdr[9] = 6; // color type: RGBA
291
+ ihdr[10] = 0; // compression method
292
+ ihdr[11] = 0; // filter method
293
+ ihdr[12] = 0; // interlace method
294
+ // Build PNG
295
+ const chunks = [];
296
+ chunks.push(new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10])); // PNG signature
297
+ chunks.push(this.createChunk("IHDR", ihdr));
298
+ // Add acTL chunk for animation control
299
+ const actl = new Uint8Array(8);
300
+ this.writeUint32(actl, 0, frames.length); // num_frames
301
+ this.writeUint32(actl, 4, 0); // num_plays (0 = infinite)
302
+ chunks.push(this.createChunk("acTL", actl));
303
+ // Add metadata chunks if available
304
+ this.addMetadataChunks(chunks, metadata);
305
+ // Add frames
306
+ let sequenceNumber = 0;
307
+ for (let i = 0; i < frames.length; i++) {
308
+ const frame = frames[i];
309
+ const fctl = new Uint8Array(26);
310
+ this.writeUint32(fctl, 0, sequenceNumber++); // sequence_number
311
+ this.writeUint32(fctl, 4, frame.width); // width
312
+ this.writeUint32(fctl, 8, frame.height); // height
313
+ this.writeUint32(fctl, 12, frame.frameMetadata?.left ?? 0); // x_offset
314
+ this.writeUint32(fctl, 16, frame.frameMetadata?.top ?? 0); // y_offset
315
+ // Convert delay from milliseconds to fraction
316
+ const delay = frame.frameMetadata?.delay ?? 100;
317
+ // Use milliseconds directly if possible (up to ~65 seconds)
318
+ if (delay < 65536) {
319
+ this.writeUint16(fctl, 20, delay); // delay_num
320
+ this.writeUint16(fctl, 22, 1000); // delay_den (1/1000 sec)
321
+ }
322
+ else {
323
+ // Fallback to 1/100 sec for longer delays
324
+ this.writeUint16(fctl, 20, Math.round(delay / 10)); // delay_num
325
+ this.writeUint16(fctl, 22, 100); // delay_den (1/100 sec)
326
+ }
327
+ // Disposal method
328
+ let disposeOp = 0; // APNG_DISPOSE_OP_NONE
329
+ if (frame.frameMetadata?.disposal === "background") {
330
+ disposeOp = 1; // APNG_DISPOSE_OP_BACKGROUND
331
+ }
332
+ else if (frame.frameMetadata?.disposal === "previous") {
333
+ disposeOp = 2; // APNG_DISPOSE_OP_PREVIOUS
334
+ }
335
+ fctl[24] = disposeOp;
336
+ fctl[25] = 0; // blend_op: APNG_BLEND_OP_SOURCE
337
+ chunks.push(this.createChunk("fcTL", fctl));
338
+ // Filter and compress frame data
339
+ const filtered = this.filterData(frame.data, frame.width, frame.height);
340
+ const compressed = await this.deflate(filtered);
341
+ if (i === 0) {
342
+ // First frame uses IDAT
343
+ chunks.push(this.createChunk("IDAT", compressed));
344
+ }
345
+ else {
346
+ // Subsequent frames use fdAT with sequence number
347
+ const fdat = new Uint8Array(4 + compressed.length);
348
+ this.writeUint32(fdat, 0, sequenceNumber++);
349
+ fdat.set(compressed, 4);
350
+ chunks.push(this.createChunk("fdAT", fdat));
351
+ }
352
+ }
353
+ chunks.push(this.createChunk("IEND", new Uint8Array(0)));
354
+ // Concatenate all chunks
355
+ return this.concatenateArrays(chunks);
356
+ }
357
+ // Helper methods for frame decoding
358
+ async decodeFrameData(chunks, width, height, bitDepth, colorType) {
359
+ // Concatenate chunks
360
+ const idatData = this.concatenateArrays(chunks);
361
+ // Decompress data
362
+ const decompressed = await this.inflate(idatData);
363
+ // Unfilter and convert to RGBA
364
+ const rgba = this.unfilterAndConvert(decompressed, width, height, bitDepth, colorType);
365
+ return rgba;
366
+ }
367
+ }
368
+ exports.APNGFormat = APNGFormat;
@@ -26,11 +26,5 @@ export declare class BMPFormat implements ImageFormat {
26
26
  * @returns Encoded BMP image bytes
27
27
  */
28
28
  encode(imageData: ImageData): Promise<Uint8Array>;
29
- private readUint16LE;
30
- private readUint32LE;
31
- private readInt32LE;
32
- private writeUint16LE;
33
- private writeUint32LE;
34
- private writeInt32LE;
35
29
  }
36
30
  //# sourceMappingURL=bmp.d.ts.map
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BMPFormat = void 0;
4
4
  const security_js_1 = require("../utils/security.js");
5
+ const byte_utils_js_1 = require("../utils/byte_utils.js");
5
6
  // Constants for unit conversions
6
7
  const INCHES_PER_METER = 39.3701;
7
8
  /**
@@ -45,10 +46,10 @@ class BMPFormat {
45
46
  throw new Error("Invalid BMP signature");
46
47
  }
47
48
  // Read BMP file header (14 bytes)
48
- const _fileSize = this.readUint32LE(data, 2);
49
- const dataOffset = this.readUint32LE(data, 10);
49
+ const _fileSize = (0, byte_utils_js_1.readUint32LE)(data, 2);
50
+ const dataOffset = (0, byte_utils_js_1.readUint32LE)(data, 10);
50
51
  // Read DIB header (at least 40 bytes for BITMAPINFOHEADER)
51
- const dibHeaderSize = this.readUint32LE(data, 14);
52
+ const dibHeaderSize = (0, byte_utils_js_1.readUint32LE)(data, 14);
52
53
  let width;
53
54
  let height;
54
55
  let bitDepth;
@@ -56,13 +57,13 @@ class BMPFormat {
56
57
  const metadata = {};
57
58
  if (dibHeaderSize >= 40) {
58
59
  // BITMAPINFOHEADER or later
59
- width = this.readInt32LE(data, 18);
60
- height = this.readInt32LE(data, 22);
61
- bitDepth = this.readUint16LE(data, 28);
62
- compression = this.readUint32LE(data, 30);
60
+ width = (0, byte_utils_js_1.readInt32LE)(data, 18);
61
+ height = (0, byte_utils_js_1.readInt32LE)(data, 22);
62
+ bitDepth = (0, byte_utils_js_1.readUint16LE)(data, 28);
63
+ compression = (0, byte_utils_js_1.readUint32LE)(data, 30);
63
64
  // Read DPI information (pixels per meter)
64
- const xPixelsPerMeter = this.readInt32LE(data, 38);
65
- const yPixelsPerMeter = this.readInt32LE(data, 42);
65
+ const xPixelsPerMeter = (0, byte_utils_js_1.readInt32LE)(data, 38);
66
+ const yPixelsPerMeter = (0, byte_utils_js_1.readInt32LE)(data, 42);
66
67
  if (xPixelsPerMeter > 0 && yPixelsPerMeter > 0) {
67
68
  // Convert pixels per meter to DPI
68
69
  metadata.dpiX = Math.round(xPixelsPerMeter / INCHES_PER_METER);
@@ -137,21 +138,21 @@ class BMPFormat {
137
138
  // BMP File Header (14 bytes)
138
139
  result[0] = 0x42; // 'B'
139
140
  result[1] = 0x4d; // 'M'
140
- this.writeUint32LE(result, 2, fileSize); // File size
141
- this.writeUint32LE(result, 6, 0); // Reserved
142
- this.writeUint32LE(result, 10, 54); // Offset to pixel data (14 + 40)
141
+ (0, byte_utils_js_1.writeUint32LE)(result, 2, fileSize); // File size
142
+ (0, byte_utils_js_1.writeUint32LE)(result, 6, 0); // Reserved
143
+ (0, byte_utils_js_1.writeUint32LE)(result, 10, 54); // Offset to pixel data (14 + 40)
143
144
  // DIB Header (BITMAPINFOHEADER - 40 bytes)
144
- this.writeUint32LE(result, 14, 40); // DIB header size
145
- this.writeInt32LE(result, 18, width); // Width
146
- this.writeInt32LE(result, 22, height); // Height (positive = bottom-up)
147
- this.writeUint16LE(result, 26, 1); // Planes
148
- this.writeUint16LE(result, 28, 32); // Bits per pixel
149
- this.writeUint32LE(result, 30, 0); // Compression (0 = uncompressed)
150
- this.writeUint32LE(result, 34, pixelDataSize); // Image size
151
- this.writeInt32LE(result, 38, xPixelsPerMeter); // X pixels per meter
152
- this.writeInt32LE(result, 42, yPixelsPerMeter); // Y pixels per meter
153
- this.writeUint32LE(result, 46, 0); // Colors in palette
154
- this.writeUint32LE(result, 50, 0); // Important colors
145
+ (0, byte_utils_js_1.writeUint32LE)(result, 14, 40); // DIB header size
146
+ (0, byte_utils_js_1.writeInt32LE)(result, 18, width); // Width
147
+ (0, byte_utils_js_1.writeInt32LE)(result, 22, height); // Height (positive = bottom-up)
148
+ (0, byte_utils_js_1.writeUint16LE)(result, 26, 1); // Planes
149
+ (0, byte_utils_js_1.writeUint16LE)(result, 28, 32); // Bits per pixel
150
+ (0, byte_utils_js_1.writeUint32LE)(result, 30, 0); // Compression (0 = uncompressed)
151
+ (0, byte_utils_js_1.writeUint32LE)(result, 34, pixelDataSize); // Image size
152
+ (0, byte_utils_js_1.writeInt32LE)(result, 38, xPixelsPerMeter); // X pixels per meter
153
+ (0, byte_utils_js_1.writeInt32LE)(result, 42, yPixelsPerMeter); // Y pixels per meter
154
+ (0, byte_utils_js_1.writeUint32LE)(result, 46, 0); // Colors in palette
155
+ (0, byte_utils_js_1.writeUint32LE)(result, 50, 0); // Important colors
155
156
  // Write pixel data (bottom-to-top, BGR(A) format)
156
157
  let offset = 54;
157
158
  for (let y = height - 1; y >= 0; y--) {
@@ -170,29 +171,5 @@ class BMPFormat {
170
171
  }
171
172
  return Promise.resolve(result);
172
173
  }
173
- readUint16LE(data, offset) {
174
- return data[offset] | (data[offset + 1] << 8);
175
- }
176
- readUint32LE(data, offset) {
177
- return data[offset] | (data[offset + 1] << 8) |
178
- (data[offset + 2] << 16) | (data[offset + 3] << 24);
179
- }
180
- readInt32LE(data, offset) {
181
- const value = this.readUint32LE(data, offset);
182
- return value > 0x7fffffff ? value - 0x100000000 : value;
183
- }
184
- writeUint16LE(data, offset, value) {
185
- data[offset] = value & 0xff;
186
- data[offset + 1] = (value >>> 8) & 0xff;
187
- }
188
- writeUint32LE(data, offset, value) {
189
- data[offset] = value & 0xff;
190
- data[offset + 1] = (value >>> 8) & 0xff;
191
- data[offset + 2] = (value >>> 16) & 0xff;
192
- data[offset + 3] = (value >>> 24) & 0xff;
193
- }
194
- writeInt32LE(data, offset, value) {
195
- this.writeUint32LE(data, offset, value < 0 ? value + 0x100000000 : value);
196
- }
197
174
  }
198
175
  exports.BMPFormat = BMPFormat;
@@ -0,0 +1,27 @@
1
+ import type { ImageData } from "../types.js";
2
+ import { TIFFFormat } from "./tiff.js";
3
+ /**
4
+ * DNG format handler
5
+ * Implements a basic Linear DNG (Digital Negative) writer.
6
+ * DNG is based on TIFF/EP. This implementation creates a valid DNG
7
+ * containing uncompressed linear RGB data (demosaiced).
8
+ */
9
+ export declare class DNGFormat extends TIFFFormat {
10
+ /** Format name identifier */
11
+ readonly name = "dng";
12
+ /** MIME type for DNG images */
13
+ readonly mimeType = "image/x-adobe-dng";
14
+ /**
15
+ * Check if the given data is a DNG image
16
+ * @param data Raw image data to check
17
+ * @returns true if data has DNG signature (TIFF signature + DNGVersion tag)
18
+ */
19
+ canDecode(data: Uint8Array): boolean;
20
+ /**
21
+ * Encode RGBA image data to DNG format (Linear DNG)
22
+ * @param imageData Image data to encode
23
+ * @returns Encoded DNG image bytes
24
+ */
25
+ encode(imageData: ImageData): Promise<Uint8Array>;
26
+ }
27
+ //# sourceMappingURL=dng.d.ts.map