fix: disable scale adjust synthetic fallback
This commit is contained in:
parent
4f06ff7151
commit
eac5bb9296
@ -87,6 +87,28 @@ export interface CanvasGenerateImageResult {
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
class ScaleAdjustOutputRejectedError extends Error {
|
||||
reason: string;
|
||||
diffScores?: number[];
|
||||
outsideDiffScores?: number[];
|
||||
|
||||
constructor(params: {
|
||||
reason: string;
|
||||
diffScores?: number[];
|
||||
outsideDiffScores?: number[];
|
||||
}) {
|
||||
const message =
|
||||
params.reason === "outside-frame-changed"
|
||||
? "比例调整结果改变了框选区域外的画面,已拒绝该模型结果"
|
||||
: "比例调整结果没有产生有效变化,已拒绝该模型结果";
|
||||
super(message);
|
||||
this.name = "ScaleAdjustOutputRejectedError";
|
||||
this.reason = params.reason;
|
||||
this.diffScores = params.diffScores;
|
||||
this.outsideDiffScores = params.outsideDiffScores;
|
||||
}
|
||||
}
|
||||
|
||||
const LOCAL_EDIT_COMPOSITE_PADDING_RATIO = 0.14;
|
||||
const LOCAL_EDIT_COMPOSITE_MIN_PADDING_RATIO = 0.012;
|
||||
const LOCAL_EDIT_COMPOSITE_MAX_PADDING_RATIO = 0.06;
|
||||
@ -97,8 +119,6 @@ const SCALE_ADJUST_OUTSIDE_DIFF_THRESHOLD = 18;
|
||||
const SCALE_ADJUST_OUTSIDE_DIFF_MIN_CHANGED_RATIO = 0.45;
|
||||
const SCALE_ADJUST_OUTSIDE_PROTECTED_MARGIN = 0.035;
|
||||
const SCALE_ADJUST_DIFF_SAMPLE_SIZE = 112;
|
||||
const SCALE_ADJUST_FALLBACK_FEATHER_MIN_PX = 8;
|
||||
const SCALE_ADJUST_FALLBACK_FEATHER_MAX_PX = 36;
|
||||
|
||||
const SUPPORTED_CANVAS_PROVIDER_TYPES = new Set([
|
||||
"gemini",
|
||||
@ -398,35 +418,6 @@ function rectToPixelBox(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function buildScaleAdjustSoftMaskSvg(width: number, height: number) {
|
||||
const blur = Math.max(
|
||||
SCALE_ADJUST_FALLBACK_FEATHER_MIN_PX,
|
||||
Math.min(
|
||||
SCALE_ADJUST_FALLBACK_FEATHER_MAX_PX,
|
||||
Math.round(Math.min(width, height) * 0.035)
|
||||
)
|
||||
);
|
||||
const inset = Math.min(
|
||||
Math.floor(Math.min(width, height) / 4),
|
||||
Math.max(1, Math.round(blur * 0.75))
|
||||
);
|
||||
const rectWidth = Math.max(1, width - inset * 2);
|
||||
const rectHeight = Math.max(1, height - inset * 2);
|
||||
|
||||
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-soft-mask" x="-30%" y="-30%" width="160%" height="160%" color-interpolation-filters="sRGB">
|
||||
<feGaussianBlur stdDeviation="${blur}" />
|
||||
</filter>
|
||||
</defs>
|
||||
<rect x="0" y="0" width="${width}" height="${height}" fill="rgba(0,0,0,0)" />
|
||||
<rect x="${inset}" y="${inset}" width="${rectWidth}" height="${rectHeight}" rx="${Math.round(blur * 0.9)}" fill="#ffffff" filter="url(#scale-adjust-soft-mask)" />
|
||||
<rect x="${Math.max(0, Math.round(inset / 2))}" y="${Math.max(0, Math.round(inset / 2))}" width="${Math.max(1, width - inset)}" height="${Math.max(1, height - inset)}" rx="${Math.round(blur * 0.5)}" fill="#ffffff" opacity="0.92" />
|
||||
</svg>
|
||||
`);
|
||||
}
|
||||
|
||||
async function getScaleAdjustRegionSample(params: {
|
||||
buffer: Buffer;
|
||||
sourceWidth: number;
|
||||
@ -539,96 +530,6 @@ function isScaleAdjustOutsideChanged(scores: number[]) {
|
||||
return changedCount >= requiredCount;
|
||||
}
|
||||
|
||||
async function buildScaleAdjustFallbackImage(params: {
|
||||
sourceBuffer: Buffer;
|
||||
sourceWidth: number;
|
||||
sourceHeight: number;
|
||||
verification: CanvasScaleAdjustVerificationConfig;
|
||||
}) {
|
||||
let workingBuffer = await sharp(params.sourceBuffer, { failOn: "none" })
|
||||
.rotate()
|
||||
.resize(params.sourceWidth, params.sourceHeight, {
|
||||
fit: "cover",
|
||||
position: "center",
|
||||
})
|
||||
.png({ compressionLevel: 6 })
|
||||
.toBuffer();
|
||||
|
||||
for (const region of params.verification.regions) {
|
||||
const sourceBox = rectToPixelBox({
|
||||
region,
|
||||
width: params.sourceWidth,
|
||||
height: params.sourceHeight,
|
||||
});
|
||||
const targetBox = rectToPixelBox({
|
||||
region: scaleRegionAroundCenter(region, params.verification.scalePercent),
|
||||
width: params.sourceWidth,
|
||||
height: params.sourceHeight,
|
||||
});
|
||||
const targetMask = buildScaleAdjustSoftMaskSvg(
|
||||
targetBox.width,
|
||||
targetBox.height
|
||||
);
|
||||
const scaledPatch = await sharp(params.sourceBuffer, { failOn: "none" })
|
||||
.rotate()
|
||||
.resize(params.sourceWidth, params.sourceHeight, {
|
||||
fit: "cover",
|
||||
position: "center",
|
||||
})
|
||||
.extract(sourceBox)
|
||||
.resize(targetBox.width, targetBox.height, { fit: "fill" })
|
||||
.ensureAlpha()
|
||||
.composite([{ input: targetMask, blend: "dest-in" }])
|
||||
.png({ compressionLevel: 6 })
|
||||
.toBuffer();
|
||||
|
||||
const composites: sharp.OverlayOptions[] = [];
|
||||
if (params.verification.scalePercent < 0) {
|
||||
const fillMask = buildScaleAdjustSoftMaskSvg(
|
||||
sourceBox.width,
|
||||
sourceBox.height
|
||||
);
|
||||
const fillPatch = await sharp(params.sourceBuffer, { failOn: "none" })
|
||||
.rotate()
|
||||
.resize(params.sourceWidth, params.sourceHeight, {
|
||||
fit: "cover",
|
||||
position: "center",
|
||||
})
|
||||
.extract(sourceBox)
|
||||
.blur(
|
||||
Math.max(
|
||||
8,
|
||||
Math.min(34, Math.round(Math.min(sourceBox.width, sourceBox.height) * 0.08))
|
||||
)
|
||||
)
|
||||
.modulate({ brightness: 1.02, saturation: 0.98 })
|
||||
.ensureAlpha()
|
||||
.composite([{ input: fillMask, blend: "dest-in" }])
|
||||
.png({ compressionLevel: 6 })
|
||||
.toBuffer();
|
||||
composites.push({
|
||||
input: fillPatch,
|
||||
left: sourceBox.left,
|
||||
top: sourceBox.top,
|
||||
});
|
||||
}
|
||||
|
||||
composites.push({
|
||||
input: scaledPatch,
|
||||
left: targetBox.left,
|
||||
top: targetBox.top,
|
||||
});
|
||||
|
||||
workingBuffer = await sharp(workingBuffer, { failOn: "none" })
|
||||
.ensureAlpha()
|
||||
.composite(composites)
|
||||
.png({ compressionLevel: 6 })
|
||||
.toBuffer();
|
||||
}
|
||||
|
||||
return workingBuffer;
|
||||
}
|
||||
|
||||
async function loadScaleAdjustSourceImage(
|
||||
verification: CanvasScaleAdjustVerificationConfig
|
||||
) {
|
||||
@ -653,61 +554,6 @@ async function loadScaleAdjustSourceImage(
|
||||
};
|
||||
}
|
||||
|
||||
async function createScaleAdjustFallbackUpload(params: {
|
||||
sourceBuffer: Buffer;
|
||||
sourceWidth: number;
|
||||
sourceHeight: number;
|
||||
verification: CanvasScaleAdjustVerificationConfig;
|
||||
debugLabel: string;
|
||||
imageIndex?: number;
|
||||
reason: string;
|
||||
diffScores?: number[];
|
||||
outsideDiffScores?: number[];
|
||||
}) {
|
||||
const fallbackBuffer = await buildScaleAdjustFallbackImage({
|
||||
sourceBuffer: params.sourceBuffer,
|
||||
sourceHeight: params.sourceHeight,
|
||||
sourceWidth: params.sourceWidth,
|
||||
verification: params.verification,
|
||||
});
|
||||
const thumbnailBuffer = await sharp(fallbackBuffer, { failOn: "none" })
|
||||
.resize(512, 512, { fit: "inside", withoutEnlargement: true })
|
||||
.png({ compressionLevel: 6 })
|
||||
.toBuffer();
|
||||
const suffix = `${Date.now()}-${params.imageIndex || 1}-${Math.random()
|
||||
.toString(36)
|
||||
.slice(2, 8)}`;
|
||||
const [originalUpload, thumbnailUpload] = await Promise.all([
|
||||
uploadFile(
|
||||
`canvas-scale-adjust-fallback-${suffix}.png`,
|
||||
bufferToArrayBuffer(fallbackBuffer)
|
||||
),
|
||||
uploadFile(
|
||||
`canvas-scale-adjust-fallback-thumb-${suffix}.png`,
|
||||
bufferToArrayBuffer(thumbnailBuffer)
|
||||
),
|
||||
]);
|
||||
|
||||
console.warn("[Canvas Generate Image] Scale adjust fallback applied:", {
|
||||
debugLabel: params.debugLabel,
|
||||
diffScores: params.diffScores?.map((score) => Math.round(score * 10) / 10),
|
||||
imageIndex: params.imageIndex,
|
||||
outsideDiffScores: params.outsideDiffScores?.map(
|
||||
(score) => Math.round(score * 10) / 10
|
||||
),
|
||||
reason: params.reason,
|
||||
regionCount: params.verification.regions.length,
|
||||
scalePercent: params.verification.scalePercent,
|
||||
sourceHeight: params.sourceHeight,
|
||||
sourceWidth: params.sourceWidth,
|
||||
});
|
||||
|
||||
return {
|
||||
originalUrl: originalUpload.url,
|
||||
thumbnailUrl: thumbnailUpload.url,
|
||||
};
|
||||
}
|
||||
|
||||
async function applyScaleAdjustVerificationToImages(params: {
|
||||
images: ParsedImage[];
|
||||
verification: CanvasScaleAdjustVerificationConfig | null;
|
||||
@ -760,22 +606,29 @@ async function applyScaleAdjustVerificationToImages(params: {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fallbackImage = await createScaleAdjustFallbackUpload({
|
||||
debugLabel,
|
||||
const rejection = new ScaleAdjustOutputRejectedError({
|
||||
diffScores,
|
||||
imageIndex: index + 1,
|
||||
outsideDiffScores,
|
||||
reason: outsideChanged ? "outside-frame-changed" : "unchanged-output",
|
||||
sourceBuffer,
|
||||
sourceHeight,
|
||||
sourceWidth,
|
||||
verification,
|
||||
});
|
||||
checkedImages.push(fallbackImage);
|
||||
console.warn("[Canvas Generate Image] Scale adjust output rejected:", {
|
||||
debugLabel,
|
||||
diffScores: diffScores.map((score) => Math.round(score * 10) / 10),
|
||||
imageIndex: index + 1,
|
||||
outsideDiffScores: outsideDiffScores.map(
|
||||
(score) => Math.round(score * 10) / 10
|
||||
),
|
||||
reason: rejection.reason,
|
||||
scalePercent: verification.scalePercent,
|
||||
});
|
||||
throw rejection;
|
||||
}
|
||||
|
||||
return checkedImages;
|
||||
} catch (error) {
|
||||
if (error instanceof ScaleAdjustOutputRejectedError) {
|
||||
throw error;
|
||||
}
|
||||
console.warn("[Canvas Generate Image] Scale adjust verification skipped:", {
|
||||
debugLabel,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
@ -1691,8 +1544,11 @@ export async function generateCanvasImage(
|
||||
].filter(Boolean))
|
||||
);
|
||||
const allowFailover = request.allowFailover !== false;
|
||||
const scaleAdjustRejected =
|
||||
caughtError instanceof ScaleAdjustOutputRejectedError;
|
||||
const shouldFailover =
|
||||
shouldFailoverAiModelError(caughtError) ||
|
||||
scaleAdjustRejected ||
|
||||
(Boolean(debugMeta?.detailFixedModelApplied) && Boolean(caughtError));
|
||||
const fallbackModel = allowFailover && shouldFailover
|
||||
? findCanvasFailoverModel({
|
||||
@ -1726,56 +1582,5 @@ export async function generateCanvasImage(
|
||||
});
|
||||
}
|
||||
|
||||
if (scaleAdjustVerification) {
|
||||
try {
|
||||
const sourceImage = await loadScaleAdjustSourceImage(scaleAdjustVerification);
|
||||
if (sourceImage) {
|
||||
const fallbackImage = await createScaleAdjustFallbackUpload({
|
||||
debugLabel,
|
||||
imageIndex: 1,
|
||||
reason: allowFailover ? "provider-error-after-failover-exhausted" : "provider-error",
|
||||
sourceBuffer: sourceImage.sourceBuffer,
|
||||
sourceHeight: sourceImage.sourceHeight,
|
||||
sourceWidth: sourceImage.sourceWidth,
|
||||
verification: scaleAdjustVerification,
|
||||
});
|
||||
|
||||
recordCanvasImageModelExecutionDuration(
|
||||
resolvedModelKey,
|
||||
Date.now() - startedAt
|
||||
);
|
||||
|
||||
console.warn("[Canvas Generate Image] Scale adjust provider failed; returned deterministic fallback:", {
|
||||
debugLabel,
|
||||
elapsedMs: Date.now() - startedAt,
|
||||
providerType: provider.type,
|
||||
selectedModelRef: selectedModel.model.id,
|
||||
selectedModelId: selectedModel.model.modelId,
|
||||
error: caughtError instanceof Error ? caughtError.message : String(caughtError),
|
||||
...debugMeta,
|
||||
});
|
||||
|
||||
return {
|
||||
images: [fallbackImage],
|
||||
model: selectedModel.model.displayName,
|
||||
modelRef: selectedModel.model.id,
|
||||
prompt: resultPrompt,
|
||||
};
|
||||
}
|
||||
} catch (fallbackError) {
|
||||
console.warn("[Canvas Generate Image] Scale adjust provider-error fallback failed:", {
|
||||
debugLabel,
|
||||
error:
|
||||
fallbackError instanceof Error
|
||||
? fallbackError.message
|
||||
: String(fallbackError),
|
||||
originalError:
|
||||
caughtError instanceof Error ? caughtError.message : String(caughtError),
|
||||
regionCount: scaleAdjustVerification.regions.length,
|
||||
scalePercent: scaleAdjustVerification.scalePercent,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
throw caughtError;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user