kubakub canvas plan
Stop stretching your 4K matrix to fit a model's grid
- size_from
- canvas_plan
- width
- height
- frames
- fps
- plan_json
- report
- k
- target_width
- target_height
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 intosize_fromand it takes the size from that instead.model_family-flux2(divisor 16, ~1 MP),ltxav(32,8k+1frames),minimax_h3(32,17k+5frames, 24 fps),sd_8(8).divisor_overrideforces another grid, e.g. 64.k- a number, a fraction like15/4, orauto.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
autochoose. - The padding is bigger than you expected. That's the price of a grid-aligned work size. Lower
k(more work pixels) or accept amax_pad_percentbump 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_frameson.
Inputs (11)
| Name | Type | Default | Description |
|---|---|---|---|
| target_width | INT | 384016–32768 | Width of the final image in pixels (the matrix = the full projection image at delivery size). |
| target_height | INT | 216016–32768 | Height of the final image in pixels. |
| model_family | COMBO | flux2 | flux2: divisor 16, ~1 MP. ltxav: divisor 32, 8k+1 frames. minimax_h3: divisor 32, 17k+5 frames, 24 fps. sd_8: divisor 8. |
| k | STRING | 2 | Downscale 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_mp | FLOAT | 0.000–16 | auto k: target megapixels on the model grid. 0 = the model's trained budget. |
| integer_k | BOOLEAN | false | auto k: only whole factors (needed for latent x2 paths). |
| target_frames | INT | 00–10000 | Video models: frames wanted. Rounded up to the model's frame rule; Canvas Restore trims the extra frames. 0 = still. |
| pad_align | COMBO | center | Where the grid padding goes: split on both sides, or all at the end (right/bottom) or start (left/top). |
| max_pad_percent | FLOAT | 2.00–50 | auto k: skip factors needing more padding (extra area in percent). |
| divisor_override | INT | 00–256 | Force another size grid (e.g. 64). 0 = the model's own. |
| size_fromopt | IMAGE | Take the target size from this image instead of the widgets. |
Outputs (10)
| Name | Type | Description |
|---|---|---|
| canvas_plan | KUBA_CANVAS_PLAN | The plan; connect to canvas to work and canvas restore. |
| width | INT | Model grid width (padded). Use for the empty latent. |
| height | INT | Model grid height (padded). |
| frames | INT | Frames to generate (1 for stills). |
| fps | FLOAT | Frame rate the model generates at. |
| plan_json | STRING | The plan as text, for saving or checking. |
| report | STRING | Readable summary: work size, padding, k, frames. |
| k | FLOAT | The downscale factor that was used (target = work x k), e.g. 2.0 or 3.75. Useful after 'auto'. |
| target_width | INT | Width of the final image in pixels (from the widget or size_from). |
| target_height | INT | Height of the final image in pixels. |