fix: use full frame draft for furniture scale adjust
This commit is contained in:
parent
dde159ea7c
commit
a3a69fed43
@ -127,6 +127,7 @@ type ScaleAdjustPatchReference = {
|
||||
compositeClipMaskUrl?: string | null;
|
||||
sourceCropUrl: string;
|
||||
structureLockedDraft?: boolean;
|
||||
targetDraftFullUrl?: string | null;
|
||||
targetDraftUrl: string;
|
||||
workspaceRect: NormalizedLocalEditRegion["rect"];
|
||||
workspacePixelRect: {
|
||||
@ -3126,7 +3127,12 @@ async function createScaleAdjustPatchReference(params: {
|
||||
.toBuffer(),
|
||||
]);
|
||||
const suffix = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
const [sourceCropUpload, targetDraftUpload, compositeClipMaskUpload] = await Promise.all([
|
||||
const [
|
||||
sourceCropUpload,
|
||||
targetDraftUpload,
|
||||
targetDraftFullUpload,
|
||||
compositeClipMaskUpload,
|
||||
] = await Promise.all([
|
||||
uploadFile(
|
||||
`canvas-scale-adjust-workspace-source-${suffix}.png`,
|
||||
toArrayBuffer(sourceCropBuffer)
|
||||
@ -3135,6 +3141,10 @@ async function createScaleAdjustPatchReference(params: {
|
||||
`canvas-scale-adjust-workspace-target-draft-${suffix}.png`,
|
||||
toArrayBuffer(targetDraftCropBuffer)
|
||||
),
|
||||
uploadFile(
|
||||
`canvas-scale-adjust-full-target-draft-${suffix}.png`,
|
||||
toArrayBuffer(targetDraftBuffer)
|
||||
),
|
||||
targetDraftReference.compositeClipMaskBuffer
|
||||
? uploadFile(
|
||||
`canvas-scale-adjust-composite-clip-mask-${suffix}.png`,
|
||||
@ -3147,6 +3157,7 @@ async function createScaleAdjustPatchReference(params: {
|
||||
compositeClipMaskUrl: compositeClipMaskUpload?.url || null,
|
||||
sourceCropUrl: sourceCropUpload.url,
|
||||
structureLockedDraft: structureLockAppliedRef.value,
|
||||
targetDraftFullUrl: targetDraftFullUpload.url,
|
||||
targetDraftUrl: targetDraftUpload.url,
|
||||
workspacePixelRect,
|
||||
workspaceRect,
|
||||
@ -4083,6 +4094,50 @@ function buildScaleAdjustPatchPrompt(
|
||||
].filter(Boolean).join("\n");
|
||||
}
|
||||
|
||||
function buildScaleAdjustFullFramePatchPrompt(
|
||||
regions: NormalizedLocalEditRegion[],
|
||||
scalePercent: ScaleAdjustPercent | null | undefined,
|
||||
sourceSize: { width: number; height: number }
|
||||
) {
|
||||
const regionLines = regions
|
||||
.map((region) => {
|
||||
const targetRect = getScaleAdjustTargetRect(region, scalePercent);
|
||||
const targetPixelRect =
|
||||
region.targetPixelRect ||
|
||||
rectToPixels(targetRect, sourceSize.width, sourceSize.height);
|
||||
const widthFactor =
|
||||
region.rect.width > 0 ? targetRect.width / region.rect.width : 1;
|
||||
const heightFactor =
|
||||
region.rect.height > 0 ? targetRect.height / region.rect.height : 1;
|
||||
const action =
|
||||
widthFactor < 0.92 || heightFactor < 0.92
|
||||
? "make the same selected furniture/product visibly smaller"
|
||||
: widthFactor > 1.08 || heightFactor > 1.08
|
||||
? "make the same selected furniture/product visibly larger"
|
||||
: "move the same selected furniture/product to the final footprint";
|
||||
return `Region ${region.index}: original selected search area in the full photo is x=${region.pixelRect.left}, y=${region.pixelRect.top}, w=${region.pixelRect.width}, h=${region.pixelRect.height}; final target footprint in input image 1 is x=${targetPixelRect.left}, y=${targetPixelRect.top}, w=${targetPixelRect.width}, h=${targetPixelRect.height}; requested scale width=${widthFactor.toFixed(2)}, height=${heightFactor.toFixed(2)}. User request: ${region.prompt || action}. Main action: ${action}.`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return [
|
||||
"Full-frame local image edit for furniture/product scale adjustment.",
|
||||
"Return exactly one clean realistic full image with the same framing, camera, aspect ratio, and canvas extent as input image 2. Do not crop, zoom, pan, or resize the whole photo.",
|
||||
"Input image 1 is a full-photo target draft. The selected furniture/product has already been moved and scaled with source-photo pixels to the required final footprint. Treat the size, position, silhouette, product structure, material, perspective, wheels, rods, panels, hooks, weave, and visible details in input image 1 as the hard target.",
|
||||
"Input image 2 is the original clean full photo. Use it only for clean background texture, lighting, perspective, non-target people, non-target objects, and untouched areas. Do not restore the selected furniture/product to its old size or old position from input image 2.",
|
||||
"AI work is limited to natural background completion, boundary blending, occlusion, contact shadows, and removing rough draft artifacts. Do not repaint, redesign, replace, stretch, duplicate, or distort the selected furniture/product interior.",
|
||||
"If the target footprint intersects a non-target person, preserve that person as the foreground occluder exactly as in the original photo: do not move, cover, deform, redraw, or change their face, body, clothes, hands, legs, feet, or pose. Blend the furniture/product behind the person naturally where needed.",
|
||||
"Erase the old larger furniture/product footprint outside the final target footprint and rebuild newly visible wall, curtain, floor, rug, shadow, and background naturally. Do not leave ghost copies, old-size remnants, rectangular haze, pale wash blocks, patch edges, black holes, dirty speckles, or visible seams.",
|
||||
"Keep all non-target content stable: person identity and anatomy, sofa, window, curtain, cabinet, lamp, wall art, floor, rug, lighting, textural grain, and global color must remain consistent.",
|
||||
"Do not output boxes, masks, labels, coordinate marks, UI marks, arrows, guide overlays, crop borders, or any annotation artifacts.",
|
||||
"中文硬约束:输入图1是完整画面的目标草稿,目标家具/商品已经在最终大小和最终位置;输入图2只是原始干净画面参考,绝对不能恢复旧尺寸。只做背景补全、遮挡融合、阴影和边缘清理。非目标人物必须保持原样,不能被目标家具盖住、变形、换脸、改姿势或改腿脚。最终必须是一张完整自然照片,不能有矩形雾块、贴片边、黑洞、残影、标注框。",
|
||||
"",
|
||||
"Region instructions:",
|
||||
regionLines,
|
||||
"",
|
||||
"Success check before returning: the selected furniture/product footprint is clearly closer to the final target footprint in input image 1 than to the original search area, all non-target people remain unchanged, and the full image looks like one natural photograph.",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth();
|
||||
@ -4401,26 +4456,39 @@ export async function POST(request: NextRequest) {
|
||||
sourceWidth,
|
||||
})
|
||||
: [];
|
||||
const useScaleAdjustFullFrameStructurePatch =
|
||||
Boolean(scaleAdjustPatchReference?.structureLockedDraft) &&
|
||||
Boolean(scaleAdjustPatchReference?.targetDraftFullUrl) &&
|
||||
regions.some((region) =>
|
||||
isScaleAdjustFurnitureProductLikePrompt(region.prompt)
|
||||
);
|
||||
const useScaleAdjustPatchTargetDraftPrimary = Boolean(scaleAdjustPatchReference);
|
||||
const includeScaleAdjustPatchSourceReference =
|
||||
Boolean(scaleAdjustPatchReference);
|
||||
const useFalScaleAdjustPatchPrompt =
|
||||
Boolean(scaleAdjustPatchReference) &&
|
||||
!useScaleAdjustFullFrameStructurePatch &&
|
||||
resolvedScaleAdjustModelEntry?.provider?.type === "fal-image";
|
||||
const prompt = isScaleAdjustOperation
|
||||
? scaleAdjustPatchReference
|
||||
? buildScaleAdjustPatchPrompt(
|
||||
regions,
|
||||
scalePercent,
|
||||
scaleAdjustPatchReference,
|
||||
{ width: sourceWidth, height: sourceHeight },
|
||||
scaleAdjustCropReferences,
|
||||
{
|
||||
falSafe: useFalScaleAdjustPatchPrompt,
|
||||
includeSourceReference: includeScaleAdjustPatchSourceReference,
|
||||
targetDraftPrimary: useScaleAdjustPatchTargetDraftPrimary,
|
||||
}
|
||||
)
|
||||
? useScaleAdjustFullFrameStructurePatch
|
||||
? buildScaleAdjustFullFramePatchPrompt(
|
||||
regions,
|
||||
scalePercent,
|
||||
{ width: sourceWidth, height: sourceHeight }
|
||||
)
|
||||
: buildScaleAdjustPatchPrompt(
|
||||
regions,
|
||||
scalePercent,
|
||||
scaleAdjustPatchReference,
|
||||
{ width: sourceWidth, height: sourceHeight },
|
||||
scaleAdjustCropReferences,
|
||||
{
|
||||
falSafe: useFalScaleAdjustPatchPrompt,
|
||||
includeSourceReference: includeScaleAdjustPatchSourceReference,
|
||||
targetDraftPrimary: useScaleAdjustPatchTargetDraftPrimary,
|
||||
}
|
||||
)
|
||||
: useProviderTextOnlyCoordinatePrompt
|
||||
? buildScaleAdjustTextOnlyPrompt(
|
||||
regions,
|
||||
@ -4459,6 +4527,8 @@ export async function POST(request: NextRequest) {
|
||||
"full room image, changed crop border, artificial graphic overlay, visible coordinate aid, visible line, visible label, UI element, annotation text, guide mark, pasted crop, hard patch edge, rectangular haze, white haze block, pale wipe block, color mismatch, gray draft patch, beige wash patch, flat painted area, jagged mask hole, black speckled stain, dark blotch, duplicate subject, duplicate person, duplicate furniture, unchanged target subject, unchanged target person, unchanged target furniture, old-size target subject, changed target identity, changed face, deformed person, broken anatomy, extra limbs, extra fingers, changed locked product, redrawn product interior, different furniture, broken woven panel, dirty woven panel, wheel inside woven panel, stretched subject, stretched furniture, distorted furniture";
|
||||
const scaleAdjustFalPatchNegativePrompt =
|
||||
"full room image, boxes, labels, UI marks, arrows, text, overlays, seams, rectangular patch edge, haze block, ghost copy, old-size subject, unchanged subject, duplicate subject, stretched subject, distorted product, changed product structure";
|
||||
const scaleAdjustFullFramePatchNegativePrompt =
|
||||
"boxes, labels, UI marks, arrows, text, overlays, seams, rectangular patch edge, haze block, ghost copy, old-size subject, unchanged subject, duplicate subject, stretched subject, distorted product, changed product structure, changed person, changed face, deformed person, extra limbs, changed hands, changed feet, whole-image zoom, crop, pan";
|
||||
const manualRegionReferenceImageUrls = regions.flatMap((region) =>
|
||||
region.referenceImages.map((reference) => reference.url)
|
||||
);
|
||||
@ -4467,7 +4537,14 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
const referenceImages = isScaleAdjustOperation
|
||||
? scaleAdjustPatchReference
|
||||
? useScaleAdjustPatchTargetDraftPrimary
|
||||
? useScaleAdjustFullFrameStructurePatch
|
||||
? [
|
||||
scaleAdjustPatchReference.targetDraftFullUrl as string,
|
||||
sourceImageUrl,
|
||||
...scaleAdjustCropReferenceImageUrls,
|
||||
...manualRegionReferenceImageUrls,
|
||||
]
|
||||
: useScaleAdjustPatchTargetDraftPrimary
|
||||
? [
|
||||
scaleAdjustPatchReference.targetDraftUrl,
|
||||
...(includeScaleAdjustPatchSourceReference
|
||||
@ -4543,7 +4620,7 @@ export async function POST(request: NextRequest) {
|
||||
: requestedImageSize;
|
||||
const imageRequest: CanvasGenerateImageRequest = {
|
||||
allowFailover: isScaleAdjustOperation ? true : undefined,
|
||||
aspectRatio: scaleAdjustPatchReference
|
||||
aspectRatio: scaleAdjustPatchReference && !useScaleAdjustFullFrameStructurePatch
|
||||
? inferClosestAspectRatio(
|
||||
scaleAdjustPatchReference.workspacePixelRect.width,
|
||||
scaleAdjustPatchReference.workspacePixelRect.height
|
||||
@ -4556,7 +4633,9 @@ export async function POST(request: NextRequest) {
|
||||
annotatedReferenceUrl: annotatedUpload.url,
|
||||
annotatedReferenceSentToModel: sendScaleAdjustAnnotatedReferenceToModel,
|
||||
annotatedReferenceType: isScaleAdjustOperation
|
||||
? useProviderTextOnlyCoordinatePrompt
|
||||
? useScaleAdjustFullFrameStructurePatch
|
||||
? "scale-adjust-full-frame-structure-target-draft"
|
||||
: useProviderTextOnlyCoordinatePrompt
|
||||
? "scale-adjust-text-coordinate-only-debug-map"
|
||||
: useScaleAdjustCoordinatePrompt
|
||||
? useScaleAdjustVisualLayoutGuide
|
||||
@ -4592,11 +4671,15 @@ export async function POST(request: NextRequest) {
|
||||
Boolean(scaleAdjustPatchReference) && hasScaleAdjustShrink,
|
||||
scaleAdjustPatchStructureLockedDraftApplied:
|
||||
scaleAdjustPatchReference?.structureLockedDraft === true,
|
||||
scaleAdjustFullFrameStructurePatchApplied:
|
||||
useScaleAdjustFullFrameStructurePatch,
|
||||
scaleAdjustFullFrameFalEditApplied: useScaleAdjustFullFrameFalEdit,
|
||||
scaleAdjustFullFrameFalEditDisabledForShrink:
|
||||
isScaleAdjustFalNanoBananaEdit && hasScaleAdjustShrink,
|
||||
scaleAdjustPatchReferenceOrder:
|
||||
scaleAdjustPatchReference && useScaleAdjustPatchTargetDraftPrimary
|
||||
scaleAdjustPatchReference && useScaleAdjustFullFrameStructurePatch
|
||||
? "full-target-draft-full-source"
|
||||
: scaleAdjustPatchReference && useScaleAdjustPatchTargetDraftPrimary
|
||||
? includeScaleAdjustPatchSourceReference
|
||||
? "target-draft-source-identity"
|
||||
: "target-draft-identity"
|
||||
@ -4607,6 +4690,8 @@ export async function POST(request: NextRequest) {
|
||||
scaleAdjustPatchReference?.sourceCropUrl || null,
|
||||
scaleAdjustPatchTargetDraftUrl:
|
||||
scaleAdjustPatchReference?.targetDraftUrl || null,
|
||||
scaleAdjustPatchTargetDraftFullUrl:
|
||||
scaleAdjustPatchReference?.targetDraftFullUrl || null,
|
||||
scaleAdjustPatchCompositeClipMaskUrl:
|
||||
scaleAdjustPatchReference?.compositeClipMaskUrl || null,
|
||||
scaleAdjustPatchWorkspacePixelRect:
|
||||
@ -4661,7 +4746,9 @@ export async function POST(request: NextRequest) {
|
||||
.length,
|
||||
scaleAdjustPromptStrategy: isScaleAdjustOperation
|
||||
? scaleAdjustPatchReference
|
||||
? scaleAdjustPatchReference.structureLockedDraft
|
||||
? useScaleAdjustFullFrameStructurePatch
|
||||
? "full-frame-structure-locked-draft-v1-one-shot"
|
||||
: scaleAdjustPatchReference.structureLockedDraft
|
||||
? "ai-patch-workspace-structure-locked-draft-v87-subject-alpha-clipped-one-shot"
|
||||
: "ai-patch-workspace-draft-v75-soft-wipe-erase-ghost-one-shot"
|
||||
: useScaleAdjustEditMask
|
||||
@ -4676,9 +4763,12 @@ export async function POST(request: NextRequest) {
|
||||
? "same-name-marked-photo-box-guide-no-old-crop-v45-one-shot"
|
||||
: "locked-plate-semantic-target-map-no-old-crop-v15"
|
||||
: undefined,
|
||||
scaleAdjustPostCompositeApplied: Boolean(scaleAdjustPatchReference),
|
||||
scaleAdjustPostCompositeApplied:
|
||||
Boolean(scaleAdjustPatchReference) &&
|
||||
!useScaleAdjustFullFrameStructurePatch,
|
||||
scaleAdjustUsesFullFrameOutput:
|
||||
isScaleAdjustOperation && !scaleAdjustPatchReference,
|
||||
isScaleAdjustOperation &&
|
||||
(!scaleAdjustPatchReference || useScaleAdjustFullFrameStructurePatch),
|
||||
sourceHeight,
|
||||
sourceNodeId: body.sourceNodeId || null,
|
||||
sourceWidth,
|
||||
@ -4696,7 +4786,9 @@ export async function POST(request: NextRequest) {
|
||||
negativePrompt:
|
||||
isScaleAdjustOperation
|
||||
? scaleAdjustPatchReference
|
||||
? useFalScaleAdjustPatchPrompt
|
||||
? useScaleAdjustFullFrameStructurePatch
|
||||
? scaleAdjustFullFramePatchNegativePrompt
|
||||
: useFalScaleAdjustPatchPrompt
|
||||
? scaleAdjustFalPatchNegativePrompt
|
||||
: scaleAdjustPatchNegativePrompt
|
||||
: useProviderTextOnlyCoordinatePrompt
|
||||
@ -4711,7 +4803,7 @@ export async function POST(request: NextRequest) {
|
||||
? { maskImageUrl: scaleAdjustEditMaskUpload.url }
|
||||
: undefined,
|
||||
referenceImages,
|
||||
scaleAdjustPatchComposite: scaleAdjustPatchReference
|
||||
scaleAdjustPatchComposite: scaleAdjustPatchReference && !useScaleAdjustFullFrameStructurePatch
|
||||
? {
|
||||
diffAlphaCutoff:
|
||||
scaleAdjustPatchReference.structureLockedDraft ? 24 : 8,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user