raw-webgpu 0.1.0-alpha.1 → 0.1.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 +32 -20
- package/dist/decode/device.d.ts +2 -0
- package/dist/develop/gpu.d.ts +1 -0
- package/dist/index.js +123 -26
- package/dist/libraw.js +1 -1
- package/dist/libraw.wasm +0 -0
- package/dist/tiff/color.d.ts +1 -1
- package/dist/tiff/index.d.ts +1 -1
- package/dist/tiff/strips.d.ts +2 -0
- package/dist/tiff/upload.d.ts +3 -3
- package/dist/tiff-worker.js +116 -19
- package/native/tiff.cpp +99 -19
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Load camera RAW, DNG and TIFF files into WebGPU textures. Develop into linear Rec.2020 `rgba16float`, with adjustable white balance and exposure.
|
|
4
4
|
|
|
5
|
-
LibRaw, Adobe DNG SDK and libjxl decode files in a WASM worker. WebGPU handles supported demosaic and color processing. Supply your own `GPUDevice`; no rendering framework or runtime dependencies are required.
|
|
5
|
+
LibRaw, Adobe DNG SDK and libjxl decode files in a WASM worker. Simple TIFF strips use original file bytes or browser Deflate; complex layouts retain the SDK fallback. WebGPU handles supported demosaic and color processing. Supply your own `GPUDevice`; no rendering framework or runtime dependencies are required.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -10,17 +10,15 @@ LibRaw, Adobe DNG SDK and libjxl decode files in a WASM worker. WebGPU handles s
|
|
|
10
10
|
- GPU Bayer and X-Trans demosaic, with library CPU preparation for special layouts and mandatory DNG corrections.
|
|
11
11
|
- Absolute temperature/tint and initial white-balance restoration without decoding again.
|
|
12
12
|
- HDR output, camera calibration, orientation and explicit resource disposal.
|
|
13
|
-
-
|
|
13
|
+
- TIFF support with ICC matrix/TRC profiles and alpha.
|
|
14
14
|
|
|
15
15
|
## Installation
|
|
16
16
|
|
|
17
|
-
`0.1.0-alpha.1` is prepared but not published yet. Once available:
|
|
18
|
-
|
|
19
17
|
```sh
|
|
20
|
-
npm install --save-exact raw-webgpu@0.1.0
|
|
18
|
+
npm install --save-exact raw-webgpu@0.1.0
|
|
21
19
|
```
|
|
22
20
|
|
|
23
|
-
WASM, workers and types are included; consumers do not compile C++. Use a WebGPU browser with WASM SIMD/exception support and a bundler that handles worker/WASM asset URLs, such as Vite.
|
|
21
|
+
WASM, workers and types are included; consumers do not compile C++. Use a WebGPU browser with WASM SIMD/exception support and a bundler that handles worker/WASM asset URLs, such as Vite. TypeScript 5.9 and newer are tested; with TypeScript 5.9, install `@webgpu/types` and include it in `compilerOptions.types`.
|
|
24
22
|
|
|
25
23
|
## Usage
|
|
26
24
|
|
|
@@ -63,29 +61,46 @@ Use separate passes for independent previews or exports. If passing an external
|
|
|
63
61
|
|
|
64
62
|
For file export, see the [Bun PNG/JPEG/BMP conversion example](docs/conversion.md).
|
|
65
63
|
|
|
64
|
+
|
|
65
|
+
## API contract and support
|
|
66
|
+
|
|
67
|
+
The supported browser baseline is Chromium with WebGPU. Verified environments are Chromium 151 on macOS with Apple M4 Pro and Linux CI with software rendering, plus Chrome 153 on macOS. Safari, Firefox, mobile browsers and other physical GPUs have not been validated. Tests simulate lower texture/buffer limits; they do not establish a device memory budget. Bun is an additional tested runtime, not a CPU fallback.
|
|
68
|
+
|
|
69
|
+
- Developed RAW and decoded TIFF output is linear Rec.2020/D65 `rgba16float`, with no display tone curve. HDR values can exceed 1. TIFF alpha is straight. Camera JPEG previews apply their own rendering and are not pixel references for this output.
|
|
70
|
+
- `source.texture` contains sensor or camera-RGB samples, not developed output. Read its format and metadata; do not assume every RAW is a one-channel mosaic. `source.size` is oriented output size; `metadata.size` is the unrotated sample size.
|
|
71
|
+
- Calibration has three RGB gains and a nine-value column-major matrix. Development applies gains before the camera-to-working-space matrix; exposure is in stops. Independent passes can use different calibration without changing the source.
|
|
72
|
+
- A decoder owns its sources, each source owns its passes and worker, and the caller owns destination textures. Disposal is idempotent. Device loss closes the decoder; create another decoder with a new device. Pending loads and calibration requests reject when their owner closes.
|
|
73
|
+
- Abort signals cover loading only, including pending GPU setup. Cancellation rejects with the signal's reason. Invalid or unsupported files reject with an `Error`; message text is diagnostic, not a stable error code. Load completion queues GPU work; wait on the device queue when you need GPU completion.
|
|
74
|
+
|
|
75
|
+
The public API and output contract are the supported baseline for 0.1.x. Release checks compare synthetic pixels against known color math and demosaic filters, test real workers and resource cleanup, and install the actual npm tarball in a separate consumer using TypeScript 5.9. The retained 30-camera corpus is a manual compatibility/regression check; X-Trans detail and Sigma color remain explicitly experimental in 0.1.0.
|
|
76
|
+
|
|
66
77
|
## Limitations and planned work
|
|
67
78
|
|
|
68
|
-
**X-Trans
|
|
79
|
+
**X-Trans quality remains experimental.** Two GPU passes reconstruct camera RGB: nearby samples provide an initial estimate, then interpolated R−G and B−G differences recover detail. White balance is applied after this fixed reconstruction; it is not equivalent to running a WB-dependent demosaic again. Further work should improve directional edges and aliasing against the retained LibRaw Markesteijn references.
|
|
80
|
+
|
|
81
|
+
The camera-RGB cache keeps WB edits fast. A 16 MP image retains about 128 MB of RGB plus the 32 MB mosaic; reconstruction temporarily needs another 128 MB texture. That temporary texture is released after submission.
|
|
69
82
|
|
|
70
|
-
RAW coverage depends on camera calibration and sensor layout. Missing as-shot multipliers use a reported daylight fallback; Sigma color remains experimental. Floating-point RAW input, baked white balance and non-three-color sensors have limitations. CPU-prepared sources cannot expose pre-demosaic edits. There is no GPU denoising, highlight reconstruction or CPU rendering fallback.
|
|
83
|
+
RAW coverage depends on camera calibration and sensor layout. Missing as-shot multipliers use a reported daylight fallback; Sigma color remains experimental and can have visible color casts. Floating-point RAW input, baked white balance and non-three-color sensors have limitations. CPU-prepared sources cannot expose pre-demosaic edits. There is no GPU denoising, highlight reconstruction or CPU rendering fallback.
|
|
71
84
|
|
|
72
|
-
TIFF passes
|
|
85
|
+
TIFF passes 20 of 24 fixtures; bilevel, palette, float64 and YCbCr JPEG are rejected. Only the first IFD is read, and unsupported or malformed embedded ICC profiles are rejected. Untagged integer TIFF defaults to sRGB; untagged floating-point TIFF is treated as linear sRGB. Images must fit the GPU's texture limits.
|
|
73
86
|
|
|
74
87
|
## Benchmark
|
|
75
88
|
|
|
76
|
-
All 30 camera samples converted in Chromium and Bun. Selected results on Apple M4 Pro with Chromium 151, measured September 9, 2026:
|
|
89
|
+
All 30 camera samples converted in Chromium and Bun. Selected results on Apple M4 Pro with Chromium 151, measured September 9, 2026; Fuji results updated September 10:
|
|
77
90
|
|
|
78
91
|
| Camera | Load | WB GPU update |
|
|
79
92
|
| --- | ---: | ---: |
|
|
80
93
|
| Nikon D800 | 397 ms | Not measured |
|
|
81
94
|
| Sony A7R II | 365 ms | Not measured |
|
|
82
|
-
| Fuji X-Pro1 |
|
|
83
|
-
| Fuji X-T10 |
|
|
95
|
+
| Fuji X-Pro1 | 267 ms | 0.8 ms |
|
|
96
|
+
| Fuji X-T10 | 232 ms | 0.9 ms |
|
|
84
97
|
| Nokia Lumia 1020 | 1,719 ms | Not measured |
|
|
85
98
|
|
|
86
|
-
Loading is the median of three runs after one warmup, from an in-memory file through decoding, upload and GPU completion. Disk reads and export encoding are excluded. WB timings measure the GPU stage over ten edits after two warmups. X-Trans
|
|
99
|
+
Loading is the median of three runs after one warmup, from an in-memory file through decoding, upload and GPU completion. Disk reads and export encoding are excluded. WB timings measure the GPU stage over ten edits after two warmups. These numbers do not guarantee application frame rates. The X-Trans refinement reduced mean absolute RGB error against the retained LibRaw exports from 3.59 to 2.15 levels for X-Pro1 and from 3.77 to 2.30 for X-T10 on an 8-bit scale. Those exports are comparison references, not ground truth.
|
|
100
|
+
|
|
101
|
+
On the same machine, TIFF loading improved from 437 to 321 ms for a 14.6 MP Deflate image, from 59 to 49 ms for a 19 MP uncompressed RGB16 image, and from 546 to 443 ms for a 19 MP Deflate image with horizontal prediction. Medians exclude one warmup and include GPU completion. The TIFF changes preserved the checked pixels across all 30 RAW samples, with no sustained RAW loading regression observed.
|
|
87
102
|
|
|
88
|
-
WASM is 1.
|
|
103
|
+
WASM is 1.82 MB, or about 570 KB with Brotli. Full measurements and downloaded files remain in the local ignored `.cache/` directory.
|
|
89
104
|
|
|
90
105
|
## Development
|
|
91
106
|
|
|
@@ -101,14 +116,11 @@ The native build downloads checksum-pinned dependencies and caches objects in `.
|
|
|
101
116
|
|
|
102
117
|
`test:package` builds a tarball, installs it in a temporary project, checks types and runs RAW/TIFF tests in Chromium with WebGPU. `bun run test:gpu` checks shader pixels; `bun run check` formats and lints.
|
|
103
118
|
|
|
104
|
-
|
|
119
|
+
Publishing runs in GitHub Actions. Merge the version change into `main`, then publish a GitHub Release with the matching tag, such as `v0.1.0`. The workflow verifies the tag and commit, installs Emscripten, and runs the package checks before publishing to npm. Stable releases use `latest`; `-alpha.N` prereleases use `alpha`. Local publishing is not required.
|
|
105
120
|
|
|
106
|
-
|
|
107
|
-
npm publish --dry-run
|
|
108
|
-
npm publish --tag alpha --access public
|
|
109
|
-
```
|
|
121
|
+
Configure [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/) once for owner `roprgm`, repository `raw-webgpu`, workflow `publish.yml`, with publishing allowed and no environment name. No npm token secret is needed; npm attaches provenance automatically.
|
|
110
122
|
|
|
111
|
-
`prepack` rebuilds the package; `prepublishOnly` checks formatting, GPU tests and the installed tarball.
|
|
123
|
+
`prepack` rebuilds the package; `prepublishOnly` checks formatting, GPU tests and the installed tarball. Linux browser tests use SwiftShader, and the release job uses vgpu's software renderer for shader tests. Performance measurements should still use real hardware.
|
|
112
124
|
|
|
113
125
|
## License
|
|
114
126
|
|
package/dist/develop/gpu.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { DevelopOptions, RawPixels } from "../types";
|
|
|
2
2
|
export declare function createPipeline(device: GPUDevice): Promise<{
|
|
3
3
|
sensor: GPURenderPipeline;
|
|
4
4
|
camera: GPURenderPipeline;
|
|
5
|
+
refine: GPURenderPipeline;
|
|
5
6
|
}>;
|
|
6
7
|
/** Owns the sensor texture and immutable uniforms; each pass owns its calibration buffer. */
|
|
7
8
|
export declare function createGpuSource(device: GPUDevice, pixels: RawPixels, pipeline: Awaited<ReturnType<typeof createPipeline>>): {
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
// src/decode/device.ts
|
|
2
|
+
var signals = new WeakMap;
|
|
3
|
+
function deviceSignal(device) {
|
|
4
|
+
let signal = signals.get(device);
|
|
5
|
+
if (!signal) {
|
|
6
|
+
const controller = new AbortController;
|
|
7
|
+
device.lost.then(() => controller.abort(Error("WebGPU device was lost.")));
|
|
8
|
+
signal = controller.signal;
|
|
9
|
+
signals.set(device, signal);
|
|
10
|
+
}
|
|
11
|
+
return signal;
|
|
12
|
+
}
|
|
13
|
+
|
|
1
14
|
// src/decode/module.ts
|
|
2
15
|
var compiled;
|
|
3
16
|
async function compile() {
|
|
@@ -35,7 +48,11 @@ function createSession(compiled2) {
|
|
|
35
48
|
}
|
|
36
49
|
pending.clear();
|
|
37
50
|
}
|
|
38
|
-
compiled2.then((module) =>
|
|
51
|
+
compiled2.then((module) => {
|
|
52
|
+
if (!failure) {
|
|
53
|
+
worker.postMessage({ module });
|
|
54
|
+
}
|
|
55
|
+
}, (error) => dispose(error instanceof Error ? error : Error(String(error))));
|
|
39
56
|
worker.onmessage = ({
|
|
40
57
|
data
|
|
41
58
|
}) => {
|
|
@@ -206,6 +223,41 @@ fn demosaicPattern(p: vec2i, size: vec2i) -> vec3f {
|
|
|
206
223
|
|
|
207
224
|
@group(0) @binding(5) var cameraSource: texture_2d<f32>;
|
|
208
225
|
|
|
226
|
+
// Refine a neutral camera-RGB estimate by interpolating color differences.
|
|
227
|
+
// The denser green samples carry detail into red and blue without repeating
|
|
228
|
+
// this reconstruction when white balance changes.
|
|
229
|
+
@fragment fn fs_refine(@builtin(position) position: vec4f) -> @location(0) vec4f {
|
|
230
|
+
let size = vec2i(textureDimensions(source));
|
|
231
|
+
var p = vec2i(position.xy);
|
|
232
|
+
if ((sensor.flip & 4u) != 0u) { p = p.yx; }
|
|
233
|
+
if ((sensor.flip & 2u) != 0u) { p.y = size.y - 1 - p.y; }
|
|
234
|
+
if ((sensor.flip & 1u) != 0u) { p.x = size.x - 1 - p.x; }
|
|
235
|
+
let center = textureLoad(cameraSource, vec2i(position.xy), 0).rgb;
|
|
236
|
+
var difference = vec3f(0.0);
|
|
237
|
+
var weight = vec3f(0.0);
|
|
238
|
+
for (var y = -3; y <= 3; y++) {
|
|
239
|
+
for (var x = -3; x <= 3; x++) {
|
|
240
|
+
let q = p + vec2i(x, y);
|
|
241
|
+
if (any(q < vec2i(0)) || any(q >= size)) { continue; }
|
|
242
|
+
let c = channel(q);
|
|
243
|
+
if (c != 0u && c != 2u) { continue; }
|
|
244
|
+
var oriented = q;
|
|
245
|
+
if ((sensor.flip & 1u) != 0u) { oriented.x = size.x - 1 - oriented.x; }
|
|
246
|
+
if ((sensor.flip & 2u) != 0u) { oriented.y = size.y - 1 - oriented.y; }
|
|
247
|
+
if ((sensor.flip & 4u) != 0u) { oriented = oriented.yx; }
|
|
248
|
+
let rgb = textureLoad(cameraSource, oriented, 0).rgb;
|
|
249
|
+
let distance2 = f32(max(x * x + y * y, 1));
|
|
250
|
+
let w = 1.0 / (distance2 * distance2);
|
|
251
|
+
difference[c] += (rgb[c] - rgb.g) * w;
|
|
252
|
+
weight[c] += w;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
var color = center.g + difference / max(weight, vec3f(0.00001));
|
|
256
|
+
let c = channel(p);
|
|
257
|
+
color[select(c, 1u, c == 3u)] = center[select(c, 1u, c == 3u)];
|
|
258
|
+
return vec4f(calibration.matrix * color * calibration.exposure, 1.0);
|
|
259
|
+
}
|
|
260
|
+
|
|
209
261
|
// Cached X-Trans camera RGB: subsequent edits only apply per-pixel linear transforms.
|
|
210
262
|
@fragment fn fs_camera(@builtin(position) position: vec4f) -> @location(0) vec4f {
|
|
211
263
|
let camera = textureLoad(cameraSource, vec2i(position.xy), 0).rgb;
|
|
@@ -230,11 +282,12 @@ async function createPipeline(device) {
|
|
|
230
282
|
primitive: { topology: "triangle-list" }
|
|
231
283
|
});
|
|
232
284
|
}
|
|
233
|
-
const [sensor, camera] = await Promise.all([
|
|
285
|
+
const [sensor, camera, refine] = await Promise.all([
|
|
234
286
|
create("fs_main"),
|
|
235
|
-
create("fs_camera")
|
|
287
|
+
create("fs_camera"),
|
|
288
|
+
create("fs_refine")
|
|
236
289
|
]);
|
|
237
|
-
return { sensor, camera };
|
|
290
|
+
return { sensor, camera, refine };
|
|
238
291
|
}
|
|
239
292
|
function createUniform(device, data) {
|
|
240
293
|
const buffer = device.createBuffer({
|
|
@@ -395,6 +448,30 @@ function createGpuSource(device, pixels, pipeline) {
|
|
|
395
448
|
}
|
|
396
449
|
});
|
|
397
450
|
prepare.dispose();
|
|
451
|
+
const estimate = cameraTexture;
|
|
452
|
+
try {
|
|
453
|
+
cameraTexture = device.createTexture({
|
|
454
|
+
size,
|
|
455
|
+
format: "rgba16float",
|
|
456
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
|
|
457
|
+
});
|
|
458
|
+
const refine = createPass(pipeline.refine, [
|
|
459
|
+
{ binding: 0, resource: texture.createView() },
|
|
460
|
+
{ binding: 1, resource: { buffer: sensor } },
|
|
461
|
+
{ binding: 4, resource: { buffer: patternBuffer } },
|
|
462
|
+
{ binding: 5, resource: estimate.createView() }
|
|
463
|
+
]);
|
|
464
|
+
refine.render({
|
|
465
|
+
destination: cameraTexture,
|
|
466
|
+
calibration: {
|
|
467
|
+
gains: [1, 1, 1],
|
|
468
|
+
matrix: [1, 0, 0, 0, 1, 0, 0, 0, 1]
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
refine.dispose();
|
|
472
|
+
} finally {
|
|
473
|
+
estimate.destroy();
|
|
474
|
+
}
|
|
398
475
|
}
|
|
399
476
|
return {
|
|
400
477
|
texture,
|
|
@@ -479,7 +556,9 @@ fn oriented(p: vec2u) -> vec2u {
|
|
|
479
556
|
}
|
|
480
557
|
}
|
|
481
558
|
|
|
482
|
-
|
|
559
|
+
override workgroupRows: u32 = 16;
|
|
560
|
+
|
|
561
|
+
@compute @workgroup_size(16, workgroupRows) fn main(@builtin(global_invocation_id) id: vec3u) {
|
|
483
562
|
if id.x >= params.width || id.y >= params.rows { return; }
|
|
484
563
|
let offset = id.y * params.rowBytes + id.x * params.channels * params.bytes;
|
|
485
564
|
|
|
@@ -508,14 +587,19 @@ function createFilledBuffer(device, data, usage) {
|
|
|
508
587
|
usage,
|
|
509
588
|
mappedAtCreation: true
|
|
510
589
|
});
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
590
|
+
try {
|
|
591
|
+
new Uint8Array(result.getMappedRange()).set(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
|
|
592
|
+
result.unmap();
|
|
593
|
+
return result;
|
|
594
|
+
} catch (error) {
|
|
595
|
+
result.destroy();
|
|
596
|
+
throw error;
|
|
597
|
+
}
|
|
514
598
|
}
|
|
515
599
|
function createParams(pixels, startRow, rows) {
|
|
516
600
|
const params = new ArrayBuffer(96);
|
|
517
601
|
const integers = new Uint32Array(params);
|
|
518
|
-
integers.set(pixels.metadata);
|
|
602
|
+
integers.set(pixels.metadata.subarray(0, 9));
|
|
519
603
|
integers[9] = startRow;
|
|
520
604
|
integers[10] = rows;
|
|
521
605
|
integers[11] = Number(pixels.bigEndian);
|
|
@@ -525,14 +609,16 @@ function createParams(pixels, startRow, rows) {
|
|
|
525
609
|
}
|
|
526
610
|
return new Uint8Array(params);
|
|
527
611
|
}
|
|
528
|
-
async function uploadTiff(device, pixels) {
|
|
612
|
+
async function uploadTiff(device, pixels, signal) {
|
|
613
|
+
const workgroupRows = Math.min(16, Math.floor(device.limits.maxComputeInvocationsPerWorkgroup / 16));
|
|
529
614
|
let pending = pipelines.get(device);
|
|
530
615
|
if (!pending) {
|
|
531
616
|
pending = device.createComputePipelineAsync({
|
|
532
617
|
layout: "auto",
|
|
533
618
|
compute: {
|
|
534
619
|
module: device.createShaderModule({ code: upload_default }),
|
|
535
|
-
entryPoint: "main"
|
|
620
|
+
entryPoint: "main",
|
|
621
|
+
constants: { workgroupRows }
|
|
536
622
|
}
|
|
537
623
|
}).catch((error) => {
|
|
538
624
|
pipelines.delete(device);
|
|
@@ -541,6 +627,7 @@ async function uploadTiff(device, pixels) {
|
|
|
541
627
|
pipelines.set(device, pending);
|
|
542
628
|
}
|
|
543
629
|
const pipeline = await pending;
|
|
630
|
+
signal?.throwIfAborted();
|
|
544
631
|
const { data, metadata, color } = pixels;
|
|
545
632
|
const [width, height, , , , orientation, , , rowBytes] = metadata;
|
|
546
633
|
const size = orientation >= 5 ? [height, width] : [width, height];
|
|
@@ -556,13 +643,15 @@ async function uploadTiff(device, pixels) {
|
|
|
556
643
|
format: "rgba16float",
|
|
557
644
|
usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC
|
|
558
645
|
});
|
|
559
|
-
|
|
646
|
+
let curves;
|
|
560
647
|
try {
|
|
648
|
+
curves = createFilledBuffer(device, color.table, GPUBufferUsage.STORAGE);
|
|
561
649
|
for (let y = 0;y < height; y += rowsPerBand) {
|
|
562
650
|
const rows = Math.min(rowsPerBand, height - y);
|
|
563
651
|
const source = createFilledBuffer(device, data.subarray(y * rowBytes, (y + rows) * rowBytes), GPUBufferUsage.STORAGE);
|
|
564
|
-
|
|
652
|
+
let params;
|
|
565
653
|
try {
|
|
654
|
+
params = createFilledBuffer(device, createParams(pixels, y, rows), GPUBufferUsage.UNIFORM);
|
|
566
655
|
const bindings = device.createBindGroup({
|
|
567
656
|
layout: pipeline.getBindGroupLayout(0),
|
|
568
657
|
entries: [
|
|
@@ -576,12 +665,12 @@ async function uploadTiff(device, pixels) {
|
|
|
576
665
|
const pass = encoder.beginComputePass();
|
|
577
666
|
pass.setPipeline(pipeline);
|
|
578
667
|
pass.setBindGroup(0, bindings);
|
|
579
|
-
pass.dispatchWorkgroups(Math.ceil(width / 16), Math.ceil(rows /
|
|
668
|
+
pass.dispatchWorkgroups(Math.ceil(width / 16), Math.ceil(rows / workgroupRows));
|
|
580
669
|
pass.end();
|
|
581
670
|
device.queue.submit([encoder.finish()]);
|
|
582
671
|
} finally {
|
|
583
672
|
source.destroy();
|
|
584
|
-
params
|
|
673
|
+
params?.destroy();
|
|
585
674
|
}
|
|
586
675
|
}
|
|
587
676
|
return { texture, size, dispose: () => texture.destroy() };
|
|
@@ -589,21 +678,20 @@ async function uploadTiff(device, pixels) {
|
|
|
589
678
|
texture.destroy();
|
|
590
679
|
throw error;
|
|
591
680
|
} finally {
|
|
592
|
-
curves
|
|
681
|
+
curves?.destroy();
|
|
593
682
|
}
|
|
594
683
|
}
|
|
595
684
|
|
|
596
685
|
// src/tiff/index.ts
|
|
597
|
-
async function decodeTiff(device, file, { signal } = {}) {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
686
|
+
async function decodeTiff(device, file, { signal: requestedSignal } = {}) {
|
|
687
|
+
const signal = requestedSignal ? AbortSignal.any([requestedSignal, deviceSignal(device)]) : deviceSignal(device);
|
|
688
|
+
signal.throwIfAborted();
|
|
601
689
|
const worker = new Worker(new URL("./tiff-worker.js", import.meta.url), {
|
|
602
690
|
type: "module"
|
|
603
691
|
});
|
|
604
692
|
const aborted = Promise.withResolvers();
|
|
605
|
-
const abort = () => aborted.reject(signal
|
|
606
|
-
signal
|
|
693
|
+
const abort = () => aborted.reject(signal.reason);
|
|
694
|
+
signal.addEventListener("abort", abort);
|
|
607
695
|
try {
|
|
608
696
|
const module = await Promise.race([compileDecoder(), aborted.promise]);
|
|
609
697
|
const decoded = new Promise((resolve, reject) => {
|
|
@@ -622,9 +710,16 @@ async function decodeTiff(device, file, { signal } = {}) {
|
|
|
622
710
|
worker.postMessage({ file, module });
|
|
623
711
|
});
|
|
624
712
|
const pixels = await Promise.race([decoded, aborted.promise]);
|
|
625
|
-
|
|
713
|
+
const uploading = uploadTiff(device, pixels, signal).then((image) => {
|
|
714
|
+
if (signal.aborted) {
|
|
715
|
+
image.dispose();
|
|
716
|
+
throw signal.reason;
|
|
717
|
+
}
|
|
718
|
+
return image;
|
|
719
|
+
});
|
|
720
|
+
return await Promise.race([uploading, aborted.promise]);
|
|
626
721
|
} finally {
|
|
627
|
-
signal
|
|
722
|
+
signal.removeEventListener("abort", abort);
|
|
628
723
|
worker.terminate();
|
|
629
724
|
}
|
|
630
725
|
}
|
|
@@ -636,14 +731,16 @@ function createRawDecoder(device) {
|
|
|
636
731
|
let closed = false;
|
|
637
732
|
function dispose() {
|
|
638
733
|
closed = true;
|
|
734
|
+
lost.removeEventListener("abort", dispose);
|
|
639
735
|
for (const close of sources) {
|
|
640
736
|
close();
|
|
641
737
|
}
|
|
642
738
|
}
|
|
643
|
-
|
|
739
|
+
const lost = deviceSignal(device);
|
|
740
|
+
lost.addEventListener("abort", dispose, { once: true });
|
|
644
741
|
return {
|
|
645
742
|
async load(file, { signal } = {}) {
|
|
646
|
-
if (closed) {
|
|
743
|
+
if (closed || lost.aborted) {
|
|
647
744
|
throw Error("RAW decoder is closed.");
|
|
648
745
|
}
|
|
649
746
|
if (signal?.aborted) {
|
package/dist/libraw.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
async function Module(moduleArg={}){var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName=import.meta.url;var scriptDirectory="";function locateFile(path){return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var runtimeInitialized=false;function getMemoryBuffer(){return wasmMemory.buffer}function updateMemoryViews(){if(HEAP8?.buffer?.resizable)return;var b=getMemoryBuffer();HEAP8=new Int8Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}function preRun(){}function initRuntime(){runtimeInitialized=true;wasmExports["s"]()}function postRun(){}function abort(what){what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";if(runtimeInitialized){___trap()}var e=new WebAssembly.RuntimeError(what);throw e}var wasmBinaryFile;function findWasmBinary(){if(Module["locateFile"]){return locateFile("libraw.wasm")}return new URL("libraw.wasm",import.meta.url).href}function getBinarySync(file){if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();var instantiateWasm=Module["instantiateWasm"];if(instantiateWasm){return new Promise(resolve=>{instantiateWasm(info,inst=>resolve(receiveInstance(inst)))})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP8;var UTF8Decoder=new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var HEAPU8;var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>{if(!ptr)return"";var end=findStringEnd(HEAPU8,ptr,maxBytesToRead,ignoreNul);return UTF8Decoder.decode(HEAPU8.subarray(ptr,end))};var SYSCALLS={varargs:undefined,getStr(ptr){var ret=UTF8ToString(ptr);return ret}};function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;return 0}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;return 0}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs}var __abort_js=()=>abort("");var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{runtimeKeepaliveCounter=0};var isLeapYear=year=>year%4===0&&(year%100!==0||year%400===0);var MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335];var MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334];var ydayFromDate=date=>{var leap=isLeapYear(date.getFullYear());var monthDaysCumulative=leap?MONTH_DAYS_LEAP_CUMULATIVE:MONTH_DAYS_REGULAR_CUMULATIVE;var yday=monthDaysCumulative[date.getMonth()]+date.getDate()-1;return yday};var INT53_MAX=9007199254740992;var INT53_MIN=-9007199254740992;var bigintToI53Checked=num=>num<INT53_MIN||num>INT53_MAX?NaN:Number(num);var HEAP32;function __localtime_js(time,tmPtr){time=bigintToI53Checked(time);var date=new Date(time*1e3);if(isNaN(date.getTime())){return 1}HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var yday=ydayFromDate(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst;return 0}var __mktime_js=function(tmPtr){var ret=(()=>{var date=new Date(HEAP32[tmPtr+20>>2]+1900,HEAP32[tmPtr+16>>2],HEAP32[tmPtr+12>>2],HEAP32[tmPtr+8>>2],HEAP32[tmPtr+4>>2],HEAP32[tmPtr>>2],0);if(isNaN(date.getTime())){return-1}var dst=HEAP32[tmPtr+32>>2];var guessedOffset=date.getTimezoneOffset();var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dstOffset=Math.min(winterOffset,summerOffset);if(dst<0){dst=Number(summerOffset!=winterOffset&&dstOffset==guessedOffset)}else if(dst>0!=(dstOffset==guessedOffset)){var nonDstOffset=Math.max(winterOffset,summerOffset);var trueOffset=dst>0?dstOffset:nonDstOffset;date.setTime(date.getTime()+(trueOffset-guessedOffset)*6e4);if(isNaN(date.getTime())){return-1}}HEAP32[tmPtr+32>>2]=dst;HEAP32[tmPtr+24>>2]=date.getDay();var yday=ydayFromDate(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getYear();return date.getTime()/1e3})();return BigInt(ret)};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>true;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i<str.length;++i){var u=str.codePointAt(i);if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var HEAPU32;var __tzset_js=(timezone,daylight,std_name,dst_name)=>{var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);var extractZone=timezoneOffset=>{var sign=timezoneOffset>=0?"-":"+";var absOffset=Math.abs(timezoneOffset);var hours=String(Math.floor(absOffset/60)).padStart(2,"0");var minutes=String(absOffset%60).padStart(2,"0");return`UTC${sign}${hours}${minutes}`};var winterName=extractZone(winterOffset);var summerName=extractZone(summerOffset);if(summerOffset<winterOffset){stringToUTF8(winterName,std_name,17);stringToUTF8(summerName,dst_name,17)}else{stringToUTF8(winterName,dst_name,17);stringToUTF8(summerName,std_name,17)}};var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var ENV={};var getExecutableName=()=>thisProgram;var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(globalThis.navigator?.language??"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var _environ_get=(__environ,environ_buf)=>{var bufSize=0;var envp=0;for(var string of getEnvStrings()){var ptr=environ_buf+bufSize;HEAPU32[__environ+envp>>2]=ptr;bufSize+=stringToUTF8(string,ptr,Infinity)+1;envp+=4}return 0};var lengthBytesUTF8=str=>{var len=0;for(var i=0;i<str.length;++i){var c=str.charCodeAt(i);if(c<=127){len++}else if(c<=2047){len+=2}else if(c>=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var _environ_sizes_get=(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;for(var string of strings){bufSize+=lengthBytesUTF8(string)+1}HEAPU32[penviron_buf_size>>2]=bufSize;return 0};var _fd_close=fd=>52;var _fd_read=(fd,iov,iovcnt,pnum)=>52;function _fd_seek(fd,offset,whence,newOffset){offset=bigintToI53Checked(offset);return 70}var printCharBuffers=[null,[],[]];var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);return UTF8Decoder.decode(heapOrArray.buffer?heapOrArray.subarray(idx,endPtr):new Uint8Array(heapOrArray.slice(idx,endPtr)))};var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(!curr||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i<iovcnt;i++){var ptr=HEAPU32[iov>>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j<len;j++){printChar(fd,HEAPU8[ptr+j])}num+=len}HEAPU32[pnum>>2]=num;return 0};var HEAPU16;var HEAPF64;{}Module["UTF8ToString"]=UTF8ToString;var _raw_error,_raw_open,_raw_calibration,_raw_original_offset,_raw_width,_raw_height,_raw_flip,_raw_cpu_demosaic,_raw_float,_raw_daylight_balance,_raw_mosaic,_raw_cfa_size,_raw_cfa,_raw_black,_raw_vignette,_raw_white,_raw_pixels,_raw_free_pixels,_raw_temperature,_raw_tint,_raw_close,_tiff_open,_tiff_error,_tiff_metadata,_tiff_original_offset,_tiff_big_endian,_tiff_pixels,_tiff_profile,_tiff_profile_size,_tiff_close,_malloc,_free,__emscripten_timeout,___trap,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_raw_error=Module["_raw_error"]=wasmExports["t"];_raw_open=Module["_raw_open"]=wasmExports["u"];_raw_calibration=Module["_raw_calibration"]=wasmExports["v"];_raw_original_offset=Module["_raw_original_offset"]=wasmExports["w"];_raw_width=Module["_raw_width"]=wasmExports["x"];_raw_height=Module["_raw_height"]=wasmExports["y"];_raw_flip=Module["_raw_flip"]=wasmExports["z"];_raw_cpu_demosaic=Module["_raw_cpu_demosaic"]=wasmExports["A"];_raw_float=Module["_raw_float"]=wasmExports["B"];_raw_daylight_balance=Module["_raw_daylight_balance"]=wasmExports["C"];_raw_mosaic=Module["_raw_mosaic"]=wasmExports["D"];_raw_cfa_size=Module["_raw_cfa_size"]=wasmExports["E"];_raw_cfa=Module["_raw_cfa"]=wasmExports["F"];_raw_black=Module["_raw_black"]=wasmExports["G"];_raw_vignette=Module["_raw_vignette"]=wasmExports["H"];_raw_white=Module["_raw_white"]=wasmExports["I"];_raw_pixels=Module["_raw_pixels"]=wasmExports["J"];_raw_free_pixels=Module["_raw_free_pixels"]=wasmExports["K"];_raw_temperature=Module["_raw_temperature"]=wasmExports["L"];_raw_tint=Module["_raw_tint"]=wasmExports["M"];_raw_close=Module["_raw_close"]=wasmExports["N"];_tiff_open=Module["_tiff_open"]=wasmExports["O"];_tiff_error=Module["_tiff_error"]=wasmExports["P"];_tiff_metadata=Module["_tiff_metadata"]=wasmExports["Q"];_tiff_original_offset=Module["_tiff_original_offset"]=wasmExports["R"];_tiff_big_endian=Module["_tiff_big_endian"]=wasmExports["S"];_tiff_pixels=Module["_tiff_pixels"]=wasmExports["T"];_tiff_profile=Module["_tiff_profile"]=wasmExports["U"];_tiff_profile_size=Module["_tiff_profile_size"]=wasmExports["V"];_tiff_close=Module["_tiff_close"]=wasmExports["W"];_malloc=Module["_malloc"]=wasmExports["X"];_free=Module["_free"]=wasmExports["Y"];__emscripten_timeout=wasmExports["Z"];___trap=wasmExports["_"];memory=wasmMemory=wasmExports["r"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={b:___syscall_fcntl64,h:___syscall_ioctl,p:___syscall_openat,q:__abort_js,l:__emscripten_runtime_keepalive_clear,m:__localtime_js,n:__mktime_js,i:__setitimer_js,o:__tzset_js,j:_emscripten_resize_heap,d:_environ_get,e:_environ_sizes_get,a:_fd_close,g:_fd_read,c:_fd_seek,f:_fd_write,k:_proc_exit};async function run(){preRun();if(ABORT)return;initRuntime();postRun()}var wasmExports;wasmExports=await createWasm();await run();
|
|
1
|
+
async function Module(moduleArg={}){var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName=import.meta.url;var scriptDirectory="";function locateFile(path){return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var runtimeInitialized=false;function getMemoryBuffer(){return wasmMemory.buffer}function updateMemoryViews(){if(HEAP8?.buffer?.resizable)return;var b=getMemoryBuffer();HEAP8=new Int8Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}function preRun(){}function initRuntime(){runtimeInitialized=true;wasmExports["t"]()}function postRun(){}function abort(what){what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";if(runtimeInitialized){___trap()}var e=new WebAssembly.RuntimeError(what);throw e}var wasmBinaryFile;function findWasmBinary(){if(Module["locateFile"]){return locateFile("libraw.wasm")}return new URL("libraw.wasm",import.meta.url).href}function getBinarySync(file){if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();var instantiateWasm=Module["instantiateWasm"];if(instantiateWasm){return new Promise(resolve=>{instantiateWasm(info,inst=>resolve(receiveInstance(inst)))})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP8;var UTF8Decoder=new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var HEAPU8;var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>{if(!ptr)return"";var end=findStringEnd(HEAPU8,ptr,maxBytesToRead,ignoreNul);return UTF8Decoder.decode(HEAPU8.subarray(ptr,end))};var SYSCALLS={varargs:undefined,getStr(ptr){var ret=UTF8ToString(ptr);return ret}};function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;return 0}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;return 0}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs}var __abort_js=()=>abort("");var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{runtimeKeepaliveCounter=0};var isLeapYear=year=>year%4===0&&(year%100!==0||year%400===0);var MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335];var MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334];var ydayFromDate=date=>{var leap=isLeapYear(date.getFullYear());var monthDaysCumulative=leap?MONTH_DAYS_LEAP_CUMULATIVE:MONTH_DAYS_REGULAR_CUMULATIVE;var yday=monthDaysCumulative[date.getMonth()]+date.getDate()-1;return yday};var INT53_MAX=9007199254740992;var INT53_MIN=-9007199254740992;var bigintToI53Checked=num=>num<INT53_MIN||num>INT53_MAX?NaN:Number(num);var HEAP32;function __localtime_js(time,tmPtr){time=bigintToI53Checked(time);var date=new Date(time*1e3);if(isNaN(date.getTime())){return 1}HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var yday=ydayFromDate(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst;return 0}var __mktime_js=function(tmPtr){var ret=(()=>{var date=new Date(HEAP32[tmPtr+20>>2]+1900,HEAP32[tmPtr+16>>2],HEAP32[tmPtr+12>>2],HEAP32[tmPtr+8>>2],HEAP32[tmPtr+4>>2],HEAP32[tmPtr>>2],0);if(isNaN(date.getTime())){return-1}var dst=HEAP32[tmPtr+32>>2];var guessedOffset=date.getTimezoneOffset();var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dstOffset=Math.min(winterOffset,summerOffset);if(dst<0){dst=Number(summerOffset!=winterOffset&&dstOffset==guessedOffset)}else if(dst>0!=(dstOffset==guessedOffset)){var nonDstOffset=Math.max(winterOffset,summerOffset);var trueOffset=dst>0?dstOffset:nonDstOffset;date.setTime(date.getTime()+(trueOffset-guessedOffset)*6e4);if(isNaN(date.getTime())){return-1}}HEAP32[tmPtr+32>>2]=dst;HEAP32[tmPtr+24>>2]=date.getDay();var yday=ydayFromDate(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getYear();return date.getTime()/1e3})();return BigInt(ret)};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>true;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i<str.length;++i){var u=str.codePointAt(i);if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var HEAPU32;var __tzset_js=(timezone,daylight,std_name,dst_name)=>{var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);var extractZone=timezoneOffset=>{var sign=timezoneOffset>=0?"-":"+";var absOffset=Math.abs(timezoneOffset);var hours=String(Math.floor(absOffset/60)).padStart(2,"0");var minutes=String(absOffset%60).padStart(2,"0");return`UTC${sign}${hours}${minutes}`};var winterName=extractZone(winterOffset);var summerName=extractZone(summerOffset);if(summerOffset<winterOffset){stringToUTF8(winterName,std_name,17);stringToUTF8(summerName,dst_name,17)}else{stringToUTF8(winterName,dst_name,17);stringToUTF8(summerName,std_name,17)}};var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var ENV={};var getExecutableName=()=>thisProgram;var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(globalThis.navigator?.language??"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var _environ_get=(__environ,environ_buf)=>{var bufSize=0;var envp=0;for(var string of getEnvStrings()){var ptr=environ_buf+bufSize;HEAPU32[__environ+envp>>2]=ptr;bufSize+=stringToUTF8(string,ptr,Infinity)+1;envp+=4}return 0};var lengthBytesUTF8=str=>{var len=0;for(var i=0;i<str.length;++i){var c=str.charCodeAt(i);if(c<=127){len++}else if(c<=2047){len+=2}else if(c>=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var _environ_sizes_get=(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;for(var string of strings){bufSize+=lengthBytesUTF8(string)+1}HEAPU32[penviron_buf_size>>2]=bufSize;return 0};var _fd_close=fd=>52;var _fd_read=(fd,iov,iovcnt,pnum)=>52;function _fd_seek(fd,offset,whence,newOffset){offset=bigintToI53Checked(offset);return 70}var printCharBuffers=[null,[],[]];var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);return UTF8Decoder.decode(heapOrArray.buffer?heapOrArray.subarray(idx,endPtr):new Uint8Array(heapOrArray.slice(idx,endPtr)))};var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(!curr||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i<iovcnt;i++){var ptr=HEAPU32[iov>>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j<len;j++){printChar(fd,HEAPU8[ptr+j])}num+=len}HEAPU32[pnum>>2]=num;return 0};var HEAPU16;var HEAPF64;{}Module["UTF8ToString"]=UTF8ToString;function read_tiff_bytes(destination,offset,count){HEAPU8.set(Module["tiffBytes"].subarray(offset,offset+count),destination)}var _raw_error,_raw_open,_raw_calibration,_raw_original_offset,_raw_width,_raw_height,_raw_flip,_raw_cpu_demosaic,_raw_float,_raw_daylight_balance,_raw_mosaic,_raw_cfa_size,_raw_cfa,_raw_black,_raw_vignette,_raw_white,_raw_pixels,_raw_free_pixels,_raw_temperature,_raw_tint,_raw_close,_tiff_open,_tiff_error,_tiff_metadata,_tiff_big_endian,_tiff_predict,_tiff_strips,_tiff_strip_count,_tiff_pixels,_tiff_profile,_tiff_profile_size,_tiff_close,_malloc,_free,__emscripten_timeout,___trap,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_raw_error=Module["_raw_error"]=wasmExports["u"];_raw_open=Module["_raw_open"]=wasmExports["v"];_raw_calibration=Module["_raw_calibration"]=wasmExports["w"];_raw_original_offset=Module["_raw_original_offset"]=wasmExports["x"];_raw_width=Module["_raw_width"]=wasmExports["y"];_raw_height=Module["_raw_height"]=wasmExports["z"];_raw_flip=Module["_raw_flip"]=wasmExports["A"];_raw_cpu_demosaic=Module["_raw_cpu_demosaic"]=wasmExports["B"];_raw_float=Module["_raw_float"]=wasmExports["C"];_raw_daylight_balance=Module["_raw_daylight_balance"]=wasmExports["D"];_raw_mosaic=Module["_raw_mosaic"]=wasmExports["E"];_raw_cfa_size=Module["_raw_cfa_size"]=wasmExports["F"];_raw_cfa=Module["_raw_cfa"]=wasmExports["G"];_raw_black=Module["_raw_black"]=wasmExports["H"];_raw_vignette=Module["_raw_vignette"]=wasmExports["I"];_raw_white=Module["_raw_white"]=wasmExports["J"];_raw_pixels=Module["_raw_pixels"]=wasmExports["K"];_raw_free_pixels=Module["_raw_free_pixels"]=wasmExports["L"];_raw_temperature=Module["_raw_temperature"]=wasmExports["M"];_raw_tint=Module["_raw_tint"]=wasmExports["N"];_raw_close=Module["_raw_close"]=wasmExports["O"];_tiff_open=Module["_tiff_open"]=wasmExports["P"];_tiff_error=Module["_tiff_error"]=wasmExports["Q"];_tiff_metadata=Module["_tiff_metadata"]=wasmExports["R"];_tiff_big_endian=Module["_tiff_big_endian"]=wasmExports["S"];_tiff_predict=Module["_tiff_predict"]=wasmExports["T"];_tiff_strips=Module["_tiff_strips"]=wasmExports["U"];_tiff_strip_count=Module["_tiff_strip_count"]=wasmExports["V"];_tiff_pixels=Module["_tiff_pixels"]=wasmExports["W"];_tiff_profile=Module["_tiff_profile"]=wasmExports["X"];_tiff_profile_size=Module["_tiff_profile_size"]=wasmExports["Y"];_tiff_close=Module["_tiff_close"]=wasmExports["Z"];_malloc=Module["_malloc"]=wasmExports["_"];_free=Module["_free"]=wasmExports["$"];__emscripten_timeout=wasmExports["aa"];___trap=wasmExports["ba"];memory=wasmMemory=wasmExports["s"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={b:___syscall_fcntl64,g:___syscall_ioctl,h:___syscall_openat,q:__abort_js,l:__emscripten_runtime_keepalive_clear,m:__localtime_js,n:__mktime_js,i:__setitimer_js,o:__tzset_js,j:_emscripten_resize_heap,c:_environ_get,d:_environ_sizes_get,a:_fd_close,f:_fd_read,p:_fd_seek,e:_fd_write,k:_proc_exit,r:read_tiff_bytes};async function run(){preRun();if(ABORT)return;initRuntime();postRun()}var wasmExports;wasmExports=await createWasm();await run();
|
|
2
2
|
;return Module}export default Module;
|
package/dist/libraw.wasm
CHANGED
|
Binary file
|
package/dist/tiff/color.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ type Profile = {
|
|
|
6
6
|
};
|
|
7
7
|
/**
|
|
8
8
|
* Reads a matrix/TRC profile: RGB colorants and a curve per channel, or a single curve for gray.
|
|
9
|
-
* Unsupported lookup-table profiles and malformed profiles
|
|
9
|
+
* Unsupported lookup-table profiles and malformed profiles are not accepted.
|
|
10
10
|
*/
|
|
11
11
|
export declare function readProfile(bytes: Uint8Array): Profile | undefined;
|
|
12
12
|
/** ICC curves and camera-independent conversion into linear Rec.2020. */
|
package/dist/tiff/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { LoadOptions } from "../types";
|
|
2
2
|
/** Decode TIFF into an owned linear Rec.2020 RGBA16F texture, with straight alpha. */
|
|
3
|
-
export declare function decodeTiff(device: GPUDevice, file: Blob, { signal }?: LoadOptions): Promise<{
|
|
3
|
+
export declare function decodeTiff(device: GPUDevice, file: Blob, { signal: requestedSignal }?: LoadOptions): Promise<{
|
|
4
4
|
texture: GPUTexture;
|
|
5
5
|
size: [number, number];
|
|
6
6
|
dispose: () => undefined;
|
package/dist/tiff/upload.d.ts
CHANGED
|
@@ -2,9 +2,9 @@ export type TiffPixels = {
|
|
|
2
2
|
/** Interleaved samples, one row after another, exactly as the file or the SDK laid them out. */
|
|
3
3
|
data: Uint8Array;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Eleven values from native/tiff.cpp: width, height, channels, bytes per sample,
|
|
6
6
|
* bits per sample (zero for float), orientation, photometric interpretation,
|
|
7
|
-
* extra-sample kind (1 for associated alpha), row bytes.
|
|
7
|
+
* extra-sample kind (1 for associated alpha), row bytes, compression, predictor.
|
|
8
8
|
*/
|
|
9
9
|
metadata: Uint32Array;
|
|
10
10
|
/** Whether the samples still carry the file's big-endian byte order. */
|
|
@@ -14,7 +14,7 @@ export type TiffPixels = {
|
|
|
14
14
|
matrix: number[];
|
|
15
15
|
};
|
|
16
16
|
};
|
|
17
|
-
export declare function uploadTiff(device: GPUDevice, pixels: TiffPixels): Promise<{
|
|
17
|
+
export declare function uploadTiff(device: GPUDevice, pixels: TiffPixels, signal?: AbortSignal): Promise<{
|
|
18
18
|
texture: GPUTexture;
|
|
19
19
|
size: [number, number];
|
|
20
20
|
dispose: () => undefined;
|
package/dist/tiff-worker.js
CHANGED
|
@@ -48,20 +48,38 @@ function readProfile(bytes) {
|
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
50
|
const tags = new Map;
|
|
51
|
+
if (view.getUint32(128) > (bytes.length - 132) / 12) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
51
54
|
for (let i = 0, count = view.getUint32(128);i < count; i++) {
|
|
52
|
-
|
|
55
|
+
const at = view.getUint32(136 + i * 12);
|
|
56
|
+
const size = view.getUint32(140 + i * 12);
|
|
57
|
+
if (at < 128 || size < 8 || at > bytes.length - size) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
tags.set(text(132 + i * 12), { at, size });
|
|
61
|
+
}
|
|
62
|
+
if ([...tags.keys()].some((name) => /^(A2B|B2A|D2B|B2D)/.test(name))) {
|
|
63
|
+
return;
|
|
53
64
|
}
|
|
54
65
|
const xyz = (name) => {
|
|
55
|
-
const
|
|
56
|
-
|
|
66
|
+
const tag = tags.get(name);
|
|
67
|
+
if (!tag || tag.size < 20 || text(tag.at) !== "XYZ ") {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
return [fixed(tag.at + 8), fixed(tag.at + 12), fixed(tag.at + 16)];
|
|
57
71
|
};
|
|
58
72
|
const curve = (name) => {
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
73
|
+
const tag = tags.get(name);
|
|
74
|
+
if (!tag || tag.size < 12) {
|
|
61
75
|
return;
|
|
62
76
|
}
|
|
77
|
+
const { at, size } = tag;
|
|
63
78
|
if (text(at) === "curv") {
|
|
64
79
|
const count = view.getUint32(at + 8);
|
|
80
|
+
if (count > (size - 12) / 2) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
65
83
|
const entry = (i) => view.getUint16(at + 12 + 2 * Math.min(i, count - 1)) / 65535;
|
|
66
84
|
if (count === 0) {
|
|
67
85
|
return identity;
|
|
@@ -78,7 +96,11 @@ function readProfile(bytes) {
|
|
|
78
96
|
}
|
|
79
97
|
if (text(at) === "para") {
|
|
80
98
|
const kind = view.getUint16(at + 8);
|
|
81
|
-
const
|
|
99
|
+
const count = [1, 3, 4, 5, 7][kind];
|
|
100
|
+
if (!count || count > (size - 12) / 4) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const [g, a = 1, b = 0, c = 0, d = 0, e = 0, f = 0] = Array.from({ length: count }, (_, i) => fixed(at + 12 + i * 4));
|
|
82
104
|
if (kind === 0) {
|
|
83
105
|
return (x) => x ** g;
|
|
84
106
|
}
|
|
@@ -111,11 +133,13 @@ function readProfile(bytes) {
|
|
|
111
133
|
}
|
|
112
134
|
var sampleCurves = (curves) => Float32Array.from({ length: 3 * tableSize }, (_, i) => curves[Math.floor(i / tableSize)](i % tableSize / (tableSize - 1)));
|
|
113
135
|
function tiffColor(icc, linear) {
|
|
114
|
-
|
|
115
|
-
|
|
136
|
+
const profile = icc ? readProfile(icc) : srgb;
|
|
137
|
+
if (!profile) {
|
|
138
|
+
throw Error("Unsupported or malformed TIFF ICC profile.");
|
|
139
|
+
}
|
|
140
|
+
const table = sampleCurves(linear ? [identity, identity, identity] : profile.curves);
|
|
116
141
|
if (!table.every(Number.isFinite)) {
|
|
117
|
-
|
|
118
|
-
table = sampleCurves(profile.curves);
|
|
142
|
+
throw Error("Malformed TIFF ICC transfer curve.");
|
|
119
143
|
}
|
|
120
144
|
return {
|
|
121
145
|
table,
|
|
@@ -123,6 +147,65 @@ function tiffColor(icc, linear) {
|
|
|
123
147
|
};
|
|
124
148
|
}
|
|
125
149
|
|
|
150
|
+
// src/tiff/strips.ts
|
|
151
|
+
async function readStrips(bytes, strips, byteLength, compressed) {
|
|
152
|
+
if (!compressed) {
|
|
153
|
+
let end = strips[0];
|
|
154
|
+
let contiguous = true;
|
|
155
|
+
for (let i = 0;i < strips.length; i += 3) {
|
|
156
|
+
contiguous &&= strips[i] === end;
|
|
157
|
+
end += strips[i + 2];
|
|
158
|
+
}
|
|
159
|
+
if (contiguous && end - strips[0] === byteLength) {
|
|
160
|
+
return bytes.subarray(strips[0], end);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const output = new Uint8Array(byteLength);
|
|
164
|
+
let next = 0;
|
|
165
|
+
let destination = 0;
|
|
166
|
+
await Promise.all(Array.from({ length: Math.min(8, strips.length / 3) }, async () => {
|
|
167
|
+
while (next < strips.length) {
|
|
168
|
+
const [offset, length, expected] = strips.subarray(next, next + 3);
|
|
169
|
+
const start = destination;
|
|
170
|
+
next += 3;
|
|
171
|
+
destination += expected;
|
|
172
|
+
if (destination > byteLength) {
|
|
173
|
+
throw Error("Invalid TIFF strip layout.");
|
|
174
|
+
}
|
|
175
|
+
if (!compressed) {
|
|
176
|
+
output.set(bytes.subarray(offset, offset + expected), start);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const stream = new Blob([bytes.subarray(offset, offset + length)]).stream().pipeThrough(new DecompressionStream("deflate"));
|
|
180
|
+
const reader = stream.getReader();
|
|
181
|
+
let written = 0;
|
|
182
|
+
try {
|
|
183
|
+
while (true) {
|
|
184
|
+
const { value, done } = await reader.read();
|
|
185
|
+
if (done) {
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
if (written + value.length > expected) {
|
|
189
|
+
await reader.cancel();
|
|
190
|
+
throw Error("TIFF strip exceeds its expected size.");
|
|
191
|
+
}
|
|
192
|
+
output.set(value, start + written);
|
|
193
|
+
written += value.length;
|
|
194
|
+
}
|
|
195
|
+
if (written !== expected) {
|
|
196
|
+
throw Error("Truncated TIFF strip.");
|
|
197
|
+
}
|
|
198
|
+
} finally {
|
|
199
|
+
reader.releaseLock();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}));
|
|
203
|
+
if (destination !== byteLength) {
|
|
204
|
+
throw Error("Incomplete TIFF strip layout.");
|
|
205
|
+
}
|
|
206
|
+
return output;
|
|
207
|
+
}
|
|
208
|
+
|
|
126
209
|
// src/decode/instantiate.ts
|
|
127
210
|
import createLibRaw from "./libraw.js";
|
|
128
211
|
function instantiateDecoder(module) {
|
|
@@ -143,24 +226,38 @@ self.onmessage = async ({
|
|
|
143
226
|
try {
|
|
144
227
|
const raw = await instantiateDecoder(module);
|
|
145
228
|
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
throw Error("Not enough memory to open TIFF.");
|
|
229
|
+
if (bytes.length > 4294967295) {
|
|
230
|
+
throw Error("TIFF files larger than 4 GiB are not supported.");
|
|
149
231
|
}
|
|
232
|
+
raw.tiffBytes = bytes;
|
|
150
233
|
let source = 0;
|
|
151
234
|
try {
|
|
152
|
-
raw.
|
|
153
|
-
source = raw._tiff_open(pointer, bytes.length);
|
|
235
|
+
source = raw._tiff_open(bytes.length, typeof DecompressionStream !== "undefined");
|
|
154
236
|
if (!source) {
|
|
155
237
|
throw Error(raw.UTF8ToString(raw._tiff_error()));
|
|
156
238
|
}
|
|
157
239
|
const metadataIndex = raw._tiff_metadata(source) / 4;
|
|
158
|
-
const metadata = raw.HEAPU32.slice(metadataIndex, metadataIndex +
|
|
240
|
+
const metadata = raw.HEAPU32.slice(metadataIndex, metadataIndex + 11);
|
|
159
241
|
const [, height, , , bitsPerSample, , , , rowBytes] = metadata;
|
|
160
242
|
const byteLength = height * rowBytes;
|
|
161
|
-
const originalOffset = raw._tiff_original_offset(source);
|
|
162
243
|
const pixelPointer = raw._tiff_pixels(source);
|
|
163
|
-
const
|
|
244
|
+
const stripCount = raw._tiff_strip_count(source);
|
|
245
|
+
const stripIndex = raw._tiff_strips(source) / 4;
|
|
246
|
+
const strips = raw.HEAPU32.slice(stripIndex, stripIndex + stripCount * 3);
|
|
247
|
+
const data = stripCount ? await readStrips(bytes, strips, byteLength, metadata[9] !== 1) : raw.HEAPU8.slice(pixelPointer, pixelPointer + byteLength);
|
|
248
|
+
if (stripCount && metadata[10] !== 1) {
|
|
249
|
+
const pointer = raw._malloc(data.length);
|
|
250
|
+
if (!pointer) {
|
|
251
|
+
throw Error("Not enough memory for TIFF prediction.");
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
raw.HEAPU8.set(data, pointer);
|
|
255
|
+
raw._tiff_predict(source, pointer);
|
|
256
|
+
data.set(raw.HEAPU8.subarray(pointer, pointer + data.length));
|
|
257
|
+
} finally {
|
|
258
|
+
raw._free(pointer);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
164
261
|
const bigEndian = Boolean(raw._tiff_big_endian(source));
|
|
165
262
|
const profilePointer = raw._tiff_profile(source);
|
|
166
263
|
const profileSize = raw._tiff_profile_size(source);
|
|
@@ -171,7 +268,7 @@ self.onmessage = async ({
|
|
|
171
268
|
if (source) {
|
|
172
269
|
raw._tiff_close(source);
|
|
173
270
|
}
|
|
174
|
-
raw.
|
|
271
|
+
raw.tiffBytes = undefined;
|
|
175
272
|
}
|
|
176
273
|
} catch (error) {
|
|
177
274
|
self.postMessage({
|
package/native/tiff.cpp
CHANGED
|
@@ -1,19 +1,44 @@
|
|
|
1
1
|
// C entry points called from src/decode/tiff-worker.ts; see src/decode/libraw.d.ts.
|
|
2
2
|
|
|
3
|
-
#include "direct.h"
|
|
4
3
|
#include "dng_host.h"
|
|
5
4
|
#include "dng_ifd.h"
|
|
6
5
|
#include "dng_info.h"
|
|
6
|
+
#include "dng_read_image.h"
|
|
7
7
|
#include "dng_simple_image.h"
|
|
8
8
|
#include "dng_stream.h"
|
|
9
9
|
#include "dng_tag_codes.h"
|
|
10
10
|
#include "dng_tag_types.h"
|
|
11
|
+
#include <algorithm>
|
|
11
12
|
#include <emscripten/emscripten.h>
|
|
12
13
|
#include <memory>
|
|
13
14
|
#include <stdexcept>
|
|
14
15
|
#include <string>
|
|
15
16
|
#include <vector>
|
|
16
17
|
|
|
18
|
+
// The SDK reads headers and compressed blocks on demand. Uncompressed pixels stay
|
|
19
|
+
// in the worker's original ArrayBuffer instead of being copied into WASM first.
|
|
20
|
+
EM_JS(void, read_tiff_bytes, (void *destination, unsigned offset, unsigned count),
|
|
21
|
+
{ HEAPU8.set(Module["tiffBytes"].subarray(offset, offset + count), destination); });
|
|
22
|
+
|
|
23
|
+
class TiffStream final : public dng_stream {
|
|
24
|
+
uint64 length;
|
|
25
|
+
|
|
26
|
+
public:
|
|
27
|
+
explicit TiffStream(unsigned size) : length(size) {}
|
|
28
|
+
|
|
29
|
+
protected:
|
|
30
|
+
uint64 DoGetLength() override {
|
|
31
|
+
return length;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
void DoRead(void *data, uint32 count, uint64 offset) override {
|
|
35
|
+
if (offset > length || count > length - offset) {
|
|
36
|
+
throw std::runtime_error("TIFF read exceeds the file size");
|
|
37
|
+
}
|
|
38
|
+
read_tiff_bytes(data, unsigned(offset), count);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
17
42
|
// Plain TIFF parsing with two additions: reject samples the SDK would silently clamp, and keep
|
|
18
43
|
// the embedded ICC profile.
|
|
19
44
|
class TiffInfo : public dng_info {
|
|
@@ -49,24 +74,63 @@ class TiffInfo : public dng_info {
|
|
|
49
74
|
|
|
50
75
|
struct Tiff {
|
|
51
76
|
std::unique_ptr<dng_simple_image> image;
|
|
52
|
-
dng_pixel_buffer pixels; //
|
|
77
|
+
dng_pixel_buffer pixels; // SDK image data, or just row layout for browser strips.
|
|
53
78
|
std::vector<uint8> profile;
|
|
79
|
+
std::vector<uint32> strips; // File offset, stored bytes, decoded bytes per strip.
|
|
54
80
|
// Mirrored by TiffPixels.metadata in src/tiff/upload.ts: width, height, channels, bytes per
|
|
55
81
|
// sample, bits per sample (zero for float), orientation, photometric, extra-sample kind,
|
|
56
|
-
// row bytes.
|
|
57
|
-
uint32 metadata[
|
|
58
|
-
uint32 originalOffset = 0;
|
|
82
|
+
// row bytes, compression, predictor.
|
|
83
|
+
uint32 metadata[11];
|
|
59
84
|
bool bigEndian = false;
|
|
60
85
|
};
|
|
61
86
|
|
|
87
|
+
// Simple strips keep their file layout for direct upload or browser-native Deflate.
|
|
88
|
+
// Planar/tiled storage and other predictors retain SDK decoding.
|
|
89
|
+
static std::vector<uint32> browser_strips(const dng_ifd &ifd, dng_stream &stream, bool deflate) {
|
|
90
|
+
if ((!deflate && ifd.fCompression != 1) ||
|
|
91
|
+
(ifd.fCompression != 1 && ifd.fCompression != 8 && ifd.fCompression != 32946) ||
|
|
92
|
+
(ifd.fPredictor != 1 &&
|
|
93
|
+
(ifd.fPredictor != 2 || stream.BigEndian() || ifd.fSampleFormat[0] != 1)) ||
|
|
94
|
+
ifd.fPlanarConfiguration != 1 || ifd.fFillOrder != 1 || !ifd.fUsesStrips ||
|
|
95
|
+
!ifd.fTileLength || ifd.fTileOffsetsCount != ifd.TilesPerImage() ||
|
|
96
|
+
ifd.fTileByteCountsCount != ifd.fTileOffsetsCount ||
|
|
97
|
+
ifd.fBitsPerSample[0] != TagTypeSize(ifd.PixelType()) * 8) {
|
|
98
|
+
return {};
|
|
99
|
+
}
|
|
100
|
+
std::vector<uint32> strips;
|
|
101
|
+
for (uint32 i = 0; i < ifd.fTileOffsetsCount; ++i) {
|
|
102
|
+
stream.SetReadPosition(ifd.fTileOffsetsOffset +
|
|
103
|
+
uint64(i) * TagTypeSize(ifd.fTileOffsetsType));
|
|
104
|
+
const auto offset = stream.TagValue_uint64(ifd.fTileOffsetsType);
|
|
105
|
+
stream.SetReadPosition(ifd.fTileByteCountsOffset +
|
|
106
|
+
uint64(i) * TagTypeSize(ifd.fTileByteCountsType));
|
|
107
|
+
const auto count = stream.TagValue_uint64(ifd.fTileByteCountsType);
|
|
108
|
+
const auto rows = std::min(uint64(ifd.fTileLength),
|
|
109
|
+
uint64(ifd.fImageLength) - uint64(i) * ifd.fTileLength);
|
|
110
|
+
const auto decoded =
|
|
111
|
+
rows * ifd.fImageWidth * ifd.fSamplesPerPixel * TagTypeSize(ifd.PixelType());
|
|
112
|
+
if (offset > stream.Length() || count > stream.Length() - offset || decoded > UINT32_MAX ||
|
|
113
|
+
(ifd.fCompression == 1 && count < decoded)) {
|
|
114
|
+
throw std::runtime_error("Invalid TIFF strip size");
|
|
115
|
+
}
|
|
116
|
+
strips.insert(strips.end(), {uint32(offset), uint32(count), uint32(decoded)});
|
|
117
|
+
}
|
|
118
|
+
return strips;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
class TiffReader final : public dng_read_image {
|
|
122
|
+
public:
|
|
123
|
+
using dng_read_image::DecodePredictor;
|
|
124
|
+
};
|
|
125
|
+
|
|
62
126
|
static std::string tiffError;
|
|
63
127
|
|
|
64
128
|
extern "C" {
|
|
65
129
|
|
|
66
|
-
EMSCRIPTEN_KEEPALIVE Tiff *tiff_open(
|
|
130
|
+
EMSCRIPTEN_KEEPALIVE Tiff *tiff_open(unsigned length, bool browserDeflate) {
|
|
67
131
|
try {
|
|
68
132
|
dng_host host;
|
|
69
|
-
|
|
133
|
+
TiffStream stream(length);
|
|
70
134
|
TiffInfo info;
|
|
71
135
|
info.Parse(host, stream);
|
|
72
136
|
if (info.fIFD.empty()) {
|
|
@@ -88,14 +152,14 @@ EMSCRIPTEN_KEEPALIVE Tiff *tiff_open(void *bytes, unsigned length) {
|
|
|
88
152
|
throw std::runtime_error("Unsupported TIFF sample format");
|
|
89
153
|
}
|
|
90
154
|
|
|
91
|
-
// Samples that already fill their storage type can be read from the file bytes directly;
|
|
92
|
-
// anything else, such as packed 12-bit or compressed data, goes through the SDK.
|
|
93
155
|
auto result = std::make_unique<Tiff>();
|
|
94
|
-
result->originalOffset =
|
|
95
|
-
ifd.fBitsPerSample[0] == TagTypeSize(type) * 8 ? uncompressed_offset(ifd, stream) : 0;
|
|
96
156
|
result->bigEndian = info.fBigEndian;
|
|
97
|
-
|
|
98
|
-
|
|
157
|
+
result->strips = browser_strips(ifd, stream, browserDeflate);
|
|
158
|
+
if (!result->strips.empty()) {
|
|
159
|
+
result->pixels.fData = nullptr; // The worker retains the original bytes.
|
|
160
|
+
result->pixels.fArea = dng_rect(ifd.fImageLength, ifd.fImageWidth);
|
|
161
|
+
result->pixels.fPlanes = ifd.fSamplesPerPixel;
|
|
162
|
+
result->pixels.fPixelType = type;
|
|
99
163
|
result->pixels.fRowStep = ifd.fImageWidth * ifd.fSamplesPerPixel;
|
|
100
164
|
} else {
|
|
101
165
|
result->image = std::make_unique<dng_simple_image>(
|
|
@@ -114,7 +178,9 @@ EMSCRIPTEN_KEEPALIVE Tiff *tiff_open(void *bytes, unsigned length) {
|
|
|
114
178
|
ifd.fOrientation,
|
|
115
179
|
ifd.fPhotometricInterpretation,
|
|
116
180
|
ifd.fExtraSamplesCount ? ifd.fExtraSamples[0] : 0,
|
|
117
|
-
uint32(result->pixels.fRowStep) * sampleBytes
|
|
181
|
+
uint32(result->pixels.fRowStep) * sampleBytes,
|
|
182
|
+
ifd.fCompression,
|
|
183
|
+
ifd.fPredictor};
|
|
118
184
|
std::copy(std::begin(metadata), std::end(metadata), result->metadata);
|
|
119
185
|
// The sample type is encoded separately from integer bit depth.
|
|
120
186
|
if (type == ttFloat) {
|
|
@@ -137,13 +203,27 @@ EMSCRIPTEN_KEEPALIVE uint32 *tiff_metadata(Tiff *source) {
|
|
|
137
203
|
return source->metadata;
|
|
138
204
|
}
|
|
139
205
|
|
|
140
|
-
|
|
141
|
-
|
|
206
|
+
// Browser strips retain file byte order; SDK-decoded samples are native-endian.
|
|
207
|
+
EMSCRIPTEN_KEEPALIVE unsigned tiff_big_endian(Tiff *source) {
|
|
208
|
+
return !source->strips.empty() && source->bigEndian;
|
|
142
209
|
}
|
|
143
210
|
|
|
144
|
-
// SDK
|
|
145
|
-
EMSCRIPTEN_KEEPALIVE
|
|
146
|
-
|
|
211
|
+
// Reuse the SDK predictor after browser decompression; no custom delta decoder.
|
|
212
|
+
EMSCRIPTEN_KEEPALIVE void tiff_predict(Tiff *source, void *data) {
|
|
213
|
+
auto buffer = source->pixels;
|
|
214
|
+
buffer.fData = data;
|
|
215
|
+
dng_ifd ifd;
|
|
216
|
+
ifd.fPredictor = source->metadata[10];
|
|
217
|
+
dng_host host;
|
|
218
|
+
TiffReader().DecodePredictor(host, ifd, buffer);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
EMSCRIPTEN_KEEPALIVE uint32 *tiff_strips(Tiff *source) {
|
|
222
|
+
return source->strips.data();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
EMSCRIPTEN_KEEPALIVE unsigned tiff_strip_count(Tiff *source) {
|
|
226
|
+
return source->strips.size() / 3;
|
|
147
227
|
}
|
|
148
228
|
|
|
149
229
|
EMSCRIPTEN_KEEPALIVE void *tiff_pixels(Tiff *source) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "raw-webgpu",
|
|
3
|
-
"version": "0.1.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Camera RAW, DNG and TIFF decoding with WebGPU development and interactive white balance.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"publishConfig": {
|
|
47
47
|
"access": "public",
|
|
48
|
-
"tag": "
|
|
48
|
+
"tag": "latest",
|
|
49
49
|
"registry": "https://registry.npmjs.org/"
|
|
50
50
|
},
|
|
51
51
|
"keywords": [
|