Files
Ewald 23dcd1c2d9 Add lens/compositing effect nodes and depth-based Defocus, A/B compare on Preview
- Add ImageOpsVignette, ImageOpsChromaticAberration, ImageOpsBloom, and
  ImageOpsLensArtifacts for common lens/compositing effects.
- Add ImageOpsDefocus: depth-driven defocus/bokeh with shaped aperture
  kernels (circle/hexagon/octagon/custom) and highlight bloom.
- Add input_space (srgb/linear) to ImageOpsColorAjust so scene-linear/HDR
  sources aren't gamma-encoded twice.
- Add image_b + compare_mode/wipe_position to ImageOpsPreview for a real
  A/B comparison, plus a separate live on-demand A/B toggle while editing.
- Redesign the live-preview HUD: unified pill-style badges, frame counter
  now shows the real total and hides for single-frame sources, and the
  Output overlay only appears for Pad Out with the real pixel size.
- Align README with the current node set and fix stale blend-mode docs.
2026-09-28 00:19:49 +02:00

54 lines
1.8 KiB
JavaScript

function clampPreviewZoom(zoom) {
return Math.max(0.35, Math.min(6, zoom));
}
function screenToWorld(x, y, zoom, panX, panY, canvasSize) {
const half = canvasSize / 2;
return {
x: (x - panX - half) / zoom + half,
y: (y - panY - half) / zoom + half
};
}
function getFitPlacement(width, height, sourceWidth, sourceHeight) {
const safeWidth = Math.max(1, sourceWidth);
const safeHeight = Math.max(1, sourceHeight);
const scale = Math.min(width / safeWidth, height / safeHeight);
const drawWidth = Math.max(1, Math.floor(safeWidth * scale));
const drawHeight = Math.max(1, Math.floor(safeHeight * scale));
const dx = Math.floor((width - drawWidth) / 2);
const dy = Math.floor((height - drawHeight) / 2);
return { dx, dy, drawWidth, drawHeight };
}
function drawFitSource(ctx, width, height, source, sourceWidth, sourceHeight, fit) {
const placement = fit ?? getFitPlacement(width, height, sourceWidth, sourceHeight);
ctx.clearRect(0, 0, width, height);
ctx.imageSmoothingEnabled = true;
ctx.drawImage(source, placement.dx, placement.dy, placement.drawWidth, placement.drawHeight);
}
function drawOutputBounds(ctx, fit) {
ctx.save();
ctx.strokeStyle = "rgba(251,191,36,0.55)";
ctx.lineWidth = 1;
ctx.setLineDash([6, 4]);
ctx.strokeRect(fit.dx + 0.5, fit.dy + 0.5, fit.drawWidth, fit.drawHeight);
ctx.setLineDash([]);
ctx.restore();
}
function getCanvasPointer(canvas, event) {
const rect = canvas.getBoundingClientRect();
if (rect.width < 1 || rect.height < 1) return { x: 0, y: 0 };
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
return {
x: (event.clientX - rect.left) * scaleX,
y: (event.clientY - rect.top) * scaleY
};
}
export {
clampPreviewZoom,
drawFitSource,
drawOutputBounds,
getCanvasPointer,
getFitPlacement,
screenToWorld
};