- Added support for new canvas node types: curves, color-adjust, light-adjust, detail-adjust, and render. - Implemented validation for adjustment nodes to restrict incoming edges to one. - Updated canvas connection validation to improve user feedback on invalid connections. - Enhanced node creation and rendering logic to accommodate new node types and their properties. - Refactored related components and utilities for better maintainability and performance.
109 lines
3.1 KiB
TypeScript
109 lines
3.1 KiB
TypeScript
import type {
|
|
RenderOptions,
|
|
RenderSizeLimits,
|
|
ResolvedRenderSize,
|
|
} from "@/lib/image-pipeline/render-types";
|
|
|
|
const DEFAULT_MAX_DIMENSION = 8192;
|
|
const DEFAULT_MAX_PIXELS = 33_554_432;
|
|
|
|
function sanitizeLimit(name: string, value: number | undefined, fallback: number): number {
|
|
if (value === undefined) {
|
|
return fallback;
|
|
}
|
|
|
|
if (!Number.isFinite(value) || value <= 0) {
|
|
throw new Error(`Invalid render limit '${name}'. Expected a positive finite number.`);
|
|
}
|
|
|
|
return Math.max(1, Math.floor(value));
|
|
}
|
|
|
|
function sanitizeDimension(name: string, value: number): number {
|
|
if (!Number.isFinite(value) || value <= 0) {
|
|
throw new Error(`Invalid ${name}. Expected a positive finite number.`);
|
|
}
|
|
|
|
return Math.max(1, Math.round(value));
|
|
}
|
|
|
|
function scaleDimensions(
|
|
width: number,
|
|
height: number,
|
|
factor: number,
|
|
): {
|
|
width: number;
|
|
height: number;
|
|
} {
|
|
return {
|
|
width: Math.max(1, Math.floor(width * factor)),
|
|
height: Math.max(1, Math.floor(height * factor)),
|
|
};
|
|
}
|
|
|
|
export function resolveRenderSize(options: {
|
|
sourceWidth: number;
|
|
sourceHeight: number;
|
|
render: RenderOptions;
|
|
limits?: RenderSizeLimits;
|
|
}): ResolvedRenderSize {
|
|
const sourceWidth = sanitizeDimension("sourceWidth", options.sourceWidth);
|
|
const sourceHeight = sanitizeDimension("sourceHeight", options.sourceHeight);
|
|
|
|
const maxDimension = sanitizeLimit(
|
|
"maxDimension",
|
|
options.limits?.maxDimension,
|
|
DEFAULT_MAX_DIMENSION,
|
|
);
|
|
const maxPixels = sanitizeLimit("maxPixels", options.limits?.maxPixels, DEFAULT_MAX_PIXELS);
|
|
|
|
let targetWidth = sourceWidth;
|
|
let targetHeight = sourceHeight;
|
|
|
|
if (options.render.resolution === "2x") {
|
|
targetWidth = sourceWidth * 2;
|
|
targetHeight = sourceHeight * 2;
|
|
} else if (options.render.resolution === "custom") {
|
|
if (!options.render.customSize) {
|
|
throw new Error("Invalid render options: resolution 'custom' requires customSize.");
|
|
}
|
|
|
|
targetWidth = sanitizeDimension("customSize.width", options.render.customSize.width);
|
|
targetHeight = sanitizeDimension("customSize.height", options.render.customSize.height);
|
|
} else if (options.render.resolution !== "original") {
|
|
throw new Error(`Unsupported render resolution '${options.render.resolution}'.`);
|
|
}
|
|
|
|
targetWidth = sanitizeDimension("targetWidth", targetWidth);
|
|
targetHeight = sanitizeDimension("targetHeight", targetHeight);
|
|
|
|
let scaleFactor = 1;
|
|
let wasClamped = false;
|
|
|
|
const dimensionScale = Math.min(1, maxDimension / Math.max(targetWidth, targetHeight));
|
|
if (dimensionScale < 1) {
|
|
const scaled = scaleDimensions(targetWidth, targetHeight, dimensionScale);
|
|
targetWidth = scaled.width;
|
|
targetHeight = scaled.height;
|
|
scaleFactor *= dimensionScale;
|
|
wasClamped = true;
|
|
}
|
|
|
|
const pixelCount = targetWidth * targetHeight;
|
|
if (pixelCount > maxPixels) {
|
|
const pixelScale = Math.sqrt(maxPixels / pixelCount);
|
|
const scaled = scaleDimensions(targetWidth, targetHeight, pixelScale);
|
|
targetWidth = scaled.width;
|
|
targetHeight = scaled.height;
|
|
scaleFactor *= pixelScale;
|
|
wasClamped = true;
|
|
}
|
|
|
|
return {
|
|
width: targetWidth,
|
|
height: targetHeight,
|
|
scaleFactor,
|
|
wasClamped,
|
|
};
|
|
}
|