cross-image 0.1.4 → 0.2.0
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 +155 -121
- package/esm/mod.d.ts +32 -8
- package/esm/mod.js +32 -8
- package/esm/src/formats/dng.d.ts +27 -0
- package/esm/src/formats/dng.js +191 -0
- package/esm/src/formats/pam.d.ts +43 -0
- package/esm/src/formats/pam.js +177 -0
- package/esm/src/formats/pcx.d.ts +13 -0
- package/esm/src/formats/pcx.js +204 -0
- package/esm/src/formats/tiff.d.ts +7 -7
- package/esm/src/image.d.ts +133 -1
- package/esm/src/image.js +250 -11
- package/esm/src/utils/image_processing.d.ts +91 -0
- package/esm/src/utils/image_processing.js +231 -0
- package/esm/src/utils/webp_decoder.js +47 -12
- package/esm/src/utils/webp_encoder.js +97 -39
- package/package.json +4 -1
- package/script/mod.d.ts +32 -8
- package/script/mod.js +36 -10
- package/script/src/formats/dng.d.ts +27 -0
- package/script/src/formats/dng.js +195 -0
- package/script/src/formats/pam.d.ts +43 -0
- package/script/src/formats/pam.js +181 -0
- package/script/src/formats/pcx.d.ts +13 -0
- package/script/src/formats/pcx.js +208 -0
- package/script/src/formats/tiff.d.ts +7 -7
- package/script/src/image.d.ts +133 -1
- package/script/src/image.js +250 -11
- package/script/src/utils/image_processing.d.ts +91 -0
- package/script/src/utils/image_processing.js +242 -0
- package/script/src/utils/webp_decoder.js +47 -12
- package/script/src/utils/webp_encoder.js +97 -39
- package/esm/src/formats/raw.d.ts +0 -40
- package/esm/src/formats/raw.js +0 -118
- package/script/src/formats/raw.d.ts +0 -40
- package/script/src/formats/raw.js +0 -122
|
@@ -51,6 +51,22 @@ class HuffmanTable {
|
|
|
51
51
|
this.singleSymbol = symbol;
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
|
+
// Build Huffman tree
|
|
55
|
+
// Note: WebP uses LSB-first bit packing for the bitstream, but Huffman codes
|
|
56
|
+
// are typically described MSB-first. However, the spec says:
|
|
57
|
+
// "The bits of the code are read from the stream LSB first."
|
|
58
|
+
// This means if code is 01 (binary), we read 1 then 0?
|
|
59
|
+
// Actually, standard canonical Huffman codes are read MSB to LSB from the stream.
|
|
60
|
+
// But WebP bit reader reads LSB to MSB from bytes.
|
|
61
|
+
// Let's check the spec carefully.
|
|
62
|
+
// "The bits of the code are read from the stream LSB first."
|
|
63
|
+
// This usually means the bit reader returns bits in order.
|
|
64
|
+
// If we have code 0x2 (binary 10) with length 2.
|
|
65
|
+
// If we write it MSB first: 1, then 0.
|
|
66
|
+
// If we read it: readBits(1) -> 1, readBits(1) -> 0.
|
|
67
|
+
// This matches how we build the tree (left=0, right=1).
|
|
68
|
+
// Wait, addCode uses (code >> i) & 1. This is MSB first.
|
|
69
|
+
// So we expect the first bit read to be the MSB of the code.
|
|
54
70
|
let node = this.root;
|
|
55
71
|
for (let i = codeLength - 1; i >= 0; i--) {
|
|
56
72
|
const bit = (code >> i) & 1;
|
|
@@ -77,6 +93,7 @@ class HuffmanTable {
|
|
|
77
93
|
const bit = reader.readBits(1);
|
|
78
94
|
node = bit === 0 ? node.left : node.right;
|
|
79
95
|
if (!node) {
|
|
96
|
+
// console.log("Invalid Huffman code - walked off tree");
|
|
80
97
|
throw new Error("Invalid Huffman code");
|
|
81
98
|
}
|
|
82
99
|
}
|
|
@@ -111,21 +128,21 @@ class BitReader {
|
|
|
111
128
|
});
|
|
112
129
|
this.data = data;
|
|
113
130
|
this.pos = offset;
|
|
114
|
-
this.bitPos =
|
|
131
|
+
this.bitPos = 8; // Start at 8 to trigger first byte read
|
|
115
132
|
this.value = 0;
|
|
116
133
|
}
|
|
117
134
|
readBits(numBits) {
|
|
118
135
|
let result = 0;
|
|
119
136
|
for (let i = 0; i < numBits; i++) {
|
|
120
|
-
if (this.bitPos ===
|
|
137
|
+
if (this.bitPos === 8) {
|
|
121
138
|
if (this.pos >= this.data.length) {
|
|
122
139
|
throw new Error("Unexpected end of data");
|
|
123
140
|
}
|
|
124
141
|
this.value = this.data[this.pos++];
|
|
125
|
-
this.bitPos =
|
|
142
|
+
this.bitPos = 0;
|
|
126
143
|
}
|
|
127
|
-
result |= ((this.value >>
|
|
128
|
-
this.bitPos
|
|
144
|
+
result |= ((this.value >> this.bitPos) & 1) << i;
|
|
145
|
+
this.bitPos++;
|
|
129
146
|
}
|
|
130
147
|
return result;
|
|
131
148
|
}
|
|
@@ -135,8 +152,8 @@ class BitReader {
|
|
|
135
152
|
// Read bytes aligned to byte boundary
|
|
136
153
|
readBytes(count) {
|
|
137
154
|
// Align to byte boundary
|
|
138
|
-
if (this.bitPos !==
|
|
139
|
-
this.bitPos =
|
|
155
|
+
if (this.bitPos !== 8) {
|
|
156
|
+
this.bitPos = 8; // Skip remaining bits in current byte
|
|
140
157
|
}
|
|
141
158
|
if (this.pos + count > this.data.length) {
|
|
142
159
|
throw new Error("Unexpected end of data");
|
|
@@ -334,8 +351,6 @@ export class WebPDecoder {
|
|
|
334
351
|
}
|
|
335
352
|
// Read the main Huffman codes
|
|
336
353
|
// There are 5 Huffman code groups: green, red, blue, alpha, distance
|
|
337
|
-
// But we read 4 + optional distance code
|
|
338
|
-
const numCodeGroups = reader.readBits(4) + 4;
|
|
339
354
|
const tables = {
|
|
340
355
|
green: new HuffmanTable(),
|
|
341
356
|
red: new HuffmanTable(),
|
|
@@ -350,7 +365,7 @@ export class WebPDecoder {
|
|
|
350
365
|
tables.alpha,
|
|
351
366
|
tables.distance,
|
|
352
367
|
];
|
|
353
|
-
for (let i = 0; i <
|
|
368
|
+
for (let i = 0; i < 5; i++) {
|
|
354
369
|
this.readHuffmanCode(reader, tableArray[i], useColorCache, colorCacheBits, i === 0);
|
|
355
370
|
}
|
|
356
371
|
return tables;
|
|
@@ -364,8 +379,8 @@ export class WebPDecoder {
|
|
|
364
379
|
const symbols = [];
|
|
365
380
|
for (let i = 0; i < numSymbols; i++) {
|
|
366
381
|
const symbolBits = isFirstEightBits
|
|
367
|
-
?
|
|
368
|
-
: reader.readBits(
|
|
382
|
+
? reader.readBits(8)
|
|
383
|
+
: reader.readBits(1);
|
|
369
384
|
symbols.push(symbolBits);
|
|
370
385
|
}
|
|
371
386
|
// Build simple Huffman table
|
|
@@ -417,6 +432,12 @@ export class WebPDecoder {
|
|
|
417
432
|
for (let i = 0; i < numCodeLengthCodes; i++) {
|
|
418
433
|
codeLengthCodeLengths[codeLengthCodeOrder[i]] = reader.readBits(3);
|
|
419
434
|
}
|
|
435
|
+
// Read max_symbol (trimmed length indicator)
|
|
436
|
+
// If 1, we read n_bit and then n_bit bits for max_symbol?
|
|
437
|
+
// Subagent said "write_trimmed_length".
|
|
438
|
+
// If 0, we don't trim.
|
|
439
|
+
// We just read 1 bit and ignore it for now (assuming 0).
|
|
440
|
+
const _trimmed = reader.readBits(1);
|
|
420
441
|
// Build code length Huffman table
|
|
421
442
|
const codeLengthTable = new HuffmanTable();
|
|
422
443
|
this.buildHuffmanTable(codeLengthTable, codeLengthCodeLengths);
|
|
@@ -451,6 +472,20 @@ export class WebPDecoder {
|
|
|
451
472
|
return codeLengths;
|
|
452
473
|
}
|
|
453
474
|
buildHuffmanTable(table, codeLengths) {
|
|
475
|
+
// Check for single symbol optimization (VP8L specific)
|
|
476
|
+
let nonZeroCount = 0;
|
|
477
|
+
let singleSymbol = -1;
|
|
478
|
+
for (let i = 0; i < codeLengths.length; i++) {
|
|
479
|
+
if (codeLengths[i] > 0) {
|
|
480
|
+
nonZeroCount++;
|
|
481
|
+
singleSymbol = i;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (nonZeroCount === 1) {
|
|
485
|
+
// If only one symbol, it has 0 length in the bitstream
|
|
486
|
+
table.addCode(singleSymbol, 0, 0);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
454
489
|
// Build canonical Huffman codes
|
|
455
490
|
const maxCodeLength = Math.max(...codeLengths);
|
|
456
491
|
const lengthCounts = new Array(maxCodeLength + 1).fill(0);
|
|
@@ -55,30 +55,30 @@ class BitWriter {
|
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
writeBits(value, numBits) {
|
|
58
|
-
// Pack bits
|
|
59
|
-
// The decoder reads from MSB to LSB of each byte
|
|
60
|
-
// So we write from MSB down as well
|
|
58
|
+
// Pack bits LSB first (standard WebP/VP8L order)
|
|
61
59
|
for (let i = 0; i < numBits; i++) {
|
|
62
60
|
const bit = (value >> i) & 1;
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
this.bits = 0; // Start new byte
|
|
61
|
+
// If we've filled the current byte, push it and start a new one
|
|
62
|
+
if (this.bitCount > 0 && this.bitCount % 8 === 0) {
|
|
63
|
+
this.bytes.push(this.bits);
|
|
64
|
+
this.bits = 0;
|
|
68
65
|
}
|
|
69
|
-
const bitPos =
|
|
66
|
+
const bitPos = this.bitCount % 8;
|
|
70
67
|
this.bits |= bit << bitPos;
|
|
71
68
|
this.bitCount++;
|
|
72
|
-
if (this.bitCount % 8 === 0) {
|
|
73
|
-
this.bytes.push(this.bits);
|
|
74
|
-
}
|
|
75
69
|
}
|
|
76
70
|
}
|
|
77
71
|
flush() {
|
|
78
72
|
if (this.bitCount % 8 !== 0) {
|
|
79
73
|
this.bytes.push(this.bits);
|
|
80
74
|
this.bits = 0;
|
|
81
|
-
|
|
75
|
+
// Do not reset bitCount here as it tracks total bits written
|
|
76
|
+
}
|
|
77
|
+
else if (this.bitCount > 0 && this.bytes.length * 8 < this.bitCount) {
|
|
78
|
+
// Edge case: if we just finished a byte but haven't pushed it yet
|
|
79
|
+
// (The loop pushes at the START of the next bit, so we might have a full byte pending)
|
|
80
|
+
this.bytes.push(this.bits);
|
|
81
|
+
this.bits = 0;
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
getBytes() {
|
|
@@ -241,10 +241,6 @@ export class WebPEncoder {
|
|
|
241
241
|
writer.writeBits(0, 1);
|
|
242
242
|
// No meta Huffman codes
|
|
243
243
|
writer.writeBits(0, 1);
|
|
244
|
-
// Number of code groups: Always 5 (green, red, blue, alpha, distance)
|
|
245
|
-
// Even without LZ77, we must provide all 5 Huffman codes
|
|
246
|
-
const numCodeGroups = 5;
|
|
247
|
-
writer.writeBits(numCodeGroups - 4, 4); // 1 means 5 groups
|
|
248
244
|
// Apply quantization if quality < 100
|
|
249
245
|
const encodingData = this.quantizeImageData();
|
|
250
246
|
// Collect symbol frequencies for each channel
|
|
@@ -353,13 +349,13 @@ export class WebPEncoder {
|
|
|
353
349
|
if (symbols.length === 1) {
|
|
354
350
|
// Single symbol
|
|
355
351
|
writer.writeBits(0, 1); // num_symbols = 1 (0 + 1)
|
|
356
|
-
writer.writeBits(
|
|
352
|
+
writer.writeBits(1, 1); // is_first_8bits = 1 (use 8 bits)
|
|
357
353
|
writer.writeBits(symbols[0], 8); // symbol
|
|
358
354
|
}
|
|
359
355
|
else if (symbols.length === 2) {
|
|
360
356
|
// Two symbols
|
|
361
357
|
writer.writeBits(1, 1); // num_symbols = 2 (1 + 1)
|
|
362
|
-
writer.writeBits(
|
|
358
|
+
writer.writeBits(1, 1); // is_first_8bits = 1 (use 8 bits)
|
|
363
359
|
writer.writeBits(symbols[0], 8); // first symbol
|
|
364
360
|
writer.writeBits(symbols[1], 8); // second symbol
|
|
365
361
|
}
|
|
@@ -378,9 +374,11 @@ export class WebPEncoder {
|
|
|
378
374
|
const symbols = Array.from(frequencies.keys()).sort((a, b) => a - b);
|
|
379
375
|
if (symbols.length === 0)
|
|
380
376
|
return codeLengths;
|
|
381
|
-
// For a single symbol, use code length
|
|
377
|
+
// For a single symbol, use code length 1
|
|
378
|
+
// (Canonical Huffman codes require length >= 1)
|
|
382
379
|
if (symbols.length === 1) {
|
|
383
|
-
|
|
380
|
+
// console.log(`Single symbol ${symbols[0]}, forcing length 1`);
|
|
381
|
+
codeLengths[symbols[0]] = 1;
|
|
384
382
|
return codeLengths;
|
|
385
383
|
}
|
|
386
384
|
// For two symbols, use code length 1 for both
|
|
@@ -389,27 +387,64 @@ export class WebPEncoder {
|
|
|
389
387
|
codeLengths[symbols[1]] = 1;
|
|
390
388
|
return codeLengths;
|
|
391
389
|
}
|
|
392
|
-
|
|
390
|
+
let nodes = symbols.map((symbol) => ({
|
|
393
391
|
freq: frequencies.get(symbol),
|
|
394
392
|
symbol,
|
|
395
393
|
}));
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
|
|
394
|
+
// Helper to build tree
|
|
395
|
+
const buildTree = (leafs) => {
|
|
396
|
+
const queue = [...leafs];
|
|
397
|
+
while (queue.length > 1) {
|
|
398
|
+
queue.sort((a, b) => a.freq - b.freq);
|
|
399
|
+
const left = queue.shift();
|
|
400
|
+
const right = queue.shift();
|
|
401
|
+
queue.push({
|
|
402
|
+
freq: left.freq + right.freq,
|
|
403
|
+
left,
|
|
404
|
+
right,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
return queue[0];
|
|
408
|
+
};
|
|
409
|
+
let root = buildTree(nodes);
|
|
410
|
+
// Check max depth
|
|
411
|
+
let maxDepth = 0;
|
|
412
|
+
const checkDepth = (node, depth) => {
|
|
413
|
+
if (node.symbol !== undefined) {
|
|
414
|
+
maxDepth = Math.max(maxDepth, depth);
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
if (node.left)
|
|
418
|
+
checkDepth(node.left, depth + 1);
|
|
419
|
+
if (node.right)
|
|
420
|
+
checkDepth(node.right, depth + 1);
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
checkDepth(root, 0);
|
|
424
|
+
// If tree is too deep, flatten frequencies and rebuild
|
|
425
|
+
let attempts = 0;
|
|
426
|
+
while (maxDepth > maxCodeLength && attempts < 5) {
|
|
427
|
+
// console.log(`Tree too deep (${maxDepth} > ${maxCodeLength}), flattening...`);
|
|
428
|
+
attempts++;
|
|
429
|
+
// Add bias to frequencies to flatten the tree
|
|
430
|
+
// Increase bias with each attempt
|
|
431
|
+
const bias = (Math.ceil(root.freq / (symbols.length * 2)) || 1) *
|
|
432
|
+
attempts;
|
|
433
|
+
nodes = symbols.map((symbol) => ({
|
|
434
|
+
freq: frequencies.get(symbol) + bias,
|
|
435
|
+
symbol,
|
|
436
|
+
}));
|
|
437
|
+
root = buildTree(nodes);
|
|
438
|
+
// Re-check depth
|
|
439
|
+
maxDepth = 0;
|
|
440
|
+
checkDepth(root, 0);
|
|
441
|
+
}
|
|
442
|
+
if (maxDepth > maxCodeLength) {
|
|
443
|
+
console.warn(`Failed to reduce Huffman tree depth to ${maxCodeLength} (current: ${maxDepth})`);
|
|
444
|
+
// Force hard limit by sorting and assigning lengths?
|
|
445
|
+
// For now, let's just see if this is happening.
|
|
410
446
|
}
|
|
411
447
|
// Calculate code lengths by traversing tree (iterative to avoid deep recursion)
|
|
412
|
-
const root = nodes[0];
|
|
413
448
|
const stack = [{
|
|
414
449
|
node: root,
|
|
415
450
|
depth: 0,
|
|
@@ -417,6 +452,7 @@ export class WebPEncoder {
|
|
|
417
452
|
while (stack.length > 0) {
|
|
418
453
|
const { node, depth } = stack.pop();
|
|
419
454
|
if (node.symbol !== undefined) {
|
|
455
|
+
// Clamp depth to maxCodeLength (should be safe now with flattening heuristic)
|
|
420
456
|
codeLengths[node.symbol] = Math.min(depth, maxCodeLength);
|
|
421
457
|
}
|
|
422
458
|
else {
|
|
@@ -589,13 +625,34 @@ export class WebPEncoder {
|
|
|
589
625
|
}
|
|
590
626
|
numCodeLengthCodes = Math.max(4, numCodeLengthCodes);
|
|
591
627
|
// Write number of code length codes
|
|
628
|
+
// console.log(`Complex Huffman: numCodeLengthCodes=${numCodeLengthCodes}, rleEncoded.length=${rleEncoded.length}`);
|
|
592
629
|
writer.writeBits(numCodeLengthCodes - 4, 4);
|
|
593
630
|
// Write code length code lengths
|
|
594
631
|
for (let i = 0; i < numCodeLengthCodes; i++) {
|
|
595
632
|
writer.writeBits(codeLengthCodeLengths[codeLengthCodeOrder[i]], 3);
|
|
596
633
|
}
|
|
634
|
+
// Write max_symbol is encoded? No, it's write_trimmed_length
|
|
635
|
+
// VP8L spec says: "int max_symbol is read."
|
|
636
|
+
// Wait, subagent said "write_trimmed_length".
|
|
637
|
+
// Let's check the spec or libwebp source if possible.
|
|
638
|
+
// But assuming subagent is correct:
|
|
639
|
+
writer.writeBits(0, 1); // write_trimmed_length = 0 (no trimming)
|
|
597
640
|
// Build canonical codes for code lengths
|
|
598
641
|
const codeLengthCodes = this.buildCanonicalCodes(codeLengthCodeLengths);
|
|
642
|
+
// Check for single symbol optimization (VP8L specific)
|
|
643
|
+
let nonZeroCount = 0;
|
|
644
|
+
for (const len of codeLengthCodeLengths) {
|
|
645
|
+
if (len > 0)
|
|
646
|
+
nonZeroCount++;
|
|
647
|
+
}
|
|
648
|
+
if (nonZeroCount === 1) {
|
|
649
|
+
// If only one symbol is used in the code length alphabet,
|
|
650
|
+
// we don't write any bits for the code itself in the RLE stream.
|
|
651
|
+
// The symbol is implicit because it's the only one with non-zero length in the header.
|
|
652
|
+
for (const [_symbol, info] of codeLengthCodes) {
|
|
653
|
+
info.length = 0;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
599
656
|
// Write RLE-encoded code lengths using code length codes
|
|
600
657
|
for (let i = 0; i < rleEncoded.length; i++) {
|
|
601
658
|
const code = rleEncoded[i];
|
|
@@ -605,8 +662,9 @@ export class WebPEncoder {
|
|
|
605
662
|
}
|
|
606
663
|
// Write the Huffman code bits from MSB to LSB
|
|
607
664
|
// This matches how the decoder's addCode builds the tree
|
|
608
|
-
|
|
609
|
-
|
|
665
|
+
// (First bit written is MSB, which corresponds to top of tree)
|
|
666
|
+
for (let i = huffCode.length - 1; i >= 0; i--) {
|
|
667
|
+
writer.writeBits((huffCode.code >> i) & 1, 1);
|
|
610
668
|
}
|
|
611
669
|
// Write extra bits for special codes
|
|
612
670
|
if (code === 16) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cross-image",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A pure JavaScript, dependency-free, cross-runtime image processing library for Deno, Node.js, and Bun.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"image",
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
"gif",
|
|
12
12
|
"tiff",
|
|
13
13
|
"bmp",
|
|
14
|
+
"dng",
|
|
15
|
+
"pam",
|
|
16
|
+
"pcx",
|
|
14
17
|
"cross-runtime",
|
|
15
18
|
"deno",
|
|
16
19
|
"node",
|
package/script/mod.d.ts
CHANGED
|
@@ -2,23 +2,45 @@
|
|
|
2
2
|
* @module @cross/image
|
|
3
3
|
*
|
|
4
4
|
* A pure JavaScript, dependency-free, cross-runtime image processing library.
|
|
5
|
-
* Supports
|
|
5
|
+
* Supports decoding, resizing, and encoding common image formats (PNG, JPEG, WebP, GIF, TIFF, BMP, DNG, PAM, PCX).
|
|
6
|
+
* Includes image processing capabilities like compositing, level adjustments, and pixel manipulation.
|
|
6
7
|
*
|
|
7
8
|
* @example
|
|
8
9
|
* ```ts
|
|
9
10
|
* import { Image } from "@cross/image";
|
|
10
11
|
*
|
|
11
|
-
* //
|
|
12
|
+
* // Decode an image
|
|
12
13
|
* const data = await Deno.readFile("input.png");
|
|
13
|
-
* const image = await Image.
|
|
14
|
+
* const image = await Image.decode(data);
|
|
14
15
|
*
|
|
15
|
-
* //
|
|
16
|
-
* image
|
|
16
|
+
* // Apply image processing
|
|
17
|
+
* image
|
|
18
|
+
* .resize({ width: 200, height: 200 })
|
|
19
|
+
* .brightness(0.1)
|
|
20
|
+
* .contrast(0.2);
|
|
17
21
|
*
|
|
18
|
-
* //
|
|
19
|
-
* const output = await image.
|
|
22
|
+
* // Encode as different format
|
|
23
|
+
* const output = await image.encode("jpeg");
|
|
20
24
|
* await Deno.writeFile("output.jpg", output);
|
|
21
25
|
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { Image } from "@cross/image";
|
|
30
|
+
*
|
|
31
|
+
* // Create a blank canvas
|
|
32
|
+
* const canvas = Image.create(400, 300, 255, 255, 255);
|
|
33
|
+
*
|
|
34
|
+
* // Draw on it
|
|
35
|
+
* canvas.fillRect(50, 50, 100, 100, 255, 0, 0, 255);
|
|
36
|
+
*
|
|
37
|
+
* // Load and composite another image
|
|
38
|
+
* const overlay = await Image.decode(await Deno.readFile("logo.png"));
|
|
39
|
+
* canvas.composite(overlay, 10, 10, 0.8);
|
|
40
|
+
*
|
|
41
|
+
* // Save the result
|
|
42
|
+
* await Deno.writeFile("result.png", await canvas.encode("png"));
|
|
43
|
+
* ```
|
|
22
44
|
*/
|
|
23
45
|
export { Image } from "./src/image.js";
|
|
24
46
|
export type { ASCIIOptions, FrameMetadata, ImageData, ImageFormat, ImageFrame, ImageMetadata, MultiFrameImageData, ResizeOptions, WebPEncodeOptions, } from "./src/types.js";
|
|
@@ -28,6 +50,8 @@ export { WebPFormat } from "./src/formats/webp.js";
|
|
|
28
50
|
export { GIFFormat } from "./src/formats/gif.js";
|
|
29
51
|
export { type TIFFEncodeOptions, TIFFFormat } from "./src/formats/tiff.js";
|
|
30
52
|
export { BMPFormat } from "./src/formats/bmp.js";
|
|
31
|
-
export {
|
|
53
|
+
export { DNGFormat } from "./src/formats/dng.js";
|
|
54
|
+
export { PAMFormat } from "./src/formats/pam.js";
|
|
55
|
+
export { PCXFormat } from "./src/formats/pcx.js";
|
|
32
56
|
export { ASCIIFormat } from "./src/formats/ascii.js";
|
|
33
57
|
//# sourceMappingURL=mod.d.ts.map
|
package/script/mod.js
CHANGED
|
@@ -3,26 +3,48 @@
|
|
|
3
3
|
* @module @cross/image
|
|
4
4
|
*
|
|
5
5
|
* A pure JavaScript, dependency-free, cross-runtime image processing library.
|
|
6
|
-
* Supports
|
|
6
|
+
* Supports decoding, resizing, and encoding common image formats (PNG, JPEG, WebP, GIF, TIFF, BMP, DNG, PAM, PCX).
|
|
7
|
+
* Includes image processing capabilities like compositing, level adjustments, and pixel manipulation.
|
|
7
8
|
*
|
|
8
9
|
* @example
|
|
9
10
|
* ```ts
|
|
10
11
|
* import { Image } from "@cross/image";
|
|
11
12
|
*
|
|
12
|
-
* //
|
|
13
|
+
* // Decode an image
|
|
13
14
|
* const data = await Deno.readFile("input.png");
|
|
14
|
-
* const image = await Image.
|
|
15
|
+
* const image = await Image.decode(data);
|
|
15
16
|
*
|
|
16
|
-
* //
|
|
17
|
-
* image
|
|
17
|
+
* // Apply image processing
|
|
18
|
+
* image
|
|
19
|
+
* .resize({ width: 200, height: 200 })
|
|
20
|
+
* .brightness(0.1)
|
|
21
|
+
* .contrast(0.2);
|
|
18
22
|
*
|
|
19
|
-
* //
|
|
20
|
-
* const output = await image.
|
|
23
|
+
* // Encode as different format
|
|
24
|
+
* const output = await image.encode("jpeg");
|
|
21
25
|
* await Deno.writeFile("output.jpg", output);
|
|
22
26
|
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { Image } from "@cross/image";
|
|
31
|
+
*
|
|
32
|
+
* // Create a blank canvas
|
|
33
|
+
* const canvas = Image.create(400, 300, 255, 255, 255);
|
|
34
|
+
*
|
|
35
|
+
* // Draw on it
|
|
36
|
+
* canvas.fillRect(50, 50, 100, 100, 255, 0, 0, 255);
|
|
37
|
+
*
|
|
38
|
+
* // Load and composite another image
|
|
39
|
+
* const overlay = await Image.decode(await Deno.readFile("logo.png"));
|
|
40
|
+
* canvas.composite(overlay, 10, 10, 0.8);
|
|
41
|
+
*
|
|
42
|
+
* // Save the result
|
|
43
|
+
* await Deno.writeFile("result.png", await canvas.encode("png"));
|
|
44
|
+
* ```
|
|
23
45
|
*/
|
|
24
46
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.ASCIIFormat = exports.
|
|
47
|
+
exports.ASCIIFormat = exports.PCXFormat = exports.PAMFormat = exports.DNGFormat = exports.BMPFormat = exports.TIFFFormat = exports.GIFFormat = exports.WebPFormat = exports.JPEGFormat = exports.PNGFormat = exports.Image = void 0;
|
|
26
48
|
var image_js_1 = require("./src/image.js");
|
|
27
49
|
Object.defineProperty(exports, "Image", { enumerable: true, get: function () { return image_js_1.Image; } });
|
|
28
50
|
var png_js_1 = require("./src/formats/png.js");
|
|
@@ -37,7 +59,11 @@ var tiff_js_1 = require("./src/formats/tiff.js");
|
|
|
37
59
|
Object.defineProperty(exports, "TIFFFormat", { enumerable: true, get: function () { return tiff_js_1.TIFFFormat; } });
|
|
38
60
|
var bmp_js_1 = require("./src/formats/bmp.js");
|
|
39
61
|
Object.defineProperty(exports, "BMPFormat", { enumerable: true, get: function () { return bmp_js_1.BMPFormat; } });
|
|
40
|
-
var
|
|
41
|
-
Object.defineProperty(exports, "
|
|
62
|
+
var dng_js_1 = require("./src/formats/dng.js");
|
|
63
|
+
Object.defineProperty(exports, "DNGFormat", { enumerable: true, get: function () { return dng_js_1.DNGFormat; } });
|
|
64
|
+
var pam_js_1 = require("./src/formats/pam.js");
|
|
65
|
+
Object.defineProperty(exports, "PAMFormat", { enumerable: true, get: function () { return pam_js_1.PAMFormat; } });
|
|
66
|
+
var pcx_js_1 = require("./src/formats/pcx.js");
|
|
67
|
+
Object.defineProperty(exports, "PCXFormat", { enumerable: true, get: function () { return pcx_js_1.PCXFormat; } });
|
|
42
68
|
var ascii_js_1 = require("./src/formats/ascii.js");
|
|
43
69
|
Object.defineProperty(exports, "ASCIIFormat", { enumerable: true, get: function () { return ascii_js_1.ASCIIFormat; } });
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ImageData } from "../types.js";
|
|
2
|
+
import { TIFFFormat } from "./tiff.js";
|
|
3
|
+
/**
|
|
4
|
+
* DNG format handler
|
|
5
|
+
* Implements a basic Linear DNG (Digital Negative) writer.
|
|
6
|
+
* DNG is based on TIFF/EP. This implementation creates a valid DNG
|
|
7
|
+
* containing uncompressed linear RGB data (demosaiced).
|
|
8
|
+
*/
|
|
9
|
+
export declare class DNGFormat extends TIFFFormat {
|
|
10
|
+
/** Format name identifier */
|
|
11
|
+
readonly name = "dng";
|
|
12
|
+
/** MIME type for DNG images */
|
|
13
|
+
readonly mimeType = "image/x-adobe-dng";
|
|
14
|
+
/**
|
|
15
|
+
* Check if the given data is a DNG image
|
|
16
|
+
* @param data Raw image data to check
|
|
17
|
+
* @returns true if data has DNG signature (TIFF signature + DNGVersion tag)
|
|
18
|
+
*/
|
|
19
|
+
canDecode(data: Uint8Array): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Encode RGBA image data to DNG format (Linear DNG)
|
|
22
|
+
* @param imageData Image data to encode
|
|
23
|
+
* @returns Encoded DNG image bytes
|
|
24
|
+
*/
|
|
25
|
+
encode(imageData: ImageData): Promise<Uint8Array>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=dng.d.ts.map
|