Files
Ewald a4154ccf05 Fix P0 bugs, remove Append V2/Lens Artifacts, and fix crop reload reset
- Fix batch bypass tuple handling, memory budget device-awareness, grain
  RNG device, roto payload limits, preview temp-file cleanup, and route
  double-registration.
- Add native VIDEO output (fps/audio) to effect nodes that previously
  only returned IMAGE/MASK when fed a VIDEO input.
- Split the oversized src/preview/ops/geometry.ts and add a starting
  golden-fixture test harness (spherize, transform) comparing backend
  and frontend math.
- Remove ImageOpsAppendV2 (Autogrow) and ImageOpsLensArtifacts; inline
  Append's helper back into its single caller.
- Collapse the Color Correct curve widget behind a toggle so the node
  no longer grows too tall.
- Fix Crop/generic-node video preview playback running at the RAF rate
  instead of the source video's real fps.
- Fix Crop losing its saved output size after a workflow reload:
  syncCropWidgets recomputed height from width and a preset aspect
  ratio on every resync (including onConfigure on load), discarding
  the restored value. It now only refreshes aspect-ratio bookkeeping
  on a resync.
2026-09-30 12:49:42 +02:00

93 lines
3.6 KiB
JavaScript

import { noiseLerp } from "../procedural.js";
import { reflectCoordinate } from "./canvas-basics.js";
function setResampleMode(ctx, filter) {
const mode = String(filter || "bilinear").toLowerCase();
ctx.imageSmoothingEnabled = mode !== "nearest";
if (ctx.imageSmoothingEnabled) {
ctx.imageSmoothingQuality = mode === "bicubic" ? "high" : "medium";
}
}
function sampleChannel(data, width, height, x, y, edgeMode) {
let px = x;
let py = y;
if (edgeMode === "zeros") {
if (px < 0 || py < 0 || px >= width || py >= height) return [0, 0, 0, 0];
} else if (edgeMode === "reflection") {
px = reflectCoordinate(px, width);
py = reflectCoordinate(py, height);
} else {
px = Math.max(0, Math.min(width - 1, px));
py = Math.max(0, Math.min(height - 1, py));
}
const offset = (py * width + px) * 4;
return [data[offset], data[offset + 1], data[offset + 2], data[offset + 3]];
}
function sampleChannelNearest(data, width, height, x, y, channel) {
const ix = Math.max(0, Math.min(width - 1, Math.round(x)));
const iy = Math.max(0, Math.min(height - 1, Math.round(y)));
return data[(iy * width + ix) * 4 + channel];
}
function sampleChannelBilinear(data, width, height, x, y, channel) {
const x0 = Math.floor(x);
const y0 = Math.floor(y);
const x1 = Math.min(width - 1, x0 + 1);
const y1 = Math.min(height - 1, y0 + 1);
const fx = x - x0;
const fy = y - y0;
const c00 = data[(Math.max(0, y0) * width + Math.max(0, x0)) * 4 + channel];
const c10 = data[(Math.max(0, y0) * width + Math.max(0, x1)) * 4 + channel];
const c01 = data[(Math.max(0, y1) * width + Math.max(0, x0)) * 4 + channel];
const c11 = data[(Math.max(0, y1) * width + Math.max(0, x1)) * 4 + channel];
return (c00 * (1 - fx) + c10 * fx) * (1 - fy) + (c01 * (1 - fx) + c11 * fx) * fy;
}
function cubicHermite(a, b, c, d, t) {
const a1 = -0.5 * a + 1.5 * b - 1.5 * c + 0.5 * d;
const a2 = a - 2.5 * b + 2 * c - 0.5 * d;
const a3 = -0.5 * a + 0.5 * c;
return ((a1 * t + a2) * t + a3) * t + b;
}
function sampleChannelBicubic(data, width, height, x, y, channel) {
const x1 = Math.floor(x);
const y1 = Math.floor(y);
const tx = x - x1;
const ty = y - y1;
const rows = new Array(4);
for (let row = -1; row <= 2; row++) {
const iy = Math.max(0, Math.min(height - 1, y1 + row));
const p0 = data[(iy * width + Math.max(0, Math.min(width - 1, x1 - 1))) * 4 + channel];
const p1 = data[(iy * width + Math.max(0, Math.min(width - 1, x1))) * 4 + channel];
const p2 = data[(iy * width + Math.max(0, Math.min(width - 1, x1 + 1))) * 4 + channel];
const p3 = data[(iy * width + Math.max(0, Math.min(width - 1, x1 + 2))) * 4 + channel];
rows[row + 1] = cubicHermite(p0, p1, p2, p3, tx);
}
return cubicHermite(rows[0], rows[1], rows[2], rows[3], ty);
}
function bilinearSample(data, width, height, x, y, edgeMode) {
const x0 = Math.floor(x);
const y0 = Math.floor(y);
const x1 = x0 + 1;
const y1 = y0 + 1;
const tx = x - x0;
const ty = y - y0;
const c00 = sampleChannel(data, width, height, x0, y0, edgeMode);
const c10 = sampleChannel(data, width, height, x1, y0, edgeMode);
const c01 = sampleChannel(data, width, height, x0, y1, edgeMode);
const c11 = sampleChannel(data, width, height, x1, y1, edgeMode);
const out = [0, 0, 0, 0];
for (let c = 0; c < 4; c++) {
const top = noiseLerp(c00[c], c10[c], tx);
const bottom = noiseLerp(c01[c], c11[c], tx);
out[c] = noiseLerp(top, bottom, ty);
}
return out;
}
export {
bilinearSample,
cubicHermite,
sampleChannel,
sampleChannelBicubic,
sampleChannelBilinear,
sampleChannelNearest,
setResampleMode
};