feat: 图片编辑器添加去水印功能,去水印结果保存到相册,使用动态占位图
This commit is contained in:
parent
f2d7aff7c6
commit
1ba3d7ec83
@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { systemSettings } from "@/lib/db/schema";
|
||||
import { systemSettings, generatedImage } from "@/lib/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { uploadFile } from "@/lib/storage/service";
|
||||
|
||||
@ -192,6 +192,21 @@ export async function POST(request: NextRequest) {
|
||||
const processingTime = (Date.now() - startTime) / 1000;
|
||||
console.log(`[WatermarkRemove] 处理完成,耗时: ${processingTime}s`);
|
||||
|
||||
// 保存到相册/历史出图记录
|
||||
try {
|
||||
await db.insert(generatedImage).values({
|
||||
userId: session.user.id,
|
||||
thumbnailUrl: uploadResult.url,
|
||||
originalUrl: uploadResult.url,
|
||||
imageSize: "2K",
|
||||
prompt: "去水印处理",
|
||||
modelId: "watermark-remove",
|
||||
});
|
||||
console.log(`[WatermarkRemove] 已保存到相册: ${uploadResult.url.substring(0, 80)}...`);
|
||||
} catch (saveError) {
|
||||
console.error("[WatermarkRemove] 保存到相册失败:", saveError);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
original_url: uploadResult.url,
|
||||
|
||||
@ -4,7 +4,7 @@ import React, { useRef, useState, useEffect, useCallback } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import Link from "next/link";
|
||||
import { useTheme } from "next-themes";
|
||||
import { X, Pencil, Circle, ArrowRight, Type, Check, Undo, Trash2, Scissors, Sparkles, Download, Expand, Move, Maximize2, ZoomIn, ZoomOut, Loader2, Move3D, Layers, Upload as UploadIcon, Image as ImageIcon } from "lucide-react";
|
||||
import { X, Pencil, Circle, ArrowRight, Type, Check, Undo, Trash2, Scissors, Sparkles, Download, Expand, Move, Maximize2, ZoomIn, ZoomOut, Loader2, Move3D, Layers, Upload as UploadIcon, Image as ImageIcon, Droplet } from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
import { Input } from "./ui/input";
|
||||
import { toast } from "sonner";
|
||||
@ -67,12 +67,13 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel, initialResizeMod
|
||||
const [isRemovingBg, setIsRemovingBg] = useState(false);
|
||||
const [isEnhancing, setIsEnhancing] = useState(false);
|
||||
const [isExtending, setIsExtending] = useState(false);
|
||||
const [isRemovingWatermark, setIsRemovingWatermark] = useState(false);
|
||||
const [showAngleEditDialog, setShowAngleEditDialog] = useState(false);
|
||||
const [showSceneFusion, setShowSceneFusion] = useState(false);
|
||||
const [originalImageUrl, setOriginalImageUrl] = useState<string | undefined>(imageUrl);
|
||||
const [processedImageUrl, setProcessedImageUrl] = useState<string | null>(null);
|
||||
const [processedImageBase64, setProcessedImageBase64] = useState<string | null>(null);
|
||||
const [processType, setProcessType] = useState<"rmbg" | "enhance" | "extend" | "resize" | null>(null);
|
||||
const [processType, setProcessType] = useState<"rmbg" | "enhance" | "extend" | "resize" | "watermark" | null>(null);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
@ -642,6 +643,66 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel, initialResizeMod
|
||||
}
|
||||
};
|
||||
|
||||
// 去水印功能
|
||||
const handleWatermarkRemove = async () => {
|
||||
if (!image || !originalImageUrl) return;
|
||||
|
||||
setIsRemovingWatermark(true);
|
||||
try {
|
||||
console.log("[去水印] 使用原图URL:", originalImageUrl);
|
||||
|
||||
// 调用去水印API
|
||||
const response = await fetch("/api/watermark-remove", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
image_url: originalImageUrl,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || "去水印失败");
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.original_url) {
|
||||
console.log("[去水印] API返回结果:", {
|
||||
originalUrl: result.original_url?.substring(0, 100),
|
||||
thumbnailUrl: result.thumbnail_url?.substring(0, 100)
|
||||
});
|
||||
|
||||
setProcessedImageUrl(result.original_url);
|
||||
// 下载图片获取base64用于显示
|
||||
try {
|
||||
const imgResponse = await fetch(result.original_url);
|
||||
const blob = await imgResponse.blob();
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
const base64 = (reader.result as string).split(',')[1];
|
||||
setProcessedImageBase64(base64);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
} catch {
|
||||
console.warn("[去水印] 无法获取Base64,仅使用URL");
|
||||
}
|
||||
setProcessType("watermark");
|
||||
|
||||
toast.success(`去水印完成!图片已保存到相册。处理时间: ${result.processing_time?.toFixed(2) || "未知"}秒`);
|
||||
} else {
|
||||
throw new Error(result.error || "去水印处理失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[去水印] 错误:", error);
|
||||
toast.error(error instanceof Error ? error.message : "去水印失败,请稍后重试");
|
||||
} finally {
|
||||
setIsRemovingWatermark(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 变清晰处理
|
||||
const handleEnhance = async () => {
|
||||
if (!image || !originalImageUrl) return;
|
||||
@ -1044,6 +1105,8 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel, initialResizeMod
|
||||
? `变清晰-${Date.now()}.${fileExtension}`
|
||||
: processType === "resize"
|
||||
? `尺寸调整-${resizeCanvasWidth}x${resizeCanvasHeight}-${Date.now()}.${fileExtension}`
|
||||
: processType === "watermark"
|
||||
? `去水印-${Date.now()}.${fileExtension}`
|
||||
: `图像拓展-${Date.now()}.${fileExtension}`;
|
||||
|
||||
// 如果尺寸调整使用Base64,直接下载(优先使用URL,如果没有URL则使用Base64)
|
||||
@ -1241,7 +1304,7 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel, initialResizeMod
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleRemoveBackground}
|
||||
disabled={isRemovingBg || isEnhancing || isExtending || isResizeMode || showAngleEditDialog || showSceneFusion || !image}
|
||||
disabled={isRemovingBg || isEnhancing || isExtending || isResizeMode || showAngleEditDialog || showSceneFusion || isRemovingWatermark || !image}
|
||||
title="移除背景(抠图)"
|
||||
className="text-zinc-300 hover:text-white hover:bg-zinc-800"
|
||||
>
|
||||
@ -1252,7 +1315,7 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel, initialResizeMod
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleEnhance}
|
||||
disabled={isEnhancing || isRemovingBg || isExtending || isResizeMode || showAngleEditDialog || showSceneFusion || !image}
|
||||
disabled={isEnhancing || isRemovingBg || isExtending || isResizeMode || showAngleEditDialog || showSceneFusion || isRemovingWatermark || !image}
|
||||
title="图片变清晰"
|
||||
className="text-zinc-300 hover:text-white hover:bg-zinc-800"
|
||||
>
|
||||
@ -1263,7 +1326,7 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel, initialResizeMod
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleExtend}
|
||||
disabled={isExtending || isRemovingBg || isEnhancing || isResizeMode || showAngleEditDialog || showSceneFusion || !image}
|
||||
disabled={isExtending || isRemovingBg || isEnhancing || isResizeMode || showAngleEditDialog || showSceneFusion || isRemovingWatermark || !image}
|
||||
title="图像拓展"
|
||||
className="text-zinc-300 hover:text-white hover:bg-zinc-800"
|
||||
>
|
||||
@ -1274,7 +1337,7 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel, initialResizeMod
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleResize}
|
||||
disabled={isResizeMode || isRemovingBg || isEnhancing || isExtending || showAngleEditDialog || showSceneFusion || !image}
|
||||
disabled={isResizeMode || isRemovingBg || isEnhancing || isExtending || showAngleEditDialog || showSceneFusion || isRemovingWatermark || !image}
|
||||
className="text-zinc-300 hover:text-white hover:bg-zinc-800"
|
||||
title="尺寸调整"
|
||||
>
|
||||
@ -1285,13 +1348,24 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel, initialResizeMod
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleAngleEdit}
|
||||
disabled={showAngleEditDialog || isRemovingBg || isEnhancing || isExtending || isResizeMode || showSceneFusion || !image}
|
||||
disabled={showAngleEditDialog || isRemovingBg || isEnhancing || isExtending || isResizeMode || showSceneFusion || isRemovingWatermark || !image}
|
||||
title="角度调整"
|
||||
className="text-zinc-300 hover:text-white hover:bg-zinc-800"
|
||||
>
|
||||
<Move3D className="mr-1 h-4 w-4" />
|
||||
角度调整
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleWatermarkRemove}
|
||||
disabled={isRemovingWatermark || isRemovingBg || isEnhancing || isExtending || isResizeMode || showAngleEditDialog || showSceneFusion || !image}
|
||||
title="去水印"
|
||||
className="text-zinc-300 hover:text-white hover:bg-zinc-800"
|
||||
>
|
||||
<Droplet className={`mr-1 h-4 w-4 ${isRemovingWatermark ? "animate-spin" : ""}`} />
|
||||
{isRemovingWatermark ? "处理中..." : "去水印"}
|
||||
</Button>
|
||||
<Button
|
||||
variant={showSceneFusion ? "secondary" : "ghost"}
|
||||
size="sm"
|
||||
@ -1685,7 +1759,7 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel, initialResizeMod
|
||||
<div className="flex flex-1 flex-col items-center justify-center">
|
||||
<div className="mb-2 flex items-center justify-between gap-2 w-full">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{processType === "rmbg" ? "抠图结果" : processType === "enhance" ? "清晰结果" : processType === "resize" ? "尺寸调整结果" : "拓展结果"}
|
||||
{processType === "rmbg" ? "抠图结果" : processType === "enhance" ? "清晰结果" : processType === "resize" ? "尺寸调整结果" : processType === "watermark" ? "去水印结果" : "拓展结果"}
|
||||
</span>
|
||||
<Button
|
||||
variant="default"
|
||||
@ -1702,14 +1776,14 @@ export function ImageAnnotator({ imageUrl, onConfirm, onCancel, initialResizeMod
|
||||
{processedImageUrl ? (
|
||||
<img
|
||||
src={processedImageUrl}
|
||||
alt={processType === "rmbg" ? "抠图结果" : processType === "enhance" ? "清晰结果" : processType === "resize" ? "尺寸调整结果" : "拓展结果"}
|
||||
alt={processType === "rmbg" ? "抠图结果" : processType === "enhance" ? "清晰结果" : processType === "resize" ? "尺寸调整结果" : processType === "watermark" ? "去水印结果" : "拓展结果"}
|
||||
className="max-h-[350px] max-w-full rounded-lg"
|
||||
style={{ maxWidth: canvasRef.current?.width || "400px" }}
|
||||
/>
|
||||
) : processedImageBase64 ? (
|
||||
<img
|
||||
src={`data:image/${processType === "rmbg" || processType === "resize" ? "png" : "jpeg"};base64,${processedImageBase64}`}
|
||||
alt={processType === "rmbg" ? "抠图结果" : processType === "enhance" ? "清晰结果" : processType === "resize" ? "尺寸调整结果" : "拓展结果"}
|
||||
alt={processType === "rmbg" ? "抠图结果" : processType === "enhance" ? "清晰结果" : processType === "resize" ? "尺寸调整结果" : processType === "watermark" ? "去水印结果" : "拓展结果"}
|
||||
className="max-h-[350px] max-w-full rounded-lg"
|
||||
style={{ maxWidth: canvasRef.current?.width || "400px" }}
|
||||
/>
|
||||
|
||||
@ -2,10 +2,10 @@
|
||||
|
||||
import { memo } from "react";
|
||||
import { SparklesIcon } from "./icons";
|
||||
import { Scissors, Expand } from "lucide-react";
|
||||
import { Scissors, Expand, Droplet } from "lucide-react";
|
||||
|
||||
interface ImageProcessingLoadingProps {
|
||||
type: "enhance" | "rmbg" | "extend"; // 处理类型:变清晰、抠图或图像拓展
|
||||
type: "enhance" | "rmbg" | "extend" | "watermark"; // 处理类型:变清晰、抠图、图像拓展或去水印
|
||||
}
|
||||
|
||||
/**
|
||||
@ -16,6 +16,7 @@ export const ImageProcessingLoading = memo(({ type }: ImageProcessingLoadingProp
|
||||
const isEnhance = type === "enhance";
|
||||
const isRmbg = type === "rmbg";
|
||||
const isExtend = type === "extend";
|
||||
const isWatermark = type === "watermark";
|
||||
|
||||
let text: string;
|
||||
let icon: any;
|
||||
@ -47,6 +48,19 @@ export const ImageProcessingLoading = memo(({ type }: ImageProcessingLoadingProp
|
||||
gradientFromOverlay: "from-green-500/40",
|
||||
gradientToOverlay: "to-green-500/30",
|
||||
};
|
||||
} else if (isWatermark) {
|
||||
text = "正在去水印";
|
||||
icon = Droplet;
|
||||
colorClasses = {
|
||||
ping: "bg-cyan-500/20",
|
||||
pulse: "bg-cyan-500/10",
|
||||
ring: "ring-cyan-500/30",
|
||||
text: "text-cyan-500",
|
||||
dot: "bg-cyan-500",
|
||||
gradientFrom: "from-cyan-400",
|
||||
gradientFromOverlay: "from-cyan-500/40",
|
||||
gradientToOverlay: "to-cyan-500/30",
|
||||
};
|
||||
} else {
|
||||
text = "正在图像拓展";
|
||||
icon = Expand;
|
||||
|
||||
@ -257,12 +257,14 @@ const PurePreviewMessage = ({
|
||||
if (type === "text") {
|
||||
if (mode === "view") {
|
||||
// 检查是否是图片处理的加载消息
|
||||
if (part.text === "正在变清晰..." || part.text === "正在抠图..." || part.text === "正在图像拓展...") {
|
||||
let type: "enhance" | "rmbg" | "extend";
|
||||
if (part.text === "正在变清晰..." || part.text === "正在抠图..." || part.text === "正在图像拓展..." || part.text === "正在去水印...") {
|
||||
let type: "enhance" | "rmbg" | "extend" | "watermark";
|
||||
if (part.text === "正在变清晰...") {
|
||||
type = "enhance";
|
||||
} else if (part.text === "正在抠图...") {
|
||||
type = "rmbg";
|
||||
} else if (part.text === "正在去水印...") {
|
||||
type = "watermark";
|
||||
} else {
|
||||
type = "extend";
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user