import { useEffect, useRef, useState, type PointerEvent as ReactPointerEvent } from "react"; import { Button, Input, Modal, Tag } from "antd"; import { Layers3, Plus, RotateCcw, X } from "lucide-react"; import { ModelPicker } from "@/components/model-picker"; import type { AiConfig } from "@/stores/use-config-store"; import { defaultImageParamsForModel } from "@/lib/model-selection"; export type CanvasImageLayerDecompositionPayload = { prompt: string; regions?: Array<[number, number, number, number]>; generationConfig?: Partial>; }; const DEFAULT_PROMPT = "将图片拆分为可独立编辑的图层:识别主要主体、前景、背景和重要物体,每个图层单独输出,保持原图外观和边缘细节,使用透明背景,不要合并不同图层。"; export function CanvasNodeLayerDecompositionDialog({ dataUrl, open, config, onClose, onConfirm, }: { dataUrl: string; open: boolean; config: AiConfig; onClose: () => void; onConfirm: (payload: CanvasImageLayerDecompositionPayload) => void; }) { const [prompt, setPrompt] = useState(DEFAULT_PROMPT); const [generationConfig, setGenerationConfig] = useState(config); const [regions, setRegions] = useState>([]); const [drawing, setDrawing] = useState<{ x: number; y: number } | null>(null); const [draft, setDraft] = useState<[number, number, number, number] | null>(null); const imageFrameRef = useRef(null); useEffect(() => { if (!open) return; setPrompt(DEFAULT_PROMPT); setGenerationConfig(config); setRegions([]); setDrawing(null); setDraft(null); }, [config, dataUrl, open]); const point = (event: ReactPointerEvent) => { const rect = imageFrameRef.current?.getBoundingClientRect(); if (!rect) return null; return { x: Math.max(0, Math.min(1000, ((event.clientX - rect.left) / Math.max(1, rect.width)) * 1000)), y: Math.max(0, Math.min(1000, ((event.clientY - rect.top) / Math.max(1, rect.height)) * 1000)), }; }; const startBox = (event: ReactPointerEvent) => { const next = point(event); if (!next) return; event.preventDefault(); event.currentTarget.setPointerCapture(event.pointerId); setDrawing(next); setDraft([next.x, next.y, next.x, next.y]); }; const moveBox = (event: ReactPointerEvent) => { if (!drawing) return; const next = point(event); if (!next) return; setDraft([Math.min(drawing.x, next.x), Math.min(drawing.y, next.y), Math.max(drawing.x, next.x), Math.max(drawing.y, next.y)]); }; const finishBox = () => { if (!draft) return; const [x1, y1, x2, y2] = draft; if (x2 - x1 >= 2 && y2 - y1 >= 2) setRegions((current) => [...current, [Math.round(x1), Math.round(y1), Math.round(x2), Math.round(y2)]]); setDrawing(null); setDraft(null); }; const selectedPrompt = regions.length ? `${prompt.trim()}\n\n重点处理用户框选的区域,并分别输出这些区域中的主体为独立透明 PNG 图层。选区坐标(图像 0-1000 坐标系):${regions.map((region, index) => `区域${index + 1} ${region.join(" ")}`).join(";")}` : prompt.trim(); return (
待拆分图片
{regions.map((region, index) => )} {draft ? : null}

拆分图片图层

可在图片上拖拽框选对象,AI 会返回多个透明背景图层,并在画布中自动排列为独立节点。

{regions.length ? `已框选 ${regions.length} 个区域` : "未框选,按描述拆分"} {regions.length ? : 在左侧图片上拖动添加选区}
setPrompt(event.target.value)} />
图层拆分模型
setGenerationConfig((current) => ({ ...current, model, imageModel: model, ...defaultImageParamsForModel(current, model) }))} />
); } function RegionBox({ region, label, draft = false }: { region: [number, number, number, number]; label: number; draft?: boolean }) { const [x1, y1, x2, y2] = region; return
{label}
; }