fix: hint old footprint for scale adjust

This commit is contained in:
134 2026-07-02 15:38:29 +08:00
parent cd19fe0b8d
commit ba1bb165c3

View File

@ -100,6 +100,7 @@ const MAX_REFERENCE_IMAGES_PER_REGION = 3;
const MAX_SCALE_ADJUST_CROP_REFERENCES = 4;
const SCALE_ADJUST_CROP_PADDING_RATIO = 0.18;
const SCALE_ADJUST_CROP_MAX_EDGE = 1536;
const SCALE_ADJUST_LAYOUT_GUIDE_WASH_COLOR = "#f5f7fb";
const MIN_REGION_EDGE_PX = 24;
const SCALE_ADJUST_OPERATION = "scale-adjust";
type LocalEditOperation = "local-edit" | typeof SCALE_ADJUST_OPERATION;
@ -855,6 +856,35 @@ function buildScaleAdjustPatchMaskSvg(params: { height: number; width: number })
);
}
function buildScaleAdjustSourceFootprintWashSvg(params: {
boxes: Array<{ left: number; top: number; width: number; height: number }>;
height: number;
width: number;
}) {
const { boxes, height, width } = params;
const blur = Math.max(4, Math.min(22, Math.round(Math.min(width, height) * 0.016)));
const rects = boxes
.map((box) => {
const left = Math.max(0, box.left - blur);
const top = Math.max(0, box.top - blur);
const right = Math.min(width, box.left + box.width + blur);
const bottom = Math.min(height, box.top + box.height + blur);
return `<rect x="${left}" y="${top}" width="${Math.max(1, right - left)}" height="${Math.max(1, bottom - top)}" fill="${SCALE_ADJUST_LAYOUT_GUIDE_WASH_COLOR}" fill-opacity="0.34" filter="url(#soft-wash)" />`;
})
.join("");
return Buffer.from(
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
<defs>
<filter id="soft-wash" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="${blur}" />
</filter>
</defs>
${rects}
</svg>`
);
}
async function buildScaleAdjustVisualLayoutGuideBuffer(params: {
regions: NormalizedLocalEditRegion[];
scalePercent?: ScaleAdjustPercent | null;
@ -864,6 +894,12 @@ async function buildScaleAdjustVisualLayoutGuideBuffer(params: {
}) {
const { regions, scalePercent, sourceBuffer, sourceHeight, sourceWidth } =
params;
const sourceFootprintBoxes: Array<{
left: number;
top: number;
width: number;
height: number;
}> = [];
const composites: Array<{ input: Buffer; left: number; top: number }> = [];
for (const region of regions) {
@ -873,6 +909,7 @@ async function buildScaleAdjustVisualLayoutGuideBuffer(params: {
scalePercent,
width: sourceWidth,
});
sourceFootprintBoxes.push(placement.sourceBox);
const resizedCropBuffer = await sharp(sourceBuffer, { failOn: "none" })
.extract(placement.sourceBox)
@ -901,8 +938,22 @@ async function buildScaleAdjustVisualLayoutGuideBuffer(params: {
});
}
const washComposite = sourceFootprintBoxes.length
? [
{
input: buildScaleAdjustSourceFootprintWashSvg({
boxes: sourceFootprintBoxes,
height: sourceHeight,
width: sourceWidth,
}),
left: 0,
top: 0,
},
]
: [];
return sharp(sourceBuffer, { failOn: "none" })
.composite(composites)
.composite([...washComposite, ...composites])
.png({ compressionLevel: 6 })
.toBuffer();
}
@ -1227,7 +1278,7 @@ function buildScaleAdjustPrompt(
"这是一项局部主体比例 inpaint/edit 任务,不是整图缩放、整图裁切或重新构图任务。",
sourceSizeLine,
"输入图片1是原始干净图,也是最终输出必须继承的唯一画面基底。除目标主体尺寸变化与必要背景补全外,输入图片1中的文字、Logo、装饰、排版、背景、光线、构图边界和所有未选区域必须保持不变。",
"输入图片2是一张与输入图片1尺寸和画幅完全相同的完整画面目标尺寸提示图,不是输出底图。它保留原图完整画幅作为稳定参考,并把源框里的同一目标主体粗略缩放后叠放到用户希望的最终位置和大小。请只读取目标主体的最终视觉占位和原画幅关系;输入图片2里仍可见的原尺寸主体/背景只用于锁定原画幅,不能表示最终还要保留原尺寸主体。不得学习、复刻或输出矩形裁切边缘、拼接痕迹或参考图粗糙画质。",
"输入图片2是一张与输入图片1尺寸和画幅完全相同的完整画面目标尺寸提示图,不是输出底图。它保留原图完整画幅作为稳定参考,只在源框旧主体占位内做轻微淡化来提示“旧主体需要被目标尺寸替换”,并把源框里的同一目标主体粗略缩放后叠放到用户希望的最终位置和大小。请只读取目标主体的最终视觉占位和原画幅关系;输入图片2里仍可见的原尺寸主体/背景只用于锁定原画幅,不能表示最终还要保留原尺寸主体。不得学习、复刻或输出淡化痕迹、矩形裁切边缘、拼接痕迹或参考图粗糙画质。",
cropLines
? `输入图片3及之后是框选区域的干净局部裁切参考图,作用是帮助你确认“要缩放的是哪个主体”。这些裁切图不是输出底图,不得被整体放大、贴回或覆盖到原图上。\n${cropLines}`
: "",
@ -1316,7 +1367,7 @@ function buildScaleAdjustCoordinatePrompt(
"中文硬约束:只调整 OLD 框中的主要家具主体。TARGET 框是最终占位;如果是缩小,OLD 框里超出 TARGET 的旧家具必须消失并补成自然背景;如果是放大,同一件家具必须明显变大。禁止整图缩放、整图裁切、整图重绘。",
"一次成功硬约束:不要保守返回原尺寸。目标家具的可见轮廓必须明显从 OLD 原始占位变到 TARGET 占位;如果最终看起来和原图一样,就是失败。",
`Return one seamless realistic photo with the same frame and pixel size as input image 1: ${sourceSize.width} x ${sourceSize.height} px.`,
"Input image 2 is a full-frame target-size placement hint with the same canvas as input image 1, not output content and not a photo layer. It keeps the original room frame visible as the locked reference and overlays the same furniture at the required TARGET footprint. Read only the final furniture footprint, center, and locked frame relationship from input image 2. If the original-size furniture is still visible behind the rough hint, ignore that old footprint in the final; the final must contain only the TARGET-size furniture. Never copy rectangular crop edges, rough patch quality, seams, or guide artifacts into the output.",
"Input image 2 is a full-frame target-size placement hint with the same canvas as input image 1, not output content and not a photo layer. It keeps the original room frame visible as the locked reference, softly mutes only the OLD source footprint to indicate replacement, and overlays the same furniture at the required TARGET footprint. Read only the final furniture footprint, center, and locked frame relationship from input image 2. If the original-size furniture is still visible behind the rough hint, ignore that old footprint in the final; the final must contain only the TARGET-size furniture. Never copy muted wash, rectangular crop edges, rough patch quality, seams, or guide artifacts into the output.",
cropLines ? `${cropLines}` : "",
"",
"Task:",
@ -1333,7 +1384,7 @@ function buildScaleAdjustCoordinatePrompt(
regionLines,
"",
"Success check: compare the final furniture footprint with input image 1 and the full-frame target-size placement hint in input image 2. The selected furniture size must clearly change from the original and align much closer to TARGET than OLD, while the rest of the photo stays stable.",
"Do not paste rectangular image areas. Do not output layout-reference overlays, coordinate-map lines, labels, boxes, seams, hard patch edges, haze, color mismatch, full-canvas zoom, crop, pan, or redesign.",
"Do not paste rectangular image areas. Do not output layout-reference overlays, muted wash, coordinate-map lines, labels, boxes, seams, hard patch edges, haze, color mismatch, full-canvas zoom, crop, pan, or redesign.",
"Return only the clean edited photo.",
].join("\n");
}
@ -1347,7 +1398,7 @@ function buildScaleAdjustCoordinateSystemPrompt() {
"Prefer a clear visible size change over a conservative unchanged result; unchanged furniture is a failed edit.",
"When shrinking, remove the old full-size furniture footprint outside the target footprint and heal it as natural background. Large furniture is editable and must not remain locked at original size.",
"Do not paste, composite, clone, crop, resize, or copy rectangular image patches from any input.",
"Never output guide artifacts from the layout reference: no overlay patch, rough crop rectangle, lines, labels, blocks, seams, rectangular borders, arrows, masks, UI marks, or annotations.",
"Never output guide artifacts from the layout reference: no muted wash, overlay patch, rough crop rectangle, lines, labels, blocks, seams, rectangular borders, arrows, masks, UI marks, or annotations.",
"Keep the camera frame, lighting, wall, floor, rug, windows, background furniture, text, logos, and all non-target regions stable.",
].join("\n");
}
@ -1571,7 +1622,7 @@ export async function POST(request: NextRequest) {
)
: buildLocalEditPrompt(regions);
const scaleAdjustCoordinateNegativePrompt =
"visible annotation, guide overlay, translucent overlay, target placement overlay, text label, arrows, boxes, rectangle border, UI marks, pasted patch, rough layout patch, hard crop edge, seam, hard rectangular edge, color mismatch, different-resolution area, whole image zoom, whole image crop, whole image pan, whole image redesign, unchanged target furniture, stretched furniture, squashed furniture, duplicate furniture, second copy, extra legs, extra rails, side panels, shelves, cabinets, lattice panels";
"visible annotation, guide overlay, translucent overlay, target placement overlay, muted wash, text label, arrows, boxes, rectangle border, UI marks, pasted patch, rough layout patch, hard crop edge, seam, hard rectangular edge, color mismatch, different-resolution area, whole image zoom, whole image crop, whole image pan, whole image redesign, unchanged target furniture, stretched furniture, squashed furniture, duplicate furniture, second copy, extra legs, extra rails, side panels, shelves, cabinets, lattice panels";
const manualRegionReferenceImageUrls = regions.flatMap((region) =>
region.referenceImages.map((reference) => reference.url)
);
@ -1669,8 +1720,8 @@ export async function POST(request: NextRequest) {
.length,
scaleAdjustPromptStrategy: isScaleAdjustOperation
? useScaleAdjustCoordinateGuide
? "same-name-locked-source-target-size-placement-hint-v34-one-shot"
: "locked-plate-target-size-placement-hint-v11"
? "same-name-locked-source-tight-muted-target-placement-hint-v35-one-shot"
: "locked-plate-tight-muted-target-placement-hint-v12"
: undefined,
scaleAdjustPostCompositeApplied: false,
scaleAdjustUsesFullFrameOutput: isScaleAdjustOperation,
@ -1695,7 +1746,7 @@ export async function POST(request: NextRequest) {
isScaleAdjustOperation
? useScaleAdjustCoordinateGuide
? scaleAdjustCoordinateNegativePrompt
: "彩色框、蓝色边框、绿色边框、红色边框、橙色边框、矩形框、箭头、连线、角点、编号圆点、提示词气泡、标注文字、UI 控件、半透明色块、蓝色色块、色条、灰底坐标图、目标占位叠图、选区边框、白边、画幅压扁、画面拉伸、整张图缩放、全图放大、全图裁切、全图平移、整体重绘、删除文字、清理文字、改写文字、文字模糊、Logo消失、主体拉伸、主体压扁、主体变窄、主体变高但变窄、横向挤压、纵向拉伸、透视变形、矩形贴片、硬边、拼接线、补丁感、局部裁切、色差边缘、背景断裂、背景错位、窗框错位、文字错位、地毯纹理错位、非主体背景重绘、主体未缩放、整块框选区域缩放"
: "彩色框、蓝色边框、绿色边框、红色边框、橙色边框、矩形框、箭头、连线、角点、编号圆点、提示词气泡、标注文字、UI 控件、半透明色块、蓝色色块、色条、灰底坐标图、目标占位叠图、淡化痕迹、选区边框、白边、画幅压扁、画面拉伸、整张图缩放、全图放大、全图裁切、全图平移、整体重绘、删除文字、清理文字、改写文字、文字模糊、Logo消失、主体拉伸、主体压扁、主体变窄、主体变高但变窄、横向挤压、纵向拉伸、透视变形、矩形贴片、硬边、拼接线、补丁感、局部裁切、色差边缘、背景断裂、背景错位、窗框错位、文字错位、地毯纹理错位、非主体背景重绘、主体未缩放、整块框选区域缩放"
: "彩色框、蓝色边框、绿色边框、红色边框、橙色边框、矩形框、编号圆点、提示词气泡、标注文字、UI 控件、半透明色块、选区边框、白边、画幅压扁、画面拉伸、主体截断、动物截断、缺头、缺尾巴、缺四肢、贴边裁切、矩形贴片、硬边、拼接线、补丁感、色差边缘、背景错位、地面纹理断裂、椅脚错位",
prompt,
promptExtend: false,