59 lines
2.0 KiB
JavaScript
59 lines
2.0 KiB
JavaScript
/*
|
|
* Web Worker for image optimization (P1). Offloads the expensive bitmap decode,
|
|
* resize, and JPEG/PNG compression to a background thread so the UI remains
|
|
* responsive while processing large photos.
|
|
*
|
|
* Uses OffscreenCanvas which is available in Chrome 69+, Firefox 105+, and
|
|
* Safari 16.4+. The main module (images.js) detects support at runtime and
|
|
* falls back to the main thread for older browsers.
|
|
*/
|
|
|
|
function fitIntoBox(originalWidth, originalHeight, maxWidth, maxHeight) {
|
|
const ratio = Math.min(maxWidth / originalWidth, maxHeight / originalHeight, 1);
|
|
|
|
return {
|
|
width: Math.round(originalWidth * ratio),
|
|
height: Math.round(originalHeight * ratio)
|
|
};
|
|
}
|
|
|
|
self.onmessage = async (event) => {
|
|
const { id, file, imageRules } = event.data;
|
|
|
|
try {
|
|
const imageBitmap = await createImageBitmap(file);
|
|
const maxWidth = imageRules?.maxWidthPx || imageBitmap.width;
|
|
const maxHeight = imageRules?.maxHeightPx || imageBitmap.height;
|
|
const { width, height } = fitIntoBox(imageBitmap.width, imageBitmap.height, maxWidth, maxHeight);
|
|
|
|
const canvas = new OffscreenCanvas(width, height);
|
|
const context = canvas.getContext('2d');
|
|
context.drawImage(imageBitmap, 0, 0, width, height);
|
|
imageBitmap.close();
|
|
|
|
const targetMimeType = file.type === 'image/png' ? 'image/png' : 'image/jpeg';
|
|
const quality = Math.min(Math.max((imageRules?.jpegQuality || 82) / 100, 0.2), 0.95);
|
|
const blob = await canvas.convertToBlob({ type: targetMimeType, quality });
|
|
|
|
if (!blob) {
|
|
throw new Error(`Failed to optimize image: ${file.name}`);
|
|
}
|
|
|
|
if (imageRules?.maxFileSizeBytes && blob.size > imageRules.maxFileSizeBytes) {
|
|
throw new Error(`Optimized image still exceeds limit: ${file.name}`);
|
|
}
|
|
|
|
self.postMessage({
|
|
id,
|
|
result: {
|
|
blob,
|
|
width,
|
|
height,
|
|
extension: targetMimeType === 'image/png' ? 'png' : 'jpg'
|
|
}
|
|
});
|
|
} catch (error) {
|
|
self.postMessage({ id, error: error.message });
|
|
}
|
|
};
|