fix: clamp scale patch alpha to edit regions
This commit is contained in:
parent
b9e1671777
commit
53be242e89
@ -790,6 +790,29 @@ function buildScaleAdjustPatchCompositeRegions(
|
||||
]);
|
||||
}
|
||||
|
||||
function buildScaleAdjustProtectedChangeRegions(
|
||||
regions: NormalizedLocalEditRegion[],
|
||||
scalePercent?: ScaleAdjustPercent | null
|
||||
) {
|
||||
return regions.flatMap((region) => {
|
||||
const targetRect = getScaleAdjustTargetRect(region, scalePercent);
|
||||
const unionRect = getRectUnion(region.rect, targetRect);
|
||||
const marginX = Math.min(
|
||||
0.045,
|
||||
Math.max(0.012, unionRect.width * 0.08)
|
||||
);
|
||||
const marginY = Math.min(
|
||||
0.045,
|
||||
Math.max(0.012, unionRect.height * 0.08)
|
||||
);
|
||||
|
||||
return [
|
||||
expandRectWithMargins(region.rect, marginX, marginY),
|
||||
expandRectWithMargins(targetRect, marginX, marginY),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
function getScaleAdjustPatchWorkspaceRect(
|
||||
regions: NormalizedLocalEditRegion[],
|
||||
scalePercent?: ScaleAdjustPercent | null
|
||||
@ -3873,9 +3896,13 @@ export async function POST(request: NextRequest) {
|
||||
const scaleAdjustPatchCompositeRegions = isScaleAdjustOperation
|
||||
? buildScaleAdjustPatchCompositeRegions(regions, scalePercent)
|
||||
: [];
|
||||
const scaleAdjustProtectedChangeRegions =
|
||||
isScaleAdjustOperation && scaleAdjustPatchReference
|
||||
? buildScaleAdjustProtectedChangeRegions(regions, scalePercent)
|
||||
: [];
|
||||
const scaleAdjustProtectedRegions =
|
||||
isScaleAdjustOperation && scaleAdjustPatchReference
|
||||
? [...scaleAdjustCompositeRegions, scaleAdjustPatchReference.workspaceRect]
|
||||
? scaleAdjustProtectedChangeRegions
|
||||
: scaleAdjustCompositeRegions;
|
||||
const scaleAdjustVerificationConfig = isScaleAdjustOperation
|
||||
? {
|
||||
|
||||
@ -2574,6 +2574,75 @@ async function buildLocalEditDiffCompositeMask(params: {
|
||||
.toBuffer();
|
||||
}
|
||||
|
||||
async function normalizeAlphaMaskToRaw(params: {
|
||||
maskBuffer: Buffer;
|
||||
width: number;
|
||||
height: number;
|
||||
}) {
|
||||
const { height, maskBuffer, width } = params;
|
||||
return sharp(maskBuffer, { failOn: "none" })
|
||||
.resize(width, height, { fit: "fill" })
|
||||
.removeAlpha()
|
||||
.extractChannel(0)
|
||||
.raw()
|
||||
.toBuffer();
|
||||
}
|
||||
|
||||
async function normalizeOpacityMaskToRaw(params: {
|
||||
maskBuffer: Buffer;
|
||||
width: number;
|
||||
height: number;
|
||||
}) {
|
||||
const { height, maskBuffer, width } = params;
|
||||
return sharp(maskBuffer, { failOn: "none" })
|
||||
.resize(width, height, { fit: "fill" })
|
||||
.ensureAlpha()
|
||||
.extractChannel(3)
|
||||
.raw()
|
||||
.toBuffer();
|
||||
}
|
||||
|
||||
function multiplyAlphaMasks(primaryMask: Buffer, clipMask: Buffer) {
|
||||
if (primaryMask.length !== clipMask.length) return primaryMask;
|
||||
const multiplied = Buffer.allocUnsafe(primaryMask.length);
|
||||
for (let index = 0; index < primaryMask.length; index += 1) {
|
||||
multiplied[index] = Math.round((primaryMask[index] * clipMask[index]) / 255);
|
||||
}
|
||||
return multiplied;
|
||||
}
|
||||
|
||||
function boostAlphaMask(mask: Buffer, cutoff: number, multiplier: number) {
|
||||
const boosted = Buffer.allocUnsafe(mask.length);
|
||||
for (let index = 0; index < mask.length; index += 1) {
|
||||
const value = mask[index];
|
||||
boosted[index] =
|
||||
value < cutoff ? 0 : Math.min(255, Math.round(value * multiplier));
|
||||
}
|
||||
return boosted;
|
||||
}
|
||||
|
||||
async function applyRawAlphaToImage(params: {
|
||||
imageBuffer: Buffer;
|
||||
alphaMaskRaw: Buffer;
|
||||
width: number;
|
||||
height: number;
|
||||
}) {
|
||||
const { alphaMaskRaw, height, imageBuffer, width } = params;
|
||||
const rgbRaw = await sharp(imageBuffer, { failOn: "none" })
|
||||
.resize(width, height, { fit: "fill" })
|
||||
.removeAlpha()
|
||||
.toColorspace("srgb")
|
||||
.raw()
|
||||
.toBuffer();
|
||||
|
||||
return sharp(rgbRaw, { raw: { channels: 3, height, width } })
|
||||
.joinChannel(alphaMaskRaw, {
|
||||
raw: { channels: 1, height, width },
|
||||
})
|
||||
.png({ compressionLevel: 6 })
|
||||
.toBuffer();
|
||||
}
|
||||
|
||||
async function applyLocalEditCompositeToImages(params: {
|
||||
images: ParsedImage[];
|
||||
composite: CanvasLocalEditCompositeConfig | null;
|
||||
@ -2613,19 +2682,22 @@ async function applyLocalEditCompositeToImages(params: {
|
||||
.toBuffer();
|
||||
const overlayBuffer =
|
||||
composite.maskMode === "diff"
|
||||
? await sharp(resizedGeneratedBuffer, { failOn: "none" })
|
||||
.removeAlpha()
|
||||
.joinChannel(
|
||||
await buildLocalEditDiffCompositeMask({
|
||||
? await applyRawAlphaToImage({
|
||||
alphaMaskRaw: await normalizeAlphaMaskToRaw({
|
||||
height: sourceHeight,
|
||||
maskBuffer: await buildLocalEditDiffCompositeMask({
|
||||
composite,
|
||||
generatedBuffer: resizedGeneratedBuffer,
|
||||
height: sourceHeight,
|
||||
sourceBuffer,
|
||||
width: sourceWidth,
|
||||
})
|
||||
)
|
||||
.png({ compressionLevel: 6 })
|
||||
.toBuffer()
|
||||
}),
|
||||
width: sourceWidth,
|
||||
}),
|
||||
height: sourceHeight,
|
||||
imageBuffer: resizedGeneratedBuffer,
|
||||
width: sourceWidth,
|
||||
})
|
||||
: await sharp(resizedGeneratedBuffer, { failOn: "none" })
|
||||
.ensureAlpha()
|
||||
.composite([
|
||||
@ -2778,6 +2850,25 @@ async function applyScaleAdjustPatchCompositeToImages(params: {
|
||||
touchesTop,
|
||||
width: workspaceBox.width,
|
||||
});
|
||||
const regionClipPatchMask = maskRegionBoxes?.length
|
||||
? buildScaleAdjustPatchCompositeMaskSvg({
|
||||
featherPx,
|
||||
height: workspaceBox.height,
|
||||
regions: maskRegionBoxes,
|
||||
touchesBottom,
|
||||
touchesLeft,
|
||||
touchesRight,
|
||||
touchesTop,
|
||||
width: workspaceBox.width,
|
||||
})
|
||||
: null;
|
||||
const regionClipPatchMaskRaw = regionClipPatchMask
|
||||
? await normalizeOpacityMaskToRaw({
|
||||
height: workspaceBox.height,
|
||||
maskBuffer: regionClipPatchMask,
|
||||
width: workspaceBox.width,
|
||||
})
|
||||
: null;
|
||||
const sourcePatchBuffer = await sharp(sourceBuffer, { failOn: "none" })
|
||||
.extract(workspaceBox)
|
||||
.png({ compressionLevel: 6 })
|
||||
@ -2829,11 +2920,24 @@ async function applyScaleAdjustPatchCompositeToImages(params: {
|
||||
throw new Error("比例调整局部结果合成失败:无法生成合成蒙版");
|
||||
}
|
||||
const patchOverlayBuffer = diffPatchMask
|
||||
? await sharp(resizedGeneratedBuffer, { failOn: "none" })
|
||||
.removeAlpha()
|
||||
.joinChannel(diffPatchMask)
|
||||
.png({ compressionLevel: 6 })
|
||||
.toBuffer()
|
||||
? await applyRawAlphaToImage({
|
||||
alphaMaskRaw: boostAlphaMask(
|
||||
multiplyAlphaMasks(
|
||||
await normalizeAlphaMaskToRaw({
|
||||
height: workspaceBox.height,
|
||||
maskBuffer: diffPatchMask,
|
||||
width: workspaceBox.width,
|
||||
}),
|
||||
regionClipPatchMaskRaw ||
|
||||
Buffer.alloc(workspaceBox.width * workspaceBox.height, 255)
|
||||
),
|
||||
8,
|
||||
3
|
||||
),
|
||||
height: workspaceBox.height,
|
||||
imageBuffer: resizedGeneratedBuffer,
|
||||
width: workspaceBox.width,
|
||||
})
|
||||
: await sharp(resizedGeneratedBuffer, { failOn: "none" })
|
||||
.ensureAlpha()
|
||||
.composite([{ input: patchMask, blend: "dest-in" }])
|
||||
@ -2877,6 +2981,7 @@ async function applyScaleAdjustPatchCompositeToImages(params: {
|
||||
debugLabel,
|
||||
featherPx,
|
||||
imageCount: mergedImages.length,
|
||||
diffRegionClipApplied: Boolean(regionClipPatchMask),
|
||||
maskMode: composite.maskMode || "workspace",
|
||||
maskRegionCount: maskRegionBoxes?.length || 0,
|
||||
sourceHeight,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user