fix: disable scale adjust synthetic fallback
This commit is contained in:
parent
4f06ff7151
commit
eac5bb9296
@ -87,6 +87,28 @@ export interface CanvasGenerateImageResult {
|
|||||||
prompt: string;
|
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_PADDING_RATIO = 0.14;
|
||||||
const LOCAL_EDIT_COMPOSITE_MIN_PADDING_RATIO = 0.012;
|
const LOCAL_EDIT_COMPOSITE_MIN_PADDING_RATIO = 0.012;
|
||||||
const LOCAL_EDIT_COMPOSITE_MAX_PADDING_RATIO = 0.06;
|
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_DIFF_MIN_CHANGED_RATIO = 0.45;
|
||||||
const SCALE_ADJUST_OUTSIDE_PROTECTED_MARGIN = 0.035;
|
const SCALE_ADJUST_OUTSIDE_PROTECTED_MARGIN = 0.035;
|
||||||
const SCALE_ADJUST_DIFF_SAMPLE_SIZE = 112;
|
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([
|
const SUPPORTED_CANVAS_PROVIDER_TYPES = new Set([
|
||||||
"gemini",
|
"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: {
|
async function getScaleAdjustRegionSample(params: {
|
||||||
buffer: Buffer;
|
buffer: Buffer;
|
||||||
sourceWidth: number;
|
sourceWidth: number;
|
||||||
@ -539,96 +530,6 @@ function isScaleAdjustOutsideChanged(scores: number[]) {
|
|||||||
return changedCount >= requiredCount;
|
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(
|
async function loadScaleAdjustSourceImage(
|
||||||
verification: CanvasScaleAdjustVerificationConfig
|
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: {
|
async function applyScaleAdjustVerificationToImages(params: {
|
||||||
images: ParsedImage[];
|
images: ParsedImage[];
|
||||||
verification: CanvasScaleAdjustVerificationConfig | null;
|
verification: CanvasScaleAdjustVerificationConfig | null;
|
||||||
@ -760,22 +606,29 @@ async function applyScaleAdjustVerificationToImages(params: {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fallbackImage = await createScaleAdjustFallbackUpload({
|
const rejection = new ScaleAdjustOutputRejectedError({
|
||||||
debugLabel,
|
|
||||||
diffScores,
|
diffScores,
|
||||||
imageIndex: index + 1,
|
|
||||||
outsideDiffScores,
|
outsideDiffScores,
|
||||||
reason: outsideChanged ? "outside-frame-changed" : "unchanged-output",
|
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;
|
return checkedImages;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof ScaleAdjustOutputRejectedError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
console.warn("[Canvas Generate Image] Scale adjust verification skipped:", {
|
console.warn("[Canvas Generate Image] Scale adjust verification skipped:", {
|
||||||
debugLabel,
|
debugLabel,
|
||||||
error: error instanceof Error ? error.message : String(error),
|
error: error instanceof Error ? error.message : String(error),
|
||||||
@ -1691,8 +1544,11 @@ export async function generateCanvasImage(
|
|||||||
].filter(Boolean))
|
].filter(Boolean))
|
||||||
);
|
);
|
||||||
const allowFailover = request.allowFailover !== false;
|
const allowFailover = request.allowFailover !== false;
|
||||||
|
const scaleAdjustRejected =
|
||||||
|
caughtError instanceof ScaleAdjustOutputRejectedError;
|
||||||
const shouldFailover =
|
const shouldFailover =
|
||||||
shouldFailoverAiModelError(caughtError) ||
|
shouldFailoverAiModelError(caughtError) ||
|
||||||
|
scaleAdjustRejected ||
|
||||||
(Boolean(debugMeta?.detailFixedModelApplied) && Boolean(caughtError));
|
(Boolean(debugMeta?.detailFixedModelApplied) && Boolean(caughtError));
|
||||||
const fallbackModel = allowFailover && shouldFailover
|
const fallbackModel = allowFailover && shouldFailover
|
||||||
? findCanvasFailoverModel({
|
? 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;
|
throw caughtError;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user