diff --git a/app/api/canvas/local-edit/route.ts b/app/api/canvas/local-edit/route.ts
index 291de03ca..7bbef034c 100644
--- a/app/api/canvas/local-edit/route.ts
+++ b/app/api/canvas/local-edit/route.ts
@@ -292,7 +292,15 @@ function scaleRectAroundCenter(
rect: NormalizedLocalEditRegion["rect"],
scalePercent: ScaleAdjustPercent
) {
- const scale = Math.max(0.1, 1 + scalePercent / 100);
+ const requestedScale = Math.max(0.1, 1 + scalePercent / 100);
+ const maxUniformScale = Math.max(
+ 0.1,
+ Math.min(
+ rect.width > 0 ? 1 / rect.width : requestedScale,
+ rect.height > 0 ? 1 / rect.height : requestedScale
+ )
+ );
+ const scale = Math.min(requestedScale, maxUniformScale);
const targetWidth = Math.min(1, rect.width * scale);
const targetHeight = Math.min(1, rect.height * scale);
const centerX = rect.x + rect.width / 2;
@@ -512,7 +520,7 @@ function buildScaleAdjustAnnotationSvg(params: {
${region.index}
目标:${escapeXml(actionLabel)},主体约 ${escapeXml(targetLabel)}
- 让主体轮廓接近虚线框,不要保持原红框大小
+ 按主体原始宽高比等比变化,禁止拉伸变形
`;
})
.join("");
@@ -594,13 +602,13 @@ function buildScaleAdjustPrompt(
const bottom = pixel.top + pixel.height;
const centerX = pixel.left + Math.round(pixel.width / 2);
const centerY = pixel.top + Math.round(pixel.height / 2);
- return `区域 ${region.index}(原始红色实线框:x=${left}%,y=${top}%,w=${width}%,h=${height}%;目标虚线框:x=${targetLeft}%,y=${targetTop}%,w=${targetWidth}%,h=${targetHeight}%;像素范围 left=${pixel.left}, top=${pixel.top}, right=${right}, bottom=${bottom}, center=(${centerX}, ${centerY})):红色实线框只是“寻找目标主体”的范围,不是要缩放或贴回的矩形补丁。请识别此范围内最主要的商品/物体主体,以主体自身轮廓和视觉中心为准,执行整体等比${actionLabel},目标主体最终外接轮廓必须接近第 1 张图中的青绿色/绿色虚线目标框,视觉尺寸约为原主体的 ${targetLabel}。如果框中只覆盖到主体的一部分,请根据连接关系推断该主体的完整可见结构,只调整该主体,不调整矩形框内背景。`;
+ return `区域 ${region.index}(原始红色实线框:x=${left}%,y=${top}%,w=${width}%,h=${height}%;目标虚线框:x=${targetLeft}%,y=${targetTop}%,w=${targetWidth}%,h=${targetHeight}%;像素范围 left=${pixel.left}, top=${pixel.top}, right=${right}, bottom=${bottom}, center=(${centerX}, ${centerY})):红色实线框只是“寻找目标主体”的范围,不是要缩放或贴回的矩形补丁;目标虚线框只是表达目标尺寸范围,不是要求主体填满矩形或变成矩形比例。请识别此范围内最主要的商品/物体主体,以主体自身轮廓、原始宽高比和视觉中心为准,执行整体等比${actionLabel},宽度与高度必须使用同一个缩放倍率,目标主体视觉尺寸约为原主体的 ${targetLabel}。严禁宽度变窄但高度变高,严禁横向挤压、纵向拉伸或改变主体透视形体。如果框中只覆盖到主体的一部分,请根据连接关系推断该主体的完整可见结构,只调整该主体,不调整矩形框内背景。`;
})
.join("\n");
return [
"输入图片1是定位参考图:它只用于告诉你用户框选的目标主体位置。输入图片1里的彩色框、编号、标签、半透明遮罩、文字和 UI 只是定位标记,绝不能出现在最终画面中。",
- "输入图片1中的红色实线框表示目标主体当前大致位置;青绿色或绿色虚线框表示比例调整后的目标视觉轮廓。最终画面不得出现这些框线,但目标主体大小必须接近虚线框所表达的新尺寸。",
+ "输入图片1中的红色实线框表示目标主体当前大致位置;青绿色或绿色虚线框只表示比例调整后的安全尺寸范围。最终画面不得出现这些框线,主体也不需要填满虚线矩形;必须保持主体自身原始宽高比和透视形体。",
"输入图片2是原始干净图,也是最终输出必须继承的唯一画面基底。如果系统或模型只能读取一张参考图,则必须使用输入图片1中的框选位置定位目标,同时主动去除所有定位标记并恢复干净画面。",
"系统已根据用户框选区域计算出下方每个区域的原图坐标;框选区域只是定位目标主体的搜索范围,不是最终画面的裁切块、贴片或蒙版边界。",
"最终结果必须保持原始干净图的画幅比例、构图边界、镜头透视、光线、色温、材质质感和清晰度。",
@@ -610,14 +618,15 @@ function buildScaleAdjustPrompt(
regionLines,
"",
"关键要求:",
- `1. 只调整目标主体物体本身的整体视觉比例,必须等比缩放到约 ${targetLabel},不得保留原尺寸;请把主体轮廓宽度和高度都调整到约 ${targetLabel},并尽量贴近第 1 张图的虚线目标框,而不是仅改变亮度、清晰度、颜色或画面裁切。`,
+ `1. 只调整目标主体物体本身的整体视觉比例,必须等比缩放到约 ${targetLabel},不得保留原尺寸;主体轮廓宽度和高度必须使用同一个倍率同步变化,宽高比必须与原主体一致,不能为了贴合虚线框而拉宽、压窄、拉高或压扁。`,
"2. 严禁把整个矩形框选区域当成图片块整体缩放、平移、复制、裁切或贴回;最终画面不得出现矩形拼接边缘、色差硬边、补丁感、裁切线或局部贴片痕迹。",
`3. 缩小时,目标主体应围绕原位置视觉中心自然变小到约 ${targetLabel};主体缩小后,原来被遮挡的背景、桌面、墙面、阴影、纹理和透视必须被重新生成并无缝补全,选区内应出现明显新增留白或背景。`,
- `4. 放大时,目标主体应围绕原位置视觉中心自然变大到约 ${targetLabel};主体边缘应越过原始红框并接近虚线目标框,边缘与遮挡关系要合理,不能变形,也不能产生突兀裁切。`,
- "5. 框选区域内非目标主体的背景、墙面、桌面、植物、文字和其它物体应保持原场景逻辑,不要跟着主体一起缩放。",
- "6. 未受比例调整影响的其它画面区域必须尽量保持原图不变,不允许整体重绘、改色、补光、移动物体或改变背景细节。",
- "7. 最终输出必须是一张完整、干净、无缝的照片,不得出现任何选区框、说明文字、对比线、比例数字或 UI 元素。",
- "8. 输出前自检:如果目标主体大小与原图相比没有明显变化,必须重新生成;禁止返回看起来和原图相同的结果。",
+ `4. 放大时,目标主体应围绕原位置视觉中心自然变大到约 ${targetLabel};宽度和高度都要同步变大,不能出现“高度变高但宽度变窄”的非等比变形;主体边缘与遮挡关系要合理,不能产生突兀裁切。`,
+ "5. 如果主体靠近画布边界导致目标尺寸空间不足,请用能完整放入画面的最大统一倍率整体缩放;绝不能为了塞进画面而只压缩宽度或只拉伸高度。",
+ "6. 框选区域内非目标主体的背景、墙面、桌面、植物、文字和其它物体应保持原场景逻辑,不要跟着主体一起缩放。",
+ "7. 未受比例调整影响的其它画面区域必须尽量保持原图不变,不允许整体重绘、改色、补光、移动物体或改变背景细节。",
+ "8. 最终输出必须是一张完整、干净、无缝的照片,不得出现任何选区框、说明文字、对比线、比例数字或 UI 元素。",
+ "9. 输出前自检:如果目标主体大小与原图相比没有明显变化,或者主体宽高比发生非等比变形,必须重新生成;禁止返回看起来和原图相同或被拉伸的结果。",
].join("\n");
}
@@ -775,7 +784,7 @@ export async function POST(request: NextRequest) {
: undefined,
negativePrompt:
isScaleAdjustOperation
- ? "彩色框、蓝色边框、绿色边框、红色边框、橙色边框、矩形框、编号圆点、提示词气泡、标注文字、UI 控件、半透明色块、选区边框、白边、画幅压扁、画面拉伸、矩形贴片、硬边、拼接线、补丁感、局部裁切、色差边缘、背景断裂、主体未缩放、整块框选区域缩放"
+ ? "彩色框、蓝色边框、绿色边框、红色边框、橙色边框、矩形框、编号圆点、提示词气泡、标注文字、UI 控件、半透明色块、选区边框、白边、画幅压扁、画面拉伸、主体拉伸、主体压扁、主体变窄、主体变高但变窄、横向挤压、纵向拉伸、透视变形、矩形贴片、硬边、拼接线、补丁感、局部裁切、色差边缘、背景断裂、主体未缩放、整块框选区域缩放"
: "彩色框、蓝色边框、绿色边框、红色边框、橙色边框、矩形框、编号圆点、提示词气泡、标注文字、UI 控件、半透明色块、选区边框、白边、画幅压扁、画面拉伸、主体截断、动物截断、缺头、缺尾巴、缺四肢、贴边裁切、矩形贴片、硬边、拼接线、补丁感、色差边缘、背景错位、地面纹理断裂、椅脚错位",
prompt,
promptExtend: false,
diff --git a/components/agents/AgentGenerationWorkspaceDarkV2.tsx b/components/agents/AgentGenerationWorkspaceDarkV2.tsx
index 2a77c0146..c765b920e 100644
--- a/components/agents/AgentGenerationWorkspaceDarkV2.tsx
+++ b/components/agents/AgentGenerationWorkspaceDarkV2.tsx
@@ -859,6 +859,7 @@ function getDetailScaleAdjustRegionPrompt(scalePercent: DetailResultScalePercent
const scaleVerb = scalePercent > 0 ? "放大" : "缩小";
return [
`对框选范围内的主物体执行等比${actionLabel},调整后主体视觉尺寸必须约为原来的 ${targetLabel},需要肉眼明显看出已经${scaleVerb}。`,
+ "必须锁定主体自身原始宽高比和透视形体,宽度与高度用同一个倍率同步变化;严禁宽度变窄但高度变高、严禁横向挤压或纵向拉伸。",
"只改变目标主体的尺寸,不改变主体外观、颜色、材质、图案和文字内容;不要把整个框选矩形当成图片块缩放。",
scalePercent < 0
? "主体缩小后露出的背景、桌面、墙面、阴影和遮挡关系必须自然补全。"
diff --git a/lib/canvas/generate-image.ts b/lib/canvas/generate-image.ts
index 625863be5..69cf75b96 100644
--- a/lib/canvas/generate-image.ts
+++ b/lib/canvas/generate-image.ts
@@ -266,7 +266,15 @@ function scaleRegionAroundCenter(
region: CanvasLocalEditCompositeRegion,
scalePercent: number
): CanvasLocalEditCompositeRegion {
- const factor = Math.max(0.05, 1 + scalePercent / 100);
+ const requestedFactor = Math.max(0.05, 1 + scalePercent / 100);
+ const maxUniformFactor = Math.max(
+ 0.05,
+ Math.min(
+ region.width > 0 ? 1 / region.width : requestedFactor,
+ region.height > 0 ? 1 / region.height : requestedFactor
+ )
+ );
+ const factor = Math.min(requestedFactor, maxUniformFactor);
let width = clampRatio(region.width * factor);
let height = clampRatio(region.height * factor);
const centerX = region.x + region.width / 2;