131 lines
3.6 KiB
JavaScript
131 lines
3.6 KiB
JavaScript
import { encode } from 'blurhash';
|
|
|
|
self.onmessage = async (e) => {
|
|
// Ignore internal browser/Vite/extension pings that don't match our exact job signature
|
|
if (e.data?.type !== 'PROCESS_IMAGE') return;
|
|
|
|
const { id, file, targetWidth, targetHeight, quality, computeBlurhash } =
|
|
e.data;
|
|
|
|
try {
|
|
let finalCanvas;
|
|
let finalCtx;
|
|
|
|
// --- 1. Attempt Hardware Resizing (Happy Path) ---
|
|
try {
|
|
const resizedBitmap = await createImageBitmap(file, {
|
|
resizeWidth: targetWidth,
|
|
resizeHeight: targetHeight,
|
|
resizeQuality: 'high',
|
|
});
|
|
|
|
finalCanvas = new OffscreenCanvas(targetWidth, targetHeight);
|
|
finalCtx = finalCanvas.getContext('2d');
|
|
if (!finalCtx) {
|
|
throw new Error('Failed to get 2d context from OffscreenCanvas');
|
|
}
|
|
finalCtx.drawImage(resizedBitmap, 0, 0, targetWidth, targetHeight);
|
|
resizedBitmap.close();
|
|
} catch (hwError) {
|
|
console.warn(
|
|
'Hardware resize failed, falling back to stepped software scaling:',
|
|
hwError
|
|
);
|
|
|
|
// --- 2. Fallback to Stepped Software Scaling (Robust Path) ---
|
|
// Bypass Android File descriptor bug by reading into memory
|
|
const buffer = await file.arrayBuffer();
|
|
const blob = new Blob([buffer], { type: file.type });
|
|
|
|
const source = await createImageBitmap(blob);
|
|
let srcWidth = source.width;
|
|
let srcHeight = source.height;
|
|
|
|
let currentCanvas = new OffscreenCanvas(srcWidth, srcHeight);
|
|
let ctx = currentCanvas.getContext('2d');
|
|
|
|
ctx.imageSmoothingEnabled = true;
|
|
ctx.imageSmoothingQuality = 'high';
|
|
ctx.drawImage(source, 0, 0);
|
|
|
|
// Step down by halves until near target
|
|
while (
|
|
currentCanvas.width * 0.5 > targetWidth &&
|
|
currentCanvas.height * 0.5 > targetHeight
|
|
) {
|
|
const nextCanvas = new OffscreenCanvas(
|
|
Math.floor(currentCanvas.width * 0.5),
|
|
Math.floor(currentCanvas.height * 0.5)
|
|
);
|
|
const nextCtx = nextCanvas.getContext('2d');
|
|
|
|
nextCtx.imageSmoothingEnabled = true;
|
|
nextCtx.imageSmoothingQuality = 'high';
|
|
|
|
nextCtx.drawImage(
|
|
currentCanvas,
|
|
0,
|
|
0,
|
|
nextCanvas.width,
|
|
nextCanvas.height
|
|
);
|
|
|
|
currentCanvas = nextCanvas;
|
|
}
|
|
|
|
// Final resize to exact target
|
|
finalCanvas = new OffscreenCanvas(targetWidth, targetHeight);
|
|
finalCtx = finalCanvas.getContext('2d');
|
|
|
|
finalCtx.imageSmoothingEnabled = true;
|
|
finalCtx.imageSmoothingQuality = 'high';
|
|
|
|
finalCtx.drawImage(currentCanvas, 0, 0, targetWidth, targetHeight);
|
|
|
|
source.close();
|
|
}
|
|
|
|
// --- 3. Generate Blurhash (if requested) ---
|
|
let blurhash = null;
|
|
if (computeBlurhash) {
|
|
try {
|
|
const imageData = finalCtx.getImageData(
|
|
0,
|
|
0,
|
|
targetWidth,
|
|
targetHeight
|
|
);
|
|
blurhash = encode(imageData.data, targetWidth, targetHeight, 4, 3);
|
|
} catch (blurhashError) {
|
|
console.warn(
|
|
'Could not generate blurhash (possible canvas fingerprinting protection):',
|
|
blurhashError
|
|
);
|
|
}
|
|
}
|
|
|
|
// --- 4. Compress to JPEG Blob ---
|
|
const finalBlob = await finalCanvas.convertToBlob({
|
|
type: 'image/jpeg',
|
|
quality: quality,
|
|
});
|
|
|
|
const dim = `${targetWidth}x${targetHeight}`;
|
|
|
|
// --- 5. Send results back to main thread ---
|
|
self.postMessage({
|
|
id,
|
|
success: true,
|
|
blob: finalBlob,
|
|
dim,
|
|
blurhash,
|
|
});
|
|
} catch (error) {
|
|
self.postMessage({
|
|
id,
|
|
success: false,
|
|
error: error.message,
|
|
});
|
|
}
|
|
};
|