diff --git a/README.md b/README.md index 9f5402b..fbfa02d 100644 --- a/README.md +++ b/README.md @@ -338,6 +338,26 @@ Useful for dynamic resizing, conditional logic based on aspect ratio, or passing +
+🎨 Color Palette Extractor - Get dominant colors from an image + +**Category:** `DebugPadawan/Image` + +Analyzes an image and extracts the most dominant colors as hex strings and a visual palette. + +**📥 Inputs:** +- `image` *(Image)*: The image to analyze. +- `color_count` *(Integer)*: Number of colors to extract (default: 5, max: 20). + +**📤 Outputs:** +- `hex_list` *(String)*: Comma-separated list of top hex colors. +- `dominant_color` *(String)*: The most frequent hex color. +- `palette_image` *(Image)*: A generated image showing the extracted color palette. + +Maintain consistent styles, extract themes from reference images, or use colors for conditional prompting. + +
+ --- ### 🧮 Math & Random @@ -522,7 +542,7 @@ Data → Wait (2.0s) → Processing → Wait (1.0s) → Output | **DebugPadawan/Timing** | Wait | Timing control and delays | | **DebugPadawan/Utilities** | List Info | Data analysis helpers | | **DebugPadawan/Logic** | Conditional String, Logic Gate | Conditional operations | -| **DebugPadawan/Image** | Image Info | Image tensor analysis | +| **DebugPadawan/Image** | Image Info, Color Palette Extractor | Image tensor analysis and color extraction | | **DebugPadawan/Math** | Int/Float Math Operation, Random Generator | Basic arithmetic and random number generation | | **DebugPadawan/List** | Get List Item, List Slicer | List manipulation | diff --git a/__init__.py b/__init__.py index 9f8d34d..739a8e2 100644 --- a/__init__.py +++ b/__init__.py @@ -13,6 +13,8 @@ from .nodes.json import NODE_CLASS_MAPPINGS as JSON_NODES from .nodes.json import NODE_DISPLAY_NAME_MAPPINGS as JSON_DISPLAY_NAMES from .nodes.image import NODE_CLASS_MAPPINGS as IMAGE_NODES from .nodes.image import NODE_DISPLAY_NAME_MAPPINGS as IMAGE_DISPLAY_NAMES +from .nodes.color_palette import NODE_CLASS_MAPPINGS as COLOR_PALETTE_NODES +from .nodes.color_palette import NODE_DISPLAY_NAME_MAPPINGS as COLOR_PALETTE_DISPLAY_NAMES from .nodes.math_nodes import NODE_CLASS_MAPPINGS as MATH_NODES from .nodes.math_nodes import NODE_DISPLAY_NAME_MAPPINGS as MATH_DISPLAY_NAMES from .nodes.list_nodes import NODE_CLASS_MAPPINGS as LIST_NODES @@ -30,6 +32,7 @@ NODE_CLASS_MAPPINGS = { **TIMING_NODES, **JSON_NODES, **IMAGE_NODES, + **COLOR_PALETTE_NODES, **MATH_NODES, **LIST_NODES, **JSON_TO_TEXT_NODES, @@ -42,6 +45,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { **TIMING_DISPLAY_NAMES, **JSON_DISPLAY_NAMES, **IMAGE_DISPLAY_NAMES, + **COLOR_PALETTE_DISPLAY_NAMES, **MATH_DISPLAY_NAMES, **LIST_DISPLAY_NAMES, **JSON_TO_TEXT_DISPLAY_NAMES, @@ -51,5 +55,5 @@ NODE_DISPLAY_NAME_MAPPINGS = { __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] # Version info -__version__ = "1.1.0" +__version__ = "1.2.0" __author__ = "DebugPadawan" \ No newline at end of file diff --git a/nodes/color_palette.py b/nodes/color_palette.py new file mode 100644 index 0000000..ba8127e --- /dev/null +++ b/nodes/color_palette.py @@ -0,0 +1,75 @@ +import numpy as np +import torch + +class ColorPaletteExtractor: + """ + Node for extracting the most dominant colors from an image + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + "color_count": ("INT", {"default": 5, "min": 1, "max": 20}), + } + } + + RETURN_TYPES = ("STRING", "STRING", "IMAGE") + RETURN_NAMES = ("hex_list", "dominant_color", "palette_image") + FUNCTION = "extract" + CATEGORY = "DebugPadawan/Image" + + def extract(self, image, color_count): + # Image is typically [B, H, W, C] + # We'll take the first image in the batch + img = image[0] + h, w, c = img.shape + + # Rescale for performance using torch + img_torch = img.permute(2, 0, 1).unsqueeze(0) # [1, C, H, W] + img_small = torch.nn.functional.interpolate(img_torch, size=(128, 128), mode='area') + img_np = img_small.squeeze(0).permute(1, 2, 0).numpy() + + # Flatten and scale to 0-255 + pixels = img_np.reshape(-1, c) * 255.0 + + # Simple quantization + pixels = (pixels / 16).astype(int) * 16 + + # Convert to hex strings + hex_colors = [] + for p in pixels: + r, g, b = p + hex_colors.append(f'#{r:02x}{g:02x}{b:02x}') + + # Count frequencies + unique, counts = np.unique(hex_colors, return_counts=True) + sorted_indices = np.argsort(-counts) + + top_hex = unique[sorted_indices[:color_count]] + dominant = top_hex[0] if len(top_hex) > 0 else "#000000" + + # Create a palette image + palette_h = 64 + palette_w = color_count * 64 + palette_img = np.zeros((palette_h, palette_w, 3), dtype=np.float32) + + for i, hex_color in enumerate(top_hex): + r = int(hex_color[1:3], 16) / 255.0 + g = int(hex_color[3:5], 16) / 255.0 + b = int(hex_color[5:7], 16) / 255.0 + palette_img[:, i*64:(i+1)*64, 0] = r + palette_img[:, i*64:(i+1)*64, 1] = g + palette_img[:, i*64:(i+1)*64, 2] = b + + palette_tensor = torch.from_numpy(palette_img).unsqueeze(0) + + return (", ".join(top_hex), dominant, palette_tensor) + +NODE_CLASS_MAPPINGS = { + "DebugPadawan_ColorPalette": ColorPaletteExtractor, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "DebugPadawan_ColorPalette": "Color Palette Extractor", +} diff --git a/nodes/math_nodes.py b/nodes/math_nodes.py index fd66e65..7d2679f 100644 --- a/nodes/math_nodes.py +++ b/nodes/math_nodes.py @@ -1,7 +1,32 @@ import random import math -class IntMathOperation: +class BaseMathOperation: + """ + Base class for math operations to reduce duplication + """ + def _perform_calculation(self, a, b, operation, is_int=True): + if operation == "add": + res = a + b + elif operation == "subtract": + res = a - b + elif operation == "multiply": + res = a * b + elif operation == "divide": + res = a / b if b != 0 else 0 + elif operation == "modulo": + res = a % b if b != 0 else 0 + elif operation == "power": + res = math.pow(a, b) + else: + res = 0 + + if is_int: + return (int(res), float(res)) + else: + return (float(res), int(res)) + +class IntMathOperation(BaseMathOperation): """ Node for performing basic integer math operations """ @@ -21,25 +46,10 @@ class IntMathOperation: CATEGORY = "DebugPadawan/Math" def perform_math(self, a, b, operation): - if operation == "add": - res = a + b - elif operation == "subtract": - res = a - b - elif operation == "multiply": - res = a * b - elif operation == "divide": - res = a / b if b != 0 else 0 - elif operation == "modulo": - res = a % b if b != 0 else 0 - elif operation == "power": - res = math.pow(a, b) - else: - res = 0 - - return (int(res), float(res)) + return self._perform_calculation(a, b, operation, is_int=True) -class FloatMathOperation: +class FloatMathOperation(BaseMathOperation): """ Node for performing basic float math operations """ @@ -59,20 +69,7 @@ class FloatMathOperation: CATEGORY = "DebugPadawan/Math" def perform_math(self, a, b, operation): - if operation == "add": - res = a + b - elif operation == "subtract": - res = a - b - elif operation == "multiply": - res = a * b - elif operation == "divide": - res = a / b if b != 0 else 0.0 - elif operation == "power": - res = math.pow(a, b) - else: - res = 0.0 - - return (float(res), int(res)) + return self._perform_calculation(a, b, operation, is_int=False) class RandomGenerator: