ai-chatbot/lib/canvas/scale-adjust-semantic.ts

115 lines
5.0 KiB
TypeScript

import type { CanvasScaleAdjustIntentRegion } from "@/lib/canvas/scale-adjust-product-lock";
const SEMANTIC_FALLBACK_MIN_HEIGHT_SPAN = 0.78;
const SEMANTIC_FALLBACK_MIN_WIDTH_SPAN = 0.62;
const SEMANTIC_FALLBACK_TALL_ASPECT_RATIO = 2.7;
const SEMANTIC_COMPOSITE_MIN_PADDING_X = 0.04;
const SEMANTIC_COMPOSITE_MAX_PADDING_X = 0.08;
const SEMANTIC_COMPOSITE_MIN_PADDING_Y = 0.06;
const SEMANTIC_COMPOSITE_MAX_PADDING_Y = 0.12;
function clampRatio(value: number) {
return Math.min(1, Math.max(0, value));
}
export function isScaleAdjustSubjectMaskLowConfidence(params: {
alpha: Uint8Array;
height: number;
width: number;
}) {
const { alpha, height, width } = params;
if (height / Math.max(1, width) >= SEMANTIC_FALLBACK_TALL_ASPECT_RATIO) {
return true;
}
let minX = width;
let minY = height;
let maxX = -1;
let maxY = -1;
for (let index = 0; index < alpha.length; index += 1) {
if (alpha[index] < 96) continue;
const x = index % width;
const y = Math.floor(index / width);
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
if (maxX < minX || maxY < minY) return true;
const edgeMargin = Math.max(2, Math.round(Math.min(width, height) * 0.012));
if (
minX <= edgeMargin ||
minY <= edgeMargin ||
maxX >= width - 1 - edgeMargin ||
maxY >= height - 1 - edgeMargin
) {
return true;
}
const widthSpan = (maxX - minX + 1) / width;
const heightSpan = (maxY - minY + 1) / height;
return (
heightSpan < SEMANTIC_FALLBACK_MIN_HEIGHT_SPAN &&
widthSpan < SEMANTIC_FALLBACK_MIN_WIDTH_SPAN
);
}
export function buildSemanticScaleAdjustCompositeRegions(
regions: CanvasScaleAdjustIntentRegion[]
) {
return regions.map((region) => {
const left = Math.min(region.rect.x, region.targetRect.x);
const top = Math.min(region.rect.y, region.targetRect.y);
const right = Math.max(
region.rect.x + region.rect.width,
region.targetRect.x + region.targetRect.width
);
const bottom = Math.max(
region.rect.y + region.rect.height,
region.targetRect.y + region.targetRect.height
);
const width = right - left;
const height = bottom - top;
const paddingX = Math.min(
SEMANTIC_COMPOSITE_MAX_PADDING_X,
Math.max(SEMANTIC_COMPOSITE_MIN_PADDING_X, width * 0.3)
);
const paddingY = Math.min(
SEMANTIC_COMPOSITE_MAX_PADDING_Y,
Math.max(SEMANTIC_COMPOSITE_MIN_PADDING_Y, height * 0.25)
);
const expandedLeft = clampRatio(left - paddingX);
const expandedTop = clampRatio(top - paddingY);
const expandedRight = clampRatio(right + paddingX);
const expandedBottom = clampRatio(bottom + paddingY);
return {
x: expandedLeft,
y: expandedTop,
width: expandedRight - expandedLeft,
height: expandedBottom - expandedTop,
};
});
}
export function buildSemanticScaleAdjustCleanupPrompt() {
return [
"这是两阶段图片编辑的第一阶段。第一张参考图是完整场景,后续参考图是用户框选目标的局部特写。",
"在完整场景中只移除局部特写所展示的整套目标产品组及其阴影,包括产品主体、结构件、支撑件、底座、挂钩,以及附着或悬挂的全部衣物、帽子、包、饰品和配件。",
"不要只移除最显眼的单个部件,不得留下立柱、底座、挂钩、衣物、包、轮廓、透明残影或旧阴影。",
"根据周围墙面、木饰面、地面和光照自然补全被遮挡背景。完整场景的画幅、相机、构图、透视、尺寸、其他家具、文字和水印必须保持原图不变。不要新增产品、文字、框线、箭头或标注。",
].join("\n");
}
export function buildSemanticScaleAdjustExtractionPrompt() {
return [
"Product extraction task. The first reference is the user's target crop and the second reference is the full scene for context. Reconstruct exactly one complete target product group as one intact object.",
"The complete group includes the main product body, every structural part, support, pole, leg, wheel, hook and base, plus every attached or hanging garment, hat, bag, strap, accessory and decoration visible in the crop.",
"Treat every foreground item that overlaps, touches or hangs from the main product inside the crop as part of this one inseparable group. Do not choose only the easiest garment or accessory.",
"Preserve the exact original design, support and base style, proportions, component count, materials, colors, textures, orientation and attachment relationships. Do not redesign the support, replace a round base with legs, simplify the group or omit less salient items.",
"Place the complete group centered and fully visible from its highest point to its lowest support on a perfectly uniform pure magenta RGB(255,0,255) background.",
"The background must be one flat exact color with no gradient, texture, checkerboard, room, floor, wall or shadow.",
"No crop, no missing parts, no duplicate parts, no text, no frame and no annotation.",
].join("\n");
}