fix: composite scale patches with diff masks

This commit is contained in:
134 2026-07-03 22:33:27 +08:00
parent 026164f86d
commit b4db755c45
2 changed files with 89 additions and 11 deletions

View File

@ -780,6 +780,16 @@ function buildScaleAdjustCompositeRegions(
});
}
function buildScaleAdjustPatchCompositeRegions(
regions: NormalizedLocalEditRegion[],
scalePercent?: ScaleAdjustPercent | null
) {
return regions.flatMap((region) => [
region.rect,
getScaleAdjustTargetRect(region, scalePercent),
]);
}
function getScaleAdjustPatchWorkspaceRect(
regions: NormalizedLocalEditRegion[],
scalePercent?: ScaleAdjustPercent | null
@ -3860,6 +3870,9 @@ export async function POST(request: NextRequest) {
const scaleAdjustCompositeRegions = isScaleAdjustOperation
? buildScaleAdjustCompositeRegions(regions, scalePercent)
: [];
const scaleAdjustPatchCompositeRegions = isScaleAdjustOperation
? buildScaleAdjustPatchCompositeRegions(regions, scalePercent)
: [];
const scaleAdjustProtectedRegions =
isScaleAdjustOperation && scaleAdjustPatchReference
? [...scaleAdjustCompositeRegions, scaleAdjustPatchReference.workspaceRect]
@ -4060,9 +4073,14 @@ export async function POST(request: NextRequest) {
referenceImages,
scaleAdjustPatchComposite: scaleAdjustPatchReference
? {
diffCompareBlurPx: 0.8,
diffHighThreshold: 42,
diffLowThreshold: 14,
featherMaxPx: 42,
featherMinPx: 10,
featherRatio: 0.018,
maskMode: "diff",
regions: scaleAdjustPatchCompositeRegions,
sourceImageUrl,
workspaceRegion: scaleAdjustPatchReference.workspaceRect,
}

View File

@ -57,6 +57,10 @@ export interface CanvasScaleAdjustPatchCompositeConfig {
sourceImageUrl: string;
workspaceRegion: CanvasLocalEditCompositeRegion;
regions?: CanvasLocalEditCompositeRegion[];
maskMode?: "workspace" | "diff";
diffLowThreshold?: number;
diffHighThreshold?: number;
diffCompareBlurPx?: number;
featherMinPx?: number;
featherMaxPx?: number;
featherRatio?: number;
@ -397,6 +401,19 @@ function normalizeScaleAdjustPatchCompositeConfig(
sourceImageUrl,
workspaceRegion,
...(regions.length > 0 ? { regions } : {}),
maskMode: value.maskMode === "diff" ? "diff" : "workspace",
diffHighThreshold: normalizeOptionalNumber(value.diffHighThreshold, {
max: 120,
min: 1,
}),
diffLowThreshold: normalizeOptionalNumber(value.diffLowThreshold, {
max: 80,
min: 0,
}),
diffCompareBlurPx: normalizeOptionalNumber(value.diffCompareBlurPx, {
max: 12,
min: 0,
}),
featherMaxPx: normalizeOptionalNumber(value.featherMaxPx, {
max: 180,
min: 0,
@ -2748,27 +2765,69 @@ async function applyScaleAdjustPatchCompositeToImages(params: {
};
})
.filter((box) => box.width > 0 && box.height > 0);
const patchMask = buildScaleAdjustPatchCompositeMaskSvg({
featherPx,
height: workspaceBox.height,
regions: maskRegionBoxes,
touchesBottom,
touchesLeft,
touchesRight,
touchesTop,
width: workspaceBox.width,
});
const staticPatchMask =
composite.maskMode === "diff"
? null
: buildScaleAdjustPatchCompositeMaskSvg({
featherPx,
height: workspaceBox.height,
regions: maskRegionBoxes,
touchesBottom,
touchesLeft,
touchesRight,
touchesTop,
width: workspaceBox.width,
});
const sourcePatchBuffer = await sharp(sourceBuffer, { failOn: "none" })
.extract(workspaceBox)
.png({ compressionLevel: 6 })
.toBuffer();
const diffRegions = (maskRegionBoxes?.length
? maskRegionBoxes
: [{ left: 0, top: 0, width: workspaceBox.width, height: workspaceBox.height }]
).map((box) => ({
x: box.left / Math.max(1, workspaceBox.width),
y: box.top / Math.max(1, workspaceBox.height),
width: box.width / Math.max(1, workspaceBox.width),
height: box.height / Math.max(1, workspaceBox.height),
}));
const mergedImages: ParsedImage[] = [];
for (const [index, image] of images.entries()) {
const generatedUrl = image.originalUrl || image.thumbnailUrl;
const generatedBuffer = await fetchImageBufferFromUrl(generatedUrl);
const patchOverlayBuffer = await sharp(generatedBuffer, { failOn: "none" })
const resizedGeneratedBuffer = await sharp(generatedBuffer, { failOn: "none" })
.rotate()
.resize(workspaceBox.width, workspaceBox.height, {
fit: "cover",
position: "center",
})
.png({ compressionLevel: 6 })
.toBuffer();
const patchMask =
composite.maskMode === "diff"
? await buildLocalEditDiffCompositeMask({
composite: {
diffCompareBlurPx: composite.diffCompareBlurPx,
diffHighThreshold: composite.diffHighThreshold,
diffLowThreshold: composite.diffLowThreshold,
featherMaxPx: composite.featherMaxPx,
featherMinPx: composite.featherMinPx,
featherRatio: composite.featherRatio,
maskMode: "diff",
regions: diffRegions,
sourceImageUrl: composite.sourceImageUrl,
},
generatedBuffer: resizedGeneratedBuffer,
height: workspaceBox.height,
sourceBuffer: sourcePatchBuffer,
width: workspaceBox.width,
})
: staticPatchMask;
if (!patchMask) {
throw new Error("比例调整局部结果合成失败:无法生成合成蒙版");
}
const patchOverlayBuffer = await sharp(resizedGeneratedBuffer, { failOn: "none" })
.ensureAlpha()
.composite([{ input: patchMask, blend: "dest-in" }])
.png({ compressionLevel: 6 })
@ -2811,6 +2870,7 @@ async function applyScaleAdjustPatchCompositeToImages(params: {
debugLabel,
featherPx,
imageCount: mergedImages.length,
maskMode: composite.maskMode || "workspace",
maskRegionCount: maskRegionBoxes?.length || 0,
sourceHeight,
sourceWidth,