/* * 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 }); } };