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

68 lines
3.2 KiB
JavaScript

const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs/promises");
const path = require("node:path");
const { createCanvas, Canvas } = require("@napi-rs/canvas");
// applyTransform/makeCanvas need a DOM canvas; @napi-rs/canvas provides one
// without requiring a real browser. Only shimmed for the duration of this file.
globalThis.HTMLCanvasElement = Canvas;
if (typeof globalThis.document === "undefined") globalThis.document = {};
globalThis.document.createElement = (tag) => {
if (tag !== "canvas") throw new Error(`unsupported tag: ${tag}`);
return createCanvas(1, 1);
};
test("Frontend applyTransform matches the backend _transform_batch_affine golden fixture", async () => {
const { applyTransform } = await import("../../js/preview/ops/geometry/transform.js");
const fixture = JSON.parse(await fs.readFile(path.join(__dirname, "..", "golden", "transform.json"), "utf-8"));
const { width, height, tolerance_abs_255: tolerance, border_margin: margin } = fixture._meta;
const { translate_x, translate_y, rotate_deg, scale, filter_mode, fill_mode, fill_color, expand } = fixture.params;
const canvas = createCanvas(width, height);
const ctx = canvas.getContext("2d");
const image = ctx.createImageData(width, height);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const [r, g, b, a] = fixture.input_rgba[y][x];
const offset = (y * width + x) * 4;
image.data[offset] = r;
image.data[offset + 1] = g;
image.data[offset + 2] = b;
image.data[offset + 3] = a;
}
}
ctx.putImageData(image, 0, 0);
const result = applyTransform(ctx, width, height, translate_x, translate_y, rotate_deg, scale, filter_mode, expand, fill_mode, fill_color);
const resultCtx = (result ?? canvas).getContext("2d");
const actual = resultCtx.getImageData(0, 0, width, height).data;
let maxDiff = 0;
let worst = null;
// The fixture's params zoom in deliberately so every sample stays inside the
// source bounds (see tests/golden/transform.json's _meta.description) — this
// isolates the core rotate/scale bilinear sampling math from a separate, real
// gap at frame edges where content maps outside the source (torch's
// grid_sample blends smoothly into zero-padding there; the frontend's
// per-pixel sampler does a hard inside/outside cutoff instead).
for (let y = margin; y < height - margin; y++) {
for (let x = margin; x < width - margin; x++) {
const [er, eg, eb, ea] = fixture.expected_rgba[y][x];
const offset = (y * width + x) * 4;
for (const [channel, expected] of [[0, er], [1, eg], [2, eb], [3, ea]]) {
const diff = Math.abs(actual[offset + channel] - expected);
if (diff > maxDiff) {
maxDiff = diff;
worst = { x, y, channel, expected, actual: actual[offset + channel] };
}
}
}
}
assert.ok(
maxDiff <= tolerance,
`applyTransform diverges from the backend by ${maxDiff} (tolerance ${tolerance}) at ${JSON.stringify(worst)}`,
);
});