fix: limit scale patch composite to edit regions

This commit is contained in:
134 2026-07-03 20:58:30 +08:00
parent 5a4e087b3d
commit 0a591456c4
2 changed files with 80 additions and 0 deletions

View File

@ -3900,6 +3900,7 @@ export async function POST(request: NextRequest) {
featherMaxPx: 42,
featherMinPx: 10,
featherRatio: 0.018,
regions: scaleAdjustCompositeRegions,
sourceImageUrl,
workspaceRegion: scaleAdjustPatchReference.workspaceRect,
}

View File

@ -56,6 +56,7 @@ export interface CanvasLocalEditCompositeConfig {
export interface CanvasScaleAdjustPatchCompositeConfig {
sourceImageUrl: string;
workspaceRegion: CanvasLocalEditCompositeRegion;
regions?: CanvasLocalEditCompositeRegion[];
featherMinPx?: number;
featherMaxPx?: number;
featherRatio?: number;
@ -384,10 +385,18 @@ function normalizeScaleAdjustPatchCompositeConfig(
if (!/^https?:\/\//i.test(sourceImageUrl)) return null;
const workspaceRegion = normalizeCanvasRegion(value.workspaceRegion);
if (!workspaceRegion) return null;
const regions = Array.isArray(value.regions)
? value.regions
.map((region) => normalizeCanvasRegion(region))
.filter((region): region is CanvasLocalEditCompositeRegion =>
Boolean(region)
)
: [];
return {
sourceImageUrl,
workspaceRegion,
...(regions.length > 0 ? { regions } : {}),
featherMaxPx: normalizeOptionalNumber(value.featherMaxPx, {
max: 180,
min: 0,
@ -2335,6 +2344,7 @@ function buildScaleAdjustPatchCompositeMaskSvg(params: {
width: number;
height: number;
featherPx: number;
regions?: Array<{ left: number; top: number; width: number; height: number }>;
touchesLeft: boolean;
touchesTop: boolean;
touchesRight: boolean;
@ -2342,6 +2352,46 @@ function buildScaleAdjustPatchCompositeMaskSvg(params: {
}) {
const { height, width } = params;
const featherPx = Math.max(0, Math.round(params.featherPx));
const regions = params.regions?.filter(
(region) => region.width > 0 && region.height > 0
);
if (regions?.length) {
const blur = Math.max(1, featherPx);
const softPad = Math.max(2, Math.round(featherPx * 1.35));
const corePad = Math.max(0, Math.round(featherPx * 0.22));
const softRects = regions
.map((region) => {
const left = Math.max(0, region.left - softPad);
const top = Math.max(0, region.top - softPad);
const right = Math.min(width, region.left + region.width + softPad);
const bottom = Math.min(height, region.top + region.height + softPad);
return `<rect x="${left}" y="${top}" width="${Math.max(1, right - left)}" height="${Math.max(1, bottom - top)}" fill="#ffffff" filter="url(#scale-adjust-patch-soft-mask)" />`;
})
.join("");
const coreRects = regions
.map((region) => {
const left = Math.max(0, region.left - corePad);
const top = Math.max(0, region.top - corePad);
const right = Math.min(width, region.left + region.width + corePad);
const bottom = Math.min(height, region.top + region.height + corePad);
return `<rect x="${left}" y="${top}" width="${Math.max(1, right - left)}" height="${Math.max(1, bottom - top)}" fill="#ffffff" />`;
})
.join("");
return Buffer.from(`
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="scale-adjust-patch-soft-mask" x="-50%" y="-50%" width="200%" height="200%" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation="${blur}" />
</filter>
</defs>
<rect x="0" y="0" width="${width}" height="${height}" fill="rgba(0,0,0,0)" />
${softRects}
${coreRects}
</svg>
`);
}
if (featherPx <= 0) {
return Buffer.from(`
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg">
@ -2671,9 +2721,37 @@ async function applyScaleAdjustPatchCompositeToImages(params: {
const touchesTop = workspaceBox.top <= 0;
const touchesRight = workspaceBox.left + workspaceBox.width >= sourceWidth;
const touchesBottom = workspaceBox.top + workspaceBox.height >= sourceHeight;
const maskRegionBoxes = composite.regions
?.map((region) =>
rectToPixelBox({
height: sourceHeight,
region,
width: sourceWidth,
})
)
.map((box) => {
const left = Math.max(0, box.left - workspaceBox.left);
const top = Math.max(0, box.top - workspaceBox.top);
const right = Math.min(
workspaceBox.width,
box.left + box.width - workspaceBox.left
);
const bottom = Math.min(
workspaceBox.height,
box.top + box.height - workspaceBox.top
);
return {
left,
top,
width: Math.max(0, right - left),
height: Math.max(0, bottom - top),
};
})
.filter((box) => box.width > 0 && box.height > 0);
const patchMask = buildScaleAdjustPatchCompositeMaskSvg({
featherPx,
height: workspaceBox.height,
regions: maskRegionBoxes,
touchesBottom,
touchesLeft,
touchesRight,
@ -2733,6 +2811,7 @@ async function applyScaleAdjustPatchCompositeToImages(params: {
debugLabel,
featherPx,
imageCount: mergedImages.length,
maskRegionCount: maskRegionBoxes?.length || 0,
sourceHeight,
sourceWidth,
workspaceBox,