image-processor.ts•8.05 kB
import sharp from 'sharp';
import sizeOf from 'image-size';
export interface ProcessingOptions {
format: 'png' | 'jpg' | 'svg';
size: number;
enhance: boolean;
quality?: number;
}
export interface ProcessedImage {
data: string; // base64编码的图像数据
mimeType: string;
width: number;
height: number;
originalSize: { width: number; height: number };
}
/**
* 图像处理器
* 负责图像的格式转换、尺寸调整、质量增强等
*/
export class ImageProcessor {
/**
* 处理图像
*/
async processImage(imageBuffer: Buffer, options: ProcessingOptions): Promise<ProcessedImage> {
try {
// 获取原始图像信息
const originalDimensions = sizeOf(imageBuffer);
if (!originalDimensions.width || !originalDimensions.height) {
throw new Error('无法获取图像尺寸信息');
}
let processedBuffer = imageBuffer;
// 如果是SVG格式,直接返回
if (options.format === 'svg') {
const svgString = imageBuffer.toString('utf8');
return {
data: Buffer.from(svgString).toString('base64'),
mimeType: 'image/svg+xml',
width: originalDimensions.width,
height: originalDimensions.height,
originalSize: {
width: originalDimensions.width,
height: originalDimensions.height,
},
};
}
// 使用Sharp处理图像
let sharpInstance = sharp(imageBuffer);
// 图像增强处理
if (options.enhance) {
processedBuffer = await this.enhanceImage(sharpInstance, originalDimensions);
sharpInstance = sharp(processedBuffer);
}
// 尺寸调整
if (options.size !== originalDimensions.width || options.size !== originalDimensions.height) {
sharpInstance = sharpInstance.resize(options.size, options.size, {
fit: 'contain',
background: { r: 255, g: 255, b: 255, alpha: 0 }, // 透明背景
});
}
// 格式转换
let finalBuffer: Buffer;
let mimeType: string;
switch (options.format) {
case 'png':
finalBuffer = await sharpInstance
.png({
quality: options.quality || 90,
compressionLevel: 6,
})
.toBuffer();
mimeType = 'image/png';
break;
case 'jpg':
finalBuffer = await sharpInstance
.jpeg({
quality: options.quality || 85,
progressive: true,
})
.toBuffer();
mimeType = 'image/jpeg';
break;
default:
throw new Error(`不支持的格式: ${options.format}`);
}
// 获取处理后的尺寸
const processedDimensions = sizeOf(finalBuffer);
return {
data: finalBuffer.toString('base64'),
mimeType,
width: processedDimensions.width || options.size,
height: processedDimensions.height || options.size,
originalSize: {
width: originalDimensions.width,
height: originalDimensions.height,
},
};
} catch (error) {
throw new Error(`图像处理失败: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* 图像增强处理
* 适用于小尺寸或低质量的Logo图像
*/
private async enhanceImage(sharpInstance: sharp.Sharp, originalDimensions: any): Promise<Buffer> {
const minDimension = Math.min(originalDimensions.width, originalDimensions.height);
// 如果图像太小,进行超分辨率处理
if (minDimension < 64) {
// 先放大2-4倍
const scaleFactor = minDimension < 32 ? 4 : 2;
sharpInstance = sharpInstance.resize(
originalDimensions.width * scaleFactor,
originalDimensions.height * scaleFactor,
{ kernel: sharp.kernel.lanczos3 }
);
}
// 应用图像增强滤镜
return await sharpInstance
.sharpen(1.0, 1.0, 2.0) // 锐化
.modulate({
brightness: 1.05, // 轻微提亮
saturation: 1.1, // 增加饱和度
})
.toBuffer();
}
/**
* 检测并移除图像空白边缘
*/
async removeWhitespace(imageBuffer: Buffer): Promise<Buffer> {
try {
return await sharp(imageBuffer)
.trim({
background: '#ffffff',
threshold: 10, // 容忍度
})
.toBuffer();
} catch (error) {
console.warn('移除空白边缘失败,返回原图像:', error);
return imageBuffer;
}
}
/**
* 检测图像是否损坏
*/
async isImageCorrupted(imageBuffer: Buffer): Promise<boolean> {
try {
const metadata = await sharp(imageBuffer).metadata();
return !metadata.width || !metadata.height || metadata.width < 1 || metadata.height < 1;
} catch {
return true;
}
}
/**
* 优化图像文件大小
*/
async optimizeSize(imageBuffer: Buffer, maxSizeKB: number = 100): Promise<Buffer> {
try {
const originalSize = imageBuffer.length / 1024; // KB
if (originalSize <= maxSizeKB) {
return imageBuffer;
}
// 计算需要的质量级别
const qualityRatio = Math.max(0.3, maxSizeKB / originalSize);
const quality = Math.floor(90 * qualityRatio);
// 尝试PNG压缩
const pngBuffer = await sharp(imageBuffer)
.png({
quality,
compressionLevel: 9,
adaptiveFiltering: true,
})
.toBuffer();
// 尝试JPEG压缩
const jpegBuffer = await sharp(imageBuffer)
.jpeg({
quality,
progressive: true,
mozjpeg: true,
})
.toBuffer();
// 返回更小的版本
return pngBuffer.length < jpegBuffer.length ? pngBuffer : jpegBuffer;
} catch (error) {
console.warn('图像优化失败,返回原图像:', error);
return imageBuffer;
}
}
/**
* 生成不同尺寸的图像变体
*/
async generateVariants(imageBuffer: Buffer, sizes: number[] = [16, 32, 64, 128, 256, 512]): Promise<Map<number, Buffer>> {
const variants = new Map<number, Buffer>();
for (const size of sizes) {
try {
const variant = await sharp(imageBuffer)
.resize(size, size, {
fit: 'contain',
background: { r: 255, g: 255, b: 255, alpha: 0 },
})
.png()
.toBuffer();
variants.set(size, variant);
} catch (error) {
console.warn(`生成 ${size}px 变体失败:`, error);
}
}
return variants;
}
/**
* 转换为圆形Logo
*/
async makeCircular(imageBuffer: Buffer, size: number = 256): Promise<Buffer> {
try {
const roundedCorners = Buffer.from(
`<svg width="${size}" height="${size}">
<defs>
<clipPath id="clip">
<circle cx="${size/2}" cy="${size/2}" r="${size/2}"/>
</clipPath>
</defs>
</svg>`
);
return await sharp(imageBuffer)
.resize(size, size, { fit: 'cover' })
.composite([{
input: roundedCorners,
blend: 'dest-in'
}])
.png()
.toBuffer();
} catch (error) {
throw new Error(`创建圆形Logo失败: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* 添加背景色
*/
async addBackground(imageBuffer: Buffer, backgroundColor: string = '#ffffff'): Promise<Buffer> {
try {
const metadata = await sharp(imageBuffer).metadata();
return await sharp({
create: {
width: metadata.width || 256,
height: metadata.height || 256,
channels: 4,
background: backgroundColor,
}
})
.composite([{ input: imageBuffer }])
.png()
.toBuffer();
} catch (error) {
throw new Error(`添加背景失败: ${error instanceof Error ? error.message : String(error)}`);
}
}
}