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
@@ -17,15 +17,20 @@ export class GIFEncoder {
17
17
  writable: true,
18
18
  value: void 0
19
19
  });
20
- Object.defineProperty(this, "data", {
20
+ Object.defineProperty(this, "frames", {
21
21
  enumerable: true,
22
22
  configurable: true,
23
23
  writable: true,
24
- value: void 0
24
+ value: []
25
25
  });
26
26
  this.width = width;
27
27
  this.height = height;
28
- this.data = data;
28
+ if (data) {
29
+ this.addFrame(data);
30
+ }
31
+ }
32
+ addFrame(data, delay = 0) {
33
+ this.frames.push({ data, delay });
29
34
  }
30
35
  writeBytes(output, bytes) {
31
36
  output.push(...bytes);
@@ -54,7 +59,7 @@ export class GIFEncoder {
54
59
  /**
55
60
  * Quantize RGBA image to 256 colors using median cut algorithm
56
61
  */
57
- quantize() {
62
+ quantize(data) {
58
63
  // Simple quantization: collect unique colors and build palette
59
64
  const colorMap = new Map();
60
65
  const colors = [];
@@ -65,10 +70,10 @@ export class GIFEncoder {
65
70
  const rgStep = 255 / RG_LEVELS; // Step size for R/G quantization
66
71
  const bStep = 255 / B_LEVELS; // Step size for B quantization
67
72
  // Collect unique colors
68
- for (let i = 0; i < this.data.length; i += 4) {
69
- const r = this.data[i];
70
- const g = this.data[i + 1];
71
- const b = this.data[i + 2];
73
+ for (let i = 0; i < data.length; i += 4) {
74
+ const r = data[i];
75
+ const g = data[i + 1];
76
+ const b = data[i + 2];
72
77
  const key = `${r},${g},${b}`;
73
78
  if (!colorMap.has(key) && colors.length < 256) {
74
79
  colorMap.set(key, colors.length);
@@ -83,12 +88,12 @@ export class GIFEncoder {
83
88
  colorMap.clear();
84
89
  colors.length = 0;
85
90
  useColorReduction = true;
86
- for (let i = 0; i < this.data.length; i += 4) {
91
+ for (let i = 0; i < data.length; i += 4) {
87
92
  // Reduce color depth: 3 bits for R/G channels, 2 bits for B channel
88
93
  // This gives us 8 bits total = 256 possible colors
89
- const r = this.quantizeChannel(this.data[i], RG_LEVELS, rgStep);
90
- const g = this.quantizeChannel(this.data[i + 1], RG_LEVELS, rgStep);
91
- const b = this.quantizeChannel(this.data[i + 2], B_LEVELS, bStep);
94
+ const r = this.quantizeChannel(data[i], RG_LEVELS, rgStep);
95
+ const g = this.quantizeChannel(data[i + 1], RG_LEVELS, rgStep);
96
+ const b = this.quantizeChannel(data[i + 2], B_LEVELS, bStep);
92
97
  const key = `${r},${g},${b}`;
93
98
  if (!colorMap.has(key)) {
94
99
  if (colors.length < 256) {
@@ -112,10 +117,10 @@ export class GIFEncoder {
112
117
  }
113
118
  // Create indexed data
114
119
  const indexed = new Uint8Array(this.width * this.height);
115
- for (let i = 0, j = 0; i < this.data.length; i += 4, j++) {
116
- let r = this.data[i];
117
- let g = this.data[i + 1];
118
- let b = this.data[i + 2];
120
+ for (let i = 0, j = 0; i < data.length; i += 4, j++) {
121
+ let r = data[i];
122
+ let g = data[i + 1];
123
+ let b = data[i + 2];
119
124
  // Apply color reduction if it was used for building the palette
120
125
  if (useColorReduction) {
121
126
  r = this.quantizeChannel(r, RG_LEVELS, rgStep);
@@ -161,10 +166,14 @@ export class GIFEncoder {
161
166
  return Math.max(2, bits);
162
167
  }
163
168
  encode() {
169
+ if (this.frames.length === 0) {
170
+ throw new Error("No frames to encode");
171
+ }
164
172
  const output = [];
165
- // Quantize image
166
- const { palette, indexed } = this.quantize();
167
- const paletteSize = palette.length / 3;
173
+ // Quantize first frame for Global Color Table
174
+ const firstFrame = this.frames[0];
175
+ const { palette: globalPalette, indexed: firstIndexed } = this.quantize(firstFrame.data);
176
+ const paletteSize = globalPalette.length / 3;
168
177
  const bitsPerColor = this.getBitsPerColor(paletteSize);
169
178
  // Header
170
179
  this.writeString(output, "GIF89a");
@@ -187,38 +196,96 @@ export class GIFEncoder {
187
196
  // So we need to write that many colors, padding if necessary
188
197
  const gctSize = 1 << bitsPerColor;
189
198
  const paddedPalette = new Uint8Array(gctSize * 3);
190
- paddedPalette.set(palette);
199
+ paddedPalette.set(globalPalette);
191
200
  this.writeBytes(output, paddedPalette);
192
- // Image Descriptor
193
- output.push(0x2c); // Image Separator
194
- // Image position and dimensions
195
- this.writeUint16LE(output, 0); // Left
196
- this.writeUint16LE(output, 0); // Top
197
- this.writeUint16LE(output, this.width);
198
- this.writeUint16LE(output, this.height);
199
- // Packed field:
200
- // - Local Color Table Flag (1 bit): 0
201
- // - Interlace Flag (1 bit): 0
202
- // - Sort Flag (1 bit): 0
203
- // - Reserved (2 bits): 0
204
- // - Size of Local Color Table (3 bits): 0
205
- output.push(0);
206
- // LZW Minimum Code Size
207
- const minCodeSize = Math.max(2, bitsPerColor);
208
- output.push(minCodeSize);
209
- // Compress image data with LZW
210
- const encoder = new LZWEncoder(minCodeSize);
211
- const compressed = encoder.compress(indexed);
212
- // Write compressed data in sub-blocks (max 255 bytes per block)
213
- for (let i = 0; i < compressed.length; i += 255) {
214
- const blockSize = Math.min(255, compressed.length - i);
215
- output.push(blockSize);
216
- for (let j = 0; j < blockSize; j++) {
217
- output.push(compressed[i + j]);
201
+ // Netscape Application Extension (Looping)
202
+ if (this.frames.length > 1) {
203
+ output.push(0x21); // Extension Introducer
204
+ output.push(0xff); // Application Extension Label
205
+ output.push(11); // Block Size
206
+ this.writeString(output, "NETSCAPE2.0");
207
+ output.push(3); // Sub-block Size
208
+ output.push(1); // Loop Indicator (1 = loop)
209
+ this.writeUint16LE(output, 0); // Loop Count (0 = infinite)
210
+ output.push(0); // Block Terminator
211
+ }
212
+ // Encode frames
213
+ for (let i = 0; i < this.frames.length; i++) {
214
+ const frame = this.frames[i];
215
+ let indexed;
216
+ let useLocalPalette = false;
217
+ let localPalette = null;
218
+ let localBitsPerColor = bitsPerColor;
219
+ if (i === 0) {
220
+ indexed = firstIndexed;
221
+ }
222
+ else {
223
+ // Quantize subsequent frames
224
+ // For simplicity, we use a Local Color Table for each frame to ensure colors are correct
225
+ const result = this.quantize(frame.data);
226
+ indexed = result.indexed;
227
+ localPalette = result.palette;
228
+ useLocalPalette = true;
229
+ const localPaletteSize = localPalette.length / 3;
230
+ localBitsPerColor = this.getBitsPerColor(localPaletteSize);
218
231
  }
232
+ // Graphic Control Extension
233
+ output.push(0x21); // Extension Introducer
234
+ output.push(0xf9); // Graphic Control Label
235
+ output.push(4); // Byte Size
236
+ // Packed Field
237
+ // Reserved (3 bits)
238
+ // Disposal Method (3 bits): 2 (Restore to background) - usually safe for animation
239
+ // User Input Flag (1 bit): 0
240
+ // Transparent Color Flag (1 bit): 0
241
+ output.push(0x08); // Disposal method 2 (Restore to background)
242
+ // Delay Time (1/100ths of a second)
243
+ // Default to 10 (100ms) if not specified
244
+ const delay = frame.delay > 0 ? Math.round(frame.delay / 10) : 10;
245
+ this.writeUint16LE(output, delay);
246
+ // Transparent Color Index
247
+ output.push(0);
248
+ output.push(0); // Block Terminator
249
+ // Image Descriptor
250
+ output.push(0x2c); // Image Separator
251
+ this.writeUint16LE(output, 0); // Left
252
+ this.writeUint16LE(output, 0); // Top
253
+ this.writeUint16LE(output, this.width);
254
+ this.writeUint16LE(output, this.height);
255
+ // Packed Field
256
+ if (useLocalPalette && localPalette) {
257
+ // LCT Flag: 1
258
+ // Interlace: 0
259
+ // Sort: 0
260
+ // Reserved: 0
261
+ // Size of LCT: localBitsPerColor - 1
262
+ const lctPacked = 0x80 | (localBitsPerColor - 1);
263
+ output.push(lctPacked);
264
+ // Write Local Color Table
265
+ const lctSize = 1 << localBitsPerColor;
266
+ const paddedLct = new Uint8Array(lctSize * 3);
267
+ paddedLct.set(localPalette);
268
+ this.writeBytes(output, paddedLct);
269
+ }
270
+ else {
271
+ output.push(0); // No LCT
272
+ }
273
+ // LZW Minimum Code Size
274
+ const minCodeSize = Math.max(2, useLocalPalette ? localBitsPerColor : bitsPerColor);
275
+ output.push(minCodeSize);
276
+ // Compress image data with LZW
277
+ const encoder = new LZWEncoder(minCodeSize);
278
+ const compressed = encoder.compress(indexed);
279
+ // Write compressed data in sub-blocks (max 255 bytes per block)
280
+ for (let k = 0; k < compressed.length; k += 255) {
281
+ const blockSize = Math.min(255, compressed.length - k);
282
+ output.push(blockSize);
283
+ for (let j = 0; j < blockSize; j++) {
284
+ output.push(compressed[k + j]);
285
+ }
286
+ }
287
+ output.push(0); // Block Terminator
219
288
  }
220
- // Block Terminator
221
- output.push(0);
222
289
  // Trailer
223
290
  output.push(0x3b);
224
291
  return new Uint8Array(output);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cross-image",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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",
package/script/mod.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * @module @cross/image
3
3
  *
4
4
  * A pure JavaScript, dependency-free, cross-runtime image processing library.
5
- * Supports decoding, resizing, and encoding common image formats (PNG, JPEG, WebP, GIF, TIFF, BMP, DNG, PAM, PCX).
5
+ * Supports decoding, resizing, and encoding common image formats (PNG, APNG, JPEG, WebP, GIF, TIFF, BMP, ICO, DNG, PAM, PCX).
6
6
  * Includes image processing capabilities like compositing, level adjustments, and pixel manipulation.
7
7
  *
8
8
  * @example
@@ -45,11 +45,13 @@
45
45
  export { Image } from "./src/image.js";
46
46
  export type { ASCIIOptions, FrameMetadata, ImageData, ImageFormat, ImageFrame, ImageMetadata, MultiFrameImageData, ResizeOptions, WebPEncodeOptions, } from "./src/types.js";
47
47
  export { PNGFormat } from "./src/formats/png.js";
48
+ export { APNGFormat } from "./src/formats/apng.js";
48
49
  export { JPEGFormat } from "./src/formats/jpeg.js";
49
50
  export { WebPFormat } from "./src/formats/webp.js";
50
51
  export { GIFFormat } from "./src/formats/gif.js";
51
52
  export { type TIFFEncodeOptions, TIFFFormat } from "./src/formats/tiff.js";
52
53
  export { BMPFormat } from "./src/formats/bmp.js";
54
+ export { ICOFormat } from "./src/formats/ico.js";
53
55
  export { DNGFormat } from "./src/formats/dng.js";
54
56
  export { PAMFormat } from "./src/formats/pam.js";
55
57
  export { PCXFormat } from "./src/formats/pcx.js";
package/script/mod.js CHANGED
@@ -3,7 +3,7 @@
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, DNG, PAM, PCX).
6
+ * Supports decoding, resizing, and encoding common image formats (PNG, APNG, JPEG, WebP, GIF, TIFF, BMP, ICO, DNG, PAM, PCX).
7
7
  * Includes image processing capabilities like compositing, level adjustments, and pixel manipulation.
8
8
  *
9
9
  * @example
@@ -44,11 +44,13 @@
44
44
  * ```
45
45
  */
46
46
  Object.defineProperty(exports, "__esModule", { value: true });
47
- exports.ASCIIFormat = exports.PCXFormat = exports.PAMFormat = exports.DNGFormat = 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;
48
48
  var image_js_1 = require("./src/image.js");
49
49
  Object.defineProperty(exports, "Image", { enumerable: true, get: function () { return image_js_1.Image; } });
50
50
  var png_js_1 = require("./src/formats/png.js");
51
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; } });
52
54
  var jpeg_js_1 = require("./src/formats/jpeg.js");
53
55
  Object.defineProperty(exports, "JPEGFormat", { enumerable: true, get: function () { return jpeg_js_1.JPEGFormat; } });
54
56
  var webp_js_1 = require("./src/formats/webp.js");
@@ -59,6 +61,8 @@ var tiff_js_1 = require("./src/formats/tiff.js");
59
61
  Object.defineProperty(exports, "TIFFFormat", { enumerable: true, get: function () { return tiff_js_1.TIFFFormat; } });
60
62
  var bmp_js_1 = require("./src/formats/bmp.js");
61
63
  Object.defineProperty(exports, "BMPFormat", { enumerable: true, get: function () { return bmp_js_1.BMPFormat; } });
64
+ var ico_js_1 = require("./src/formats/ico.js");
65
+ Object.defineProperty(exports, "ICOFormat", { enumerable: true, get: function () { return ico_js_1.ICOFormat; } });
62
66
  var dng_js_1 = require("./src/formats/dng.js");
63
67
  Object.defineProperty(exports, "DNGFormat", { enumerable: true, get: function () { return dng_js_1.DNGFormat; } });
64
68
  var pam_js_1 = require("./src/formats/pam.js");
@@ -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