gongxiangp2/components/upload/image-processor.ts

127 lines
3.5 KiB
TypeScript

/**
* 图片处理工具函数
* 用于压缩和切片图片
*/
/**
* 加载图片文件为HTMLImageElement
*/
export function loadImage(file: File): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
if (typeof window === 'undefined') {
reject(new Error('仅在浏览器环境中支持图片处理'))
return
}
const img = window.document.createElement('img')
img.onload = () => {
URL.revokeObjectURL(img.src)
resolve(img)
}
img.onerror = (err) => {
URL.revokeObjectURL(img.src)
reject(err)
}
img.src = URL.createObjectURL(file)
})
}
/**
* 将Canvas转换为File
*/
export function canvasToFile(
canvas: HTMLCanvasElement,
origin: File,
quality = 0.85,
suffix = ''
): Promise<File> {
return new Promise((resolve, reject) => {
canvas.toBlob(
(blob) => {
if (!blob) {
reject(new Error('图片压缩失败'))
return
}
const baseName = origin.name.replace(/\.[^/.]+$/, '')
const ext = '.jpg'
const fileName = suffix ? `${baseName}${suffix}${ext}` : `${baseName}${ext}`
resolve(new File([blob], fileName, { type: 'image/jpeg' }))
},
'image/jpeg',
quality
)
})
}
/**
* 预览图:只做压缩,不切片
*/
export async function compressPreviewImage(file: File): Promise<File> {
const img = await loadImage(file)
const maxWidth = 800
const maxHeight = 2000
let { width, height } = img
const ratio = Math.min(maxWidth / width, maxHeight / height, 1)
const canvas = document.createElement('canvas')
canvas.width = Math.round(width * ratio)
canvas.height = Math.round(height * ratio)
const ctx = canvas.getContext('2d')
if (!ctx) throw new Error('无法获取画布上下文')
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
return await canvasToFile(canvas, file, 0.85)
}
/**
* 展示图:压缩 + 切片,高度>1500则按 1500px 一段切
*/
export async function compressAndSliceDisplayImage(file: File): Promise<File[]> {
const img = await loadImage(file)
const maxWidth = 800
const maxHeight = 2000
let { width, height } = img
const ratio = Math.min(maxWidth / width, maxHeight / height, 1)
const targetWidth = Math.round(width * ratio)
const targetHeight = Math.round(height * ratio)
const sliceMaxHeight = 1500
const results: File[] = []
if (targetHeight <= sliceMaxHeight) {
const canvas = document.createElement('canvas')
canvas.width = targetWidth
canvas.height = targetHeight
const ctx = canvas.getContext('2d')
if (!ctx) throw new Error('无法获取画布上下文')
ctx.drawImage(img, 0, 0, targetWidth, targetHeight)
const f = await canvasToFile(canvas, file, 0.85)
results.push(f)
return results
}
// 需要切片
const sliceCount = Math.ceil(targetHeight / sliceMaxHeight)
for (let i = 0; i < sliceCount; i++) {
const sliceTop = i * sliceMaxHeight
const sliceHeight = Math.min(sliceMaxHeight, targetHeight - sliceTop)
const canvas = document.createElement('canvas')
canvas.width = targetWidth
canvas.height = sliceHeight
const ctx = canvas.getContext('2d')
if (!ctx) throw new Error('无法获取画布上下文')
ctx.drawImage(
img,
0,
sliceTop / ratio,
width,
sliceHeight / ratio,
0,
0,
targetWidth,
sliceHeight
)
const f = await canvasToFile(canvas, file, 0.85, `-part${i + 1}`)
results.push(f)
}
return results
}