import { BmpParser } from '@/napcat-image-size/src/parser/BmpParser'; import { GifParser } from '@/napcat-image-size/src/parser/GifParser'; import { JpegParser } from '@/napcat-image-size/src/parser/JpegParser'; import { PngParser } from '@/napcat-image-size/src/parser/PngParser'; import { TiffParser } from '@/napcat-image-size/src/parser/TiffParser'; import { WebpParser } from '@/napcat-image-size/src/parser/WebpParser'; import * as fs from 'fs'; import { ReadStream } from 'fs'; import { Readable } from 'stream'; export interface ImageSize { width: number; height: number; } export enum ImageType { JPEG = 'jpeg', PNG = 'png', BMP = 'bmp', GIF = 'gif', WEBP = 'webp', TIFF = 'tiff', UNKNOWN = 'unknown', } export interface ImageParser { readonly type: ImageType; canParse (buffer: Buffer): boolean; parseSize (stream: ReadStream): Promise; } // 魔术匹配 export function matchMagic (buffer: Buffer, magic: number[], offset = 0): boolean { if (buffer.length < offset + magic.length) { return false; } for (let i = 0; i < magic.length; i++) { if (buffer[offset + i] !== magic[i]) { return false; } } return true; } // 所有解析器实例 const parserInstances = { png: new PngParser(), jpeg: new JpegParser(), bmp: new BmpParser(), gif: new GifParser(), webp: new WebpParser(), tiff: new TiffParser(), }; // 首字节到可能的图片类型映射,用于快速筛选 const firstByteMap = new Map([ [0x42, [ImageType.BMP]], // 'B' - BMP [0x47, [ImageType.GIF]], // 'G' - GIF [0x49, [ImageType.TIFF]], // 'I' - TIFF (II - little endian) [0x4D, [ImageType.TIFF]], // 'M' - TIFF (MM - big endian) [0x52, [ImageType.WEBP]], // 'R' - RIFF (WebP) [0x89, [ImageType.PNG]], // PNG signature [0xFF, [ImageType.JPEG]], // JPEG SOI ]); // 类型到解析器的映射 const typeToParser = new Map([ [ImageType.PNG, parserInstances.png], [ImageType.JPEG, parserInstances.jpeg], [ImageType.BMP, parserInstances.bmp], [ImageType.GIF, parserInstances.gif], [ImageType.WEBP, parserInstances.webp], [ImageType.TIFF, parserInstances.tiff], ]); // 所有解析器列表(用于回退) const parsers: ReadonlyArray = Object.values(parserInstances); export async function detectImageType (filePath: string): Promise { return new Promise((resolve, reject) => { const stream = fs.createReadStream(filePath, { highWaterMark: 64, // 优化读取buffer大小 start: 0, end: 63, }); const chunks: Buffer[] = []; stream.on('error', (err) => { stream.destroy(); reject(err); }); stream.on('data', (chunk: Buffer | string) => { const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); chunks.push(chunkBuffer); }); stream.on('end', () => { const buffer = Buffer.concat(chunks); if (buffer.length === 0) { return resolve(ImageType.UNKNOWN); } for (const parser of parsers) { if (parser.canParse(buffer)) { return resolve(parser.type); } } resolve(ImageType.UNKNOWN); }); }); } export async function imageSizeFromFile (filePath: string): Promise { try { // 先检测类型 const type = await detectImageType(filePath); const parser = typeToParser.get(type); if (!parser) { return undefined; } // 用流式方式解析尺寸 const stream = fs.createReadStream(filePath); try { return await parser.parseSize(stream); } catch (err) { console.error(`解析图片尺寸出错: ${err}`); return undefined; } finally { if (!stream.destroyed) { stream.destroy(); } } } catch (err) { console.error(`检测图片类型出错: ${err}`); return undefined; } } export async function imageSizeFallBack ( filePath: string, fallback: ImageSize = { width: 1024, height: 1024, } ): Promise { return await imageSizeFromFile(filePath) ?? fallback; } // 从 Buffer 创建可读流 function bufferToReadStream (buffer: Buffer): ReadStream { const readable = new Readable({ read () { this.push(buffer); this.push(null); } }); return readable as unknown as ReadStream; } // 从 Buffer 检测图片类型(使用首字节快速筛选) export function detectImageTypeFromBuffer (buffer: Buffer): ImageType { if (buffer.length === 0) { return ImageType.UNKNOWN; } const firstByte = buffer[0]!; const possibleTypes = firstByteMap.get(firstByte); if (possibleTypes) { // 根据首字节快速筛选可能的类型 for (const type of possibleTypes) { const parser = typeToParser.get(type); if (parser && parser.canParse(buffer)) { return parser.type; } } } // 回退:遍历所有解析器 for (const parser of parsers) { if (parser.canParse(buffer)) { return parser.type; } } return ImageType.UNKNOWN; } // 从 Buffer 解析图片尺寸 export async function imageSizeFromBuffer (buffer: Buffer): Promise { const type = detectImageTypeFromBuffer(buffer); const parser = typeToParser.get(type); if (!parser) { return undefined; } try { const stream = bufferToReadStream(buffer); return await parser.parseSize(stream); } catch (err) { console.error(`解析图片尺寸出错: ${err}`); return undefined; } } // 从 Buffer 解析图片尺寸,带回退值 export async function imageSizeFromBufferFallBack ( buffer: Buffer, fallback: ImageSize = { width: 1024, height: 1024, } ): Promise { return await imageSizeFromBuffer(buffer) ?? fallback; }