fix: filter cutout matte artifacts

This commit is contained in:
134 2026-07-03 20:20:54 +08:00
parent 2875b02d2a
commit 8ecece64e1

View File

@ -111,8 +111,7 @@ const SCALE_ADJUST_STRUCTURE_LOCK_MIN_MASK_RATIO = 0.006;
const SCALE_ADJUST_STRUCTURE_LOCK_COMPONENT_MIN_RATIO = 0.00018;
const SCALE_ADJUST_RMBG_MIN_ALPHA_RATIO = 0.012;
const SCALE_ADJUST_RMBG_MAX_SOLID_ALPHA_RATIO = 0.92;
const SCALE_ADJUST_RMBG_ALPHA_CLEAN_THRESHOLD = 96;
const SCALE_ADJUST_RMBG_ALPHA_MEDIAN_SIZE = 5;
const SCALE_ADJUST_RMBG_ALPHA_CLEAN_THRESHOLD = 42;
const MIN_REGION_EDGE_PX = 24;
const SCALE_ADJUST_OPERATION = "scale-adjust";
type LocalEditOperation = "local-edit" | typeof SCALE_ADJUST_OPERATION;
@ -1712,36 +1711,48 @@ function getScaleAdjustAlphaBoundingBox(params: {
async function cleanScaleAdjustAlphaChannel(params: {
height: number;
referenceRawRgb?: Buffer | null;
rawRgba: Buffer;
threshold?: number;
width: number;
}) {
const {
height,
referenceRawRgb = null,
rawRgba,
threshold = SCALE_ADJUST_RMBG_ALPHA_CLEAN_THRESHOLD,
width,
} = params;
const pixelCount = Math.floor(rawRgba.length / 4);
const initialAlpha = Buffer.alloc(pixelCount);
for (let index = 0; index < pixelCount; index += 1) {
const sourceAlpha = rawRgba[index * 4 + 3] || 0;
initialAlpha[index] = sourceAlpha >= threshold ? 255 : 0;
}
const alphaData = await sharp(initialAlpha, {
raw: { channels: 1, height, width },
})
.median(SCALE_ADJUST_RMBG_ALPHA_MEDIAN_SIZE)
.threshold(128)
.raw()
.toBuffer();
const cleanedRaw = Buffer.from(rawRgba);
const cleanedAlpha = Buffer.alloc(pixelCount);
const range = Math.max(1, 255 - threshold);
const hasReference =
referenceRawRgb && referenceRawRgb.length >= pixelCount * 3;
for (let index = 0; index < pixelCount; index += 1) {
const colorIndex = index * 4;
const alphaIndex = index * 4 + 3;
const nextAlpha = alphaData[index] >= 128 ? 255 : 0;
const sourceAlpha = rawRgba[alphaIndex] || 0;
let matteArtifact = false;
if (hasReference && sourceAlpha >= threshold) {
const r = rawRgba[colorIndex] || 0;
const g = rawRgba[colorIndex + 1] || 0;
const b = rawRgba[colorIndex + 2] || 0;
const luma = getScaleAdjustPixelLuma(r, g, b);
const channelSpread = Math.max(r, g, b) - Math.min(r, g, b);
const refIndex = index * 3;
const refLuma = getScaleAdjustPixelLuma(
referenceRawRgb?.[refIndex] || 0,
referenceRawRgb?.[refIndex + 1] || 0,
referenceRawRgb?.[refIndex + 2] || 0
);
matteArtifact = luma <= 18 && channelSpread <= 18 && refLuma >= 42;
}
const nextAlpha =
sourceAlpha < threshold || matteArtifact
? 0
: Math.max(0, Math.min(255, Math.round(((sourceAlpha - threshold) / range) * 255)));
cleanedRaw[alphaIndex] = nextAlpha;
cleanedAlpha[index] = nextAlpha;
}
@ -1818,6 +1829,16 @@ async function buildScaleAdjustRmbgStructureLockedComposite(params: {
const cutoutWidth = Math.round(Number(cutoutMetadata.width || 0));
const cutoutHeight = Math.round(Number(cutoutMetadata.height || 0));
if (cutoutWidth <= 0 || cutoutHeight <= 0) return null;
const sameSizeCutout =
Math.abs(cutoutWidth - placement.sourceBox.width) <= 2 &&
Math.abs(cutoutHeight - placement.sourceBox.height) <= 2;
const sourceCropRaw = sameSizeCutout
? await sharp(sourceCropBuffer, { failOn: "none" })
.removeAlpha()
.toColorspace("srgb")
.raw()
.toBuffer()
: null;
const cutoutRaw = await sharp(normalizedCutout, { failOn: "none" })
.ensureAlpha()
@ -1825,6 +1846,7 @@ async function buildScaleAdjustRmbgStructureLockedComposite(params: {
.toBuffer();
const { cleanedAlpha, cleanedRaw } = await cleanScaleAdjustAlphaChannel({
height: cutoutHeight,
referenceRawRgb: sourceCropRaw,
rawRgba: cutoutRaw,
width: cutoutWidth,
});
@ -1851,9 +1873,6 @@ async function buildScaleAdjustRmbgStructureLockedComposite(params: {
return null;
}
const sameSizeCutout =
Math.abs(cutoutWidth - placement.sourceBox.width) <= 2 &&
Math.abs(cutoutHeight - placement.sourceBox.height) <= 2;
const fullAlphaMask = cleanedAlpha;
const sourceMaskBuffer = sameSizeCutout
? fullAlphaMask