importScripts('https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js'); self.onmessage = async function(e) { const { type, payload } = e.data; if (type === 'EXPORT_ZIP') { const { items, zipName } = payload; try { const zip = new JSZip(); const usedNames = new Set(); for (let i = 0; i < items.length; i++) { const item = items[i]; self.postMessage({ type: 'PROGRESS', text: `处理图片 ${i + 1}/${items.length}`, percent: (i / items.length) * 50 }); // Decode image efficiently const bmp = await createImageBitmap(item.file); const canvas = new OffscreenCanvas(bmp.width, bmp.height); const ctx = canvas.getContext('2d'); ctx.drawImage(bmp, 0, 0); // Draw Watermark drawWatermarkWorker(ctx, canvas.width, canvas.height, item.data); // Convert to Blob const blob = await canvas.convertToBlob({ type: 'image/jpeg', quality: 0.9 }); bmp.close(); // Free memory // Prepare filename const data = item.data; const sanitize = (str) => (str || '').replace(/[\\/:*?"<>|]/g, '_'); const cinema = sanitize(data.cinema); const movie = sanitize(data.movie); let formattedTime = data.time.replace(/\s+/g, '_').replace(/:/g, '-'); const tickets = sanitize(data.tickets); const baseName = `${cinema}_${movie}_${formattedTime}_共出票${tickets}张`; let finalName = baseName; let counter = 1; while (usedNames.has(finalName)) { counter++; finalName = `${baseName}_${counter}`; } usedNames.add(finalName); zip.file(`${finalName}.jpg`, blob); } self.postMessage({ type: 'PROGRESS', text: '正在生成 ZIP 文件...', percent: 50 }); const content = await zip.generateAsync({ type: "blob", compression: "STORE" // Faster than DEFLATE, fine for JPEGs }, (metadata) => { self.postMessage({ type: 'PROGRESS', text: `正在压缩... ${metadata.percent.toFixed(0)}%`, percent: 50 + (metadata.percent * 0.5) }); }); self.postMessage({ type: 'DONE', blob: content, filename: zipName }); } catch (error) { self.postMessage({ type: 'ERROR', error: error.message }); } } else if (type === 'EXPORT_SINGLE') { const { item, filename } = payload; try { self.postMessage({ type: 'PROGRESS', text: '处理中...', percent: 50 }); const bmp = await createImageBitmap(item.file); const canvas = new OffscreenCanvas(bmp.width, bmp.height); const ctx = canvas.getContext('2d'); ctx.drawImage(bmp, 0, 0); drawWatermarkWorker(ctx, canvas.width, canvas.height, item.data); const blob = await canvas.convertToBlob({ type: 'image/jpeg', quality: 0.9 }); bmp.close(); self.postMessage({ type: 'DONE_SINGLE', blob, filename }); } catch(error) { self.postMessage({ type: 'ERROR', error: error.message }); } } else if (type === 'EXPORT_ALL_SINGLE') { const { items } = payload; try { for (let i = 0; i < items.length; i++) { const item = items[i]; self.postMessage({ type: 'PROGRESS', text: `导出图片 ${i + 1}/${items.length}`, percent: (i / items.length) * 100 }); const bmp = await createImageBitmap(item.file); const canvas = new OffscreenCanvas(bmp.width, bmp.height); const ctx = canvas.getContext('2d'); ctx.drawImage(bmp, 0, 0); drawWatermarkWorker(ctx, canvas.width, canvas.height, item.data); const blob = await canvas.convertToBlob({ type: 'image/jpeg', quality: 0.9 }); bmp.close(); const data = item.data; const sanitize = (str) => (str || '').replace(/[\\/:*?"<>|]/g, '_'); const cinema = sanitize(data.cinema); const movie = sanitize(data.movie); let formattedTime = data.time.replace(/\s+/g, '_').replace(/:/g, '-'); const tickets = sanitize(data.tickets); const newName = `${cinema}_${movie}_${formattedTime}_共出票${tickets}张_${i+1}.jpg`; self.postMessage({ type: 'DONE_SINGLE', blob, filename: newName }); } self.postMessage({ type: 'DONE_ALL_SINGLE' }); } catch(error) { self.postMessage({ type: 'ERROR', error: error.message }); } } }; function drawWatermarkWorker(ctx, width, height, data) { const baseFontSize = Math.max(30, Math.floor(height * 0.03)); const fontSize = baseFontSize * (data.sizeScale || 1.0); ctx.font = `bold ${fontSize}px sans-serif`; ctx.textAlign = 'center'; ctx.fillStyle = data.color || '#ffffff'; ctx.strokeStyle = '#000000'; ctx.lineWidth = Math.max(2, Math.floor(fontSize / 15)); const movieTitle = data.movie ? `《${data.movie}》` : ''; const lines = [ data.cinema, movieTitle, data.time, data.tickets ? `共出票${data.tickets}张` : '', data.extra1, data.extra2 ]; const validLines = lines.filter(line => line && line.trim() !== ''); const xOffset = (data.offsetX || 0) * (width / 1000); const yOffset = (data.offsetY || 0) * (height / 1000); const lineHeight = fontSize * 1.5; const totalTextHeight = validLines.length * lineHeight; const startY = height - totalTextHeight - (height * 0.05) + yOffset; const x = (width / 2) + xOffset; for (let index = 0; index < validLines.length; index++) { const line = validLines[index]; const y = startY + (index * lineHeight) + fontSize; ctx.strokeText(line, x, y); ctx.fillText(line, x, y); } }