hejianhao
2026-04-02 0940d56748a73525790c350c393a5be960aab62c
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
/**
 * 基于 Web API 的图片处理器
 * 完全不依赖外部库,使用浏览器原生 API
 * 适用于 Electron 渲染进程
 */
 
class WebImageProcessor {
  /**
   * 使用 Canvas API 压缩图片
   * @param {File|Blob|string} input - 输入图片
   * @param {Object} options - 压缩选项
   * @returns {Promise<Blob>} - 压缩后的图片
   */
  static async compressImage(input, options = {}) {
    const {
      maxWidth = 1920,
      maxHeight = 1080,
      quality = 0.8,
      format = 'image/jpeg'
    } = options;
 
    // 1. 加载图片
    const img = await this.loadImage(input);
    
    // 2. 计算新尺寸
    const { width, height } = this.calculateDimensions(
      img.width,
      img.height,
      maxWidth,
      maxHeight
    );
    
    // 3. 创建 Canvas 并绘制
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    
    const ctx = canvas.getContext('2d');
    
    // 设置高质量缩放
    ctx.imageSmoothingEnabled = true;
    ctx.imageSmoothingQuality = 'high';
    
    // 绘制图片
    ctx.drawImage(img, 0, 0, width, height);
    
    // 4. 导出压缩后的图片
    return new Promise((resolve) => {
      canvas.toBlob(
        (blob) => resolve(blob),
        format,
        quality
      );
    });
  }
 
  /**
   * 生成缩略图
   */
  static async generateThumbnail(input, options = {}) {
    const {
      width = 200,
      height = 200,
      quality = 0.7,
      fit = 'cover' // 'cover' | 'contain' | 'fill'
    } = options;
 
    const img = await this.loadImage(input);
    
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    
    const ctx = canvas.getContext('2d');
    
    if (fit === 'cover') {
      // 裁剪以填充
      const scale = Math.max(width / img.width, height / img.height);
      const scaledWidth = img.width * scale;
      const scaledHeight = img.height * scale;
      const x = (width - scaledWidth) / 2;
      const y = (height - scaledHeight) / 2;
      
      ctx.drawImage(img, x, y, scaledWidth, scaledHeight);
    } else if (fit === 'contain') {
      // 保持比例,可能有空白
      const scale = Math.min(width / img.width, height / img.height);
      const scaledWidth = img.width * scale;
      const scaledHeight = img.height * scale;
      const x = (width - scaledWidth) / 2;
      const y = (height - scaledHeight) / 2;
      
      // 填充背景
      ctx.fillStyle = '#f0f0f0';
      ctx.fillRect(0, 0, width, height);
      
      ctx.drawImage(img, x, y, scaledWidth, scaledHeight);
    } else {
      // 拉伸填充
      ctx.drawImage(img, 0, 0, width, height);
    }
    
    return new Promise((resolve) => {
      canvas.toBlob(
        (blob) => resolve(blob),
        'image/jpeg',
        quality
      );
    });
  }
 
  /**
   * 加载图片
   */
  static async loadImage(input) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      
      img.onload = () => resolve(img);
      img.onerror = reject;
      
      if (input instanceof File || input instanceof Blob) {
        img.src = URL.createObjectURL(input);
      } else if (typeof input === 'string') {
        img.src = input;
      } else {
        reject(new Error('不支持的输入类型'));
      }
    });
  }
 
  /**
   * 计算新尺寸
   */
  static calculateDimensions(origWidth, origHeight, maxWidth, maxHeight) {
    if (origWidth <= maxWidth && origHeight <= maxHeight) {
      return { width: origWidth, height: origHeight };
    }
    
    const ratio = Math.min(maxWidth / origWidth, maxHeight / origHeight);
    
    return {
      width: Math.round(origWidth * ratio),
      height: Math.round(origHeight * ratio)
    };
  }
 
  /**
   * 批量处理图片
   */
  static async batchCompress(files, options = {}) {
    const results = [];
    
    for (const file of files) {
      try {
        const compressed = await this.compressImage(file, options);
        results.push({
          success: true,
          original: file,
          compressed,
          reduction: ((file.size - compressed.size) / file.size * 100).toFixed(2)
        });
      } catch (error) {
        results.push({
          success: false,
          original: file,
          error: error.message
        });
      }
    }
    
    return results;
  }
 
  /**
   * 转换图片格式
   */
  static async convertFormat(input, targetFormat = 'image/webp', quality = 0.9) {
    const img = await this.loadImage(input);
    
    const canvas = document.createElement('canvas');
    canvas.width = img.width;
    canvas.height = img.height;
    
    const ctx = canvas.getContext('2d');
    ctx.drawImage(img, 0, 0);
    
    return new Promise((resolve) => {
      canvas.toBlob(
        (blob) => resolve(blob),
        targetFormat,
        quality
      );
    });
  }
 
  /**
   * 获取图片信息
   */
  static async getImageInfo(input) {
    const img = await this.loadImage(input);
    
    return {
      width: img.width,
      height: img.height,
      aspectRatio: (img.width / img.height).toFixed(2),
      size: input.size || 0,
      type: input.type || 'unknown'
    };
  }
 
  /**
   * 智能压缩(根据图片大小自动调整)
   */
  static async smartCompress(input, targetSizeKB = 500) {
    const originalSize = input.size || 0;
    const targetSize = targetSizeKB * 1024;
    
    // 如果已经小于目标大小,直接返回
    if (originalSize <= targetSize) {
      return input;
    }
    
    // 估算压缩质量
    let quality = Math.min(targetSize / originalSize, 0.9);
    let compressed = await this.compressImage(input, { quality });
    
    // 二分查找最佳质量
    let minQuality = 0.1;
    let maxQuality = quality;
    let attempts = 0;
    const maxAttempts = 5;
    
    while (attempts < maxAttempts) {
      if (compressed.size <= targetSize) {
        // 如果小于目标,尝试提高质量
        minQuality = quality;
      } else {
        // 如果大于目标,降低质量
        maxQuality = quality;
      }
      
      quality = (minQuality + maxQuality) / 2;
      compressed = await this.compressImage(input, { quality });
      
      // 如果接近目标大小(±10%),停止
      if (Math.abs(compressed.size - targetSize) / targetSize < 0.1) {
        break;
      }
      
      attempts++;
    }
    
    return compressed;
  }
}
 
// 用于主进程的包装器
class MainProcessImageHandler {
  /**
   * 在主进程中处理图片(使用 Canvas 库)
   */
  static async processInMainProcess(imagePath, outputPath, options = {}) {
    const fs = require('fs').promises;
    const { nativeImage } = require('electron');
    
    try {
      // 读取图片
      const imageBuffer = await fs.readFile(imagePath);
      const image = nativeImage.createFromBuffer(imageBuffer);
      
      // 获取尺寸
      const { width: origWidth, height: origHeight } = image.getSize();
      
      // 计算新尺寸
      const maxWidth = options.maxWidth || 1920;
      const maxHeight = options.maxHeight || 1080;
      const scale = Math.min(1, maxWidth / origWidth, maxHeight / origHeight);
      
      // 调整大小
      const resized = image.resize({
        width: Math.round(origWidth * scale),
        height: Math.round(origHeight * scale),
        quality: 'best'
      });
      
      // 保存
      const quality = Math.round((options.quality || 0.8) * 100);
      const outputBuffer = resized.toJPEG(quality);
      await fs.writeFile(outputPath, outputBuffer);
      
      return {
        success: true,
        path: outputPath,
        size: outputBuffer.length
      };
      
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
}
 
// 导出
if (typeof module !== 'undefined' && module.exports) {
  module.exports = {
    WebImageProcessor,
    MainProcessImageHandler
  };
}