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