feat: add Color Palette Extractor and refactor math nodes (v1.2.0)
This commit is contained in:
@@ -338,6 +338,26 @@ Useful for dynamic resizing, conditional logic based on aspect ratio, or passing
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>🎨 Color Palette Extractor</b> - Get dominant colors from an image</summary>
|
||||
|
||||
**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.
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
### 🧮 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 |
|
||||
|
||||
|
||||
+5
-1
@@ -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"
|
||||
@@ -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",
|
||||
}
|
||||
+29
-32
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user