Files
lemonspace_app/lib/image-formats.ts
Matthias a5cde14573 feat: refactor canvas and node components for improved functionality and styling
- Removed unused hooks and optimized edge handling in the canvas component.
- Adjusted positioning of handles in the compare node for better alignment.
- Enhanced prompt node to utilize incoming edges for dynamic prompt generation and improved user feedback.
- Updated text node to synchronize content changes with the React Flow state.
- Improved logging in edge removal to handle idempotent operations gracefully.
2026-03-26 17:35:25 +01:00

86 lines
2.8 KiB
TypeScript

/** OpenRouter / Gemini image_config.aspect_ratio values */
export const DEFAULT_ASPECT_RATIO = "1:1" as const;
export type ImageFormatGroup = "square" | "landscape" | "portrait";
export type ImageFormatPreset = {
label: string;
aspectRatio: string;
group: ImageFormatGroup;
};
export const IMAGE_FORMAT_GROUP_LABELS: Record<ImageFormatGroup, string> = {
square: "Quadratisch",
landscape: "Querformat",
portrait: "Hochformat",
};
/** Presets for Prompt Node Select (labels DE, ratios API-compatible) */
export const IMAGE_FORMAT_PRESETS: ImageFormatPreset[] = [
{ label: "1:1 · Quadrat", aspectRatio: "1:1", group: "square" },
{ label: "16:9 · Breitbild", aspectRatio: "16:9", group: "landscape" },
{ label: "21:9 · Kino", aspectRatio: "21:9", group: "landscape" },
{ label: "4:3 · Klassisch", aspectRatio: "4:3", group: "landscape" },
{ label: "3:2 · Foto (quer)", aspectRatio: "3:2", group: "landscape" },
{ label: "5:4 · leicht quer", aspectRatio: "5:4", group: "landscape" },
{ label: "9:16 · Story", aspectRatio: "9:16", group: "portrait" },
{ label: "3:4 · Porträt", aspectRatio: "3:4", group: "portrait" },
{ label: "2:3 · Foto (hoch)", aspectRatio: "2:3", group: "portrait" },
{ label: "4:5 · Social hoch", aspectRatio: "4:5", group: "portrait" },
];
/** Header row + footer strip (prompt preview) inside AI Image node */
export const AI_IMAGE_NODE_HEADER_PX = 40;
export const AI_IMAGE_NODE_FOOTER_PX = 48;
export function parseAspectRatioString(aspectRatio: string): {
w: number;
h: number;
} {
const parts = aspectRatio.split(":").map((x) => Number.parseInt(x, 10));
if (
parts.length !== 2 ||
parts.some((n) => !Number.isFinite(n) || n <= 0)
) {
throw new Error(`Invalid aspect ratio: ${aspectRatio}`);
}
return { w: parts[0]!, h: parts[1]! };
}
/** Bildfläche: längere Kante = maxEdgePx */
export function getImageViewportSize(
aspectRatio: string,
options?: { maxEdge?: number }
): { width: number; height: number } {
const maxEdge = options?.maxEdge ?? 320;
const { w, h } = parseAspectRatioString(aspectRatio);
if (w >= h) {
return {
width: maxEdge,
height: Math.max(1, Math.round(maxEdge * (h / w))),
};
}
return {
width: Math.max(1, Math.round(maxEdge * (w / h))),
height: maxEdge,
};
}
/** Outer Convex / React Flow node size (includes chrome) */
export function getAiImageNodeOuterSize(viewport: {
width: number;
height: number;
}): { width: number; height: number } {
return {
width: viewport.width,
height: AI_IMAGE_NODE_HEADER_PX + viewport.height + AI_IMAGE_NODE_FOOTER_PX,
};
}
export function getPresetLabel(aspectRatio: string): string {
return (
IMAGE_FORMAT_PRESETS.find((p) => p.aspectRatio === aspectRatio)?.label ??
aspectRatio
);
}