fix(raw): stop the develop from handing the studio zero bytes

libraw-wasm moves the buffer it is given into its worker, so raw.open() detaches
the caller's array. developRaw read its embedded JPEG preview as a view of that
same array, so the open emptied the preview, Skia rejected the detached buffer
and the catch returned the emptied preview as the develop - every RAW opened from
the LIBRARY reached the studio as 0 bytes and the app said "Could not develop
this RAW file. Try again, or use its JPG."

Hand the worker a copy instead, in the develop and in the thumbnail path. The
EXIF stamps read off the caller's bytes afterwards are intact again.

The ORF check now hands the bytes over in the transfer list the way the package
does, so it fails on any develop that forgets this.
This commit is contained in:
2026-10-06 16:16:28 +07:00
parent 16c098cd6a
commit 8ed094cd9e
2 changed files with 18 additions and 9 deletions
+4 -6
View File
@@ -24,8 +24,9 @@ const transpile = (path) =>
// `self.onmessage` / `self.postMessage`, where Node has parentPort. So the worker // `self.onmessage` / `self.postMessage`, where Node has parentPort. So the worker
// is booted behind a shim that renames one into the other, and the package's own // is booted behind a shim that renames one into the other, and the package's own
// message protocol ({ id, fn, args } / { id, out, error }) is spoken here. // message protocol ({ id, fn, args } / { id, out, error }) is spoken here.
// The worker is handed the file bytes copy itself — the transfer list would // The file bytes are handed over in the transfer list exactly as the package
// detach the caller's. // does — so opening detaches them, and this check fails on any develop that
// forgets to keep the caller's array intact.
const LIBRAW_NODE_SRC = ` const LIBRAW_NODE_SRC = `
import { Worker } from 'node:worker_threads'; import { Worker } from 'node:worker_threads';
@@ -68,10 +69,7 @@ export default class LibRaw {
this.worker.postMessage({ id, fn, args }, transfer); this.worker.postMessage({ id, fn, args }, transfer);
}); });
} }
open(bytes, settings) { open(bytes, settings) { return this.run('open', bytes, settings); }
const copy = Uint8Array.from(bytes);
return this.run('open', copy, settings);
}
metadata(full) { return this.run('metadata', !!full); } metadata(full) { return this.run('metadata', !!full); }
imageData() { return this.run('imageData'); } imageData() { return this.run('imageData'); }
thumbnailData() { return this.run('thumbnailData'); } thumbnailData() { return this.run('thumbnailData'); }
+14 -3
View File
@@ -386,7 +386,9 @@ export async function rawThumbnail(bytes: Uint8Array, fileName?: string): Promis
const raw = new LibRaw(); const raw = new LibRaw();
try { try {
await raw.open(bytes as unknown as BufferSource, SETTINGS); // The same transfer as in the develop: the worker is handed a copy, so the
// caller's bytes stay whole for whatever the catalogue reads off them next.
await raw.open(bytes.slice() as unknown as BufferSource, SETTINGS);
return await cameraPreview(raw); return await cameraPreview(raw);
} catch { } catch {
return tiffThumbnail(bytes); return tiffThumbnail(bytes);
@@ -565,8 +567,17 @@ export async function developRaw(bytes: Uint8Array): Promise<Uint8Array> {
try { try {
// Extract largest embedded JPEG preview from file bytes first // Extract largest embedded JPEG preview from file bytes first
preview = extractEmbeddedJpeg(bytes); preview = extractEmbeddedJpeg(bytes);
// LibRaw copies the buffer it is handed, so the caller's bytes stay intact. // libraw-wasm moves the buffer it is handed into its worker — the argument is
await raw.open(bytes as unknown as BufferSource, SETTINGS); // in the transfer list of its postMessage — so opening DETACHES the array the
// caller passed. This line used to believe the package's own doc that it
// copies: the preview above is a view of that same buffer, so the open emptied
// it, previewGrid handed Skia a detached array ("Cannot perform Construct on a
// detached or out-of-bounds ArrayBuffer"), and the catch then returned that
// same emptied preview AS the develop — every RAW reached the studio as zero
// bytes, which is the "Could not develop this RAW file" the LIBRARY reported.
// The caller's bytes are also what the EXIF stamps are read off afterwards, so
// the worker is handed a copy of its own to move instead.
await raw.open(bytes.slice() as unknown as BufferSource, SETTINGS);
if (!preview) { if (!preview) {
preview = await cameraPreview(raw); preview = await cameraPreview(raw);
} }