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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CdgKVDqEbD14EJ4RC3GPmg
This commit is contained in:
co-authored by
Claude Opus 5
parent
5970c4000f
commit
877a5ad776
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user