flnxi-Wu/lib/image-processor.ts
WuYongJie f646b71813
Some checks failed
CI Check / check (push) Has been cancelled
chore: 初始化本地仓库并同步最近改动
2025-11-18 17:50:16 +08:00

182 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sharp from 'sharp'
interface ProcessImageOptions {
maxSizeKB?: number // 最大文件大小(KB)
maxWidth?: number // 最大宽度(像素)
maxHeight?: number // 最大高度(像素)
format?: 'webp' | 'jpeg' | 'png'
}
/**
* 处理图片:压缩、调整尺寸、转换格式
* @param buffer 原始图片buffer
* @param options 处理选项
* @returns 处理后的图片buffer
*/
export async function processImage(
buffer: Buffer,
options: ProcessImageOptions = {}
): Promise<Buffer> {
const {
maxSizeKB = 300, // 默认300KB
maxWidth = 600, // 默认600像素
maxHeight = 800, // 默认800像素(适配300x400显示)
format = 'webp', // 默认webP格式
} = options
try {
// 获取原始图片信息
const image = sharp(buffer)
const metadata = await image.metadata()
// 计算新尺寸(保持比例,同时限制宽度和高度)
let width = metadata.width || maxWidth
let height = metadata.height || maxHeight
// 如果宽度超过限制
if (width > maxWidth) {
const ratio = maxWidth / width
width = maxWidth
height = Math.round(height * ratio)
}
// 如果高度超过限制,再次调整
if (height > maxHeight) {
const ratio = maxHeight / height
height = maxHeight
width = Math.round(width * ratio)
}
// 初始质量
let quality = 80
let processedBuffer: Buffer
// 第一次处理
processedBuffer = await image
.resize(width, height, {
fit: 'inside',
withoutEnlargement: true,
})
.webp({ quality })
.toBuffer()
// 如果文件还是太大,逐步降低质量
const maxBytes = maxSizeKB * 1024
while (processedBuffer.length > maxBytes && quality > 20) {
quality -= 10
processedBuffer = await sharp(buffer)
.resize(width, height, {
fit: 'inside',
withoutEnlargement: true,
})
.webp({ quality })
.toBuffer()
}
console.log(`图片处理完成: ${metadata.width}x${metadata.height} -> ${width}x${height}, ${(buffer.length / 1024).toFixed(2)}KB -> ${(processedBuffer.length / 1024).toFixed(2)}KB, 质量: ${quality}`)
return processedBuffer
} catch (error) {
console.error('图片处理失败:', error)
throw new Error('图片处理失败')
}
}
/**
* 获取处理后的文件名(添加.webp扩展名)
*/
export function getProcessedFileName(originalName: string): string {
const nameWithoutExt = originalName.replace(/\.[^/.]+$/, '')
return `${nameWithoutExt}.webp`
}
/**
* 切割长图片为多个最高1500px的图片片段,并压缩到指定大小
* @param buffer 原始图片buffer
* @param maxHeight 每个片段的最大高度(默认1500px)
* @param maxSizeKB 每个切片的最大文件大小(默认500KB)
* @returns 切割后的图片buffer数组
*/
export async function sliceImage(
buffer: Buffer,
maxHeight: number = 1500,
maxSizeKB: number = 500
): Promise<Buffer[]> {
try {
const image = sharp(buffer)
const metadata = await image.metadata()
if (!metadata.width || !metadata.height) {
throw new Error('无法获取图片尺寸')
}
const { width, height } = metadata
const slices: Buffer[] = []
const maxBytes = maxSizeKB * 1024
// 如果图片高度不超过maxHeight,直接返回原图(压缩处理)
if (height <= maxHeight) {
let quality = 90
let processedBuffer = await image
.webp({ quality })
.toBuffer()
// 如果文件还是太大,逐步降低质量
while (processedBuffer.length > maxBytes && quality > 20) {
quality -= 10
processedBuffer = await image
.webp({ quality })
.toBuffer()
}
console.log(`图片无需切割: ${width}x${height}, 压缩到 ${(processedBuffer.length / 1024).toFixed(2)}KB, 质量: ${quality}`)
return [processedBuffer]
}
// 计算需要切割的片数
const sliceCount = Math.ceil(height / maxHeight)
console.log(`开始切割图片: ${width}x${height} -> ${sliceCount}个片段,每片限制${maxSizeKB}KB`)
// 切割图片
for (let i = 0; i < sliceCount; i++) {
const top = i * maxHeight
const extractHeight = Math.min(maxHeight, height - top)
// 初始质量
let quality = 90
let sliceBuffer = await sharp(buffer)
.extract({
left: 0,
top: top,
width: width,
height: extractHeight
})
.webp({ quality })
.toBuffer()
// 如果文件还是太大,逐步降低质量
while (sliceBuffer.length > maxBytes && quality > 20) {
quality -= 10
sliceBuffer = await sharp(buffer)
.extract({
left: 0,
top: top,
width: width,
height: extractHeight
})
.webp({ quality })
.toBuffer()
}
slices.push(sliceBuffer)
console.log(`切割片段 ${i + 1}/${sliceCount}: 高度 ${extractHeight}px, 大小 ${(sliceBuffer.length / 1024).toFixed(2)}KB, 质量: ${quality}`)
}
return slices
} catch (error) {
console.error('图片切割失败:', error)
throw new Error('图片切割失败')
}
}