/** * 统一图片处理器 * 集成缩略图生成、图片压缩、尺寸调整等功能 * 使用 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} 处理结果 */ 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();