fix(library): optimize image scanning, fast raw preview extraction and lazy thumbnail loading

This commit is contained in:
2026-10-05 06:34:37 +07:00
parent 44468ac178
commit 77cf872b16
13 changed files with 625 additions and 160 deletions
+69 -6
View File
@@ -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 {}
}
}