Update README and refactor node categories to Craftopia

- Revised README to clarify node functionalities and typical use cases.
- Changed category for SmartResize, SmartBatchResize, and SmartSizeMultiplier nodes to "Craftopia".
This commit is contained in:
rubz
2026-03-03 13:00:32 +01:00
parent aa2e2bd16c
commit 98dbd02632
4 changed files with 96 additions and 98 deletions
+45 -23
View File
@@ -1,6 +1,8 @@
# ComfyUI-SmartResize
Two focused resize nodes for ComfyUI — both with **Lanczos** interpolation and **multiple_of** snapping.
Smart image resize nodes for ComfyUI — batch processing, LoRA prep & SeedVR support.
All nodes appear under the **Craftopia** category in the node menu.
---
@@ -9,9 +11,13 @@ Two focused resize nodes for ComfyUI — both with **Lanczos** interpolation and
### 📐 Smart Resize
Resize any image (or batch) so the **longest side** equals `max_pixels`, with aspect ratio preserved.
Use this as a **pipeline node** — IMAGE in, IMAGE out. Perfect for downscaling SeedVR upscales before LoRA training, or any workflow step where you need a clean downscale without saving to disk.
Use this as a **pipeline node** — IMAGE in, IMAGE out. No files are saved to disk.
**Typical usecases:**
- Downscale a SeedVR (or other upscaler) result back to your target training size — the upscaler adds fine detail, Smart Resize brings it to the right dimensions for LoRA training
- Any workflow step that needs a clean resize before the next node
- Normalize mixed-resolution batches to a consistent size
**Inputs:**
| Input | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | Single image or batch |
@@ -20,55 +26,71 @@ Use this as a **pipeline node** — IMAGE in, IMAGE out. Perfect for downscaling
| interpolation | ENUM | lanczos | lanczos / bicubic / bilinear / nearest |
| upscale_if_smaller | BOOLEAN | false | Also upscale images already smaller than max_pixels |
**Outputs:** `IMAGE`, `width`, `height`
**Outputs:** `image`, `width`, `height`
---
### 📁 Batch Resize & Save
Load **all images from a folder**, resize each one (longest side), and save them with the original filename + a suffix into a subfolder.
### 📁 Smart Batch Resize
Load **all images from a folder**, resize each one by longest side, and save with the original filename + a suffix into a subfolder. Original filenames are always preserved.
Use this for **bulk preprocessing** — e.g. preparing a LoRA dataset from a folder of high-res images.
**Inputs:**
| Input | Type | Default | Description |
|---|---|---|---|
| input_folder | STRING | D:/images/mijnmap | Source folder path |
| input_folder | STRING | D:/images/input | Source folder path |
| max_pixels | INT | 1536 | Target size for longest side |
| filename_suffix | STRING | _Small | Appended to each output filename |
| filename_suffix | STRING | _Small | Appended to each output filename before the extension |
| output_subfolder | STRING | resized | Subfolder created inside input_folder |
| multiple_of | INT | 8 | Snap dimensions to this multiple |
| interpolation | ENUM | lanczos | lanczos / nearest / bilinear / bicubic |
| interpolation | ENUM | lanczos | lanczos / bicubic / bilinear / nearest |
| skip_if_exists | BOOLEAN | false | Skip files that already exist in output |
**Outputs:** `images` (list), `filenames` (list), `output_paths` (list), `count`
---
### 📏 Smart Size Multiplier
Multiply image dimensions by a factor and output the results as integers.
SeedVR takes a single `resolution` INT (the longest side) — not a width and height separately. Standard math nodes give you a FLOAT or require multiple steps to get there. This node does it cleanly in one step: give it your image and a multiplier, and it outputs `width`, `height`, and `resolution` (longest side) ready to connect directly to SeedVR's `max_resolution` input.
Also useful for LoRA dataset prep — using mixed resolutions in your training set generally produces better results than training on a single fixed size. Smart Size Multiplier makes it easy to dynamically calculate the right target size per image rather than hardcoding a value.
| Input | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | Source image |
| multiplier | FLOAT | 2.0 | Multiply width and height by this factor |
| multiple_of | INT | 8 | Snap dimensions to this multiple |
**Outputs:** `width`, `height`, `resolution` (longest side as INT → directly into SeedVR)
---
## Why these nodes?
Existing nodes like `JWImageResizeByLongerSide` lack **Lanczos** interpolation (the best choice for downscaling). Other batch nodes don't preserve original filenames or offer `multiple_of` snapping.
These nodes fill that gap with a minimal, focused feature set.
- ComfyUI's built-in `Resize Images by Longer Edge` [BETA] has no Lanczos and no `multiple_of` snapping
- `JWImageResizeByLongerSide` (comfyui-various) has no Lanczos in the official release
- No existing node combines batch folder loading + longest-side resize + original filename preservation
- No existing node outputs a ready-to-use `resolution` INT for SeedVR
---
## Installation
**Option A — ComfyUI Manager:**
Search for `ComfyUI-SmartResize` and install.
**ComfyUI Manager:** search for `ComfyUI-SmartResize` and install.
**Option B — Manual:**
**Manual:**
```bash
cd ComfyUI/custom_nodes
git clone https://github.com/YOUR_USERNAME/ComfyUI-SmartResize
git clone https://github.com/CraftopiaStudio/ComfyUI-SmartResize
```
Restart ComfyUI. Nodes appear under:
- `image/transform` → **Smart Resize 📐**
- `image/batch` → **Batch Resize & Save 📁**
Restart ComfyUI. All nodes appear under **Craftopia** in the node menu:
- `Craftopia` → **Smart Resize 📐**
- `Craftopia` → **Smart Batch Resize 📁**
- `Craftopia` → **Smart Size Multiplier 📏**
---
## Requirements
- ComfyUI
- Pillow (included with ComfyUI)
- PyTorch (included with ComfyUI)
Pillow, NumPy, PyTorch — all included with ComfyUI. No extra dependencies.
+1 -1
View File
@@ -74,7 +74,7 @@ class SmartBatchResize:
RETURN_NAMES = ("images", "filenames", "output_paths", "count")
OUTPUT_IS_LIST = (True, True, True, False)
FUNCTION = "run"
CATEGORY = "SmartResize"
CATEGORY = "Craftopia"
OUTPUT_NODE = True
def run(self, input_folder, max_pixels, filename_suffix,
+49 -73
View File
@@ -1,48 +1,53 @@
import math
from PIL import Image as PILImage
import numpy as np
import torch
INTERP_MAP = {
"lanczos": PILImage.LANCZOS,
"bicubic": PILImage.BICUBIC,
"bilinear": PILImage.BILINEAR,
"nearest": PILImage.NEAREST,
}
def _calc_new_size(w, h, max_pixels, multiple_of, upscale_if_smaller):
longest = max(w, h)
if longest <= max_pixels and not upscale_if_smaller:
new_w, new_h = w, h
else:
scale = max_pixels / longest
new_w = int(w * scale)
new_h = int(h * scale)
if multiple_of > 1:
new_w = max(multiple_of, round(new_w / multiple_of) * multiple_of)
new_h = max(multiple_of, round(new_h / multiple_of) * multiple_of)
return new_w, new_h
class SmartResize:
"""
Resize images so the longest side equals max_pixels.
Supports lanczos interpolation and multiple_of snapping.
Works with single images and batches.
Resize IMAGE by longest side. Lanczos included. Multiple_of snapping.
Works with single images and batches. IMAGE in → IMAGE out.
"""
INTERP_MAP = {
"lanczos": PILImage.LANCZOS,
"bicubic": PILImage.BICUBIC,
"bilinear": PILImage.BILINEAR,
"nearest": PILImage.NEAREST,
}
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"max_pixels": ("INT", {
"default": 1536,
"min": 64,
"max": 8192,
"step": 8,
"tooltip": "Longest side will be resized to this value (aspect ratio preserved)"
"default": 1536, "min": 64, "max": 8192, "step": 8,
"tooltip": "Longest side target in pixels. Aspect ratio is always preserved."
}),
"multiple_of": ("INT", {
"default": 8,
"min": 1,
"max": 64,
"step": 1,
"tooltip": "Round dimensions to a multiple of this value (8 = SD compatible)"
}),
"interpolation": (["lanczos", "bicubic", "bilinear", "nearest"], {
"default": "lanczos"
"default": 8, "min": 1, "max": 64, "step": 1,
"tooltip": "Snap both dimensions to a multiple of this value. Use 8 for SD/Flux."
}),
"interpolation": (["lanczos", "bicubic", "bilinear", "nearest"],),
"upscale_if_smaller": ("BOOLEAN", {
"default": False,
"tooltip": "Also upscale images that are already smaller than max_pixels"
"tooltip": "Upscale images that are already smaller than max_pixels."
}),
}
}
@@ -50,60 +55,31 @@ class SmartResize:
RETURN_TYPES = ("IMAGE", "INT", "INT")
RETURN_NAMES = ("image", "width", "height")
FUNCTION = "run"
CATEGORY = "image/transform"
def _resize_one(self, img_tensor, max_pixels, multiple_of, interp, upscale_if_smaller):
"""Resize a single [H, W, C] tensor."""
h, w = img_tensor.shape[0], img_tensor.shape[1]
longest = max(w, h)
# Skip if already small enough and upscale disabled
if longest <= max_pixels and not upscale_if_smaller:
# Still snap to multiple_of
new_w = max(multiple_of, round(w / multiple_of) * multiple_of) if multiple_of > 1 else w
new_h = max(multiple_of, round(h / multiple_of) * multiple_of) if multiple_of > 1 else h
if new_w == w and new_h == h:
return img_tensor, w, h
else:
scale = max_pixels / longest
new_w = int(w * scale)
new_h = int(h * scale)
# Snap to multiple_of
if multiple_of > 1:
new_w = max(multiple_of, round(new_w / multiple_of) * multiple_of)
new_h = max(multiple_of, round(new_h / multiple_of) * multiple_of)
# Convert tensor [H, W, C] float32 → PIL → resize → tensor
arr = (img_tensor.numpy() * 255).astype(np.uint8)
pil = PILImage.fromarray(arr, mode="RGB")
pil_resized = pil.resize((new_w, new_h), interp)
arr_resized = np.array(pil_resized).astype(np.float32) / 255.0
return torch.from_numpy(arr_resized), new_w, new_h
CATEGORY = "Craftopia"
def run(self, image, max_pixels, multiple_of, interpolation, upscale_if_smaller):
# image shape: [B, H, W, C]
interp = self.INTERP_MAP[interpolation]
interp = INTERP_MAP[interpolation]
results = []
last_w, last_h = 0, 0
for i in range(image.shape[0]):
resized, w, h = self._resize_one(
image[i], max_pixels, multiple_of, interp, upscale_if_smaller
)
results.append(resized)
last_w, last_h = w, h
frame = image[i] # [H, W, C]
h, w = frame.shape[0], frame.shape[1]
new_w, new_h = _calc_new_size(w, h, max_pixels, multiple_of, upscale_if_smaller)
# Stack back to [B, H, W, C] — all frames must be same size
# (they will be if all input frames are same size, which is the ComfyUI norm)
output = torch.stack(results, dim=0)
return (output, last_w, last_h)
if new_w == w and new_h == h:
results.append(frame)
else:
arr = (frame.numpy() * 255).astype("uint8")
pil = PILImage.fromarray(arr, mode="RGB")
pil = pil.resize((new_w, new_h), interp)
arr = np.array(pil).astype("float32") / 255.0
results.append(torch.from_numpy(arr))
last_w, last_h = new_w, new_h
return (torch.stack(results, dim=0), last_w, last_h)
NODE_CLASS_MAPPINGS = {
"SmartResize": SmartResize,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"SmartResize": "Smart Resize 📐",
}
NODE_CLASS_MAPPINGS = {"SmartResize": SmartResize}
NODE_DISPLAY_NAME_MAPPINGS = {"SmartResize": "Smart Resize 📐"}
+1 -1
View File
@@ -31,7 +31,7 @@ class SmartSizeMultiplier:
RETURN_TYPES = ("INT", "INT", "INT")
RETURN_NAMES = ("width", "height", "resolution")
FUNCTION = "run"
CATEGORY = "SmartResize"
CATEGORY = "Craftopia"
def run(self, image, multiplier, multiple_of):
h, w = image.shape[1], image.shape[2]