fix(fe): canvas fallback for decodeOrientedBitmap when createImageBitmap is absent
The LAN dev URL is a non-secure, sometimes restricted browser where createImageBitmap may be undefined; the pick-calibration -> mask decode would then reject. Fall back to a canvas + Image decode returning an ImageBitmap. Contract unchanged (still returns ImageBitmap).
This commit is contained in:
@@ -4,24 +4,54 @@
|
|||||||
*
|
*
|
||||||
* Matched-set / DJI JPEGs often carry EXIF Orientation 8 (stored landscape,
|
* Matched-set / DJI JPEGs often carry EXIF Orientation 8 (stored landscape,
|
||||||
* display portrait). `new Image()` and bare `createImageBitmap(blob)` can
|
* display portrait). `new Image()` and bare `createImageBitmap(blob)` can
|
||||||
* disagree across browsers → axis arrow space ≠ mask bitset space.
|
* disagree across browsers -> axis arrow space != mask bitset space.
|
||||||
*
|
*
|
||||||
* Contract: always request `imageOrientation: 'from-image'`. If the runtime
|
* Contract: always request `imageOrientation: 'from-image'` when supported.
|
||||||
* rejects that option (older engines), fall back to bare `createImageBitmap`;
|
* The dev environment is LAN-HTTP (a non-secure, sometimes restricted
|
||||||
* engines that already apply EXIF by default still match Image/canvas size.
|
* browser) where `createImageBitmap` may be absent, so fall back to a
|
||||||
|
* canvas + Image decode that returns an ImageBitmap.
|
||||||
*/
|
*/
|
||||||
export const ORIENTED_BITMAP_OPTIONS: ImageBitmapOptions = {
|
export const ORIENTED_BITMAP_OPTIONS: ImageBitmapOptions = {
|
||||||
imageOrientation: 'from-image',
|
imageOrientation: 'from-image',
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function decodeOrientedBitmap(blob: Blob): Promise<ImageBitmap> {
|
export async function decodeOrientedBitmap(blob: Blob): Promise<ImageBitmap> {
|
||||||
try {
|
if (typeof createImageBitmap === 'function') {
|
||||||
return await createImageBitmap(blob, ORIENTED_BITMAP_OPTIONS);
|
|
||||||
} catch (first) {
|
|
||||||
try {
|
try {
|
||||||
return await createImageBitmap(blob);
|
return await createImageBitmap(blob, ORIENTED_BITMAP_OPTIONS);
|
||||||
} catch {
|
} catch (first) {
|
||||||
throw first;
|
try {
|
||||||
|
return await createImageBitmap(blob);
|
||||||
|
} catch {
|
||||||
|
throw first;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return decodeViaCanvas(blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Canvas + Image decode for engines without createImageBitmap. */
|
||||||
|
async function decodeViaCanvas(blob: Blob): Promise<ImageBitmap> {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
try {
|
||||||
|
const img = await loadImage(url);
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = img.naturalWidth;
|
||||||
|
canvas.height = img.naturalHeight;
|
||||||
|
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||||
|
if (!ctx) throw new Error('2d context unavailable');
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
return await createImageBitmap(canvas);
|
||||||
|
} finally {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadImage(url: string): Promise<HTMLImageElement> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => resolve(img);
|
||||||
|
img.onerror = () => reject(new Error('image decode failed'));
|
||||||
|
img.src = url;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user