fix: avoid broad mask in product lock scale adjust

This commit is contained in:
134 2026-07-07 19:44:10 +08:00
parent a201ebb346
commit 40ce24feb8

View File

@ -2297,15 +2297,11 @@ async function buildScaleAdjustOpaqueEditMaskFromOpacityMask(params: {
async function buildScaleAdjustProductLockBackgroundRepairMaskBuffer(params: {
productSourceMaskBuffer: Buffer;
regions: NormalizedLocalEditRegion[];
scalePercent?: ScaleAdjustPercent | null;
sourceHeight: number;
sourceWidth: number;
}) {
const {
productSourceMaskBuffer,
regions,
scalePercent,
sourceHeight,
sourceWidth,
} = params;
@ -2318,141 +2314,16 @@ async function buildScaleAdjustProductLockBackgroundRepairMaskBuffer(params: {
.raw()
.toBuffer();
const minSide = Math.max(1, Math.min(sourceWidth, sourceHeight));
const blur = Math.max(8, Math.min(34, Math.round(minSide * 0.018)));
const repairRects: string[] = [];
const protectRects: string[] = [];
for (const region of regions) {
const targetRect = getScaleAdjustTargetRect(region, scalePercent);
const unionBox = rectToPixels(
getRectUnion(region.rect, targetRect),
sourceWidth,
sourceHeight
);
const sourceBox = region.pixelRect;
const protectedPersonSide = getScaleAdjustProtectedPersonSide(region.prompt);
const leftPad = Math.max(10, Math.round(sourceBox.width * 0.09));
const rightPad =
protectedPersonSide === "right"
? Math.max(6, Math.round(sourceBox.width * 0.025))
: Math.max(10, Math.round(sourceBox.width * 0.09));
const topPad = Math.max(10, Math.round(sourceBox.height * 0.055));
const bottomPad = Math.max(10, Math.round(sourceBox.height * 0.035));
const repairLeft = clamp(
Math.min(unionBox.left, sourceBox.left) - leftPad,
0,
Math.max(0, sourceWidth - 1)
);
const repairTop = clamp(
Math.min(unionBox.top, sourceBox.top) - topPad,
0,
Math.max(0, sourceHeight - 1)
);
const repairRight = clamp(
Math.max(unionBox.left + unionBox.width, sourceBox.left + sourceBox.width) +
rightPad,
repairLeft + 1,
sourceWidth
);
const repairBottom = clamp(
Math.max(
unionBox.top + unionBox.height,
sourceBox.top + sourceBox.height
) + bottomPad,
repairTop + 1,
sourceHeight
);
repairRects.push(
`<rect x="${repairLeft}" y="${repairTop}" width="${Math.max(
1,
repairRight - repairLeft
)}" height="${Math.max(1, repairBottom - repairTop)}" rx="${Math.max(
8,
Math.round(blur * 0.9)
)}" ry="${Math.max(8, Math.round(blur * 0.9))}" fill="#ffffff" filter="url(#soft-repair)" />`
);
const workspaceBox = rectToPixels(
getScaleAdjustPatchWorkspaceRect([region], scalePercent),
sourceWidth,
sourceHeight
);
const panelBottom = clamp(
sourceBox.top + Math.round(sourceBox.height * 0.38),
repairTop + 1,
sourceHeight
);
repairRects.push(
`<rect x="${workspaceBox.left}" y="${repairTop}" width="${Math.max(
1,
workspaceBox.width
)}" height="${Math.max(1, panelBottom - repairTop)}" rx="${Math.max(
8,
Math.round(blur * 0.9)
)}" ry="${Math.max(8, Math.round(blur * 0.9))}" fill="#ffffff" filter="url(#soft-repair)" />`
);
if (protectedPersonSide === "right") {
const protectLeft = clamp(
sourceBox.left + Math.round(sourceBox.width * 0.73),
0,
Math.max(0, sourceWidth - 1)
);
const protectTop = clamp(
sourceBox.top + Math.round(sourceBox.height * 0.26),
0,
Math.max(0, sourceHeight - 1)
);
protectRects.push(
`<rect x="${protectLeft}" y="${protectTop}" width="${Math.max(
1,
sourceWidth - protectLeft
)}" height="${Math.max(1, sourceHeight - protectTop)}" fill="#000000" filter="url(#soft-protect)" />`
);
} else if (protectedPersonSide === "left") {
const protectRight = clamp(
sourceBox.left + Math.round(sourceBox.width * 0.27),
1,
sourceWidth
);
const protectTop = clamp(
sourceBox.top + Math.round(sourceBox.height * 0.26),
0,
Math.max(0, sourceHeight - 1)
);
protectRects.push(
`<rect x="0" y="${protectTop}" width="${protectRight}" height="${Math.max(
1,
sourceHeight - protectTop
)}" fill="#000000" filter="url(#soft-protect)" />`
);
}
}
const repairSvg = Buffer.from(
`<svg xmlns="http://www.w3.org/2000/svg" width="${sourceWidth}" height="${sourceHeight}" viewBox="0 0 ${sourceWidth} ${sourceHeight}">
<defs>
<filter id="soft-repair" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="${blur}" />
</filter>
<filter id="soft-protect" x="-8%" y="-8%" width="116%" height="116%">
<feGaussianBlur stdDeviation="${Math.max(4, Math.round(blur * 0.55))}" />
</filter>
</defs>
<rect x="0" y="0" width="${sourceWidth}" height="${sourceHeight}" fill="#000000" />
${repairRects.join("\n")}
${protectRects.join("\n")}
</svg>`
);
const repairAlpha = await sharp(repairSvg, { failOn: "none" })
.resize(sourceWidth, sourceHeight, { fit: "fill" })
.greyscale()
const repairAlpha = await sharp(productAlpha, {
raw: { channels: 1, height: sourceHeight, width: sourceWidth },
})
.blur(Math.max(1.4, Math.min(10, Math.round(minSide * 0.0045))))
.raw()
.toBuffer();
const rgba = Buffer.alloc(sourceWidth * sourceHeight * 4);
for (let index = 0; index < sourceWidth * sourceHeight; index += 1) {
const offset = index * 4;
const value = Math.max(repairAlpha[index] || 0, productAlpha[index] || 0);
const value = Math.max(productAlpha[index] || 0, repairAlpha[index] || 0);
rgba[offset] = 255;
rgba[offset + 1] = 255;
rgba[offset + 2] = 255;
@ -3920,12 +3791,18 @@ async function buildScaleAdjustVisualLayoutGuideBuffer(params: {
width: sourceWidth,
})
: null;
const sourceOnlyRepairMaskBuffer = compositeClipComposites.length
? await buildScaleAdjustCompositeClipMaskBuffer({
height: sourceHeight,
sourceMaskComposites: compositeClipComposites,
targetMaskComposites: [],
width: sourceWidth,
})
: null;
const productLockBackgroundRepairMaskBuffer =
productLockComposites.length && compositeClipMaskBuffer
productLockComposites.length && sourceOnlyRepairMaskBuffer
? await buildScaleAdjustProductLockBackgroundRepairMaskBuffer({
productSourceMaskBuffer: compositeClipMaskBuffer,
regions,
scalePercent,
productSourceMaskBuffer: sourceOnlyRepairMaskBuffer,
sourceHeight,
sourceWidth,
})