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
package/README.md
CHANGED
|
@@ -1,121 +1,155 @@
|
|
|
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
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
image
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
##
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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, DNG, PAM, and PCX
|
|
14
|
+
support
|
|
15
|
+
- ✂️ **Image manipulation** - Resize, crop, composite, and more
|
|
16
|
+
- 🎛️ **Image processing** - Adjust brightness, contrast, saturation, exposure
|
|
17
|
+
- 🖌️ **Drawing operations** - Create, fill, and manipulate pixels
|
|
18
|
+
- 🔧 **Simple API** - Easy to use, intuitive interface
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
### Deno
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { Image } from "jsr:@cross/image";
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Node.js
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx jsr add @cross/image
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { Image } from "@cross/image";
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Bun
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
bunx jsr add @cross/image
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { Image } from "@cross/image";
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
### Deno
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { Image } from "@cross/image";
|
|
54
|
+
|
|
55
|
+
// Decode an image (auto-detects format)
|
|
56
|
+
const data = await Deno.readFile("input.png");
|
|
57
|
+
const image = await Image.decode(data);
|
|
58
|
+
|
|
59
|
+
console.log(`Image size: ${image.width}x${image.height}`);
|
|
60
|
+
|
|
61
|
+
// Create a new blank image
|
|
62
|
+
const canvas = Image.create(800, 600, 255, 255, 255); // white background
|
|
63
|
+
|
|
64
|
+
// Composite the loaded image on top
|
|
65
|
+
canvas.composite(image, 50, 50);
|
|
66
|
+
|
|
67
|
+
// Apply image processing
|
|
68
|
+
canvas
|
|
69
|
+
.brightness(0.1)
|
|
70
|
+
.contrast(0.2)
|
|
71
|
+
.saturation(-0.1);
|
|
72
|
+
|
|
73
|
+
// Encode in a different format
|
|
74
|
+
const jpeg = await canvas.encode("jpeg");
|
|
75
|
+
await Deno.writeFile("output.jpg", jpeg);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Node.js
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { Image } from "cross-image";
|
|
82
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
83
|
+
|
|
84
|
+
// Read an image (auto-detects format)
|
|
85
|
+
const data = await readFile("input.png");
|
|
86
|
+
const image = await Image.read(data);
|
|
87
|
+
|
|
88
|
+
console.log(`Image size: ${image.width}x${image.height}`);
|
|
89
|
+
|
|
90
|
+
// Resize the image
|
|
91
|
+
image.resize({ width: 800, height: 600 });
|
|
92
|
+
|
|
93
|
+
// Save in a different format
|
|
94
|
+
const jpeg = await image.save("jpeg");
|
|
95
|
+
await writeFile("output.jpg", jpeg);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Supported Formats
|
|
99
|
+
|
|
100
|
+
| Format | Pure-JS | Notes |
|
|
101
|
+
| ------ | ----------- | ------------------------------- |
|
|
102
|
+
| PNG | ✅ Full | Complete pure-JS implementation |
|
|
103
|
+
| BMP | ✅ Full | Complete pure-JS implementation |
|
|
104
|
+
| GIF | ✅ Full | Complete pure-JS implementation |
|
|
105
|
+
| RAW | ✅ Full | Uncompressed RGBA |
|
|
106
|
+
| ASCII | ✅ Full | Text-based ASCII art |
|
|
107
|
+
| JPEG | ⚠️ Baseline | Pure-JS baseline DCT only |
|
|
108
|
+
| WebP | ⚠️ Lossless | Pure-JS lossless VP8L |
|
|
109
|
+
| TIFF | ⚠️ Basic | Pure-JS uncompressed + LZW |
|
|
110
|
+
|
|
111
|
+
See the
|
|
112
|
+
[full format support documentation](https://cross-org.github.io/image/formats.html)
|
|
113
|
+
for detailed compatibility information.
|
|
114
|
+
|
|
115
|
+
## Documentation
|
|
116
|
+
|
|
117
|
+
- **[API Reference](https://cross-org.github.io/image/api.html)** - Complete API
|
|
118
|
+
documentation
|
|
119
|
+
- **[Examples](https://cross-org.github.io/image/examples.html)** - Usage
|
|
120
|
+
examples for common tasks
|
|
121
|
+
- **[Format Support](https://cross-org.github.io/image/formats.html)** -
|
|
122
|
+
Supported formats and specifications
|
|
123
|
+
- **[JPEG Implementation](https://cross-org.github.io/image/jpeg-implementation.html)** -
|
|
124
|
+
Technical details for JPEG
|
|
125
|
+
- **[WebP Implementation](https://cross-org.github.io/image/webp-implementation.html)** -
|
|
126
|
+
Technical details for WebP
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
### Running Tests
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
deno test -A
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Linting and Formatting
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
deno fmt --check
|
|
140
|
+
deno lint
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Type Checking
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
deno check mod.ts
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT License - see LICENSE file for details.
|
|
152
|
+
|
|
153
|
+
## Contributing
|
|
154
|
+
|
|
155
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
package/esm/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/esm/mod.js
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 { PNGFormat } from "./src/formats/png.js";
|
|
@@ -27,5 +49,7 @@ export { WebPFormat } from "./src/formats/webp.js";
|
|
|
27
49
|
export { GIFFormat } from "./src/formats/gif.js";
|
|
28
50
|
export { TIFFFormat } from "./src/formats/tiff.js";
|
|
29
51
|
export { BMPFormat } from "./src/formats/bmp.js";
|
|
30
|
-
export {
|
|
52
|
+
export { DNGFormat } from "./src/formats/dng.js";
|
|
53
|
+
export { PAMFormat } from "./src/formats/pam.js";
|
|
54
|
+
export { PCXFormat } from "./src/formats/pcx.js";
|
|
31
55
|
export { ASCIIFormat } from "./src/formats/ascii.js";
|
|
@@ -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
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { TIFFFormat } from "./tiff.js";
|
|
2
|
+
/**
|
|
3
|
+
* DNG format handler
|
|
4
|
+
* Implements a basic Linear DNG (Digital Negative) writer.
|
|
5
|
+
* DNG is based on TIFF/EP. This implementation creates a valid DNG
|
|
6
|
+
* containing uncompressed linear RGB data (demosaiced).
|
|
7
|
+
*/
|
|
8
|
+
export class DNGFormat extends TIFFFormat {
|
|
9
|
+
constructor() {
|
|
10
|
+
super(...arguments);
|
|
11
|
+
/** Format name identifier */
|
|
12
|
+
Object.defineProperty(this, "name", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
configurable: true,
|
|
15
|
+
writable: true,
|
|
16
|
+
value: "dng"
|
|
17
|
+
});
|
|
18
|
+
/** MIME type for DNG images */
|
|
19
|
+
Object.defineProperty(this, "mimeType", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
value: "image/x-adobe-dng"
|
|
24
|
+
});
|
|
25
|
+
// Helper methods duplicated from TIFFFormat because they are protected/private there
|
|
26
|
+
// and we can't easily access them if they are private.
|
|
27
|
+
// Let's check TIFFFormat visibility.
|
|
28
|
+
// The read/write helpers were not exported in the previous read_file output.
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Check if the given data is a DNG image
|
|
32
|
+
* @param data Raw image data to check
|
|
33
|
+
* @returns true if data has DNG signature (TIFF signature + DNGVersion tag)
|
|
34
|
+
*/
|
|
35
|
+
canDecode(data) {
|
|
36
|
+
// DNG is a TIFF file, so it must have a TIFF signature
|
|
37
|
+
if (!super.canDecode(data)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
// To be strictly a DNG, it should have the DNGVersion tag (0xC612 / 50706)
|
|
41
|
+
// However, scanning for tags in canDecode might be slow.
|
|
42
|
+
// For now, we rely on the fact that it's a TIFF-based format.
|
|
43
|
+
// If we want to be stricter, we would need to parse the IFD here.
|
|
44
|
+
// Let's do a quick check for the DNGVersion tag in the first few bytes if possible,
|
|
45
|
+
// but tags are in the IFD which can be anywhere.
|
|
46
|
+
// So we'll just check if it's a TIFF and maybe rely on extension or user intent.
|
|
47
|
+
// But wait, if we register both TIFF and DNG, who wins?
|
|
48
|
+
// The first one in the list.
|
|
49
|
+
// So we should probably implement a proper check if possible, or just accept that
|
|
50
|
+
// DNGs are TIFFs.
|
|
51
|
+
// For this implementation, we'll assume if it's a TIFF, it *could* be a DNG.
|
|
52
|
+
// But to distinguish, we really should check for DNGVersion.
|
|
53
|
+
// Let's try to find the DNGVersion tag (50706) in the first IFD.
|
|
54
|
+
try {
|
|
55
|
+
const isLittleEndian = data[0] === 0x49;
|
|
56
|
+
const ifdOffset = this.readUint32(data, 4, isLittleEndian);
|
|
57
|
+
// Safety check for offset
|
|
58
|
+
if (ifdOffset >= data.length)
|
|
59
|
+
return false;
|
|
60
|
+
const numEntries = this.readUint16(data, ifdOffset, isLittleEndian);
|
|
61
|
+
for (let i = 0; i < numEntries; i++) {
|
|
62
|
+
const entryOffset = ifdOffset + 2 + (i * 12);
|
|
63
|
+
if (entryOffset + 12 > data.length)
|
|
64
|
+
break;
|
|
65
|
+
const tag = this.readUint16(data, entryOffset, isLittleEndian);
|
|
66
|
+
if (tag === 50706) { // DNGVersion
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Encode RGBA image data to DNG format (Linear DNG)
|
|
78
|
+
* @param imageData Image data to encode
|
|
79
|
+
* @returns Encoded DNG image bytes
|
|
80
|
+
*/
|
|
81
|
+
async encode(imageData) {
|
|
82
|
+
const { width, height, data } = imageData;
|
|
83
|
+
// We'll create a Linear DNG (demosaiced RGB)
|
|
84
|
+
// This is very similar to a standard TIFF but with specific tags.
|
|
85
|
+
const result = [];
|
|
86
|
+
// Header (8 bytes)
|
|
87
|
+
// Little-endian byte order
|
|
88
|
+
result.push(0x49, 0x49); // "II"
|
|
89
|
+
result.push(0x2a, 0x00); // 42
|
|
90
|
+
// IFD offset (will be after header and pixel data)
|
|
91
|
+
const ifdOffset = 8 + data.length;
|
|
92
|
+
this.writeUint32LE(result, ifdOffset);
|
|
93
|
+
// Pixel data (Uncompressed RGBA)
|
|
94
|
+
for (let i = 0; i < data.length; i++) {
|
|
95
|
+
result.push(data[i]);
|
|
96
|
+
}
|
|
97
|
+
// IFD (Image File Directory)
|
|
98
|
+
const ifdStart = result.length;
|
|
99
|
+
// Tags we need for DNG:
|
|
100
|
+
// - NewSubfileType (254)
|
|
101
|
+
// - ImageWidth (256)
|
|
102
|
+
// - ImageHeight (257)
|
|
103
|
+
// - BitsPerSample (258)
|
|
104
|
+
// - Compression (259)
|
|
105
|
+
// - PhotometricInterpretation (262)
|
|
106
|
+
// - StripOffsets (273)
|
|
107
|
+
// - SamplesPerPixel (277)
|
|
108
|
+
// - RowsPerStrip (278)
|
|
109
|
+
// - StripByteCounts (279)
|
|
110
|
+
// - PlanarConfiguration (284)
|
|
111
|
+
// - ExtraSamples (338) - for Alpha
|
|
112
|
+
// - DNGVersion (50706)
|
|
113
|
+
// - UniqueCameraModel (50708)
|
|
114
|
+
const numEntries = 14;
|
|
115
|
+
this.writeUint16LE(result, numEntries);
|
|
116
|
+
// Calculate offsets for variable-length data
|
|
117
|
+
let dataOffset = ifdStart + 2 + numEntries * 12 + 4; // +4 for next IFD offset (0)
|
|
118
|
+
// 1. NewSubfileType (0x00FE) - 0 = Full resolution image
|
|
119
|
+
this.writeIFDEntry(result, 0x00FE, 4, 1, 0);
|
|
120
|
+
// 2. ImageWidth (0x0100)
|
|
121
|
+
this.writeIFDEntry(result, 0x0100, 4, 1, width);
|
|
122
|
+
// 3. ImageHeight (0x0101)
|
|
123
|
+
this.writeIFDEntry(result, 0x0101, 4, 1, height);
|
|
124
|
+
// 4. BitsPerSample (0x0102) - 8, 8, 8, 8
|
|
125
|
+
this.writeIFDEntry(result, 0x0102, 3, 4, dataOffset);
|
|
126
|
+
// Write the actual values later
|
|
127
|
+
const bitsPerSampleOffset = dataOffset;
|
|
128
|
+
dataOffset += 8; // 4 * 2 bytes
|
|
129
|
+
// 5. Compression (0x0103) - 1 = Uncompressed
|
|
130
|
+
this.writeIFDEntry(result, 0x0103, 3, 1, 1);
|
|
131
|
+
// 6. PhotometricInterpretation (0x0106) - 2 = RGB (Linear DNG)
|
|
132
|
+
// For Raw DNG it would be 32803 (CFA), but we are saving processed RGB data
|
|
133
|
+
this.writeIFDEntry(result, 0x0106, 3, 1, 2);
|
|
134
|
+
// 7. StripOffsets (0x0111)
|
|
135
|
+
this.writeIFDEntry(result, 0x0111, 4, 1, 8); // Pixel data starts at offset 8
|
|
136
|
+
// 8. SamplesPerPixel (0x0115) - 4 (RGBA)
|
|
137
|
+
this.writeIFDEntry(result, 0x0115, 3, 1, 4);
|
|
138
|
+
// 9. RowsPerStrip (0x0116)
|
|
139
|
+
this.writeIFDEntry(result, 0x0116, 4, 1, height);
|
|
140
|
+
// 10. StripByteCounts (0x0117)
|
|
141
|
+
this.writeIFDEntry(result, 0x0117, 4, 1, data.length);
|
|
142
|
+
// 11. PlanarConfiguration (0x011C) - 1 = Chunky
|
|
143
|
+
this.writeIFDEntry(result, 0x011C, 3, 1, 1);
|
|
144
|
+
// 12. ExtraSamples (0x0152) - 2 = Unassociated alpha
|
|
145
|
+
this.writeIFDEntry(result, 0x0152, 3, 1, 2); // 1 value, fits in offset field? No, it's SHORT (3), count 1. Value 2.
|
|
146
|
+
// Wait, writeIFDEntry puts value in offset field if count*type_size <= 4.
|
|
147
|
+
// SHORT is 2 bytes. 1 * 2 = 2 <= 4. So value goes in offset.
|
|
148
|
+
// 13. DNGVersion (0xC612 / 50706) - 1, 4, 0, 0
|
|
149
|
+
this.writeIFDEntry(result, 50706, 1, 4, 0x01040000);
|
|
150
|
+
// BYTE (1) count 4. 1*4=4. Fits.
|
|
151
|
+
// 1, 4, 0, 0 -> 0x01, 0x04, 0x00, 0x00.
|
|
152
|
+
// Little endian: 01 04 00 00.
|
|
153
|
+
// As uint32: 0x00000401? No, bytes are 1, 4, 0, 0.
|
|
154
|
+
// In file: 01 04 00 00.
|
|
155
|
+
// readUint32LE would read this as 0x00000401.
|
|
156
|
+
// So we pass 0x00000401?
|
|
157
|
+
// Let's verify writeIFDEntry logic in TIFFFormat (I can't see it but I assume it writes value directly if it fits).
|
|
158
|
+
// Actually, I need to check how writeIFDEntry works.
|
|
159
|
+
// Assuming it takes a number and writes it.
|
|
160
|
+
// 14. UniqueCameraModel (0xC614 / 50708) - "Cross Image DNG"
|
|
161
|
+
const modelName = "Cross Image DNG\0";
|
|
162
|
+
const modelNameBytes = new TextEncoder().encode(modelName);
|
|
163
|
+
this.writeIFDEntry(result, 50708, 2, modelNameBytes.length, dataOffset);
|
|
164
|
+
const modelNameOffset = dataOffset;
|
|
165
|
+
dataOffset += modelNameBytes.length;
|
|
166
|
+
// Next IFD offset (0)
|
|
167
|
+
this.writeUint32LE(result, 0);
|
|
168
|
+
// Write variable length data
|
|
169
|
+
// BitsPerSample data (8, 8, 8, 8)
|
|
170
|
+
// We need to write this at bitsPerSampleOffset
|
|
171
|
+
// But we are appending to result array.
|
|
172
|
+
// We calculated dataOffset relative to start of file?
|
|
173
|
+
// No, dataOffset was initialized to `ifdStart + 2 + numEntries * 12 + 4`.
|
|
174
|
+
// This is correct absolute offset.
|
|
175
|
+
// But we need to fill the gap between end of IFD and dataOffset?
|
|
176
|
+
// No, we are writing sequentially.
|
|
177
|
+
// Wait, `result` is an array of bytes.
|
|
178
|
+
// We wrote the IFD entries. Now we are at `ifdStart + 2 + numEntries * 12 + 4`.
|
|
179
|
+
// This matches `bitsPerSampleOffset`.
|
|
180
|
+
// Write BitsPerSample (8, 8, 8, 8)
|
|
181
|
+
this.writeUint16LE(result, 8);
|
|
182
|
+
this.writeUint16LE(result, 8);
|
|
183
|
+
this.writeUint16LE(result, 8);
|
|
184
|
+
this.writeUint16LE(result, 8);
|
|
185
|
+
// Write UniqueCameraModel string
|
|
186
|
+
for (let i = 0; i < modelNameBytes.length; i++) {
|
|
187
|
+
result.push(modelNameBytes[i]);
|
|
188
|
+
}
|
|
189
|
+
return new Uint8Array(result);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ImageData, ImageFormat } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* PAM format handler
|
|
4
|
+
* Implements the Netpbm PAM (Portable Arbitrary Map) format.
|
|
5
|
+
* This is a standard uncompressed format supported by GIMP and other tools.
|
|
6
|
+
*
|
|
7
|
+
* Format structure:
|
|
8
|
+
* - Header (text):
|
|
9
|
+
* P7
|
|
10
|
+
* WIDTH <width>
|
|
11
|
+
* HEIGHT <height>
|
|
12
|
+
* DEPTH 4
|
|
13
|
+
* MAXVAL 255
|
|
14
|
+
* TUPLTYPE RGB_ALPHA
|
|
15
|
+
* ENDHDR
|
|
16
|
+
* - Data (binary):
|
|
17
|
+
* RGBA pixel data (width * height * 4 bytes)
|
|
18
|
+
*/
|
|
19
|
+
export declare class PAMFormat implements ImageFormat {
|
|
20
|
+
/** Format name identifier */
|
|
21
|
+
readonly name = "pam";
|
|
22
|
+
/** MIME type for PAM images */
|
|
23
|
+
readonly mimeType = "image/x-portable-arbitrary-map";
|
|
24
|
+
/**
|
|
25
|
+
* Check if the given data is a PAM image
|
|
26
|
+
* @param data Raw image data to check
|
|
27
|
+
* @returns true if data has PAM signature
|
|
28
|
+
*/
|
|
29
|
+
canDecode(data: Uint8Array): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Decode PAM image data to RGBA
|
|
32
|
+
* @param data Raw PAM image data
|
|
33
|
+
* @returns Decoded image data with RGBA pixels
|
|
34
|
+
*/
|
|
35
|
+
decode(data: Uint8Array): Promise<ImageData>;
|
|
36
|
+
/**
|
|
37
|
+
* Encode RGBA image data to PAM format
|
|
38
|
+
* @param imageData Image data to encode
|
|
39
|
+
* @returns Encoded PAM image bytes
|
|
40
|
+
*/
|
|
41
|
+
encode(imageData: ImageData): Promise<Uint8Array>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=pam.d.ts.map
|