Jet Image Scale
Scale, Crop, or Letterbox in One Node — and Your Mask Comes Along
- image
- mask
- image
- mask
- original_size
- width
- height
If you've ever built the resize-then-pad-then-math chain in vanilla ComfyUI just to get an image to a target aspect ratio, you know how fiddly it is. Jet Image Scale collapses all of that into one node: pick a ratio, pick how to fit it, and out comes a correctly sized image and a correctly aligned mask. No model downloads, no extra Python, just one file that does a job ComfyUI makes surprisingly annoying on its own.
Aspect ratio is where most of the classic failure modes live. Models are trained at specific ratios - SDXL's 1024x1024 and friends - and the standard trick from the KB is to generate at a nearby trained ratio, then crop or pad to your target (7:4 → crop to 16:9, for instance). That final crop/pad step is exactly what this node automates.
What it actually does
It takes an IMAGE (batch, height, width, channels) and optionally a MASK, and resizes to a target aspect ratio in one of three ways:
- crop - scale so the image covers the target box, then center-crop the excess. This is the one you want for "generate at 7:4, output 16:9."
- letterbox - scale so the image fits inside the box, then pad the leftover bars with a color you choose. Think YouTube's black bars, or padding a 1:1 generation up to 16:9 for a video workflow.
- fill - just stretch/squeeze to the exact size. This distorts, and it's supposed to. Handy for compositing, rarely for final output.
Under the hood it converts each frame through PIL, does the resize with a real resampling filter (default lanczos, also bicubic, hamming, bilinear, nearest), and converts back to a tensor. It handles grayscale, RGB, and RGBA inputs. The mask gets resized, cropped, or padded with the exact same geometry, so it stays pixel-aligned with the image - that's the feature that stops you hand-rolling a broken version.
The inputs that matter
You'll touch maybe five of these:
aspect_ratio- presets for 1:1, 3:2, 4:3, 16:9 and their rotations, plusoriginal(no ratio change, just resize) andcustom. Portrait is9:16, not16:9; the ratio is always width:height.proportional_width/proportional_height- the two ints that definecustom. Want 21:9? 21 and 9.fit-crop,letterbox, orfill, above.scale_to_side+scale_to_length- this sets the size, not just the shape.longest/shortestanchor on the longest/shortest edge toscale_to_length(default 1280), or you can pinwidth/heightdirectly.noneis the subtle one: it keeps the image's original pixel scale and only adjusts the canvas to the target ratio.background_color- hex for the letterbox bars. Defaults to#000000, so if you're padding a white-background workflow, set it.
The optional mask in: resized with the image, so it's safe to feed into inpainting or ControlNet conditioning downstream. Even without a mask it still emits one (all zeros) at the new size, so the output wires cleanly.
Outputs
image and mask are the obvious ones. original_size (a TUPLE of the pre-resize width, height) is handy if you later want to go back to source dimensions. And width / height as plain ints let you feed exact numbers into an empty-latent node or a text prompt that wants the real size.
Installing it
The boring, reliable way:
cd ComfyUI/custom_nodes
git clone https://github.com/JetterTW/ComfyUI-JetImageScale.git
Then restart ComfyUI. ComfyUI Manager finds it as ComfyUI-JetImageScale if you'd rather click. There's no requirements.txt and no models to fetch - the whole node is one Python file using only torch, numpy, and PIL, all of which ComfyUI already ships. It's about as low-risk a custom node as exists in an ecosystem where that's worth checking.
Where people get tripped up
The biggest misunderstanding: this resizes, it doesn't upscale. Lanczos interpolation is a great "more pixels, source already sharp" tool - the KB still recommends it there - but it can't invent detail. If you're upscaling for quality, run this for the layout and a real upscaler (SeedVR2 or an ESRGAN variant) for the pixels.
Also remember it's a single node, not a full workflow: if your letterboxed image is going into a model, match the latent size too, or the bars you added get resampled again. And check background_color before you wonder why your padding is black.
It won't change your life, but the next time a workflow demands a specific ratio, you won't be digging out a resize node, a pad node, and a calculator. That's a win.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| aspect_ratio | COMBO | original | 9 options: original, custom, 1:1, 3:2, 4:3, 16:9, +3 |
| proportional_width | INT | 11–99999 | — |
| proportional_height | INT | 11–99999 | — |
| fit | COMBO | crop | 3 options: crop, letterbox, fill |
| method | COMBO | lanczos | 5 options: lanczos, bicubic, hamming, bilinear, nearest |
| scale_to_side | COMBO | longest | 5 options: none, longest, shortest, width, height |
| scale_to_length | INT | 12801–99999 | — |
| background_color | STRING | #000000 | — |
| maskopt | MASK | — |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| mask | MASK | — |
| original_size | TUPLE | — |
| width | INT | — |
| height | INT | — |