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