From 8ed094cd9ef1973cdd38c8bda74eca82a5afc7a2 Mon Sep 17 00:00:00 2001 From: 3dtours Date: Tue, 6 Oct 2026 16:16:28 +0700 Subject: [PATCH] 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. --- docker/frontend/scripts/raw-orf-check.mjs | 10 ++++------ docker/frontend/src/engine/rawDevelop.ts | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docker/frontend/scripts/raw-orf-check.mjs b/docker/frontend/scripts/raw-orf-check.mjs index 2ede25d..8036ab7 100644 --- a/docker/frontend/scripts/raw-orf-check.mjs +++ b/docker/frontend/scripts/raw-orf-check.mjs @@ -24,8 +24,9 @@ const transpile = (path) => // `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 // message protocol ({ id, fn, args } / { id, out, error }) is spoken here. -// The worker is handed the file bytes copy itself — the transfer list would -// detach the caller's. +// The file bytes are handed over in the transfer list exactly as the package +// 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 = ` import { Worker } from 'node:worker_threads'; @@ -68,10 +69,7 @@ export default class LibRaw { this.worker.postMessage({ id, fn, args }, transfer); }); } - open(bytes, settings) { - const copy = Uint8Array.from(bytes); - return this.run('open', copy, settings); - } + open(bytes, settings) { return this.run('open', bytes, settings); } metadata(full) { return this.run('metadata', !!full); } imageData() { return this.run('imageData'); } thumbnailData() { return this.run('thumbnailData'); } diff --git a/docker/frontend/src/engine/rawDevelop.ts b/docker/frontend/src/engine/rawDevelop.ts index 23eeb89..a81c5e9 100644 --- a/docker/frontend/src/engine/rawDevelop.ts +++ b/docker/frontend/src/engine/rawDevelop.ts @@ -386,7 +386,9 @@ export async function rawThumbnail(bytes: Uint8Array, fileName?: string): Promis const raw = new LibRaw(); 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); } catch { return tiffThumbnail(bytes); @@ -565,8 +567,17 @@ export async function developRaw(bytes: Uint8Array): Promise { try { // Extract largest embedded JPEG preview from file bytes first preview = extractEmbeddedJpeg(bytes); - // LibRaw copies the buffer it is handed, so the caller's bytes stay intact. - await raw.open(bytes as unknown as BufferSource, SETTINGS); + // libraw-wasm moves the buffer it is handed into its worker — the argument is + // 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) { preview = await cameraPreview(raw); }