97 lines
3.3 KiB
TypeScript
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,
|
|
},
|
|
);
|
|
}
|