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
@@ -2,13 +2,18 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Image = void 0;
4
4
  const resize_js_1 = require("./utils/resize.js");
5
+ const image_processing_js_1 = require("./utils/image_processing.js");
5
6
  const png_js_1 = require("./formats/png.js");
7
+ const apng_js_1 = require("./formats/apng.js");
6
8
  const jpeg_js_1 = require("./formats/jpeg.js");
7
9
  const webp_js_1 = require("./formats/webp.js");
8
10
  const gif_js_1 = require("./formats/gif.js");
9
11
  const tiff_js_1 = require("./formats/tiff.js");
10
12
  const bmp_js_1 = require("./formats/bmp.js");
11
- const raw_js_1 = require("./formats/raw.js");
13
+ const ico_js_1 = require("./formats/ico.js");
14
+ const dng_js_1 = require("./formats/dng.js");
15
+ const pam_js_1 = require("./formats/pam.js");
16
+ const pcx_js_1 = require("./formats/pcx.js");
12
17
  const ascii_js_1 = require("./formats/ascii.js");
13
18
  const security_js_1 = require("./utils/security.js");
14
19
  /**
@@ -265,6 +270,31 @@ class Image {
265
270
  image.imageData = { width, height, data: new Uint8Array(data) };
266
271
  return image;
267
272
  }
273
+ /**
274
+ * Create a blank image with the specified dimensions and color
275
+ * @param width Image width
276
+ * @param height Image height
277
+ * @param r Red component (0-255, default: 0)
278
+ * @param g Green component (0-255, default: 0)
279
+ * @param b Blue component (0-255, default: 0)
280
+ * @param a Alpha component (0-255, default: 255)
281
+ * @returns Image instance
282
+ */
283
+ static create(width, height, r = 0, g = 0, b = 0, a = 255) {
284
+ // Validate dimensions for security (prevent integer overflow and heap exhaustion)
285
+ (0, security_js_1.validateImageDimensions)(width, height);
286
+ const data = new Uint8Array(width * height * 4);
287
+ // Fill with the specified color
288
+ for (let i = 0; i < data.length; i += 4) {
289
+ data[i] = r;
290
+ data[i + 1] = g;
291
+ data[i + 2] = b;
292
+ data[i + 3] = a;
293
+ }
294
+ const image = new Image();
295
+ image.imageData = { width, height, data };
296
+ return image;
297
+ }
268
298
  /**
269
299
  * Resize the image
270
300
  * @param options Resize options
@@ -351,6 +381,174 @@ class Image {
351
381
  };
352
382
  return image;
353
383
  }
384
+ /**
385
+ * Composite another image on top of this image at the specified position
386
+ * @param overlay Image to place on top
387
+ * @param x X position (can be negative)
388
+ * @param y Y position (can be negative)
389
+ * @param opacity Opacity of overlay (0-1, default: 1)
390
+ * @returns This image instance for chaining
391
+ */
392
+ composite(overlay, x, y, opacity = 1) {
393
+ if (!this.imageData)
394
+ throw new Error("No image loaded");
395
+ if (!overlay.imageData)
396
+ throw new Error("Overlay has no image loaded");
397
+ this.imageData.data = (0, image_processing_js_1.composite)(this.imageData.data, this.imageData.width, this.imageData.height, overlay.imageData.data, overlay.imageData.width, overlay.imageData.height, x, y, opacity);
398
+ return this;
399
+ }
400
+ /**
401
+ * Adjust brightness of the image
402
+ * @param amount Brightness adjustment (-1 to 1, where 0 is no change)
403
+ * @returns This image instance for chaining
404
+ */
405
+ brightness(amount) {
406
+ if (!this.imageData)
407
+ throw new Error("No image loaded");
408
+ this.imageData.data = (0, image_processing_js_1.adjustBrightness)(this.imageData.data, amount);
409
+ return this;
410
+ }
411
+ /**
412
+ * Adjust contrast of the image
413
+ * @param amount Contrast adjustment (-1 to 1, where 0 is no change)
414
+ * @returns This image instance for chaining
415
+ */
416
+ contrast(amount) {
417
+ if (!this.imageData)
418
+ throw new Error("No image loaded");
419
+ this.imageData.data = (0, image_processing_js_1.adjustContrast)(this.imageData.data, amount);
420
+ return this;
421
+ }
422
+ /**
423
+ * Adjust exposure of the image
424
+ * @param amount Exposure adjustment in stops (-3 to 3, where 0 is no change)
425
+ * @returns This image instance for chaining
426
+ */
427
+ exposure(amount) {
428
+ if (!this.imageData)
429
+ throw new Error("No image loaded");
430
+ this.imageData.data = (0, image_processing_js_1.adjustExposure)(this.imageData.data, amount);
431
+ return this;
432
+ }
433
+ /**
434
+ * Adjust saturation of the image
435
+ * @param amount Saturation adjustment (-1 to 1, where 0 is no change)
436
+ * @returns This image instance for chaining
437
+ */
438
+ saturation(amount) {
439
+ if (!this.imageData)
440
+ throw new Error("No image loaded");
441
+ this.imageData.data = (0, image_processing_js_1.adjustSaturation)(this.imageData.data, amount);
442
+ return this;
443
+ }
444
+ /**
445
+ * Invert colors of the image
446
+ * @returns This image instance for chaining
447
+ */
448
+ invert() {
449
+ if (!this.imageData)
450
+ throw new Error("No image loaded");
451
+ this.imageData.data = (0, image_processing_js_1.invert)(this.imageData.data);
452
+ return this;
453
+ }
454
+ /**
455
+ * Convert the image to grayscale
456
+ * @returns This image instance for chaining
457
+ */
458
+ grayscale() {
459
+ if (!this.imageData)
460
+ throw new Error("No image loaded");
461
+ this.imageData.data = (0, image_processing_js_1.grayscale)(this.imageData.data);
462
+ return this;
463
+ }
464
+ /**
465
+ * Fill a rectangular region with a color
466
+ * @param x Starting X position
467
+ * @param y Starting Y position
468
+ * @param width Width of the fill region
469
+ * @param height Height of the fill region
470
+ * @param r Red component (0-255)
471
+ * @param g Green component (0-255)
472
+ * @param b Blue component (0-255)
473
+ * @param a Alpha component (0-255, default: 255)
474
+ * @returns This image instance for chaining
475
+ */
476
+ fillRect(x, y, width, height, r, g, b, a = 255) {
477
+ if (!this.imageData)
478
+ throw new Error("No image loaded");
479
+ this.imageData.data = (0, image_processing_js_1.fillRect)(this.imageData.data, this.imageData.width, this.imageData.height, x, y, width, height, r, g, b, a);
480
+ return this;
481
+ }
482
+ /**
483
+ * Crop the image to a rectangular region
484
+ * @param x Starting X position
485
+ * @param y Starting Y position
486
+ * @param width Width of the crop region
487
+ * @param height Height of the crop region
488
+ * @returns This image instance for chaining
489
+ */
490
+ crop(x, y, width, height) {
491
+ if (!this.imageData)
492
+ throw new Error("No image loaded");
493
+ const result = (0, image_processing_js_1.crop)(this.imageData.data, this.imageData.width, this.imageData.height, x, y, width, height);
494
+ this.imageData.width = result.width;
495
+ this.imageData.height = result.height;
496
+ this.imageData.data = result.data;
497
+ // Update physical dimensions if DPI is set
498
+ if (this.imageData.metadata) {
499
+ const metadata = this.imageData.metadata;
500
+ if (metadata.dpiX) {
501
+ this.imageData.metadata.physicalWidth = result.width / metadata.dpiX;
502
+ }
503
+ if (metadata.dpiY) {
504
+ this.imageData.metadata.physicalHeight = result.height / metadata.dpiY;
505
+ }
506
+ }
507
+ return this;
508
+ }
509
+ /**
510
+ * Get the pixel color at the specified position
511
+ * @param x X position
512
+ * @param y Y position
513
+ * @returns Object with r, g, b, a components (0-255) or undefined if out of bounds
514
+ */
515
+ getPixel(x, y) {
516
+ if (!this.imageData)
517
+ throw new Error("No image loaded");
518
+ if (x < 0 || x >= this.imageData.width || y < 0 || y >= this.imageData.height) {
519
+ return undefined;
520
+ }
521
+ const idx = (y * this.imageData.width + x) * 4;
522
+ return {
523
+ r: this.imageData.data[idx],
524
+ g: this.imageData.data[idx + 1],
525
+ b: this.imageData.data[idx + 2],
526
+ a: this.imageData.data[idx + 3],
527
+ };
528
+ }
529
+ /**
530
+ * Set the pixel color at the specified position
531
+ * @param x X position
532
+ * @param y Y position
533
+ * @param r Red component (0-255)
534
+ * @param g Green component (0-255)
535
+ * @param b Blue component (0-255)
536
+ * @param a Alpha component (0-255, default: 255)
537
+ * @returns This image instance for chaining
538
+ */
539
+ setPixel(x, y, r, g, b, a = 255) {
540
+ if (!this.imageData)
541
+ throw new Error("No image loaded");
542
+ if (x < 0 || x >= this.imageData.width || y < 0 || y >= this.imageData.height) {
543
+ return this;
544
+ }
545
+ const idx = (y * this.imageData.width + x) * 4;
546
+ this.imageData.data[idx] = r;
547
+ this.imageData.data[idx + 1] = g;
548
+ this.imageData.data[idx + 2] = b;
549
+ this.imageData.data[idx + 3] = a;
550
+ return this;
551
+ }
354
552
  }
355
553
  exports.Image = Image;
356
554
  Object.defineProperty(Image, "formats", {
@@ -359,12 +557,16 @@ Object.defineProperty(Image, "formats", {
359
557
  writable: true,
360
558
  value: [
361
559
  new png_js_1.PNGFormat(),
560
+ new apng_js_1.APNGFormat(),
362
561
  new jpeg_js_1.JPEGFormat(),
363
562
  new webp_js_1.WebPFormat(),
364
563
  new gif_js_1.GIFFormat(),
365
564
  new tiff_js_1.TIFFFormat(),
366
565
  new bmp_js_1.BMPFormat(),
367
- new raw_js_1.RAWFormat(),
566
+ new ico_js_1.ICOFormat(),
567
+ new dng_js_1.DNGFormat(),
568
+ new pam_js_1.PAMFormat(),
569
+ new pcx_js_1.PCXFormat(),
368
570
  new ascii_js_1.ASCIIFormat(),
369
571
  ]
370
572
  });
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Shared byte-level read/write utilities for image formats
3
+ * These functions handle reading and writing multi-byte integers
4
+ * in little-endian byte order, commonly used in BMP, ICO, GIF, and other formats.
5
+ */
6
+ /**
7
+ * Read a 16-bit unsigned integer in little-endian format
8
+ */
9
+ export declare function readUint16LE(data: Uint8Array, offset: number): number;
10
+ /**
11
+ * Read a 32-bit unsigned integer in little-endian format
12
+ */
13
+ export declare function readUint32LE(data: Uint8Array, offset: number): number;
14
+ /**
15
+ * Read a 32-bit signed integer in little-endian format
16
+ */
17
+ export declare function readInt32LE(data: Uint8Array, offset: number): number;
18
+ /**
19
+ * Write a 16-bit unsigned integer in little-endian format
20
+ */
21
+ export declare function writeUint16LE(data: Uint8Array, offset: number, value: number): void;
22
+ /**
23
+ * Write a 32-bit unsigned integer in little-endian format
24
+ */
25
+ export declare function writeUint32LE(data: Uint8Array, offset: number, value: number): void;
26
+ /**
27
+ * Write a 32-bit signed integer in little-endian format
28
+ */
29
+ export declare function writeInt32LE(data: Uint8Array, offset: number, value: number): void;
30
+ //# sourceMappingURL=byte_utils.d.ts.map
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ /**
3
+ * Shared byte-level read/write utilities for image formats
4
+ * These functions handle reading and writing multi-byte integers
5
+ * in little-endian byte order, commonly used in BMP, ICO, GIF, and other formats.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.readUint16LE = readUint16LE;
9
+ exports.readUint32LE = readUint32LE;
10
+ exports.readInt32LE = readInt32LE;
11
+ exports.writeUint16LE = writeUint16LE;
12
+ exports.writeUint32LE = writeUint32LE;
13
+ exports.writeInt32LE = writeInt32LE;
14
+ // Constants for signed/unsigned integer conversion
15
+ const INT32_MAX = 0x7fffffff;
16
+ const UINT32_RANGE = 0x100000000;
17
+ /**
18
+ * Read a 16-bit unsigned integer in little-endian format
19
+ */
20
+ function readUint16LE(data, offset) {
21
+ return data[offset] | (data[offset + 1] << 8);
22
+ }
23
+ /**
24
+ * Read a 32-bit unsigned integer in little-endian format
25
+ */
26
+ function readUint32LE(data, offset) {
27
+ return data[offset] | (data[offset + 1] << 8) |
28
+ (data[offset + 2] << 16) | (data[offset + 3] << 24);
29
+ }
30
+ /**
31
+ * Read a 32-bit signed integer in little-endian format
32
+ */
33
+ function readInt32LE(data, offset) {
34
+ const value = readUint32LE(data, offset);
35
+ return value > INT32_MAX ? value - UINT32_RANGE : value;
36
+ }
37
+ /**
38
+ * Write a 16-bit unsigned integer in little-endian format
39
+ */
40
+ function writeUint16LE(data, offset, value) {
41
+ data[offset] = value & 0xff;
42
+ data[offset + 1] = (value >>> 8) & 0xff;
43
+ }
44
+ /**
45
+ * Write a 32-bit unsigned integer in little-endian format
46
+ */
47
+ function writeUint32LE(data, offset, value) {
48
+ data[offset] = value & 0xff;
49
+ data[offset + 1] = (value >>> 8) & 0xff;
50
+ data[offset + 2] = (value >>> 16) & 0xff;
51
+ data[offset + 3] = (value >>> 24) & 0xff;
52
+ }
53
+ /**
54
+ * Write a 32-bit signed integer in little-endian format
55
+ */
56
+ function writeInt32LE(data, offset, value) {
57
+ writeUint32LE(data, offset, value < 0 ? value + UINT32_RANGE : value);
58
+ }
@@ -5,8 +5,9 @@
5
5
  export declare class GIFEncoder {
6
6
  private width;
7
7
  private height;
8
- private data;
9
- constructor(width: number, height: number, data: Uint8Array);
8
+ private frames;
9
+ constructor(width: number, height: number, data?: Uint8Array);
10
+ addFrame(data: Uint8Array, delay?: number): void;
10
11
  private writeBytes;
11
12
  private writeUint16LE;
12
13
  private writeString;
@@ -20,15 +20,20 @@ class GIFEncoder {
20
20
  writable: true,
21
21
  value: void 0
22
22
  });
23
- Object.defineProperty(this, "data", {
23
+ Object.defineProperty(this, "frames", {
24
24
  enumerable: true,
25
25
  configurable: true,
26
26
  writable: true,
27
- value: void 0
27
+ value: []
28
28
  });
29
29
  this.width = width;
30
30
  this.height = height;
31
- this.data = data;
31
+ if (data) {
32
+ this.addFrame(data);
33
+ }
34
+ }
35
+ addFrame(data, delay = 0) {
36
+ this.frames.push({ data, delay });
32
37
  }
33
38
  writeBytes(output, bytes) {
34
39
  output.push(...bytes);
@@ -57,7 +62,7 @@ class GIFEncoder {
57
62
  /**
58
63
  * Quantize RGBA image to 256 colors using median cut algorithm
59
64
  */
60
- quantize() {
65
+ quantize(data) {
61
66
  // Simple quantization: collect unique colors and build palette
62
67
  const colorMap = new Map();
63
68
  const colors = [];
@@ -68,10 +73,10 @@ class GIFEncoder {
68
73
  const rgStep = 255 / RG_LEVELS; // Step size for R/G quantization
69
74
  const bStep = 255 / B_LEVELS; // Step size for B quantization
70
75
  // Collect unique colors
71
- for (let i = 0; i < this.data.length; i += 4) {
72
- const r = this.data[i];
73
- const g = this.data[i + 1];
74
- const b = this.data[i + 2];
76
+ for (let i = 0; i < data.length; i += 4) {
77
+ const r = data[i];
78
+ const g = data[i + 1];
79
+ const b = data[i + 2];
75
80
  const key = `${r},${g},${b}`;
76
81
  if (!colorMap.has(key) && colors.length < 256) {
77
82
  colorMap.set(key, colors.length);
@@ -86,12 +91,12 @@ class GIFEncoder {
86
91
  colorMap.clear();
87
92
  colors.length = 0;
88
93
  useColorReduction = true;
89
- for (let i = 0; i < this.data.length; i += 4) {
94
+ for (let i = 0; i < data.length; i += 4) {
90
95
  // Reduce color depth: 3 bits for R/G channels, 2 bits for B channel
91
96
  // This gives us 8 bits total = 256 possible colors
92
- const r = this.quantizeChannel(this.data[i], RG_LEVELS, rgStep);
93
- const g = this.quantizeChannel(this.data[i + 1], RG_LEVELS, rgStep);
94
- const b = this.quantizeChannel(this.data[i + 2], B_LEVELS, bStep);
97
+ const r = this.quantizeChannel(data[i], RG_LEVELS, rgStep);
98
+ const g = this.quantizeChannel(data[i + 1], RG_LEVELS, rgStep);
99
+ const b = this.quantizeChannel(data[i + 2], B_LEVELS, bStep);
95
100
  const key = `${r},${g},${b}`;
96
101
  if (!colorMap.has(key)) {
97
102
  if (colors.length < 256) {
@@ -115,10 +120,10 @@ class GIFEncoder {
115
120
  }
116
121
  // Create indexed data
117
122
  const indexed = new Uint8Array(this.width * this.height);
118
- for (let i = 0, j = 0; i < this.data.length; i += 4, j++) {
119
- let r = this.data[i];
120
- let g = this.data[i + 1];
121
- let b = this.data[i + 2];
123
+ for (let i = 0, j = 0; i < data.length; i += 4, j++) {
124
+ let r = data[i];
125
+ let g = data[i + 1];
126
+ let b = data[i + 2];
122
127
  // Apply color reduction if it was used for building the palette
123
128
  if (useColorReduction) {
124
129
  r = this.quantizeChannel(r, RG_LEVELS, rgStep);
@@ -164,10 +169,14 @@ class GIFEncoder {
164
169
  return Math.max(2, bits);
165
170
  }
166
171
  encode() {
172
+ if (this.frames.length === 0) {
173
+ throw new Error("No frames to encode");
174
+ }
167
175
  const output = [];
168
- // Quantize image
169
- const { palette, indexed } = this.quantize();
170
- const paletteSize = palette.length / 3;
176
+ // Quantize first frame for Global Color Table
177
+ const firstFrame = this.frames[0];
178
+ const { palette: globalPalette, indexed: firstIndexed } = this.quantize(firstFrame.data);
179
+ const paletteSize = globalPalette.length / 3;
171
180
  const bitsPerColor = this.getBitsPerColor(paletteSize);
172
181
  // Header
173
182
  this.writeString(output, "GIF89a");
@@ -190,38 +199,96 @@ class GIFEncoder {
190
199
  // So we need to write that many colors, padding if necessary
191
200
  const gctSize = 1 << bitsPerColor;
192
201
  const paddedPalette = new Uint8Array(gctSize * 3);
193
- paddedPalette.set(palette);
202
+ paddedPalette.set(globalPalette);
194
203
  this.writeBytes(output, paddedPalette);
195
- // Image Descriptor
196
- output.push(0x2c); // Image Separator
197
- // Image position and dimensions
198
- this.writeUint16LE(output, 0); // Left
199
- this.writeUint16LE(output, 0); // Top
200
- this.writeUint16LE(output, this.width);
201
- this.writeUint16LE(output, this.height);
202
- // Packed field:
203
- // - Local Color Table Flag (1 bit): 0
204
- // - Interlace Flag (1 bit): 0
205
- // - Sort Flag (1 bit): 0
206
- // - Reserved (2 bits): 0
207
- // - Size of Local Color Table (3 bits): 0
208
- output.push(0);
209
- // LZW Minimum Code Size
210
- const minCodeSize = Math.max(2, bitsPerColor);
211
- output.push(minCodeSize);
212
- // Compress image data with LZW
213
- const encoder = new lzw_js_1.LZWEncoder(minCodeSize);
214
- const compressed = encoder.compress(indexed);
215
- // Write compressed data in sub-blocks (max 255 bytes per block)
216
- for (let i = 0; i < compressed.length; i += 255) {
217
- const blockSize = Math.min(255, compressed.length - i);
218
- output.push(blockSize);
219
- for (let j = 0; j < blockSize; j++) {
220
- output.push(compressed[i + j]);
204
+ // Netscape Application Extension (Looping)
205
+ if (this.frames.length > 1) {
206
+ output.push(0x21); // Extension Introducer
207
+ output.push(0xff); // Application Extension Label
208
+ output.push(11); // Block Size
209
+ this.writeString(output, "NETSCAPE2.0");
210
+ output.push(3); // Sub-block Size
211
+ output.push(1); // Loop Indicator (1 = loop)
212
+ this.writeUint16LE(output, 0); // Loop Count (0 = infinite)
213
+ output.push(0); // Block Terminator
214
+ }
215
+ // Encode frames
216
+ for (let i = 0; i < this.frames.length; i++) {
217
+ const frame = this.frames[i];
218
+ let indexed;
219
+ let useLocalPalette = false;
220
+ let localPalette = null;
221
+ let localBitsPerColor = bitsPerColor;
222
+ if (i === 0) {
223
+ indexed = firstIndexed;
224
+ }
225
+ else {
226
+ // Quantize subsequent frames
227
+ // For simplicity, we use a Local Color Table for each frame to ensure colors are correct
228
+ const result = this.quantize(frame.data);
229
+ indexed = result.indexed;
230
+ localPalette = result.palette;
231
+ useLocalPalette = true;
232
+ const localPaletteSize = localPalette.length / 3;
233
+ localBitsPerColor = this.getBitsPerColor(localPaletteSize);
221
234
  }
235
+ // Graphic Control Extension
236
+ output.push(0x21); // Extension Introducer
237
+ output.push(0xf9); // Graphic Control Label
238
+ output.push(4); // Byte Size
239
+ // Packed Field
240
+ // Reserved (3 bits)
241
+ // Disposal Method (3 bits): 2 (Restore to background) - usually safe for animation
242
+ // User Input Flag (1 bit): 0
243
+ // Transparent Color Flag (1 bit): 0
244
+ output.push(0x08); // Disposal method 2 (Restore to background)
245
+ // Delay Time (1/100ths of a second)
246
+ // Default to 10 (100ms) if not specified
247
+ const delay = frame.delay > 0 ? Math.round(frame.delay / 10) : 10;
248
+ this.writeUint16LE(output, delay);
249
+ // Transparent Color Index
250
+ output.push(0);
251
+ output.push(0); // Block Terminator
252
+ // Image Descriptor
253
+ output.push(0x2c); // Image Separator
254
+ this.writeUint16LE(output, 0); // Left
255
+ this.writeUint16LE(output, 0); // Top
256
+ this.writeUint16LE(output, this.width);
257
+ this.writeUint16LE(output, this.height);
258
+ // Packed Field
259
+ if (useLocalPalette && localPalette) {
260
+ // LCT Flag: 1
261
+ // Interlace: 0
262
+ // Sort: 0
263
+ // Reserved: 0
264
+ // Size of LCT: localBitsPerColor - 1
265
+ const lctPacked = 0x80 | (localBitsPerColor - 1);
266
+ output.push(lctPacked);
267
+ // Write Local Color Table
268
+ const lctSize = 1 << localBitsPerColor;
269
+ const paddedLct = new Uint8Array(lctSize * 3);
270
+ paddedLct.set(localPalette);
271
+ this.writeBytes(output, paddedLct);
272
+ }
273
+ else {
274
+ output.push(0); // No LCT
275
+ }
276
+ // LZW Minimum Code Size
277
+ const minCodeSize = Math.max(2, useLocalPalette ? localBitsPerColor : bitsPerColor);
278
+ output.push(minCodeSize);
279
+ // Compress image data with LZW
280
+ const encoder = new lzw_js_1.LZWEncoder(minCodeSize);
281
+ const compressed = encoder.compress(indexed);
282
+ // Write compressed data in sub-blocks (max 255 bytes per block)
283
+ for (let k = 0; k < compressed.length; k += 255) {
284
+ const blockSize = Math.min(255, compressed.length - k);
285
+ output.push(blockSize);
286
+ for (let j = 0; j < blockSize; j++) {
287
+ output.push(compressed[k + j]);
288
+ }
289
+ }
290
+ output.push(0); // Block Terminator
222
291
  }
223
- // Block Terminator
224
- output.push(0);
225
292
  // Trailer
226
293
  output.push(0x3b);
227
294
  return new Uint8Array(output);
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Image processing utilities for common operations like compositing,
3
+ * level adjustments, and color manipulations.
4
+ */
5
+ /**
6
+ * Composite one image on top of another at a specified position
7
+ * @param base Base image data (RGBA)
8
+ * @param baseWidth Base image width
9
+ * @param baseHeight Base image height
10
+ * @param overlay Overlay image data (RGBA)
11
+ * @param overlayWidth Overlay image width
12
+ * @param overlayHeight Overlay image height
13
+ * @param x X position to place overlay (can be negative)
14
+ * @param y Y position to place overlay (can be negative)
15
+ * @param opacity Opacity of overlay (0-1, default: 1)
16
+ * @returns New image data with overlay composited on base
17
+ */
18
+ export declare function composite(base: Uint8Array, baseWidth: number, baseHeight: number, overlay: Uint8Array, overlayWidth: number, overlayHeight: number, x: number, y: number, opacity?: number): Uint8Array;
19
+ /**
20
+ * Adjust brightness of an image
21
+ * @param data Image data (RGBA)
22
+ * @param amount Brightness adjustment (-1 to 1, where 0 is no change)
23
+ * @returns New image data with adjusted brightness
24
+ */
25
+ export declare function adjustBrightness(data: Uint8Array, amount: number): Uint8Array;
26
+ /**
27
+ * Adjust contrast of an image
28
+ * @param data Image data (RGBA)
29
+ * @param amount Contrast adjustment (-1 to 1, where 0 is no change)
30
+ * @returns New image data with adjusted contrast
31
+ */
32
+ export declare function adjustContrast(data: Uint8Array, amount: number): Uint8Array;
33
+ /**
34
+ * Adjust exposure of an image
35
+ * @param data Image data (RGBA)
36
+ * @param amount Exposure adjustment in stops (-3 to 3, where 0 is no change)
37
+ * @returns New image data with adjusted exposure
38
+ */
39
+ export declare function adjustExposure(data: Uint8Array, amount: number): Uint8Array;
40
+ /**
41
+ * Adjust saturation of an image
42
+ * @param data Image data (RGBA)
43
+ * @param amount Saturation adjustment (-1 to 1, where 0 is no change)
44
+ * @returns New image data with adjusted saturation
45
+ */
46
+ export declare function adjustSaturation(data: Uint8Array, amount: number): Uint8Array;
47
+ /**
48
+ * Invert colors of an image
49
+ * @param data Image data (RGBA)
50
+ * @returns New image data with inverted colors
51
+ */
52
+ export declare function invert(data: Uint8Array): Uint8Array;
53
+ /**
54
+ * Convert image to grayscale
55
+ * @param data Image data (RGBA)
56
+ * @returns New image data in grayscale
57
+ */
58
+ export declare function grayscale(data: Uint8Array): Uint8Array;
59
+ /**
60
+ * Fill a rectangular region with a color
61
+ * @param data Image data (RGBA)
62
+ * @param width Image width
63
+ * @param height Image height
64
+ * @param x Starting X position
65
+ * @param y Starting Y position
66
+ * @param fillWidth Width of the fill region
67
+ * @param fillHeight Height of the fill region
68
+ * @param r Red component (0-255)
69
+ * @param g Green component (0-255)
70
+ * @param b Blue component (0-255)
71
+ * @param a Alpha component (0-255)
72
+ * @returns Modified image data
73
+ */
74
+ export declare function fillRect(data: Uint8Array, width: number, height: number, x: number, y: number, fillWidth: number, fillHeight: number, r: number, g: number, b: number, a: number): Uint8Array;
75
+ /**
76
+ * Crop an image to a rectangular region
77
+ * @param data Image data (RGBA)
78
+ * @param width Image width
79
+ * @param height Image height
80
+ * @param x Starting X position
81
+ * @param y Starting Y position
82
+ * @param cropWidth Width of the crop region
83
+ * @param cropHeight Height of the crop region
84
+ * @returns Cropped image data and dimensions
85
+ */
86
+ export declare function crop(data: Uint8Array, width: number, height: number, x: number, y: number, cropWidth: number, cropHeight: number): {
87
+ data: Uint8Array;
88
+ width: number;
89
+ height: number;
90
+ };
91
+ //# sourceMappingURL=image_processing.d.ts.map