cross-image 0.1.2 → 0.1.5

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 (88) hide show
  1. package/README.md +143 -606
  2. package/esm/mod.d.ts +5 -5
  3. package/esm/mod.js +5 -5
  4. package/esm/src/formats/ascii.d.ts +11 -0
  5. package/esm/src/formats/ascii.js +14 -0
  6. package/esm/src/formats/bmp.d.ts +17 -0
  7. package/esm/src/formats/bmp.js +20 -0
  8. package/esm/src/formats/gif.d.ts +21 -0
  9. package/esm/src/formats/gif.js +26 -0
  10. package/esm/src/formats/jpeg.d.ts +17 -0
  11. package/esm/src/formats/jpeg.js +20 -0
  12. package/esm/src/formats/png.d.ts +17 -0
  13. package/esm/src/formats/png.js +20 -0
  14. package/esm/src/formats/raw.d.ts +17 -0
  15. package/esm/src/formats/raw.js +20 -0
  16. package/esm/src/formats/tiff.d.ts +16 -0
  17. package/esm/src/formats/tiff.js +37 -12
  18. package/esm/src/formats/webp.d.ts +18 -0
  19. package/esm/src/formats/webp.js +21 -0
  20. package/esm/src/image.d.ts +34 -1
  21. package/esm/src/image.js +55 -9
  22. package/esm/src/utils/security.d.ts +28 -0
  23. package/esm/src/utils/security.js +48 -0
  24. package/esm/src/utils/webp_decoder.js +3 -0
  25. package/package.json +1 -4
  26. package/script/mod.d.ts +5 -5
  27. package/script/mod.js +5 -5
  28. package/script/src/formats/ascii.d.ts +11 -0
  29. package/script/src/formats/ascii.js +14 -0
  30. package/script/src/formats/bmp.d.ts +17 -0
  31. package/script/src/formats/bmp.js +20 -0
  32. package/script/src/formats/gif.d.ts +21 -0
  33. package/script/src/formats/gif.js +26 -0
  34. package/script/src/formats/jpeg.d.ts +17 -0
  35. package/script/src/formats/jpeg.js +20 -0
  36. package/script/src/formats/png.d.ts +17 -0
  37. package/script/src/formats/png.js +20 -0
  38. package/script/src/formats/raw.d.ts +17 -0
  39. package/script/src/formats/raw.js +20 -0
  40. package/script/src/formats/tiff.d.ts +16 -0
  41. package/script/src/formats/tiff.js +37 -12
  42. package/script/src/formats/webp.d.ts +18 -0
  43. package/script/src/formats/webp.js +21 -0
  44. package/script/src/image.d.ts +34 -1
  45. package/script/src/image.js +55 -9
  46. package/script/src/utils/security.d.ts +28 -0
  47. package/script/src/utils/security.js +53 -0
  48. package/script/src/utils/webp_decoder.js +3 -0
  49. package/esm/mod.d.ts.map +0 -1
  50. package/esm/src/formats/ascii.d.ts.map +0 -1
  51. package/esm/src/formats/bmp.d.ts.map +0 -1
  52. package/esm/src/formats/gif.d.ts.map +0 -1
  53. package/esm/src/formats/jpeg.d.ts.map +0 -1
  54. package/esm/src/formats/png.d.ts.map +0 -1
  55. package/esm/src/formats/raw.d.ts.map +0 -1
  56. package/esm/src/formats/tiff.d.ts.map +0 -1
  57. package/esm/src/formats/webp.d.ts.map +0 -1
  58. package/esm/src/image.d.ts.map +0 -1
  59. package/esm/src/types.d.ts.map +0 -1
  60. package/esm/src/utils/gif_decoder.d.ts.map +0 -1
  61. package/esm/src/utils/gif_encoder.d.ts.map +0 -1
  62. package/esm/src/utils/jpeg_decoder.d.ts.map +0 -1
  63. package/esm/src/utils/jpeg_encoder.d.ts.map +0 -1
  64. package/esm/src/utils/lzw.d.ts.map +0 -1
  65. package/esm/src/utils/resize.d.ts.map +0 -1
  66. package/esm/src/utils/tiff_lzw.d.ts.map +0 -1
  67. package/esm/src/utils/webp_decoder.d.ts.map +0 -1
  68. package/esm/src/utils/webp_encoder.d.ts.map +0 -1
  69. package/script/mod.d.ts.map +0 -1
  70. package/script/src/formats/ascii.d.ts.map +0 -1
  71. package/script/src/formats/bmp.d.ts.map +0 -1
  72. package/script/src/formats/gif.d.ts.map +0 -1
  73. package/script/src/formats/jpeg.d.ts.map +0 -1
  74. package/script/src/formats/png.d.ts.map +0 -1
  75. package/script/src/formats/raw.d.ts.map +0 -1
  76. package/script/src/formats/tiff.d.ts.map +0 -1
  77. package/script/src/formats/webp.d.ts.map +0 -1
  78. package/script/src/image.d.ts.map +0 -1
  79. package/script/src/types.d.ts.map +0 -1
  80. package/script/src/utils/gif_decoder.d.ts.map +0 -1
  81. package/script/src/utils/gif_encoder.d.ts.map +0 -1
  82. package/script/src/utils/jpeg_decoder.d.ts.map +0 -1
  83. package/script/src/utils/jpeg_encoder.d.ts.map +0 -1
  84. package/script/src/utils/lzw.d.ts.map +0 -1
  85. package/script/src/utils/resize.d.ts.map +0 -1
  86. package/script/src/utils/tiff_lzw.d.ts.map +0 -1
  87. package/script/src/utils/webp_decoder.d.ts.map +0 -1
  88. package/script/src/utils/webp_encoder.d.ts.map +0 -1
package/README.md CHANGED
@@ -1,606 +1,143 @@
1
- # @cross/image
2
-
3
- A pure JavaScript, dependency-free, cross-runtime image processing library for
4
- Deno, Node.js, and Bun.
5
-
6
- 📚 **[Full Documentation](https://cross-org.github.io/image/)**
7
-
8
- ## Features
9
-
10
- - 🚀 **Pure JavaScript** - No native dependencies
11
- - 🔌 **Pluggable formats** - Easy to extend with custom formats
12
- - 📦 **Cross-runtime** - Works on Deno, Node.js (18+), and Bun
13
- - 🎨 **Multiple formats** - PNG, JPEG, WebP, GIF, TIFF, BMP, and RAW support
14
- - ✂️ **Image manipulation** - Resize with multiple algorithms
15
- - 🔧 **Simple API** - Easy to use, intuitive interface
16
-
17
- ## Installation
18
-
19
- ### Deno
20
-
21
- ```ts
22
- import { Image } from "jsr:@cross/image";
23
- ```
24
-
25
- ### Node.js
26
-
27
- ```bash
28
- npm install cross-image
29
- ```
30
-
31
- ```ts
32
- import { Image } from "cross-image";
33
- ```
34
-
35
- ### Bun
36
-
37
- ```bash
38
- bun add cross-image
39
- ```
40
-
41
- ```ts
42
- import { Image } from "cross-image";
43
- ```
44
-
45
- ## Quick Start
46
-
47
- ### Reading and Saving Images
48
-
49
- ```ts
50
- import { Image } from "@cross/image";
51
-
52
- // Read an image (auto-detects format)
53
- const data = await Deno.readFile("input.png");
54
- const image = await Image.read(data);
55
-
56
- console.log(`Image size: ${image.width}x${image.height}`);
57
-
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)
72
- image.resize({ width: 800, height: 600 });
73
-
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);
234
- ```
235
-
236
- ## Supported Formats
237
-
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
578
-
579
- ## Development
580
-
581
- ### Running Tests
582
-
583
- ```bash
584
- deno test -A
585
- ```
586
-
587
- ### Linting and Formatting
588
-
589
- ```bash
590
- deno fmt --check
591
- deno lint
592
- ```
593
-
594
- ### Type Checking
595
-
596
- ```bash
597
- deno check mod.ts
598
- ```
599
-
600
- ## License
601
-
602
- MIT License - see LICENSE file for details.
603
-
604
- ## Contributing
605
-
606
- Contributions are welcome! Please feel free to submit a Pull Request.
1
+ # @cross/image
2
+
3
+ A pure JavaScript, dependency-free, cross-runtime image processing library for
4
+ Deno, Node.js, and Bun.
5
+
6
+ 📚 **[Full Documentation](https://cross-org.github.io/image/)**
7
+
8
+ ## Features
9
+
10
+ - 🚀 **Pure JavaScript** - No native dependencies
11
+ - 🔌 **Pluggable formats** - Easy to extend with custom formats
12
+ - 📦 **Cross-runtime** - Works on Deno, Node.js (18+), and Bun
13
+ - 🎨 **Multiple formats** - PNG, JPEG, WebP, GIF, TIFF, BMP, and RAW support
14
+ - ✂️ **Image manipulation** - Resize with multiple algorithms
15
+ - 🔧 **Simple API** - Easy to use, intuitive interface
16
+
17
+ ## Installation
18
+
19
+ ### Deno
20
+
21
+ ```ts
22
+ import { Image } from "jsr:@cross/image";
23
+ ```
24
+
25
+ ### Node.js
26
+
27
+ ```bash
28
+ npx jsr add @cross/image
29
+ ```
30
+
31
+ ```ts
32
+ import { Image } from "@cross/image";
33
+ ```
34
+
35
+ ### Bun
36
+
37
+ ```bash
38
+ bunx jsr add @cross/image
39
+ ```
40
+
41
+ ```ts
42
+ import { Image } from "@cross/image";
43
+ ```
44
+
45
+ ## Quick Start
46
+
47
+ ### Deno
48
+
49
+ ```ts
50
+ import { Image } from "@cross/image";
51
+
52
+ // Decode an image (auto-detects format)
53
+ const data = await Deno.readFile("input.png");
54
+ const image = await Image.decode(data);
55
+
56
+ console.log(`Image size: ${image.width}x${image.height}`);
57
+
58
+ // Resize the image
59
+ image.resize({ width: 800, height: 600 });
60
+
61
+ // Encode in a different format
62
+ const jpeg = await image.encode("jpeg");
63
+ await Deno.writeFile("output.jpg", jpeg);
64
+ ```
65
+
66
+ ### Node.js
67
+
68
+ ```ts
69
+ import { Image } from "cross-image";
70
+ import { readFile, writeFile } from "node:fs/promises";
71
+
72
+ // Read an image (auto-detects format)
73
+ const data = await readFile("input.png");
74
+ const image = await Image.read(data);
75
+
76
+ console.log(`Image size: ${image.width}x${image.height}`);
77
+
78
+ // Resize the image
79
+ image.resize({ width: 800, height: 600 });
80
+
81
+ // Save in a different format
82
+ const jpeg = await image.save("jpeg");
83
+ await writeFile("output.jpg", jpeg);
84
+ ```
85
+
86
+ ## Supported Formats
87
+
88
+ | Format | Pure-JS | Notes |
89
+ | ------ | ----------- | ------------------------------- |
90
+ | PNG | Full | Complete pure-JS implementation |
91
+ | BMP | ✅ Full | Complete pure-JS implementation |
92
+ | GIF | Full | Complete pure-JS implementation |
93
+ | RAW | Full | Uncompressed RGBA |
94
+ | ASCII | Full | Text-based ASCII art |
95
+ | JPEG | ⚠️ Baseline | Pure-JS baseline DCT only |
96
+ | WebP | ⚠️ Lossless | Pure-JS lossless VP8L |
97
+ | TIFF | ⚠️ Basic | Pure-JS uncompressed + LZW |
98
+
99
+ See the
100
+ [full format support documentation](https://cross-org.github.io/image/formats.html)
101
+ for detailed compatibility information.
102
+
103
+ ## Documentation
104
+
105
+ - **[API Reference](https://cross-org.github.io/image/api.html)** - Complete API
106
+ documentation
107
+ - **[Examples](https://cross-org.github.io/image/examples.html)** - Usage
108
+ examples for common tasks
109
+ - **[Format Support](https://cross-org.github.io/image/formats.html)** -
110
+ Supported formats and specifications
111
+ - **[JPEG Implementation](https://cross-org.github.io/image/jpeg-implementation.html)** -
112
+ Technical details for JPEG
113
+ - **[WebP Implementation](https://cross-org.github.io/image/webp-implementation.html)** -
114
+ Technical details for WebP
115
+
116
+ ## Development
117
+
118
+ ### Running Tests
119
+
120
+ ```bash
121
+ deno test -A
122
+ ```
123
+
124
+ ### Linting and Formatting
125
+
126
+ ```bash
127
+ deno fmt --check
128
+ deno lint
129
+ ```
130
+
131
+ ### Type Checking
132
+
133
+ ```bash
134
+ deno check mod.ts
135
+ ```
136
+
137
+ ## License
138
+
139
+ MIT License - see LICENSE file for details.
140
+
141
+ ## Contributing
142
+
143
+ Contributions are welcome! Please feel free to submit a Pull Request.