Files
wish-pool/lib/image-utils.ts
2025-07-19 02:12:37 +08:00

191 lines
4.8 KiB
TypeScript
Raw Permalink 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.

// 圖片處理工具
export interface ImageFile {
id: string
file?: File // 可選,因為從 localStorage 恢復時不會有原始 File
url: string // 改為 base64 URL
name: string
size: number
type: string
base64?: string // 新增 base64 字段
}
export interface ImageValidationResult {
isValid: boolean
error?: string
suggestion?: string
}
// 允許的圖片格式
export const ALLOWED_IMAGE_TYPES = ["image/jpeg", "image/jpg", "image/png", "image/webp", "image/gif"]
// 允許的檔案副檔名
export const ALLOWED_EXTENSIONS = [".jpg", ".jpeg", ".png", ".webp", ".gif"]
// 檔案大小限制 (5MB)
export const MAX_FILE_SIZE = 5 * 1024 * 1024
// 單次上傳數量限制
export const MAX_FILES_PER_UPLOAD = 10
// 總檔案數量限制
export const MAX_TOTAL_FILES = 20
export function validateImageFile(file: File): ImageValidationResult {
// 檢查檔案類型
if (!ALLOWED_IMAGE_TYPES.includes(file.type)) {
return {
isValid: false,
error: `不支援的檔案格式: ${file.type}`,
suggestion: `請使用 JPG、PNG、WebP 或 GIF 格式`,
}
}
// 檢查檔案大小
if (file.size > MAX_FILE_SIZE) {
const sizeMB = (file.size / (1024 * 1024)).toFixed(1)
return {
isValid: false,
error: `檔案過大: ${sizeMB}MB`,
suggestion: `請壓縮圖片至 5MB 以下`,
}
}
// 檢查檔案名稱
if (file.name.length > 100) {
return {
isValid: false,
error: "檔案名稱過長",
suggestion: "請使用較短的檔案名稱",
}
}
return { isValid: true }
}
export function formatFileSize(bytes: number): string {
if (bytes === 0) return "0 Bytes"
const k = 1024
const sizes = ["Bytes", "KB", "MB", "GB"]
const i = Math.floor(Math.log(bytes) / Math.log(k))
return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i]
}
// 將 File 轉換為 base64
export function fileToBase64(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => {
if (typeof reader.result === "string") {
resolve(reader.result)
} else {
reject(new Error("Failed to convert file to base64"))
}
}
reader.onerror = () => reject(reader.error)
reader.readAsDataURL(file)
})
}
// 創建圖片文件對象(使用 base64
export async function createImageFile(file: File): Promise<ImageFile> {
const base64 = await fileToBase64(file)
return {
id: `img_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
file,
url: base64, // 直接使用 base64 作為 URL
name: file.name,
size: file.size,
type: file.type,
base64,
}
}
// 從儲存的數據恢復圖片對象
export function restoreImageFile(data: any): ImageFile {
return {
id: data.id,
url: data.base64 || data.url, // 優先使用 base64向後兼容
name: data.name,
size: data.size,
type: data.type,
base64: data.base64,
}
}
// 不再需要 revokeImageUrl因為使用 base64
export function revokeImageUrl(imageFile: ImageFile): void {
// base64 不需要手動釋放
return
}
// 壓縮圖片並轉為 base64
export function compressImage(file: File, maxWidth = 1920, quality = 0.8): Promise<File> {
return new Promise((resolve) => {
const canvas = document.createElement("canvas")
const ctx = canvas.getContext("2d")
const img = new Image()
img.onload = () => {
// 計算新尺寸
let { width, height } = img
if (width > maxWidth) {
height = (height * maxWidth) / width
width = maxWidth
}
canvas.width = width
canvas.height = height
// 繪製壓縮後的圖片
ctx?.drawImage(img, 0, 0, width, height)
canvas.toBlob(
(blob) => {
if (blob) {
const compressedFile = new File([blob], file.name, {
type: file.type,
lastModified: Date.now(),
})
resolve(compressedFile)
} else {
resolve(file) // 如果壓縮失敗,返回原檔案
}
},
file.type,
quality,
)
}
img.src = URL.createObjectURL(file)
})
}
// 生成縮圖
export function generateThumbnail(file: File, size = 200): Promise<string> {
return new Promise((resolve) => {
const canvas = document.createElement("canvas")
const ctx = canvas.getContext("2d")
const img = new Image()
img.onload = () => {
canvas.width = size
canvas.height = size
// 計算裁切區域 (正方形縮圖)
const minDimension = Math.min(img.width, img.height)
const x = (img.width - minDimension) / 2
const y = (img.height - minDimension) / 2
ctx?.drawImage(img, x, y, minDimension, minDimension, 0, 0, size, size)
resolve(canvas.toDataURL(file.type, 0.7))
}
img.src = URL.createObjectURL(file)
})
}