fix(library): optimize image scanning, fast raw preview extraction and lazy thumbnail loading
This commit is contained in:
@@ -215,17 +215,78 @@ async function cameraPreview(raw: LibRaw): Promise<Uint8Array | null> {
|
||||
return new Uint8Array(thumb.data);
|
||||
}
|
||||
|
||||
// Fast pure JS binary scanner to extract embedded JPEG preview from RAW files (ARW, NEF, CR2, CR3, DNG, RAF, RW2, ORF, PEF).
|
||||
// Runs in ~1-3ms without loading full file or initializing LibRaw WebAssembly.
|
||||
export function extractEmbeddedJpeg(buf: Uint8Array): Uint8Array | null {
|
||||
const len = buf.length;
|
||||
for (let i = 0; i < len - 4; i++) {
|
||||
if (buf[i] === 0xff && buf[i + 1] === 0xd8 && buf[i + 2] === 0xff) {
|
||||
// Found JPEG SOI marker
|
||||
let pos = i + 2;
|
||||
let eoiPos = -1;
|
||||
let valid = true;
|
||||
while (pos < len - 1) {
|
||||
if (buf[pos] !== 0xff) {
|
||||
pos++;
|
||||
continue;
|
||||
}
|
||||
const marker = buf[pos + 1];
|
||||
if (marker === 0x00 || (marker >= 0xd0 && marker <= 0xd7)) {
|
||||
pos += 2;
|
||||
continue;
|
||||
}
|
||||
if (marker === 0xd9) {
|
||||
eoiPos = pos + 2;
|
||||
break;
|
||||
}
|
||||
if (marker === 0xd8) {
|
||||
pos += 2;
|
||||
continue;
|
||||
}
|
||||
if (pos + 3 >= len) break;
|
||||
const segLen = (buf[pos + 2] << 8) | buf[pos + 3];
|
||||
if (segLen < 2) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
if (marker === 0xda) {
|
||||
// Start of scan: entropy-coded data until EOI
|
||||
pos += 2 + segLen;
|
||||
while (pos < len - 1) {
|
||||
if (buf[pos] === 0xff) {
|
||||
const m = buf[pos + 1];
|
||||
if (m === 0xd9) {
|
||||
eoiPos = pos + 2;
|
||||
break;
|
||||
}
|
||||
if (m !== 0x00 && !(m >= 0xd0 && m <= 0xd7)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
if (eoiPos !== -1) break;
|
||||
} else {
|
||||
pos += 2 + segLen;
|
||||
}
|
||||
}
|
||||
if (valid && eoiPos > i && (eoiPos - i) > 10000) {
|
||||
return buf.subarray(i, eoiPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// The preview on its own, for the catalogue: a folder of RAW files has to show
|
||||
// a tile per frame, and unpack_thumb is a seek and a copy where the develop
|
||||
// above is a full decode of every pixel at full resolution. No preview inside
|
||||
// the file means no tile — the row still lists the frame by name, and the
|
||||
// studio develops it the moment it is opened.
|
||||
//
|
||||
// ponytail: still a whole LibRaw open per file, reading the file's bytes into
|
||||
// memory to do it — and with the scan's lanes, a few of those opens at once on a
|
||||
// folder of RAW. Give the catalogue a worker and a sync-access handle if a
|
||||
// 10k-frame folder ever turns up.
|
||||
export async function rawThumbnail(bytes: Uint8Array): Promise<Uint8Array | null> {
|
||||
const fast = extractEmbeddedJpeg(bytes);
|
||||
if (fast) return fast;
|
||||
|
||||
const raw = new LibRaw();
|
||||
try {
|
||||
await raw.open(bytes as unknown as BufferSource, SETTINGS);
|
||||
@@ -233,7 +294,9 @@ export async function rawThumbnail(bytes: Uint8Array): Promise<Uint8Array | null
|
||||
} catch {
|
||||
return null;
|
||||
} finally {
|
||||
raw.dispose();
|
||||
try {
|
||||
raw.dispose();
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user