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:
@@ -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.
|
||||
|
||||
@@ -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
@@ -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 📐"}
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user