Files
ausboss-ComfyUI-AusBoss/tests/pad_canvas.test.mjs
T
ausboss 415ca971ad Load Image + Pad: no strip along an edge nobody padded
Rounding the canvas to Multiple put the leftover on the right and bottom
whatever was padded, so the published Krea 2 Outpaint workflow (top and
bottom padded, Multiple 16, Budget 1.6) grew a 6 px grey strip down the
photo's right edge. Krea painted it flat, the feather opened the photo
beside it, and Stitch's colour match read it as a seam.

The leftover now joins a padded side (the far one when both are). An
axis nobody padded is scaled onto the multiple when a Budget is set (one
uniform scale) and trimmed evenly without one; the node's canvas shows
the trim as -N px. The JS preview (pad_canvas.mjs / pad_panel.mjs)
mirrors the Python plan to the pixel. Crop + Rotate + Pad keeps its old
rule for now.
2026-09-26 22:22:14 -05:00

210 lines
8.9 KiB
JavaScript

import assert from "node:assert/strict";
import test from "node:test";
import {
EDGE_HIT_BAND,
THIN_BAND_PX,
canvasHeightForWidth,
edgeCursor,
finalOutputSize,
fitRect,
hitPadEdge,
labelMode,
padDragValue,
padGeometry,
parseImageReference,
stageGeometry,
} from "../js/shared/pad_canvas.mjs";
test("image references parse subfolders, backslashes, and type annotations", () => {
assert.deepEqual(parseImageReference("photo.png"), {
filename: "photo.png",
subfolder: "",
type: "input",
});
assert.deepEqual(parseImageReference("sub/dir/photo.png"), {
filename: "photo.png",
subfolder: "sub/dir",
type: "input",
});
assert.deepEqual(parseImageReference("sub\\dir\\photo.png [temp]"), {
filename: "photo.png",
subfolder: "sub/dir",
type: "temp",
});
assert.deepEqual(parseImageReference("clip.png [output]"), {
filename: "clip.png",
subfolder: "",
type: "output",
});
assert.equal(parseImageReference(""), null);
assert.equal(parseImageReference(" "), null);
assert.equal(parseImageReference(null), null);
});
const NO_TRIM = { trimLeft: 0, trimTop: 0, trimRight: 0, trimBottom: 0 };
const pads = (left, top, right, bottom, canvas_multiple, target_megapixels = 0) => ({
pad_left: left, pad_top: top, pad_right: right, pad_bottom: bottom, canvas_multiple, target_megapixels,
});
test("both padded sides keep the multiple remainder on the far side", () => {
const geom = padGeometry(800, 600, {
pad_left: 10, pad_top: 20, pad_right: 30, pad_bottom: 40, canvas_multiple: 8,
});
// Pinned against nodes/_pad_helpers.py resolve_pad_geometry — the same
// numbers appear in tests/test_pad_helpers.py so drift breaks both.
assert.deepEqual(geom, {
left: 10, top: 20, right: 30, bottom: 44, outputWidth: 840, outputHeight: 664, ...NO_TRIM,
});
});
test("one padded side takes the whole remainder", () => {
// Pinned in tests/test_pad_helpers.py too.
assert.deepEqual(padGeometry(100, 64, pads(20, 0, 0, 0, 16)), {
left: 28, top: 0, right: 0, bottom: 0, outputWidth: 128, outputHeight: 64, ...NO_TRIM,
});
assert.deepEqual(padGeometry(100, 64, pads(0, 0, 20, 0, 16)), {
left: 0, top: 0, right: 28, bottom: 0, outputWidth: 128, outputHeight: 64, ...NO_TRIM,
});
assert.deepEqual(padGeometry(128, 100, pads(0, 30, 0, 0, 64)), {
left: 0, top: 92, right: 0, bottom: 0, outputWidth: 128, outputHeight: 192, ...NO_TRIM,
});
assert.deepEqual(padGeometry(128, 100, pads(0, 0, 0, 30, 64)), {
left: 0, top: 0, right: 0, bottom: 92, outputWidth: 128, outputHeight: 192, ...NO_TRIM,
});
});
test("an axis nobody padded trims the source instead of growing a strip", () => {
assert.deepEqual(padGeometry(1130, 638, pads(0, 271, 0, 509, 16)), {
left: 0, top: 271, right: 0, bottom: 515, outputWidth: 1120, outputHeight: 1424,
trimLeft: 5, trimTop: 0, trimRight: 5, trimBottom: 0,
});
assert.deepEqual(padGeometry(1000, 750, pads(0, 0, 0, 0, 64)), {
left: 0, top: 0, right: 0, bottom: 0, outputWidth: 960, outputHeight: 704,
trimLeft: 20, trimTop: 23, trimRight: 20, trimBottom: 23,
});
const odd = padGeometry(1001, 64, pads(0, 0, 0, 0, 16));
assert.deepEqual([odd.trimLeft, odd.trimRight, odd.outputWidth], [4, 5, 992]);
// Smaller than one multiple: nothing to trim to, so it grows on the far side.
const tiny = padGeometry(10, 750, pads(0, 0, 0, 0, 16));
assert.deepEqual([tiny.right, tiny.outputWidth, tiny.trimTop, tiny.trimBottom], [6, 16, 7, 7]);
});
test("the published Krea 2 Outpaint canvas has no strip, to the pixel of the Python plan", () => {
// plan_pad_canvas(2720, 1536, 0, 652, 0, 1212, 16, 1.6): the width is
// resized onto the multiple and sets the scale, where it used to leave a
// 6 px strip on the right.
const bridge = finalOutputSize(2720, 1536, pads(0, 652, 0, 1212, 16, 1.6));
assert.deepEqual(bridge, { width: 1136, height: 1424, scale: 1136 / 2720 });
assert.deepEqual(finalOutputSize(2720, 1536, pads(0, 652, 0, 1212, 64, 1.6)), {
width: 1088, height: 1408, scale: 0.4,
});
// Padded top and right: the height's remainder joins the top.
const rain = finalOutputSize(502, 634, pads(0, 240, 320, 0, 16, 1.6));
assert.deepEqual([rain.width, rain.height], [1216, 1296]);
assert.equal(rain.scale, 1.4782809899727065);
// Nothing padded: each side snaps to the nearest multiple.
const plain = finalOutputSize(1000, 750, pads(0, 0, 0, 0, 16, 1.0));
assert.deepEqual([plain.width, plain.height], [1152, 864]);
});
test("a side under half a multiple keeps the budget scale", () => {
// plan_pad_canvas(2000, 20, 100, 0, 100, 0, 64, 0.25): snapping 26 px up
// to 64 would more than double the scale, so it grows on the far side.
assert.deepEqual(finalOutputSize(2000, 20, pads(100, 0, 100, 0, 64, 0.25)), {
width: 2944, height: 64, scale: Math.sqrt(0.25e6 / (2240 * 64)),
});
});
test("the stage draws an unpadded axis edge to edge when a megapixel target resizes it", () => {
const values = pads(0, 271, 0, 509, 16);
assert.deepEqual(stageGeometry(1130, 638, values), padGeometry(1130, 638, values));
const resized = stageGeometry(1130, 638, { ...values, target_megapixels: 1.6 });
assert.deepEqual(resized, {
left: 0, top: 271, right: 0, bottom: 515, outputWidth: 1130, outputHeight: 1424, ...NO_TRIM,
});
// Also below one multiple, where the geometry alone would grow a strip.
const tiny = stageGeometry(10, 64, pads(0, 8, 0, 8, 16, 1.0));
assert.deepEqual([tiny.right, tiny.outputWidth], [0, 10]);
});
test("final output size mirrors the Python megapixel plan to the pixel", () => {
const values = {
pad_left: 10, pad_top: 20, pad_right: 30, pad_bottom: 40,
canvas_multiple: 8, target_megapixels: 1.0,
};
const final = finalOutputSize(800, 600, values);
// Pinned against plan_pad_canvas(800, 600, 10, 20, 30, 40, 8, 1.0).
assert.equal(final.width, 1128);
assert.equal(final.height, 888);
assert.ok(Math.abs(final.scale - 1.3389868666385072) < 1e-9);
// The badge lands within multiple-rounding distance of the target.
assert.ok(Math.abs((final.width * final.height) / 1e6 - 1.0) < 0.02);
});
test("megapixels off is a passthrough of the multiple-rounded size", () => {
const final = finalOutputSize(800, 600, {
pad_left: 10, pad_top: 20, pad_right: 30, pad_bottom: 40,
canvas_multiple: 8, target_megapixels: 0,
});
assert.deepEqual(final, { width: 840, height: 664, scale: 1 });
});
test("the whole edge is the handle and corners resolve to the nearer edge", () => {
const rect = { x: 100, y: 100, width: 200, height: 150 };
assert.equal(hitPadEdge({ x: 100, y: 175 }, rect), "left");
assert.equal(hitPadEdge({ x: 108, y: 130 }, rect), "left");
assert.equal(hitPadEdge({ x: 300, y: 175 }, rect), "right");
assert.equal(hitPadEdge({ x: 200, y: 96 }, rect), "top");
assert.equal(hitPadEdge({ x: 240, y: 252 }, rect), "bottom");
// Corners: the strictly nearer edge wins.
assert.equal(hitPadEdge({ x: 102, y: 99 }, rect), "top");
assert.equal(hitPadEdge({ x: 99, y: 104 }, rect), "left");
// Middle of the rect and far outside are not handles (clicks fall through).
assert.equal(hitPadEdge({ x: 200, y: 175 }, rect), null);
assert.equal(hitPadEdge({ x: 400, y: 175 }, rect), null);
assert.equal(hitPadEdge({ x: 200, y: 175 - 0 }, rect, 100), "top"); // wider band reaches further
assert.equal(hitPadEdge({ x: 200, y: 175 }, null), null);
assert.ok(EDGE_HIT_BAND >= 12); // generous by contract
});
test("drag deltas map to raw pads with outward-positive signs and a floor at 0", () => {
const start = { left: 10, top: 0, right: 5, bottom: 20 };
assert.equal(padDragValue("left", start, -30, 0), 40);
assert.equal(padDragValue("left", start, 30, 0), 0);
assert.equal(padDragValue("right", start, 30.4, 0), 35);
assert.equal(padDragValue("top", start, 0, -12), 12);
assert.equal(padDragValue("bottom", start, 0, 12), 32);
assert.equal(padDragValue("bottom", start, 0, -100), 0);
});
test("labels hop onto the pill exactly below the thin-band threshold", () => {
assert.equal(labelMode(THIN_BAND_PX), "band");
assert.equal(labelMode(THIN_BAND_PX - 0.1), "pill");
assert.equal(labelMode(0), "pill");
assert.equal(labelMode(400), "band");
});
test("cursors match the drag axis", () => {
assert.equal(edgeCursor("left"), "ew-resize");
assert.equal(edgeCursor("right"), "ew-resize");
assert.equal(edgeCursor("top"), "ns-resize");
assert.equal(edgeCursor("bottom"), "ns-resize");
assert.equal(edgeCursor(null), "");
});
test("panel height tracks width inside the clamp", () => {
assert.equal(canvasHeightForWidth(200), 180);
assert.equal(canvasHeightForWidth(400), 264);
assert.equal(canvasHeightForWidth(2000), 520);
});
test("fitRect centers the world rect at the limiting scale", () => {
assert.deepEqual(fitRect(100, 50, 200, 100, 0), { scale: 2, x: 0, y: 0 });
const fit = fitRect(100, 100, 220, 120, 10);
assert.equal(fit.scale, 1); // limited by height: (120-20)/100
assert.equal(fit.x, 60);
assert.equal(fit.y, 10);
});