diff --git a/app/api/canvas/local-edit/route.ts b/app/api/canvas/local-edit/route.ts index 259fec9d6..aa3d169a6 100644 --- a/app/api/canvas/local-edit/route.ts +++ b/app/api/canvas/local-edit/route.ts @@ -2404,34 +2404,6 @@ async function buildScaleAdjustRmbgStructureLockedComposite(params: { width: cutoutWidth, }); if (!highConfidenceCutoutBbox) return null; - const refinedCutoutRaw = await applyScaleAdjustAlphaMaskToRgba({ - alphaMask: highConfidenceCutoutMask, - height: cutoutHeight, - rawRgba: cleanedRaw, - width: cutoutWidth, - }); - const cleanedCutout = await sharp(refinedCutoutRaw, { - raw: { channels: 4, height: cutoutHeight, width: cutoutWidth }, - }) - .png({ compressionLevel: 6 }) - .toBuffer(); - const alphaBbox = getScaleAdjustAlphaBoundingBox({ - height: cutoutHeight, - rawRgba: refinedCutoutRaw, - width: cutoutWidth, - }); - if (!alphaBbox) return null; - - const alphaRatio = alphaBbox.count / Math.max(1, cutoutWidth * cutoutHeight); - const bboxCoversCanvas = - alphaBbox.width >= cutoutWidth * 0.97 && - alphaBbox.height >= cutoutHeight * 0.97; - if ( - alphaRatio < SCALE_ADJUST_RMBG_MIN_ALPHA_RATIO || - (alphaRatio > SCALE_ADJUST_RMBG_MAX_SOLID_ALPHA_RATIO && bboxCoversCanvas) - ) { - return null; - } const fullAlphaMask = Buffer.from(highConfidenceCutoutMask); const sourceMaskBuffer = sameSizeCutout @@ -2464,15 +2436,68 @@ async function buildScaleAdjustRmbgStructureLockedComposite(params: { maskBuffer: sourceMaskBuffer, threshold: 96, }); + const protectedPersonMask = + protectedPersonSide && sameSizeCutout && sourceCropRaw + ? validateScaleAdjustProtectedPersonMask({ + box: { width: cutoutWidth, height: cutoutHeight }, + mask: buildScaleAdjustProtectedPersonSeedMask({ + box: { left: 0, top: 0, width: cutoutWidth, height: cutoutHeight }, + raw: sourceCropRaw, + side: protectedPersonSide, + width: cutoutWidth, + }), + }) + : null; const filteredSourceMask = filterScaleAdjustStructureComponents({ box: placement.sourceBox, mask: highConfidenceSourceMask, + protectedPersonMask, + protectedPersonSide, }); const filteredSourceBbox = getScaleAdjustMaskBoundingBox({ height: placement.sourceBox.height, mask: filteredSourceMask, width: placement.sourceBox.width, }); + const overlayAlphaMask = + sameSizeCutout && filteredSourceBbox + ? filteredSourceMask + : highConfidenceCutoutMask; + const overlayAlphaBbox = getScaleAdjustMaskBoundingBox({ + height: cutoutHeight, + mask: overlayAlphaMask, + width: cutoutWidth, + }); + if (!overlayAlphaBbox) return null; + const refinedCutoutRaw = await applyScaleAdjustAlphaMaskToRgba({ + alphaMask: overlayAlphaMask, + height: cutoutHeight, + rawRgba: cleanedRaw, + width: cutoutWidth, + }); + const cleanedCutout = await sharp(refinedCutoutRaw, { + raw: { channels: 4, height: cutoutHeight, width: cutoutWidth }, + }) + .png({ compressionLevel: 6 }) + .toBuffer(); + const alphaBbox = getScaleAdjustAlphaBoundingBox({ + height: cutoutHeight, + rawRgba: refinedCutoutRaw, + width: cutoutWidth, + }); + if (!alphaBbox) return null; + + const alphaRatio = + overlayAlphaBbox.count / Math.max(1, cutoutWidth * cutoutHeight); + const bboxCoversCanvas = + overlayAlphaBbox.width >= cutoutWidth * 0.97 && + overlayAlphaBbox.height >= cutoutHeight * 0.97; + if ( + alphaRatio < SCALE_ADJUST_RMBG_MIN_ALPHA_RATIO || + (alphaRatio > SCALE_ADJUST_RMBG_MAX_SOLID_ALPHA_RATIO && bboxCoversCanvas) + ) { + return null; + } const sourceWashBaseMask = filteredSourceBbox ? solidifyScaleAdjustStructureMask({ box: placement.sourceBox, @@ -2582,9 +2607,13 @@ async function buildScaleAdjustRmbgStructureLockedComposite(params: { alphaRatio: Number(alphaRatio.toFixed(3)), cutoutHeight, cutoutWidth, + filteredAlphaRatio: Number( + (overlayAlphaBbox.count / Math.max(1, cutoutWidth * cutoutHeight)).toFixed(3) + ), placedHeight, placedWidth, protectedPersonSide, + protectedPersonMaskApplied: Boolean(protectedPersonMask), sameSizeCutout, alphaBandingStats, sourceHeight: placement.sourceBox.height, @@ -5229,7 +5258,7 @@ export async function POST(request: NextRequest) { scaleAdjustPromptStrategy: isScaleAdjustOperation ? scaleAdjustPatchReference ? useScaleAdjustProductLockComposite - ? "product-lock-background-plate-rmbg-v1-one-shot" + ? "product-lock-background-plate-rmbg-v2-source-patch-one-shot" : useScaleAdjustFullFrameStructurePatch ? "full-frame-source-locked-target-guide-v8-one-shot" : scaleAdjustPatchReference.structureLockedDraft @@ -5251,7 +5280,8 @@ export async function POST(request: NextRequest) { : undefined, scaleAdjustPostCompositeApplied: Boolean(scaleAdjustPatchReference) && - !useScaleAdjustFullFrameStructurePatch, + (!useScaleAdjustFullFrameStructurePatch || + useScaleAdjustProductLockComposite), scaleAdjustUsesFullFrameOutput: isScaleAdjustOperation && (!scaleAdjustPatchReference || useScaleAdjustFullFrameStructurePatch), @@ -5291,7 +5321,27 @@ export async function POST(request: NextRequest) { ? { maskImageUrl: scaleAdjustEditMaskUpload.url } : undefined, referenceImages, - scaleAdjustPatchComposite: scaleAdjustPatchReference && !useScaleAdjustFullFrameStructurePatch + scaleAdjustPatchComposite: useScaleAdjustProductLockComposite + ? { + diffAlphaCutoff: 18, + diffAlphaMultiplier: 2.6, + diffCompareBlurPx: 1.1, + diffHighThreshold: 64, + diffLowThreshold: 20, + featherMaxPx: 34, + featherMinPx: 8, + featherRatio: 0.015, + generatedFrame: "full", + maskMode: "diff", + regions: scaleAdjustPatchCompositeRegions, + sourceImageUrl, + clipMaskImageUrl: + scaleAdjustPatchReference?.compositeClipMaskUrl || undefined, + workspaceRegion: + (scaleAdjustPatchReference as ScaleAdjustPatchReference) + .workspaceRect, + } + : scaleAdjustPatchReference && !useScaleAdjustFullFrameStructurePatch ? { diffAlphaCutoff: scaleAdjustPatchReference.structureLockedDraft ? 24 : 8,