fix: crop layer split overlays by bounds
This commit is contained in:
parent
60c9c57263
commit
d2b1fd22d0
@ -63,6 +63,16 @@ interface ImageDisplayMetrics {
|
|||||||
naturalHeight: number;
|
naturalHeight: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LayerSplitBounds {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
originalWidth: number;
|
||||||
|
originalHeight: number;
|
||||||
|
coverage: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface BoxCutoutPoint {
|
interface BoxCutoutPoint {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
@ -399,6 +409,56 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
}, [detailSubjectAnalysis, detailUiDslDocumentFromMetadata, detailUiScreenOverlay]);
|
}, [detailSubjectAnalysis, detailUiDslDocumentFromMetadata, detailUiScreenOverlay]);
|
||||||
const shouldRenderDetailOverlay =
|
const shouldRenderDetailOverlay =
|
||||||
data.metadata?.detailGenerationMode === "agent_layout" && Boolean(detailUiScreenOverlay);
|
data.metadata?.detailGenerationMode === "agent_layout" && Boolean(detailUiScreenOverlay);
|
||||||
|
const layerSplitBounds = useMemo<LayerSplitBounds | null>(() => {
|
||||||
|
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<React.CSSProperties | undefined>(() => {
|
||||||
|
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
|
const shortAgentExecutionSnapshotId = agentExecutionSnapshotId
|
||||||
? agentExecutionSnapshotId.slice(0, 8)
|
? agentExecutionSnapshotId.slice(0, 8)
|
||||||
: "";
|
: "";
|
||||||
@ -1978,7 +2038,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLayerSplitComplete(
|
const addedLayerCount = handleLayerSplitComplete(
|
||||||
layers.map((layer) => ({
|
layers.map((layer) => ({
|
||||||
originalUrl: layer.originalUrl,
|
originalUrl: layer.originalUrl,
|
||||||
title: layer.title,
|
title: layer.title,
|
||||||
@ -1987,7 +2047,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
bounds: layer.bounds ?? null,
|
bounds: layer.bounds ?? null,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
toast.success(`图层拆分完成,已新增 ${layers.length} 个图层结果`);
|
toast.success(`图层拆分完成,已新增 ${addedLayerCount} 个图层结果`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("图层拆分失败:", error);
|
console.error("图层拆分失败:", error);
|
||||||
toast.error("图层拆分失败");
|
toast.error("图层拆分失败");
|
||||||
@ -2208,12 +2268,20 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
throw new Error("没有可添加到画布的图层结果");
|
throw new Error("没有可添加到画布的图层结果");
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderedLayers = [...validLayers].sort((left, right) => {
|
const isFullFrameLayer = (bounds?: LayerSplitBounds | null) => {
|
||||||
const leftArea = (left.bounds?.width || node.size.width) * (left.bounds?.height || node.size.height);
|
if (!bounds) return false;
|
||||||
const rightArea =
|
return (
|
||||||
(right.bounds?.width || node.size.width) * (right.bounds?.height || node.size.height);
|
bounds.coverage >= 0.92 &&
|
||||||
return rightArea - leftArea;
|
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) => {
|
orderedLayers.forEach((layer, index) => {
|
||||||
const layerTitle = `${displayTitle}-${layer.title || `图层${layer.layerIndex || index + 1}`}`;
|
const layerTitle = `${displayTitle}-${layer.title || `图层${layer.layerIndex || index + 1}`}`;
|
||||||
@ -2263,6 +2331,8 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
connections: [node.id],
|
connections: [node.id],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return orderedLayers.length;
|
||||||
},
|
},
|
||||||
[addNode, data.src, displayTitle, node.id, node.position.x, node.position.y, node.size.height, node.size.width]
|
[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}
|
src={displayImageSrc}
|
||||||
alt={displayTitle}
|
alt={displayTitle}
|
||||||
className={cn(
|
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"
|
boxCutoutMode ? "cursor-crosshair" : isThisNodePicking ? "cursor-crosshair" : "cursor-pointer"
|
||||||
)}
|
)}
|
||||||
|
style={layerSplitCropStyle}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user