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.
This commit is contained in:
parent
885cab9fca
commit
b4b6e1a738
@ -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));
|
||||
}}
|
||||
>
|
||||
<div className="pointer-events-none absolute inset-0" style={{backgroundImage:"radial-gradient(circle,rgba(255,255,255,0.14) 0.7px,transparent 0.7px)",backgroundPosition:"0.5px 0.5px",backgroundSize:"16px 16px"}} />
|
||||
@ -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<string,any> || {}),assets:((node.draftValues?.smartEditDraft as Record<string,any>)?.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<string,any>);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user