From 1975140d822127ff137f033d65941598e70c023d Mon Sep 17 00:00:00 2001 From: MdONeilsl Date: Tue, 14 Jul 2026 10:15:27 -0400 Subject: [PATCH] Add descriptions and tooltips to nodes Add DESCRIPTION class attributes to all node classes for UI documentation. Add OUTPUT_TOOLTIPS to describe output values and 'tooltip' keys to input parameters for better user guidance in the ComfyUI interface. Also standardize formatting and remove unnecessary docstrings. --- nodes/color_nodes.py | 102 +++++++-- nodes/img_color_nodes.py | 462 +++++++++++++++++++++------------------ nodes/layer_nodes.py | 18 +- 3 files changed, 339 insertions(+), 243 deletions(-) diff --git a/nodes/color_nodes.py b/nodes/color_nodes.py index d60c3d2..a1d7a1b 100644 --- a/nodes/color_nodes.py +++ b/nodes/color_nodes.py @@ -1,4 +1,3 @@ - from ..py.libs.color import pack_rgb module_cat = "md/color" @@ -9,18 +8,26 @@ MAX_RGB_INT = 0xFFFFFF # RGB # ============================================================================= class mdPrimitiveColorRGB: + DESCRIPTION = "Converts RGB values to an integer color code and a hexadecimal string representation." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "Red": ("FLOAT", {"default": 0, "min": 0, "max": 255, "step": 1}), - "Green": ("FLOAT", {"default": 0, "min": 0, "max": 255, "step": 1}), - "Blue": ("FLOAT", {"default": 0, "min": 0, "max": 255, "step": 1}), + "Red": ("FLOAT", {"default": 0, "min": 0, "max": 255, "step": 1, "tooltip": "Red component value (0-255)."}), + "Green": ("FLOAT", {"default": 0, "min": 0, "max": 255, "step": 1, "tooltip": "Green component value (0-255)."}), + "Blue": ("FLOAT", {"default": 0, "min": 0, "max": 255, "step": 1, "tooltip": "Blue component value (0-255)."}), }, } RETURN_TYPES = ("INT", "STRING") RETURN_NAMES = ("int", "HEX") + + OUTPUT_TOOLTIPS = [ + "Packed 24-bit integer color value (0-16777215).", + "Hexadecimal color string (e.g., #FF0000)." + ] + FUNCTION = "exec" CATEGORY = module_cat @@ -37,18 +44,26 @@ class mdPrimitiveColorRGB: # HSV # ============================================================================= class mdPrimitiveColorHSV: + DESCRIPTION = "Converts HSV color values to an integer color code and a hexadecimal string representation." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "Hue": ("FLOAT", {"default": 0, "min": 0, "max": 360, "step": 0.1}), - "Saturation": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1}), - "Value": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1}), + "Hue": ("FLOAT", {"default": 0, "min": 0, "max": 360, "step": 0.1, "tooltip": "Hue value in degrees (0-360)."}), + "Saturation": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1, "tooltip": "Saturation percentage (0-100%)."}), + "Value": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1, "tooltip": "Value (brightness) percentage (0-100%)."}), }, } RETURN_TYPES = ("INT", "STRING") RETURN_NAMES = ("int", "HEX") + + OUTPUT_TOOLTIPS = [ + "Packed 24-bit integer color value (0-16777215).", + "Hexadecimal color string (e.g., #FF0000)." + ] + FUNCTION = "exec" CATEGORY = module_cat @@ -85,18 +100,26 @@ class mdPrimitiveColorHSV: # HSL # ============================================================================= class mdPrimitiveColorHSL: + DESCRIPTION = "Converts HSL color values to an integer color code and a hexadecimal string representation." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "Hue": ("FLOAT", {"default": 0, "min": 0, "max": 360, "step": 0.1}), - "Saturation": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1}), - "Lightness": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1}), + "Hue": ("FLOAT", {"default": 0, "min": 0, "max": 360, "step": 0.1, "tooltip": "Hue value in degrees (0-360)."}), + "Saturation": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1, "tooltip": "Saturation percentage (0-100%)."}), + "Lightness": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1, "tooltip": "Lightness percentage (0-100%)."}), }, } RETURN_TYPES = ("INT", "STRING") RETURN_NAMES = ("int", "HEX") + + OUTPUT_TOOLTIPS = [ + "Packed 24-bit integer color value (0-16777215).", + "Hexadecimal color string (e.g., #FF0000)." + ] + FUNCTION = "exec" CATEGORY = module_cat @@ -128,19 +151,27 @@ class mdPrimitiveColorHSL: # CMYK # ============================================================================= class mdPrimitiveColorCMYK: + DESCRIPTION = "Converts CMYK color values to an integer color code and a hexadecimal string representation." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "Cyan": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1}), - "Magenta": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1}), - "Yellow": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1}), - "Key": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1}), + "Cyan": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1, "tooltip": "Cyan percentage (0-100%)."}), + "Magenta": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1, "tooltip": "Magenta percentage (0-100%)."}), + "Yellow": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1, "tooltip": "Yellow percentage (0-100%)."}), + "Key": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1, "tooltip": "Key (black) percentage (0-100%)."}), }, } RETURN_TYPES = ("INT", "STRING") RETURN_NAMES = ("int", "HEX") + + OUTPUT_TOOLTIPS = [ + "Packed 24-bit integer color value (0-16777215).", + "Hexadecimal color string (e.g., #FF0000)." + ] + FUNCTION = "exec" CATEGORY = module_cat @@ -160,18 +191,26 @@ class mdPrimitiveColorCMYK: # CIE 1931 XYZ # ============================================================================= class mdPrimitiveColorXYZ: + DESCRIPTION = "Converts CIE XYZ color values to an integer color code and a hexadecimal string representation (D65 white point)." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "X": ("FLOAT", {"default": 0.95047, "min": 0.0, "max": 1.0, "step": 0.001}), - "Y": ("FLOAT", {"default": 1.00000, "min": 0.0, "max": 1.0, "step": 0.001}), - "Z": ("FLOAT", {"default": 1.08883, "min": 0.0, "max": 1.0, "step": 0.001}), + "X": ("FLOAT", {"default": 0.95047, "min": 0.0, "max": 1.0, "step": 0.001, "tooltip": "X coordinate in CIE XYZ color space."}), + "Y": ("FLOAT", {"default": 1.00000, "min": 0.0, "max": 1.0, "step": 0.001, "tooltip": "Y coordinate in CIE XYZ color space."}), + "Z": ("FLOAT", {"default": 1.08883, "min": 0.0, "max": 1.0, "step": 0.001, "tooltip": "Z coordinate in CIE XYZ color space."}), }, } RETURN_TYPES = ("INT", "STRING") RETURN_NAMES = ("int", "HEX") + + OUTPUT_TOOLTIPS = [ + "Packed 24-bit integer color value (0-16777215).", + "Hexadecimal color string (e.g., #FF0000)." + ] + FUNCTION = "exec" CATEGORY = module_cat @@ -197,18 +236,26 @@ class mdPrimitiveColorXYZ: # CIELAB # ============================================================================= class mdPrimitiveColorLAB: + DESCRIPTION = "Converts CIELAB color values to an integer color code and a hexadecimal string representation (D65 white point)." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "L": ("FLOAT", {"default": 50, "min": 0, "max": 100, "step": 0.1}), - "A": ("FLOAT", {"default": 0, "min": -128, "max": 128, "step": 0.1}), - "B": ("FLOAT", {"default": 0, "min": -128, "max": 128, "step": 0.1}), + "L": ("FLOAT", {"default": 50, "min": 0, "max": 100, "step": 0.1, "tooltip": "Lightness component (0-100)."}), + "A": ("FLOAT", {"default": 0, "min": -128, "max": 128, "step": 0.1, "tooltip": "A component (green-red axis)."}), + "B": ("FLOAT", {"default": 0, "min": -128, "max": 128, "step": 0.1, "tooltip": "B component (blue-yellow axis)."}), }, } RETURN_TYPES = ("INT", "STRING") RETURN_NAMES = ("int", "HEX") + + OUTPUT_TOOLTIPS = [ + "Packed 24-bit integer color value (0-16777215).", + "Hexadecimal color string (e.g., #FF0000)." + ] + FUNCTION = "exec" CATEGORY = module_cat @@ -251,18 +298,26 @@ class mdPrimitiveColorLAB: # Y′CbCr # ============================================================================= class mdPrimitiveColorYCbCr: + DESCRIPTION = "Converts Y′CbCr color values to an integer color code and a hexadecimal string representation." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "Luma": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "BlueDiff": ("FLOAT", {"default": 0.0, "min": -0.5, "max": 0.5, "step": 0.001}), - "RedDiff": ("FLOAT", {"default": 0.0, "min": -0.5, "max": 0.5, "step": 0.001}), + "Luma": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001, "tooltip": "Luma (brightness) component (0-1)."}), + "BlueDiff": ("FLOAT", {"default": 0.0, "min": -0.5, "max": 0.5, "step": 0.001, "tooltip": "Blue chroma difference component (-0.5 to 0.5)."}), + "RedDiff": ("FLOAT", {"default": 0.0, "min": -0.5, "max": 0.5, "step": 0.001, "tooltip": "Red chroma difference component (-0.5 to 0.5)."}), }, } RETURN_TYPES = ("INT", "STRING") RETURN_NAMES = ("int", "HEX") + + OUTPUT_TOOLTIPS = [ + "Packed 24-bit integer color value (0-16777215).", + "Hexadecimal color string (e.g., #FF0000)." + ] + FUNCTION = "exec" CATEGORY = module_cat @@ -300,3 +355,4 @@ NODE_DISPLAY_NAME_MAPPINGS = { "mdPrimitiveColorLAB": "Primitive Color CIELAB (MD)", "mdPrimitiveColorYCbCr": "Primitive Color YCbCr (MD)", } + diff --git a/nodes/img_color_nodes.py b/nodes/img_color_nodes.py index 456a8a5..c007b73 100644 --- a/nodes/img_color_nodes.py +++ b/nodes/img_color_nodes.py @@ -37,25 +37,28 @@ MAX_RGB_INT = 0xFFFFFF #=================================================================================== class mdColorBalance: + DESCRIPTION = "Adjusts the color balance for shadows, midtones, or highlights using cyan-red, magenta-green, and yellow-blue sliders." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "range": (["Shadow", "Midtonnes", "Highlights"],), - "Cyan-Red": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1}), - "Magenta-Green": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1}), - "Yellow-Blue": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1}), - "Preserve luminosity": ("BOOLEAN", {"default": True}), - "jdata": ("STRING", {"default": "[[0,0,0],[0,0,0],[0,0,0]]"}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "range": (["Shadow", "Midtonnes", "Highlights"], {"tooltip": "Tonal range to adjust: Shadow, Midtones, or Highlights."}), + "Cyan-Red": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1, "tooltip": "Shift towards Cyan (negative) or Red (positive)."}), + "Magenta-Green": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1, "tooltip": "Shift towards Magenta (negative) or Green (positive)."}), + "Yellow-Blue": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1, "tooltip": "Shift towards Yellow (negative) or Blue (positive)."}), + "Preserve luminosity": ("BOOLEAN", {"default": True, "tooltip": "Preserve image luminosity when applying the balance."}), + "jdata": ("STRING", {"default": "[[0,0,0],[0,0,0],[0,0,0]]", "tooltip": "JSON string representing adjustment values for all three ranges (advanced)."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("image",) + OUTPUT_TOOLTIPS = ("The color-balanced image.",) FUNCTION = "exec" CATEGORY = module_cat @@ -94,27 +97,32 @@ class mdColorBalance: #=================================================================================== class mdColorTemperature: + DESCRIPTION = "Adjusts the white balance by simulating a change in color temperature from an original to an intended temperature (in Kelvin)." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), "original_temp": ("FLOAT", { "default": 6500, "min": 1000, "max": 40000, "step": 1, - "display": "Original Temperature" + "display": "Original Temperature", + "tooltip": "Original color temperature of the scene in Kelvin." }), "intended_temp": ("FLOAT", { "default": 6500, "min": 1000, "max": 40000, "step": 1, - "display": "Intended Temperature" + "display": "Intended Temperature", + "tooltip": "Desired color temperature after adjustment in Kelvin." }), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES: Tuple[torch.Tensor] = ("IMAGE",) RETURN_NAMES: Tuple[str] = ("image",) + OUTPUT_TOOLTIPS: Tuple[str] = ("The white-balanced image.",) FUNCTION: str = "exec" CATEGORY: str = module_cat @@ -134,22 +142,25 @@ class mdColorTemperature: #=================================================================================== class mdColorHueChroma: + DESCRIPTION = "Adjusts hue (rotation), chroma (saturation), and lightness using the LCh color space." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "Hue": ("FLOAT", {"default": 0, "min": -180, "max": 180, "step": 0.1}), - "Chroma": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1}), - "Lightness": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "Hue": ("FLOAT", {"default": 0, "min": -180, "max": 180, "step": 0.1, "tooltip": "Hue shift in degrees (-180 to 180)."}), + "Chroma": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1, "tooltip": "Chroma (saturation) adjustment (-100 to 100)."}), + "Lightness": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1, "tooltip": "Lightness adjustment (-100 to 100)."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES: Tuple[torch.Tensor] = ("IMAGE",) RETURN_NAMES: Tuple[str] = ("image",) + OUTPUT_TOOLTIPS: Tuple[str] = ("The adjusted image.",) FUNCTION: str = "exec" CATEGORY: str = module_cat @@ -175,57 +186,45 @@ class mdColorHueChroma: #=================================================================================== class mdColorHueSaturation: - """ - Adjusts master and per‑colour hue, lightness, and saturation of an image. - - The adjustment is driven by a 7×3 matrix (`jdata`) that provides master - and six per‑hue shifts (Red, Yellow, Green, Cyan, Blue, Magenta). An - `Overlap` parameter controls how much neighbouring hues are affected - (0 = hard edges, 100 = broad blending). A luminance‑based `mask` can - optionally limit the effect to certain areas. Works on both RGB and - RGBA images; when alpha is present it is left untouched. - - Inputs - ------ - image : IMAGE tensor (B, H, W, C) with C = 3 or 4. - color : (unused in the computation – kept for UI compatibility) - Overlap : overlap spread (0–100 %) - Hue / Lightness / Saturation : (unused; shifts come from `jdata`) - jdata : JSON string representing a 7×3 array (master + 6 hues) - mask : optional MASK tensor (B, H, W) or (B, 1, H, W) to limit effect - - Returns - ------- - IMAGE tensor with the same number of channels as the input. - """ + DESCRIPTION = ( + "Adjusts master and per‑colour hue, lightness, and saturation of an image.\n\n" + "The adjustment is driven by a 7×3 matrix (`jdata`) that provides master " + "and six per‑hue shifts (Red, Yellow, Green, Cyan, Blue, Magenta). An " + "`Overlap` parameter controls how much neighbouring hues are affected " + "(0 = hard edges, 100 = broad blending). A luminance‑based `mask` can " + "optionally limit the effect to certain areas. Works on both RGB and " + "RGBA images; when alpha is present it is left untouched." + ) @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), + "image": ("IMAGE", {"tooltip": "Input image tensor (RGB or RGBA)."}), "color": ( ["Master", "Red", "Yellow", "Green", "Cyan", "Blue", "Magenta"], - {"default": "Master"}, + {"default": "Master", "tooltip": "Select a specific hue band for the slider controls (overridden by jdata if valid)."}, ), - "Overlap": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1}), - "Hue": ("FLOAT", {"default": 0, "min": -180, "max": 180, "step": 0.1}), - "Lightness": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1}), - "Saturation": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1}), + "Overlap": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1, "tooltip": "Overlap spread percentage (0-100) for blending neighbouring hues."}), + "Hue": ("FLOAT", {"default": 0, "min": -180, "max": 180, "step": 0.1, "tooltip": "Hue shift for the selected hue band (ignored if jdata is used)."}), + "Lightness": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1, "tooltip": "Lightness shift for the selected hue band (ignored if jdata is used)."}), + "Saturation": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1, "tooltip": "Saturation shift for the selected hue band (ignored if jdata is used)."}), "jdata": ( "STRING", - {"default": "[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]]"}, + {"default": "[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]]", + "tooltip": "JSON string representing a 7×3 matrix of [Hue, Lightness, Saturation] adjustments for Master and six hues."}, ), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("image",) + OUTPUT_TOOLTIPS = ("The hue/saturation adjusted image.",) FUNCTION = "exec" - CATEGORY = module_cat # provided by the surrounding framework + CATEGORY = module_cat def exec(self, image, color, Overlap, Hue, Lightness, Saturation, jdata, mask=None): # ---------- Parse and validate jdata ---------- @@ -263,21 +262,24 @@ class mdColorHueSaturation: #=================================================================================== class mdColorSaturation: + DESCRIPTION = "Adjusts the saturation of an image using a scale factor, with a selectable color space." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "Scale": ("FLOAT", {"default": 1, "min": 0, "max": 10, "step": 0.01}), - "Color_Space": (["Native", "CIE Lab/Lch", "CIE Yuv"], {"default": "Native"}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "Scale": ("FLOAT", {"default": 1, "min": 0, "max": 10, "step": 0.01, "tooltip": "Saturation scale factor (0 = desaturated, 1 = original, >1 increases saturation)."}), + "Color_Space": (["Native", "CIE Lab/Lch", "CIE Yuv"], {"default": "Native", "tooltip": "Color space to use for saturation adjustment."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES: Tuple[torch.Tensor] = ("IMAGE",) RETURN_NAMES: Tuple[str] = ("image",) + OUTPUT_TOOLTIPS: Tuple[str] = ("The saturation-adjusted image.",) FUNCTION: str = "exec" CATEGORY: str = module_cat @@ -286,21 +288,24 @@ class mdColorSaturation: # =================================================================================== class mdColorExposure: + DESCRIPTION = "Adjusts exposure (gain) and black level offset of an image." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "Black_level": ("FLOAT", {"default": 0, "min": -0.1, "max": 0.1, "step": 0.001}), - "Exposure": ("FLOAT", {"default": 0, "min": -10, "max": 10, "step": 0.01}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "Black_level": ("FLOAT", {"default": 0, "min": -0.1, "max": 0.1, "step": 0.001, "tooltip": "Black level offset added to all channels."}), + "Exposure": ("FLOAT", {"default": 0, "min": -10, "max": 10, "step": 0.01, "tooltip": "Exposure adjustment in stops (0 = unchanged)."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES: Tuple[torch.Tensor] = ("IMAGE",) RETURN_NAMES: Tuple[str] = ("image",) + OUTPUT_TOOLTIPS: Tuple[str] = ("The exposure-adjusted image.",) FUNCTION: str = "exec" CATEGORY: str = module_cat @@ -311,31 +316,33 @@ class mdColorExposure: Exposure: float, mask: torch.Tensor = None, ) -> Tuple[torch.Tensor]: - # Minimum code in exec() - delegate everything to the operator function return (exec_operator_color_exposure(image, Black_level, Exposure, mask),) # =================================================================================== class mdColorShadowsHighlights: + DESCRIPTION = "Recovers detail in shadows and highlights by adjusting brightness and color, with control over radius and compression." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "Shadows": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1}), - "Shadows_color_adj": ("FLOAT", {"default": 100, "min": 0, "max": 100, "step": 0.1}), - "Highlights": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1}), - "Highlights_color_adj": ("FLOAT", {"default": 50, "min": 0, "max": 100, "step": 0.1}), - "White_point_adj": ("FLOAT", {"default": 0, "min": -10, "max": 10, "step": 0.001}), - "Radius": ("FLOAT", {"default": 100, "min": 0.1, "max": 1500, "step": 0.1}), - "Compress": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "Shadows": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1, "tooltip": "Amount to lighten shadows (-100 to 100)."}), + "Shadows_color_adj": ("FLOAT", {"default": 100, "min": 0, "max": 100, "step": 0.1, "tooltip": "Color adjustment strength for shadow areas (0-100)."}), + "Highlights": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 0.1, "tooltip": "Amount to darken highlights (-100 to 100)."}), + "Highlights_color_adj": ("FLOAT", {"default": 50, "min": 0, "max": 100, "step": 0.1, "tooltip": "Color adjustment strength for highlight areas (0-100)."}), + "White_point_adj": ("FLOAT", {"default": 0, "min": -10, "max": 10, "step": 0.001, "tooltip": "White point adjustment to control overall brightness."}), + "Radius": ("FLOAT", {"default": 100, "min": 0.1, "max": 1500, "step": 0.1, "tooltip": "Radius of the local area used for detection (higher = broader effect)."}), + "Compress": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 0.1, "tooltip": "Compression strength to prevent clipping (0-100)."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES: Tuple[torch.Tensor] = ("IMAGE",) RETURN_NAMES: Tuple[str] = ("image",) + OUTPUT_TOOLTIPS: Tuple[str] = ("The adjusted image.",) FUNCTION: str = "exec" CATEGORY: str = module_cat @@ -354,21 +361,24 @@ class mdColorShadowsHighlights: # =================================================================================== class mdColorBrightnessContrast: + DESCRIPTION = "Adjusts the brightness and contrast of an image." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "Brightness": ("FLOAT", {"default": 0, "min": -127, "max": 127, "step": 0.1}), - "Contrast": ("FLOAT", {"default": 0, "min": -127, "max": 127, "step": 0.1}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "Brightness": ("FLOAT", {"default": 0, "min": -127, "max": 127, "step": 0.1, "tooltip": "Brightness offset (-127 to 127)."}), + "Contrast": ("FLOAT", {"default": 0, "min": -127, "max": 127, "step": 0.1, "tooltip": "Contrast factor (-127 to 127)."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("image",) + OUTPUT_TOOLTIPS = ("The brightness/contrast adjusted image.",) FUNCTION = "exec" CATEGORY = module_cat @@ -379,37 +389,39 @@ class mdColorBrightnessContrast: Contrast: float, mask: Optional[torch.Tensor] = None ) -> Tuple[torch.Tensor]: - # Delegate all work to the standalone function return exec_operator_color_brightness_contrast(image, Brightness, Contrast, mask) # =================================================================================== class mdColorLevels: + DESCRIPTION = "Adjusts input/output levels and gamma for a selected color channel, with optional auto-input range." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "Channel": (["Value", "Red", "Green", "Blue", "Alpha"], {"default": "Value"}), - "Interpolation": (["Linear", "Non-Linear", "Perceptual"], {"default": "Non-Linear"}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "Channel": (["Value", "Red", "Green", "Blue", "Alpha"], {"default": "Value", "tooltip": "Which channel to apply the levels adjustment."}), + "Interpolation": (["Linear", "Non-Linear", "Perceptual"], {"default": "Non-Linear", "tooltip": "Type of interpolation for the level mapping."}), - "Input_low": ("FLOAT", {"default": 0, "min": 0, "max": 255, "step": 1}), - "Input_high": ("FLOAT", {"default": 255, "min": 0, "max": 255, "step": 1}), - "Input_gamma": ("FLOAT", {"default": 1, "min": 0.1, "max": 10, "step": 0.01}), - "Input_clamp": ("BOOLEAN", {"default": False}), + "Input_low": ("FLOAT", {"default": 0, "min": 0, "max": 255, "step": 1, "tooltip": "Lower bound for input levels (0-255)."}), + "Input_high": ("FLOAT", {"default": 255, "min": 0, "max": 255, "step": 1, "tooltip": "Upper bound for input levels (0-255)."}), + "Input_gamma": ("FLOAT", {"default": 1, "min": 0.1, "max": 10, "step": 0.01, "tooltip": "Gamma correction applied to the input range."}), + "Input_clamp": ("BOOLEAN", {"default": False, "tooltip": "Clamp input values to the specified range before mapping."}), - "Output_low": ("FLOAT", {"default": 0, "min": 0, "max": 255, "step": 1}), - "Output_high": ("FLOAT", {"default": 255, "min": 0, "max": 255, "step": 1}), - "Output_clamp": ("BOOLEAN", {"default": False}), + "Output_low": ("FLOAT", {"default": 0, "min": 0, "max": 255, "step": 1, "tooltip": "Lower bound for output levels (0-255)."}), + "Output_high": ("FLOAT", {"default": 255, "min": 0, "max": 255, "step": 1, "tooltip": "Upper bound for output levels (0-255)."}), + "Output_clamp": ("BOOLEAN", {"default": False, "tooltip": "Clamp output values to the specified range after mapping."}), - "Auto_input": ("BOOLEAN", {"default": False}), + "Auto_input": ("BOOLEAN", {"default": False, "tooltip": "Automatically determine input low/high from image content."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES: Tuple[torch.Tensor] = ("IMAGE",) RETURN_NAMES: Tuple[str] = ("image",) + OUTPUT_TOOLTIPS: Tuple[str] = ("The levels-adjusted image.",) FUNCTION: str = "exec" CATEGORY: str = module_cat @@ -428,10 +440,6 @@ class mdColorLevels: Auto_input: bool, mask: torch.Tensor = None, ) -> Tuple[torch.Tensor]: - """ - Execute the Levels adjustment with minimal code - delegate all work to - exec_operator_color_levels. - """ return (exec_operator_color_levels( image=image, channel=Channel, @@ -447,21 +455,23 @@ class mdColorLevels: mask=mask, ),) -#=================================================================================== #=================================================================================== class mdColorInvert: + DESCRIPTION = "Inverts the colors of the image using various methods (RGB, Linear, Value)." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "type": (["RGB", "Linear", "Value"], {"default": "RGB"}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "type": (["RGB", "Linear", "Value"], {"default": "RGB", "tooltip": "Inversion method: RGB per channel, Linear negative, or HSV value inversion."}), }, - "optional": {"mask": ("MASK",)}, + "optional": {"mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."})}, } RETURN_TYPES: Tuple[torch.Tensor] = ("IMAGE",) RETURN_NAMES: Tuple[str] = ("image",) + OUTPUT_TOOLTIPS: Tuple[str] = ("The inverted image.",) FUNCTION: str = "exec" CATEGORY: str = module_cat @@ -469,17 +479,19 @@ class mdColorInvert: return exec_operator_color_invert(image, type, mask) #=================================================================================== -#=================================================================================== class mdColorAutoEqualize: + DESCRIPTION = "Automatically equalizes the histogram of the image to enhance contrast." + @classmethod def INPUT_TYPES(cls) -> dict: return { - "required": {"image": ("IMAGE",),}, - "optional": {"mask": ("MASK",),}, + "required": {"image": ("IMAGE", {"tooltip": "Input image tensor."})}, + "optional": {"mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."})}, } RETURN_TYPES: Tuple[torch.Tensor] = ("IMAGE",) RETURN_NAMES: Tuple[str] = ("image",) + OUTPUT_TOOLTIPS: Tuple[str] = ("The equalized image.",) FUNCTION: str = "exec" CATEGORY: str = module_cat @@ -488,15 +500,18 @@ class mdColorAutoEqualize: #=================================================================================== class mdColorAutoWhiteBalance: + DESCRIPTION = "Automatically adjusts white balance by estimating the light source color." + @classmethod def INPUT_TYPES(cls) -> dict: return { - "required": {"image": ("IMAGE",),}, - "optional": {"mask": ("MASK",),}, + "required": {"image": ("IMAGE", {"tooltip": "Input image tensor."})}, + "optional": {"mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."})}, } RETURN_TYPES: Tuple[str] = ("IMAGE",) RETURN_NAMES: Tuple[str] = ("image",) + OUTPUT_TOOLTIPS: Tuple[str] = ("The white-balanced image.",) FUNCTION: str = "exec" CATEGORY: str = module_cat @@ -505,25 +520,24 @@ class mdColorAutoWhiteBalance: #=================================================================================== class mdColorAutoStretchContrast: - """ - ComfyUI node that performs GIMP-like "Auto Stretch Contrast" - """ + DESCRIPTION = "Automatically stretches the contrast of the image similar to GIMP's Auto Stretch Contrast, with options to preserve colors and use non-linear mapping." @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "keep_colors": ("BOOLEAN", {"default": True}), - "non_linear": ("BOOLEAN", {"default": False}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "keep_colors": ("BOOLEAN", {"default": True, "tooltip": "Preserve the color ratios when stretching contrast."}), + "non_linear": ("BOOLEAN", {"default": False, "tooltip": "Use a non-linear (perceptual) stretching method."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES: Tuple[str] = ("IMAGE",) RETURN_NAMES: Tuple[str] = ("image",) + OUTPUT_TOOLTIPS: Tuple[str] = ("The contrast-stretched image.",) FUNCTION: str = "exec" CATEGORY: str = module_cat @@ -534,7 +548,6 @@ class mdColorAutoStretchContrast: non_linear: bool = False, mask: torch.Tensor = None, ) -> Tuple[torch.Tensor]: - return exec_operator_color_auto_stretch_contrast( image=image, keep_colors=keep_colors, @@ -544,47 +557,52 @@ class mdColorAutoStretchContrast: #=================================================================================== class mdColorAutoColorEnhance: + DESCRIPTION = "Automatically enhances color saturation and contrast." + @classmethod def INPUT_TYPES(cls) -> dict: return { - "required": {"image": ("IMAGE",),}, - "optional": {"mask": ("MASK",),}, + "required": {"image": ("IMAGE", {"tooltip": "Input image tensor."})}, + "optional": {"mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."})}, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("image",) + OUTPUT_TOOLTIPS = ("The color-enhanced image.",) FUNCTION = "exec" CATEGORY = module_cat def exec(self, image: torch.Tensor, mask: Optional[torch.Tensor] = None) -> Tuple[torch.Tensor]: - # Delegate everything – this is the "minimum code" required in the method return exec_operator_color_auto_color_enhance(image, mask) #=================================================================================== class mdColorCompChannelMixer: + DESCRIPTION = "Mixes RGB channels by specifying how much of each source channel goes into each output channel, with optional luminosity preservation." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "Red_in_red": ("FLOAT", {"default": 1, "min": -2, "max": 2, "step": 0.01}), - "Green_in_red": ("FLOAT", {"default": 0, "min": -2, "max": 2, "step": 0.01}), - "Blue_in_red": ("FLOAT", {"default": 0, "min": -2, "max": 2, "step": 0.01}), - "Red_in_green": ("FLOAT", {"default": 0, "min": -2, "max": 2, "step": 0.01}), - "Green_in_green": ("FLOAT", {"default": 1, "min": -2, "max": 2, "step": 0.01}), - "Blue_in_green": ("FLOAT", {"default": 0, "min": -2, "max": 2, "step": 0.01}), - "Red_in_blue": ("FLOAT", {"default": 0, "min": -2, "max": 2, "step": 0.01}), - "Green_in_blue": ("FLOAT", {"default": 0, "min": -2, "max": 2, "step": 0.01}), - "Blue_in_blue": ("FLOAT", {"default": 1, "min": -2, "max": 2, "step": 0.01}), - "Preserve_luminosity": ("BOOLEAN", {"default": False}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "Red_in_red": ("FLOAT", {"default": 1, "min": -2, "max": 2, "step": 0.01, "tooltip": "Contribution of red channel to output red."}), + "Green_in_red": ("FLOAT", {"default": 0, "min": -2, "max": 2, "step": 0.01, "tooltip": "Contribution of green channel to output red."}), + "Blue_in_red": ("FLOAT", {"default": 0, "min": -2, "max": 2, "step": 0.01, "tooltip": "Contribution of blue channel to output red."}), + "Red_in_green": ("FLOAT", {"default": 0, "min": -2, "max": 2, "step": 0.01, "tooltip": "Contribution of red channel to output green."}), + "Green_in_green": ("FLOAT", {"default": 1, "min": -2, "max": 2, "step": 0.01, "tooltip": "Contribution of green channel to output green."}), + "Blue_in_green": ("FLOAT", {"default": 0, "min": -2, "max": 2, "step": 0.01, "tooltip": "Contribution of blue channel to output green."}), + "Red_in_blue": ("FLOAT", {"default": 0, "min": -2, "max": 2, "step": 0.01, "tooltip": "Contribution of red channel to output blue."}), + "Green_in_blue": ("FLOAT", {"default": 0, "min": -2, "max": 2, "step": 0.01, "tooltip": "Contribution of green channel to output blue."}), + "Blue_in_blue": ("FLOAT", {"default": 1, "min": -2, "max": 2, "step": 0.01, "tooltip": "Contribution of blue channel to output blue."}), + "Preserve_luminosity": ("BOOLEAN", {"default": False, "tooltip": "Preserve the overall luminance after mixing."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("image",) + OUTPUT_TOOLTIPS = ("The channel-mixed image.",) FUNCTION = "exec" CATEGORY = module_cat @@ -603,7 +621,6 @@ class mdColorCompChannelMixer: Preserve_luminosity: bool, mask: torch.Tensor = None, ) -> Tuple[torch.Tensor]: - # Delegate all work to the core function return exec_operator_color_comp_channel_mixer( image, Red_in_red, @@ -621,11 +638,13 @@ class mdColorCompChannelMixer: #=================================================================================== class mdColorCompExtract: + DESCRIPTION = "Extracts a specific color component (e.g., red, hue, lightness) as a mask, with optional inversion and linear conversion." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), "Component": ([ "RGB red", "RGB green", "RGB blue", "Hue", @@ -636,22 +655,22 @@ class mdColorCompExtract: "LAB L", "LAB A", "LAB B", "LCH C(ab)", "LCH H(ab)", "Alpha" - ], {"default": "RGB red"}), - "Invert": ("BOOLEAN", {"default": False}), - "Linear": ("BOOLEAN", {"default": False}), + ], {"default": "RGB red", "tooltip": "Which component to extract."}), + "Invert": ("BOOLEAN", {"default": False, "tooltip": "Invert the extracted mask."}), + "Linear": ("BOOLEAN", {"default": False, "tooltip": "Convert to linear before extraction (for sRGB inputs)."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the extraction area."}), }, } RETURN_TYPES = ("MASK",) RETURN_NAMES = ("mask",) + OUTPUT_TOOLTIPS = ("The extracted mask.",) FUNCTION = "exec" CATEGORY = module_cat def exec(self, image: torch.Tensor, Component: str, Invert: bool, Linear: bool, mask: torch.Tensor = None) -> Tuple[torch.Tensor]: - # Delegate all work to the core function return exec_operator_color_comp_extract( image=image, component=Component, @@ -662,23 +681,26 @@ class mdColorCompExtract: #=================================================================================== class mdColorCompMonoMixer: + DESCRIPTION = "Converts an image to grayscale by mixing red, green, and blue channels with custom weights, with optional luminosity preservation." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "preserve_luminosity": ("BOOLEAN", {"default": False}), - "red_multiplier": ("FLOAT", {"default": 0.333, "min": -5.0, "max": 5.0, "step": 0.01}), - "green_multiplier": ("FLOAT", {"default": 0.333, "min": -5.0, "max": 5.0, "step": 0.01}), - "blue_multiplier": ("FLOAT", {"default": 0.333, "min": -5.0, "max": 5.0, "step": 0.01}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "preserve_luminosity": ("BOOLEAN", {"default": False, "tooltip": "Preserve the overall luminance after mixing."}), + "red_multiplier": ("FLOAT", {"default": 0.333, "min": -5.0, "max": 5.0, "step": 0.01, "tooltip": "Weight for the red channel."}), + "green_multiplier": ("FLOAT", {"default": 0.333, "min": -5.0, "max": 5.0, "step": 0.01, "tooltip": "Weight for the green channel."}), + "blue_multiplier": ("FLOAT", {"default": 0.333, "min": -5.0, "max": 5.0, "step": 0.01, "tooltip": "Weight for the blue channel."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the conversion area."}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("image",) + OUTPUT_TOOLTIPS = ("The grayscale image.",) FUNCTION = "exec" CATEGORY = module_cat @@ -691,7 +713,6 @@ class mdColorCompMonoMixer: blue_multiplier: float, mask: Optional[torch.Tensor] = None, ) -> Tuple[torch.Tensor]: - # Delegate all work to the core function return exec_operator_color_comp_mono_mixer( image, preserve_luminosity, @@ -701,27 +722,29 @@ class mdColorCompMonoMixer: mask, ) -#=================================================================================== #=================================================================================== # Too slow class mdColorDesaturateColor2Gray: + DESCRIPTION = "Converts an image to grayscale while preserving color contrasts using a color-to-gray algorithm (may be slow)." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "Radius": ("FLOAT", {"default": 300, "min": 2, "max": 6000, "step": 1}), - "Samples": ("FLOAT", {"default": 4, "min": 1, "max": 1000, "step": 1}), - "Iterations": ("FLOAT", {"default": 10, "min": 1, "max": 1000, "step": 1}), - "Enhance_Shadows": ("BOOLEAN", {"default": False}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "Radius": ("FLOAT", {"default": 300, "min": 2, "max": 6000, "step": 1, "tooltip": "Radius for contrast detection."}), + "Samples": ("FLOAT", {"default": 4, "min": 1, "max": 1000, "step": 1, "tooltip": "Number of samples."}), + "Iterations": ("FLOAT", {"default": 10, "min": 1, "max": 1000, "step": 1, "tooltip": "Number of iterations."}), + "Enhance_Shadows": ("BOOLEAN", {"default": False, "tooltip": "Enhance shadow detail."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the conversion area."}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("image",) + OUTPUT_TOOLTIPS = ("The grayscale image.",) FUNCTION = "exec" CATEGORY = module_cat @@ -734,7 +757,6 @@ class mdColorDesaturateColor2Gray: Enhance_Shadows: bool, mask: torch.Tensor = None, ) -> Tuple[torch.Tensor]: - # Delegate all work to the core function return exec_operator_color_desat_color_2_gray( image=image, radius=int(Radius), @@ -746,23 +768,26 @@ class mdColorDesaturateColor2Gray: #=================================================================================== class mdColorDesaturateDesaturate: + DESCRIPTION = "Desaturates (converts to grayscale) the image using various luminance formulas (Luminance, Luma, HSL Lightness, etc.)." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), "Mode": ( ["Luminance", "Luma", "Lightness (HSL)", "Avg (HSI Intensity)", "Value (HSV)"], - {"default": "Luminance"} + {"default": "Luminance", "tooltip": "Desaturation method."} ), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("image",) + OUTPUT_TOOLTIPS = ("The desaturated image.",) FUNCTION = "exec" CATEGORY = module_cat @@ -772,26 +797,28 @@ class mdColorDesaturateDesaturate: Mode: str, mask: torch.Tensor = None ) -> Tuple[torch.Tensor]: - # Delegate all work to the core function return exec_operator_color_desat_desturation(image, Mode, mask) #=================================================================================== class mdColorDesaturateSepia: + DESCRIPTION = "Applies a sepia tone effect with adjustable strength." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}), - "sRGB": ("BOOLEAN", {"default": True}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "Sepia strength (0 = original, 1 = full sepia)."}), + "sRGB": ("BOOLEAN", {"default": True, "tooltip": "Use sRGB color space for conversion."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("image",) + OUTPUT_TOOLTIPS = ("The sepia-toned image.",) FUNCTION = "exec" CATEGORY = module_cat @@ -802,37 +829,39 @@ class mdColorDesaturateSepia: sRGB: bool, mask: torch.Tensor | None = None, ) -> Tuple[torch.Tensor]: - # Delegate all work to the core function return exec_operator_color_desat_sepia(image, strength, sRGB, mask) #=================================================================================== class mdColorMapAlien: + DESCRIPTION = "Applies an 'alien' color mapping by modulating RGB channels with sinusoidal functions, with adjustable frequencies, shifts, and preservation." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "Model": (["RGB", "HSL"], {"default": "RGB"}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "Model": (["RGB", "HSL"], {"default": "RGB", "tooltip": "Color model (RGB or HSL) for mapping."}), - "Red_frequency": ("FLOAT", {"default": 1, "min": 0, "max": 20, "step": 0.025}), - "Green_frequency": ("FLOAT", {"default": 1, "min": 0, "max": 20, "step": 0.025}), - "Blue_frequency": ("FLOAT", {"default": 1, "min": 0, "max": 20, "step": 0.025}), + "Red_frequency": ("FLOAT", {"default": 1, "min": 0, "max": 20, "step": 0.025, "tooltip": "Frequency of the red channel modulation."}), + "Green_frequency": ("FLOAT", {"default": 1, "min": 0, "max": 20, "step": 0.025, "tooltip": "Frequency of the green channel modulation."}), + "Blue_frequency": ("FLOAT", {"default": 1, "min": 0, "max": 20, "step": 0.025, "tooltip": "Frequency of the blue channel modulation."}), - "Red_shift": ("FLOAT", {"default": 0, "min": 0, "max": 360, "step": 1}), - "Green_shift": ("FLOAT", {"default": 0, "min": 0, "max": 360, "step": 1}), - "Blue_shift": ("FLOAT", {"default": 0, "min": 0, "max": 360, "step": 1}), + "Red_shift": ("FLOAT", {"default": 0, "min": 0, "max": 360, "step": 1, "tooltip": "Phase shift for the red channel (degrees)."}), + "Green_shift": ("FLOAT", {"default": 0, "min": 0, "max": 360, "step": 1, "tooltip": "Phase shift for the green channel (degrees)."}), + "Blue_shift": ("FLOAT", {"default": 0, "min": 0, "max": 360, "step": 1, "tooltip": "Phase shift for the blue channel (degrees)."}), - "Keep_red": ("BOOLEAN", {"default": False}), - "Keep_green": ("BOOLEAN", {"default": False}), - "Keep_blue": ("BOOLEAN", {"default": False}), + "Keep_red": ("BOOLEAN", {"default": False, "tooltip": "Preserve the original red channel when true."}), + "Keep_green": ("BOOLEAN", {"default": False, "tooltip": "Preserve the original green channel when true."}), + "Keep_blue": ("BOOLEAN", {"default": False, "tooltip": "Preserve the original blue channel when true."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("image",) + OUTPUT_TOOLTIPS = ("The alien-mapped image.",) FUNCTION = "exec" CATEGORY = module_cat @@ -851,9 +880,6 @@ class mdColorMapAlien: Keep_blue: bool, mask: Optional[torch.Tensor] = None ) -> Tuple[torch.Tensor]: - """ - Minimal exec method – all work is delegated to exec_operator_color_map_alien. - """ return exec_operator_color_map_alien( image=image, model=Model, @@ -871,24 +897,27 @@ class mdColorMapAlien: #=================================================================================== class mdColorMapColorExchange: + DESCRIPTION = "Replaces colors close to a source color with a target color, using per-channel threshold tolerances." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "from_color": ("INT", {"default": 0x7A95C7, "min": 0, "max": MAX_RGB_INT}), - "to_color": ("INT", {"default": 0xFF0000, "min": 0, "max": MAX_RGB_INT}), - "Red_Threshold": ("FLOAT", {"default": 0.106, "min": 0, "max": 1, "step": 0.001}), - "Green_Threshold": ("FLOAT", {"default": 0.100, "min": 0, "max": 1, "step": 0.001}), - "Blue_Threshold": ("FLOAT", {"default": 0.190, "min": 0, "max": 1, "step": 0.001}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "from_color": ("INT", {"default": 0x7A95C7, "min": 0, "max": MAX_RGB_INT, "tooltip": "Source color (RGB hex integer)."}), + "to_color": ("INT", {"default": 0xFF0000, "min": 0, "max": MAX_RGB_INT, "tooltip": "Target replacement color (RGB hex integer)."}), + "Red_Threshold": ("FLOAT", {"default": 0.106, "min": 0, "max": 1, "step": 0.001, "tooltip": "Tolerance for the red channel (0-1)."}), + "Green_Threshold": ("FLOAT", {"default": 0.100, "min": 0, "max": 1, "step": 0.001, "tooltip": "Tolerance for the green channel (0-1)."}), + "Blue_Threshold": ("FLOAT", {"default": 0.190, "min": 0, "max": 1, "step": 0.001, "tooltip": "Tolerance for the blue channel (0-1)."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("image",) + OUTPUT_TOOLTIPS = ("The color-exchanged image.",) FUNCTION = "exec" CATEGORY = module_cat @@ -902,7 +931,6 @@ class mdColorMapColorExchange: Blue_Threshold: float, mask: torch.Tensor = None, ) -> Tuple[torch.Tensor]: - # Minimal exec – delegate all work to the helper return exec_operator_color_map_color_exchange( image=image, from_color=from_color, @@ -915,32 +943,35 @@ class mdColorMapColorExchange: #=================================================================================== class mdColorMapColorRotate: + DESCRIPTION = "Rotates a range of source hues to a destination hue range, with options for handling grays and custom hue/saturation replacement." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), - "Source_from": ("FLOAT", {"default": 0, "min": 0, "max": 360, "step": 0.1}), - "Source_to": ("FLOAT", {"default": 90, "min": 0, "max": 360, "step": 0.1}), - "Source_clockwise": ("BOOLEAN", {"default": False}), + "Source_from": ("FLOAT", {"default": 0, "min": 0, "max": 360, "step": 0.1, "tooltip": "Start of the source hue range (degrees)."}), + "Source_to": ("FLOAT", {"default": 90, "min": 0, "max": 360, "step": 0.1, "tooltip": "End of the source hue range (degrees)."}), + "Source_clockwise": ("BOOLEAN", {"default": False, "tooltip": "Interpret the source range clockwise."}), - "Destination_from": ("FLOAT", {"default": 0, "min": 0, "max": 360, "step": 0.1}), - "Destination_to": ("FLOAT", {"default": 90, "min": 0, "max": 360, "step": 0.1}), - "Destination_clockwise": ("BOOLEAN", {"default": False}), + "Destination_from": ("FLOAT", {"default": 0, "min": 0, "max": 360, "step": 0.1, "tooltip": "Start of the destination hue range (degrees)."}), + "Destination_to": ("FLOAT", {"default": 90, "min": 0, "max": 360, "step": 0.1, "tooltip": "End of the destination hue range (degrees)."}), + "Destination_clockwise": ("BOOLEAN", {"default": False, "tooltip": "Interpret the destination range clockwise."}), - "Gray_threshold": ("FLOAT", {"default": 0, "min": 0, "max": 1, "step": 0.01}), - "Mode": (["Change to this", "Treat as this"], {"default": "Change to this"}), - "Hue": ("FLOAT", {"default": 0, "min": 0, "max": 360, "step": 0.025}), - "Saturation": ("FLOAT", {"default": 0, "min": 0, "max": 1, "step": 0.001}), + "Gray_threshold": ("FLOAT", {"default": 0, "min": 0, "max": 1, "step": 0.01, "tooltip": "Saturation threshold below which pixels are treated as gray."}), + "Mode": (["Change to this", "Treat as this"], {"default": "Change to this", "tooltip": "How to handle gray pixels: replace with target hue/saturation or treat them as in range."}), + "Hue": ("FLOAT", {"default": 0, "min": 0, "max": 360, "step": 0.025, "tooltip": "Target hue for replaced colors."}), + "Saturation": ("FLOAT", {"default": 0, "min": 0, "max": 1, "step": 0.001, "tooltip": "Target saturation for replaced colors."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("image",) + OUTPUT_TOOLTIPS = ("The hue-rotated image.",) FUNCTION = "exec" CATEGORY = module_cat @@ -959,7 +990,6 @@ class mdColorMapColorRotate: Saturation: float, mask: Optional[torch.Tensor] = None ) -> Tuple[torch.Tensor]: - # Minimal exec – delegate all work to the helper return exec_operator_color_map_color_rotate( image=image, Source_from=Source_from, @@ -975,26 +1005,28 @@ class mdColorMapColorRotate: mask=mask ) -#=================================================================================== #=================================================================================== class mdColorToneMappingFattalEtAl2002: + DESCRIPTION = "Applies tone mapping using the Fattal et al. 2002 method to compress high dynamic range." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "Alpha": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 2.0, "step": 0.001}), - "Beta": ("FLOAT", {"default": 0.9, "min": 0.1, "max": 2.0, "step": 0.001}), - "Saturation": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 1.0, "step": 0.001}), - "Noise": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "Alpha": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 2.0, "step": 0.001, "tooltip": "Gradient attenuation factor."}), + "Beta": ("FLOAT", {"default": 0.9, "min": 0.1, "max": 2.0, "step": 0.001, "tooltip": "Detail enhancement factor."}), + "Saturation": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 1.0, "step": 0.001, "tooltip": "Color saturation adjustment."}), + "Noise": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001, "tooltip": "Noise suppression level."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("image",) + OUTPUT_TOOLTIPS = ("The tone-mapped image.",) FUNCTION = "exec" CATEGORY = module_cat @@ -1007,33 +1039,34 @@ class mdColorToneMappingFattalEtAl2002: Noise: float, mask: torch.Tensor = None, ) -> Tuple[torch.Tensor]: - # Delegate all the work to the core function immediately return exec_operator_color_tone_mapping_fattal_et_al_2002( image, Alpha, Beta, Saturation, Noise, mask ) -#=================================================================================== #=================================================================================== class mdColorThreshold: + DESCRIPTION = "Thresholds an image based on a selected channel, mapping values below Low to 0 and above High to 255." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), "Channel": ( ["Value", "Red", "Green", "Blue", "Alpha", "Luminance", "RGB"], - {"default": "Value"}, + {"default": "Value", "tooltip": "Channel to use for thresholding."}, ), - "Low": ("FLOAT", {"default": 127, "min": 0, "max": 255, "step": 1}), - "High": ("FLOAT", {"default": 255, "min": 0, "max": 255, "step": 1}), + "Low": ("FLOAT", {"default": 127, "min": 0, "max": 255, "step": 1, "tooltip": "Lower threshold value (0-255)."}), + "High": ("FLOAT", {"default": 255, "min": 0, "max": 255, "step": 1, "tooltip": "Upper threshold value (0-255)."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("image",) + OUTPUT_TOOLTIPS = ("The thresholded image.",) FUNCTION = "exec" CATEGORY = module_cat @@ -1045,28 +1078,30 @@ class mdColorThreshold: High: float, mask: torch.Tensor = None, ) -> Tuple[torch.Tensor]: - # Immediately delegate all work to the core function return exec_operator_color_threshold(image, Channel, Low, High, mask) #=================================================================================== class mdColorColorize: + DESCRIPTION = "Colorizes the image by mapping its luminance to a given hue, saturation, and lightness, with an alternative solid color tint." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "Hue": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.001}), - "Saturation": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.001}), - "Lightness": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.001}), - "Color": ("INT", {"default": 0x7A95C7, "min": 0, "max": MAX_RGB_INT}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "Hue": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.001, "tooltip": "Target hue (0-1)."}), + "Saturation": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.001, "tooltip": "Target saturation (0-1)."}), + "Lightness": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.001, "tooltip": "Target lightness (0-1)."}), + "Color": ("INT", {"default": 0x7A95C7, "min": 0, "max": MAX_RGB_INT, "tooltip": "Solid color to apply as a tint (RGB hex integer)."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("image",) + OUTPUT_TOOLTIPS = ("The colorized image.",) FUNCTION = "exec" CATEGORY = module_cat @@ -1079,25 +1114,27 @@ class mdColorColorize: Color: int, mask: Optional[torch.Tensor] = None ) -> Tuple[torch.Tensor]: - # Immediately delegate all work to the core function return exec_operator_color_colorize(image, Hue, Saturation, Lightness, Color, mask) #=================================================================================== class mdColorPosterize: + DESCRIPTION = "Reduces the number of colors in the image by quantizing each channel to a limited number of levels." + @classmethod def INPUT_TYPES(cls) -> dict: return { "required": { - "image": ("IMAGE",), - "Color": ("INT", {"default": 3, "min": 2, "max": 256}), + "image": ("IMAGE", {"tooltip": "Input image tensor."}), + "Color": ("INT", {"default": 3, "min": 2, "max": 256, "tooltip": "Number of colors (levels) per channel (2-256)."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to restrict the adjustment area."}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("image",) + OUTPUT_TOOLTIPS = ("The posterized image.",) FUNCTION = "exec" CATEGORY = module_cat @@ -1107,7 +1144,6 @@ class mdColorPosterize: Color: int, mask: torch.Tensor = None, ) -> Tuple[torch.Tensor]: - # Immediately delegate all processing to the core operator return exec_operator_color_posterize(image, Color, mask) #=================================================================================== diff --git a/nodes/layer_nodes.py b/nodes/layer_nodes.py index cb05ec6..3d7bff3 100644 --- a/nodes/layer_nodes.py +++ b/nodes/layer_nodes.py @@ -1,4 +1,3 @@ - module_cat = "md/layers" from ..py.layers_blending import composite_layer_stack @@ -11,13 +10,14 @@ class mdLayerStackNode: Composites a layer over a background using a blend mode, a mask, and a global transparency. Handles RGBA images (4 channels) and respects the layer's own alpha channel. """ - + DESCRIPTION = "Composites a layer over a background using a blend mode, mask, and transparency. Supports multiple blend modes including Normal, Screen, Multiply, Overlay, etc." + @classmethod def INPUT_TYPES(cls): return { "required": { - "background": ("IMAGE",), - "layer": ("IMAGE",), + "background": ("IMAGE", {"tooltip": "The base image to which the layer will be composited."}), + "layer": ("IMAGE", {"tooltip": "The layer to composite over the background."}), "blending_mode": (["Normal", "Screen", "Multiply", "Overlay", "Difference", "Dissolve", "Clear", "Add", "Subtract", "Replace", "Darken Only", "Lighten Only", "Color Dodge", "Color Burn", @@ -25,11 +25,11 @@ class mdLayerStackNode: "Linear Burn (Multiply)", "Vivid Light", "Halo", "Negate", "Merge RGB", "Merge Cyan", "Merge Magenta", "Merge Yellow", "Merge Black", "Fill with FG Color", "Fill with BG Color", - "Average", "Composite", "Behind", "Template"],), - "transparency": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "Average", "Composite", "Behind", "Template"], {"tooltip": "The blend mode to use when compositing the layer."}), + "transparency": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "Global transparency for the layer (0.0 = fully transparent, 1.0 = fully opaque)."}), }, "optional": { - "mask": ("MASK",), + "mask": ("MASK", {"tooltip": "Optional mask to control where the layer is applied (white = visible, black = hidden)."}), } } @@ -37,6 +37,9 @@ class mdLayerStackNode: FUNCTION = "exec" CATEGORY = module_cat + OUTPUT_TOOLTIPS = ["Composited image result"] + RETURN_NAMES = ("Composited Image",) + def exec(self, background, layer, blending_mode, transparency, mask=None): return composite_layer_stack(background, layer, blending_mode, transparency, mask) @@ -48,3 +51,4 @@ NODE_CLASS_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = { "mdLayerStackNode": "Layer Stack Node (MD)", } +