fix: exclude protected people from furniture scale masks

This commit is contained in:
134 2026-07-04 18:58:30 +08:00
parent 970bd89638
commit fa7d9d81ff

View File

@ -1356,6 +1356,24 @@ function isScaleAdjustFurnitureProductLikePrompt(prompt: string) {
);
}
type ScaleAdjustProtectedPersonSide = "auto" | "left" | "right";
function getScaleAdjustProtectedPersonSide(
prompt: string
): ScaleAdjustProtectedPersonSide | null {
const text = normalizeScaleAdjustModelText(prompt);
if (
!/person|people|human|woman|man|model|girl|boy|face|body|人物|人像|人|模特|女人|男人|女孩|男孩|脸|身体/.test(
text
)
) {
return null;
}
if (/right|右侧|右边|右方|右/.test(text)) return "right";
if (/left|左侧|左边|左方|左/.test(text)) return "left";
return "auto";
}
function hasScaleAdjustPersonPixelSignal(params: {
box: { left: number; top: number; width: number; height: number };
raw: Buffer;
@ -1550,11 +1568,61 @@ function buildScaleAdjustStructureInitialMask(params: {
});
}
function buildScaleAdjustProtectedPersonSeedMask(params: {
box: { left: number; top: number; width: number; height: number };
raw: Buffer;
side: ScaleAdjustProtectedPersonSide;
width: number;
}) {
const { box, raw, side, width } = params;
const seed = new Uint8Array(box.width * box.height);
let seedCount = 0;
const sideAllowsX = (localX: number) => {
if (side === "right") return localX >= box.width * 0.52;
if (side === "left") return localX <= box.width * 0.48;
return true;
};
for (let localY = 0; localY < box.height; localY += 1) {
const y = box.top + localY;
for (let localX = 0; localX < box.width; localX += 1) {
if (!sideAllowsX(localX)) continue;
const x = box.left + localX;
const rawIndex = getScaleAdjustRawPixelIndex(width, x, y);
const r = raw[rawIndex] || 0;
const g = raw[rawIndex + 1] || 0;
const b = raw[rawIndex + 2] || 0;
const { luma, saturation } = getScaleAdjustPixelStats(r, g, b);
const likelySkin = isScaleAdjustLikelySkinPixel(r, g, b);
const likelyWhiteFabric =
isScaleAdjustLikelyWhiteFabricPixel(r, g, b, luma, saturation) &&
luma < 246 &&
saturation < 0.22;
if (!likelySkin && !likelyWhiteFabric) continue;
seed[localY * box.width + localX] = 255;
seedCount += 1;
}
}
const seedRatio = seedCount / Math.max(1, box.width * box.height);
if (seedCount < 24 || seedRatio < 0.0006) return null;
return dilateScaleAdjustStructureMask({
height: box.height,
mask: seed,
radius: Math.max(4, Math.round(Math.min(box.width, box.height) * 0.032)),
width: box.width,
});
}
function filterScaleAdjustStructureComponents(params: {
box: { width: number; height: number };
mask: Uint8Array;
protectedPersonMask?: Uint8Array | null;
protectedPersonSide?: ScaleAdjustProtectedPersonSide | null;
}) {
const { box, mask } = params;
const { box, mask, protectedPersonMask = null, protectedPersonSide = null } =
params;
const width = box.width;
const height = box.height;
const visited = new Uint8Array(mask.length);
@ -1582,6 +1650,7 @@ function filterScaleAdjustStructureComponents(params: {
let minY = y;
let maxX = x;
let maxY = y;
let protectedPersonOverlap = 0;
while (head < queueX.length) {
const cx = queueX[head] || 0;
@ -1592,6 +1661,9 @@ function filterScaleAdjustStructureComponents(params: {
minY = Math.min(minY, cy);
maxX = Math.max(maxX, cx);
maxY = Math.max(maxY, cy);
if (protectedPersonMask?.[cy * width + cx]) {
protectedPersonOverlap += 1;
}
for (let dy = -1; dy <= 1; dy += 1) {
for (let dx = -1; dx <= 1; dx += 1) {
@ -1616,7 +1688,27 @@ function filterScaleAdjustStructureComponents(params: {
const centerYRatio = (minY + componentHeight / 2) / height;
const touchesTop = minY <= height * 0.04;
const touchesRight = maxX >= width * 0.88;
const touchesLeft = minX <= width * 0.12;
const edgeSliver = touchesRight && componentWidth < width * 0.18;
const touchesProtectedPersonSide =
protectedPersonSide === "right"
? maxX >= width * 0.68
: protectedPersonSide === "left"
? minX <= width * 0.32
: protectedPersonSide === "auto"
? touchesLeft || touchesRight
: false;
const protectedPersonOverlapRatio =
protectedPersonOverlap / Math.max(1, area);
const likelyProtectedPersonComponent =
Boolean(protectedPersonMask) &&
protectedPersonOverlap > 0 &&
(protectedPersonOverlapRatio >= 0.012 ||
(touchesProtectedPersonSide &&
protectedPersonOverlap >= Math.max(6, area * 0.002))) &&
(touchesProtectedPersonSide ||
componentHeight >= height * 0.16 ||
componentWidth >= width * 0.16);
const broadTopBackground =
touchesTop &&
centerYRatio <= 0.45 &&
@ -1642,7 +1734,8 @@ function filterScaleAdjustStructureComponents(params: {
likelyStructure &&
centeredEnough &&
!edgeSliver &&
!broadTopBackground
!broadTopBackground &&
!likelyProtectedPersonComponent
) {
if (fillSurfaceComponent) {
for (let fillY = minY; fillY <= maxY; fillY += 1) {
@ -2446,6 +2539,7 @@ async function buildScaleAdjustStructureLockedComposite(params: {
includePersonPixels?: boolean;
placement: ReturnType<typeof getScaleAdjustVisualGuidePlacement>;
preferPixelStructure?: boolean;
protectedPersonSide?: ScaleAdjustProtectedPersonSide | null;
raw: Buffer;
sourceBuffer: Buffer;
sourceImageUrl?: string;
@ -2456,6 +2550,7 @@ async function buildScaleAdjustStructureLockedComposite(params: {
includePersonPixels = false,
placement,
preferPixelStructure = false,
protectedPersonSide = null,
raw,
sourceBuffer,
sourceImageUrl,
@ -2487,9 +2582,20 @@ async function buildScaleAdjustStructureLockedComposite(params: {
raw,
width: sourceWidth,
});
const protectedPersonMask =
preferPixelStructure && protectedPersonSide
? buildScaleAdjustProtectedPersonSeedMask({
box: placement.sourceBox,
raw,
side: protectedPersonSide,
width: sourceWidth,
})
: null;
const mask = filterScaleAdjustStructureComponents({
box: placement.sourceBox,
mask: initialMask,
protectedPersonMask,
protectedPersonSide,
});
const structureBbox = getScaleAdjustMaskBoundingBox({
height: placement.sourceBox.height,
@ -2781,6 +2887,9 @@ async function buildScaleAdjustVisualLayoutGuideBuffer(params: {
const preferPixelStructure = isScaleAdjustFurnitureProductLikePrompt(
region.prompt
);
const protectedPersonSide = preferPixelStructure
? getScaleAdjustProtectedPersonSide(region.prompt)
: null;
const hasPersonPixels =
!preferPixelStructure &&
!!sourceRaw &&
@ -2797,6 +2906,7 @@ async function buildScaleAdjustVisualLayoutGuideBuffer(params: {
includePersonPixels,
placement,
preferPixelStructure,
protectedPersonSide,
raw: sourceRaw,
sourceBuffer,
sourceImageUrl,