Nodes/kubakub nodes/kubakub canvas plan
ComfyUI Node

kubakub canvas plan

Stop stretching your 4K matrix to fit a model's grid

By kubakubkub·Created about a month ago·Updated 3 days ago· 10
kubakub canvas plan
  • size_from
  • canvas_plan
  • width
  • height
  • frames
  • fps
  • plan_json
  • report
  • k
  • target_width
  • target_height
◄target_width3840►
◄target_height2160►
◄model_familyflux2►
◄k2►
◄budget_mp0.00►
◄integer_kfalse►
◄target_frames0►
◄pad_aligncenter►
◄max_pad_percent2.0►
◄divisor_override0►

What it's for

Models don't generate at your delivery size. They generate at a size that divides by 16 or 32, at roughly a megapixel, with a frame count that follows a rule like 8k+1. Your matrix is 3840×2160 because that's what the building's projector wants.

The lazy way to bridge that gap is to resize your matrix down to something the model accepts and resize the result back up. That stretches it - 3840×2160 doesn't divide cleanly into 1024×1024, so faces widen, circles become ellipses, and windows subtly change aspect. On a facade that lands as a visible mismatch at the edges.

kubakub canvas plan is the arithmetic node that avoids it. It works out a work size that is target ÷ k for one uniform factor k, padded up to the model's grid, never stretched. You give it the target size and it hands you the numbers.

This is the boring node in the canvas trio, and it's the one that decides whether the other two are useful. The KB's resolution-multiple lore (Flux wants multiples of 64, native resolutions matter more than big ones) is the same problem in different clothes.

How it works

It computes target / k, rounds up to the model family's divisor, and reports exactly how much padding that cost. Every output is a number you can wire, plus a report you should actually read once per project.

Practical examples straight out of the docs for a 3840×2160 matrix: flux2 with k=2 gives 1920×1088, k=3 gives a clean 1280×720, ltxav with k=4 gives 960×544, minimax_h3 with k=3 gives 1280×736.

k = "auto" is the interesting one. It picks the factor from your megapixel budget, and with integer_k on it will only consider whole factors - which you need for the classic latent ×2 upscale path, where a fractional factor would break the pixel grid. max_pad_percent (default 2) is the sanity valve: auto k skips factors that would need more padding than that.

Give it a k that doesn't divide the target into whole pixels and it refuses, listing the nearest factors that do. That's the feature - silently accepting 2.3 and rounding is how you get a half-pixel offset on a 4K matrix.

Inputs and outputs

Set these:

  • target_width / target_height - the matrix, or whatever your delivery is. Or feed an image into size_from and it takes the size from that instead.
  • model_family - flux2 (divisor 16, ~1 MP), ltxav (32, 8k+1 frames), minimax_h3 (32, 17k+5 frames, 24 fps), sd_8 (8). divisor_override forces another grid, e.g. 64.
  • k - a number, a fraction like 15/4, or auto.
  • target_frames - video only; rounded up to the model's frame rule, and canvas restore trims the extras. 0 means still.
  • pad_align - where the padding goes: center, right/bottom, or left/top. Center unless you have a reason.

Everything else is tuning. budget_mp sets the auto-k megapixel target (0 = the model's trained budget).

Wire width and height into your Empty Latent (they're the padded model-grid size), and the canvas_plan output into canvas to work and canvas restore - all three nodes share the same plan object, so a mismatch there is a type error, not a silent bug. frames and fps go to the latent and the video save. k comes back as a float, which is handy after auto; plan_json and report are for saving and reading.

Install

Manager → kubakub nodes. Or:

cd ComfyUI/custom_nodes
git clone https://github.com/kubakubkub/ComfyUI_KubaNodes
python -m pip install -r ComfyUI_KubaNodes/requirements.txt

ComfyUI 0.37.0+; only OpenCV is added. No model, no Blender. There's a model-free test for the whole geometry module:

python ComfyUI/custom_nodes/ComfyUI_KubaNodes/tests/test_geometry.py

(portable build: python_embeded\python.exe ....)

Common issues

  • "k must divide the target into whole pixels". Working as intended. Read the nearest factors it lists, or let auto choose.
  • The padding is bigger than you expected. That's the price of a grid-aligned work size. Lower k (more work pixels) or accept a max_pad_percent bump if the frame rule, not the divisor, is what's costing you.
  • Padding is visible in the output. It shouldn't be - canvas to work controls what the model sees in the padding and canvas restore crops it back off. If you see a band of replicated pixels in your final, you either skipped restore or fed a different plan.
  • Frames came back long by a few. Video families round frame counts up, not down. Restore trims them with trim_frames on.
Categorykubakub/2d/regions/canvas

Inputs (11)

NameTypeDefaultDescription
target_widthINT384016–32768Width of the final image in pixels (the matrix = the full projection image at delivery size).
target_heightINT216016–32768Height of the final image in pixels.
model_familyCOMBOflux2flux2: divisor 16, ~1 MP. ltxav: divisor 32, 8k+1 frames. minimax_h3: divisor 32, 17k+5 frames, 24 fps. sd_8: divisor 8.
kSTRING2Downscale factor target -> work. A number (2, 2.5), a fraction (15/4) or 'auto' to pick from the MP budget. It must divide the target into whole pixels.
budget_mpFLOAT0.000–16auto k: target megapixels on the model grid. 0 = the model's trained budget.
integer_kBOOLEANfalseauto k: only whole factors (needed for latent x2 paths).
target_framesINT00–10000Video models: frames wanted. Rounded up to the model's frame rule; Canvas Restore trims the extra frames. 0 = still.
pad_alignCOMBOcenterWhere the grid padding goes: split on both sides, or all at the end (right/bottom) or start (left/top).
max_pad_percentFLOAT2.00–50auto k: skip factors needing more padding (extra area in percent).
divisor_overrideINT00–256Force another size grid (e.g. 64). 0 = the model's own.
size_fromoptIMAGETake the target size from this image instead of the widgets.

Outputs (10)

NameTypeDescription
canvas_planKUBA_CANVAS_PLANThe plan; connect to canvas to work and canvas restore.
widthINTModel grid width (padded). Use for the empty latent.
heightINTModel grid height (padded).
framesINTFrames to generate (1 for stills).
fpsFLOATFrame rate the model generates at.
plan_jsonSTRINGThe plan as text, for saving or checking.
reportSTRINGReadable summary: work size, padding, k, frames.
kFLOATThe downscale factor that was used (target = work x k), e.g. 2.0 or 3.75. Useful after 'auto'.
target_widthINTWidth of the final image in pixels (from the widget or size_from).
target_heightINTHeight of the final image in pixels.