From 173b040a92274751938bcb151a3b64ef8aee96e2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 20 Jan 2026 08:32:19 +0700 Subject: [PATCH] Initial commit: PixelForge Resolution Matrix node --- README.md | 38 +++++++++++++ __init__.py | 6 ++ resolution_matrix.py | 129 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 README.md create mode 100644 __init__.py create mode 100644 resolution_matrix.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..b4660fe --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# PixelForge Resolution Matrix + +A ComfyUI custom node for selecting mathematically valid image resolutions. + +## Features + +- Aspect ratio selection (1:1, 3:2, 4:3, 16:9, 16:10) +- Orientation switch (landscape / portrait) +- Divisibility constraints (16 / 32 / 64) +- Max megapixel limit + - 1 MP = 1024 × 1024 = 1,048,576 pixels +- Dynamically filtered resolution dropdown +- Outputs: + - Width (px) + - Height (px) + - Aspect ratio (W/H) + - Orientation + - Total megapixels (float) + +## Installation + +Copy the folder into: + +``` +ComfyUI/custom_nodes/ +``` + +Restart ComfyUI. + +## Category + +``` +PixelForge +``` + +## License + +MIT diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..a932899 --- /dev/null +++ b/__init__.py @@ -0,0 +1,6 @@ +from .resolution_matrix import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__all__ = [ + "NODE_CLASS_MAPPINGS", + "NODE_DISPLAY_NAME_MAPPINGS", +] diff --git a/resolution_matrix.py b/resolution_matrix.py new file mode 100644 index 0000000..e6996cf --- /dev/null +++ b/resolution_matrix.py @@ -0,0 +1,129 @@ +class PixelForge: + """ + PixelForge Resolution Matrix + A ComfyUI node for selecting mathematically valid resolutions + filtered by aspect ratio, divisibility, orientation, and megapixel limit. + """ + + ASPECT_RATIOS = { + "1:1": (1, 1), + "3:2": (3, 2), + "4:3": (4, 3), + "16:9": (16, 9), + "16:10": (16, 10), + } + + MP_BASE = 1024 * 1024 # 1 MP = 1,048,576 pixels + + # --- cached inputs for filtered dropdown (ComfyUI pattern) --- + _last_aspect = "3:2" + _last_divisible = 16 + _last_max_mp = 4 + + @classmethod + def INPUT_TYPES(cls): + resolutions = cls._build_resolution_list() + + return { + "required": { + "aspect_ratio": ( + list(cls.ASPECT_RATIOS.keys()), + {"default": cls._last_aspect}, + ), + "orientation": ( + ["landscape", "portrait"], + {"default": "landscape"}, + ), + "divisible_by": ( + [16, 32, 64], + {"default": cls._last_divisible}, + ), + "max_megapixels": ( + ["1 MP", "2 MP", "4 MP", "6 MP", "8 MP", "12 MP", "16 MP"], + {"default": f"{cls._last_max_mp} MP"}, + ), + "resolution": (resolutions,), + } + } + + RETURN_TYPES = ("INT", "INT", "INT", "INT", "STRING", "FLOAT") + RETURN_NAMES = ( + "width_px", + "height_px", + "ratio_w", + "ratio_h", + "orientation", + "total_megapixels", + ) + + FUNCTION = "forge" + CATEGORY = "PixelForge" + + # ------------------------------------------------------------ + + @classmethod + def _build_resolution_list(cls): + ratio_w, ratio_h = cls.ASPECT_RATIOS[cls._last_aspect] + max_pixels = cls._last_max_mp * cls.MP_BASE + div = cls._last_divisible + + resolutions = [] + k = div + + while True: + w = ratio_w * k + h = ratio_h * k + total = w * h + + if total > max_pixels: + break + + mp = total / cls.MP_BASE + resolutions.append(f"{w}×{h}") + + k += div + + if not resolutions: + resolutions.append("INVALID") + + return resolutions + + def forge( + self, + aspect_ratio, + orientation, + divisible_by, + max_megapixels, + resolution, + ): + # ---- update cache for dynamic dropdown ---- + PixelForge._last_aspect = aspect_ratio + PixelForge._last_divisible = divisible_by + PixelForge._last_max_mp = int(max_megapixels.split()[0]) + + ratio_w, ratio_h = self.ASPECT_RATIOS[aspect_ratio] + + width, height = map(int, resolution.split("×")) + + if orientation == "portrait": + width, height = height, width + + total_mp = (width * height) / self.MP_BASE + + return ( + width, + height, + ratio_w, + ratio_h, + orientation, + round(total_mp, 4), + ) + + +NODE_CLASS_MAPPINGS = { + "PixelForge": PixelForge +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "PixelForge": "PixelForge · Resolution Matrix" +}