fix: reject banded rmbg scale drafts

This commit is contained in:
134 2026-07-03 21:50:34 +08:00
parent 01e17361c8
commit 5ec43fa8a3

View File

@ -1709,6 +1709,83 @@ function getScaleAdjustAlphaBoundingBox(params: {
};
}
function thresholdScaleAdjustAlphaMask(params: {
maskBuffer: Buffer | Uint8Array;
threshold?: number;
}) {
const { maskBuffer, threshold = 96 } = params;
const output = new Uint8Array(maskBuffer.length);
for (let index = 0; index < maskBuffer.length; index += 1) {
output[index] = (maskBuffer[index] || 0) >= threshold ? 255 : 0;
}
return output;
}
function getScaleAdjustAlphaBandingStats(params: {
height: number;
maskBuffer: Buffer | Uint8Array;
threshold?: number;
width: number;
}) {
const { height, maskBuffer, threshold = 24, width } = params;
const mask = thresholdScaleAdjustAlphaMask({ maskBuffer, threshold });
const bbox = getScaleAdjustMaskBoundingBox({ height, mask, width });
if (!bbox) {
return {
activeRowRatio: 0,
bboxHeightRatio: 0,
bboxWidthRatio: 0,
detected: false,
transitionRatio: 0,
wideRowRatio: 0,
};
}
let activeRows = 0;
let transitions = 0;
let wideRows = 0;
let previousCoverage: number | null = null;
for (let y = bbox.top; y < bbox.top + bbox.height; y += 1) {
let rowCount = 0;
for (let x = bbox.left; x < bbox.left + bbox.width; x += 1) {
if (mask[y * width + x] !== 0) rowCount += 1;
}
const coverage = rowCount / Math.max(1, bbox.width);
if (coverage >= 0.08) activeRows += 1;
if (coverage >= 0.45) wideRows += 1;
if (
previousCoverage !== null &&
Math.abs(coverage - previousCoverage) >= 0.28
) {
transitions += 1;
}
previousCoverage = coverage;
}
const bboxHeightRatio = bbox.height / Math.max(1, height);
const bboxWidthRatio = bbox.width / Math.max(1, width);
const activeRowRatio = activeRows / Math.max(1, bbox.height);
const transitionRatio = transitions / Math.max(1, bbox.height - 1);
const wideRowRatio = wideRows / Math.max(1, bbox.height);
const detected =
(bboxWidthRatio >= 0.38 &&
bboxHeightRatio >= 0.22 &&
wideRowRatio >= 0.18 &&
transitionRatio >= 0.12) ||
(bboxWidthRatio >= 0.55 &&
activeRowRatio >= 0.45 &&
transitionRatio >= 0.09);
return {
activeRowRatio: Number(activeRowRatio.toFixed(3)),
bboxHeightRatio: Number(bboxHeightRatio.toFixed(3)),
bboxWidthRatio: Number(bboxWidthRatio.toFixed(3)),
detected,
transitionRatio: Number(transitionRatio.toFixed(3)),
wideRowRatio: Number(wideRowRatio.toFixed(3)),
};
}
async function cleanScaleAdjustAlphaChannel(params: {
height: number;
referenceRawRgb?: Buffer | null;
@ -1883,14 +1960,45 @@ async function buildScaleAdjustRmbgStructureLockedComposite(params: {
targetWidth: placement.sourceBox.width,
width: cutoutWidth,
});
const alphaBandingStats = getScaleAdjustAlphaBandingStats({
height: placement.sourceBox.height,
maskBuffer: sourceMaskBuffer,
width: placement.sourceBox.width,
});
if (alphaBandingStats.detected) {
console.warn(
"[Canvas Local Edit] Scale adjust RMBG alpha banding detected; fallback to pixel mask:",
alphaBandingStats
);
return null;
}
const background = estimateScaleAdjustBoxBackground({
box: placement.sourceBox,
raw,
width: sourceWidth,
});
const highConfidenceSourceMask = thresholdScaleAdjustAlphaMask({
maskBuffer: sourceMaskBuffer,
threshold: 96,
});
const filteredSourceMask = filterScaleAdjustStructureComponents({
box: placement.sourceBox,
mask: highConfidenceSourceMask,
});
const filteredSourceBbox = getScaleAdjustMaskBoundingBox({
height: placement.sourceBox.height,
mask: filteredSourceMask,
width: placement.sourceBox.width,
});
const sourceWashBaseMask = filteredSourceBbox
? solidifyScaleAdjustStructureMask({
box: placement.sourceBox,
mask: filteredSourceMask,
})
: highConfidenceSourceMask;
const sourceWashMask = dilateScaleAdjustStructureMask({
height: placement.sourceBox.height,
mask: new Uint8Array(sourceMaskBuffer),
mask: sourceWashBaseMask,
radius: Math.max(
2,
Math.round(
@ -1977,6 +2085,7 @@ async function buildScaleAdjustRmbgStructureLockedComposite(params: {
placedHeight,
placedWidth,
sameSizeCutout,
alphaBandingStats,
sourceHeight: placement.sourceBox.height,
sourceWidth: placement.sourceBox.width,
});