fix: guide fal scale adjust with marked photo

This commit is contained in:
134 2026-07-02 17:49:03 +08:00
parent 291597a69f
commit 2cccac34ff

View File

@ -1371,6 +1371,84 @@ function buildScaleAdjustZoomedCoordinateSvg(params: {
`);
}
function buildScaleAdjustFalPhotoGuideSvg(params: {
width: number;
height: number;
regions: NormalizedLocalEditRegion[];
scalePercent?: ScaleAdjustPercent | null;
}) {
const { width, height, regions, scalePercent } = params;
const minSide = Math.max(1, Math.min(width, height));
const strokeWidth = Math.max(3, Math.round(minSide * 0.005));
const fontSize = Math.max(16, Math.round(minSide * 0.022));
const labelPadX = Math.round(fontSize * 0.48);
const labelHeight = Math.round(fontSize * 1.35);
const currentColor = "#00b8d4";
const finalColor = "#2563eb";
const label = (text: string, x: number, y: number, color: string) => {
const labelWidth = Math.round(text.length * fontSize * 0.74 + labelPadX * 2);
const left = clamp(x, 6, Math.max(6, width - labelWidth - 6));
const top = clamp(y, 6, Math.max(6, height - labelHeight - 6));
return `
<rect x="${left}" y="${top}" width="${labelWidth}" height="${labelHeight}" rx="${Math.round(labelHeight * 0.22)}" fill="${color}" opacity="0.92" />
<text x="${left + labelPadX}" y="${top + Math.round(labelHeight * 0.68)}" fill="#ffffff" font-size="${fontSize}" font-family="Arial, Helvetica, sans-serif" font-weight="800">${escapeXml(text)}</text>
`;
};
const regionSvg = regions
.map((region) => {
const source = region.pixelRect;
const targetRect = getScaleAdjustTargetRect(region, scalePercent);
const target =
region.targetPixelRect || rectToPixels(targetRect, width, height);
const radius = Math.max(8, Math.round(strokeWidth * 2));
return `
<rect x="${source.left}" y="${source.top}" width="${source.width}" height="${source.height}" rx="${radius}" fill="${currentColor}" opacity="0.08" />
<rect x="${target.left}" y="${target.top}" width="${target.width}" height="${target.height}" rx="${radius}" fill="${finalColor}" opacity="0.14" />
<rect x="${source.left}" y="${source.top}" width="${source.width}" height="${source.height}" rx="${radius}" fill="none" stroke="${currentColor}" stroke-width="${strokeWidth}" stroke-dasharray="${strokeWidth * 2} ${strokeWidth * 1.4}" opacity="0.95" />
<rect x="${target.left}" y="${target.top}" width="${target.width}" height="${target.height}" rx="${radius}" fill="none" stroke="${finalColor}" stroke-width="${Math.max(3, Math.round(strokeWidth * 1.25))}" opacity="0.98" />
${label(`CURRENT ${region.index}`, source.left + strokeWidth, source.top + strokeWidth, currentColor)}
${label(`FINAL ${region.index}`, target.left + strokeWidth, target.top + strokeWidth, finalColor)}
`;
})
.join("");
return Buffer.from(`
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="${width}" height="${height}" fill="#f8fafc" opacity="0.24" />
${regionSvg}
</svg>
`);
}
async function buildScaleAdjustFalPhotoGuideBuffer(params: {
sourceBuffer: Buffer;
width: number;
height: number;
regions: NormalizedLocalEditRegion[];
scalePercent?: ScaleAdjustPercent | null;
}) {
const { sourceBuffer, width, height, regions, scalePercent } = params;
return sharp(sourceBuffer, { failOn: "none" })
.rotate()
.resize(width, height, { fit: "cover", position: "center" })
.modulate({ brightness: 1.04, saturation: 0.45 })
.composite([
{
input: buildScaleAdjustFalPhotoGuideSvg({
height,
regions,
scalePercent,
width,
}),
left: 0,
top: 0,
},
])
.png({ compressionLevel: 6 })
.toBuffer();
}
function buildLocalEditPrompt(regions: NormalizedLocalEditRegion[]) {
const regionLines = regions
.map((region) => {
@ -1576,8 +1654,8 @@ function buildScaleAdjustCoordinatePrompt(
return [
"Interior furniture photo edit.",
`Return one realistic photo with the same camera frame and pixel size as input image 1: ${sourceSize.width} x ${sourceSize.height} px.`,
"Input image 1 is the source photo. Input image 2 is a coordinate guide only and should not appear in the final photo.",
"The guide labels OLD as the current furniture box and TARGET as the requested final footprint.",
"Input image 1 is the source photo. Input image 2 is a marked copy of the source photo for location guidance only and should not appear in the final photo.",
"The guide labels CURRENT as the existing furniture box and FINAL as the requested final footprint.",
"Resize only the furniture subject indicated by each current box. Keep the camera, room layout, walls, windows, floor, rug, sofa, lamp, text, and background stable.",
"Preserve the furniture identity, material, perspective, objects on top, and natural aspect ratio.",
"For a smaller final footprint, show the furniture smaller and blend newly visible floor, rug, wall, window area, shadow, and background around it naturally. For a larger final footprint, scale the same furniture proportionally with natural edges and contact shadows.",
@ -1799,17 +1877,25 @@ export async function POST(request: NextRequest) {
resolvedScaleAdjustModelEntry?.provider?.type === "fal-image";
const annotatedBuffer = isScaleAdjustOperation
? useScaleAdjustCoordinateGuide
? await sharp(
buildScaleAdjustZoomedCoordinateSvg({
? useFalScaleAdjustCoordinatePrompt
? await buildScaleAdjustFalPhotoGuideBuffer({
height: sourceHeight,
regions,
scalePercent,
sourceBuffer,
width: sourceWidth,
}),
{ failOn: "none" }
)
.png({ compressionLevel: 6 })
.toBuffer()
})
: await sharp(
buildScaleAdjustZoomedCoordinateSvg({
height: sourceHeight,
regions,
scalePercent,
width: sourceWidth,
}),
{ failOn: "none" }
)
.png({ compressionLevel: 6 })
.toBuffer()
: await buildScaleAdjustVisualLayoutGuideBuffer({
regions,
scalePercent,
@ -1915,7 +2001,9 @@ export async function POST(request: NextRequest) {
annotatedReferenceSentToModel: isScaleAdjustOperation,
annotatedReferenceType: isScaleAdjustOperation
? useScaleAdjustCoordinateGuide
? "scale-adjust-neutral-coordinate-map"
? useFalScaleAdjustCoordinatePrompt
? "scale-adjust-fal-marked-photo-guide"
: "scale-adjust-neutral-coordinate-map"
: "scale-adjust-full-frame-semantic-edit-map"
: "source-overlay",
cleanSourceReferenceIndex: 1,
@ -1932,7 +2020,10 @@ export async function POST(request: NextRequest) {
scaleAdjustModelFamily: scaleAdjustModelFamily || null,
scaleAdjustCoordinateGuideApplied: useScaleAdjustCoordinateGuide,
scaleAdjustNeutralCoordinateMapApplied:
isScaleAdjustOperation && useScaleAdjustCoordinateGuide,
isScaleAdjustOperation &&
useScaleAdjustCoordinateGuide &&
!useFalScaleAdjustCoordinatePrompt,
scaleAdjustFalMarkedPhotoGuideApplied: useFalScaleAdjustCoordinatePrompt,
scaleAdjustVisualLayoutGuideApplied:
isScaleAdjustOperation && !useScaleAdjustCoordinateGuide,
falScaleAdjustCoordinateGuideApplied:
@ -1963,7 +2054,7 @@ export async function POST(request: NextRequest) {
scaleAdjustPromptStrategy: isScaleAdjustOperation
? useScaleAdjustCoordinateGuide
? useFalScaleAdjustCoordinatePrompt
? "fal-safe-zoomed-coordinate-map-no-old-crop-v42-one-shot"
? "fal-safe-marked-photo-guide-no-old-crop-v43-one-shot"
: "same-name-zoomed-coordinate-map-no-old-crop-v42-one-shot"
: "locked-plate-semantic-target-map-no-old-crop-v15"
: undefined,