cross-image 0.1.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 (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +606 -0
  3. package/esm/mod.d.ts +33 -0
  4. package/esm/mod.d.ts.map +1 -0
  5. package/esm/mod.js +31 -0
  6. package/esm/package.json +3 -0
  7. package/esm/src/formats/ascii.d.ts +27 -0
  8. package/esm/src/formats/ascii.d.ts.map +1 -0
  9. package/esm/src/formats/ascii.js +172 -0
  10. package/esm/src/formats/bmp.d.ts +19 -0
  11. package/esm/src/formats/bmp.d.ts.map +1 -0
  12. package/esm/src/formats/bmp.js +174 -0
  13. package/esm/src/formats/gif.d.ts +40 -0
  14. package/esm/src/formats/gif.d.ts.map +1 -0
  15. package/esm/src/formats/gif.js +385 -0
  16. package/esm/src/formats/jpeg.d.ts +18 -0
  17. package/esm/src/formats/jpeg.d.ts.map +1 -0
  18. package/esm/src/formats/jpeg.js +414 -0
  19. package/esm/src/formats/png.d.ts +33 -0
  20. package/esm/src/formats/png.d.ts.map +1 -0
  21. package/esm/src/formats/png.js +544 -0
  22. package/esm/src/formats/raw.d.ts +23 -0
  23. package/esm/src/formats/raw.d.ts.map +1 -0
  24. package/esm/src/formats/raw.js +98 -0
  25. package/esm/src/formats/tiff.d.ts +58 -0
  26. package/esm/src/formats/tiff.d.ts.map +1 -0
  27. package/esm/src/formats/tiff.js +791 -0
  28. package/esm/src/formats/webp.d.ts +22 -0
  29. package/esm/src/formats/webp.d.ts.map +1 -0
  30. package/esm/src/formats/webp.js +403 -0
  31. package/esm/src/image.d.ts +124 -0
  32. package/esm/src/image.d.ts.map +1 -0
  33. package/esm/src/image.js +320 -0
  34. package/esm/src/types.d.ts +167 -0
  35. package/esm/src/types.d.ts.map +1 -0
  36. package/esm/src/types.js +1 -0
  37. package/esm/src/utils/gif_decoder.d.ts +42 -0
  38. package/esm/src/utils/gif_decoder.d.ts.map +1 -0
  39. package/esm/src/utils/gif_decoder.js +374 -0
  40. package/esm/src/utils/gif_encoder.d.ts +29 -0
  41. package/esm/src/utils/gif_encoder.d.ts.map +1 -0
  42. package/esm/src/utils/gif_encoder.js +226 -0
  43. package/esm/src/utils/jpeg_decoder.d.ts +39 -0
  44. package/esm/src/utils/jpeg_decoder.d.ts.map +1 -0
  45. package/esm/src/utils/jpeg_decoder.js +580 -0
  46. package/esm/src/utils/jpeg_encoder.d.ts +33 -0
  47. package/esm/src/utils/jpeg_encoder.d.ts.map +1 -0
  48. package/esm/src/utils/jpeg_encoder.js +1017 -0
  49. package/esm/src/utils/lzw.d.ts +43 -0
  50. package/esm/src/utils/lzw.d.ts.map +1 -0
  51. package/esm/src/utils/lzw.js +309 -0
  52. package/esm/src/utils/resize.d.ts +9 -0
  53. package/esm/src/utils/resize.d.ts.map +1 -0
  54. package/esm/src/utils/resize.js +52 -0
  55. package/esm/src/utils/tiff_lzw.d.ts +44 -0
  56. package/esm/src/utils/tiff_lzw.d.ts.map +1 -0
  57. package/esm/src/utils/tiff_lzw.js +306 -0
  58. package/esm/src/utils/webp_decoder.d.ts +39 -0
  59. package/esm/src/utils/webp_decoder.d.ts.map +1 -0
  60. package/esm/src/utils/webp_decoder.js +493 -0
  61. package/esm/src/utils/webp_encoder.d.ts +72 -0
  62. package/esm/src/utils/webp_encoder.d.ts.map +1 -0
  63. package/esm/src/utils/webp_encoder.js +627 -0
  64. package/package.json +41 -0
  65. package/script/mod.d.ts +33 -0
  66. package/script/mod.d.ts.map +1 -0
  67. package/script/mod.js +43 -0
  68. package/script/package.json +3 -0
  69. package/script/src/formats/ascii.d.ts +27 -0
  70. package/script/src/formats/ascii.d.ts.map +1 -0
  71. package/script/src/formats/ascii.js +176 -0
  72. package/script/src/formats/bmp.d.ts +19 -0
  73. package/script/src/formats/bmp.d.ts.map +1 -0
  74. package/script/src/formats/bmp.js +178 -0
  75. package/script/src/formats/gif.d.ts +40 -0
  76. package/script/src/formats/gif.d.ts.map +1 -0
  77. package/script/src/formats/gif.js +389 -0
  78. package/script/src/formats/jpeg.d.ts +18 -0
  79. package/script/src/formats/jpeg.d.ts.map +1 -0
  80. package/script/src/formats/jpeg.js +451 -0
  81. package/script/src/formats/png.d.ts +33 -0
  82. package/script/src/formats/png.d.ts.map +1 -0
  83. package/script/src/formats/png.js +548 -0
  84. package/script/src/formats/raw.d.ts +23 -0
  85. package/script/src/formats/raw.d.ts.map +1 -0
  86. package/script/src/formats/raw.js +102 -0
  87. package/script/src/formats/tiff.d.ts +58 -0
  88. package/script/src/formats/tiff.d.ts.map +1 -0
  89. package/script/src/formats/tiff.js +795 -0
  90. package/script/src/formats/webp.d.ts +22 -0
  91. package/script/src/formats/webp.d.ts.map +1 -0
  92. package/script/src/formats/webp.js +440 -0
  93. package/script/src/image.d.ts +124 -0
  94. package/script/src/image.d.ts.map +1 -0
  95. package/script/src/image.js +324 -0
  96. package/script/src/types.d.ts +167 -0
  97. package/script/src/types.d.ts.map +1 -0
  98. package/script/src/types.js +2 -0
  99. package/script/src/utils/gif_decoder.d.ts +42 -0
  100. package/script/src/utils/gif_decoder.d.ts.map +1 -0
  101. package/script/src/utils/gif_decoder.js +378 -0
  102. package/script/src/utils/gif_encoder.d.ts +29 -0
  103. package/script/src/utils/gif_encoder.d.ts.map +1 -0
  104. package/script/src/utils/gif_encoder.js +230 -0
  105. package/script/src/utils/jpeg_decoder.d.ts +39 -0
  106. package/script/src/utils/jpeg_decoder.d.ts.map +1 -0
  107. package/script/src/utils/jpeg_decoder.js +584 -0
  108. package/script/src/utils/jpeg_encoder.d.ts +33 -0
  109. package/script/src/utils/jpeg_encoder.d.ts.map +1 -0
  110. package/script/src/utils/jpeg_encoder.js +1021 -0
  111. package/script/src/utils/lzw.d.ts +43 -0
  112. package/script/src/utils/lzw.d.ts.map +1 -0
  113. package/script/src/utils/lzw.js +314 -0
  114. package/script/src/utils/resize.d.ts +9 -0
  115. package/script/src/utils/resize.d.ts.map +1 -0
  116. package/script/src/utils/resize.js +56 -0
  117. package/script/src/utils/tiff_lzw.d.ts +44 -0
  118. package/script/src/utils/tiff_lzw.d.ts.map +1 -0
  119. package/script/src/utils/tiff_lzw.js +311 -0
  120. package/script/src/utils/webp_decoder.d.ts +39 -0
  121. package/script/src/utils/webp_decoder.d.ts.map +1 -0
  122. package/script/src/utils/webp_decoder.js +497 -0
  123. package/script/src/utils/webp_encoder.d.ts +72 -0
  124. package/script/src/utils/webp_encoder.d.ts.map +1 -0
  125. package/script/src/utils/webp_encoder.js +631 -0
@@ -0,0 +1,27 @@
1
+ import type { ASCIIOptions, ImageData, ImageFormat } from "../types.js";
2
+ /**
3
+ * ASCII format handler
4
+ * Converts images to ASCII art text representation
5
+ *
6
+ * Format structure:
7
+ * - Magic bytes (6 bytes): "ASCII\n" (0x41 0x53 0x43 0x49 0x49 0x0A)
8
+ * - Options line: "width:W charset:C aspectRatio:A invert:I\n"
9
+ * - ASCII art text (UTF-8 encoded)
10
+ *
11
+ * Note: This format is primarily for encoding (image to ASCII).
12
+ * Decoding reconstructs a basic grayscale approximation.
13
+ */
14
+ export declare class ASCIIFormat implements ImageFormat {
15
+ readonly name = "ascii";
16
+ readonly mimeType = "text/plain";
17
+ private readonly MAGIC_BYTES;
18
+ private readonly CHARSETS;
19
+ canDecode(data: Uint8Array): boolean;
20
+ decode(data: Uint8Array): Promise<ImageData>;
21
+ encode(imageData: ImageData, options?: ASCIIOptions): Promise<Uint8Array>;
22
+ /**
23
+ * Parse options from the options line
24
+ */
25
+ private parseOptions;
26
+ }
27
+ //# sourceMappingURL=ascii.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ascii.d.ts","sourceRoot":"","sources":["../../../src/src/formats/ascii.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAExE;;;;;;;;;;;GAWG;AACH,qBAAa,WAAY,YAAW,WAAW;IAC7C,QAAQ,CAAC,IAAI,WAAW;IACxB,QAAQ,CAAC,QAAQ,gBAAgB;IAEjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAOzB;IAGH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAOvB;IAEF,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO;IAcpC,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IA+D5C,MAAM,CACJ,SAAS,EAAE,SAAS,EACpB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,UAAU,CAAC;IAoDtB;;OAEG;IACH,OAAO,CAAC,YAAY;CA6BrB"}
@@ -0,0 +1,172 @@
1
+ /**
2
+ * ASCII format handler
3
+ * Converts images to ASCII art text representation
4
+ *
5
+ * Format structure:
6
+ * - Magic bytes (6 bytes): "ASCII\n" (0x41 0x53 0x43 0x49 0x49 0x0A)
7
+ * - Options line: "width:W charset:C aspectRatio:A invert:I\n"
8
+ * - ASCII art text (UTF-8 encoded)
9
+ *
10
+ * Note: This format is primarily for encoding (image to ASCII).
11
+ * Decoding reconstructs a basic grayscale approximation.
12
+ */
13
+ export class ASCIIFormat {
14
+ constructor() {
15
+ Object.defineProperty(this, "name", {
16
+ enumerable: true,
17
+ configurable: true,
18
+ writable: true,
19
+ value: "ascii"
20
+ });
21
+ Object.defineProperty(this, "mimeType", {
22
+ enumerable: true,
23
+ configurable: true,
24
+ writable: true,
25
+ value: "text/plain"
26
+ });
27
+ Object.defineProperty(this, "MAGIC_BYTES", {
28
+ enumerable: true,
29
+ configurable: true,
30
+ writable: true,
31
+ value: new Uint8Array([
32
+ 0x41,
33
+ 0x53,
34
+ 0x43,
35
+ 0x49,
36
+ 0x49,
37
+ 0x0A,
38
+ ])
39
+ }); // "ASCII\n"
40
+ // Character sets ordered from darkest to lightest
41
+ Object.defineProperty(this, "CHARSETS", {
42
+ enumerable: true,
43
+ configurable: true,
44
+ writable: true,
45
+ value: {
46
+ simple: " .:-=+*#%@",
47
+ extended: " .'`^\",:;Il!i><~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$",
48
+ blocks: " ░▒▓█",
49
+ detailed: " .`-_':,;^=+/\"|)\\<>)iv%xclrs{*}I?!][1taeo7zjLunT#JCwfy325Fh9kP6pqdbVOlS8X$KHEA4D3RZG0MQNWU&%B@",
50
+ }
51
+ });
52
+ }
53
+ canDecode(data) {
54
+ // Check if data starts with "ASCII\n"
55
+ if (data.length < 6) {
56
+ return false;
57
+ }
58
+ return data[0] === this.MAGIC_BYTES[0] &&
59
+ data[1] === this.MAGIC_BYTES[1] &&
60
+ data[2] === this.MAGIC_BYTES[2] &&
61
+ data[3] === this.MAGIC_BYTES[3] &&
62
+ data[4] === this.MAGIC_BYTES[4] &&
63
+ data[5] === this.MAGIC_BYTES[5];
64
+ }
65
+ decode(data) {
66
+ if (!this.canDecode(data)) {
67
+ throw new Error("Invalid ASCII art signature");
68
+ }
69
+ // Convert to string
70
+ const text = new TextDecoder().decode(data);
71
+ const lines = text.split("\n");
72
+ if (lines.length < 2) {
73
+ throw new Error("Invalid ASCII art format");
74
+ }
75
+ // Parse options from second line
76
+ const optionsLine = lines[1];
77
+ const options = this.parseOptions(optionsLine);
78
+ // Get ASCII art content (skip magic line and options line)
79
+ const artLines = lines.slice(2).filter((line) => line.length > 0);
80
+ if (artLines.length === 0) {
81
+ throw new Error("No ASCII art content found");
82
+ }
83
+ // Calculate dimensions
84
+ const height = artLines.length;
85
+ const width = Math.max(...artLines.map((line) => line.length));
86
+ // Convert ASCII art back to image data
87
+ const imageData = new Uint8Array(width * height * 4);
88
+ const charset = this.CHARSETS[options.charset] || this.CHARSETS.simple;
89
+ for (let y = 0; y < height; y++) {
90
+ const line = artLines[y];
91
+ for (let x = 0; x < width; x++) {
92
+ const char = x < line.length ? line[x] : " ";
93
+ const charIndex = charset.indexOf(char);
94
+ // Calculate brightness (0-255)
95
+ let brightness;
96
+ if (charIndex === -1) {
97
+ brightness = 0; // Unknown character = black
98
+ }
99
+ else {
100
+ brightness = Math.floor((charIndex / (charset.length - 1)) * 255);
101
+ }
102
+ if (options.invert) {
103
+ brightness = 255 - brightness;
104
+ }
105
+ const offset = (y * width + x) * 4;
106
+ imageData[offset] = brightness; // R
107
+ imageData[offset + 1] = brightness; // G
108
+ imageData[offset + 2] = brightness; // B
109
+ imageData[offset + 3] = 255; // A
110
+ }
111
+ }
112
+ return Promise.resolve({ width, height, data: imageData });
113
+ }
114
+ encode(imageData, options = {}) {
115
+ const { width: targetWidth = 80, charset = "simple", aspectRatio = 0.5, invert = false, } = options;
116
+ // Get character set
117
+ const chars = this.CHARSETS[charset] || this.CHARSETS.simple;
118
+ // Calculate target height based on aspect ratio
119
+ const { width: imgWidth, height: imgHeight, data } = imageData;
120
+ const targetHeight = Math.floor((imgHeight / imgWidth) * targetWidth * aspectRatio);
121
+ // Build ASCII art
122
+ const lines = [];
123
+ for (let y = 0; y < targetHeight; y++) {
124
+ let line = "";
125
+ for (let x = 0; x < targetWidth; x++) {
126
+ // Map to source pixel
127
+ const srcX = Math.floor((x / targetWidth) * imgWidth);
128
+ const srcY = Math.floor((y / targetHeight) * imgHeight);
129
+ const offset = (srcY * imgWidth + srcX) * 4;
130
+ // Calculate grayscale value
131
+ const r = data[offset];
132
+ const g = data[offset + 1];
133
+ const b = data[offset + 2];
134
+ const gray = Math.floor(0.299 * r + 0.587 * g + 0.114 * b);
135
+ // Map to character
136
+ const brightness = invert ? 255 - gray : gray;
137
+ const charIndex = Math.floor((brightness / 255) * (chars.length - 1));
138
+ line += chars[charIndex];
139
+ }
140
+ lines.push(line);
141
+ }
142
+ // Create output with magic bytes and options
143
+ const optionsLine = `width:${targetWidth} charset:${charset} aspectRatio:${aspectRatio} invert:${invert}`;
144
+ const content = `ASCII\n${optionsLine}\n${lines.join("\n")}`;
145
+ return Promise.resolve(new TextEncoder().encode(content));
146
+ }
147
+ /**
148
+ * Parse options from the options line
149
+ */
150
+ parseOptions(line) {
151
+ const defaults = {
152
+ charset: "simple",
153
+ invert: false,
154
+ };
155
+ if (!line)
156
+ return defaults;
157
+ const parts = line.split(" ");
158
+ const options = { ...defaults };
159
+ for (const part of parts) {
160
+ const [key, value] = part.split(":");
161
+ if (key === "charset" && value) {
162
+ if (["simple", "extended", "blocks", "detailed"].includes(value)) {
163
+ options.charset = value;
164
+ }
165
+ }
166
+ else if (key === "invert" && value) {
167
+ options.invert = value === "true";
168
+ }
169
+ }
170
+ return options;
171
+ }
172
+ }
@@ -0,0 +1,19 @@
1
+ import type { ImageData, ImageFormat } from "../types.js";
2
+ /**
3
+ * BMP format handler
4
+ * Implements a pure JavaScript BMP decoder and encoder
5
+ */
6
+ export declare class BMPFormat implements ImageFormat {
7
+ readonly name = "bmp";
8
+ readonly mimeType = "image/bmp";
9
+ canDecode(data: Uint8Array): boolean;
10
+ decode(data: Uint8Array): Promise<ImageData>;
11
+ encode(imageData: ImageData): Promise<Uint8Array>;
12
+ private readUint16LE;
13
+ private readUint32LE;
14
+ private readInt32LE;
15
+ private writeUint16LE;
16
+ private writeUint32LE;
17
+ private writeInt32LE;
18
+ }
19
+ //# sourceMappingURL=bmp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bmp.d.ts","sourceRoot":"","sources":["../../../src/src/formats/bmp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAiB,MAAM,aAAa,CAAC;AAKzE;;;GAGG;AACH,qBAAa,SAAU,YAAW,WAAW;IAC3C,QAAQ,CAAC,IAAI,SAAS;IACtB,QAAQ,CAAC,QAAQ,eAAe;IAEhC,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO;IAMpC,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IAwF5C,MAAM,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC;IA8DjD,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,YAAY;CAGrB"}
@@ -0,0 +1,174 @@
1
+ // Constants for unit conversions
2
+ const INCHES_PER_METER = 39.3701;
3
+ /**
4
+ * BMP format handler
5
+ * Implements a pure JavaScript BMP decoder and encoder
6
+ */
7
+ export class BMPFormat {
8
+ constructor() {
9
+ Object.defineProperty(this, "name", {
10
+ enumerable: true,
11
+ configurable: true,
12
+ writable: true,
13
+ value: "bmp"
14
+ });
15
+ Object.defineProperty(this, "mimeType", {
16
+ enumerable: true,
17
+ configurable: true,
18
+ writable: true,
19
+ value: "image/bmp"
20
+ });
21
+ }
22
+ canDecode(data) {
23
+ // BMP signature: 'BM' (0x42 0x4D)
24
+ return data.length >= 2 &&
25
+ data[0] === 0x42 && data[1] === 0x4d;
26
+ }
27
+ decode(data) {
28
+ if (!this.canDecode(data)) {
29
+ throw new Error("Invalid BMP signature");
30
+ }
31
+ // Read BMP file header (14 bytes)
32
+ const _fileSize = this.readUint32LE(data, 2);
33
+ const dataOffset = this.readUint32LE(data, 10);
34
+ // Read DIB header (at least 40 bytes for BITMAPINFOHEADER)
35
+ const dibHeaderSize = this.readUint32LE(data, 14);
36
+ let width;
37
+ let height;
38
+ let bitDepth;
39
+ let compression;
40
+ const metadata = {};
41
+ if (dibHeaderSize >= 40) {
42
+ // BITMAPINFOHEADER or later
43
+ width = this.readInt32LE(data, 18);
44
+ height = this.readInt32LE(data, 22);
45
+ bitDepth = this.readUint16LE(data, 28);
46
+ compression = this.readUint32LE(data, 30);
47
+ // Read DPI information (pixels per meter)
48
+ const xPixelsPerMeter = this.readInt32LE(data, 38);
49
+ const yPixelsPerMeter = this.readInt32LE(data, 42);
50
+ if (xPixelsPerMeter > 0 && yPixelsPerMeter > 0) {
51
+ // Convert pixels per meter to DPI
52
+ metadata.dpiX = Math.round(xPixelsPerMeter / INCHES_PER_METER);
53
+ metadata.dpiY = Math.round(yPixelsPerMeter / INCHES_PER_METER);
54
+ metadata.physicalWidth = Math.abs(width) / metadata.dpiX;
55
+ metadata.physicalHeight = Math.abs(height) / metadata.dpiY;
56
+ }
57
+ }
58
+ else {
59
+ throw new Error("Unsupported BMP header format");
60
+ }
61
+ // Handle negative height (top-down bitmap)
62
+ const isTopDown = height < 0;
63
+ const absHeight = Math.abs(height);
64
+ // Only support uncompressed BMPs for now
65
+ if (compression !== 0) {
66
+ throw new Error(`Compressed BMP not supported (compression type: ${compression})`);
67
+ }
68
+ // Only support 24-bit and 32-bit BMPs
69
+ if (bitDepth !== 24 && bitDepth !== 32) {
70
+ throw new Error(`Unsupported bit depth: ${bitDepth}. Only 24 and 32-bit BMPs are supported.`);
71
+ }
72
+ // Calculate row size (must be multiple of 4 bytes)
73
+ const bytesPerPixel = bitDepth / 8;
74
+ const rowSize = Math.floor((bitDepth * width + 31) / 32) * 4;
75
+ // Read pixel data
76
+ const rgba = new Uint8Array(width * absHeight * 4);
77
+ for (let y = 0; y < absHeight; y++) {
78
+ const rowIndex = isTopDown ? y : (absHeight - 1 - y); // BMP stores bottom-to-top by default
79
+ const rowOffset = dataOffset + y * rowSize;
80
+ for (let x = 0; x < width; x++) {
81
+ const pixelOffset = rowOffset + x * bytesPerPixel;
82
+ const outIndex = (rowIndex * width + x) * 4;
83
+ // BMP stores pixels as BGR(A)
84
+ rgba[outIndex] = data[pixelOffset + 2]; // R
85
+ rgba[outIndex + 1] = data[pixelOffset + 1]; // G
86
+ rgba[outIndex + 2] = data[pixelOffset]; // B
87
+ rgba[outIndex + 3] = bitDepth === 32 ? data[pixelOffset + 3] : 255; // A
88
+ }
89
+ }
90
+ return Promise.resolve({
91
+ width,
92
+ height: absHeight,
93
+ data: rgba,
94
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
95
+ });
96
+ }
97
+ encode(imageData) {
98
+ const { width, height, data, metadata } = imageData;
99
+ // Calculate sizes
100
+ const bytesPerPixel = 4; // We'll encode as 32-bit RGBA
101
+ const rowSize = Math.floor((32 * width + 31) / 32) * 4;
102
+ const pixelDataSize = rowSize * height;
103
+ const fileSize = 14 + 40 + pixelDataSize; // File header + DIB header + pixel data
104
+ const result = new Uint8Array(fileSize);
105
+ // Calculate DPI values
106
+ let xPixelsPerMeter = 2835; // Default 72 DPI
107
+ let yPixelsPerMeter = 2835;
108
+ if (metadata?.dpiX && metadata.dpiX > 0) {
109
+ xPixelsPerMeter = Math.round(metadata.dpiX * INCHES_PER_METER);
110
+ }
111
+ if (metadata?.dpiY && metadata.dpiY > 0) {
112
+ yPixelsPerMeter = Math.round(metadata.dpiY * INCHES_PER_METER);
113
+ }
114
+ // BMP File Header (14 bytes)
115
+ result[0] = 0x42; // 'B'
116
+ result[1] = 0x4d; // 'M'
117
+ this.writeUint32LE(result, 2, fileSize); // File size
118
+ this.writeUint32LE(result, 6, 0); // Reserved
119
+ this.writeUint32LE(result, 10, 54); // Offset to pixel data (14 + 40)
120
+ // DIB Header (BITMAPINFOHEADER - 40 bytes)
121
+ this.writeUint32LE(result, 14, 40); // DIB header size
122
+ this.writeInt32LE(result, 18, width); // Width
123
+ this.writeInt32LE(result, 22, height); // Height (positive = bottom-up)
124
+ this.writeUint16LE(result, 26, 1); // Planes
125
+ this.writeUint16LE(result, 28, 32); // Bits per pixel
126
+ this.writeUint32LE(result, 30, 0); // Compression (0 = uncompressed)
127
+ this.writeUint32LE(result, 34, pixelDataSize); // Image size
128
+ this.writeInt32LE(result, 38, xPixelsPerMeter); // X pixels per meter
129
+ this.writeInt32LE(result, 42, yPixelsPerMeter); // Y pixels per meter
130
+ this.writeUint32LE(result, 46, 0); // Colors in palette
131
+ this.writeUint32LE(result, 50, 0); // Important colors
132
+ // Write pixel data (bottom-to-top, BGR(A) format)
133
+ let offset = 54;
134
+ for (let y = height - 1; y >= 0; y--) {
135
+ for (let x = 0; x < width; x++) {
136
+ const srcIndex = (y * width + x) * 4;
137
+ result[offset++] = data[srcIndex + 2]; // B
138
+ result[offset++] = data[srcIndex + 1]; // G
139
+ result[offset++] = data[srcIndex]; // R
140
+ result[offset++] = data[srcIndex + 3]; // A
141
+ }
142
+ // Add padding to make row size multiple of 4
143
+ const padding = rowSize - width * bytesPerPixel;
144
+ for (let p = 0; p < padding; p++) {
145
+ result[offset++] = 0;
146
+ }
147
+ }
148
+ return Promise.resolve(result);
149
+ }
150
+ readUint16LE(data, offset) {
151
+ return data[offset] | (data[offset + 1] << 8);
152
+ }
153
+ readUint32LE(data, offset) {
154
+ return data[offset] | (data[offset + 1] << 8) |
155
+ (data[offset + 2] << 16) | (data[offset + 3] << 24);
156
+ }
157
+ readInt32LE(data, offset) {
158
+ const value = this.readUint32LE(data, offset);
159
+ return value > 0x7fffffff ? value - 0x100000000 : value;
160
+ }
161
+ writeUint16LE(data, offset, value) {
162
+ data[offset] = value & 0xff;
163
+ data[offset + 1] = (value >>> 8) & 0xff;
164
+ }
165
+ writeUint32LE(data, offset, value) {
166
+ data[offset] = value & 0xff;
167
+ data[offset + 1] = (value >>> 8) & 0xff;
168
+ data[offset + 2] = (value >>> 16) & 0xff;
169
+ data[offset + 3] = (value >>> 24) & 0xff;
170
+ }
171
+ writeInt32LE(data, offset, value) {
172
+ this.writeUint32LE(data, offset, value < 0 ? value + 0x100000000 : value);
173
+ }
174
+ }
@@ -0,0 +1,40 @@
1
+ import type { ImageData, ImageFormat, MultiFrameImageData } from "../types.js";
2
+ /**
3
+ * GIF format handler
4
+ * Now includes pure-JS implementation with custom LZW compression/decompression
5
+ *
6
+ * Features:
7
+ * - LZW compression/decompression
8
+ * - Color quantization and palette generation for encoding
9
+ * - Interlacing support
10
+ * - Transparency support
11
+ * - Multi-frame animation support (decoding and encoding)
12
+ * - Falls back to runtime APIs when pure-JS fails
13
+ */
14
+ export declare class GIFFormat implements ImageFormat {
15
+ readonly name = "gif";
16
+ readonly mimeType = "image/gif";
17
+ supportsMultipleFrames(): boolean;
18
+ canDecode(data: Uint8Array): boolean;
19
+ decode(data: Uint8Array): Promise<ImageData>;
20
+ private extractMetadata;
21
+ encode(imageData: ImageData): Promise<Uint8Array>;
22
+ /**
23
+ * Decode all frames from an animated GIF
24
+ */
25
+ decodeFrames(data: Uint8Array): Promise<MultiFrameImageData>;
26
+ /**
27
+ * Encode multi-frame image data to animated GIF
28
+ * Note: Currently not implemented, will encode only first frame
29
+ */
30
+ encodeFrames(imageData: MultiFrameImageData, _options?: unknown): Promise<Uint8Array>;
31
+ private mapDisposalMethod;
32
+ private readUint16LE;
33
+ private decodeUsingRuntime;
34
+ private readDataSubBlocks;
35
+ private parseComment;
36
+ private parseXMP;
37
+ private injectMetadata;
38
+ private createCommentText;
39
+ }
40
+ //# sourceMappingURL=gif.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gif.d.ts","sourceRoot":"","sources":["../../../src/src/formats/gif.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EAEX,mBAAmB,EACpB,MAAM,aAAa,CAAC;AAIrB;;;;;;;;;;;GAWG;AACH,qBAAa,SAAU,YAAW,WAAW;IAC3C,QAAQ,CAAC,IAAI,SAAS;IACtB,QAAQ,CAAC,QAAQ,eAAe;IAEhC,sBAAsB,IAAI,OAAO;IAIjC,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO;IAS9B,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IA2ClD,OAAO,CAAC,eAAe;IAwDjB,MAAM,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC;IAyDvD;;OAEG;IACH,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAkC5D;;;OAGG;IACH,YAAY,CACV,SAAS,EAAE,mBAAmB,EAC9B,QAAQ,CAAC,EAAE,OAAO,GACjB,OAAO,CAAC,UAAU,CAAC;IAkBtB,OAAO,CAAC,iBAAiB;IAgBzB,OAAO,CAAC,YAAY;YAIN,kBAAkB;IAqChC,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,YAAY;IAmCpB,OAAO,CAAC,QAAQ;IAuBhB,OAAO,CAAC,cAAc;IAqDtB,OAAO,CAAC,iBAAiB;CAY1B"}