cross-image 0.1.2 → 0.1.4

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 (82) hide show
  1. package/README.md +35 -520
  2. package/esm/src/formats/ascii.d.ts +11 -0
  3. package/esm/src/formats/ascii.js +14 -0
  4. package/esm/src/formats/bmp.d.ts +17 -0
  5. package/esm/src/formats/bmp.js +20 -0
  6. package/esm/src/formats/gif.d.ts +21 -0
  7. package/esm/src/formats/gif.js +26 -0
  8. package/esm/src/formats/jpeg.d.ts +17 -0
  9. package/esm/src/formats/jpeg.js +20 -0
  10. package/esm/src/formats/png.d.ts +17 -0
  11. package/esm/src/formats/png.js +20 -0
  12. package/esm/src/formats/raw.d.ts +17 -0
  13. package/esm/src/formats/raw.js +20 -0
  14. package/esm/src/formats/tiff.d.ts +16 -0
  15. package/esm/src/formats/tiff.js +37 -12
  16. package/esm/src/formats/webp.d.ts +18 -0
  17. package/esm/src/formats/webp.js +21 -0
  18. package/esm/src/image.js +5 -0
  19. package/esm/src/utils/security.d.ts +28 -0
  20. package/esm/src/utils/security.js +48 -0
  21. package/esm/src/utils/webp_decoder.js +3 -0
  22. package/package.json +1 -4
  23. package/script/src/formats/ascii.d.ts +11 -0
  24. package/script/src/formats/ascii.js +14 -0
  25. package/script/src/formats/bmp.d.ts +17 -0
  26. package/script/src/formats/bmp.js +20 -0
  27. package/script/src/formats/gif.d.ts +21 -0
  28. package/script/src/formats/gif.js +26 -0
  29. package/script/src/formats/jpeg.d.ts +17 -0
  30. package/script/src/formats/jpeg.js +20 -0
  31. package/script/src/formats/png.d.ts +17 -0
  32. package/script/src/formats/png.js +20 -0
  33. package/script/src/formats/raw.d.ts +17 -0
  34. package/script/src/formats/raw.js +20 -0
  35. package/script/src/formats/tiff.d.ts +16 -0
  36. package/script/src/formats/tiff.js +37 -12
  37. package/script/src/formats/webp.d.ts +18 -0
  38. package/script/src/formats/webp.js +21 -0
  39. package/script/src/image.js +5 -0
  40. package/script/src/utils/security.d.ts +28 -0
  41. package/script/src/utils/security.js +53 -0
  42. package/script/src/utils/webp_decoder.js +3 -0
  43. package/esm/mod.d.ts.map +0 -1
  44. package/esm/src/formats/ascii.d.ts.map +0 -1
  45. package/esm/src/formats/bmp.d.ts.map +0 -1
  46. package/esm/src/formats/gif.d.ts.map +0 -1
  47. package/esm/src/formats/jpeg.d.ts.map +0 -1
  48. package/esm/src/formats/png.d.ts.map +0 -1
  49. package/esm/src/formats/raw.d.ts.map +0 -1
  50. package/esm/src/formats/tiff.d.ts.map +0 -1
  51. package/esm/src/formats/webp.d.ts.map +0 -1
  52. package/esm/src/image.d.ts.map +0 -1
  53. package/esm/src/types.d.ts.map +0 -1
  54. package/esm/src/utils/gif_decoder.d.ts.map +0 -1
  55. package/esm/src/utils/gif_encoder.d.ts.map +0 -1
  56. package/esm/src/utils/jpeg_decoder.d.ts.map +0 -1
  57. package/esm/src/utils/jpeg_encoder.d.ts.map +0 -1
  58. package/esm/src/utils/lzw.d.ts.map +0 -1
  59. package/esm/src/utils/resize.d.ts.map +0 -1
  60. package/esm/src/utils/tiff_lzw.d.ts.map +0 -1
  61. package/esm/src/utils/webp_decoder.d.ts.map +0 -1
  62. package/esm/src/utils/webp_encoder.d.ts.map +0 -1
  63. package/script/mod.d.ts.map +0 -1
  64. package/script/src/formats/ascii.d.ts.map +0 -1
  65. package/script/src/formats/bmp.d.ts.map +0 -1
  66. package/script/src/formats/gif.d.ts.map +0 -1
  67. package/script/src/formats/jpeg.d.ts.map +0 -1
  68. package/script/src/formats/png.d.ts.map +0 -1
  69. package/script/src/formats/raw.d.ts.map +0 -1
  70. package/script/src/formats/tiff.d.ts.map +0 -1
  71. package/script/src/formats/webp.d.ts.map +0 -1
  72. package/script/src/image.d.ts.map +0 -1
  73. package/script/src/types.d.ts.map +0 -1
  74. package/script/src/utils/gif_decoder.d.ts.map +0 -1
  75. package/script/src/utils/gif_encoder.d.ts.map +0 -1
  76. package/script/src/utils/jpeg_decoder.d.ts.map +0 -1
  77. package/script/src/utils/jpeg_encoder.d.ts.map +0 -1
  78. package/script/src/utils/lzw.d.ts.map +0 -1
  79. package/script/src/utils/resize.d.ts.map +0 -1
  80. package/script/src/utils/tiff_lzw.d.ts.map +0 -1
  81. package/script/src/utils/webp_decoder.d.ts.map +0 -1
  82. package/script/src/utils/webp_encoder.d.ts.map +0 -1
package/README.md CHANGED
@@ -25,27 +25,25 @@ import { Image } from "jsr:@cross/image";
25
25
  ### Node.js
26
26
 
27
27
  ```bash
28
- npm install cross-image
28
+ npx jsr add @cross/image
29
29
  ```
30
30
 
31
31
  ```ts
32
- import { Image } from "cross-image";
32
+ import { Image } from "@cross/image";
33
33
  ```
34
34
 
35
35
  ### Bun
36
36
 
37
37
  ```bash
38
- bun add cross-image
38
+ bunx jsr add @cross/image
39
39
  ```
40
40
 
41
41
  ```ts
42
- import { Image } from "cross-image";
42
+ import { Image } from "@cross/image";
43
43
  ```
44
44
 
45
45
  ## Quick Start
46
46
 
47
- ### Reading and Saving Images
48
-
49
47
  ```ts
50
48
  import { Image } from "@cross/image";
51
49
 
@@ -55,526 +53,43 @@ const image = await Image.read(data);
55
53
 
56
54
  console.log(`Image size: ${image.width}x${image.height}`);
57
55
 
58
- // Save as different format
59
- const jpeg = await image.save("jpeg");
60
- await Deno.writeFile("output.jpg", jpeg);
61
- ```
62
-
63
- ### Resizing Images
64
-
65
- ```ts
66
- import { Image } from "@cross/image";
67
-
68
- const data = await Deno.readFile("input.png");
69
- const image = await Image.read(data);
70
-
71
- // Resize with bilinear interpolation (default)
56
+ // Resize the image
72
57
  image.resize({ width: 800, height: 600 });
73
58
 
74
- // Or use nearest neighbor for faster, pixelated results
75
- image.resize({ width: 400, height: 300, method: "nearest" });
76
-
77
- // Save the result
78
- const output = await image.save("png");
79
- await Deno.writeFile("resized.png", output);
80
- ```
81
-
82
- ### Creating Images from Scratch
83
-
84
- ```ts
85
- import { Image } from "@cross/image";
86
-
87
- // Create a 100x100 red square
88
- const width = 100;
89
- const height = 100;
90
- const data = new Uint8Array(width * height * 4);
91
-
92
- for (let i = 0; i < data.length; i += 4) {
93
- data[i] = 255; // R
94
- data[i + 1] = 0; // G
95
- data[i + 2] = 0; // B
96
- data[i + 3] = 255; // A
97
- }
98
-
99
- const image = Image.fromRGBA(width, height, data);
100
- const png = await image.save("png");
101
- await Deno.writeFile("red-square.png", png);
102
- ```
103
-
104
- ### Using TIFF with LZW Compression
105
-
106
- ```ts
107
- import { Image } from "@cross/image";
108
-
109
- const data = await Deno.readFile("input.png");
110
- const image = await Image.read(data);
111
-
112
- // Save as uncompressed TIFF
113
- const uncompressed = await image.save("tiff");
114
- await Deno.writeFile("output.tiff", uncompressed);
115
-
116
- // Save with LZW compression (smaller file size)
117
- const compressed = await image.save("tiff", { compression: "lzw" });
118
- await Deno.writeFile("output-compressed.tiff", compressed);
119
- ```
120
-
121
- ### Using WebP with Quality Settings
122
-
123
- ```ts
124
- import { Image } from "@cross/image";
125
-
126
- const data = await Deno.readFile("input.png");
127
- const image = await Image.read(data);
128
-
129
- // Save as lossless WebP (quality = 100)
130
- const lossless = await image.save("webp", { quality: 100 });
131
- await Deno.writeFile("output-lossless.webp", lossless);
132
-
133
- // Save as lossy WebP with high quality (smaller file size)
134
- const highQuality = await image.save("webp", { quality: 90 });
135
- await Deno.writeFile("output-hq.webp", highQuality);
136
-
137
- // Save as lossy WebP with medium quality (much smaller)
138
- const mediumQuality = await image.save("webp", { quality: 75 });
139
- await Deno.writeFile("output-med.webp", mediumQuality);
140
-
141
- // Force lossless even with quality < 100 (pure-JS VP8L)
142
- const forcedLossless = await image.save("webp", {
143
- quality: 80,
144
- lossless: true,
145
- });
146
- await Deno.writeFile("output-forced.webp", forcedLossless);
147
- ```
148
-
149
- ### Converting to ASCII Art
150
-
151
- ```ts
152
- import { type ASCIIOptions, Image } from "@cross/image";
153
-
154
- const data = await Deno.readFile("photo.jpg");
155
- const image = await Image.read(data);
156
-
157
- // Convert to ASCII art with simple characters
158
- const ascii = await image.save("ascii", { width: 80, charset: "simple" });
159
- console.log(new TextDecoder().decode(ascii));
160
-
161
- // Or use block characters for better gradients
162
- const blocks = await image.save("ascii", {
163
- width: 60,
164
- charset: "blocks",
165
- aspectRatio: 0.5,
166
- });
167
- console.log(new TextDecoder().decode(blocks));
168
-
169
- // Save ASCII art to file
170
- await Deno.writeFile("output.txt", ascii);
171
- ```
172
-
173
- ### Working with Multi-Frame Images
174
-
175
- Read and manipulate animated GIFs and multi-page TIFFs:
176
-
177
- ```ts
178
- import { Image } from "@cross/image";
179
-
180
- // Read all frames from an animated GIF
181
- const gifData = await Deno.readFile("animated.gif");
182
- const multiFrame = await Image.readFrames(gifData);
183
-
184
- console.log(`Canvas: ${multiFrame.width}x${multiFrame.height}`);
185
- console.log(`Number of frames: ${multiFrame.frames.length}`);
186
-
187
- // Access individual frames
188
- for (let i = 0; i < multiFrame.frames.length; i++) {
189
- const frame = multiFrame.frames[i];
190
- console.log(`Frame ${i}: ${frame.width}x${frame.height}`);
191
- console.log(` Delay: ${frame.frameMetadata?.delay}ms`);
192
- console.log(` Disposal: ${frame.frameMetadata?.disposal}`);
193
- }
194
-
195
- // Create a multi-page TIFF from multiple images
196
- const page1 = Image.fromRGBA(100, 100, new Uint8Array(100 * 100 * 4));
197
- const page2 = Image.fromRGBA(100, 100, new Uint8Array(100 * 100 * 4));
198
-
199
- const multiPageTiff = {
200
- width: 100,
201
- height: 100,
202
- frames: [
203
- { width: 100, height: 100, data: page1.data },
204
- { width: 100, height: 100, data: page2.data },
205
- ],
206
- };
207
-
208
- // Save as multi-page TIFF with LZW compression
209
- const tiffData = await Image.saveFrames("tiff", multiPageTiff, {
210
- compression: "lzw",
211
- });
212
- await Deno.writeFile("multipage.tiff", tiffData);
213
-
214
- // Read all pages from a multi-page TIFF
215
- const pages = await Image.readFrames(tiffData);
216
- console.log(`Read ${pages.frames.length} pages from TIFF`);
217
- ```
218
-
219
- ### Chaining Operations
220
-
221
- ```ts
222
- import { Image } from "@cross/image";
223
-
224
- const data = await Deno.readFile("input.png");
225
- const image = await Image.read(data);
226
-
227
- // Chain multiple operations
228
- image
229
- .resize({ width: 1920, height: 1080 })
230
- .resize({ width: 800, height: 600 });
231
-
232
- const output = await image.save("webp");
233
- await Deno.writeFile("output.webp", output);
59
+ // Save in a different format
60
+ const jpeg = await image.save("jpeg");
61
+ await Deno.writeFile("output.jpg", jpeg);
234
62
  ```
235
63
 
236
64
  ## Supported Formats
237
65
 
238
- ### Format Support Matrix
239
-
240
- This table shows which image formats are supported and their implementation
241
- status:
242
-
243
- | Format | Read | Write | Pure-JS Decode | Pure-JS Encode | Native API Decode | Native API Encode | Notes |
244
- | ------ | ---- | ----- | -------------- | -------------- | ----------------- | ------------------ | -------------------------------------------- |
245
- | PNG | ✅ | ✅ | Full | Full | ImageDecoder | ✅ OffscreenCanvas | Complete pure-JS implementation |
246
- | BMP | ✅ | ✅ | Full | ✅ Full | ✅ ImageDecoder | ✅ OffscreenCanvas | Complete pure-JS implementation |
247
- | RAW | ✅ | | Full | Full | N/A | N/A | Uncompressed RGBA (no metadata) |
248
- | ASCII | ✅ | ✅ | ✅ Full | ✅ Full | N/A | N/A | Text-based ASCII art representation |
249
- | JPEG | ✅ | ✅ | ⚠️ Baseline | ⚠️ Baseline | ✅ ImageDecoder | ✅ OffscreenCanvas | Pure-JS for baseline DCT only |
250
- | GIF | ✅ | ✅ | ✅ Full | ✅ Full | ✅ ImageDecoder | ✅ OffscreenCanvas | Complete pure-JS implementation |
251
- | WebP | ✅ | ✅ | ⚠️ Lossless | ⚠️ Quantized | ✅ ImageDecoder | ✅ OffscreenCanvas | Pure-JS VP8L with quality-based quantization |
252
- | TIFF | ✅ | ✅ | ⚠️ Basic | ⚠️ Basic | ✅ ImageDecoder | ✅ OffscreenCanvas | Pure-JS for uncompressed & LZW RGB/RGBA |
253
-
254
- **Legend:**
255
-
256
- - ✅ **Full support** - Complete implementation with all common features
257
- - ⚠️ **Limited support** - Partial implementation with restrictions
258
- - **Not supported** - Feature not available in pure-JS, requires native APIs
259
- - **Pure-JS** - Works in all JavaScript runtimes without native dependencies
260
- - **Native API** - Uses runtime APIs like ImageDecoder (decode) or
261
- OffscreenCanvas (encode)
262
-
263
- ### Format Specifications Supported
264
-
265
- This table shows which format standards and variants are supported:
266
-
267
- | Format | Specification/Variant | Support Level | Implementation |
268
- | ------ | ----------------------------------- | ----------------- | -------------- |
269
- | PNG | PNG 1.2 (ISO/IEC 15948) | ✅ Full | Pure-JS |
270
- | | - Interlaced (Adam7) | ❌ Not Yet | - |
271
- | | - Color types: Grayscale, RGB, RGBA | ✅ Full | Pure-JS |
272
- | | - Metadata: pHYs, tEXt, iTXt, eXIf | ✅ Full | Pure-JS |
273
- | BMP | Windows BMP (BITMAPINFOHEADER) | ✅ Full | Pure-JS |
274
- | | - 24-bit RGB | ✅ Full | Pure-JS |
275
- | | - 32-bit RGBA | ✅ Full | Pure-JS |
276
- | | - Compressed variants (RLE) | ❌ Not Yet | - |
277
- | JPEG | JPEG/JFIF Baseline DCT | ✅ Full | Pure-JS |
278
- | | Progressive DCT | ⚠️ Native only | ImageDecoder |
279
- | | - EXIF metadata | ✅ Full | Pure-JS |
280
- | | - JFIF (APP0) with DPI | ✅ Full | Pure-JS |
281
- | WebP | WebP Lossless (VP8L) | ⚠️ Basic | Pure-JS |
282
- | | - Simple Huffman coding | ✅ Full | Pure-JS |
283
- | | - LZ77 backward references | ❌ Not in encoder | - |
284
- | | - Color cache | ❌ Not in encoder | - |
285
- | | - Transforms (predictor, etc.) | ❌ Not Yet | - |
286
- | | WebP Lossy (VP8L with quantization) | ✅ Quality-based | Pure-JS |
287
- | | - Color quantization for lossy | ✅ Full | Pure-JS |
288
- | | WebP Lossy (VP8) | ⚠️ Native only | ImageDecoder |
289
- | | - EXIF, XMP metadata | ✅ Full | Pure-JS |
290
- | TIFF | TIFF 6.0 - Uncompressed RGB/RGBA | ✅ Full | Pure-JS |
291
- | | TIFF 6.0 - LZW compressed RGB/RGBA | ✅ Full | Pure-JS |
292
- | | - JPEG, PackBits compression | ⚠️ Native only | ImageDecoder |
293
- | | - Multi-page/IFD (decode & encode) | ✅ Full | Pure-JS |
294
- | | - EXIF, Artist, Copyright metadata | ✅ Full | Pure-JS |
295
- | GIF | GIF87a, GIF89a | ✅ Full | Pure-JS |
296
- | | - LZW compression/decompression | ✅ Full | Pure-JS |
297
- | | - Color quantization (encoding) | ✅ Full | Pure-JS |
298
- | | - Transparency support | ✅ Full | Pure-JS |
299
- | | - Interlacing support | ✅ Full | Pure-JS |
300
- | | - Animation (multi-frame decode) | ✅ Full | Pure-JS |
301
- | | - Animation (encode first frame) | ⚠️ Single frame | Pure-JS |
302
- | | - Comment extensions, XMP | ✅ Full | Pure-JS |
303
- | RAW | Uncompressed RGBA | ✅ Full | Pure-JS |
304
- | ASCII | Text-based ASCII art | ✅ Full | Pure-JS |
305
- | | - Multiple character sets | ✅ Full | Pure-JS |
306
- | | - Configurable width & aspect ratio | ✅ Full | Pure-JS |
307
- | | - Brightness inversion | ✅ Full | Pure-JS |
308
-
309
- ### Runtime Compatibility by Format
310
-
311
- | Format | Deno 2.x | Node.js 18+ | Node.js 20+ | Bun | Notes |
312
- | ------ | -------- | ----------- | ----------- | --- | -------------------------------------------- |
313
- | PNG | ✅ | ✅ | ✅ | ✅ | Pure-JS works everywhere |
314
- | BMP | ✅ | ✅ | ✅ | ✅ | Pure-JS works everywhere |
315
- | RAW | ✅ | ✅ | ✅ | ✅ | Pure-JS works everywhere |
316
- | ASCII | ✅ | ✅ | ✅ | ✅ | Pure-JS works everywhere |
317
- | GIF | ✅ | ✅ | ✅ | ✅ | Pure-JS works everywhere |
318
- | JPEG | ✅ | ⚠️ Baseline | ✅ | ✅ | Node 18: pure-JS baseline only, 20+: full |
319
- | WebP | ✅ | ⚠️ Lossless | ✅ | ✅ | Node 18: pure-JS lossless only, 20+: full |
320
- | TIFF | ✅ | ✅ | ✅ | ✅ | Node 18: pure-JS uncompressed+LZW, 20+: full |
321
-
322
- **Note**: For maximum compatibility across all runtimes, use PNG, BMP, GIF,
323
- ASCII or RAW formats which have complete pure-JS implementations.
324
-
325
- ## Extending with Custom Formats
326
-
327
- ```ts
328
- import { Image, type ImageData, type ImageFormat } from "@cross/image";
329
-
330
- class MyCustomFormat implements ImageFormat {
331
- readonly name = "custom";
332
- readonly mimeType = "image/custom";
333
-
334
- canDecode(data: Uint8Array): boolean {
335
- // Check if data matches your format
336
- return data[0] === 0x42; // Example magic byte
337
- }
338
-
339
- async decode(data: Uint8Array): Promise<ImageData> {
340
- // Decode your format to RGBA
341
- return {
342
- width: 100,
343
- height: 100,
344
- data: new Uint8Array(100 * 100 * 4),
345
- };
346
- }
347
-
348
- async encode(imageData: ImageData): Promise<Uint8Array> {
349
- // Encode RGBA to your format
350
- return new Uint8Array([0x42 /* ... */]);
351
- }
352
- }
353
-
354
- // Register the format
355
- Image.registerFormat(new MyCustomFormat());
356
-
357
- // Now you can use it
358
- const image = await Image.read(customData, "custom");
359
- const output = await image.save("custom");
360
- ```
361
-
362
- ## API Reference
363
-
364
- ### `Image`
365
-
366
- The main class for working with images.
367
-
368
- #### Static Methods
369
-
370
- - `Image.read(data: Uint8Array, format?: string): Promise<Image>` - Read an
371
- image from bytes
372
- - `Image.readFrames(data: Uint8Array, format?: string): Promise<MultiFrameImageData>` -
373
- Read all frames from a multi-frame image (animated GIF or multi-page TIFF)
374
- - `Image.saveFrames(format: string, imageData: MultiFrameImageData, options?: unknown): Promise<Uint8Array>` -
375
- Save multi-frame image data to bytes in the specified format
376
- - `Image.fromRGBA(width: number, height: number, data: Uint8Array): Image` -
377
- Create an image from raw RGBA data
378
- - `Image.registerFormat(format: ImageFormat): void` - Register a custom format
379
- - `Image.getFormats(): readonly ImageFormat[]` - Get all registered formats
380
-
381
- #### Instance Properties
382
-
383
- - `width: number` - Image width in pixels (read-only)
384
- - `height: number` - Image height in pixels (read-only)
385
- - `data: Uint8Array` - Raw RGBA pixel data (read-only)
386
-
387
- #### Instance Methods
388
-
389
- - `resize(options: ResizeOptions): this` - Resize the image (chainable)
390
- - `save(format: string, options?: unknown): Promise<Uint8Array>` - Save to bytes
391
- in specified format with optional format-specific options
392
- - `clone(): Image` - Create a copy of the image
393
-
394
- ### Types
395
-
396
- #### `ResizeOptions`
397
-
398
- ```ts
399
- interface ResizeOptions {
400
- width: number; // Target width
401
- height: number; // Target height
402
- method?: "nearest" | "bilinear"; // Resize algorithm (default: "bilinear")
403
- }
404
- ```
405
-
406
- #### `ASCIIOptions`
407
-
408
- ```ts
409
- interface ASCIIOptions {
410
- width?: number; // Target width in characters (default: 80)
411
- charset?: "simple" | "extended" | "blocks" | "detailed"; // Character set (default: "simple")
412
- aspectRatio?: number; // Aspect ratio correction for terminal (default: 0.5)
413
- invert?: boolean; // Invert brightness (default: false)
414
- }
415
- ```
416
-
417
- **Character sets:**
418
-
419
- - `simple`: 10 characters (`.:-=+*#%@`) - good for basic art
420
- - `extended`: 70 characters - detailed gradients
421
- - `blocks`: 5 block characters (`░▒▓█`) - smooth gradients
422
- - `detailed`: 92 characters - maximum detail
423
-
424
- **Usage:**
425
-
426
- ```ts
427
- const ascii = await image.save("ascii", {
428
- width: 60,
429
- charset: "blocks",
430
- aspectRatio: 0.5,
431
- invert: false,
432
- });
433
- ```
434
-
435
- #### `WebPEncodeOptions`
436
-
437
- ```ts
438
- interface WebPEncodeOptions {
439
- quality?: number; // Encoding quality 1-100 (default: 90)
440
- lossless?: boolean; // Force lossless encoding (default: false)
441
- }
442
- ```
443
-
444
- **Quality levels:**
445
-
446
- - `100`: Lossless encoding (VP8L without quantization)
447
- - `90-99`: Very high quality with minimal quantization
448
- - `70-89`: High quality with light quantization
449
- - `50-69`: Medium quality with noticeable quantization
450
- - `30-49`: Lower quality with heavy quantization
451
- - `1-29`: Low quality with very heavy quantization
452
-
453
- **Lossless flag:**
454
-
455
- - When `lossless: true`, forces lossless VP8L encoding even if quality < 100
456
- - Useful when you want pure-JS encoding without quantization
457
-
458
- **Usage:**
459
-
460
- ```ts
461
- // Lossless WebP
462
- const lossless = await image.save("webp", { quality: 100 });
463
-
464
- // Lossy WebP with high quality
465
- const lossy = await image.save("webp", { quality: 85 });
466
-
467
- // Force lossless in pure-JS
468
- const forcedLossless = await image.save("webp", {
469
- quality: 80,
470
- lossless: true,
471
- });
472
- ```
473
-
474
- **Note:** When OffscreenCanvas is available (Deno, modern browsers, Bun), the
475
- runtime's native WebP encoder is used for better compression and quality. In
476
- pure-JS mode (Node.js without OffscreenCanvas), VP8L format with quality-based
477
- color quantization is used for lossy encoding.
478
-
479
- #### `TIFFEncodeOptions`
480
-
481
- ```ts
482
- interface TIFFEncodeOptions {
483
- compression?: "none" | "lzw"; // Compression method (default: "none")
484
- }
485
- ```
486
-
487
- **Compression methods:**
488
-
489
- - `none`: Uncompressed TIFF - larger file size, fastest encoding
490
- - `lzw`: LZW compression - smaller file size for most images, lossless
491
-
492
- **Usage:**
493
-
494
- ```ts
495
- // Save as uncompressed TIFF (default)
496
- const uncompressed = await image.save("tiff");
497
-
498
- // Save with LZW compression
499
- const compressed = await image.save("tiff", { compression: "lzw" });
500
- ```
501
-
502
- #### `ImageData`
503
-
504
- ```ts
505
- interface ImageData {
506
- width: number; // Image width in pixels
507
- height: number; // Image height in pixels
508
- data: Uint8Array; // Raw RGBA data (4 bytes per pixel)
509
- metadata?: ImageMetadata; // Optional metadata
510
- }
511
- ```
512
-
513
- #### `MultiFrameImageData`
514
-
515
- ```ts
516
- interface MultiFrameImageData {
517
- width: number; // Canvas width in pixels
518
- height: number; // Canvas height in pixels
519
- frames: ImageFrame[]; // Array of frames
520
- metadata?: ImageMetadata; // Optional global metadata
521
- }
522
- ```
523
-
524
- #### `ImageFrame`
525
-
526
- ```ts
527
- interface ImageFrame {
528
- width: number; // Frame width in pixels
529
- height: number; // Frame height in pixels
530
- data: Uint8Array; // Raw RGBA data (4 bytes per pixel)
531
- frameMetadata?: FrameMetadata; // Optional frame-specific metadata
532
- }
533
- ```
534
-
535
- #### `FrameMetadata`
536
-
537
- ```ts
538
- interface FrameMetadata {
539
- delay?: number; // Frame delay in milliseconds (for animations)
540
- disposal?: "none" | "background" | "previous"; // Frame disposal method
541
- left?: number; // X offset of frame within canvas
542
- top?: number; // Y offset of frame within canvas
543
- }
544
- ```
545
-
546
- #### `ImageFormat`
547
-
548
- ```ts
549
- interface ImageFormat {
550
- readonly name: string; // Format name (e.g., "png")
551
- readonly mimeType: string; // MIME type (e.g., "image/png")
552
- canDecode(data: Uint8Array): boolean; // Check if data is in this format
553
- decode(data: Uint8Array): Promise<ImageData>; // Decode to RGBA
554
- encode(imageData: ImageData): Promise<Uint8Array>; // Encode from RGBA
555
- decodeFrames?(data: Uint8Array): Promise<MultiFrameImageData>; // Decode all frames
556
- encodeFrames?(
557
- imageData: MultiFrameImageData,
558
- options?: unknown,
559
- ): Promise<Uint8Array>; // Encode multi-frame
560
- supportsMultipleFrames?(): boolean; // Check if format supports multiple frames
561
- }
562
- ```
563
-
564
- ## Runtime Compatibility
565
-
566
- - **Deno 2.x** - Full support for all formats
567
- - **Node.js 18+** - Full support with pure-JS fallbacks for formats without
568
- ImageDecoder
569
- - **Node.js 20+** - Full support including ImageDecoder API for all formats
570
- - **Bun** - Full support for all formats
571
-
572
- The library automatically selects the best available implementation:
573
-
574
- 1. Pure-JS decoders/encoders are tried first when available
575
- 2. Native APIs (ImageDecoder, OffscreenCanvas) are used as fallbacks or for
576
- formats without pure-JS support
577
- 3. Graceful degradation ensures maximum compatibility across runtimes
66
+ | Format | Pure-JS | Notes |
67
+ | ------ | ----------- | ------------------------------- |
68
+ | PNG | Full | Complete pure-JS implementation |
69
+ | BMP | ✅ Full | Complete pure-JS implementation |
70
+ | GIF | ✅ Full | Complete pure-JS implementation |
71
+ | RAW | Full | Uncompressed RGBA |
72
+ | ASCII | Full | Text-based ASCII art |
73
+ | JPEG | ⚠️ Baseline | Pure-JS baseline DCT only |
74
+ | WebP | ⚠️ Lossless | Pure-JS lossless VP8L |
75
+ | TIFF | ⚠️ Basic | Pure-JS uncompressed + LZW |
76
+
77
+ See the
78
+ [full format support documentation](https://cross-org.github.io/image/formats.html)
79
+ for detailed compatibility information.
80
+
81
+ ## Documentation
82
+
83
+ - **[API Reference](https://cross-org.github.io/image/api.html)** - Complete API
84
+ documentation
85
+ - **[Examples](https://cross-org.github.io/image/examples.html)** - Usage
86
+ examples for common tasks
87
+ - **[Format Support](https://cross-org.github.io/image/formats.html)** -
88
+ Supported formats and specifications
89
+ - **[JPEG Implementation](https://cross-org.github.io/image/jpeg-implementation.html)** -
90
+ Technical details for JPEG
91
+ - **[WebP Implementation](https://cross-org.github.io/image/webp-implementation.html)** -
92
+ Technical details for WebP
578
93
 
579
94
  ## Development
580
95
 
@@ -17,7 +17,18 @@ export declare class ASCIIFormat implements ImageFormat {
17
17
  private readonly MAGIC_BYTES;
18
18
  private readonly CHARSETS;
19
19
  canDecode(data: Uint8Array): boolean;
20
+ /**
21
+ * Decode ASCII art to a basic grayscale RGBA image
22
+ * @param data Raw ASCII art data
23
+ * @returns Decoded image data with grayscale RGBA pixels
24
+ */
20
25
  decode(data: Uint8Array): Promise<ImageData>;
26
+ /**
27
+ * Encode RGBA image data to ASCII art
28
+ * @param imageData Image data to encode
29
+ * @param options Optional ASCII encoding options
30
+ * @returns Encoded ASCII art as UTF-8 bytes
31
+ */
21
32
  encode(imageData: ImageData, options?: ASCIIOptions): Promise<Uint8Array>;
22
33
  /**
23
34
  * Parse options from the options line
@@ -1,3 +1,4 @@
1
+ import { validateImageDimensions } from "../utils/security.js";
1
2
  /**
2
3
  * ASCII format handler
3
4
  * Converts images to ASCII art text representation
@@ -62,6 +63,11 @@ export class ASCIIFormat {
62
63
  data[4] === this.MAGIC_BYTES[4] &&
63
64
  data[5] === this.MAGIC_BYTES[5];
64
65
  }
66
+ /**
67
+ * Decode ASCII art to a basic grayscale RGBA image
68
+ * @param data Raw ASCII art data
69
+ * @returns Decoded image data with grayscale RGBA pixels
70
+ */
65
71
  decode(data) {
66
72
  if (!this.canDecode(data)) {
67
73
  throw new Error("Invalid ASCII art signature");
@@ -83,6 +89,8 @@ export class ASCIIFormat {
83
89
  // Calculate dimensions
84
90
  const height = artLines.length;
85
91
  const width = Math.max(...artLines.map((line) => line.length));
92
+ // Validate dimensions for security (prevent integer overflow and heap exhaustion)
93
+ validateImageDimensions(width, height);
86
94
  // Convert ASCII art back to image data
87
95
  const imageData = new Uint8Array(width * height * 4);
88
96
  const charset = this.CHARSETS[options.charset] || this.CHARSETS.simple;
@@ -111,6 +119,12 @@ export class ASCIIFormat {
111
119
  }
112
120
  return Promise.resolve({ width, height, data: imageData });
113
121
  }
122
+ /**
123
+ * Encode RGBA image data to ASCII art
124
+ * @param imageData Image data to encode
125
+ * @param options Optional ASCII encoding options
126
+ * @returns Encoded ASCII art as UTF-8 bytes
127
+ */
114
128
  encode(imageData, options = {}) {
115
129
  const { width: targetWidth = 80, charset = "simple", aspectRatio = 0.5, invert = false, } = options;
116
130
  // Get character set
@@ -4,10 +4,27 @@ import type { ImageData, ImageFormat } from "../types.js";
4
4
  * Implements a pure JavaScript BMP decoder and encoder
5
5
  */
6
6
  export declare class BMPFormat implements ImageFormat {
7
+ /** Format name identifier */
7
8
  readonly name = "bmp";
9
+ /** MIME type for BMP images */
8
10
  readonly mimeType = "image/bmp";
11
+ /**
12
+ * Check if the given data is a BMP image
13
+ * @param data Raw image data to check
14
+ * @returns true if data has BMP signature
15
+ */
9
16
  canDecode(data: Uint8Array): boolean;
17
+ /**
18
+ * Decode BMP image data to RGBA
19
+ * @param data Raw BMP image data
20
+ * @returns Decoded image data with RGBA pixels
21
+ */
10
22
  decode(data: Uint8Array): Promise<ImageData>;
23
+ /**
24
+ * Encode RGBA image data to BMP format
25
+ * @param imageData Image data to encode
26
+ * @returns Encoded BMP image bytes
27
+ */
11
28
  encode(imageData: ImageData): Promise<Uint8Array>;
12
29
  private readUint16LE;
13
30
  private readUint32LE;