fix: verify scale adjust footprint shrink
This commit is contained in:
parent
0515fd3e46
commit
1ae9b5a89b
@ -170,6 +170,8 @@ const SCALE_ADJUST_DIFF_SAMPLE_SIZE = 112;
|
||||
const SCALE_ADJUST_FALLBACK_MASK_DISTANCE_THRESHOLD = 42;
|
||||
const SCALE_ADJUST_FALLBACK_MASK_LUMA_GAP = 18;
|
||||
const SCALE_ADJUST_FALLBACK_INPAINT_ITERATIONS = 180;
|
||||
const SCALE_ADJUST_SHRINK_OUTSIDE_FOOTPRINT_MAX_RATIO = 0.72;
|
||||
const SCALE_ADJUST_SHRINK_MIN_SOURCE_OUTSIDE_PIXELS = 60;
|
||||
|
||||
const SUPPORTED_CANVAS_PROVIDER_TYPES = new Set([
|
||||
"gemini",
|
||||
@ -815,6 +817,109 @@ function isScaleAdjustOutsidePixelChanged(params: {
|
||||
);
|
||||
}
|
||||
|
||||
async function getScaleAdjustFootprintChangeStats(params: {
|
||||
generatedBuffer: Buffer;
|
||||
sourceBuffer: Buffer;
|
||||
sourceWidth: number;
|
||||
sourceHeight: number;
|
||||
verification: CanvasScaleAdjustVerificationConfig;
|
||||
}) {
|
||||
const { generatedBuffer, sourceBuffer, sourceHeight, sourceWidth, verification } =
|
||||
params;
|
||||
const normalizeToRaw = (buffer: Buffer) =>
|
||||
sharp(buffer, { failOn: "none" })
|
||||
.rotate()
|
||||
.resize(sourceWidth, sourceHeight, {
|
||||
fit: "cover",
|
||||
position: "center",
|
||||
})
|
||||
.removeAlpha()
|
||||
.toColorspace("srgb")
|
||||
.raw()
|
||||
.toBuffer();
|
||||
const [sourceRaw, generatedRaw] = await Promise.all([
|
||||
normalizeToRaw(sourceBuffer),
|
||||
normalizeToRaw(generatedBuffer),
|
||||
]);
|
||||
const shrinkStats: Array<{
|
||||
generatedOutsidePixels: number;
|
||||
outsideRatio: number;
|
||||
passed: boolean;
|
||||
regionIndex: number;
|
||||
sourceOutsidePixels: number;
|
||||
}> = [];
|
||||
|
||||
for (const [index, region] of verification.regions.entries()) {
|
||||
const targetRegion =
|
||||
verification.targetRegions?.[index] ||
|
||||
(typeof verification.scalePercent === "number"
|
||||
? scaleRegionAroundCenter(region, verification.scalePercent)
|
||||
: null);
|
||||
if (!targetRegion) continue;
|
||||
const widthFactor = region.width > 0 ? targetRegion.width / region.width : 1;
|
||||
const heightFactor = region.height > 0 ? targetRegion.height / region.height : 1;
|
||||
const isShrink = widthFactor < 0.92 || heightFactor < 0.92;
|
||||
if (!isShrink) continue;
|
||||
|
||||
const sourceBox = rectToPixelBox({
|
||||
height: sourceHeight,
|
||||
region,
|
||||
width: sourceWidth,
|
||||
});
|
||||
const targetBox = rectToPixelBox({
|
||||
height: sourceHeight,
|
||||
region: targetRegion,
|
||||
width: sourceWidth,
|
||||
});
|
||||
const sourceMask = buildScaleAdjustFallbackSubjectMask({
|
||||
box: sourceBox,
|
||||
raw: sourceRaw,
|
||||
width: sourceWidth,
|
||||
});
|
||||
const generatedMask = buildScaleAdjustFallbackSubjectMask({
|
||||
box: sourceBox,
|
||||
raw: generatedRaw,
|
||||
width: sourceWidth,
|
||||
});
|
||||
let sourceOutsidePixels = 0;
|
||||
let generatedOutsidePixels = 0;
|
||||
for (let localY = 0; localY < sourceBox.height; localY += 1) {
|
||||
const y = sourceBox.top + localY;
|
||||
for (let localX = 0; localX < sourceBox.width; localX += 1) {
|
||||
const x = sourceBox.left + localX;
|
||||
const insideTarget =
|
||||
x >= targetBox.left &&
|
||||
x < targetBox.left + targetBox.width &&
|
||||
y >= targetBox.top &&
|
||||
y < targetBox.top + targetBox.height;
|
||||
if (insideTarget) continue;
|
||||
const maskIndex = localY * sourceBox.width + localX;
|
||||
if (sourceMask[maskIndex] > 0) sourceOutsidePixels += 1;
|
||||
if (generatedMask[maskIndex] > 0) generatedOutsidePixels += 1;
|
||||
}
|
||||
}
|
||||
const outsideRatio =
|
||||
sourceOutsidePixels > 0 ? generatedOutsidePixels / sourceOutsidePixels : 0;
|
||||
const passed =
|
||||
sourceOutsidePixels < SCALE_ADJUST_SHRINK_MIN_SOURCE_OUTSIDE_PIXELS ||
|
||||
outsideRatio <= SCALE_ADJUST_SHRINK_OUTSIDE_FOOTPRINT_MAX_RATIO;
|
||||
shrinkStats.push({
|
||||
generatedOutsidePixels,
|
||||
outsideRatio,
|
||||
passed,
|
||||
regionIndex: index,
|
||||
sourceOutsidePixels,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
failedRegionCount: shrinkStats.filter((item) => !item.passed).length,
|
||||
passed: shrinkStats.every((item) => item.passed),
|
||||
shrinkRegionCount: shrinkStats.length,
|
||||
shrinkStats,
|
||||
};
|
||||
}
|
||||
|
||||
async function loadScaleAdjustSourceImage(
|
||||
verification: CanvasScaleAdjustVerificationConfig
|
||||
) {
|
||||
@ -1038,6 +1143,7 @@ async function applyScaleAdjustVerificationToImages(params: {
|
||||
changedInside: boolean;
|
||||
cleanedImage?: ParsedImage;
|
||||
diffScores: number[];
|
||||
footprintStats: Awaited<ReturnType<typeof getScaleAdjustFootprintChangeStats>>;
|
||||
image: ParsedImage;
|
||||
index: number;
|
||||
outsideChanged: boolean;
|
||||
@ -1050,7 +1156,13 @@ async function applyScaleAdjustVerificationToImages(params: {
|
||||
const generatedUrl = image.originalUrl || image.thumbnailUrl;
|
||||
const generatedBuffer = await fetchImageBufferFromUrl(generatedUrl);
|
||||
const changeRegions = getScaleAdjustEffectiveChangeRegions(verification);
|
||||
const [diffScores, changeDiffScores, outsideDiffScores, outsidePixelStats] = await Promise.all([
|
||||
const [
|
||||
diffScores,
|
||||
changeDiffScores,
|
||||
outsideDiffScores,
|
||||
outsidePixelStats,
|
||||
footprintStats,
|
||||
] = await Promise.all([
|
||||
getScaleAdjustDiffScores({
|
||||
generatedBuffer,
|
||||
regions: verification.regions,
|
||||
@ -1079,10 +1191,18 @@ async function applyScaleAdjustVerificationToImages(params: {
|
||||
sourceWidth: sourceImage.sourceWidth,
|
||||
verification,
|
||||
}),
|
||||
getScaleAdjustFootprintChangeStats({
|
||||
generatedBuffer,
|
||||
sourceBuffer: sourceImage.sourceBuffer,
|
||||
sourceHeight: sourceImage.sourceHeight,
|
||||
sourceWidth: sourceImage.sourceWidth,
|
||||
verification,
|
||||
}),
|
||||
]);
|
||||
const changedInside = changeDiffScores.some(
|
||||
const visuallyChangedInside = changeDiffScores.some(
|
||||
(score) => score >= SCALE_ADJUST_VISUAL_DIFF_THRESHOLD
|
||||
);
|
||||
const changedInside = visuallyChangedInside && footprintStats.passed;
|
||||
const outsideChanged =
|
||||
isScaleAdjustOutsideChanged(outsideDiffScores) ||
|
||||
isScaleAdjustOutsidePixelChanged(outsidePixelStats);
|
||||
@ -1090,6 +1210,18 @@ async function applyScaleAdjustVerificationToImages(params: {
|
||||
console.info("[Canvas Generate Image] Scale adjust result guard:", {
|
||||
debugLabel,
|
||||
changedInside,
|
||||
footprintChanged: footprintStats.passed,
|
||||
footprintStats: {
|
||||
failedRegionCount: footprintStats.failedRegionCount,
|
||||
shrinkRegionCount: footprintStats.shrinkRegionCount,
|
||||
shrinkStats: footprintStats.shrinkStats.map((item) => ({
|
||||
generatedOutsidePixels: item.generatedOutsidePixels,
|
||||
outsideRatio: Number(item.outsideRatio.toFixed(3)),
|
||||
passed: item.passed,
|
||||
regionIndex: item.regionIndex,
|
||||
sourceOutsidePixels: item.sourceOutsidePixels,
|
||||
})),
|
||||
},
|
||||
imageIndex: index,
|
||||
outsideChanged,
|
||||
outsideDiffScores: outsideDiffScores.map((score) => Number(score.toFixed(2))),
|
||||
@ -1120,6 +1252,18 @@ async function applyScaleAdjustVerificationToImages(params: {
|
||||
imageIndex: index,
|
||||
reasons: guardWarnings,
|
||||
changedInside,
|
||||
footprintChanged: footprintStats.passed,
|
||||
footprintStats: {
|
||||
failedRegionCount: footprintStats.failedRegionCount,
|
||||
shrinkRegionCount: footprintStats.shrinkRegionCount,
|
||||
shrinkStats: footprintStats.shrinkStats.map((item) => ({
|
||||
generatedOutsidePixels: item.generatedOutsidePixels,
|
||||
outsideRatio: Number(item.outsideRatio.toFixed(3)),
|
||||
passed: item.passed,
|
||||
regionIndex: item.regionIndex,
|
||||
sourceOutsidePixels: item.sourceOutsidePixels,
|
||||
})),
|
||||
},
|
||||
outsideChanged,
|
||||
outsideDiffScores: outsideDiffScores.map((score) => Number(score.toFixed(2))),
|
||||
outsidePixelStats: {
|
||||
@ -1155,6 +1299,7 @@ async function applyScaleAdjustVerificationToImages(params: {
|
||||
changedInside,
|
||||
cleanedImage,
|
||||
diffScores,
|
||||
footprintStats,
|
||||
image,
|
||||
index,
|
||||
outsideChanged,
|
||||
@ -1176,6 +1321,7 @@ async function applyScaleAdjustVerificationToImages(params: {
|
||||
changedInside,
|
||||
cleanedImage,
|
||||
diffScores,
|
||||
footprintStats,
|
||||
image,
|
||||
index,
|
||||
outsideChanged,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user