fix: guide scale adjust as localized edit
This commit is contained in:
parent
072fac14cc
commit
8519aa70e7
@ -600,13 +600,9 @@ function buildScaleAdjustAnnotationSvg(params: {
|
||||
}) {
|
||||
const { width, height, regions, scalePercent } = params;
|
||||
const minSide = Math.max(1, Math.min(width, height));
|
||||
const strokeWidth = Math.max(4, Math.round(minSide * 0.006));
|
||||
const fontSize = Math.max(22, Math.round(minSide * 0.024));
|
||||
const smallFontSize = Math.max(18, Math.round(fontSize * 0.78));
|
||||
const strokeWidth = Math.max(3, Math.round(minSide * 0.0048));
|
||||
const targetColor = scalePercent > 0 ? "#00d4ff" : "#00c853";
|
||||
const sourceColor = "#ff3b30";
|
||||
const actionLabel = getScaleAdjustLabel(scalePercent);
|
||||
const targetLabel = getScaleAdjustTargetLabel(scalePercent);
|
||||
const dash = Math.max(12, Math.round(strokeWidth * 2.8));
|
||||
const gap = Math.max(8, Math.round(strokeWidth * 1.8));
|
||||
|
||||
@ -615,71 +611,31 @@ function buildScaleAdjustAnnotationSvg(params: {
|
||||
const source = region.pixelRect;
|
||||
const targetRect = scaleRectAroundCenter(region.rect, scalePercent);
|
||||
const target = rectToPixels(targetRect, width, height);
|
||||
const sourceCx = source.left + source.width / 2;
|
||||
const sourceCy = source.top + source.height / 2;
|
||||
const targetCx = target.left + target.width / 2;
|
||||
const targetCy = target.top + target.height / 2;
|
||||
const badgeSize = Math.max(32, Math.round(fontSize * 1.3));
|
||||
const labelWidth = Math.min(
|
||||
Math.max(260, Math.round(fontSize * 12.8)),
|
||||
Math.max(180, width - 24)
|
||||
const radius = Math.max(8, Math.round(strokeWidth * 2));
|
||||
const badgeSize = Math.max(22, Math.round(minSide * 0.022));
|
||||
const badgeX = clamp(
|
||||
source.left + Math.round(badgeSize * 0.45),
|
||||
badgeSize / 2 + 4,
|
||||
width - badgeSize / 2 - 4
|
||||
);
|
||||
const labelHeight = Math.round(fontSize * 2.55);
|
||||
const labelX = clamp(
|
||||
target.left,
|
||||
12,
|
||||
Math.max(12, width - labelWidth - 12)
|
||||
const badgeY = clamp(
|
||||
source.top + Math.round(badgeSize * 0.45),
|
||||
badgeSize / 2 + 4,
|
||||
height - badgeSize / 2 - 4
|
||||
);
|
||||
const labelY = clamp(
|
||||
target.top - labelHeight - Math.round(fontSize * 0.55),
|
||||
12,
|
||||
Math.max(12, height - labelHeight - 12)
|
||||
);
|
||||
|
||||
const sourceCorners = [
|
||||
[source.left, source.top],
|
||||
[source.left + source.width, source.top],
|
||||
[source.left + source.width, source.top + source.height],
|
||||
[source.left, source.top + source.height],
|
||||
];
|
||||
const targetCorners = [
|
||||
[target.left, target.top],
|
||||
[target.left + target.width, target.top],
|
||||
[target.left + target.width, target.top + target.height],
|
||||
[target.left, target.top + target.height],
|
||||
];
|
||||
const arrows = sourceCorners
|
||||
.map(([sx, sy], index) => {
|
||||
const [tx, ty] = targetCorners[index];
|
||||
const fromX = scalePercent > 0 ? sx : tx;
|
||||
const fromY = scalePercent > 0 ? sy : ty;
|
||||
const toX = scalePercent > 0 ? tx : sx;
|
||||
const toY = scalePercent > 0 ? ty : sy;
|
||||
return `<line x1="${fromX}" y1="${fromY}" x2="${toX}" y2="${toY}" stroke="${targetColor}" stroke-width="${Math.max(3, strokeWidth * 0.72)}" stroke-linecap="round" opacity="0.82" marker-end="url(#scale-adjust-arrow)" />`;
|
||||
})
|
||||
.join("");
|
||||
const fontSize = Math.max(13, Math.round(badgeSize * 0.52));
|
||||
|
||||
return `
|
||||
<rect x="${source.left}" y="${source.top}" width="${source.width}" height="${source.height}" rx="${Math.round(strokeWidth * 2)}" fill="${sourceColor}" opacity="0.07" stroke="${sourceColor}" stroke-width="${strokeWidth}" />
|
||||
<rect x="${target.left}" y="${target.top}" width="${target.width}" height="${target.height}" rx="${Math.round(strokeWidth * 2.5)}" fill="${targetColor}" opacity="0.09" stroke="${targetColor}" stroke-width="${Math.max(4, Math.round(strokeWidth * 1.15))}" stroke-dasharray="${dash} ${gap}" />
|
||||
${arrows}
|
||||
<line x1="${sourceCx}" y1="${sourceCy}" x2="${targetCx}" y2="${targetCy}" stroke="${targetColor}" stroke-width="${Math.max(2, Math.round(strokeWidth * 0.45))}" opacity="0.5" />
|
||||
<circle cx="${clamp(source.left, badgeSize / 2 + 6, width - badgeSize / 2 - 6)}" cy="${clamp(source.top, badgeSize / 2 + 6, height - badgeSize / 2 - 6)}" r="${badgeSize / 2}" fill="${sourceColor}" stroke="#ffffff" stroke-width="${Math.max(2, Math.round(strokeWidth * 0.45))}" />
|
||||
<text x="${clamp(source.left, badgeSize / 2 + 6, width - badgeSize / 2 - 6)}" y="${clamp(source.top, badgeSize / 2 + 6, height - badgeSize / 2 - 6) + smallFontSize * 0.38}" text-anchor="middle" fill="#ffffff" font-size="${smallFontSize}" font-family="Arial, Helvetica, sans-serif" font-weight="800">${region.index}</text>
|
||||
<rect x="${labelX}" y="${labelY}" width="${labelWidth}" height="${labelHeight}" rx="${Math.round(fontSize * 0.55)}" fill="#061017" opacity="0.86" stroke="${targetColor}" stroke-width="${Math.max(2, Math.round(strokeWidth * 0.5))}" />
|
||||
<text x="${labelX + Math.round(fontSize * 0.7)}" y="${labelY + Math.round(fontSize * 1.03)}" fill="#ffffff" font-size="${fontSize}" font-family="Arial, Helvetica, sans-serif" font-weight="800">目标:${escapeXml(actionLabel)},主体约 ${escapeXml(targetLabel)}</text>
|
||||
<text x="${labelX + Math.round(fontSize * 0.7)}" y="${labelY + Math.round(fontSize * 2.03)}" fill="${targetColor}" font-size="${smallFontSize}" font-family="Arial, Helvetica, sans-serif" font-weight="700">按主体原始宽高比等比变化,禁止拉伸变形</text>
|
||||
<rect x="${source.left}" y="${source.top}" width="${source.width}" height="${source.height}" rx="${radius}" fill="${sourceColor}" opacity="0.025" stroke="${sourceColor}" stroke-width="${strokeWidth}" />
|
||||
<rect x="${target.left}" y="${target.top}" width="${target.width}" height="${target.height}" rx="${radius}" fill="${targetColor}" opacity="0.035" stroke="${targetColor}" stroke-width="${Math.max(3, Math.round(strokeWidth * 1.05))}" stroke-dasharray="${dash} ${gap}" />
|
||||
<circle cx="${badgeX}" cy="${badgeY}" r="${badgeSize / 2}" fill="${sourceColor}" opacity="0.92" stroke="#ffffff" stroke-width="${Math.max(1.5, Math.round(strokeWidth * 0.36))}" />
|
||||
<text x="${badgeX}" y="${badgeY + fontSize * 0.36}" text-anchor="middle" fill="#ffffff" font-size="${fontSize}" font-family="Arial, Helvetica, sans-serif" font-weight="800">${region.index}</text>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
return Buffer.from(`
|
||||
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="scale-adjust-arrow" markerWidth="12" markerHeight="12" refX="9" refY="6" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M2,2 L10,6 L2,10 Z" fill="${targetColor}" />
|
||||
</marker>
|
||||
</defs>
|
||||
${regionSvg}
|
||||
</svg>
|
||||
`);
|
||||
@ -731,10 +687,14 @@ function buildLocalEditPrompt(regions: NormalizedLocalEditRegion[]) {
|
||||
function buildScaleAdjustPrompt(
|
||||
regions: NormalizedLocalEditRegion[],
|
||||
scalePercent: ScaleAdjustPercent,
|
||||
cropReferences: ScaleAdjustCropReference[] = []
|
||||
cropReferences: ScaleAdjustCropReference[] = [],
|
||||
sourceSize?: { width: number; height: number }
|
||||
) {
|
||||
const actionLabel = getScaleAdjustLabel(scalePercent);
|
||||
const targetLabel = getScaleAdjustTargetLabel(scalePercent);
|
||||
const sourceSizeLine = sourceSize
|
||||
? `最终输出必须保持与输入图片1完全相同的画幅和像素尺寸:${sourceSize.width} x ${sourceSize.height} px。`
|
||||
: "最终输出必须保持与输入图片1完全相同的画幅和像素尺寸。";
|
||||
const regionLines = regions
|
||||
.map((region) => {
|
||||
const left = Math.round(region.rect.x * 1000) / 10;
|
||||
@ -769,8 +729,10 @@ function buildScaleAdjustPrompt(
|
||||
.join("\n");
|
||||
|
||||
return [
|
||||
"这是一项局部主体比例 inpaint/edit 任务,不是整图缩放、整图裁切或重新构图任务。",
|
||||
sourceSizeLine,
|
||||
"输入图片1是原始干净图,也是最终输出必须继承的唯一画面基底。除目标主体尺寸变化与必要背景补全外,输入图片1中的文字、Logo、装饰、排版、背景、光线、构图边界和所有未选区域必须保持不变。",
|
||||
"输入图片2是定位参考图:它只用于告诉你用户框选的目标主体位置和目标尺寸范围,不是输出底图。输入图片2里的彩色框、编号、标签、半透明遮罩、文字和 UI 只是定位标记,绝不能出现在最终画面中,也不得影响输入图片1的文字和排版。",
|
||||
"输入图片2是低噪声定位参考图:红色实线框用于寻找当前主体,青绿色或绿色虚线框用于表达目标尺寸范围。输入图片2不是输出底图,不得学习或复刻它的框线、编号、半透明遮罩或 UI 标记。",
|
||||
cropLines
|
||||
? `输入图片3及之后是框选区域的干净局部裁切参考图,作用是帮助你确认“要缩放的是哪个主体”。这些裁切图不是输出底图,不得被整体放大、贴回或覆盖到原图上。\n${cropLines}`
|
||||
: "",
|
||||
@ -778,9 +740,10 @@ function buildScaleAdjustPrompt(
|
||||
"如果系统或模型只能读取一张参考图,必须优先以输入图片1为输出基底,不得清理、改写或删除输入图片1中框选区域外的任何文字和图形。",
|
||||
"系统已根据用户框选区域计算出下方每个区域的原图坐标;框选区域只是定位目标主体的搜索范围,不是最终画面的裁切块、贴片或蒙版边界。",
|
||||
"最终结果必须保持原始干净图的画幅比例、构图边界、镜头透视、光线、色温、材质质感和清晰度;严禁整张图整体缩放、裁切、平移、重新排版或重新生成。",
|
||||
"Highest priority rule: do NOT zoom, crop, resize, reframe, pan, or regenerate the full canvas. Treat image 1 as a locked background plate and only inpaint the selected subject footprint.",
|
||||
"请不要生成任何彩色框、矩形边框、编号圆点、提示词气泡、标注文字、UI 控件或半透明色块。",
|
||||
"",
|
||||
`任务:对用户框选的主体物体做“比例调整”,统一执行${actionLabel}。目标主体调整后的视觉尺寸必须约为原主体的 ${targetLabel},普通用户肉眼应能清楚看出主体尺寸已经变化;如果输出看起来和原图一样,就是失败。`,
|
||||
`任务:先在输入图片1中识别红色实线框附近最主要、可独立缩放的主体物体,再只对这个主体做局部比例调整,统一执行${actionLabel}。目标主体调整后的视觉尺寸必须约为原主体的 ${targetLabel},普通用户肉眼应能清楚看出主体尺寸已经变化;如果输出看起来和原图一样,就是失败。`,
|
||||
regionLines,
|
||||
"",
|
||||
"关键要求:",
|
||||
@ -935,7 +898,8 @@ export async function POST(request: NextRequest) {
|
||||
? buildScaleAdjustPrompt(
|
||||
regions,
|
||||
scalePercent as ScaleAdjustPercent,
|
||||
scaleAdjustCropReferences
|
||||
scaleAdjustCropReferences,
|
||||
{ width: sourceWidth, height: sourceHeight }
|
||||
)
|
||||
: buildLocalEditPrompt(regions);
|
||||
const manualRegionReferenceImageUrls = regions.flatMap((region) =>
|
||||
@ -1015,6 +979,9 @@ export async function POST(request: NextRequest) {
|
||||
regionCount: regions.length,
|
||||
scalePercent,
|
||||
scaleAdjustCropReferenceCount: scaleAdjustCropReferenceImageUrls.length,
|
||||
scaleAdjustPromptStrategy: isScaleAdjustOperation
|
||||
? "locked-plate-local-subject-v2"
|
||||
: undefined,
|
||||
scaleAdjustUsesFullFrameOutput: isScaleAdjustOperation,
|
||||
sourceHeight,
|
||||
sourceNodeId: body.sourceNodeId || null,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user