From 877a5ad7762afa98bf24c37dc8bc1f3ece7c5a98 Mon Sep 17 00:00:00 2001 From: dreevelle <123705989+dreevelle@users.noreply.github.com> Date: Sun, 9 Aug 2026 14:14:00 +0100 Subject: [PATCH] docs: downscale guidance for fixed-canvas delivery Generating on the exact-ratio lattice and downscaling beats generating at the delivery size and cropping. 1920x1080 is exactly 16:9 and so is every lattice row, so the whole table resizes onto it with no crop -- 1920x1088 is the only entry needing one, being the only non-16:9 size. Downscaling is also supersampling, which a 1:1 render plus a trim isn't. Filter choice measured on a zone plate against an antialiased reference: lanczos wins (RMSE 0.00298 at 0.9375x), bicubic second. Notably `area` is ~10x worse than bicubic despite the usual "area is the downscale filter" advice -- that only holds for large reductions; a box filter at a non-integer factor near 1 weights unevenly. Documents lanczos's 8-bit quantisation in comfy/utils.py and when it matters. Recommends ImageScale over ImageScaleBy: identical code path, but scale_by's 0.01 step rounds four of seven lattice factors to sizes that aren't 1080p, and a pinned delivery size is correct for every row without edits while a hardcoded factor is correct for exactly one. Adds wiring for both models and the alignment=32 simplification (legal on Krea 2 and H3 simultaneously). All 79 table rows verified by parsing them back out of the README: megapixel rows re-derived through the node, crop rows checked for exact 16:9 and uniform scale, ImageScaleBy rows confirmed to actually produce the wrong sizes claimed. Zero mismatches. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CdgKVDqEbD14EJ4RC3GPmg --- README.md | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/README.md b/README.md index c3a5458..5e6373c 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,119 @@ is the node default. 1.33 MP (1536×864) is a mild ~29% stretch and the highest worth running by default. 2.36 MP is 2.3× the trained area and 3.69 MP is 3.6×; motion coherence degrades before sharpness improves. +## Delivering to a fixed screen size + +If your output has to land on a fixed canvas — a 1920×1080 game screen, for instance — +generate on the exact-ratio lattice and **downscale**. Do not generate at the delivery +size and crop. + +1920×1080 is exactly 16:9, and so is every row of the lattice, so the whole table +resizes onto it with no crop at all: + +| Generate | MP | → 1920×1080 | Crop | +|---|---|---|---| +| 1280 × 720 | 0.92 | ×1.5 upscale | none | +| 1536 × 864 | 1.33 | ×1.25 upscale | none | +| 1792 × 1008 | 1.81 | ×1.0714 upscale | none | +| 2048 × 1152 | 2.36 | ×0.9375 downscale | none | +| 2304 × 1296 | 2.99 | ×0.8333 downscale | none | +| 2560 × 1440 | 3.69 | ×0.75 downscale | none | +| 1920 × 1088 | 2.09 | x:1.0 / y:0.9926 | **8 px** | + +1920×1088 is the only entry needing a crop, because it is the only one that isn't 16:9 +(1.7647). Generating over-size and downscaling is also **supersampling** — you get free +anti-aliasing on hair, eyelashes and fabric that a 1:1 render plus a trim never gives +you. 2048×1152 is the pick for Krea 2; 2560×1440 → ×0.75 is an unusually clean resample +(4 pixels become 3) if you want more of it. + +### Which filter + +Measured on a zone plate (frequencies rising toward the edges, so aliasing shows up as +error) against a properly antialiased reference: + +| Filter | RMSE, 2048×1152 → 1920×1080 | RMSE, 2560×1440 → 1920×1080 | +|---|---|---| +| **lanczos** | **0.00298** | **0.00325** | +| bicubic | 0.00577 | 0.00365 | +| bilinear | 0.00650 | 0.00418 | +| area | 0.04291 | 0.03082 | +| nearest-exact | 0.04198 | 0.03787 | + +**Use `lanczos`.** Two caveats: + +- **`area` is a trap.** The usual advice is "area is the downscale filter," and here it + is ~10× worse than bicubic. That advice only holds for large reductions. A box filter + at a non-integer factor near 1 averages one input pixel for some outputs and two for + others — uneven weighting, visible as blotchiness. +- **`lanczos` quantises to 8-bit.** `comfy/utils.py` routes it through PIL via + `Image.fromarray(np.clip(255. * ..., 0, 255).astype(np.uint8))`. Irrelevant for 8-bit + delivery (PNG / WebP / VP9), but if you keep a 16-bit master, save it *before* the + scale node. To stay in float through the resize, use `bicubic` — second best, small + gap. + +### Use `Upscale Image`, not `Upscale Image By` + +Pin the delivery size, not the scale factor. `ImageScaleBy` computes +`round(dim * scale_by)` and then calls the same `common_upscale` as `ImageScale`, so the +pixels are identical — but its `scale_by` widget has `step: 0.01`, and the rounded +factors are wrong for most of the table: + +| Source | typed at step 0.01 | result | +|---|---|---| +| 1792 × 1008 | 1.07 | 1917 × 1079 ✗ | +| 2048 × 1152 | 0.94 | 1925 × 1083 ✗ | +| 2304 × 1296 | 0.83 | 1912 × 1076 ✗ | +| 2816 × 1584 | 0.68 | 1915 × 1077 ✗ | + +Feeding the factor from a full-precision float node avoids that, but buys nothing: +`ImageScale` at `1920 × 1080` is exact by construction, needs no extra node, and is +correct for *every* lattice row without edits — whereas a hardcoded factor is correct +for exactly one source resolution and goes silently stale the moment you change the +megapixel target. + +The lattice already did the work upstream by making the source exactly 16:9. `0.9375` +is just what `1920/2048` happens to equal; it isn't a number you need to carry around. + +### Wiring it up + +Stills (Krea 2): + +``` +Resolution Selector (Real MP) aspect 16:9 · megapixels 2.36 · alignment 32 · exact_ratio true + │ width, height → 2048 × 1152 + ▼ +EmptySD3LatentImage → KSampler → VAEDecode + ▼ +Upscale Image lanczos · width 1920 · height 1080 · crop disabled + ▼ +Save Image +``` + +`EmptySD3LatentImage` is the right latent node for Krea 2 — `[B, 16, H//8, W//8]`, +matching its 16-channel Wan21 latent format. + +Video (MiniMax H3): same front end into the H3 nodes' `width`/`height`. `Upscale Image` +handles a `[B,H,W,C]` batch frame by frame, but if you are dumping a PNG sequence and +encoding with ffmpeg anyway, fold the resize into that pass instead — it avoids an +intermediate 8-bit round trip and ffmpeg's lanczos is full precision: + +```bash +ffmpeg -framerate 24 -i raw_%05d.png \ + -vf "scale=1920:1080:flags=lanczos,format=yuv420p" \ + -c:v libvpx-vp9 -crf 28 -b:v 0 -row-mt 1 \ + out.webm +``` + +24 fps matches H3's internal `FPS = 24`. + +### One simplification + +Set **`alignment = 32` for everything.** 32 is a multiple of 16, so every size it +produces is legal on Krea 2 *and* MiniMax H3 — one configuration for a whole project +instead of tracking which model needs which grid. The lattice is coarser, but at 16:9 +the useful rows are all still there (1024×576, 1536×864, 2048×1152, 2560×1440), and +2048×1152 comes out identical either way. + ## Nodes ### Resolution Selector (Real MP)