import {
createPanoramaRenderer,
PANO_FOV_DEFAULT_DEG,
wrapDegrees,
} from './panorama.js';
import {
FLAT_FULL_SIZE,
FLAT_VIEW_SIZE,
IMAGE_TIMEOUT_MS,
KARTAVIEW_PROVIDER_ID,
PANO_FULL_SIZE,
PANO_MID_SIZE,
PANO_PREVIEW_SIZE,
cameraBearing,
kartaViewPhotoUrl,
resizedImageUrl,
} from './policy.js';
const LOAD_FAILED = 'The KartaView photo could not be loaded';
/**
* Load an image within the timeout. A CORS-clean one (for WebGL) is decoded
* off the main thread into an ImageBitmap where the browser can: uploading
* a decoded bitmap costs a fraction of uploading an
(90 ms → 15 ms for
* 4096×2048 on a phone-class CPU).
*/
export function loadImage(
url,
{ crossOrigin = true, timeoutMs = IMAGE_TIMEOUT_MS } = {},
) {
if (
crossOrigin &&
url &&
typeof globalThis.createImageBitmap === 'function' &&
typeof globalThis.fetch === 'function'
)
return loadBitmap(url, timeoutMs);
return new Promise((resolve, reject) => {
if (!url) return reject(new Error(LOAD_FAILED));
const image = new Image();
if (crossOrigin) image.crossOrigin = 'anonymous';
image.decoding = 'async';
const timer = setTimeout(() => {
image.src = '';
reject(new Error(LOAD_FAILED));
}, timeoutMs);
image.onload = () => {
clearTimeout(timer);
resolve(image);
};
image.onerror = () => {
clearTimeout(timer);
reject(new Error(LOAD_FAILED));
};
image.src = url;
});
}
async function loadBitmap(url, timeoutMs) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
mode: 'cors',
signal: controller.signal,
});
if (!response.ok) throw new Error(LOAD_FAILED);
return await createImageBitmap(await response.blob());
} catch {
throw new Error(LOAD_FAILED);
} finally {
clearTimeout(timer);
}
}
/** Free a decoded bitmap nobody will draw (an
has nothing to free). */
function release(image) {
image?.close?.();
}
/** The panorama size a canvas this wide can show sharply at this field of view. */
export function panoSizeFor(canvasPixels, fovDeg) {
const needed = (Math.max(1, canvasPixels) * 360) / Math.max(1, fovDeg);
return needed > PANO_MID_SIZE[0] ? PANO_FULL_SIZE : PANO_MID_SIZE;
}
/**
* Viewer adapter for KartaView photos: a flat photo in an
, a 360°
* photo on a WebGL panorama (drag to look, wheel to zoom), and ‹ › to step
* along the sequence (also ← →). Poses follow the view direction.
*
* @param {object} options
* @param {(photoId: string) => Promise