You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

122 lines
3.6 KiB

/**
* Utility functions for calculating node dimensions based on output aspect ratio.
*/
/**
* Extract dimensions from a base64 data URL image.
* @param base64DataUrl - The image as a base64 data URL (e.g., "data:image/png;base64,...")
* @returns Promise resolving to {width, height} or null if extraction fails
*/
export function getImageDimensions(
base64DataUrl: string
): Promise<{ width: number; height: number } | null> {
return new Promise((resolve) => {
if (!base64DataUrl || !base64DataUrl.startsWith("data:image")) {
resolve(null);
return;
}
const img = new Image();
img.onload = () => {
resolve({ width: img.naturalWidth, height: img.naturalHeight });
};
img.onerror = () => {
resolve(null);
};
img.src = base64DataUrl;
});
}
/**
* Extract dimensions from a video data URL or blob URL.
* @param videoUrl - The video as a data URL or blob URL
* @returns Promise resolving to {width, height} or null if extraction fails
*/
export function getVideoDimensions(
videoUrl: string
): Promise<{ width: number; height: number } | null> {
return new Promise((resolve) => {
if (!videoUrl) {
resolve(null);
return;
}
const video = document.createElement("video");
video.onloadedmetadata = () => {
resolve({ width: video.videoWidth, height: video.videoHeight });
};
video.onerror = () => {
resolve(null);
};
video.src = videoUrl;
});
}
// Node sizing constraints
const MIN_WIDTH = 200;
const MAX_WIDTH = 500;
const MIN_HEIGHT = 200;
const MAX_HEIGHT = 600;
// Node chrome: header (~40px), controls/padding (~60px)
const NODE_CHROME_HEIGHT = 100;
/**
* Calculate node dimensions that maintain aspect ratio within constraints.
* @param aspectRatio - Width divided by height (e.g., 16/9 for landscape, 9/16 for portrait)
* @param baseWidth - Starting width to calculate from (default 300px)
* @returns {width, height} dimensions that fit within constraints
*/
export function calculateNodeSize(
aspectRatio: number,
baseWidth: number = 300
): { width: number; height: number } {
// Handle invalid aspect ratios
if (!aspectRatio || aspectRatio <= 0 || !isFinite(aspectRatio)) {
return { width: 300, height: 300 }; // Return default square
}
// Start with base width and calculate content height
let width = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, baseWidth));
// Calculate content area height based on aspect ratio
// Content height = width / aspectRatio
let contentHeight = width / aspectRatio;
let totalHeight = contentHeight + NODE_CHROME_HEIGHT;
// Check if height exceeds max - if so, scale down width to fit
if (totalHeight > MAX_HEIGHT) {
contentHeight = MAX_HEIGHT - NODE_CHROME_HEIGHT;
width = contentHeight * aspectRatio;
totalHeight = MAX_HEIGHT;
}
// Check if height is below min - if so, scale up width to fit
if (totalHeight < MIN_HEIGHT) {
contentHeight = MIN_HEIGHT - NODE_CHROME_HEIGHT;
width = contentHeight * aspectRatio;
totalHeight = MIN_HEIGHT;
}
// Clamp width to constraints
if (width > MAX_WIDTH) {
width = MAX_WIDTH;
contentHeight = width / aspectRatio;
totalHeight = contentHeight + NODE_CHROME_HEIGHT;
// Re-clamp height
totalHeight = Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, totalHeight));
}
if (width < MIN_WIDTH) {
width = MIN_WIDTH;
contentHeight = width / aspectRatio;
totalHeight = contentHeight + NODE_CHROME_HEIGHT;
// Re-clamp height
totalHeight = Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, totalHeight));
}
return {
width: Math.round(width),
height: Math.round(totalHeight),
};
}