add image compression and client-side download cache

This commit is contained in:
jingrow 2025-12-21 19:54:55 +08:00
parent ea6d1b218c
commit ad43d0f8bb
2 changed files with 160 additions and 33 deletions

View File

@ -211,6 +211,7 @@ const urlInputRef = ref<HTMLInputElement | null>(null)
const uploadedImage = ref<File | null>(null)
const uploadedImageUrl = ref<string>('')
const resultImage = ref<string>('')
const resultImageBlobUrl = ref<string>('') // blob URL
const imageUrl = ref<string>('')
const resultImageUrl = computed(() => {
if (!resultImage.value) return ''
@ -459,6 +460,9 @@ const handleRemoveBackground = async () => {
if (firstResult.success && firstResult.image_url) {
resultImage.value = firstResult.image_url
// blob URL
await cacheResultImage(firstResult.image_url)
//
if (currentHistoryIndex.value === -1 && uploadedImage.value && uploadedImageUrl.value) {
const historyItem: HistoryItem = {
@ -495,6 +499,30 @@ const handleRemoveBackground = async () => {
}
}
/**
* 缓存结果图片为 blob URL用于下载
*/
const cacheResultImage = async (imageUrl: string) => {
try {
// blob URL
if (resultImageBlobUrl.value) {
URL.revokeObjectURL(resultImageBlobUrl.value)
resultImageBlobUrl.value = ''
}
// blob URL
const response = await fetch(imageUrl)
if (!response.ok) {
throw new Error(`Failed to fetch image: ${response.status}`)
}
const blob = await response.blob()
resultImageBlobUrl.value = URL.createObjectURL(blob)
} catch (error) {
console.error('缓存图片失败:', error)
//
}
}
const handleDownload = async () => {
if (!resultImage.value) return
@ -506,15 +534,27 @@ const handleDownload = async () => {
}
try {
const response = await fetch(resultImage.value)
const blob = await response.blob()
const blobUrl = URL.createObjectURL(blob)
// 使 blob URL
let blobUrl = resultImageBlobUrl.value
//
if (!blobUrl) {
const response = await fetch(resultImage.value)
if (!response.ok) {
throw new Error(`Failed to fetch image: ${response.status}`)
}
const blob = await response.blob()
blobUrl = URL.createObjectURL(blob)
resultImageBlobUrl.value = blobUrl
}
const link = document.createElement('a')
link.href = blobUrl
link.download = `removed-background-${Date.now()}.png`
link.click()
URL.revokeObjectURL(blobUrl)
// blob URL使
} catch (error) {
console.error('下载失败:', error)
message.error('下载失败')
}
}
@ -523,6 +563,11 @@ const resetUpload = () => {
if (uploadedImageUrl.value && uploadedImageUrl.value.startsWith('blob:')) {
URL.revokeObjectURL(uploadedImageUrl.value)
}
// blob URL
if (resultImageBlobUrl.value) {
URL.revokeObjectURL(resultImageBlobUrl.value)
resultImageBlobUrl.value = ''
}
uploadedImage.value = null
uploadedImageUrl.value = ''
resultImage.value = ''
@ -771,7 +816,7 @@ const handleUrlSubmit = async () => {
}
}
const selectHistoryItem = (index: number) => {
const selectHistoryItem = async (index: number) => {
if (index < 0 || index >= historyList.value.length) return
const item = historyList.value[index]
@ -780,6 +825,11 @@ const selectHistoryItem = (index: number) => {
resultImage.value = item.resultImage
splitPosition.value = 0
uploadedImage.value = item.originalImageFile
//
if (item.resultImage) {
await cacheResultImage(item.resultImage)
}
}
const getHistoryThumbnailUrl = (item: HistoryItem): string => {
@ -836,6 +886,10 @@ onUnmounted(() => {
if (uploadedImageUrl.value && uploadedImageUrl.value.startsWith('blob:')) {
URL.revokeObjectURL(uploadedImageUrl.value)
}
// blob URL
if (resultImageBlobUrl.value) {
URL.revokeObjectURL(resultImageBlobUrl.value)
}
})
</script>

View File

@ -191,6 +191,7 @@ import { t } from '@/shared/i18n'
import { get_session_api_headers } from '@/shared/api/auth'
import { useAuthStore } from '@/shared/stores/auth'
import { useSEO } from '@/shared/composables/useSEO'
import { compressImageFile } from '@/shared/utils/imageResize'
const message = useMessage()
const authStore = useAuthStore()
@ -217,6 +218,7 @@ const urlInputRef = ref<HTMLInputElement | null>(null)
const uploadedImage = ref<File | null>(null)
const uploadedImageUrl = ref<string>('')
const resultImage = ref<string>('')
const resultImageBlobUrl = ref<string>('') // blob URL
const imageUrl = ref<string>('')
const resultImageUrl = computed(() => {
if (!resultImage.value) return ''
@ -367,14 +369,27 @@ const handleUrlSubmit = async () => {
}
// File
const file = new File([blob], 'image-from-url', { type: blob.type })
uploadedImage.value = file
const originalFile = new File([blob], 'image-from-url', { type: blob.type })
// URL
uploadedImageUrl.value = URL.createObjectURL(blob)
//
await handleRemoveBackground()
// 1024x1024
try {
const compressedFile = await compressImageFile(originalFile, {
maxWidth: 1024,
maxHeight: 1024,
quality: 0.92,
mode: 'contain'
})
uploadedImage.value = compressedFile
uploadedImageUrl.value = URL.createObjectURL(compressedFile)
//
await handleRemoveBackground()
} catch (error) {
console.error('图片压缩失败:', error)
message.error('图片处理失败,请重试')
processing.value = false
}
} catch (error: any) {
console.error('加载图片URL失败:', error)
let errorMessage = t('Failed to load image from URL')
@ -402,6 +417,10 @@ onUnmounted(() => {
if (uploadedImageUrl.value && uploadedImageUrl.value.startsWith('blob:')) {
URL.revokeObjectURL(uploadedImageUrl.value)
}
// blob URL
if (resultImageBlobUrl.value) {
URL.revokeObjectURL(resultImageBlobUrl.value)
}
})
const triggerFileInput = () => {
@ -458,7 +477,7 @@ const handleDrop = (event: DragEvent) => {
}
}
const processFile = (file: File) => {
const processFile = async (file: File) => {
const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']
if (!validTypes.includes(file.type)) {
message.warning(t('Unsupported image format. Please use JPG, PNG, or WebP'))
@ -471,17 +490,30 @@ const processFile = (file: File) => {
return
}
uploadedImage.value = file
resultImage.value = ''
splitPosition.value = 0
currentHistoryIndex.value = -1
const reader = new FileReader()
reader.onload = (e) => {
uploadedImageUrl.value = e.target?.result as string
handleRemoveBackground()
// 1024x1024
try {
const compressedFile = await compressImageFile(file, {
maxWidth: 1024,
maxHeight: 1024,
quality: 0.92,
mode: 'contain'
})
uploadedImage.value = compressedFile
resultImage.value = ''
splitPosition.value = 0
currentHistoryIndex.value = -1
const reader = new FileReader()
reader.onload = (e) => {
uploadedImageUrl.value = e.target?.result as string
handleRemoveBackground()
}
reader.readAsDataURL(compressedFile)
} catch (error) {
console.error('图片压缩失败:', error)
message.error('图片处理失败,请重试')
}
reader.readAsDataURL(file)
}
const resetUpload = () => {
@ -489,6 +521,11 @@ const resetUpload = () => {
if (uploadedImageUrl.value && uploadedImageUrl.value.startsWith('blob:')) {
URL.revokeObjectURL(uploadedImageUrl.value)
}
// blob URL
if (resultImageBlobUrl.value) {
URL.revokeObjectURL(resultImageBlobUrl.value)
resultImageBlobUrl.value = ''
}
uploadedImage.value = null
uploadedImageUrl.value = ''
resultImage.value = ''
@ -504,7 +541,7 @@ const resetUpload = () => {
}
}
const selectHistoryItem = (index: number) => {
const selectHistoryItem = async (index: number) => {
if (index < 0 || index >= historyList.value.length) return
const item = historyList.value[index]
@ -513,6 +550,11 @@ const selectHistoryItem = (index: number) => {
resultImage.value = item.resultImage
splitPosition.value = 0
uploadedImage.value = item.originalImageFile
//
if (item.resultImage) {
await cacheResultImage(item.resultImage)
}
}
const getHistoryThumbnailUrl = (item: HistoryItem): string => {
@ -581,6 +623,9 @@ const handleRemoveBackground = async () => {
if (firstResult.success && firstResult.image_url) {
resultImage.value = firstResult.image_url
// blob URL
await cacheResultImage(firstResult.image_url)
if (currentHistoryIndex.value === -1 && uploadedImage.value && uploadedImageUrl.value) {
const historyItem: HistoryItem = {
id: `history-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
@ -633,18 +678,47 @@ const handleRemoveBackground = async () => {
}
}
/**
* 缓存结果图片为 blob URL用于下载
*/
const cacheResultImage = async (imageUrl: string) => {
try {
// blob URL
if (resultImageBlobUrl.value) {
URL.revokeObjectURL(resultImageBlobUrl.value)
resultImageBlobUrl.value = ''
}
// blob URL
const response = await fetch(imageUrl)
if (!response.ok) {
throw new Error(`Failed to fetch image: ${response.status}`)
}
const blob = await response.blob()
resultImageBlobUrl.value = URL.createObjectURL(blob)
} catch (error) {
console.error('缓存图片失败:', error)
//
}
}
const handleDownload = async () => {
if (!resultImage.value) return
try {
// Blob
const response = await fetch(resultImage.value)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
// 使 blob URL
let blobUrl = resultImageBlobUrl.value
const blob = await response.blob()
const blobUrl = URL.createObjectURL(blob)
//
if (!blobUrl) {
const response = await fetch(resultImage.value)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const blob = await response.blob()
blobUrl = URL.createObjectURL(blob)
resultImageBlobUrl.value = blobUrl
}
//
const link = document.createElement('a')
@ -655,10 +729,9 @@ const handleDownload = async () => {
document.body.appendChild(link)
link.click()
// DOM blob URL
// DOM blob URL使
requestAnimationFrame(() => {
document.body.removeChild(link)
URL.revokeObjectURL(blobUrl)
})
} catch (error: any) {
console.error('下载图片失败:', error)