Files
lemonspace_app/lib/canvas-render-preview.ts
Matthias Meister 8703387617 Enhance canvas functionality with storage URL resolution and debugging improvements
- Added a fallback mechanism for resolving storage URLs in `canvas-helpers.ts`, improving reliability when URLs are not directly available.
- Introduced new utility functions in `canvas.tsx` for summarizing update and resize payloads, enhancing debugging capabilities during canvas operations.
- Updated `compare-node.tsx` to improve state management and rendering logic, allowing for better handling of incoming edges and display modes.
- Refactored `render-node.tsx` to streamline the rendering process and include detailed logging for debugging render operations.
- Updated `.gitignore` to exclude `.kilo` files, ensuring cleaner repository management.
2026-04-02 16:12:56 +02:00

143 lines
3.7 KiB
TypeScript

import {
collectPipeline,
getSourceImage,
hashPipeline,
type PipelineStep,
} from "@/lib/image-pipeline/contracts";
export type RenderPreviewGraphNode = {
id: string;
type: string;
data?: unknown;
};
export type RenderPreviewGraphEdge = {
source: string;
target: string;
};
export type RenderPreviewInput = {
sourceUrl: string;
steps: PipelineStep[];
};
type RenderResolutionOption = "original" | "2x" | "custom";
type RenderFormatOption = "png" | "jpeg" | "webp";
const DEFAULT_OUTPUT_RESOLUTION: RenderResolutionOption = "original";
const DEFAULT_FORMAT: RenderFormatOption = "png";
const DEFAULT_JPEG_QUALITY = 90;
const MIN_CUSTOM_DIMENSION = 1;
const MAX_CUSTOM_DIMENSION = 16_384;
function sanitizeDimension(value: unknown): number | undefined {
if (typeof value !== "number" || !Number.isFinite(value)) {
return undefined;
}
const rounded = Math.round(value);
if (rounded < MIN_CUSTOM_DIMENSION || rounded > MAX_CUSTOM_DIMENSION) {
return undefined;
}
return rounded;
}
const SOURCE_NODE_TYPES = new Set(["image", "ai-image", "asset"]);
export const RENDER_PREVIEW_PIPELINE_TYPES = new Set([
"curves",
"color-adjust",
"light-adjust",
"detail-adjust",
]);
export function resolveRenderFingerprint(data: unknown): {
resolution: RenderResolutionOption;
customWidth?: number;
customHeight?: number;
format: RenderFormatOption;
jpegQuality?: number;
} {
const record = (data ?? {}) as Record<string, unknown>;
const resolution: RenderResolutionOption =
record.outputResolution === "2x" || record.outputResolution === "custom"
? record.outputResolution
: DEFAULT_OUTPUT_RESOLUTION;
const format: RenderFormatOption =
record.format === "jpeg" || record.format === "webp"
? record.format
: DEFAULT_FORMAT;
const jpegQuality =
typeof record.jpegQuality === "number" && Number.isFinite(record.jpegQuality)
? Math.max(1, Math.min(100, Math.round(record.jpegQuality)))
: DEFAULT_JPEG_QUALITY;
return {
resolution,
customWidth: resolution === "custom" ? sanitizeDimension(record.customWidth) : undefined,
customHeight: resolution === "custom" ? sanitizeDimension(record.customHeight) : undefined,
format,
jpegQuality: format === "jpeg" ? jpegQuality : undefined,
};
}
export function resolveRenderPipelineHash(args: {
sourceUrl: string | null;
steps: PipelineStep[];
data: unknown;
}): string | null {
if (!args.sourceUrl) {
return null;
}
return hashPipeline(
{ sourceUrl: args.sourceUrl, render: resolveRenderFingerprint(args.data) },
args.steps,
);
}
export function resolveNodeImageUrl(data: unknown): string | null {
const record = (data ?? {}) as Record<string, unknown>;
const directUrl = typeof record.url === "string" ? record.url : null;
if (directUrl && directUrl.length > 0) {
return directUrl;
}
const previewUrl =
typeof record.previewUrl === "string" ? record.previewUrl : null;
if (previewUrl && previewUrl.length > 0) {
return previewUrl;
}
return null;
}
export function resolveRenderPreviewInput(args: {
nodeId: string;
nodes: readonly RenderPreviewGraphNode[];
edges: readonly RenderPreviewGraphEdge[];
}): { sourceUrl: string | null; steps: PipelineStep[] } {
const sourceUrl = getSourceImage({
nodeId: args.nodeId,
nodes: args.nodes,
edges: args.edges,
isSourceNode: (node) => SOURCE_NODE_TYPES.has(node.type ?? ""),
getSourceImageFromNode: (node) => resolveNodeImageUrl(node.data),
});
const steps = collectPipeline({
nodeId: args.nodeId,
nodes: args.nodes,
edges: args.edges,
isPipelineNode: (node) => RENDER_PREVIEW_PIPELINE_TYPES.has(node.type ?? ""),
}) as PipelineStep[];
return {
sourceUrl,
steps,
};
}