diff --git a/app/api/canvas/local-edit/route.ts b/app/api/canvas/local-edit/route.ts index 3f0dc7147..038dcfacc 100644 --- a/app/api/canvas/local-edit/route.ts +++ b/app/api/canvas/local-edit/route.ts @@ -2931,18 +2931,42 @@ async function buildScaleAdjustStructureLockedComposite(params: { ((sourceCenterY - placement.sourceFootprintBox.top) / Math.max(1, placement.sourceFootprintBox.height)) * placement.targetBox.height; - const structureOverlay = await sharp(subjectCrop, { failOn: "none" }) - .joinChannel(subjectAlpha) - .resize(placedWidth, placedHeight, { - fit: "fill", - kernel: sharp.kernel.lanczos3, - }) - .png({ compressionLevel: 6 }) - .toBuffer(); + const useRectCropTargetGuide = preferPixelStructure && Boolean(protectedPersonSide); + const structureOverlay = useRectCropTargetGuide + ? await sharp(sourceBuffer, { failOn: "none" }) + .extract(placement.sourceBox) + .resize(placement.placedWidth, placement.placedHeight, { + fit: "fill", + kernel: sharp.kernel.lanczos3, + }) + .ensureAlpha() + .composite([ + { + input: buildScaleAdjustPatchMaskSvg({ + height: placement.placedHeight, + width: placement.placedWidth, + }), + blend: "dest-in", + }, + ]) + .png({ compressionLevel: 6 }) + .toBuffer() + : await sharp(subjectCrop, { failOn: "none" }) + .joinChannel(subjectAlpha) + .resize(placedWidth, placedHeight, { + fit: "fill", + kernel: sharp.kernel.lanczos3, + }) + .png({ compressionLevel: 6 }) + .toBuffer(); const targetClipMaskPatch = await buildScaleAdjustOverlayClipMaskPatch(structureOverlay); - const overlayLeft = Math.round(targetCenterX - placedWidth / 2); - const overlayTop = Math.round(targetCenterY - placedHeight / 2); + const overlayLeft = useRectCropTargetGuide + ? placement.placedLeft + : Math.round(targetCenterX - placedWidth / 2); + const overlayTop = useRectCropTargetGuide + ? placement.placedTop + : Math.round(targetCenterY - placedHeight / 2); return { compositeClip: { @@ -2950,7 +2974,7 @@ async function buildScaleAdjustStructureLockedComposite(params: { left: placement.sourceBox.left, top: placement.sourceBox.top, }, - method: "pixel-mask", + method: useRectCropTargetGuide ? "rect-crop-guide" : "pixel-mask", overlay: { input: structureOverlay, left: overlayLeft, @@ -5032,7 +5056,8 @@ export async function POST(request: NextRequest) { : "locked-plate-semantic-target-map-no-old-crop-v15" : undefined, scaleAdjustPostCompositeApplied: - Boolean(scaleAdjustPatchReference), + Boolean(scaleAdjustPatchReference) && + !useScaleAdjustFullFrameStructurePatch, scaleAdjustUsesFullFrameOutput: isScaleAdjustOperation && (!scaleAdjustPatchReference || useScaleAdjustFullFrameStructurePatch), @@ -5070,7 +5095,7 @@ export async function POST(request: NextRequest) { ? { maskImageUrl: scaleAdjustEditMaskUpload.url } : undefined, referenceImages, - scaleAdjustPatchComposite: scaleAdjustPatchReference + scaleAdjustPatchComposite: scaleAdjustPatchReference && !useScaleAdjustFullFrameStructurePatch ? { diffAlphaCutoff: scaleAdjustPatchReference.structureLockedDraft ? 24 : 8, @@ -5086,9 +5111,6 @@ export async function POST(request: NextRequest) { scaleAdjustPatchReference.structureLockedDraft ? 36 : 42, featherMinPx: 10, featherRatio: 0.018, - generatedFrame: useScaleAdjustFullFrameStructurePatch - ? "full" - : "workspace", maskMode: "diff", regions: scaleAdjustPatchCompositeRegions, sourceImageUrl,