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.
- package/README.md +35 -520
- package/esm/src/formats/ascii.d.ts +11 -0
- package/esm/src/formats/ascii.js +14 -0
- package/esm/src/formats/bmp.d.ts +17 -0
- package/esm/src/formats/bmp.js +20 -0
- package/esm/src/formats/gif.d.ts +21 -0
- package/esm/src/formats/gif.js +26 -0
- package/esm/src/formats/jpeg.d.ts +17 -0
- package/esm/src/formats/jpeg.js +20 -0
- package/esm/src/formats/png.d.ts +17 -0
- package/esm/src/formats/png.js +20 -0
- package/esm/src/formats/raw.d.ts +17 -0
- package/esm/src/formats/raw.js +20 -0
- package/esm/src/formats/tiff.d.ts +16 -0
- package/esm/src/formats/tiff.js +37 -12
- package/esm/src/formats/webp.d.ts +18 -0
- package/esm/src/formats/webp.js +21 -0
- package/esm/src/image.js +5 -0
- package/esm/src/utils/security.d.ts +28 -0
- package/esm/src/utils/security.js +48 -0
- package/esm/src/utils/webp_decoder.js +3 -0
- package/package.json +1 -4
- package/script/src/formats/ascii.d.ts +11 -0
- package/script/src/formats/ascii.js +14 -0
- package/script/src/formats/bmp.d.ts +17 -0
- package/script/src/formats/bmp.js +20 -0
- package/script/src/formats/gif.d.ts +21 -0
- package/script/src/formats/gif.js +26 -0
- package/script/src/formats/jpeg.d.ts +17 -0
- package/script/src/formats/jpeg.js +20 -0
- package/script/src/formats/png.d.ts +17 -0
- package/script/src/formats/png.js +20 -0
- package/script/src/formats/raw.d.ts +17 -0
- package/script/src/formats/raw.js +20 -0
- package/script/src/formats/tiff.d.ts +16 -0
- package/script/src/formats/tiff.js +37 -12
- package/script/src/formats/webp.d.ts +18 -0
- package/script/src/formats/webp.js +21 -0
- package/script/src/image.js +5 -0
- package/script/src/utils/security.d.ts +28 -0
- package/script/src/utils/security.js +53 -0
- package/script/src/utils/webp_decoder.js +3 -0
- package/esm/mod.d.ts.map +0 -1
- package/esm/src/formats/ascii.d.ts.map +0 -1
- package/esm/src/formats/bmp.d.ts.map +0 -1
- package/esm/src/formats/gif.d.ts.map +0 -1
- package/esm/src/formats/jpeg.d.ts.map +0 -1
- package/esm/src/formats/png.d.ts.map +0 -1
- package/esm/src/formats/raw.d.ts.map +0 -1
- package/esm/src/formats/tiff.d.ts.map +0 -1
- package/esm/src/formats/webp.d.ts.map +0 -1
- package/esm/src/image.d.ts.map +0 -1
- package/esm/src/types.d.ts.map +0 -1
- package/esm/src/utils/gif_decoder.d.ts.map +0 -1
- package/esm/src/utils/gif_encoder.d.ts.map +0 -1
- package/esm/src/utils/jpeg_decoder.d.ts.map +0 -1
- package/esm/src/utils/jpeg_encoder.d.ts.map +0 -1
- package/esm/src/utils/lzw.d.ts.map +0 -1
- package/esm/src/utils/resize.d.ts.map +0 -1
- package/esm/src/utils/tiff_lzw.d.ts.map +0 -1
- package/esm/src/utils/webp_decoder.d.ts.map +0 -1
- package/esm/src/utils/webp_encoder.d.ts.map +0 -1
- package/script/mod.d.ts.map +0 -1
- package/script/src/formats/ascii.d.ts.map +0 -1
- package/script/src/formats/bmp.d.ts.map +0 -1
- package/script/src/formats/gif.d.ts.map +0 -1
- package/script/src/formats/jpeg.d.ts.map +0 -1
- package/script/src/formats/png.d.ts.map +0 -1
- package/script/src/formats/raw.d.ts.map +0 -1
- package/script/src/formats/tiff.d.ts.map +0 -1
- package/script/src/formats/webp.d.ts.map +0 -1
- package/script/src/image.d.ts.map +0 -1
- package/script/src/types.d.ts.map +0 -1
- package/script/src/utils/gif_decoder.d.ts.map +0 -1
- package/script/src/utils/gif_encoder.d.ts.map +0 -1
- package/script/src/utils/jpeg_decoder.d.ts.map +0 -1
- package/script/src/utils/jpeg_encoder.d.ts.map +0 -1
- package/script/src/utils/lzw.d.ts.map +0 -1
- package/script/src/utils/resize.d.ts.map +0 -1
- package/script/src/utils/tiff_lzw.d.ts.map +0 -1
- package/script/src/utils/webp_decoder.d.ts.map +0 -1
- 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
|
-
|
|
28
|
+
npx jsr add @cross/image
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
```ts
|
|
32
|
-
import { Image } from "cross
|
|
32
|
+
import { Image } from "@cross/image";
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
### Bun
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
|
|
38
|
+
bunx jsr add @cross/image
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
```ts
|
|
42
|
-
import { Image } from "cross
|
|
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
|
-
//
|
|
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
|
-
//
|
|
75
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
|
244
|
-
|
|
|
245
|
-
|
|
|
246
|
-
|
|
|
247
|
-
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
-
|
|
258
|
-
|
|
259
|
-
- **
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
package/esm/src/formats/ascii.js
CHANGED
|
@@ -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
|
package/esm/src/formats/bmp.d.ts
CHANGED
|
@@ -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;
|