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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
/**
 * 统一图片处理器
 * 集成缩略图生成、图片压缩、尺寸调整等功能
 * 使用 Jimp 和 Electron nativeImage 多引擎支持
 */
 
const fs = require('fs').promises;
const path = require('path');
const { nativeImage } = require('electron');
 
class ImageProcessor {
  constructor() {
    this.engines = [];
    this.initEngines();
  }
 
  /**
   * 初始化所有可用引擎
   */
  initEngines() {
    // 引擎优先级:Jimp > Electron Native
    
    // 1. Jimp (纯 JS,跨平台兼容性最好)
    this.engines.push({
      name: 'jimp',
      check: () => {
        try {
          require('jimp');
          return true;
        } catch {
          return false;
        }
      },
      process: this.processWithJimp.bind(this)
    });
 
    // 2. Electron nativeImage (始终可用的备用方案)
    this.engines.push({
      name: 'electron',
      check: () => true,
      process: this.processWithElectron.bind(this)
    });
 
    console.log('[ImageProcessor] 初始化完成,可用引擎:', 
      this.engines.filter(e => e.check()).map(e => e.name).join(', '));
  }
 
  /**
   * 生成缩略图(自动选择引擎)
   * @param {string} inputPath - 输入图片路径
   * @param {string} outputPath - 输出图片路径
   * @param {Object} options - 选项
   * @param {number} options.width - 宽度,默认200
   * @param {number} options.height - 高度,默认200
   * @param {number} options.quality - 质量,默认80
   * @param {string} options.mode - 缩放模式,默认'cover'
   * @returns {Promise<Object>} 处理结果
   */
  async generateThumbnail(inputPath, outputPath, options = {}) {
    const {
      width = 200,
      height = 200,
      quality = 80,
      mode = 'cover'
    } = options;
 
    // 确保输出目录存在
    const outputDir = path.dirname(outputPath);
    await fs.mkdir(outputDir, { recursive: true });
 
    // 尝试每个引擎
    for (const engine of this.engines) {
      if (!engine.check()) {
        console.log(`[ImageProcessor] ${engine.name} 不可用,跳过`);
        continue;
      }
 
      try {
        console.log(`[ImageProcessor] 使用 ${engine.name} 引擎`);
        const result = await engine.process(inputPath, outputPath, { width, height, quality, mode });
        console.log(`[ImageProcessor] ${engine.name} 成功处理图片`);
        return { 
          ...result, 
          engine: engine.name,
          success: true 
        };
      } catch (error) {
        console.error(`[ImageProcessor] ${engine.name} 失败:`, error.message);
        // 继续尝试下一个引擎
      }
    }
 
    throw new Error('所有图片处理引擎都失败了');
  }
 
  /**
   * Jimp 引擎处理
   */
  async processWithJimp(inputPath, outputPath, options) {
    const Jimp = require('jimp');
    
    const image = await Jimp.read(inputPath);
    const { width, height, quality, mode = 'cover' } = options;
    
    // 根据模式选择不同的缩放方式
    if (mode === 'cover') {
      // 覆盖模式:保持比例,裁剪多余部分
      await image
        .cover(width, height, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE)
        .quality(quality)
        .writeAsync(outputPath);
    } else if (mode === 'contain') {
      // 包含模式:保持比例,完整显示
      await image
        .contain(width, height, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE)
        .quality(quality)
        .writeAsync(outputPath);
    } else if (mode === 'resize') {
      // 拉伸模式:不保持比例,直接缩放
      await image
        .resize(width, height)
        .quality(quality)
        .writeAsync(outputPath);
    } else if (mode === 'scaleToFit') {
      // 适应模式:保持比例,缩放到适合尺寸
      await image
        .scaleToFit(width, height)
        .quality(quality)
        .writeAsync(outputPath);
    } else {
      // 默认使用 cover 模式
      await image
        .cover(width, height, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE)
        .quality(quality)
        .writeAsync(outputPath);
    }
 
    const stats = await fs.stat(outputPath);
    return { 
      path: outputPath, 
      size: stats.size,
      mode: mode 
    };
  }
 
  /**
   * Electron 原生引擎处理
   */
  async processWithElectron(inputPath, outputPath, options) {
    // 读取图片
    const imageBuffer = await fs.readFile(inputPath);
    const image = nativeImage.createFromBuffer(imageBuffer);
 
    // 获取原始尺寸
    const { width: origWidth, height: origHeight } = image.getSize();
    const { width, height, quality, mode = 'cover' } = options;
 
    let resized;
 
    if (mode === 'cover') {
      // 覆盖模式:保持比例,裁剪多余部分
      const scale = Math.max(width / origWidth, height / origHeight);
      const scaledWidth = Math.round(origWidth * scale);
      const scaledHeight = Math.round(origHeight * scale);
 
      resized = image.resize({
        width: scaledWidth,
        height: scaledHeight,
        quality: 'best'
      });
 
      // 从中心裁剪到精确尺寸
      if (scaledWidth !== width || scaledHeight !== height) {
        const x = Math.round((scaledWidth - width) / 2);
        const y = Math.round((scaledHeight - height) / 2);
        
        resized = resized.crop({
          x: Math.max(0, x),
          y: Math.max(0, y),
          width: width,
          height: height
        });
      }
    } else if (mode === 'contain') {
      // 包含模式:保持比例,完整显示(可能有黑边)
      const scale = Math.min(width / origWidth, height / origHeight);
      const scaledWidth = Math.round(origWidth * scale);
      const scaledHeight = Math.round(origHeight * scale);
 
      resized = image.resize({
        width: scaledWidth,
        height: scaledHeight,
        quality: 'best'
      });
    } else if (mode === 'resize') {
      // 拉伸模式:不保持比例,直接缩放
      resized = image.resize({
        width: width,
        height: height,
        quality: 'best'
      });
    } else if (mode === 'scaleToFit') {
      // 适应模式:保持比例,缩放到不超过目标尺寸
      const scale = Math.min(width / origWidth, height / origHeight, 1);
      const scaledWidth = Math.round(origWidth * scale);
      const scaledHeight = Math.round(origHeight * scale);
 
      resized = image.resize({
        width: scaledWidth,
        height: scaledHeight,
        quality: 'best'
      });
    } else {
      // 默认使用 cover 模式
      const scale = Math.max(width / origWidth, height / origHeight);
      const scaledWidth = Math.round(origWidth * scale);
      const scaledHeight = Math.round(origHeight * scale);
 
      resized = image.resize({
        width: scaledWidth,
        height: scaledHeight,
        quality: 'best'
      });
    }
 
    // 转换为 JPEG
    const jpegBuffer = resized.toJPEG(quality);
    await fs.writeFile(outputPath, jpegBuffer);
 
    return { 
      path: outputPath, 
      size: jpegBuffer.length,
      mode: mode
    };
  }
 
  /**
   * 批量生成缩略图
   */
  async batchGenerate(images, options = {}) {
    const results = [];
    const { concurrency = 3 } = options;
 
    // 分批处理
    for (let i = 0; i < images.length; i += concurrency) {
      const batch = images.slice(i, i + concurrency);
      const batchPromises = batch.map(({ input, output, ...imgOptions }) =>
        this.generate(input, output, { ...options, ...imgOptions })
          .then(result => ({ ...result, input, output }))
          .catch(error => ({ 
            success: false, 
            error: error.message, 
            input, 
            output 
          }))
      );
 
      const batchResults = await Promise.all(batchPromises);
      results.push(...batchResults);
    }
 
    return results;
  }
 
  /**
   * 获取可用引擎信息
   */
  getAvailableEngines() {
    return this.engines
      .filter(e => e.check())
      .map(e => e.name);
  }
 
  /**
   * 检查特定引擎是否可用
   */
  isEngineAvailable(engineName) {
    const engine = this.engines.find(e => e.name === engineName);
    return engine ? engine.check() : false;
  }
 
  /**
   * 便捷方法:生成缩略图(向后兼容)
   * @deprecated 使用 generateThumbnail 代替
   */
  async generate(inputPath, outputPath, options = {}) {
    console.warn('[ImageProcessor] generate() 方法已废弃,请使用 generateThumbnail()');
    return this.generateThumbnail(inputPath, outputPath, options);
  }
 
  /**
   * 便捷方法:压缩图片
   */
  async compress(inputPath, outputPath, options = {}) {
    return this.generateThumbnail(inputPath, outputPath, {
      ...options,
      mode: 'scaleToFit' // 压缩时保持比例
    });
  }
 
  /**
   * 便捷方法:调整尺寸
   */
  async resize(inputPath, outputPath, width, height, options = {}) {
    return this.generateThumbnail(inputPath, outputPath, {
      width,
      height,
      mode: 'resize', // 拉伸模式
      ...options
    });
  }
 
  /**
   * 获取图片信息
   */
  async getImageInfo(imagePath) {
    try {
      // 优先使用 Jimp 获取详细信息
      if (this.isEngineAvailable('jimp')) {
        const Jimp = require('jimp');
        const image = await Jimp.read(imagePath);
        return {
          width: image.bitmap.width,
          height: image.bitmap.height,
          mime: image._originalMime,
          hasAlpha: image.hasAlpha()
        };
      }
      
      // 备用:使用 Electron nativeImage
      const imageBuffer = await fs.readFile(imagePath);
      const image = nativeImage.createFromBuffer(imageBuffer);
      const { width, height } = image.getSize();
      
      return {
        width,
        height,
        mime: 'unknown',
        hasAlpha: false
      };
    } catch (error) {
      console.error('[ImageProcessor] 获取图片信息失败:', error);
      throw error;
    }
  }
}
 
// 导出单例
module.exports = new ImageProcessor();