ai-chatbot/lib/agents/psd-image-placement.ts
Your Name be5b9aef12
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
chore: sync latest workspace changes
2026-04-05 02:08:26 +08:00

175 lines
4.6 KiB
TypeScript

import type {
AgentPsdTemplateImageContentBounds,
AgentPsdTemplateReplaceMode,
} from "@/lib/agents/psd-template-config";
export interface AgentPsdImagePlacement {
scale: number;
offsetX: number;
offsetY: number;
}
export interface AgentPsdImageSize {
width: number;
height: number;
}
export interface AgentPsdImageFrame {
width: number;
height: number;
left: number;
top: number;
centerX: number;
centerY: number;
}
export function getDefaultAgentPsdImagePlacement(): AgentPsdImagePlacement {
return {
scale: 1,
offsetX: 0,
offsetY: 0,
};
}
export function normalizeAgentPsdImagePlacement(
value: Partial<AgentPsdImagePlacement> | null | undefined
): AgentPsdImagePlacement {
const scale = Number(value?.scale);
const offsetX = Number(value?.offsetX);
const offsetY = Number(value?.offsetY);
return {
scale: Number.isFinite(scale) && scale > 0 ? Math.max(0.1, Math.min(8, scale)) : 1,
offsetX: Number.isFinite(offsetX) ? offsetX : 0,
offsetY: Number.isFinite(offsetY) ? offsetY : 0,
};
}
function resolveBaseFittedSize(params: {
containerWidth: number;
containerHeight: number;
imageWidth: number;
imageHeight: number;
replaceMode: AgentPsdTemplateReplaceMode;
}) {
const { containerWidth, containerHeight, imageWidth, imageHeight, replaceMode } = params;
const safeImageWidth = Math.max(1, imageWidth);
const safeImageHeight = Math.max(1, imageHeight);
if (replaceMode === "stretch") {
return {
width: containerWidth,
height: containerHeight,
left: 0,
top: 0,
};
}
const fitRatio =
replaceMode === "contain"
? Math.min(containerWidth / safeImageWidth, containerHeight / safeImageHeight)
: Math.max(containerWidth / safeImageWidth, containerHeight / safeImageHeight);
const width = safeImageWidth * fitRatio;
const height = safeImageHeight * fitRatio;
return {
width,
height,
left: (containerWidth - width) / 2,
top: (containerHeight - height) / 2,
};
}
function normalizeContainerBounds(params: {
containerWidth: number;
containerHeight: number;
contentBounds?: AgentPsdTemplateImageContentBounds;
}) {
const { containerWidth, containerHeight, contentBounds } = params;
if (!contentBounds) {
return {
left: 0,
top: 0,
width: containerWidth,
height: containerHeight,
};
}
const left = Math.max(0, Math.min(containerWidth - 1, Math.round(contentBounds.left)));
const top = Math.max(0, Math.min(containerHeight - 1, Math.round(contentBounds.top)));
const width = Math.max(
1,
Math.min(Math.round(contentBounds.width), Math.max(1, containerWidth - left))
);
const height = Math.max(
1,
Math.min(Math.round(contentBounds.height), Math.max(1, containerHeight - top))
);
return {
left,
top,
width,
height,
};
}
export function resolveAgentPsdImageBaseFrame(params: {
containerWidth: number;
containerHeight: number;
imageWidth: number;
imageHeight: number;
replaceMode: AgentPsdTemplateReplaceMode;
contentBounds?: AgentPsdTemplateImageContentBounds;
}): AgentPsdImageFrame {
const { containerWidth, containerHeight, imageWidth, imageHeight, replaceMode } = params;
const contentBounds = normalizeContainerBounds({
containerWidth,
containerHeight,
contentBounds: params.contentBounds,
});
const baseFrame = resolveBaseFittedSize({
containerWidth,
containerHeight,
imageWidth,
imageHeight,
replaceMode,
});
const anchorCenterX = contentBounds.left + contentBounds.width / 2;
const anchorCenterY = contentBounds.top + contentBounds.height / 2;
return {
width: baseFrame.width,
height: baseFrame.height,
left: anchorCenterX - baseFrame.width / 2,
top: anchorCenterY - baseFrame.height / 2,
centerX: anchorCenterX,
centerY: anchorCenterY,
};
}
export function resolveAgentPsdImagePlacementFrame(params: {
containerWidth: number;
containerHeight: number;
imageWidth: number;
imageHeight: number;
replaceMode: AgentPsdTemplateReplaceMode;
contentBounds?: AgentPsdTemplateImageContentBounds;
placement?: Partial<AgentPsdImagePlacement> | null;
}): AgentPsdImageFrame {
const placement = normalizeAgentPsdImagePlacement(params.placement);
const baseFrame = resolveAgentPsdImageBaseFrame(params);
const width = baseFrame.width * placement.scale;
const height = baseFrame.height * placement.scale;
return {
width,
height,
left: baseFrame.centerX + placement.offsetX - width / 2,
top: baseFrame.centerY + placement.offsetY - height / 2,
centerX: baseFrame.centerX + placement.offsetX,
centerY: baseFrame.centerY + placement.offsetY,
};
}