mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
Image loading (photoViewer.js) - One AbortController per open: a new open, close or unmount stops the previous photo's first and sharper downloads (an <img> by src = ''). A collapsed viewer never falls back to the original; Panoramax's flat fallback is the thumbnail. A superseded open resolves quietly. - Flat HD tracked as "loading" and "shown" apart: a discarded response no longer blocks later upgrades, and resizes while it loads ask once. - No upgrade of the photo still shown while another opens. - Size limits before decoding: imageSize.js reads JPEG/PNG/WebP/GIF headers; more than 64 MP or 16,384 px a side is refused undecoded, an <img> that decodes larger is refused and let go, flat HD needs a known width of at most 8,192 px, and panoramas always decode with a resize. Panoramax provider - Retry-After parsed as seconds or an HTTP date; the browser cannot read it here, so busy answers back off 5 s doubling to 300 s, reset once answered. - Headers timed, then the body by an idle timeout (15 s of silence), so a slow link still gets its megabyte tiles. - Cones centre only on the picture shown (onShowing), never on a late lookup; lookups nobody waits for are aborted (shared, refcounted). - onReopen: a GPU reset redraws in the viewer, not through the core. - Cones draw the home z15 tile at once and add neighbours as they arrive; a neighbour that failed is not counted as drawn. - Coverage tiles decode in ~8 ms slices (a 2 MB z10 tile over Paris was one ~85-200 ms task); sorted by measured length without a Map. - Box search asks for 20, not 100 (the API returns them nearest first). - looksLikeEmail checks every '@'; creatorOf de-duplicates with a Set. lineCoverage.js - A redraw while a swap still builds keeps what is on screen (the swap's old lines and its ready new ones) and drops only the unready ones. Tests for each (mutation-checked), two vacuous tests made real, the cones' pictures lane and readable()'s busy/timeout paths covered; the fixture's box search is nearest first. Docs: README filters and share links, CHANGELOG HD wording, CURRENT-STATE. Co-Authored-By: Claude Opus 5.5 <[email protected]>
65 lines
2.3 KiB
JavaScript
65 lines
2.3 KiB
JavaScript
/**
|
|
* An image's pixel size read from its header, before anything decodes it:
|
|
* a picture host can serve a small file that declares enormous dimensions,
|
|
* and a decode of it alone can take the tab (or the GPU process) down.
|
|
* JPEG (SOFn), PNG (IHDR), WebP (VP8, VP8L, VP8X) and GIF; null for any
|
|
* other format, or a header this does not reach.
|
|
* @param {Uint8Array} bytes The start of the file (a JPEG's EXIF can push its SOF past 64 kB).
|
|
* @returns {{width: number, height: number}|null}
|
|
*/
|
|
export function imageSize(bytes) {
|
|
if (!bytes || bytes.length < 12) return null;
|
|
const b = bytes;
|
|
const be16 = (i) => (b[i] << 8) | b[i + 1];
|
|
const le16 = (i) => b[i] | (b[i + 1] << 8);
|
|
const le24 = (i) => b[i] | (b[i + 1] << 8) | (b[i + 2] << 16);
|
|
const ascii = (i, text) =>
|
|
[...text].every((char, k) => b[i + k] === char.charCodeAt(0));
|
|
// JPEG: walk the markers to the first start-of-frame.
|
|
if (b[0] === 0xff && b[1] === 0xd8) {
|
|
let i = 2;
|
|
while (i + 9 < b.length) {
|
|
if (b[i] !== 0xff) return null;
|
|
const marker = b[i + 1];
|
|
// Fill bytes, and markers with no length.
|
|
if (marker === 0xff) {
|
|
i++;
|
|
continue;
|
|
}
|
|
if (marker === 0x01 || (marker >= 0xd0 && marker <= 0xd9)) {
|
|
i += 2;
|
|
continue;
|
|
}
|
|
const isFrame =
|
|
marker >= 0xc0 &&
|
|
marker <= 0xcf &&
|
|
marker !== 0xc4 &&
|
|
marker !== 0xc8 &&
|
|
marker !== 0xcc;
|
|
if (isFrame) return { width: be16(i + 7), height: be16(i + 5) };
|
|
i += 2 + be16(i + 2);
|
|
}
|
|
return null;
|
|
}
|
|
// PNG: IHDR is the first chunk.
|
|
if (b[0] === 0x89 && ascii(1, 'PNG') && ascii(12, 'IHDR')) {
|
|
const u32 = (i) =>
|
|
((b[i] << 24) | (b[i + 1] << 16) | (b[i + 2] << 8) | b[i + 3]) >>> 0;
|
|
return { width: u32(16), height: u32(20) };
|
|
}
|
|
if (ascii(0, 'RIFF') && ascii(8, 'WEBP') && b.length >= 30) {
|
|
if (ascii(12, 'VP8 '))
|
|
return { width: le16(26) & 0x3fff, height: le16(28) & 0x3fff };
|
|
if (ascii(12, 'VP8L'))
|
|
return {
|
|
width: 1 + (((b[22] & 0x3f) << 8) | b[21]),
|
|
height:
|
|
1 + (((b[24] & 0x0f) << 10) | (b[23] << 2) | ((b[22] & 0xc0) >> 6)),
|
|
};
|
|
if (ascii(12, 'VP8X')) return { width: 1 + le24(24), height: 1 + le24(27) };
|
|
return null;
|
|
}
|
|
if (ascii(0, 'GIF8')) return { width: le16(6), height: le16(8) };
|
|
return null;
|
|
}
|