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