Skip to main content
Glama
image-processor.ts8.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)}`); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/OnePieceLwc/logo-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server