Enhance canvas functionality with new node types and validation
- 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.
This commit is contained in:
108
lib/image-pipeline/render-size.ts
Normal file
108
lib/image-pipeline/render-size.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user