diff --git a/components/canvas/nodes/ImageNode.tsx b/components/canvas/nodes/ImageNode.tsx index fcd42fec1..fe92ca7be 100644 --- a/components/canvas/nodes/ImageNode.tsx +++ b/components/canvas/nodes/ImageNode.tsx @@ -2188,6 +2188,15 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) { title?: string; layerIndex: number; totalLayers: number; + bounds?: { + x: number; + y: number; + width: number; + height: number; + originalWidth: number; + originalHeight: number; + coverage: number; + } | null; }> ) => { const validLayers = layers.filter( @@ -2198,23 +2207,44 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) { throw new Error("没有可添加到画布的图层结果"); } - const maxColumns = 3; - const gap = 20; - const baseX = node.position.x + node.size.width + gap; - const baseY = node.position.y; + 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; + }); - validLayers.forEach((layer, index) => { - const column = index % maxColumns; - const row = Math.floor(index / maxColumns); + orderedLayers.forEach((layer, index) => { const layerTitle = `${displayTitle}-${layer.title || `图层${layer.layerIndex || index + 1}`}`; + const bounds = layer.bounds; + const hasUsableBounds = + bounds && + bounds.originalWidth > 0 && + bounds.originalHeight > 0 && + bounds.width > 0 && + bounds.height > 0; + const scaleX = hasUsableBounds ? node.size.width / bounds.originalWidth : 1; + const scaleY = hasUsableBounds ? node.size.height / bounds.originalHeight : 1; + const targetPosition = hasUsableBounds + ? { + x: node.position.x + bounds.x * scaleX, + y: node.position.y + bounds.y * scaleY, + } + : { + x: node.position.x, + y: node.position.y, + }; + const targetSize = hasUsableBounds + ? { + width: Math.max(1, bounds.width * scaleX), + height: Math.max(1, bounds.height * scaleY), + } + : { width: node.size.width, height: node.size.height }; addNode({ type: "image", - position: { - x: baseX + column * (node.size.width + gap), - y: baseY + row * (node.size.height + gap), - }, - size: { width: node.size.width, height: node.size.height }, + position: targetPosition, + size: targetSize, data: { src: layer.originalUrl, status: "completed", @@ -2224,6 +2254,9 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) { metadata: { layerIndex: layer.layerIndex, totalLayers: layer.totalLayers, + bounds: layer.bounds, + allowOverlap: true, + placementMode: "overlay", }, }, connections: [node.id], diff --git a/lib/ai/layer-split/service.ts b/lib/ai/layer-split/service.ts index dfb0d3a3e..8060b7244 100644 --- a/lib/ai/layer-split/service.ts +++ b/lib/ai/layer-split/service.ts @@ -60,6 +60,15 @@ export type LayerSplitResultItem = { title: string; layerIndex: number; totalLayers: number; + bounds?: { + x: number; + y: number; + width: number; + height: number; + originalWidth: number; + originalHeight: number; + coverage: number; + } | null; }; export type ExecuteLayerSplitJobResult = { @@ -438,6 +447,60 @@ async function createThumbnail(buffer: Buffer, mimeType: string): Promise maxX) maxX = x; + if (y > maxY) maxY = y; + } + } + + if (maxX < minX || maxY < minY || visiblePixels <= 0) { + return null; + } + + return { + x: minX, + y: minY, + width: maxX - minX + 1, + height: maxY - minY + 1, + originalWidth: width, + originalHeight: height, + coverage: visiblePixels / (width * height), + }; + } catch (error) { + console.warn("[LayerSplit] Failed to detect visible bounds:", error); + return null; + } +} + async function uploadBufferPair(buffer: Buffer, mimeType: string, prefix: string) { const ext = extFromMimeType(mimeType); const timestamp = Date.now(); @@ -571,6 +634,7 @@ export async function executeLayerSplitJob(params: LayerSplitParams): Promise; model?: string; provider?: string; diff --git a/store/canvas/index.ts b/store/canvas/index.ts index fee3ca931..44007df46 100644 --- a/store/canvas/index.ts +++ b/store/canvas/index.ts @@ -209,12 +209,19 @@ export const useCanvasStore = create()( // 节点操作 addNode: (nodeData) => { + const imageMetadata = + nodeData.type === 'image' && nodeData.data && typeof nodeData.data === 'object' + ? (((nodeData.data as any).metadata ?? {}) as Record) + : {}; + const shouldPreserveImagePosition = + imageMetadata.allowOverlap === true || imageMetadata.placementMode === 'overlay'; + const resolveImagePosition = ( basePosition: Position, size: Size, nodes: CanvasNode[] ): Position => { - if (nodeData.type !== 'image') return basePosition; + if (nodeData.type !== 'image' || shouldPreserveImagePosition) return basePosition; const padding = 20; const width = size?.width || 300; const height = size?.height || 300;