Add SVGColorPicker node and integrate with color picker UI extension

This commit is contained in:
tj_comfyui
2025-11-08 15:30:06 +09:00
parent 8b3cf1d1c2
commit 92eca1808d
3 changed files with 175 additions and 3 deletions
+5 -2
View File
@@ -16,6 +16,7 @@ from .svg_style_editor_simple import NODE_CLASS_MAPPINGS as SVG_STYLE_SIMPLE_NOD
from .svg_reorder import NODE_CLASS_MAPPINGS as SVG_REORDER_NODES, NODE_DISPLAY_NAME_MAPPINGS as SVG_REORDER_NAMES
from .svg_auto_reorder import NODE_CLASS_MAPPINGS as SVG_AUTO_REORDER_NODES, NODE_DISPLAY_NAME_MAPPINGS as SVG_AUTO_REORDER_NAMES
from .svg_visibility import NODE_CLASS_MAPPINGS as SVG_VISIBILITY_NODES, NODE_DISPLAY_NAME_MAPPINGS as SVG_VISIBILITY_NAMES
from .svg_color_picker import NODE_CLASS_MAPPINGS as SVG_COLOR_PICKER_NODES, NODE_DISPLAY_NAME_MAPPINGS as SVG_COLOR_PICKER_NAMES
# Combine all node mappings
NODE_CLASS_MAPPINGS = {
@@ -31,7 +32,8 @@ NODE_CLASS_MAPPINGS = {
**SVG_STYLE_SIMPLE_NODES,
**SVG_REORDER_NODES,
**SVG_AUTO_REORDER_NODES,
**SVG_VISIBILITY_NODES
**SVG_VISIBILITY_NODES,
**SVG_COLOR_PICKER_NODES
}
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -47,7 +49,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
**SVG_STYLE_SIMPLE_NAMES,
**SVG_REORDER_NAMES,
**SVG_AUTO_REORDER_NAMES,
**SVG_VISIBILITY_NAMES
**SVG_VISIBILITY_NAMES,
**SVG_COLOR_PICKER_NAMES
}
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
+166
View File
@@ -0,0 +1,166 @@
"""
SVG Color Hex Output Node for ComfyUI
Outputs a normalized #RRGGBBAA color string for use in downstream SVG style editors.
Features:
- Accept raw input (hex #RGB, #RRGGBB, #RRGGBBAA, rgb()/rgba(), named colors)
- Sliders for R,G,B,A (0-255) override (if > -1)
- Optional preset dropdown (basic palette)
- Normalizes final output to #RRGGBBAA
Inputs:
- base_color (STRING)
- preset (CHOICE)
- r_override / g_override / b_override / a_override (INT -1 = no override)
Outputs:
- STRING (color_hex): Normalized #RRGGBBAA
- STRING (meta): JSON with parsed components
"""
import json
from typing import Tuple
from xml.etree import ElementTree as ET # (kept for parity; not used directly)
class SVGColorPicker:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"base_color": ("STRING", {"default": "#FFFFFFFF", "multiline": False, "tooltip": "Input color (hex, rgba(), or name)"}),
},
"optional": {
"preset": ([
"none",
"transparent",
"white",
"black",
"red",
"green",
"blue",
"yellow",
"cyan",
"magenta",
"gray"
], {"default": "none"}),
"r_override": ("INT", {"default": -1, "min": -1, "max": 255, "step": 1}),
"g_override": ("INT", {"default": -1, "min": -1, "max": 255, "step": 1}),
"b_override": ("INT", {"default": -1, "min": -1, "max": 255, "step": 1}),
"a_override": ("INT", {"default": -1, "min": -1, "max": 255, "step": 1, "tooltip": "Alpha 0-255 (-1 keeps parsed)"}),
}
}
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("color_hex", "meta")
FUNCTION = "make_color"
CATEGORY = "TJ_Vector"
def make_color(self,
base_color: str = "#FFFFFFFF",
preset: str = "none",
r_override: int = -1,
g_override: int = -1,
b_override: int = -1,
a_override: int = -1):
# If preset selected, override base_color
if preset != "none":
base_color = self._preset_to_color(preset)
r, g, b, a = self._parse_color(base_color)
# Apply overrides if provided
if r_override >= 0: r = self._clamp(r_override)
if g_override >= 0: g = self._clamp(g_override)
if b_override >= 0: b = self._clamp(b_override)
if a_override >= 0: a = self._clamp(a_override)
hex_out = f"#{r:02X}{g:02X}{b:02X}{a:02X}"
meta = {
"input": base_color,
"preset": preset,
"r": r,
"g": g,
"b": b,
"a": a,
"hex": hex_out
}
return (hex_out, json.dumps(meta, indent=2))
# ----- helpers -----
def _preset_to_color(self, name: str) -> str:
mapping = {
"transparent": "#00000000",
"white": "#FFFFFFFF",
"black": "#000000FF",
"red": "#FF0000FF",
"green": "#00FF00FF",
"blue": "#0000FFFF",
"yellow": "#FFFF00FF",
"cyan": "#00FFFFFF",
"magenta": "#FF00FFFF",
"gray": "#808080FF",
}
return mapping.get(name, "#FFFFFFFF")
def _parse_color(self, s: str) -> Tuple[int,int,int,int]:
if not s:
return (255,255,255,255)
s = s.strip()
# Hex forms
if s.startswith('#'):
h = s[1:]
if len(h) == 3: # RGB shorthand
r = int(h[0]*2,16); g = int(h[1]*2,16); b = int(h[2]*2,16); a = 255
return (r,g,b,a)
if len(h) == 4: # RGBA shorthand
r = int(h[0]*2,16); g = int(h[1]*2,16); b = int(h[2]*2,16); a = int(h[3]*2,16)
return (r,g,b,a)
if len(h) == 6: # RRGGBB
r = int(h[0:2],16); g = int(h[2:4],16); b = int(h[4:6],16); a = 255
return (r,g,b,a)
if len(h) == 8: # RRGGBBAA
r = int(h[0:2],16); g = int(h[2:4],16); b = int(h[4:6],16); a = int(h[6:8],16)
return (r,g,b,a)
# rgba() or rgb()
if s.lower().startswith('rgb'):
import re
m = re.match(r'rgba?\(([^)]+)\)', s, re.IGNORECASE)
if m:
parts = [p.strip() for p in m.group(1).split(',')]
if len(parts) >= 3:
try:
r = int(float(parts[0])); g = int(float(parts[1])); b = int(float(parts[2]))
a = 255
if len(parts) >= 4:
alpha = float(parts[3])
if alpha <= 1.0: alpha *= 255
a = int(alpha)
return (self._clamp(r), self._clamp(g), self._clamp(b), self._clamp(a))
except ValueError:
pass
# Named colors fallback via simple mapping
named = {
'red': (255,0,0,255),
'green': (0,255,0,255),
'blue': (0,0,255,255),
'black': (0,0,0,255),
'white': (255,255,255,255),
'yellow': (255,255,0,255),
'cyan': (0,255,255,255),
'magenta': (255,0,255,255),
'gray': (128,128,128,255),
'transparent': (0,0,0,0)
}
return named.get(s.lower(), (255,255,255,255))
def _clamp(self, v: int) -> int:
return max(0, min(255, int(v)))
NODE_CLASS_MAPPINGS = {
"SVGColorPicker": SVGColorPicker,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"SVGColorPicker": "SVG Color Picker",
}
+4 -1
View File
@@ -11,7 +11,8 @@ app.registerExtension({
"SVGToImage", // svg_to_raster
"SVGGroupLayout", // svg_group_layout
"SVG Style Editor (Simple)", // svg_style_editor_simple display name
"SVG Style Editor" // svg_style_editor
"SVG Style Editor", // svg_style_editor
"SVG Color Picker" // svg_color_picker
]);
const colorFields = new Set([
@@ -20,6 +21,8 @@ app.registerExtension({
"override_stroke_color",
"override_fill_color",
"control_point_color",
// color picker node
"base_color",
// group layout
"grid_line_color",
"label_color",