From d2b1fd22d0d28d3de45dcce02f92a57d88effef2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 24 Mar 2026 03:26:45 +0800 Subject: [PATCH] fix: crop layer split overlays by bounds --- components/canvas/nodes/ImageNode.tsx | 91 ++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 9 deletions(-) diff --git a/components/canvas/nodes/ImageNode.tsx b/components/canvas/nodes/ImageNode.tsx index b21d33a75..d8feebe55 100644 --- a/components/canvas/nodes/ImageNode.tsx +++ b/components/canvas/nodes/ImageNode.tsx @@ -63,6 +63,16 @@ interface ImageDisplayMetrics { naturalHeight: number; } +interface LayerSplitBounds { + x: number; + y: number; + width: number; + height: number; + originalWidth: number; + originalHeight: number; + coverage: number; +} + interface BoxCutoutPoint { x: number; y: number; @@ -399,6 +409,56 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) { }, [detailSubjectAnalysis, detailUiDslDocumentFromMetadata, detailUiScreenOverlay]); const shouldRenderDetailOverlay = data.metadata?.detailGenerationMode === "agent_layout" && Boolean(detailUiScreenOverlay); + const layerSplitBounds = useMemo(() => { + const raw = data.metadata?.bounds; + if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null; + + const x = Number((raw as any).x); + const y = Number((raw as any).y); + const width = Number((raw as any).width); + const height = Number((raw as any).height); + const originalWidth = Number((raw as any).originalWidth); + const originalHeight = Number((raw as any).originalHeight); + const coverage = Number((raw as any).coverage); + + if ( + !Number.isFinite(x) || + !Number.isFinite(y) || + !Number.isFinite(width) || + !Number.isFinite(height) || + !Number.isFinite(originalWidth) || + !Number.isFinite(originalHeight) || + width <= 0 || + height <= 0 || + originalWidth <= 0 || + originalHeight <= 0 + ) { + return null; + } + + return { + x, + y, + width, + height, + originalWidth, + originalHeight, + coverage: Number.isFinite(coverage) ? coverage : 0, + }; + }, [data.metadata]); + const layerSplitCropStyle = useMemo(() => { + if (!layerSplitBounds) return undefined; + + return { + position: "absolute", + width: `${(layerSplitBounds.originalWidth / layerSplitBounds.width) * 100}%`, + height: `${(layerSplitBounds.originalHeight / layerSplitBounds.height) * 100}%`, + maxWidth: "none", + maxHeight: "none", + left: `${-(layerSplitBounds.x / layerSplitBounds.width) * 100}%`, + top: `${-(layerSplitBounds.y / layerSplitBounds.height) * 100}%`, + }; + }, [layerSplitBounds]); const shortAgentExecutionSnapshotId = agentExecutionSnapshotId ? agentExecutionSnapshotId.slice(0, 8) : ""; @@ -1978,7 +2038,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) { return; } - handleLayerSplitComplete( + const addedLayerCount = handleLayerSplitComplete( layers.map((layer) => ({ originalUrl: layer.originalUrl, title: layer.title, @@ -1987,7 +2047,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) { bounds: layer.bounds ?? null, })) ); - toast.success(`图层拆分完成,已新增 ${layers.length} 个图层结果`); + toast.success(`图层拆分完成,已新增 ${addedLayerCount} 个图层结果`); } catch (error) { console.error("图层拆分失败:", error); toast.error("图层拆分失败"); @@ -2208,12 +2268,20 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) { throw new Error("没有可添加到画布的图层结果"); } - const orderedLayers = [...validLayers].sort((left, right) => { - const leftArea = (left.bounds?.width || node.size.width) * (left.bounds?.height || node.size.height); - const rightArea = - (right.bounds?.width || node.size.width) * (right.bounds?.height || node.size.height); - return rightArea - leftArea; - }); + const isFullFrameLayer = (bounds?: LayerSplitBounds | null) => { + if (!bounds) return false; + return ( + bounds.coverage >= 0.92 && + bounds.width >= bounds.originalWidth - 2 && + bounds.height >= bounds.originalHeight - 2 + ); + }; + + const drawableLayers = validLayers.filter((layer) => !isFullFrameLayer(layer.bounds)); + const orderedLayers = + (drawableLayers.length > 0 ? drawableLayers : validLayers).sort( + (left, right) => (left.layerIndex || 0) - (right.layerIndex || 0) + ); orderedLayers.forEach((layer, index) => { const layerTitle = `${displayTitle}-${layer.title || `图层${layer.layerIndex || index + 1}`}`; @@ -2263,6 +2331,8 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) { connections: [node.id], }); }); + + return orderedLayers.length; }, [addNode, data.src, displayTitle, node.id, node.position.x, node.position.y, node.size.height, node.size.width] ); @@ -2655,9 +2725,12 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) { src={displayImageSrc} alt={displayTitle} className={cn( - "w-full h-full object-contain bg-muted/30", + layerSplitCropStyle + ? "absolute bg-transparent" + : "w-full h-full object-contain bg-muted/30", boxCutoutMode ? "cursor-crosshair" : isThisNodePicking ? "cursor-crosshair" : "cursor-pointer" )} + style={layerSplitCropStyle} draggable={false} loading="lazy" decoding="async"