Initial commit: ComfyUI Resolution Suggest & Downscale

This commit is contained in:
unknown
2025-12-12 12:00:54 +01:00
commit 6bb4e14716
8 changed files with 568 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
name: Publish to Comfy registry
on:
workflow_dispatch:
push:
branches:
- main
paths:
- "pyproject.toml"
permissions:
issues: write
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
# Safety: only run publish from your own repo user
if: ${{ github.repository_owner == 'TuonoMindCode' }}
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@v1
with:
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
+24
View File
@@ -0,0 +1,24 @@
# Python
__pycache__/
*.pyc
*.pyo
*.pyd
*.egg-info/
.venv/
venv/
.env
.env.*
dist/
build/
# macOS / Windows
.DS_Store
Thumbs.db
# IDE
.vscode/
.idea/
# ComfyUI cache/temp (if any)
cache/
temp/
+155
View File
@@ -0,0 +1,155 @@
![Resolution Suggest](resolution_suggest.PNG)
![Resolution Suggest 02](resolution_suggest_02.PNG)
# Tuono Resolution Suggest & Downscale
This is a simple resolution suggester that downscales images. It preserves aspect ratio and uses hardcoded suggested resolutions that were provided by ChatGPT 5.1 based on common resolutions and common downscaled targets. None of the suggested or common resolutions in this program were authored by me; they were provided by ChatGPT.
Use the output info: STRING with diagnostic details, for easy debugging
The entire code was generated by ChatGPT 5.1, I have no programming experience.
And rest of this readme file is done by Chatgpt.
A ComfyUI custom node that helps you pick sensible downscaled resolutions for an input image. It preserves the aspect ratio, snaps to a chosen multiple (8/16/32/64), and never upscales beyond the source size.
- Input: Image tensor
- Output: Passthrough image, target width/height, and a human-readable info string
---
## Highlights
- Multiple-of snapping profiles: 8, 16, 32, 64
- Two ways to choose the target size:
- Percentage presets (e.g., 10–60% smaller)
- Resolution suggestions from a static table for common input sizes
- Never upscales beyond the original resolution
- Structurized info string shows what happened (profile, preset, scale/reduction, raw/scanned sizes, suggestions)
---
## Inputs
- image: ComfyUI IMAGE tensor (passthrough)
- model_profile:
- multiple_of: 8 (SD / General (8))
- multiple_of: 16 (WAN 2.2 / Strict (16))
- multiple_of: 32 (Video / Advanced (32))
- multiple_of: 64 (Legacy / Extra Safe (64))
- Default: 16
- scale_preset:
- Percentage-based:
- 0% smaller (keep original)
- 10% smaller
- 20% smaller
- 30% smaller
- 40% smaller
- 50% smaller
- 60% smaller
- Default: 30% smaller
- Resolution suggestions:
- Resolution suggestion 1 (highest from table)
- Resolution suggestion 2 (lower from table)
- Resolution suggestion 3 (lowest from table)
- Resolution suggestion 4 (very low VRAM)
Notes:
- Percentage presets compute a scale factor, apply to the input, then snap to the chosen multiple_of.
- Suggestion presets try known resolutions for the current input from an internal table; if the input is unknown:
- Suggestion 1 → ~80%
- Suggestion 2 → ~70%
- Suggestion 3 → ~60%
- Suggestion 4 → ~50%
---
## Outputs
- image: Passthrough of the original IMAGE tensor
- width: INT target width (snapped)
- height: INT target height (snapped)
- info: STRING with diagnostic details, for easy debugging
Example info:
multiple_of: 16 (WAN 2.2 / Strict (16)) | scale_preset=Resolution suggestion 1, reduction≈12.5% (scale≈0.8750) | input image: 1024x1024 | result: raw 896x896 -> snapped 896x896 | suggested/common resolutions: 896x896, 768x768, 640x640 | less common lower resolution (same aspect ratio): 512x512
---
## How sizes are picked
- Aspect ratio preserved
- Snapping:
- Target dimensions are rounded down to the nearest multiple_of
- Never exceeds the original size (no upscaling)
- Suggestions:
- For known inputs (e.g., 1920×1080, 1280×720, 1024×1024, 896×1152, etc.), the node consults a static table of common downscaled resolutions
- Suggestion 1/2/3 → first/second/third entry from the table (highest to lower)
- Suggestion 4 → always a dynamically computed ~50% downscale (snapped), intended for very low VRAM
---
## Installation
1. Go to your ComfyUI `custom_nodes` directory
2. Clone this repository into it:
```
ComfyUI/
custom_nodes/
ComfyUI-TuonoResolutionSuggestDownscale/
tuono_resolution_suggest_downscale.py
README.md
pyproject.toml
.github/
workflows/
publish.yaml
```
3. Restart ComfyUI
You should find the node as:
- Name: Resolution Suggest & Downscale
- Category: Resolution/Utilities
---
## Usage
Typical workflow:
- Load Image → connect IMAGE to Tuono Resolution Suggest & Downscale.image
- Choose:
- model_profile (e.g., multiple_of: 16)
- scale_preset (e.g., Resolution suggestion 2 or a percentage preset)
- Connect:
- width → Resize Image (V2).width
- height → Resize Image (V2).height
- Optional:
- Connect info → Show Text/Print for debugging
- Use the passthrough image output to continue the graph
---
## Examples
- Input: 1920×1080, model_profile=16, scale_preset=30% smaller
- Raw scaled: 1344×756 → snapped: 1344×752 (snaps down to multiples of 16)
- Input: 1024×1024, model_profile=16, scale_preset=Resolution suggestion 1
- From table: 896×896 → snapped: 896×896
- Input: 1280×720, model_profile=16, scale_preset=Resolution suggestion 2
- From table: 1024×576 → snapped: 1024×576
---
## Tips and notes
- If a dimension snaps slightly lower than expected, it’s due to rounding down to the nearest multiple_of (by design, to avoid upscaling and stay compatible with model constraints).
- Suggestion 4 is a quick “very low VRAM” option if you need to reduce memory usage aggressively.
- For unknown input sizes, suggestions still work via approximate scaling; the info output will indicate this.
---
## License
MIT (unless otherwise specified).
+5
View File
@@ -0,0 +1,5 @@
# __init__.py for ComfyUI-Resolution-Suggest-Downscale
# Expose node mappings from the main implementation file.
from .tuono_resolution_suggest_downscale import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
+18
View File
@@ -0,0 +1,18 @@
[project]
name = "comfyui-resolution-suggest-downscale"
version = "0.1.0"
description = "Resolution suggestion and downscale helper node for ComfyUI."
readme = "README.md"
requires-python = ">=3.10"
license = { text = "MIT" }
dependencies = []
[project.urls]
Repository = "https://github.com/TuonoMindCode/ComfyUI-Resolution-Suggest-Downscale"
[tool.comfy]
PublisherId = "TODO_PUBLISHER_ID"
DisplayName = "Resolution Suggest & Downscale"
Icon = ""
Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

+338
View File
@@ -0,0 +1,338 @@
# tuono_resolution_suggest_downscale.py
#
# Resolution Suggest & Downscale
#
# - Chooses multiple_of from a model profile (8 / 16 / 32 / 64).
# - Either:
# * Downscales image by a percentage preset, OR
# * Uses "Resolution suggestion 1/2/3/4" based on:
# - static table (1–3)
# - dynamic very low VRAM (4)
# - Preserves aspect ratio.
# - Snaps width/height to the chosen multiple_of.
# - Info output shows:
# * multiple_of: N (Profile name)
# * scale_preset + reduction/scale
# * input image: WxH
# * result: raw WxH -> snapped WxH
# * suggested/common resolutions + less common lower resolution
#
# Typical flow:
# Load Image ─────────────┐
# │
# (image) ▼
# Resolution Suggest & Downscale ─► width/height ─► Resize Image (V2)
# │
# └─► info (STRING)
class TuonoResolutionSuggestDownscale:
"""
Resolution Suggest & Downscale
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"model_profile": (
[
"multiple_of: 8 (SD / General (8))",
"multiple_of: 16 (WAN 2.2 / Strict (16))",
"multiple_of: 32 (Video / Advanced (32))",
"multiple_of: 64 (Legacy / Extra Safe (64))",
],
{
"default": "multiple_of: 16 (WAN 2.2 / Strict (16))",
},
),
"scale_preset": (
[
"0% smaller (keep original) (1920x1080→1920x1080, 1280x720→1280x720)",
"10% smaller (1920x1080→1728x972, 1280x720→1152x648)",
"20% smaller (1920x1080→1536x864, 1280x720→1024x576)",
"30% smaller (1920x1080→1344x756, 1280x720→896x504)",
"40% smaller (1920x1080→1152x648, 1280x720→768x432)",
"50% smaller (1920x1080→960x540, 1280x720→640x360)",
"60% smaller (1920x1080→768x432, 1280x720→512x288)",
"Resolution suggestion 1 (highest from table)",
"Resolution suggestion 2 (lower from table)",
"Resolution suggestion 3 (lowest from table)",
"Resolution suggestion 4 (very low VRAM)",
],
{
"default": "30% smaller (1920x1080→1344x756, 1280x720→896x504)",
},
),
},
}
RETURN_TYPES = ("IMAGE", "INT", "INT", "STRING")
RETURN_NAMES = ("image", "width", "height", "info")
FUNCTION = "calc"
CATEGORY = "Resolution/Utilities"
# --- Helpers: core ---
def _snap_dim(self, value, multiple):
if value <= 0:
return multiple
v = int(value)
snapped = (v // multiple) * multiple
if snapped < multiple:
snapped = multiple
return snapped
def _profile_to_multiple(self, model_profile: str):
"""
Map dropdown string to (multiple_of, profile_label)
profile_label används bara i info-texten.
"""
if model_profile.startswith("multiple_of: 8"):
return 8, "SD / General (8)"
if model_profile.startswith("multiple_of: 16"):
return 16, "WAN 2.2 / Strict (16)"
if model_profile.startswith("multiple_of: 32"):
return 32, "Video / Advanced (32)"
if model_profile.startswith("multiple_of: 64"):
return 64, "Legacy / Extra Safe (64)"
# fallback
return 8, "SD / General (8)"
# --- Helpers: percentage presets ---
def _scale_from_percentage_preset(self, scale_preset: str):
"""
Convert percentage scale presets to (reduction_percent, scale).
"""
if scale_preset.startswith("0%"):
reduction = 0.0
elif scale_preset.startswith("10%"):
reduction = 10.0
elif scale_preset.startswith("20%"):
reduction = 20.0
elif scale_preset.startswith("30%"):
reduction = 30.0
elif scale_preset.startswith("40%"):
reduction = 40.0
elif scale_preset.startswith("50%"):
reduction = 50.0
elif scale_preset.startswith("60%"):
reduction = 60.0
else:
reduction = 0.0
scale = 1.0 - (reduction / 100.0)
if scale < 0.1:
scale = 0.1
return reduction, scale
def _scale_and_snap(self, w, h, scale, multiple_of):
raw_w = w * scale
raw_h = h * scale
snapped_w = self._snap_dim(round(raw_w), multiple_of)
snapped_h = self._snap_dim(round(raw_h), multiple_of)
if snapped_w > w:
snapped_w = self._snap_dim(w, multiple_of)
if snapped_h > h:
snapped_h = self._snap_dim(h, multiple_of)
return int(snapped_w), int(snapped_h), int(round(raw_w)), int(round(raw_h))
# --- Helpers: common resolutions table ---
def _get_common_resolution_table(self):
return {
# 4K and high resolutions
(3840, 2160): "2560x1440, 1920x1080, 1280x720, 960x540, 854x480",
(3440, 1440): "2560x1080, 2560x1440, 1920x810, 1600x720, 1280x540",
(2560, 1440): "1920x1080, 1600x900, 1280x720, 1024x576, 960x540",
(2560, 1080): "1920x810, 1920x800, 1600x900, 1280x720, 1024x576",
(2048, 1152): "1920x1080, 1600x900, 1280x720, 1024x576, 960x540",
# SDXL / Civitai-style portrait & landscape
(1024, 1024): "896x896, 768x768, 640x640",
(1152, 896): "1024x800, 960x768, 896x736",
(896, 1152): "768x992, 672x864, 576x736",
(1216, 832): "1152x768, 1024x704, 896x640",
(832, 1216): "768x1120, 704x1024, 640x928",
(1344, 768): "1216x704, 1152x672, 1024x576",
(768, 1344): "704x1216, 672x1152, 576x1024",
(1536, 640): "1344x576, 1216x512, 1024x448",
(640, 1536): "576x1344, 512x1216, 448x1024",
(640, 960): "576x864, 512x768, 480x720",
(768, 1152): "704x1056, 640x960, 576x864",
(800, 1200): "704x1056, 640x960, 576x864",
(832, 1216): "768x1120, 704x1024, 640x928",
# Classic 16:9 landscape
(1920, 1080): "1600x900, 1536x864, 1280x720, 1024x576, 960x540",
(1720, 720): "1600x720, 1280x720, 1024x576, 960x540, 854x480",
(1680, 1050): "1600x900, 1440x900, 1280x800, 1280x720, 1024x640",
(1600, 900): "1440x810, 1280x720, 1024x576",
(1536, 864): "1440x810, 1280x720, 1024x576",
(1366, 768): "1280x720, 1152x648, 1024x576",
(1360, 768): "1280x720, 1152x648, 1024x576",
(1280, 720): "1152x648, 1024x576, 960x540",
(1270, 720): "1152x648, 1024x576, 960x540",
(1024, 576): "960x540, 854x480, 768x432",
(960, 540): "854x480, 848x480, 800x450",
(854, 480): "800x450, 768x432, 640x360",
(848, 480): "800x450, 768x432, 640x360",
(800, 450): "768x432, 720x405, 640x360",
(720, 405): "640x360, 576x324, 512x288",
(640, 360): "576x324, 512x288, 426x240",
(426, 240): "400x225, 384x216, 320x180",
# Square / near-square
(2048, 2048): "1536x1536, 1024x1024, 768x768",
(1536, 1536): "1280x1280, 1024x1024, 768x768",
(1280, 1280): "1024x1024, 896x896, 768x768",
(1024, 1024): "896x896, 768x768, 640x640",
(896, 896): "768x768, 704x704, 640x640",
(768, 768): "640x640, 576x576, 512x512",
(640, 640): "576x576, 512x512, 480x480",
(512, 512): "448x448, 384x384, 320x320",
# Portrait
(1080, 1920): "900x1600, 720x1280, 540x960",
(720, 1280): "640x1138, 540x960, 480x853",
(640, 1136): "576x1024, 540x960, 480x853",
# 1152x896 variant
(1152, 896): "1024x800, 960x768, 896x672",
}
def _parse_res_list(self, text):
results = []
parts = text.split(",")
for p in parts:
p = p.strip()
if "x" not in p:
continue
w_str, h_str = p.split("x", 1)
try:
w = int(w_str.strip())
h = int(h_str.strip())
results.append((w, h))
except Exception:
continue
return results
def _static_resolution_suggestions(self, current_w, current_h, multiple_of):
table = self._get_common_resolution_table()
key = (current_w, current_h)
if key in table:
base = f"suggested/common resolutions: {table[key]}"
else:
base = "suggested/common resolutions: (no static entries for this input size)"
low_w, low_h, _, _ = self._scale_and_snap(current_w, current_h, 0.5, multiple_of)
low = f"less common lower resolution (same aspect ratio): {low_w}x{low_h}"
return f"{base} | {low}"
# --- Helpers: resolution suggestion presets ---
def _get_suggestion_target(self, w, h, multiple_of, scale_preset: str):
preset_lower = scale_preset.lower()
# Suggestion 4: always dynamic low VRAM (~50%)
if "suggestion 4" in preset_lower or "very low vram" in preset_lower:
new_w, new_h, raw_w, raw_h = self._scale_and_snap(w, h, 0.5, multiple_of)
return new_w, new_h, raw_w, raw_h
# For suggestion 1/2/3 we try static table first
table = self._get_common_resolution_table()
key = (w, h)
suggestions = []
if key in table:
suggestions = self._parse_res_list(table[key])
if "suggestion 1" in preset_lower:
idx = 0
elif "suggestion 2" in preset_lower:
idx = 1
elif "suggestion 3" in preset_lower:
idx = 2
else:
idx = None
if suggestions and idx is not None:
if 0 <= idx < len(suggestions):
target_w, target_h = suggestions[idx]
else:
target_w, target_h = suggestions[-1]
snapped_w = self._snap_dim(target_w, multiple_of)
snapped_h = self._snap_dim(target_h, multiple_of)
if snapped_w > w:
snapped_w = self._snap_dim(w, multiple_of)
if snapped_h > h:
snapped_h = self._snap_dim(h, multiple_of)
raw_w = target_w
raw_h = target_h
return snapped_w, snapped_h, raw_w, raw_h
# No static suggestions: dynamic fallbacks
if "suggestion 1" in preset_lower:
scale = 0.8
elif "suggestion 2" in preset_lower:
scale = 0.7
elif "suggestion 3" in preset_lower:
scale = 0.6
else:
scale = 0.5
new_w, new_h, raw_w, raw_h = self._scale_and_snap(w, h, scale, multiple_of)
return new_w, new_h, raw_w, raw_h
# --- main ---
def calc(self, image, model_profile, scale_preset):
h = image.shape[1]
w = image.shape[2]
if h <= 0 or w <= 0:
info = "Invalid image size – cannot calculate."
return (image, int(w), int(h), info)
multiple_of, profile_label = self._profile_to_multiple(model_profile)
is_suggestion = scale_preset.startswith("Resolution suggestion")
if is_suggestion:
new_w, new_h, raw_w, raw_h = self._get_suggestion_target(
w, h, multiple_of, scale_preset
)
if w > 0:
scale = new_w / float(w)
else:
scale = 1.0
reduction_percent = (1.0 - scale) * 100.0
else:
reduction_percent, scale = self._scale_from_percentage_preset(scale_preset)
new_w, new_h, raw_w, raw_h = self._scale_and_snap(w, h, scale, multiple_of)
simple_preset = scale_preset.split(" (")[0]
suggestions_info = self._static_resolution_suggestions(w, h, multiple_of)
lines = []
lines.append(f"multiple_of: {multiple_of} ({profile_label})")
lines.append(
f"scale_preset={simple_preset}, reduction≈{reduction_percent:.1f}% (scale≈{scale:.4f})"
)
lines.append(f"input image: {w}x{h}")
lines.append(f"result: raw {raw_w}x{raw_h} -> snapped {new_w}x{new_h}")
lines.append(suggestions_info)
info = " | ".join(lines)
return (image, int(new_w), int(new_h), info)
NODE_CLASS_MAPPINGS = {
"TuonoResolutionSuggestDownscale": TuonoResolutionSuggestDownscale,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"TuonoResolutionSuggestDownscale": "Resolution Suggest & Downscale",
}