From 97195708979d6ea8950893fdbcac4a354d2fc188 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 29 Dec 2025 07:24:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9C=A8=E5=9B=BE=E7=89=87=E6=A0=87?= =?UTF-8?q?=E6=B3=A8=E7=BB=84=E4=BB=B6=E4=B8=AD=E6=B7=BB=E5=8A=A0=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=90=8E=E5=9B=BE=E7=89=87=E5=B9=B6=E6=8E=92=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E5=92=8C=E4=B8=8B=E8=BD=BD=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=8A=A0=E5=9B=BE=E5=92=8C=E5=8F=98=E6=B8=85?= =?UTF-8?q?=E6=99=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/image-annotator.tsx | 151 +++++++++++++++++++++------------ 1 file changed, 98 insertions(+), 53 deletions(-) diff --git a/components/image-annotator.tsx b/components/image-annotator.tsx index 6a4d2aa56..5f8233389 100644 --- a/components/image-annotator.tsx +++ b/components/image-annotator.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useRef, useState, useEffect, useCallback } from "react"; -import { X, Pencil, Circle, ArrowRight, Type, Check, Undo, Trash2, Scissors, Sparkles } from "lucide-react"; +import { X, Pencil, Circle, ArrowRight, Type, Check, Undo, Trash2, Scissors, Sparkles, Download } from "lucide-react"; import { Button } from "./ui/button"; import { toast } from "sonner"; @@ -59,10 +59,18 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel }: ImageAnnotator const [isLoading, setIsLoading] = useState(true); const [isRemovingBg, setIsRemovingBg] = useState(false); const [isEnhancing, setIsEnhancing] = useState(false); + const [originalImageUrl, setOriginalImageUrl] = useState(imageUrl); + const [processedImageUrl, setProcessedImageUrl] = useState(null); + const [processedImageBase64, setProcessedImageBase64] = useState(null); + const [processType, setProcessType] = useState<"rmbg" | "enhance" | null>(null); // 加载图片 useEffect(() => { setIsLoading(true); + setOriginalImageUrl(imageUrl); + setProcessedImageUrl(null); + setProcessedImageBase64(null); + setProcessType(null); const img = new window.Image(); img.crossOrigin = "anonymous"; img.onload = () => { @@ -344,28 +352,16 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel }: ImageAnnotator const result = await response.json(); if (result.success && result.image_base64) { - // 将返回的Base64图片加载为新图片 - const processedImage = new window.Image(); - processedImage.crossOrigin = "anonymous"; - - processedImage.onload = () => { - // 更新图片和画布 - setImage(processedImage); - // 清空所有标注(因为背景已移除) - setActions([]); - toast.success(`抠图完成!图片已保存到相册。处理时间: ${result.processing_time?.toFixed(2) || "未知"}秒`); - }; - - processedImage.onerror = () => { - throw new Error("加载处理后的图片失败"); - }; - - // 优先使用S3 URL(如果可用),否则使用Base64 + // 保存处理后的图片URL和Base64 if (result.original_url) { - processedImage.src = result.original_url; + setProcessedImageUrl(result.original_url); } else { - processedImage.src = `data:image/png;base64,${result.image_base64}`; + setProcessedImageUrl(null); } + setProcessedImageBase64(result.image_base64); + setProcessType("rmbg"); + + toast.success(`抠图完成!图片已保存到相册。处理时间: ${result.processing_time?.toFixed(2) || "未知"}秒`); } else { throw new Error(result.error || "抠图处理失败"); } @@ -410,28 +406,16 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel }: ImageAnnotator const result = await response.json(); if (result.success && result.image_base64) { - // 将返回的Base64图片加载为新图片 - const processedImage = new window.Image(); - processedImage.crossOrigin = "anonymous"; - - processedImage.onload = () => { - // 更新图片和画布 - setImage(processedImage); - // 清空所有标注(因为图片已更新) - setActions([]); - toast.success(`变清晰完成!图片已保存到相册。处理时间: ${result.processing_time?.toFixed(2) || "未知"}秒`); - }; - - processedImage.onerror = () => { - throw new Error("加载处理后的图片失败"); - }; - - // 优先使用S3 URL(如果可用),否则使用Base64 + // 保存处理后的图片URL和Base64 if (result.original_url) { - processedImage.src = result.original_url; + setProcessedImageUrl(result.original_url); } else { - processedImage.src = `data:image/jpeg;base64,${result.image_base64}`; + setProcessedImageUrl(null); } + setProcessedImageBase64(result.image_base64); + setProcessType("enhance"); + + toast.success(`变清晰完成!图片已保存到相册。处理时间: ${result.processing_time?.toFixed(2) || "未知"}秒`); } else { throw new Error(result.error || "变清晰处理失败"); } @@ -443,6 +427,33 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel }: ImageAnnotator } }; + // 下载处理后的图片 + const handleDownloadProcessed = () => { + if (!processedImageBase64) return; + + try { + // 确定文件类型和MIME类型 + const isPng = processType === "rmbg"; + const mimeType = isPng ? "image/png" : "image/jpeg"; + const fileExtension = isPng ? "png" : "jpg"; + const fileName = processType === "rmbg" ? `抠图-${Date.now()}.${fileExtension}` : `变清晰-${Date.now()}.${fileExtension}`; + + // 创建下载链接 + const dataUrl = `data:${mimeType};base64,${processedImageBase64}`; + const link = document.createElement("a"); + link.href = dataUrl; + link.download = fileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + toast.success("图片下载成功"); + } catch (error) { + console.error("[下载] 错误:", error); + toast.error("下载失败,请稍后重试"); + } + }; + // 确认 const handleConfirm = () => { const canvas = canvasRef.current; @@ -473,7 +484,7 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel }: ImageAnnotator {/* 弹窗容器 */}
e.stopPropagation()} > {/* 顶部标题栏 */} @@ -587,8 +598,8 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel }: ImageAnnotator
- {/* 画布区域 */} -
+ {/* 画布区域 - 并排显示原图和处理后的图片 */} +
{isLoading ? ( /* 加载动画 */
@@ -609,17 +620,51 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel }: ImageAnnotator 正在加载图片...
) : ( - + <> + {/* 原图画布 */} +
+
原图
+ +
+ + {/* 处理后的图片展示区域 */} + {processedImageBase64 && ( +
+
+ + {processType === "rmbg" ? "抠图结果" : "清晰结果"} + + +
+
+ {processType +
+
+ )} + )}