From b4b6e1a7380941bb8e45e091e2fa2cbcf2d74233 Mon Sep 17 00:00:00 2001 From: 134 Date: Fri, 18 Sep 2026 18:34:23 +0800 Subject: [PATCH] Size dropped image nodes to intrinsic aspect ratio Dropped images landed in fixed 420x280 nodes and rendered with object-cover, cropping portraits and wide photos. Read the image's natural size during ingestion and fit the node frame to the aspect ratio (landscape caps height, portrait caps width) so the full picture stays visible like the reference product. --- .../canvas/LiblibVideoCanvasReplica.tsx | 52 ++++++++++++++++--- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/components/canvas/LiblibVideoCanvasReplica.tsx b/components/canvas/LiblibVideoCanvasReplica.tsx index d8ab6aa45..5adbd86ec 100644 --- a/components/canvas/LiblibVideoCanvasReplica.tsx +++ b/components/canvas/LiblibVideoCanvasReplica.tsx @@ -5375,14 +5375,54 @@ export function LiblibVideoCanvasReplica({ }; }, []); + const readImageIntrinsicSize = useCallback(async (file: File) => { + if (!file.type.startsWith("image/")) return null; + try { + const bitmap = await createImageBitmap(file); + const size = { width: bitmap.width, height: bitmap.height }; + bitmap.close(); + return size; + } catch { + try { + return await new Promise<{ width: number; height: number }>((resolve, reject) => { + const url = URL.createObjectURL(file); + const image = new Image(); + image.onload = () => { + URL.revokeObjectURL(url); + resolve({ width: image.naturalWidth, height: image.naturalHeight }); + }; + image.onerror = () => { + URL.revokeObjectURL(url); + reject(new Error("image decode failed")); + }; + image.src = url; + }); + } catch { + return null; + } + } + }, []); + const addUploadedFilesAtPoint = useCallback( - (files: File[], center: { x: number; y: number }, connectTargetId?: string) => { + async (files: File[], center: { x: number; y: number }, connectTargetId?: string) => { if (files.length === 0) return; const { files: batch, errors } = prepareCanvasMediaFiles(files); if (errors.length) toast.error(errors.slice(0, 3).join("\n")); if (!batch.length) return; + const sizes = await Promise.all(batch.map(file => readImageIntrinsicSize(file))); const newNodes = batch.map((file, index) => { const created = createUploadedMediaNode(file, "", 0, 0, nodes.length + index + 1); + const size = sizes[index]; + if (size && size.width > 0 && size.height > 0) { + const aspect = size.width / size.height; + if (aspect >= 1) { + created.width = 420; + created.height = Math.min(560, Math.max(180, Math.round(420 / aspect))); + } else { + created.height = 560; + created.width = Math.min(420, Math.max(220, Math.round(560 * aspect))); + } + } return { ...created, x: Math.round(center.x - created.width / 2 + index * 450), y: Math.round(center.y - created.height / 2), draftValues: { ...created.draftValues, uploadStatus: "uploading" } }; @@ -5412,12 +5452,12 @@ export function LiblibVideoCanvasReplica({ } }); }, - [addNodes, nodes.length, uploadCanvasMediaFile] + [addNodes, nodes.length, readImageIntrinsicSize, uploadCanvasMediaFile] ); const addUploadedFiles = useCallback( (files: File[]) => { - addUploadedFilesAtPoint(files, toCanvasPoint(window.innerWidth / 2, window.innerHeight / 2)); + void addUploadedFilesAtPoint(files, toCanvasPoint(window.innerWidth / 2, window.innerHeight / 2)); }, [addUploadedFilesAtPoint, toCanvasPoint] ); @@ -5763,7 +5803,7 @@ export function LiblibVideoCanvasReplica({ const addCanvasCommandUploadedFiles = useCallback( (files: File[]) => { if (!canvasCommand) return; - addUploadedFilesAtPoint(files, { x: canvasCommand.canvasX, y: canvasCommand.canvasY }); + void addUploadedFilesAtPoint(files, { x: canvasCommand.canvasX, y: canvasCommand.canvasY }); }, [addUploadedFilesAtPoint, canvasCommand] ); @@ -10111,7 +10151,7 @@ export function LiblibVideoCanvasReplica({ if (isBusy) { toast.error("画布正在加载,请稍候再拖入素材"); return; } const files = Array.from(event.dataTransfer.files); if (!files.length) { toast.error("未能读取拖入的文件,请从桌面直接拖拽素材后松开"); return; } - addUploadedFilesAtPoint(files, toCanvasPoint(event.clientX, event.clientY)); + void addUploadedFilesAtPoint(files, toCanvasPoint(event.clientX, event.clientY)); }} >
@@ -10547,7 +10587,7 @@ export function LiblibVideoCanvasReplica({ setNodes(current => current.map(node => node.id === selectedNode.id ? {...node,draftValues:{...node.draftValues,smartEditDraft:{...(node.draftValues?.smartEditDraft as Record || {}),assets:((node.draftValues?.smartEditDraft as Record)?.assets || []).filter((asset:{id:string}) => asset.id !== source)}}} : node)); }} onReference={() => setLineConnectState({sourceId:null,targetId:selectedNode.id,cursorCanvasX:selectedNode.x-80,cursorCanvasY:selectedNode.y+selectedNode.height/2})} - onUpload={files => addUploadedFilesAtPoint(files,{x:selectedNode.x-600,y:selectedNode.y},selectedNode.id)} + onUpload={files => void addUploadedFilesAtPoint(files,{x:selectedNode.x-600,y:selectedNode.y},selectedNode.id)} onSubmit={async () => { assertConnectedInputsReady(selectedTargetSourceNodes); const draft = resolveSmartEditIntent((selectedNode.draftValues?.smartEditDraft || {}) as Record);