fix: honor target boxes in scale adjust guard
This commit is contained in:
parent
4d65ba0b79
commit
3760f7ff44
@ -398,35 +398,13 @@ function scaleRectAroundCenter(
|
||||
|
||||
function getScaleAdjustTargetRect(
|
||||
region: NormalizedLocalEditRegion,
|
||||
scalePercent: ScaleAdjustPercent
|
||||
scalePercent?: ScaleAdjustPercent | null
|
||||
) {
|
||||
return region.targetRect || scaleRectAroundCenter(region.rect, scalePercent);
|
||||
}
|
||||
|
||||
function deriveScaleAdjustPercentFromTargetRect(
|
||||
sourceRect: NormalizedLocalEditRegion["rect"],
|
||||
targetRect: NormalizedLocalEditRegion["rect"] | null | undefined
|
||||
) {
|
||||
if (!targetRect || sourceRect.width <= 0 || sourceRect.height <= 0) {
|
||||
return null;
|
||||
if (region.targetRect) return region.targetRect;
|
||||
if (typeof scalePercent === "number" && Number.isFinite(scalePercent)) {
|
||||
return scaleRectAroundCenter(region.rect, scalePercent);
|
||||
}
|
||||
const widthRatio = Math.max(0.05, targetRect.width / sourceRect.width);
|
||||
const heightRatio = Math.max(0.05, targetRect.height / sourceRect.height);
|
||||
const uniformRatio = Math.sqrt(widthRatio * heightRatio);
|
||||
const percent = Math.round((uniformRatio - 1) * 100);
|
||||
if (!Number.isFinite(percent) || percent === 0) return null;
|
||||
return clamp(percent, MIN_SCALE_ADJUST_PERCENT, MAX_SCALE_ADJUST_PERCENT);
|
||||
}
|
||||
|
||||
function deriveScaleAdjustPercentFromRegions(regions: NormalizedLocalEditRegion[]) {
|
||||
for (const region of regions) {
|
||||
const percent = deriveScaleAdjustPercentFromTargetRect(
|
||||
region.rect,
|
||||
region.targetRect
|
||||
);
|
||||
if (percent !== null) return percent;
|
||||
}
|
||||
return null;
|
||||
return region.rect;
|
||||
}
|
||||
|
||||
function expandRectWithMargins(
|
||||
@ -450,11 +428,31 @@ function expandRectWithMargins(
|
||||
function getScaleAdjustWorkspaceRect(
|
||||
sourceRect: NormalizedLocalEditRegion["rect"],
|
||||
targetRect: NormalizedLocalEditRegion["rect"],
|
||||
scalePercent: ScaleAdjustPercent
|
||||
scalePercent?: ScaleAdjustPercent | null
|
||||
) {
|
||||
if (scalePercent <= 0) return targetRect;
|
||||
const sourceWidth = Math.max(0.0001, sourceRect.width);
|
||||
const sourceHeight = Math.max(0.0001, sourceRect.height);
|
||||
const widthGrowthRatio = Math.max(0, targetRect.width / sourceWidth - 1);
|
||||
const heightGrowthRatio = Math.max(0, targetRect.height / sourceHeight - 1);
|
||||
const areaGrowthRatio = Math.max(
|
||||
0,
|
||||
Math.sqrt(
|
||||
Math.max(0.0001, targetRect.width * targetRect.height) /
|
||||
Math.max(0.0001, sourceWidth * sourceHeight)
|
||||
) - 1
|
||||
);
|
||||
const explicitGrowthRatio = Math.max(
|
||||
widthGrowthRatio,
|
||||
heightGrowthRatio,
|
||||
areaGrowthRatio
|
||||
);
|
||||
const fallbackScaleRatio =
|
||||
typeof scalePercent === "number" && Number.isFinite(scalePercent)
|
||||
? Math.min(1, Math.max(0, scalePercent / 100))
|
||||
: 0;
|
||||
const scaleRatio = Math.max(explicitGrowthRatio, fallbackScaleRatio);
|
||||
if (scaleRatio <= 0) return targetRect;
|
||||
|
||||
const scaleRatio = Math.min(1, Math.max(0, scalePercent / 100));
|
||||
const marginX = Math.min(
|
||||
0.12,
|
||||
Math.max(0.018, sourceRect.width * scaleRatio * 0.22)
|
||||
@ -469,7 +467,7 @@ function getScaleAdjustWorkspaceRect(
|
||||
|
||||
function buildScaleAdjustCompositeRegions(
|
||||
regions: NormalizedLocalEditRegion[],
|
||||
scalePercent: ScaleAdjustPercent
|
||||
scalePercent?: ScaleAdjustPercent | null
|
||||
) {
|
||||
return regions.flatMap((region) => {
|
||||
const targetRect = getScaleAdjustTargetRect(region, scalePercent);
|
||||
@ -478,7 +476,8 @@ function buildScaleAdjustCompositeRegions(
|
||||
targetRect,
|
||||
scalePercent
|
||||
);
|
||||
return scalePercent > 0
|
||||
return workspaceRect.width > targetRect.width ||
|
||||
workspaceRect.height > targetRect.height
|
||||
? [region.rect, targetRect, workspaceRect]
|
||||
: [region.rect, targetRect];
|
||||
});
|
||||
@ -691,7 +690,7 @@ function buildScaleAdjustAnnotationSvg(params: {
|
||||
width: number;
|
||||
height: number;
|
||||
regions: NormalizedLocalEditRegion[];
|
||||
scalePercent: ScaleAdjustPercent;
|
||||
scalePercent?: ScaleAdjustPercent | null;
|
||||
}) {
|
||||
const { width, height, regions, scalePercent } = params;
|
||||
const minSide = Math.max(1, Math.min(width, height));
|
||||
@ -782,7 +781,7 @@ function buildLocalEditPrompt(regions: NormalizedLocalEditRegion[]) {
|
||||
|
||||
function buildScaleAdjustPrompt(
|
||||
regions: NormalizedLocalEditRegion[],
|
||||
scalePercent: ScaleAdjustPercent,
|
||||
scalePercent: ScaleAdjustPercent | null | undefined,
|
||||
cropReferences: ScaleAdjustCropReference[] = [],
|
||||
sourceSize?: { width: number; height: number }
|
||||
) {
|
||||
@ -925,21 +924,27 @@ export async function POST(request: NextRequest) {
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
if (operation === SCALE_ADJUST_OPERATION && scalePercent === null) {
|
||||
scalePercent = deriveScaleAdjustPercentFromRegions(regions);
|
||||
if (scalePercent === null) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "请提供比例调整幅度,或为每个区域提供有效的目标框 targetRect",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
const hasScaleAdjustTargetRects =
|
||||
operation === SCALE_ADJUST_OPERATION &&
|
||||
regions.length > 0 &&
|
||||
regions.every((region) => Boolean(region.targetRect));
|
||||
if (
|
||||
operation === SCALE_ADJUST_OPERATION &&
|
||||
!hasScaleAdjustTargetRects &&
|
||||
scalePercent === null
|
||||
) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "请为每个区域提供有效的目标框 targetRect,或提供比例调整幅度 scalePercent",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const shouldUseScaleAdjustDetailModel =
|
||||
operation === SCALE_ADJUST_OPERATION && scalePercent !== null;
|
||||
operation === SCALE_ADJUST_OPERATION &&
|
||||
(hasScaleAdjustTargetRects || scalePercent !== null);
|
||||
const [pointsConfig, siteSettingsForModel, scaleAdjustModelCandidates] =
|
||||
await Promise.all([
|
||||
getPointsSystemConfig(),
|
||||
@ -968,14 +973,15 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
const isScaleAdjustOperation =
|
||||
operation === SCALE_ADJUST_OPERATION && scalePercent !== null;
|
||||
operation === SCALE_ADJUST_OPERATION &&
|
||||
(hasScaleAdjustTargetRects || scalePercent !== null);
|
||||
const displayPrompt = buildLocalEditDisplayPrompt({ operation, scalePercent });
|
||||
|
||||
const annotationSvg = isScaleAdjustOperation
|
||||
? buildScaleAdjustAnnotationSvg({
|
||||
height: sourceHeight,
|
||||
regions,
|
||||
scalePercent: scalePercent as ScaleAdjustPercent,
|
||||
scalePercent,
|
||||
width: sourceWidth,
|
||||
})
|
||||
: buildAnnotationSvg({
|
||||
@ -1015,7 +1021,7 @@ export async function POST(request: NextRequest) {
|
||||
isScaleAdjustOperation
|
||||
? buildScaleAdjustPrompt(
|
||||
regions,
|
||||
scalePercent as ScaleAdjustPercent,
|
||||
scalePercent,
|
||||
scaleAdjustCropReferences,
|
||||
{ width: sourceWidth, height: sourceHeight }
|
||||
)
|
||||
@ -1067,10 +1073,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
const resolvedModel = detailFixedModel || requestedModel;
|
||||
const scaleAdjustCompositeRegions = isScaleAdjustOperation
|
||||
? buildScaleAdjustCompositeRegions(
|
||||
regions,
|
||||
scalePercent as ScaleAdjustPercent
|
||||
)
|
||||
? buildScaleAdjustCompositeRegions(regions, scalePercent)
|
||||
: [];
|
||||
|
||||
const imageRequest: CanvasGenerateImageRequest = {
|
||||
@ -1100,6 +1103,9 @@ export async function POST(request: NextRequest) {
|
||||
regionCount: regions.length,
|
||||
scalePercent,
|
||||
scaleAdjustCropReferenceCount: scaleAdjustCropReferenceImageUrls.length,
|
||||
scaleAdjustProtectionMode: hasScaleAdjustTargetRects
|
||||
? "target-rect"
|
||||
: "scale-percent",
|
||||
scaleAdjustTargetRegionCount: regions.filter((region) => region.targetRect)
|
||||
.length,
|
||||
scaleAdjustPromptStrategy: isScaleAdjustOperation
|
||||
@ -1129,7 +1135,21 @@ export async function POST(request: NextRequest) {
|
||||
protectedRegions: scaleAdjustCompositeRegions,
|
||||
sourceImageUrl,
|
||||
regions: regions.map((region) => region.rect),
|
||||
scalePercent: scalePercent as ScaleAdjustPercent,
|
||||
...(typeof scalePercent === "number"
|
||||
? { scalePercent: scalePercent as ScaleAdjustPercent }
|
||||
: {}),
|
||||
...(hasScaleAdjustTargetRects
|
||||
? {
|
||||
targetRegions: regions
|
||||
.map((region) => region.targetRect)
|
||||
.filter(
|
||||
(
|
||||
rect
|
||||
): rect is NonNullable<NormalizedLocalEditRegion["targetRect"]> =>
|
||||
Boolean(rect)
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
}
|
||||
: undefined,
|
||||
localEditComposite: isScaleAdjustOperation
|
||||
|
||||
@ -54,7 +54,8 @@ export interface CanvasScaleAdjustVerificationConfig {
|
||||
protectedRegions?: CanvasLocalEditCompositeRegion[];
|
||||
sourceImageUrl: string;
|
||||
regions: CanvasLocalEditCompositeRegion[];
|
||||
scalePercent: number;
|
||||
targetRegions?: CanvasLocalEditCompositeRegion[];
|
||||
scalePercent?: number;
|
||||
}
|
||||
|
||||
export interface CanvasGenerateImageRequest {
|
||||
@ -331,13 +332,23 @@ function normalizeScaleAdjustVerificationConfig(
|
||||
if (!value || typeof value !== "object") return null;
|
||||
const sourceImageUrl = String(value.sourceImageUrl || "").trim();
|
||||
if (!/^https?:\/\//i.test(sourceImageUrl)) return null;
|
||||
const scalePercent = Number(value.scalePercent);
|
||||
if (!Number.isFinite(scalePercent) || Math.abs(scalePercent) < 1) return null;
|
||||
if (!Array.isArray(value.regions) || value.regions.length === 0) return null;
|
||||
const rawScalePercent = Number(value.scalePercent);
|
||||
const scalePercent =
|
||||
Number.isFinite(rawScalePercent) && Math.abs(rawScalePercent) >= 1
|
||||
? rawScalePercent
|
||||
: undefined;
|
||||
|
||||
const regions = value.regions
|
||||
.map((region) => normalizeCanvasRegion(region))
|
||||
.filter((region): region is CanvasLocalEditCompositeRegion => Boolean(region));
|
||||
const targetRegions = Array.isArray((value as { targetRegions?: unknown[] }).targetRegions)
|
||||
? (value as { targetRegions?: unknown[] }).targetRegions
|
||||
?.map((region) => normalizeCanvasRegion(region))
|
||||
.filter((region): region is CanvasLocalEditCompositeRegion =>
|
||||
Boolean(region)
|
||||
) || []
|
||||
: [];
|
||||
const protectedRegions = Array.isArray(value.protectedRegions)
|
||||
? value.protectedRegions
|
||||
.map((region) => normalizeCanvasRegion(region))
|
||||
@ -346,11 +357,13 @@ function normalizeScaleAdjustVerificationConfig(
|
||||
)
|
||||
: [];
|
||||
|
||||
return regions.length > 0
|
||||
return regions.length > 0 &&
|
||||
(targetRegions.length > 0 || typeof scalePercent === "number")
|
||||
? {
|
||||
sourceImageUrl,
|
||||
regions,
|
||||
scalePercent,
|
||||
...(targetRegions.length > 0 ? { targetRegions } : {}),
|
||||
...(typeof scalePercent === "number" ? { scalePercent } : {}),
|
||||
...(protectedRegions.length > 0 ? { protectedRegions } : {}),
|
||||
}
|
||||
: null;
|
||||
@ -425,13 +438,32 @@ function getScaleAdjustProtectedRegions(
|
||||
expandCanvasRegion(region, SCALE_ADJUST_OUTSIDE_PROTECTED_MARGIN)
|
||||
);
|
||||
}
|
||||
return verification.regions.flatMap((region) => [
|
||||
expandCanvasRegion(region, SCALE_ADJUST_OUTSIDE_PROTECTED_MARGIN),
|
||||
expandCanvasRegion(
|
||||
scaleRegionAroundCenter(region, verification.scalePercent),
|
||||
SCALE_ADJUST_OUTSIDE_PROTECTED_MARGIN
|
||||
),
|
||||
]);
|
||||
if (verification.targetRegions?.length) {
|
||||
return verification.regions.flatMap((region, index) => {
|
||||
const targetRegion = verification.targetRegions?.[index];
|
||||
return targetRegion
|
||||
? [
|
||||
expandCanvasRegion(region, SCALE_ADJUST_OUTSIDE_PROTECTED_MARGIN),
|
||||
expandCanvasRegion(
|
||||
targetRegion,
|
||||
SCALE_ADJUST_OUTSIDE_PROTECTED_MARGIN
|
||||
),
|
||||
]
|
||||
: [expandCanvasRegion(region, SCALE_ADJUST_OUTSIDE_PROTECTED_MARGIN)];
|
||||
});
|
||||
}
|
||||
if (typeof verification.scalePercent === "number") {
|
||||
return verification.regions.flatMap((region) => [
|
||||
expandCanvasRegion(region, SCALE_ADJUST_OUTSIDE_PROTECTED_MARGIN),
|
||||
expandCanvasRegion(
|
||||
scaleRegionAroundCenter(region, verification.scalePercent as number),
|
||||
SCALE_ADJUST_OUTSIDE_PROTECTED_MARGIN
|
||||
),
|
||||
]);
|
||||
}
|
||||
return verification.regions.map((region) =>
|
||||
expandCanvasRegion(region, SCALE_ADJUST_OUTSIDE_PROTECTED_MARGIN)
|
||||
);
|
||||
}
|
||||
|
||||
function getScaleAdjustOutsideSampleRegions(
|
||||
@ -756,7 +788,8 @@ async function applyScaleAdjustVerificationToImages(params: {
|
||||
debugLabel,
|
||||
imageCount: images.length,
|
||||
regionCount: verification.regions.length,
|
||||
scalePercent: verification.scalePercent,
|
||||
scalePercent: verification.scalePercent ?? null,
|
||||
targetRegionCount: verification.targetRegions?.length || 0,
|
||||
});
|
||||
return images;
|
||||
}
|
||||
@ -810,7 +843,8 @@ async function applyScaleAdjustVerificationToImages(params: {
|
||||
protectedRegionCount: verification.protectedRegions?.length || 0,
|
||||
regionCount: verification.regions.length,
|
||||
regionDiffScores: diffScores.map((score) => Number(score.toFixed(2))),
|
||||
scalePercent: verification.scalePercent,
|
||||
scalePercent: verification.scalePercent ?? null,
|
||||
targetRegionCount: verification.targetRegions?.length || 0,
|
||||
});
|
||||
|
||||
if (outsideChanged) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user