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;