fix: focus scale adjust subject guide
This commit is contained in:
parent
956f9e0fed
commit
98e987e75b
@ -791,8 +791,18 @@ function getScaleAdjustVisualGuidePlacement(params: {
|
||||
sourceTop + 1,
|
||||
height
|
||||
);
|
||||
const sourceWidth = Math.max(1, Math.round(sourceRight - sourceLeft));
|
||||
const sourceHeight = Math.max(1, Math.round(sourceBottom - sourceTop));
|
||||
const sourceFootprintBox = {
|
||||
left: Math.round(sourceLeft),
|
||||
top: Math.round(sourceTop),
|
||||
width: Math.max(1, Math.round(sourceRight - sourceLeft)),
|
||||
height: Math.max(1, Math.round(sourceBottom - sourceTop)),
|
||||
};
|
||||
const sourceBox = getScaleAdjustFocusedSourceBox({
|
||||
region,
|
||||
sourceBox: sourceFootprintBox,
|
||||
});
|
||||
const sourceWidth = sourceBox.width;
|
||||
const sourceHeight = sourceBox.height;
|
||||
const widthScale = target.width / Math.max(1, sourceWidth);
|
||||
const heightScale = target.height / Math.max(1, sourceHeight);
|
||||
const uniformScale = Math.max(
|
||||
@ -831,16 +841,64 @@ function getScaleAdjustVisualGuidePlacement(params: {
|
||||
placedTop: Math.max(0, Math.round(placedTop)),
|
||||
placedWidth: Math.max(1, Math.round(placedWidth)),
|
||||
sourceBox: {
|
||||
left: Math.round(sourceLeft),
|
||||
top: Math.round(sourceTop),
|
||||
left: sourceBox.left,
|
||||
top: sourceBox.top,
|
||||
width: sourceWidth,
|
||||
height: sourceHeight,
|
||||
},
|
||||
sourceFootprintBox,
|
||||
targetBox: target,
|
||||
uniformScale,
|
||||
};
|
||||
}
|
||||
|
||||
function getScaleAdjustFocusDirection(region: NormalizedLocalEditRegion) {
|
||||
const text = normalizeScaleAdjustModelText(region.prompt);
|
||||
const compact = text.replace(/[^a-z0-9\u4e00-\u9fa5]+/g, "");
|
||||
if (text.includes("left") || compact.includes("左")) return "left";
|
||||
if (text.includes("right") || compact.includes("右")) return "right";
|
||||
if (text.includes("top") || compact.includes("上")) return "top";
|
||||
if (text.includes("bottom") || compact.includes("下")) return "bottom";
|
||||
return "";
|
||||
}
|
||||
|
||||
function getScaleAdjustFocusedSourceBox(params: {
|
||||
region: NormalizedLocalEditRegion;
|
||||
sourceBox: { left: number; top: number; width: number; height: number };
|
||||
}) {
|
||||
const { region, sourceBox } = params;
|
||||
const direction = getScaleAdjustFocusDirection(region);
|
||||
if (!direction) return sourceBox;
|
||||
|
||||
const minWidth = Math.max(1, Math.round(sourceBox.width * 0.42));
|
||||
const minHeight = Math.max(1, Math.round(sourceBox.height * 0.42));
|
||||
let left = sourceBox.left;
|
||||
let top = sourceBox.top;
|
||||
let width = sourceBox.width;
|
||||
let height = sourceBox.height;
|
||||
|
||||
if (direction === "left") {
|
||||
width = Math.max(minWidth, Math.round(sourceBox.width * 0.68));
|
||||
} else if (direction === "right") {
|
||||
const nextWidth = Math.max(minWidth, Math.round(sourceBox.width * 0.68));
|
||||
left = sourceBox.left + sourceBox.width - nextWidth;
|
||||
width = nextWidth;
|
||||
} else if (direction === "top") {
|
||||
height = Math.max(minHeight, Math.round(sourceBox.height * 0.72));
|
||||
} else if (direction === "bottom") {
|
||||
const nextHeight = Math.max(minHeight, Math.round(sourceBox.height * 0.72));
|
||||
top = sourceBox.top + sourceBox.height - nextHeight;
|
||||
height = nextHeight;
|
||||
}
|
||||
|
||||
return {
|
||||
left,
|
||||
top,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}
|
||||
|
||||
function buildScaleAdjustPatchMaskSvg(params: { height: number; width: number }) {
|
||||
const { height, width } = params;
|
||||
const blur = Math.max(2, Math.min(14, Math.round(Math.min(width, height) * 0.035)));
|
||||
@ -966,7 +1024,7 @@ async function buildScaleAdjustVisualLayoutGuideBuffer(params: {
|
||||
scalePercent,
|
||||
width: sourceWidth,
|
||||
});
|
||||
sourceFootprintBoxes.push(placement.sourceBox);
|
||||
sourceFootprintBoxes.push(placement.sourceFootprintBox);
|
||||
placements.push(placement);
|
||||
|
||||
const resizedCropBuffer = await sharp(sourceBuffer, { failOn: "none" })
|
||||
@ -1989,6 +2047,7 @@ export async function POST(request: NextRequest) {
|
||||
: undefined,
|
||||
sourceImageUrl,
|
||||
regions: regions.map((region) => region.rect),
|
||||
regionPrompts: regions.map((region) => region.prompt),
|
||||
...(typeof scalePercent === "number"
|
||||
? { scalePercent: scalePercent as ScaleAdjustPercent }
|
||||
: {}),
|
||||
|
||||
@ -56,6 +56,7 @@ export interface CanvasScaleAdjustVerificationConfig {
|
||||
allowOutsideChangedWhenChangedInside?: boolean;
|
||||
protectedRegions?: CanvasLocalEditCompositeRegion[];
|
||||
rejectFailedCandidates?: boolean;
|
||||
regionPrompts?: string[];
|
||||
sourceImageUrl: string;
|
||||
regions: CanvasLocalEditCompositeRegion[];
|
||||
targetRegions?: CanvasLocalEditCompositeRegion[];
|
||||
@ -1405,6 +1406,63 @@ function getRawPixelIndex(width: number, x: number, y: number) {
|
||||
return (y * width + x) * 3;
|
||||
}
|
||||
|
||||
function normalizeScaleAdjustFocusText(input: unknown) {
|
||||
return String(input || "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.normalize("NFKC")
|
||||
.replace(/[「」『』【】[\]()(){}]/g, " ")
|
||||
.replace(/[_/|::,,.。+]+/g, " ")
|
||||
.replace(/\s+/g, " ");
|
||||
}
|
||||
|
||||
function getScaleAdjustFallbackFocusDirection(prompt: unknown) {
|
||||
const text = normalizeScaleAdjustFocusText(prompt);
|
||||
const compact = text.replace(/[^a-z0-9\u4e00-\u9fa5]+/g, "");
|
||||
if (text.includes("left") || compact.includes("左")) return "left";
|
||||
if (text.includes("right") || compact.includes("右")) return "right";
|
||||
if (text.includes("top") || compact.includes("上")) return "top";
|
||||
if (text.includes("bottom") || compact.includes("下")) return "bottom";
|
||||
return "";
|
||||
}
|
||||
|
||||
function getScaleAdjustFallbackFocusedSourceBox(params: {
|
||||
prompt?: string;
|
||||
sourceBox: ReturnType<typeof rectToPixelBox>;
|
||||
}) {
|
||||
const { prompt, sourceBox } = params;
|
||||
const direction = getScaleAdjustFallbackFocusDirection(prompt);
|
||||
if (!direction) return sourceBox;
|
||||
|
||||
const minWidth = Math.max(1, Math.round(sourceBox.width * 0.42));
|
||||
const minHeight = Math.max(1, Math.round(sourceBox.height * 0.42));
|
||||
let left = sourceBox.left;
|
||||
let top = sourceBox.top;
|
||||
let width = sourceBox.width;
|
||||
let height = sourceBox.height;
|
||||
|
||||
if (direction === "left") {
|
||||
width = Math.max(minWidth, Math.round(sourceBox.width * 0.68));
|
||||
} else if (direction === "right") {
|
||||
const nextWidth = Math.max(minWidth, Math.round(sourceBox.width * 0.68));
|
||||
left = sourceBox.left + sourceBox.width - nextWidth;
|
||||
width = nextWidth;
|
||||
} else if (direction === "top") {
|
||||
height = Math.max(minHeight, Math.round(sourceBox.height * 0.72));
|
||||
} else if (direction === "bottom") {
|
||||
const nextHeight = Math.max(minHeight, Math.round(sourceBox.height * 0.72));
|
||||
top = sourceBox.top + sourceBox.height - nextHeight;
|
||||
height = nextHeight;
|
||||
}
|
||||
|
||||
return {
|
||||
left,
|
||||
top,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}
|
||||
|
||||
function getLuma(r: number, g: number, b: number) {
|
||||
return r * 0.299 + g * 0.587 + b * 0.114;
|
||||
}
|
||||
@ -1662,13 +1720,21 @@ async function buildScaleAdjustDeterministicFallbackImages(params: {
|
||||
region: targetRegion,
|
||||
width: sourceImage.sourceWidth,
|
||||
});
|
||||
const prompt = verification.regionPrompts?.[index] || "";
|
||||
const focusedSourceBox = getScaleAdjustFallbackFocusedSourceBox({
|
||||
prompt,
|
||||
sourceBox,
|
||||
});
|
||||
const mask = buildScaleAdjustFallbackSubjectMask({
|
||||
box: sourceBox,
|
||||
box: focusedSourceBox,
|
||||
raw: baseRaw,
|
||||
width: sourceImage.sourceWidth,
|
||||
});
|
||||
const maskedPixels = mask.reduce((sum, value) => sum + (value ? 1 : 0), 0);
|
||||
if (maskedPixels < Math.max(24, sourceBox.width * sourceBox.height * 0.015)) {
|
||||
if (
|
||||
maskedPixels <
|
||||
Math.max(24, focusedSourceBox.width * focusedSourceBox.height * 0.015)
|
||||
) {
|
||||
console.warn("[Canvas Generate Image] Scale adjust deterministic fallback skipped sparse mask:", {
|
||||
debugLabel,
|
||||
maskedPixels,
|
||||
@ -1677,19 +1743,19 @@ async function buildScaleAdjustDeterministicFallbackImages(params: {
|
||||
continue;
|
||||
}
|
||||
const background = estimateScaleAdjustFallbackBackground({
|
||||
box: sourceBox,
|
||||
box: focusedSourceBox,
|
||||
raw: baseRaw,
|
||||
width: sourceImage.sourceWidth,
|
||||
});
|
||||
const objectBuffer = await buildScaleAdjustFallbackObjectPng({
|
||||
box: sourceBox,
|
||||
box: focusedSourceBox,
|
||||
mask,
|
||||
sourceBuffer: sourceImage.sourceBuffer,
|
||||
targetBox,
|
||||
});
|
||||
baseRaw = inpaintScaleAdjustMaskedRegion({
|
||||
background,
|
||||
box: sourceBox,
|
||||
box: focusedSourceBox,
|
||||
mask,
|
||||
raw: baseRaw,
|
||||
width: sourceImage.sourceWidth,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user