comfyui_imgtools
A ComfyUI custom node for automatically fitting images to standard model dimensions using center cropping, supporting SD, Flux, and Z-Turbo models.
Nodes (11)
Affinity Photo's denoise filter, rebuilt as a ComfyUI node
Snap any size to the resolution your model actually wants
Padding math for outpainting, without the mental arithmetic
The downscale that doesn't leave diagonal ghosts
Your LoRA training folder, loaded with captions intact
Load Image, but the .txt caption comes along for free
Bake two LoRAs into one file — exactly, not approximately
The boring node that saves you ten minutes of string surgery
Seed-controlled random prompt fragments, with per-option toggles
Let this node detect the aspect ratio for you
Save PNGs with matching captions — build a LoRA dataset in one pass
comfyui_rholdorf
A small ComfyUI custom-node pack: image processing (denoise, resize), resolution helpers, LoRA training-data loaders/savers, a LoRA merger, and a text utility.
All nodes are labelled ... (rholdorf) in the node menu and live under a
rholdorf/... category.
Installation
Clone or copy this folder into your ComfyUI custom_nodes directory:
ComfyUI/
custom_nodes/
comfyui_rholdorf/
Restart ComfyUI. There is no build step and no extra dependency — everything runs on what ComfyUI already ships (torch, numpy, Pillow).
Changes to Python require a ComfyUI restart; changes to the JS under web/
only need a browser refresh.
Nodes
| Node | Category | Purpose |
|---|---|---|
| Image Denoise (Affinity style) | rholdorf/image | Wavelet denoise with Affinity Photo's five sliders |
| Image Resize (Lanczos 3 non-separable) | rholdorf/image | Radial Lanczos downscale, no diagonal artifacts |
| Image Dimension Fitter | rholdorf/image | Snap a size to the closest native resolution of a model |
| Image Padding Calculator | rholdorf/image | Padding needed to centre an image in a target size |
| Load Image with Caption | rholdorf/image | Load Image + its sidecar .txt caption |
| Load Images with Captions from Dir | rholdorf/image | Batch-load a folder of image/caption pairs |
| Save Image with Caption | rholdorf/image | Save PNG + matching .txt caption |
| Resolution Selector from Dimensions | rholdorf/utils | Resolution Selector with auto-detected aspect ratio |
| Path Splitter | rholdorf/utils | Split a path into directory / filename / stem |
| Merge LoRAs to File | rholdorf/lora | Bake two LoRAs into one .safetensors |
| Random Line Concatenator | rholdorf/text | Pick a random line from a toggleable pool |
Image Denoise (Affinity style)
Reproduces the control surface of Affinity Photo 2's Denoise filter. Not bit-identical (the original is proprietary), but the same five knobs with an equivalent response curve.
The image is converted to BT.709 YCbCr and decomposed with an à trous (starlet) wavelet transform; detail levels are shrunk with the non-negative garrote, which leaves strong coefficients — edges — untouched instead of dulling them the way soft thresholding would. Luma and chroma are filtered independently, which is what lets you crush colour noise hard without touching detail.
| Input | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | Input image |
| luminance | FLOAT 0–100 | 30 | Luma noise reduction strength |
| luminance_detail | FLOAT 0–100 | 50 | Higher keeps more fine texture (and more grain) |
| luminance_contribution | FLOAT 0–100 | 100 | Blend of the filtered luma over the original |
| colours | FLOAT 0–100 | 50 | Chroma noise reduction strength |
| colours_contribution | FLOAT 0–100 | 100 | Blend of the filtered chroma over the original |
| levels | INT 1–7 (optional) | 5 | Wavelet depth; auto-capped to the image size |
Output: IMAGE.
Notes:
- Luma and chroma use different noise models on purpose. Luma assumes white noise (sigma measured once at the finest scale), which keeps coarse structure safe. Chroma re-estimates sigma per level, because colour blotching is spatially correlated and carries almost no energy at the finest scale — with the luma model it would read as "no noise" and nothing would be removed.
- Chroma depth is capped relative to the image size; past that point the filter starts flattening real colour gradients.
- Runs on CPU or GPU (whatever device the tensor is on). ~1–3 s for 4K on CPU.
contribution = 0or strength0is an exact pass-through.
Typical starting points: 30 / 50 / 100 / 50 / 100 for light grain, around
60 / 50 / 100 / 70 / 100 for heavy high-ISO noise. If detail goes mushy,
raise luminance_detail before lowering luminance.
Image Resize (Lanczos 3 non-separable)
Downscale with a radial Lanczos-3 kernel: the kernel is evaluated on the 2D
distance sqrt(dx² + dy²) rather than applied as separable passes along X then
Y. The result is circularly symmetric and free of the diagonal artifacts the
separable form introduces — this matches Affinity Photo's "Lanczos 3
(non-separable)" resampling.
| Input | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | Input image |
| max_width | INT | 1024 | Bounding width |
| max_height | INT | 1024 | Bounding height |
Outputs: image, width, height.
The result fits inside the bounds preserving aspect ratio and never upscales — an image that already fits is passed through untouched.
Image Dimension Fitter
Given a reference width/height, returns the closest native resolution for
the selected model, matched by aspect-ratio distance. Ties resolve to the
smaller total-pixel candidate, so it never picks a larger resolution than needed.
| Input | Type | Default | Description |
|---|---|---|---|
| width | INT | 1024 | Reference width |
| height | INT | 1024 | Reference height |
| model | SD / Flux / Z-Turbo / Krea 2 | SD | Target model |
Outputs: target_width, target_height — feed them into Empty Latent Image or
any node that takes dimensions.
Resolution tables:
- SD (~262k px, /8): 512×512, 640×512, 512×640, 704×512, 512×704, 768×512, 512×768
- Flux (~1MP, /32): 1024×1024, 1152×896, 1216×832, 1344×768, 1536×640, 1920×1080 (+ portrait counterparts)
- Z-Turbo: same as Flux minus the 1920×1080 pair
- Krea 2 (~1MP, /32): 1024×1024, 1152×864, 1248×832, 1344×768, 1120×896 (+ portrait counterparts)
Krea does not publish exact pixel dimensions per ratio, so the Krea 2 table is derived from its documented ratios targeting ~1MP, each side floored to a multiple of 32 — approximating downward on purpose.
Image Padding Calculator
Padding needed to centre an image inside a target size.
| Input | Type | Default |
|---|---|---|
| image | IMAGE | — |
| target_width | INT | 1024 |
| target_height | INT | 1024 |
Outputs: left, top, right, bottom. Odd leftovers go to the right/bottom
side. An image already at or above the target gets zero padding on that axis.
Load Image with Caption
Mirrors the built-in Load Image (dropdown plus upload button) and
additionally returns the sidecar .txt caption. See
image + caption pairs.
| Input | Type | Description |
|---|---|---|
| image | combo | File from the input/ folder |
Outputs: image, mask, caption.
The node adds an "upload pair (.png + .txt)" button: multi-select the image
and its .txt in the file dialog (Cmd/Ctrl+click) and both are uploaded at
once. If ComfyUI renames the image because input/ already had that name, the
.txt is renamed to match — otherwise the pair would silently desync.
Load Images with Captions from Dir
Batch equivalent of the above: replicates the Inspire pack's Load Image List From Dir (same inputs, same per-item list semantics, images at their original size) and adds the caption output.
| Input | Type | Default | Description |
|---|---|---|---|
| directory | STRING | — | Folder to scan (.jpg, .jpeg, .png, .webp) |
| image_load_cap | INT (optional) | 0 | Stop after N images (0 = no limit) |
| start_index | INT (optional) | 0 | Skip N files from the start of the sorted list |
| load_always | BOOLEAN (optional) | false | Re-read the folder every run, defeating ComfyUI's cache |
| sort_method | combo (optional) | None | Alphabetical / Numerical / Datetime, ASC or DESC |
Outputs: image, mask, caption, file_path — all lists
(OUTPUT_IS_LIST), so downstream nodes execute once per item instead of
receiving a batched tensor.
Raises if the directory is missing or contains no images; a missing .txt is
not an error.
Save Image with Caption
Mirrors the built-in SaveImage and writes a matching UTF-8 .txt beside
each PNG.
| Input | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | — | Images to save |
| caption | STRING (multiline) | "" | Caption text |
| filename_prefix | STRING | ComfyUI | Supports %date:yyyy-MM-dd%, %Empty Latent Image.width%, etc. |
| caption_in | STRING (optional, input-only) | — | Connected upstream string; overrides the widget when present |
Output node — no outputs. Uses ComfyUI's standard get_save_image_path
prefix/counter logic and respects --disable-metadata.
Resolution Selector from Dimensions
Same math as ComfyUI's built-in Resolution Selector, but the aspect ratio is
auto-detected from a reference (width, height) pair instead of picked from a
combo.
| Input | Type | Default | Description |
|---|---|---|---|
| width | INT | 1024 | Reference width (only used to detect the ratio) |
| height | INT | 1024 | Reference height |
| megapixels | FLOAT 0.1–16 | 1.0 | Target total megapixels |
| multiple | INT 8–128 | 8 | Snap the result to this multiple |
Outputs: width, height, aspect_ratio (label, also drawn on the node face).
Ratios recognised: 1:1, 2:3, 3:2, 3:4, 4:3, 9:16, 16:9, 21:9. Detection uses
log-space distance so 2:3 and 3:2 are treated symmetrically — linear distance in
w/h biases toward landscape.
Path Splitter
| Input | Type |
|---|---|
| path | STRING |
Outputs: directory, filename, stem.
Merge LoRAs to File
Bakes two LoRAs into a single .safetensors, reproducing exactly what applying
both in sequence does (à la rgthree's Power Lora Loader).
| Input | Type | Default | Description |
|---|---|---|---|
| lora_name1 / lora_name2 | combo | — | LoRAs to combine |
| strength1 / strength2 | FLOAT | 0.5 | Strength baked in for each |
| filename_prefix | STRING | merged_lora | Output name, relative to the loras folder |
| target_rank | INT (optional) | 0 | 0 = exact; a positive value recompresses via truncated SVD |
Outputs: path, retained_energy. Also an output node — the retained-energy
percentage is displayed on the node body after execution.
Use the merged file at strength 1.0. The output alpha is set equal to the
merged rank so the load-time scale is exactly 1.0.
How it works: because [Up_A | Up_B] @ [Down_A ; Down_B] == Up_A@Down_A + Up_B@Down_B, concatenating the up matrices along the rank dimension (with each
LoRA's strength and alpha/rank scale folded into the up side) and the down
matrices along rank yields the exact weighted sum of both deltas. No SVD, no
approximation. Ranks may differ — 32 + 64 gives a merged rank of 96.
target_rank trades exactness for file size: a positive value recompresses the
merged delta to that rank via truncated SVD (Eckart–Young optimal). The
recompression works on the small factors, so it never materialises the full
weight matrix and stays cheap on big layers. retained_energy reports the
average retained Frobenius energy as a quality signal.
Supported: standard lora_up/lora_down (+ alpha) and the other up/down
naming schemes ComfyUI recognises, plus direct .diff/.diff_b deltas.
Refused: LoHa, LoKr, OFT, DoRA and LoCon-mid files — those aren't a plain
up @ down product, so the node errors rather than emit a wrong result.
Random Line Concatenator
Picks one line at random from a toggleable pool and concatenates it between optional prefix/suffix strings.
| Input | Type | Default | Description |
|---|---|---|---|
| seed | INT | 0 | With the standard control-after-generate dropdown |
| adjust_whitespace | BOOLEAN | true | Collapse whitespace runs and join parts with a single space |
| start | STRING | "" | Prepended before the chosen line |
| random_choice | STRING | [] | The options pool — edit through the rows in the node UI |
| end | STRING | "" | Appended after the chosen line |
| text_in | STRING (optional, input-only) | — | Upstream string, placed before everything else |
Output: text.
The pool is edited through a custom widget (checkbox + text + delete per row). Disabled rows and blank rows are skipped when picking. An empty pool yields an empty choice rather than an error.
Image + caption pairs
Several nodes here share one convention used for LoRA training data: an image
(.png, .jpg, .jpeg, .webp) and a UTF-8 .txt caption with the same
base name in the same directory:
dataset/
imagem001.png
imagem001.txt
imagem002.png <- no caption; loads as ""
Matching is by stem only, and the caption side is optional — a missing .txt is
read as an empty string, never an error, because real training folders routinely
contain unlabelled images.
Save Image with Caption writes these pairs; Load Image with Caption and
Load Images with Captions from Dir read them back.
License
MIT