Eses Image Resize
One node for every resizing job, mask included
- image
- upscale_model
- mask
- ref_image
- ref_mask
- IMAGE
- MASK
- width
- height
- metadata
Every ComfyUI graph ends up with a pile of resize nodes. There's the one that scales by ratio, the one that scales to megapixels, the one that letterboxes, the one that handles masks - and you can never remember which is which. Eses Image Resize is the answer to that specific annoyance: one node that does pretty much every resizing task you'll hit, plus it hands you a matching mask and the final dimensions on the side. The author's own pitch in the r/comfyui release thread nailed it: "a single node that doesn't do anything new, but does everything in a single node." It's not clever. It's convenient - after a week you'll have quietly stopped reaching for three other nodes.
Where it fits: right after VAE decode when you're prepping an image for img2img at a model-native resolution, before a ControlNet preprocessor that wants specific dimensions, or as the front end of an upscale pass. The default divisible_by of 8 points at the same thing - models like latents on clean multiples.
How it works
The mechanism is unglamorous in the best way: a thin Pillow wrapper. It takes your IMAGE tensor, converts it to a PIL image, does the resize / crop / letterbox math with Pillow's resampling filters, and converts back. That's exactly why it's so light - PyTorch, Pillow, and numpy, all of which ComfyUI already ships. No model files, no downloads, no API keys.
The one non-trivial piece is the optional upscale_model input. Wire one in and, if the target is bigger than your source, the node runs the model iteratively via ComfyUI's tiled_scale (512px tiles, halving automatically if you run out of VRAM), stopping just before it would overshoot the target and finishing with an interpolation pass to the exact size. Without a model it just interpolates, which is fine for most things.
The mask output deserves a shout-out. Lots of resize nodes drop the mask on the floor, which bites you later when you're feeding a resized image+mask pair into inpaint or compositing. Here the MASK output is always populated: a connected mask gets resized with the image (nearest-neighbor), and if you didn't connect one you get a black mask of the new dimensions.
The inputs that actually matter
scale_mode is the boss of this node. Six modes: multiplier (0.01–100x), megapixels (target total pixel count), megapixels_with_ar (target megapixels at a specific aspect ratio via ar_width/ar_height, e.g. 16:9), target_width, target_height, and both_dimensions. Keep an eye on keep_aspect_ratio - in the width/height modes it's on by default, and flipping it off lets the image distort to hit the target.
The other two you'll actually touch:
crop_to_fitvsfit_to_frame- leave both off and a mismatched aspect ratio just distorts; crop_to_fit fills the target frame and crops the excess from the center (no distortion), while fit_to_frame letterboxes instead. The bars takeletterbox_color(hex like000000or255,0,0), andletterbox_mask_is_whitedecides whether the padded area reads as active or inactive in the mask.divisible_by(default 8) - rounds final dimensions to a multiple; set to 0 to disable.
And ref_image / ref_mask are a nice touch: connect an image and the node just uses its dimensions as the target - a built-in replacement for plumbing a "get size" node. Added within a day of someone asking for it in the release thread.
Outputs are IMAGE, MASK, width, height, and a metadata JSON string documenting what it did. Those width/height INTs are handy to wire into other nodes without a separate size node.
Installing
Easiest is ComfyUI Manager - search "EsesImageResize" and install. Or the manual route:
cd ComfyUI/custom_nodes
git clone https://github.com/quasiblob/ComfyUI-EsesImageResize
Restart ComfyUI and it shows up under "Eses Nodes/Image". No requirements.txt to babysit, nothing to download - that's the selling point.
Where people trip up
The most common confusion is baked into the design. keep_aspect_ratio and crop_to_fit look contradictory, but they're not: content never distorts by default, and crop_to_fit is how you hit a target aspect ratio. Set megapixels_with_ar to 3:4 with crop_to_fit on, and you get a 3:4 canvas with the original content undistorted inside it. That exact exchange happened in the node's release thread, so you're not alone if it took you a minute.
Two things the docs don't lead with: it processes only the first image in a batch (with a warning if you feed it more), and if your mask's dimensions don't match the image it silently resizes the mask to match. Also worth knowing: the default interpolation is area, which is great for downscaling but not the crispiest for upscaling - switch to lanczos or bicubic when you're going bigger. And the pack ships under a custom "My ComfyUI Nodes" license: free to use, but no rebranding and no redistributing modifications. Fine for personal use.
It's a small, niche tool - almost no search traffic, updated every few months by someone who built it for themselves. But it's honest work: no bloat, one job, done well, with a mask in hand at the end.
Inputs (19)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| scale_mode | COMBO | 6 options: multiplier, megapixels, target_width, target_height, both_dimensions, megapixels_with_ar | |
| interpolation_method | COMBO | 5 options: area, bilinear, bicubic, lanczos, nearest-neighbor | |
| upscale_modelopt | UPSCALE_MODEL | — | |
| maskopt | MASK | — | |
| ref_imageopt | IMAGE | If connected, use this image's dimensions as the target width and height. | |
| ref_maskopt | MASK | If connected, use this mask's dimensions as the target. Overridden by Reference Image. | |
| multiplieropt | FLOAT | 1.000.01–100 | Multiplies original dimensions by this factor (Scale Mode: ratio) |
| megapixelsopt | FLOAT | 2.000.01–100 | Sets target total pixels in megapixels (Scale Mode: megapixels/megapixels_with_ar) |
| target_widthopt | INT | 5128–8192 | Target width in pixels (Scale Mode: target_width/both_dimensions) |
| target_heightopt | INT | 5128–8192 | Target height in pixels (Scale Mode: target_height/both_dimensions) |
| ar_widthopt | INT | 161–4096 | Aspect ratio width component (Scale Mode: megapixels_with_ar) |
| ar_heightopt | INT | 91–4096 | Aspect ratio height component (Scale Mode: megapixels_with_ar) |
| keep_aspect_ratioopt | BOOLEAN | true | If true, preserves original aspect ratio when using target_width/height modes (otherwise may distort) |
| crop_to_fitopt | BOOLEAN | false | Scales and crops the image to fill the target dimensions (no letterboxing). Takes priority over 'Fit to Frame'. |
| fit_to_frameopt | BOOLEAN | false | Scales the image to fit entirely within the target dimensions, adding colored bars (letterboxing). Overridden by 'Crop to Fit'. |
| letterbox_coloropt | STRING | 0,0,0 | Color for letterboxing/padding (Hex: RRGGBB, RRGGBBAA; or RGB/RGBA: 255,255,255,255). Default: Black. |
| letterbox_mask_is_whiteopt | BOOLEAN | false | If 'Fit to Frame' is active, sets the padded area in the output mask to white (255); otherwise, it's black (0). |
| divisible_byopt | INT | 80–64 | Rounds final dimensions to be divisible by this number. Set to 0 to disable. |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |
| MASK | MASK | — |
| width | INT | — |
| height | INT | — |
| metadata | STRING | — |