Spaces:
Running
Running
File size: 6,684 Bytes
53b24b2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | 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);
}
} |