ai-chatbot/lib/ai/image-size-constraints.ts
2026-06-13 13:33:02 +08:00

97 lines
3.3 KiB
TypeScript

export type ImageSizeBucket = "1K" | "2K" | "4K";
export type ImageDimensionConstraints = {
maxEdge?: number;
maxPixels?: number;
minEdge?: number;
multiple?: number;
};
export type ImageDimensions = {
width: number;
height: number;
};
export function normalizeImageSizeBucket(value?: string, fallback: ImageSizeBucket = "2K"): ImageSizeBucket {
const normalized = String(value || "").trim().toUpperCase();
if (normalized === "1K" || normalized === "2K" || normalized === "4K") return normalized;
return fallback;
}
export function parseAspectRatioPair(value?: string): ImageDimensions | null {
const normalized = String(value || "").trim().toLowerCase();
if (!normalized || normalized === "auto") return null;
const match = normalized.match(/^(\d+(?:\.\d+)?)\s*:\s*(\d+(?:\.\d+)?)$/);
if (!match) return null;
const width = Number(match[1]);
const height = Number(match[2]);
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
return null;
}
return { width, height };
}
export function roundToMultiple(value: number, multiple = 1) {
return Math.max(multiple, Math.round(value / multiple) * multiple);
}
export function floorToMultiple(value: number, multiple = 1) {
return Math.max(multiple, Math.floor(value / multiple) * multiple);
}
export function constrainImageDimensions(
dimensions: ImageDimensions,
constraints: ImageDimensionConstraints = {},
): ImageDimensions {
const multiple = Math.max(1, Math.floor(constraints.multiple || 1));
const minEdge = Math.max(multiple, Math.floor(constraints.minEdge || multiple));
const maxEdge = Number.isFinite(constraints.maxEdge)
? Math.max(minEdge, Math.floor(Number(constraints.maxEdge)))
: Number.POSITIVE_INFINITY;
const maxPixels = Number.isFinite(constraints.maxPixels)
? Math.max(minEdge * minEdge, Math.floor(Number(constraints.maxPixels)))
: Number.POSITIVE_INFINITY;
let width = Math.max(minEdge, floorToMultiple(Math.max(1, dimensions.width), multiple));
let height = Math.max(minEdge, floorToMultiple(Math.max(1, dimensions.height), multiple));
while (width > maxEdge || height > maxEdge || width * height > maxPixels) {
const scale = Math.min(maxEdge / width, maxEdge / height, Math.sqrt(maxPixels / (width * height)), 1);
const nextWidth = Math.max(minEdge, floorToMultiple(width * scale, multiple));
const nextHeight = Math.max(minEdge, floorToMultiple(height * scale, multiple));
if (nextWidth === width && nextHeight === height) {
if (width >= height && width > minEdge) width -= multiple;
else if (height > minEdge) height -= multiple;
else break;
} else {
width = nextWidth;
height = nextHeight;
}
}
return { width, height };
}
export function resolveLongestEdgeDimensions(params: {
aspectRatio?: string;
longestEdge: number;
multiple?: number;
constraints?: ImageDimensionConstraints;
}): ImageDimensions {
const ratioPair = parseAspectRatioPair(params.aspectRatio) || { width: 1, height: 1 };
const scale = params.longestEdge / Math.max(ratioPair.width, ratioPair.height);
return constrainImageDimensions(
{
width: ratioPair.width * scale,
height: ratioPair.height * scale,
},
{
multiple: params.multiple || params.constraints?.multiple,
...params.constraints,
},
);
}