175 lines
4.6 KiB
TypeScript
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,
|
|
};
|
|
}
|