- 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.
93 lines
3.6 KiB
JavaScript
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
|
|
};
|