fix(canvas): 图片生成尺寸 - 显式比例不再被参考图节点尺寸覆盖
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { NODE_DEFAULT_SIZE } from "@/constant/canvas";
|
||||
import { fitNodeSize, VIDEO_NODE_MAX_SIZE } from "@/lib/canvas/canvas-node-size";
|
||||
import { fitNodeSize, nodeSizeFromRatio, VIDEO_NODE_MAX_SIZE } from "@/lib/canvas/canvas-node-size";
|
||||
import { compositeEmotionImage } from "@/lib/canvas/canvas-emotion";
|
||||
import { storeGeneratedAudio } from "@/services/api/audio";
|
||||
import { storeGeneratedVideo } from "@/services/api/video";
|
||||
@@ -82,16 +82,22 @@ export async function buildGenerationTaskNodeResult(node: CanvasNodeData, task:
|
||||
? { url: await resolveImageUrl(image.storageKey, image.dataUrl), storageKey: image.storageKey, width: image.width || 1024, height: image.height || 1024, bytes: image.bytes || 0, mimeType: image.mimeType || "image/png" }
|
||||
: await uploadImage(resultDataUrl);
|
||||
const imageConfig = NODE_DEFAULT_SIZE[CanvasNodeType.Image];
|
||||
const imageSize = node.metadata?.generationType === "edit"
|
||||
const requestedImageSize = nodeSizeFromRatio(node.metadata?.size || "auto", imageConfig.width, imageConfig.height);
|
||||
const imageSizeBounds = requestedImageSize || { width: node.width || imageConfig.width, height: node.height || imageConfig.height };
|
||||
const hasReportedImageSize = Boolean(image.width && image.width > 0 && image.height && image.height > 0);
|
||||
const resultWidth = image.storageKey && !hasReportedImageSize && requestedImageSize ? requestedImageSize.width : uploaded.width;
|
||||
const resultHeight = image.storageKey && !hasReportedImageSize && requestedImageSize ? requestedImageSize.height : uploaded.height;
|
||||
const normalizedImage = resultWidth === uploaded.width && resultHeight === uploaded.height ? uploaded : { ...uploaded, width: resultWidth, height: resultHeight };
|
||||
const imageSize = node.metadata?.generationType === "edit" && !requestedImageSize
|
||||
? { width: node.width || imageConfig.width, height: node.height || imageConfig.height }
|
||||
: fitNodeSize(uploaded.width, uploaded.height, node.width || imageConfig.width, node.height || imageConfig.height);
|
||||
: fitNodeSize(resultWidth, resultHeight, imageSizeBounds.width, imageSizeBounds.height);
|
||||
return {
|
||||
...node,
|
||||
type: CanvasNodeType.Image,
|
||||
width: imageSize.width,
|
||||
height: imageSize.height,
|
||||
position: { x: node.position.x + node.width / 2 - imageSize.width / 2, y: node.position.y + node.height / 2 - imageSize.height / 2 },
|
||||
metadata: { ...node.metadata, ...imageMetadata(uploaded), prompt, ...completedTaskMetadata(task), errorDetails: undefined },
|
||||
metadata: { ...node.metadata, ...imageMetadata(normalizedImage), prompt, ...completedTaskMetadata(task), errorDetails: undefined },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -30,10 +30,27 @@ export function nodeSizeFromRatio(size: string, baseWidth: number, baseHeight: n
|
||||
|
||||
export function ensureMediaNodeMinimumSize(node: CanvasNodeData) {
|
||||
if (node.type !== CanvasNodeType.Image && node.type !== CanvasNodeType.Video) return node;
|
||||
if (node.width >= MEDIA_NODE_MIN_SIZE.width && node.height >= MEDIA_NODE_MIN_SIZE.height) return node;
|
||||
const scale = Math.max(1, MEDIA_NODE_MIN_SIZE.width / Math.max(1, node.width), MEDIA_NODE_MIN_SIZE.height / Math.max(1, node.height));
|
||||
const width = node.width * scale;
|
||||
const height = node.height * scale;
|
||||
let width = node.width;
|
||||
let height = node.height;
|
||||
const naturalWidth = node.metadata?.naturalWidth || 0;
|
||||
const naturalHeight = node.metadata?.naturalHeight || 0;
|
||||
const requestedSize = node.type === CanvasNodeType.Image && node.metadata?.generationType === "edit"
|
||||
? nodeSizeFromRatio(node.metadata.size || "auto", node.width, node.height)
|
||||
: null;
|
||||
const naturalRatio = naturalWidth / Math.max(1, naturalHeight);
|
||||
const nodeRatio = node.width / Math.max(1, node.height);
|
||||
// 修复旧版图生图无条件继承参考节点尺寸造成的比例错误,不覆盖自由拉伸或锁定布局。
|
||||
if (requestedSize && naturalWidth > 0 && naturalHeight > 0 && !node.metadata?.freeResize && !node.metadata?.locked && Math.abs(naturalRatio - nodeRatio) > 0.01) {
|
||||
const alignedSize = fitNodeSize(naturalWidth, naturalHeight, requestedSize.width, requestedSize.height);
|
||||
width = alignedSize.width;
|
||||
height = alignedSize.height;
|
||||
}
|
||||
if (width < MEDIA_NODE_MIN_SIZE.width || height < MEDIA_NODE_MIN_SIZE.height) {
|
||||
const scale = Math.max(1, MEDIA_NODE_MIN_SIZE.width / Math.max(1, width), MEDIA_NODE_MIN_SIZE.height / Math.max(1, height));
|
||||
width *= scale;
|
||||
height *= scale;
|
||||
}
|
||||
if (width === node.width && height === node.height) return node;
|
||||
return {
|
||||
...node,
|
||||
position: {
|
||||
|
||||
@@ -48,10 +48,11 @@ export async function executeImageGeneration({
|
||||
const parentConfig = NODE_DEFAULT_SIZE[isConfigNode ? CanvasNodeType.Config : isImageNode ? CanvasNodeType.Image : CanvasNodeType.Text];
|
||||
const imageDefaults = NODE_DEFAULT_SIZE[CanvasNodeType.Image];
|
||||
// 生成中占位框按设置比例显示,避免 16:9 任务显示成默认 340x240。
|
||||
const imageConfig = nodeSizeFromRatio(generationConfig.size || "auto", imageDefaults.width, imageDefaults.height) || imageDefaults;
|
||||
// 单图图生图沿用来源节点的可视尺寸,避免竖图回落到默认横向框后被压成窄卡片。
|
||||
const requestedImageSize = nodeSizeFromRatio(generationConfig.size || "auto", imageDefaults.width, imageDefaults.height);
|
||||
const imageConfig = requestedImageSize || imageDefaults;
|
||||
// auto 图生图沿用来源节点尺寸;用户明确选择比例时必须以目标比例创建节点。
|
||||
const referenceNode = referenceImages.length === 1 ? canvasNodes.find((node) => node.id === referenceImages[0].id && node.type === CanvasNodeType.Image) : undefined;
|
||||
const imageSizeSource = isImageNode && sourceNode?.metadata?.content ? sourceNode : referenceNode;
|
||||
const imageSizeSource = requestedImageSize ? undefined : (isImageNode && sourceNode?.metadata?.content ? sourceNode : referenceNode);
|
||||
const outputNodeSize = imageSizeSource ? { width: imageSizeSource.width, height: imageSizeSource.height } : imageConfig;
|
||||
const parentPosition = sourceNode?.position || { x: 0, y: 0 };
|
||||
const parentWidth = sourceNode?.width || parentConfig.width;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { buildNodeGenerationContext, hydrateNodeGenerationContext } from "@/comp
|
||||
import type { CanvasNodeGenerationMode } from "@/components/canvas/canvas-node-prompt-panel";
|
||||
import { NODE_DEFAULT_SIZE } from "@/constant/canvas";
|
||||
import { audioMetadata, imageMetadata, videoMetadata } from "@/lib/canvas/canvas-generation-task-sync";
|
||||
import { fitNodeSize, VIDEO_NODE_MAX_SIZE } from "@/lib/canvas/canvas-node-size";
|
||||
import { fitNodeSize, nodeSizeFromRatio, VIDEO_NODE_MAX_SIZE } from "@/lib/canvas/canvas-node-size";
|
||||
import { buildEmotionImageArtifacts, compositeEmotionImage, emotionGenerationSize } from "@/lib/canvas/canvas-emotion";
|
||||
import {
|
||||
buildAudioGenerationMetadata,
|
||||
@@ -192,13 +192,14 @@ export function useCanvasGenerationRetry({ projectId, domainProjectId, activated
|
||||
if (!image?.dataUrl) throw new Error("后端任务没有返回图片");
|
||||
const uploadedImage = await uploadImage(image.dataUrl);
|
||||
const imageConfig = NODE_DEFAULT_SIZE[CanvasNodeType.Image];
|
||||
const imageSize = useReferenceImages
|
||||
const requestedImageSize = nodeSizeFromRatio(generationConfig.size || "auto", imageConfig.width, imageConfig.height);
|
||||
const imageSize = useReferenceImages && !requestedImageSize
|
||||
? { width: node.width, height: node.height }
|
||||
: fitNodeSize(uploadedImage.width, uploadedImage.height, imageConfig.width, imageConfig.height);
|
||||
: fitNodeSize(uploadedImage.width, uploadedImage.height, requestedImageSize?.width || imageConfig.width, requestedImageSize?.height || imageConfig.height);
|
||||
const generationMetadata = savedImageMetadata?.generationType
|
||||
? { generationType: savedImageMetadata.generationType, model: generationConfig.model, size: generationConfig.size, quality: generationConfig.quality, transparentBackground: generationConfig.transparentBackground, count: savedImageMetadata.count || 1, references: savedImageMetadata.references }
|
||||
: buildImageGenerationMetadata(useReferenceImages ? "edit" : "generation", generationConfig, 1, retryImages);
|
||||
setNodes((current) => current.map((item) => (item.id === node.id ? { ...item, type: CanvasNodeType.Image, width: imageSize.width, height: imageSize.height, metadata: { ...item.metadata, ...imageMetadata(uploadedImage), prompt, ...generationMetadata } } : item)));
|
||||
setNodes((current) => current.map((item) => (item.id === node.id ? { ...item, type: CanvasNodeType.Image, position: { x: item.position.x + item.width / 2 - imageSize.width / 2, y: item.position.y + item.height / 2 - imageSize.height / 2 }, width: imageSize.width, height: imageSize.height, metadata: { ...item.metadata, ...imageMetadata(uploadedImage), prompt, ...generationMetadata } } : item)));
|
||||
} catch (error) {
|
||||
if (isGenerationCanceled(error)) return;
|
||||
const failure = generationFailureMetadata(error, retryPromptSource);
|
||||
|
||||
Reference in New Issue
Block a user