first pass on cleaning all emoji from input/output
This commit is contained in:
+18
-159
@@ -13,27 +13,31 @@
|
||||
@reference: https://github.com/Amorano/Jovimetrix
|
||||
@tags: adjust, animate, compose, compositing, composition, device, flow, video,
|
||||
mask, shape, animation, logic
|
||||
@description: GIPHY. Animation via tick.
|
||||
Wave-based parameter modulation, Math operations with
|
||||
Unary and Binary support, universal Value conversion for all major
|
||||
types (int, string, list, dict, Image, Mask), shape masking, image channel ops,
|
||||
batch processing, dynamic bus routing. Queue & Load from URLs.
|
||||
@description: Animation via tick. Parameter manipulation with wave generator.
|
||||
Unary and Binary math support. Value convert int/float/bool, VectorN and Image,
|
||||
Mask types. Shape mask generator. Stack images, do channel ops, split, merge
|
||||
and randomize arrays and batches. Load images & video from anywhere. Dynamic
|
||||
bus routing. Save output anywhere! Flatten, crop, transform; check
|
||||
colorblindness or linear interpolate values.
|
||||
@node list:
|
||||
ConstantNode, ShapeNode, StereogramNode, StereoscopicNode, TextNode,
|
||||
AdjustNode, BlendNode, ColorBlindNode, ColorMatchNode, ColorTheoryNode, CropNode,
|
||||
FilterMaskNode, FlattenNode, GradientMapNode, PixelMergeNode, PixelSplitNode,
|
||||
PixelSwapNode, StackNode, ThresholdNode,TransformNode,
|
||||
ComparisonNode, DelayNode, LerpNode, CalcUnaryOPNode, CalcBinaryOPNode,
|
||||
StringerNode, SwizzleNode, TickNode, ValueNode, WaveGeneratorNode,
|
||||
AkashicNode, ArrayNode, ExportNode, ValueGraphNode, ImageInfoNode, QueueNode,
|
||||
QueueTooNode, RouteNode, SaveOutputNode
|
||||
TickNode, TickSimpleNode, WaveGeneratorNode
|
||||
BitSplitNode, ComparisonNode, LerpNode, OPUnaryNode, OPBinaryNode, StringerNode, SwizzleNode,
|
||||
ColorBlindNode, ColorMatchNode, ColorKMeansNode, ColorTheoryNode, GradientMapNode,
|
||||
AdjustNode, BlendNode, FilterMaskNode, PixelMergeNode, PixelSplitNode, PixelSwapNode, ThresholdNode,
|
||||
ConstantNode, ShapeNode, TextNode,
|
||||
CropNode, FlattenNode, StackNode, TransformNode,
|
||||
|
||||
ArrayNode, QueueNode, QueueTooNode,
|
||||
AkashicNode, GraphNode, ImageInfoNode,
|
||||
DelayNode, ExportNode, RouteNode, SaveOutputNode
|
||||
|
||||
ValueNode, Vector2Node, Vector3Node, Vector4Node,
|
||||
"""
|
||||
|
||||
__author__ = "Alexander G. Morano"
|
||||
__email__ = "amorano@gmail.com"
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from cozy_comfyui import \
|
||||
logger
|
||||
@@ -52,151 +56,6 @@ except FileNotFoundError:
|
||||
if JOV_DOCKERENV:
|
||||
logger.info("RUNNING IN A DOCKER")
|
||||
|
||||
# ==============================================================================
|
||||
# === LEXICON ===
|
||||
# ==============================================================================
|
||||
|
||||
# EMOJI OCD Support
|
||||
# 🔗 ⚓ 📀 🍿 🎪 🐘 🤯 😱 💀 ⛓️ 🔒 🔑 🪀 🪁 🧿 🧯 🦚 ♻️ ⚜️ 🚮 🤲🏽 👍 ✳️ ✌🏽 ☝🏽
|
||||
|
||||
class LexiconMeta(type):
|
||||
def __new__(cls, name, bases, dct) -> object:
|
||||
_tooltips = {}
|
||||
for attr_name, attr_value in dct.items():
|
||||
if isinstance(attr_value, tuple):
|
||||
attr_name = attr_value[1]
|
||||
attr_value = attr_value[0]
|
||||
_tooltips[attr_value] = attr_name
|
||||
dct['_tooltipsDB'] = _tooltips
|
||||
return super().__new__(cls, name, bases, dct)
|
||||
|
||||
def __getattribute__(cls, name) -> Any | None:
|
||||
parts = name.split('.')
|
||||
value = super().__getattribute__(parts[0])
|
||||
if type(value) == tuple:
|
||||
try:
|
||||
idx = int(parts[-1])
|
||||
value = value[idx]
|
||||
except:
|
||||
value = value[0]
|
||||
return value
|
||||
|
||||
# HSV = 'HSV', "Hue, Saturation and Value"
|
||||
# INDEX = 'INDEX', "Current item index in the Queue list"
|
||||
# JUSTIFY = 'JUSTIFY', "How to align the text to the side margins of the canvas: Left, Right, or Centered"
|
||||
# LETTER = 'LETTER', "If each letter be generated and output in a batch"
|
||||
# LMH = 'LMH', "Low, Middle, High"
|
||||
# MARGIN = 'MARGIN', "Whitespace padding around canvas"
|
||||
# MODE = 'MODE', "Decide whether the images should be resized to fit a specific dimension. Available modes include scaling to fit within given dimensions or keeping the original size"
|
||||
# QUALITY_M = 'MOTION', "Motion Quality"
|
||||
# RANGE = 'RANGE', "start index, ending index (0 means full length) and how many items to skip per step"
|
||||
# REPLACE = 'REPLACE', "String to use as replacement"
|
||||
# SHAPE = 'SHAPE', "Circle, Square or Polygonal forms"
|
||||
# SIDES = 'SIDES', "Number of sides polygon has (3-100)"
|
||||
# SPACING = 'SPACING', "Line Spacing between Text Lines"
|
||||
# START = 'START', "Start of the range"
|
||||
# TILE = 'TILE', "How many times to repeat the data in the X and Y"
|
||||
# TOTAL = 'TOTAL', "Total items in the current Queue List"
|
||||
|
||||
class Lexicon(metaclass=LexiconMeta):
|
||||
A = '⬜', "Alpha"
|
||||
AMP = '🔊', "Amplitude"
|
||||
ANGLE = '📐', "Rotation Angle"
|
||||
ANY = '🔮', "Any Type"
|
||||
ANY_OUT = '🦄', "Any Type"
|
||||
B = '🟦', "Blue"
|
||||
BI = '💙', "Blue Channel"
|
||||
BOOLEAN = '🇴', "Boolean"
|
||||
BOTTOM = '🔽', "Bottom"
|
||||
C1 = '🔵', "Color Scheme Result 1"
|
||||
C2 = '🟡', "Color Scheme Result 2"
|
||||
C3 = '🟣', "Color Scheme Result 3"
|
||||
C4 = '⚫️', "Color Scheme Result 4"
|
||||
C5 = '⚪', "Color Scheme Result 5"
|
||||
COLORMAP = '🇸🇨', "One of two dozen CV2 Built-in Colormap LUT (Look Up Table) Presets"
|
||||
COMP_A = '😍', "pass this data on a successful condition"
|
||||
COMP_B = '🥵', "pass this data on a failure condition"
|
||||
COMPARE = '🕵🏽♀️', "Comparison function. Will pass the data in 😍 on successful comparison"
|
||||
CONTRAST = '🌓', "Contrast"
|
||||
FLIP = '🙃', "Flip Input A and Input B with each other"
|
||||
FLOAT = '🛟', "Float"
|
||||
FOCAL = '📽️', "Focal Length"
|
||||
FPS = '🏎️', "Frames per second"
|
||||
FUNC = '⚒️', "Function"
|
||||
G = '🟩', "Green"
|
||||
GAMMA = '🔆', "Gamma"
|
||||
GI = '💚', "Green Channel"
|
||||
GRADIENT = '🇲🇺', "Gradient"
|
||||
H = '🇭', "Hue"
|
||||
IMAGE = '🖼️', "RGB-A color image with alpha channel"
|
||||
IN_A = '🅰️', "Input A"
|
||||
IN_B = '🅱️', "Input B"
|
||||
INT = '🔟', "Integer"
|
||||
INVERT = '🔳', "Color Inversion"
|
||||
KEY = '🔑', "Key"
|
||||
LEFT = '◀️', "Left"
|
||||
LINEAR = '🛟', "Linear"
|
||||
LIST = '🧾', "List"
|
||||
LOOP = '🔄', "Loop"
|
||||
LUT = '😎', "Size of each output lut palette square"
|
||||
MASK = '😷', "Mask or Image to use as Mask to control where adjustments are applied"
|
||||
MI = '🤍', "Alpha Channel"
|
||||
MIRROR = '🪞', "Mirror"
|
||||
NOTE = '🎶', "Note"
|
||||
PALETTE = '🎨', "Palette"
|
||||
PASS_IN = '📥', "Pass In"
|
||||
PASS_OUT = '📤', "Pass Out"
|
||||
PIXEL = '👾', "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
PIXEL_A = '👾A', "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
PIXEL_B = '👾B', "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
R = '🟥', "Red"
|
||||
RADIUS = '🅡', "Radius"
|
||||
RGB = '🌈', "RGB (no alpha) Color"
|
||||
RGB_A = '🌈A', "RGB (no alpha) Color"
|
||||
RGBA_A = '🌈A', "RGB with Alpha Color"
|
||||
RI = '❤️', "Red Channel"
|
||||
RIGHT = '▶️', "Right"
|
||||
ROUTE = '🚌', "Route"
|
||||
S = '🇸', "Saturation"
|
||||
SAMPLE = '🎞️', "Method for resizing images."
|
||||
SIZE = '📏', "Scalar by which to scale the input"
|
||||
STEP = '🦶🏽', "Steps/Stride between pulses -- useful to do odd or even batches. If set to 0 will stretch from (VAL -> LOOP) / Batch giving a linear range of values."
|
||||
STRENGTH = '💪🏽', "Strength"
|
||||
STRING = '📝', "String Entry"
|
||||
THRESHOLD = '📉', "Threshold"
|
||||
TIME = '🕛', "Time"
|
||||
TIMER = '⏱', "Timer"
|
||||
TOP = '🔼', "Top"
|
||||
TRIGGER = '⚡', "Trigger"
|
||||
TYPE = '❓', "Type"
|
||||
UNKNOWN = '❔', "Unknown"
|
||||
V = '🇻', "Value"
|
||||
W = '🇼', "Width"
|
||||
WAIT = '✋🏽', "Wait"
|
||||
WAVE = '♒', "Wave Function"
|
||||
WH = '🇼🇭', "Width and Height as a Vector2 (x,y)"
|
||||
WHC = '🇼🇭🇨', "Width, Height and Channel as a Vector3 (x,y,z)"
|
||||
X = '🇽', "X"
|
||||
XY = '🇽🇾', "X and Y"
|
||||
Y = '🇾', "Y"
|
||||
Z = '🇿', "Z"
|
||||
|
||||
@classmethod
|
||||
def _parse(cls, node: dict) -> Dict[str, str]:
|
||||
for cat, entry in node.items():
|
||||
if cat not in ['optional', 'required']:
|
||||
continue
|
||||
for k, v in entry.items():
|
||||
if (widget_data := v[1] if isinstance(v, (tuple, list,)) and len(v) > 1 else None) is None:
|
||||
continue
|
||||
if (tip := widget_data.get("tooltip", None)):
|
||||
continue
|
||||
if (tip := cls._tooltipsDB.get(k, None)) is None:
|
||||
continue
|
||||
widget_data["tooltip"] = tip
|
||||
node[cat][k] = (v[0], widget_data)
|
||||
return node
|
||||
|
||||
# ==============================================================================
|
||||
# === GLOBAL ===
|
||||
# ==============================================================================
|
||||
|
||||
+30
-31
@@ -19,9 +19,6 @@ from cozy_comfyui.node import \
|
||||
from cozy_comfyui.api import \
|
||||
comfy_api_post, parse_reset
|
||||
|
||||
from .. import \
|
||||
Lexicon
|
||||
|
||||
from ..sup.anim import \
|
||||
EnumWave, \
|
||||
wave_op
|
||||
@@ -44,7 +41,7 @@ class TickNode(CozyBaseNode):
|
||||
NAME = "TICK (JOV) ⏱"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = ("INT", "FLOAT", "FLOAT", COZY_TYPE_ANY, COZY_TYPE_ANY,)
|
||||
RETURN_NAMES = ("VAL", Lexicon.LINEAR, Lexicon.FPS, Lexicon.TRIGGER, "BATCH",)
|
||||
RETURN_NAMES = ("VAL", "LINEAR", "FPS", "TRIGGER", "BATCH",)
|
||||
OUTPUT_IS_LIST = (True, False, False, False, False,)
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"Current value for the configured tick as ComfyUI List",
|
||||
@@ -64,7 +61,7 @@ A timer and frame counter, emitting pulses or signals based on time intervals. I
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
# data to pass on a pulse of the loop
|
||||
Lexicon.TRIGGER: (COZY_TYPE_ANY, {
|
||||
"TRIGGER": (COZY_TYPE_ANY, {
|
||||
"default": None,
|
||||
"tooltip":"Output to send when beat (BPM setting) is hit"
|
||||
}),
|
||||
@@ -73,11 +70,11 @@ A timer and frame counter, emitting pulses or signals based on time intervals. I
|
||||
"default": 0, "min": 0, "max": sys.maxsize,
|
||||
"tooltip": "the current frame number of the tick"
|
||||
}),
|
||||
Lexicon.LOOP: ("INT", {
|
||||
"LOOP": ("INT", {
|
||||
"default": 0, "min": 0, "max": sys.maxsize,
|
||||
"tooltip": "number of frames before looping starts. 0 means continuous playback (no loop point)"
|
||||
}),
|
||||
Lexicon.FPS: ("INT", {
|
||||
"FPS": ("INT", {
|
||||
"default": 24, "min": 1,
|
||||
"tooltip": "Fixed frame step rate based on FPS (1/FPS)"
|
||||
}),
|
||||
@@ -85,25 +82,26 @@ A timer and frame counter, emitting pulses or signals based on time intervals. I
|
||||
"default": 120, "min": 1, "max": 60000,
|
||||
"tooltip": "BPM trigger rate to send the input. If input is empty, TRUE is sent on trigger"
|
||||
}),
|
||||
Lexicon.NOTE: ("INT", {
|
||||
"NOTE": ("INT", {
|
||||
"default": 4, "min": 1, "max": 256,
|
||||
"tooltip":"Number of beats per measure. Quarter note is 4, Eighth is 8, 16 is 16, etc."}),
|
||||
"tooltip":"Number of beats per measure. Quarter note is 4, Eighth is 8, 16 is 16, etc."}),
|
||||
# stick the current "count"
|
||||
Lexicon.WAIT: ("BOOLEAN", {
|
||||
"HOLD": ("BOOLEAN", {
|
||||
"default": False}),
|
||||
# manual total = 0
|
||||
"RESET": ("BOOLEAN", {"default": False}),
|
||||
"RESET": ("BOOLEAN", {
|
||||
"default": False}),
|
||||
# how many frames to dump....
|
||||
"BATCH": ("INT", {
|
||||
"default": 1, "min": 1, "max": 32767,
|
||||
"tooltip": "Number of frames wanted"
|
||||
}),
|
||||
Lexicon.STEP: ("INT", {
|
||||
"STEP": ("INT", {
|
||||
"default": 0, "min": 0, "max": sys.maxsize
|
||||
}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def __init__(self, *arg, **kw) -> None:
|
||||
super().__init__(*arg, **kw)
|
||||
@@ -111,17 +109,17 @@ A timer and frame counter, emitting pulses or signals based on time intervals. I
|
||||
self.__frame = 0
|
||||
|
||||
def run(self, ident, **kw) -> tuple[int, float, float, Any]:
|
||||
passthru = parse_param(kw, Lexicon.TRIGGER, EnumConvertType.ANY, None)[0]
|
||||
stride = parse_param(kw, Lexicon.STEP, EnumConvertType.INT, 0, 0, sys.maxsize)[0]
|
||||
loop = parse_param(kw, Lexicon.LOOP, EnumConvertType.INT, 0, 0, sys.maxsize)[0]
|
||||
passthru = parse_param(kw, "TRIGGER", EnumConvertType.ANY, None)[0]
|
||||
stride = parse_param(kw, "STEP", EnumConvertType.INT, 0, 0, sys.maxsize)[0]
|
||||
loop = parse_param(kw, "LOOP", EnumConvertType.INT, 0, 0, sys.maxsize)[0]
|
||||
self.__frame = parse_param(kw, "VALUE", EnumConvertType.INT, self.__frame, 0, sys.maxsize)[0]
|
||||
if loop != 0:
|
||||
self.__frame %= loop
|
||||
# start_frame = max(0, start_frame)
|
||||
hold = parse_param(kw, Lexicon.WAIT, EnumConvertType.BOOLEAN, False)[0]
|
||||
fps = parse_param(kw, Lexicon.FPS, EnumConvertType.INT, 24, 1)[0]
|
||||
hold = parse_param(kw, "HOLD", EnumConvertType.BOOLEAN, False)[0]
|
||||
fps = parse_param(kw, "FPS", EnumConvertType.INT, 24, 1)[0]
|
||||
bpm = parse_param(kw, "BPM", EnumConvertType.INT, 120, 1)[0]
|
||||
divisor = parse_param(kw, Lexicon.NOTE, EnumConvertType.INT, 4, 1)[0]
|
||||
divisor = parse_param(kw, "NOTE", EnumConvertType.INT, 4, 1)[0]
|
||||
beat = 60. / max(1., bpm) / divisor
|
||||
batch = parse_param(kw, "BATCH", EnumConvertType.INT, 1, 1)[0]
|
||||
step_fps = 1. / max(1., float(fps))
|
||||
@@ -194,7 +192,7 @@ Value generator with normalized values based on based on time interval.
|
||||
}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[int, float|int]:
|
||||
value = parse_param(kw, "VALUE", EnumConvertType.INT, 0, -sys.maxsize, sys.maxsize)[0]
|
||||
@@ -221,7 +219,7 @@ class WaveGeneratorNode(CozyBaseNode):
|
||||
NAME_PRETTY = "WAVE GEN (JOV) 🌊"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = ("FLOAT", "INT", )
|
||||
RETURN_NAMES = (Lexicon.FLOAT, Lexicon.INT, )
|
||||
RETURN_NAMES = ("FLOAT", "INT", )
|
||||
SORT = 90
|
||||
DESCRIPTION = """
|
||||
Produce waveforms like sine, square, or sawtooth with adjustable frequency, amplitude, phase, and offset. It's handy for creating oscillating patterns or controlling animation dynamics. This node emits both continuous floating-point values and integer representations of the generated waves.
|
||||
@@ -232,36 +230,37 @@ Produce waveforms like sine, square, or sawtooth with adjustable frequency, ampl
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.WAVE: (EnumWave._member_names_, {
|
||||
"WAVE": (EnumWave._member_names_, {
|
||||
"default": EnumWave.SIN.name}),
|
||||
"FREQ": ("FLOAT", {
|
||||
"default": 1, "min": 0, "max": sys.maxsize, "step": 0.01,
|
||||
"tooltip": "Frequency"}),
|
||||
Lexicon.AMP: ("FLOAT", {
|
||||
"default": 1, "min": 0, "max": sys.maxsize, "step": 0.01}),
|
||||
"AMP": ("FLOAT", {
|
||||
"default": 1, "min": 0, "max": sys.maxsize, "step": 0.01,
|
||||
"tooltip": "Amplitude"}),
|
||||
"PHASE": ("FLOAT", {
|
||||
"default": 0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"OFFSET": ("FLOAT", {
|
||||
"default": 0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
Lexicon.TIME: ("FLOAT", {
|
||||
"TIME": ("FLOAT", {
|
||||
"default": 0, "min": 0, "max": sys.maxsize, "step": 0.0001}),
|
||||
Lexicon.INVERT: ("BOOLEAN", {
|
||||
"INVERT": ("BOOLEAN", {
|
||||
"default": False}),
|
||||
"ABSOLUTE": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltips": "Return the absolute value of the input"}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[float, int]:
|
||||
op = parse_param(kw, Lexicon.WAVE, EnumWave, EnumWave.SIN.name)
|
||||
op = parse_param(kw, "WAVE", EnumWave, EnumWave.SIN.name)
|
||||
freq = parse_param(kw, "FREQ", EnumConvertType.FLOAT, 1., 0.000001, sys.maxsize)
|
||||
amp = parse_param(kw, Lexicon.AMP, EnumConvertType.FLOAT, 1., 0., sys.maxsize)
|
||||
amp = parse_param(kw, "AMP", EnumConvertType.FLOAT, 1., 0., sys.maxsize)
|
||||
phase = parse_param(kw, "PHASE", EnumConvertType.FLOAT, 0.)
|
||||
shift = parse_param(kw, "OFFSET", EnumConvertType.FLOAT, 0.)
|
||||
delta_time = parse_param(kw, Lexicon.TIME, EnumConvertType.FLOAT, 0., 0., sys.maxsize)
|
||||
invert = parse_param(kw, Lexicon.INVERT, EnumConvertType.BOOLEAN, False)
|
||||
delta_time = parse_param(kw, "TIME", EnumConvertType.FLOAT, 0., 0., sys.maxsize)
|
||||
invert = parse_param(kw, "INVERT", EnumConvertType.BOOLEAN, False)
|
||||
absolute = parse_param(kw, "ABSOLUTE", EnumConvertType.BOOLEAN, False)
|
||||
results = []
|
||||
params = list(zip_longest_fill(op, freq, amp, phase, shift, delta_time, invert, absolute))
|
||||
|
||||
+285
-275
@@ -22,9 +22,6 @@ from cozy_comfyui.node import \
|
||||
COZY_TYPE_ANY, COZY_TYPE_NUMERICAL, COZY_TYPE_FULL, \
|
||||
CozyBaseNode
|
||||
|
||||
from .. import \
|
||||
Lexicon
|
||||
|
||||
from ..sup.anim import \
|
||||
EnumEase, \
|
||||
ease_op
|
||||
@@ -218,7 +215,7 @@ class BitSplitNode(CozyBaseNode):
|
||||
NAME = "BIT SPLIT (JOV) ⭄"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = (COZY_TYPE_ANY, "BOOLEAN",)
|
||||
RETURN_NAMES = ("BIT", Lexicon.BOOLEAN,)
|
||||
RETURN_NAMES = ("BIT", "BOOL",)
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"Bits as Numerical output (0 or 1)",
|
||||
"Bits as Boolean output (True or False)"
|
||||
@@ -240,7 +237,7 @@ IMAGE and MASK will return a TRUE bit for any non-black pixel, as a stream of bi
|
||||
"MSB": ("BOOLEAN", {"default": False, "tooltip":"return the most signifigant bits (True) or least signifigant bits first"})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[List[int], List[bool]]:
|
||||
value = parse_param(kw, "VALUE", EnumConvertType.ANY, [0])
|
||||
@@ -270,11 +267,249 @@ IMAGE and MASK will return a TRUE bit for any non-black pixel, as a stream of bi
|
||||
pbar.update_absolute(idx)
|
||||
return *list(zip(*results)),
|
||||
|
||||
class CalcUnaryOPNode(CozyBaseNode):
|
||||
class ComparisonNode(CozyBaseNode):
|
||||
NAME = "COMPARISON (JOV) 🕵🏽"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = (COZY_TYPE_ANY, COZY_TYPE_ANY,)
|
||||
RETURN_NAMES = ("OUT", "VAL",)
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"Outputs the input at PASS or FAIL depending the evaluation",
|
||||
"The comparison result value"
|
||||
)
|
||||
SORT = 130
|
||||
DESCRIPTION = """
|
||||
Evaluates two inputs (A and B) with a specified comparison operators and optional values for successful and failed comparisons. The node performs the specified operation element-wise between corresponding elements of A and B. If the comparison is successful for all elements, it returns the success value; otherwise, it returns the failure value. The node supports various comparison operators such as EQUAL, GREATER_THAN, LESS_THAN, AND, OR, IS, IN, etc.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> InputType:
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
"A": (COZY_TYPE_FULL, {
|
||||
"default": 0,
|
||||
"tooltip":"First value to compare"}),
|
||||
"B": (COZY_TYPE_FULL, {
|
||||
"default": 0,
|
||||
"tooltip":"Second value to compare"}),
|
||||
"PASS": (COZY_TYPE_ANY, {
|
||||
"default": 0,
|
||||
"tooltip": "Passed to OUT on a successful condition"}),
|
||||
"FAIL": (COZY_TYPE_ANY, {
|
||||
"default": 0,
|
||||
"tooltip": "Passed to OUT on a failure condition"}),
|
||||
"COMPARE": (EnumComparison._member_names_, {
|
||||
"default": EnumComparison.EQUAL.name,
|
||||
"tooltip": "Comparison function. Sends the data in PASS on successful comparison to OUT, otherwise sends the value in FAIL"}),
|
||||
"FLIP": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Reverse the inputs A and B"}),
|
||||
"INVERT": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Reverse the successful and failure inputs"}),
|
||||
}
|
||||
})
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[Any, Any]:
|
||||
A = parse_param(kw, "A", EnumConvertType.ANY, [0])
|
||||
B = parse_param(kw, "B", EnumConvertType.ANY, [0])
|
||||
size = max(len(A), len(B))
|
||||
good = parse_param(kw, "PASS", EnumConvertType.ANY, [0])[:size]
|
||||
fail = parse_param(kw, "FAIL", EnumConvertType.ANY, [0])[:size]
|
||||
op = parse_param(kw, "COMPARE", EnumComparison, EnumComparison.EQUAL.name)[:size]
|
||||
flip = parse_param(kw, "FLIP", EnumConvertType.BOOLEAN, False)[:size]
|
||||
invert = parse_param(kw, "INVERT", EnumConvertType.BOOLEAN, False)[:size]
|
||||
params = list(zip_longest_fill(A, B, good, fail, op, flip, invert))
|
||||
pbar = ProgressBar(len(params))
|
||||
vals = []
|
||||
results = []
|
||||
for idx, (A, B, good, fail, op, flip, invert) in enumerate(params):
|
||||
if not isinstance(A, (tuple, list,)):
|
||||
A = [A]
|
||||
if not isinstance(B, (tuple, list,)):
|
||||
B = [B]
|
||||
|
||||
size = min(4, max(len(A), len(B))) - 1
|
||||
typ = [EnumConvertType.FLOAT, EnumConvertType.VEC2, EnumConvertType.VEC3, EnumConvertType.VEC4][size]
|
||||
|
||||
val_a = parse_value(A, typ, [A[-1]] * size)
|
||||
if not isinstance(val_a, (list,)):
|
||||
val_a = [val_a]
|
||||
|
||||
val_b = parse_value(B, typ, [B[-1]] * size)
|
||||
if not isinstance(val_b, (list,)):
|
||||
val_b = [val_b]
|
||||
|
||||
if flip:
|
||||
val_a, val_b = val_b, val_a
|
||||
|
||||
match op:
|
||||
case EnumComparison.EQUAL:
|
||||
val = [a == b for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.GREATER_THAN:
|
||||
val = [a > b for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.GREATER_THAN_EQUAL:
|
||||
val = [a >= b for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.LESS_THAN:
|
||||
val = [a < b for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.LESS_THAN_EQUAL:
|
||||
val = [a <= b for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.NOT_EQUAL:
|
||||
val = [a != b for a, b in zip(val_a, val_b)]
|
||||
# LOGIC
|
||||
# case EnumBinaryOperation.NOT = 10
|
||||
case EnumComparison.AND:
|
||||
val = [a and b for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.NAND:
|
||||
val = [not(a and b) for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.OR:
|
||||
val = [a or b for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.NOR:
|
||||
val = [not(a or b) for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.XOR:
|
||||
val = [(a and not b) or (not a and b) for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.XNOR:
|
||||
val = [not((a and not b) or (not a and b)) for a, b in zip(val_a, val_b)]
|
||||
# IDENTITY
|
||||
case EnumComparison.IS:
|
||||
val = [a is b for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.IS_NOT:
|
||||
val = [a is not b for a, b in zip(val_a, val_b)]
|
||||
# GROUP
|
||||
case EnumComparison.IN:
|
||||
val = [a in val_b for a in val_a]
|
||||
case EnumComparison.NOT_IN:
|
||||
val = [a not in val_b for a in val_a]
|
||||
|
||||
output = all([bool(v) for v in val])
|
||||
if invert:
|
||||
output = not output
|
||||
|
||||
output = good if output == True else fail
|
||||
results.append([output, val])
|
||||
pbar.update_absolute(idx)
|
||||
|
||||
outs, vals = zip(*results)
|
||||
if isinstance(outs[0], (TensorType,)):
|
||||
if len(outs) > 1:
|
||||
outs = torch.stack(outs)
|
||||
else:
|
||||
outs = outs[0].unsqueeze(0)
|
||||
else:
|
||||
outs = list(outs)
|
||||
return outs, *vals,
|
||||
|
||||
class LerpNode(CozyBaseNode):
|
||||
NAME = "LERP (JOV) 🔰"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = (COZY_TYPE_ANY,)
|
||||
RETURN_NAMES = ("🦄",)
|
||||
OUTPUT_TOOLTIPS = (
|
||||
f"Output can vary depending on the type chosen in the {"TYPE"} parameter"
|
||||
)
|
||||
SORT = 30
|
||||
DESCRIPTION = """
|
||||
Calculate linear interpolation between two values or vectors based on a blending factor (alpha).
|
||||
|
||||
The node accepts optional start (IN_A) and end (IN_B) points, a blending factor (FLOAT), and various input types for both start and end points, such as single values (X, Y), 2-value vectors (IN_A2, IN_B2), 3-value vectors (IN_A3, IN_B3), and 4-value vectors (IN_A4, IN_B4).
|
||||
|
||||
Additionally, you can specify the easing function (EASE) and the desired output type (TYPE). It supports various easing functions for smoother transitions.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> InputType:
|
||||
d = super().INPUT_TYPES()
|
||||
names_convert = EnumConvertType._member_names_[:6]
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
"A": (COZY_TYPE_FULL, {
|
||||
"tooltip": "Custom Start Point"
|
||||
}),
|
||||
"B": (COZY_TYPE_FULL, {
|
||||
"tooltip": "Custom End Point"
|
||||
}),
|
||||
"ALPHA": ("VEC4", {
|
||||
"default": (0.5, 0.5, 0.5, 0.5), "mij": 0., "maj": 1.0,
|
||||
"tooltip": "Blend Amount. 0 = full A, 1 = full B"
|
||||
}),
|
||||
"AA": ("VEC4", {
|
||||
"default": (0, 0, 0, 0),
|
||||
"tooltip":"default value vector for A"
|
||||
}),
|
||||
"BB": ("VEC4", {
|
||||
"default": (1,1,1,1),
|
||||
"tooltip":"default value vector for B"
|
||||
}),
|
||||
"TYPE": (names_convert, {
|
||||
"default": "FLOAT",
|
||||
"tooltip":"Output type desired from resultant operation"
|
||||
}),
|
||||
"EASE": (["NONE"] + EnumEase._member_names_, {
|
||||
"default": "NONE"
|
||||
}),
|
||||
}
|
||||
})
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[Any, Any]:
|
||||
A = parse_param(kw, "A", EnumConvertType.ANY, [0])
|
||||
B = parse_param(kw, "B", EnumConvertType.ANY, [0])
|
||||
a_xyzw = parse_param(kw, "AA", EnumConvertType.VEC4, [(0, 0, 0, 0)])
|
||||
b_xyzw = parse_param(kw, "BB", EnumConvertType.VEC4, [(1, 1, 1, 1)])
|
||||
alpha = parse_param(kw, "FLOAT",EnumConvertType.VEC4, [(0.5,0.5,0.5,0.5)], 0, 1)
|
||||
op = parse_param(kw, "EASE", EnumEase, EnumEase.SIN_IN_OUT.name)
|
||||
typ = parse_param(kw, "TYPE", EnumNumberType, EnumNumberType.FLOAT.name)
|
||||
values = []
|
||||
params = list(zip_longest_fill(A, B, a_xyzw, b_xyzw, alpha, op, typ))
|
||||
pbar = ProgressBar(len(params))
|
||||
for idx, (A, B, a_xyzw, b_xyzw, alpha, op, typ) in enumerate(params):
|
||||
size = int(typ.value / 10)
|
||||
|
||||
if A is None:
|
||||
A = a_xyzw[:size]
|
||||
if B is None:
|
||||
B = b_xyzw[:size]
|
||||
|
||||
val_a = parse_value(A, EnumConvertType.VEC4, a_xyzw)
|
||||
val_b = parse_value(B, EnumConvertType.VEC4, b_xyzw)
|
||||
alpha = parse_value(alpha, EnumConvertType.VEC4, alpha)
|
||||
|
||||
if size > 1:
|
||||
val_a = val_a[:size + 1]
|
||||
val_b = val_b[:size + 1]
|
||||
else:
|
||||
val_a = [val_a[0]]
|
||||
val_b = [val_b[0]]
|
||||
|
||||
# logger.debug([A, B, val_a, val_b, alpha, size])
|
||||
|
||||
if op == "NONE":
|
||||
val = [val_b[x] * alpha[x] + val_a[x] * (1 - alpha[x]) for x in range(size)]
|
||||
else:
|
||||
# ease = EnumEase[op]
|
||||
val = [ease_op(op, val_a[x], val_b[x], alpha=alpha[x]) for x in range(size)]
|
||||
|
||||
convert = int if "INT" in typ.name else float
|
||||
ret = []
|
||||
for v in val:
|
||||
try:
|
||||
ret.append(convert(v))
|
||||
except OverflowError:
|
||||
ret.append(0)
|
||||
except Exception as e:
|
||||
logger.error(f"{e} :: {op}")
|
||||
ret.append(0)
|
||||
val = ret[0] if size == 1 else ret[:size+1]
|
||||
values.append(val)
|
||||
pbar.update_absolute(idx)
|
||||
return [values]
|
||||
|
||||
class OPUnaryNode(CozyBaseNode):
|
||||
NAME = "OP UNARY (JOV) 🎲"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = (COZY_TYPE_ANY,)
|
||||
RETURN_NAMES = (Lexicon.UNKNOWN,)
|
||||
RETURN_NAMES = ("❔",)
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"Output type will match the input type"
|
||||
)
|
||||
@@ -288,16 +523,18 @@ Perform single function operations like absolute value, mean, median, mode, magn
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.IN_A: (COZY_TYPE_NUMERICAL, {"default": None}),
|
||||
Lexicon.FUNC: (EnumUnaryOperation._member_names_, {"default": EnumUnaryOperation.ABS.name})
|
||||
"A": (COZY_TYPE_NUMERICAL, {
|
||||
"default": None}),
|
||||
"FUNCTION": (EnumUnaryOperation._member_names_, {
|
||||
"default": EnumUnaryOperation.ABS.name})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[bool]:
|
||||
results = []
|
||||
A = parse_param(kw, Lexicon.IN_A, EnumConvertType.ANY, [0])
|
||||
op = parse_param(kw, Lexicon.FUNC, EnumUnaryOperation, EnumUnaryOperation.ABS.name)
|
||||
A = parse_param(kw, "A", EnumConvertType.ANY, [0])
|
||||
op = parse_param(kw, "FUNCTION", EnumUnaryOperation, EnumUnaryOperation.ABS.name)
|
||||
params = list(zip_longest_fill(A, op))
|
||||
pbar = ProgressBar(len(params))
|
||||
for idx, (A, op) in enumerate(params):
|
||||
@@ -371,11 +608,11 @@ Perform single function operations like absolute value, mean, median, mode, magn
|
||||
pbar.update_absolute(idx)
|
||||
return (results,)
|
||||
|
||||
class CalcBinaryOPNode(CozyBaseNode):
|
||||
class OPBinaryNode(CozyBaseNode):
|
||||
NAME = "OP BINARY (JOV) 🌟"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = (COZY_TYPE_ANY,)
|
||||
RETURN_NAMES = (Lexicon.UNKNOWN,)
|
||||
RETURN_NAMES = ("❔",)
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"Output type will match the input type"
|
||||
)
|
||||
@@ -391,41 +628,41 @@ Execute binary operations like addition, subtraction, multiplication, division,
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.IN_A: (COZY_TYPE_NUMERICAL, {
|
||||
"A": (COZY_TYPE_NUMERICAL, {
|
||||
"default": None,
|
||||
"tooltip":"Passes a raw value directly, or supplies defaults for any value inputs without connections"}),
|
||||
Lexicon.IN_B: (COZY_TYPE_NUMERICAL, {
|
||||
"B": (COZY_TYPE_NUMERICAL, {
|
||||
"default": None,
|
||||
"tooltip":"Passes a raw value directly, or supplies defaults for any value inputs without connections"}),
|
||||
Lexicon.FUNC: (EnumBinaryOperation._member_names_, {
|
||||
"FUNCTION": (EnumBinaryOperation._member_names_, {
|
||||
"default": EnumBinaryOperation.ADD.name,
|
||||
"tooltip":"Arithmetic operation to perform"}),
|
||||
Lexicon.TYPE: (names_convert, {
|
||||
"TYPE": (names_convert, {
|
||||
"default": names_convert[2],
|
||||
"tooltip":"Output type desired from resultant operation"}),
|
||||
Lexicon.FLIP: ("BOOLEAN", {"default": False}),
|
||||
Lexicon.IN_A+Lexicon.IN_A: ("VEC4", {
|
||||
"FLIP": ("BOOLEAN", {
|
||||
"default": False}),
|
||||
"AA": ("VEC4", {
|
||||
"default": (0,0,0,0),
|
||||
"label": [Lexicon.X, Lexicon.Y, Lexicon.Z, Lexicon.W],
|
||||
"label": ["X", "Y", "Z", "W"],
|
||||
"tooltip":"value vector"}),
|
||||
Lexicon.IN_B+Lexicon.IN_B: ("VEC4", {
|
||||
"BB": ("VEC4", {
|
||||
"default": (0,0,0,0),
|
||||
"label": [Lexicon.X, Lexicon.Y, Lexicon.Z, Lexicon.W],
|
||||
"label": ["X", "Y", "Z", "W"],
|
||||
"tooltip":"value vector"}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[bool]:
|
||||
results = []
|
||||
A = parse_param(kw, Lexicon.IN_A, EnumConvertType.ANY, None)
|
||||
print('a', kw[Lexicon.IN_A], A)
|
||||
B = parse_param(kw, Lexicon.IN_B, EnumConvertType.ANY, None)
|
||||
a_xyzw = parse_param(kw, Lexicon.IN_A+Lexicon.IN_A, EnumConvertType.VEC4, [(0, 0, 0, 0)])
|
||||
b_xyzw = parse_param(kw, Lexicon.IN_B+Lexicon.IN_B, EnumConvertType.VEC4, [(0, 0, 0, 0)])
|
||||
op = parse_param(kw, Lexicon.FUNC, EnumBinaryOperation, EnumBinaryOperation.ADD.name)
|
||||
typ = parse_param(kw, Lexicon.TYPE, EnumConvertType, EnumConvertType.FLOAT.name)
|
||||
flip = parse_param(kw, Lexicon.FLIP, EnumConvertType.BOOLEAN, False)
|
||||
A = parse_param(kw, "A", EnumConvertType.ANY, None)
|
||||
B = parse_param(kw, "B", EnumConvertType.ANY, None)
|
||||
a_xyzw = parse_param(kw, "AA", EnumConvertType.VEC4, [(0, 0, 0, 0)])
|
||||
b_xyzw = parse_param(kw, "BB", EnumConvertType.VEC4, [(0, 0, 0, 0)])
|
||||
op = parse_param(kw, "FUNCTION", EnumBinaryOperation, EnumBinaryOperation.ADD.name)
|
||||
typ = parse_param(kw, "TYPE", EnumConvertType, EnumConvertType.FLOAT.name)
|
||||
flip = parse_param(kw, "FLIP", EnumConvertType.BOOLEAN, False)
|
||||
params = list(zip_longest_fill(A, B, a_xyzw, b_xyzw, op, typ, flip))
|
||||
pbar = ProgressBar(len(params))
|
||||
for idx, (A, B, a_xyzw, b_xyzw, op, typ, flip) in enumerate(params):
|
||||
@@ -526,238 +763,11 @@ Execute binary operations like addition, subtraction, multiplication, division,
|
||||
pbar.update_absolute(idx)
|
||||
return results
|
||||
|
||||
class ComparisonNode(CozyBaseNode):
|
||||
NAME = "COMPARISON (JOV) 🕵🏽"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = (COZY_TYPE_ANY, COZY_TYPE_ANY,)
|
||||
RETURN_NAMES = (Lexicon.TRIGGER, "VAL",)
|
||||
OUTPUT_TOOLTIPS = (
|
||||
f"Outputs the input at {Lexicon.IN_A} or {Lexicon.IN_B} depending on which evaluated TRUE",
|
||||
"The comparison result value"
|
||||
)
|
||||
SORT = 130
|
||||
DESCRIPTION = """
|
||||
Evaluates two inputs (A and B) with a specified comparison operators and optional values for successful and failed comparisons. The node performs the specified operation element-wise between corresponding elements of A and B. If the comparison is successful for all elements, it returns the success value; otherwise, it returns the failure value. The node supports various comparison operators such as EQUAL, GREATER_THAN, LESS_THAN, AND, OR, IS, IN, etc.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> InputType:
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.IN_A: (COZY_TYPE_FULL, {"default": 0,
|
||||
"tooltip":"Master Comparator"}),
|
||||
Lexicon.IN_B: (COZY_TYPE_FULL, {"default": 0,
|
||||
"tooltip":"Secondary Comparator"}),
|
||||
Lexicon.COMP_A: (COZY_TYPE_ANY, {"default": 0}),
|
||||
Lexicon.COMP_B: (COZY_TYPE_ANY, {"default": 0}),
|
||||
Lexicon.COMPARE: (EnumComparison._member_names_, {"default": EnumComparison.EQUAL.name}),
|
||||
Lexicon.FLIP: ("BOOLEAN", {"default": False}),
|
||||
Lexicon.INVERT: ("BOOLEAN", {"default": False,
|
||||
"tooltip":"reverse the successful and failure inputs"}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
|
||||
def run(self, **kw) -> tuple[Any, Any]:
|
||||
A = parse_param(kw, Lexicon.IN_A, EnumConvertType.ANY, [0])
|
||||
B = parse_param(kw, Lexicon.IN_B, EnumConvertType.ANY, [0])
|
||||
size = max(len(A), len(B))
|
||||
good = parse_param(kw, Lexicon.COMP_A, EnumConvertType.ANY, [0])[:size]
|
||||
fail = parse_param(kw, Lexicon.COMP_B, EnumConvertType.ANY, [0])[:size]
|
||||
op = parse_param(kw, Lexicon.COMPARE, EnumComparison, EnumComparison.EQUAL.name)[:size]
|
||||
flip = parse_param(kw, Lexicon.FLIP, EnumConvertType.BOOLEAN, False)[:size]
|
||||
invert = parse_param(kw, Lexicon.INVERT, EnumConvertType.BOOLEAN, False)[:size]
|
||||
params = list(zip_longest_fill(A, B, good, fail, op, flip, invert))
|
||||
pbar = ProgressBar(len(params))
|
||||
vals = []
|
||||
results = []
|
||||
for idx, (A, B, good, fail, op, flip, invert) in enumerate(params):
|
||||
if not isinstance(A, (tuple, list,)):
|
||||
A = [A]
|
||||
if not isinstance(B, (tuple, list,)):
|
||||
B = [B]
|
||||
|
||||
size = min(4, max(len(A), len(B))) - 1
|
||||
typ = [EnumConvertType.FLOAT, EnumConvertType.VEC2, EnumConvertType.VEC3, EnumConvertType.VEC4][size]
|
||||
|
||||
val_a = parse_value(A, typ, [A[-1]] * size)
|
||||
if not isinstance(val_a, (list,)):
|
||||
val_a = [val_a]
|
||||
|
||||
val_b = parse_value(B, typ, [B[-1]] * size)
|
||||
if not isinstance(val_b, (list,)):
|
||||
val_b = [val_b]
|
||||
|
||||
if flip:
|
||||
val_a, val_b = val_b, val_a
|
||||
|
||||
match op:
|
||||
case EnumComparison.EQUAL:
|
||||
val = [a == b for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.GREATER_THAN:
|
||||
val = [a > b for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.GREATER_THAN_EQUAL:
|
||||
val = [a >= b for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.LESS_THAN:
|
||||
val = [a < b for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.LESS_THAN_EQUAL:
|
||||
val = [a <= b for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.NOT_EQUAL:
|
||||
val = [a != b for a, b in zip(val_a, val_b)]
|
||||
# LOGIC
|
||||
# case EnumBinaryOperation.NOT = 10
|
||||
case EnumComparison.AND:
|
||||
val = [a and b for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.NAND:
|
||||
val = [not(a and b) for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.OR:
|
||||
val = [a or b for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.NOR:
|
||||
val = [not(a or b) for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.XOR:
|
||||
val = [(a and not b) or (not a and b) for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.XNOR:
|
||||
val = [not((a and not b) or (not a and b)) for a, b in zip(val_a, val_b)]
|
||||
# IDENTITY
|
||||
case EnumComparison.IS:
|
||||
val = [a is b for a, b in zip(val_a, val_b)]
|
||||
case EnumComparison.IS_NOT:
|
||||
val = [a is not b for a, b in zip(val_a, val_b)]
|
||||
# GROUP
|
||||
case EnumComparison.IN:
|
||||
val = [a in val_b for a in val_a]
|
||||
case EnumComparison.NOT_IN:
|
||||
val = [a not in val_b for a in val_a]
|
||||
|
||||
output = all([bool(v) for v in val])
|
||||
if invert:
|
||||
output = not output
|
||||
|
||||
output = good if output == True else fail
|
||||
results.append([output, val])
|
||||
pbar.update_absolute(idx)
|
||||
|
||||
outs, vals = zip(*results)
|
||||
if isinstance(outs[0], (TensorType,)):
|
||||
if len(outs) > 1:
|
||||
outs = torch.stack(outs)
|
||||
else:
|
||||
outs = outs[0].unsqueeze(0)
|
||||
else:
|
||||
outs = list(outs)
|
||||
return outs, *vals,
|
||||
|
||||
class LerpNode(CozyBaseNode):
|
||||
NAME = "LERP (JOV) 🔰"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = (COZY_TYPE_ANY,)
|
||||
RETURN_NAMES = (Lexicon.ANY_OUT,)
|
||||
OUTPUT_TOOLTIPS = (
|
||||
f"Output can vary depending on the type chosen in the {Lexicon.TYPE} parameter"
|
||||
)
|
||||
SORT = 30
|
||||
DESCRIPTION = """
|
||||
Calculate linear interpolation between two values or vectors based on a blending factor (alpha).
|
||||
|
||||
The node accepts optional start (IN_A) and end (IN_B) points, a blending factor (FLOAT), and various input types for both start and end points, such as single values (X, Y), 2-value vectors (IN_A2, IN_B2), 3-value vectors (IN_A3, IN_B3), and 4-value vectors (IN_A4, IN_B4).
|
||||
|
||||
Additionally, you can specify the easing function (EASE) and the desired output type (TYPE). It supports various easing functions for smoother transitions.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> InputType:
|
||||
d = super().INPUT_TYPES()
|
||||
names_convert = EnumConvertType._member_names_[:6]
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.IN_A: (COZY_TYPE_FULL, {
|
||||
"tooltip": "Custom Start Point"
|
||||
}),
|
||||
Lexicon.IN_B: (COZY_TYPE_FULL, {
|
||||
"tooltip": "Custom End Point"
|
||||
}),
|
||||
Lexicon.FLOAT: ("VEC4", {
|
||||
"default": (0.5, 0.5, 0.5, 0.5), "mij": 0., "maj": 1.0,
|
||||
"tooltip": "Blend Amount. 0 = full A, 1 = full B"
|
||||
}),
|
||||
Lexicon.IN_A+Lexicon.IN_A: ("VEC4", {
|
||||
"default": (0, 0, 0, 0),
|
||||
"tooltip":"default value vector for A"
|
||||
}),
|
||||
Lexicon.IN_B+Lexicon.IN_B: ("VEC4", {
|
||||
"default": (1,1,1,1),
|
||||
"tooltip":"default value vector for B"
|
||||
}),
|
||||
Lexicon.TYPE: (names_convert, {
|
||||
"default": "FLOAT",
|
||||
"tooltip":"Output type desired from resultant operation"
|
||||
}),
|
||||
"EASE": (["NONE"] + EnumEase._member_names_, {
|
||||
"default": "NONE"
|
||||
}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
|
||||
def run(self, **kw) -> tuple[Any, Any]:
|
||||
A = parse_param(kw, Lexicon.IN_A, EnumConvertType.ANY, [0])
|
||||
B = parse_param(kw, Lexicon.IN_B, EnumConvertType.ANY, [0])
|
||||
a_xyzw = parse_param(kw, Lexicon.IN_A+Lexicon.IN_A, EnumConvertType.VEC4, [(0, 0, 0, 0)])
|
||||
b_xyzw = parse_param(kw, Lexicon.IN_B+Lexicon.IN_B, EnumConvertType.VEC4, [(1, 1, 1, 1)])
|
||||
alpha = parse_param(kw, Lexicon.FLOAT,EnumConvertType.VEC4, [(0.5,0.5,0.5,0.5)], 0, 1)
|
||||
op = parse_param(kw, "EASE", EnumEase, EnumEase.SIN_IN_OUT.name)
|
||||
typ = parse_param(kw, Lexicon.TYPE, EnumNumberType, EnumNumberType.FLOAT.name)
|
||||
values = []
|
||||
params = list(zip_longest_fill(A, B, a_xyzw, b_xyzw, alpha, op, typ))
|
||||
pbar = ProgressBar(len(params))
|
||||
for idx, (A, B, a_xyzw, b_xyzw, alpha, op, typ) in enumerate(params):
|
||||
size = int(typ.value / 10)
|
||||
|
||||
if A is None:
|
||||
A = a_xyzw[:size]
|
||||
if B is None:
|
||||
B = b_xyzw[:size]
|
||||
|
||||
val_a = parse_value(A, EnumConvertType.VEC4, a_xyzw)
|
||||
val_b = parse_value(B, EnumConvertType.VEC4, b_xyzw)
|
||||
alpha = parse_value(alpha, EnumConvertType.VEC4, alpha)
|
||||
|
||||
if size > 1:
|
||||
val_a = val_a[:size + 1]
|
||||
val_b = val_b[:size + 1]
|
||||
else:
|
||||
val_a = [val_a[0]]
|
||||
val_b = [val_b[0]]
|
||||
|
||||
# logger.debug([A, B, val_a, val_b, alpha, size])
|
||||
|
||||
if op == "NONE":
|
||||
val = [val_b[x] * alpha[x] + val_a[x] * (1 - alpha[x]) for x in range(size)]
|
||||
else:
|
||||
# ease = EnumEase[op]
|
||||
val = [ease_op(op, val_a[x], val_b[x], alpha=alpha[x]) for x in range(size)]
|
||||
|
||||
convert = int if "INT" in typ.name else float
|
||||
ret = []
|
||||
for v in val:
|
||||
try:
|
||||
ret.append(convert(v))
|
||||
except OverflowError:
|
||||
ret.append(0)
|
||||
except Exception as e:
|
||||
logger.error(f"{e} :: {op}")
|
||||
ret.append(0)
|
||||
val = ret[0] if size == 1 else ret[:size+1]
|
||||
values.append(val)
|
||||
pbar.update_absolute(idx)
|
||||
return [values]
|
||||
|
||||
class StringerNode(CozyBaseNode):
|
||||
NAME = "STRINGER (JOV) 🪀"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = ("STRING", "INT",)
|
||||
RETURN_NAMES = (Lexicon.STRING, "COUNT",)
|
||||
RETURN_NAMES = ("STRING", "COUNT",)
|
||||
SORT = 44
|
||||
DESCRIPTION = """
|
||||
Manipulate strings through filtering
|
||||
@@ -769,10 +779,10 @@ Manipulate strings through filtering
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
# split, join, replace, trim/lift
|
||||
Lexicon.FUNC: (EnumConvertString._member_names_, {
|
||||
"FUNCTION": (EnumConvertString._member_names_, {
|
||||
"default": EnumConvertString.SPLIT.name,
|
||||
"tooltip":"Operation to perform on the input string"}),
|
||||
Lexicon.KEY: ("STRING", {
|
||||
"KEY": ("STRING", {
|
||||
"default":"", "dynamicPrompt":False,
|
||||
"tooltip":"Delimiter (SPLIT/JOIN) or string to use as search string (FIND/REPLACE)."}),
|
||||
"REPLACE": ("STRING", {
|
||||
@@ -782,19 +792,19 @@ Manipulate strings through filtering
|
||||
"tooltip":"Start, End and Step. Values will clip to the actual list size(s)."}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[TensorType, ...]:
|
||||
# turn any all inputs into the
|
||||
data_list = parse_dynamic(kw, Lexicon.UNKNOWN, EnumConvertType.ANY, [""])
|
||||
data_list = parse_dynamic(kw, "❔", EnumConvertType.ANY, [""])
|
||||
if data_list is None:
|
||||
logger.warn("no data for list")
|
||||
return ([],)
|
||||
# flat list of ALL the dynamic inputs...
|
||||
#data_list = flatten(data_list)
|
||||
# single operation mode -- like array node
|
||||
op = parse_param(kw, Lexicon.FUNC, EnumConvertString, EnumConvertString.SPLIT.name)[0]
|
||||
key = parse_param(kw, Lexicon.KEY, EnumConvertType.STRING, "")[0]
|
||||
op = parse_param(kw, "FUNCTION", EnumConvertString, EnumConvertString.SPLIT.name)[0]
|
||||
key = parse_param(kw, "KEY", EnumConvertType.STRING, "")[0]
|
||||
replace = parse_param(kw, "REPLACE", EnumConvertType.STRING, "")[0]
|
||||
stenst = parse_param(kw, "RANGE", EnumConvertType.VEC3INT, [(0, -1, 1)])[0]
|
||||
results = []
|
||||
@@ -828,7 +838,7 @@ class SwizzleNode(CozyBaseNode):
|
||||
NAME = "SWIZZLE (JOV) 😵"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = (COZY_TYPE_ANY,)
|
||||
RETURN_NAMES = (Lexicon.ANY_OUT,)
|
||||
RETURN_NAMES = ("🦄",)
|
||||
SORT = 40
|
||||
DESCRIPTION = """
|
||||
Swap components between two vectors based on specified swizzle patterns and values. It provides flexibility in rearranging vector elements dynamically.
|
||||
@@ -840,9 +850,9 @@ Swap components between two vectors based on specified swizzle patterns and valu
|
||||
names_convert = EnumConvertType._member_names_[3:6]
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.IN_A: (COZY_TYPE_NUMERICAL, {}),
|
||||
Lexicon.IN_B: (COZY_TYPE_NUMERICAL, {}),
|
||||
Lexicon.TYPE: (names_convert, {
|
||||
"A": (COZY_TYPE_NUMERICAL, {}),
|
||||
"B": (COZY_TYPE_NUMERICAL, {}),
|
||||
"TYPE": (names_convert, {
|
||||
"default": names_convert[2],
|
||||
"tooltip":"Output type desired from resultant operation"
|
||||
}),
|
||||
@@ -864,20 +874,20 @@ Swap components between two vectors based on specified swizzle patterns and valu
|
||||
}),
|
||||
"VEC": ("VEC4", {
|
||||
"default": (0,0,0,0), "mij": -sys.maxsize, "maj": sys.maxsize,
|
||||
"tooltip": "Compound value of type float, vec2, vec3 or vec4"
|
||||
"tooltip": "Default values for missing channels"
|
||||
})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[TensorType, ...]:
|
||||
pA = parse_param(kw, Lexicon.IN_A, EnumConvertType.VEC4, [(0,0,0,0)])
|
||||
pB = parse_param(kw, Lexicon.IN_B, EnumConvertType.VEC4, [(0,0,0,0)])
|
||||
pA = parse_param(kw, "A", EnumConvertType.VEC4, [(0,0,0,0)])
|
||||
pB = parse_param(kw, "B", EnumConvertType.VEC4, [(0,0,0,0)])
|
||||
swap_x = parse_param(kw, "SWAP_X", EnumSwizzle, EnumSwizzle.A_X.name)
|
||||
swap_y = parse_param(kw, "SWAP_Y", EnumSwizzle, EnumSwizzle.A_Y.name)
|
||||
swap_z = parse_param(kw, "SWAP_Z", EnumSwizzle, EnumSwizzle.A_W.name)
|
||||
swap_w = parse_param(kw, "SWAP_W", EnumSwizzle, EnumSwizzle.A_Z.name)
|
||||
default = parse_param(kw, "VECTOR", EnumConvertType.VEC4, 0, -sys.maxsize, sys.maxsize)
|
||||
default = parse_param(kw, "VEC", EnumConvertType.VEC4, 0, -sys.maxsize, sys.maxsize)
|
||||
|
||||
params = list(zip_longest_fill(pA, pB, swap_x, x, swap_y, y, swap_z, z, swap_w, w))
|
||||
results = []
|
||||
|
||||
+59
-49
@@ -27,9 +27,6 @@ from cozy_comfyui.image.convert import \
|
||||
from cozy_comfyui.image.misc import \
|
||||
image_stack
|
||||
|
||||
from .. import \
|
||||
Lexicon
|
||||
|
||||
from ..sup.image.color import \
|
||||
EnumCBDeficiency, EnumCBSimulator, EnumColorMap, EnumColorTheory, \
|
||||
color_lut_full, color_lut_match, color_lut_palette, \
|
||||
@@ -75,7 +72,9 @@ Simulate color blindness effects on images. You can select various types of colo
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL: (COZY_TYPE_IMAGE, {}),
|
||||
"IMAGE": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
}),
|
||||
"DEFICIENCY": (EnumCBDeficiency._member_names_, {
|
||||
"default": EnumCBDeficiency.PROTAN.name,
|
||||
"tooltip": "Type of color deficiency: Red (Protanopia), Green (Deuteranopia), Blue (Tritanopia)"
|
||||
@@ -90,10 +89,10 @@ Simulate color blindness effects on images. You can select various types of colo
|
||||
}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
pA = parse_param(kw, Lexicon.PIXEL, EnumConvertType.IMAGE, None)
|
||||
pA = parse_param(kw, "IMAGE", EnumConvertType.IMAGE, None)
|
||||
deficiency = parse_param(kw, "DEFICIENCY", EnumCBDeficiency, EnumCBDeficiency.PROTAN.name)
|
||||
simulator = parse_param(kw, "SIMULATOR", EnumCBSimulator, EnumCBSimulator.AUTOSELECT.name)
|
||||
severity = parse_param(kw, "VAL", EnumConvertType.FLOAT, 1)
|
||||
@@ -119,8 +118,12 @@ Adjust the color scheme of one image to match another with the Color Match Node.
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL_A: (COZY_TYPE_IMAGE, {}),
|
||||
Lexicon.PIXEL_B: (COZY_TYPE_IMAGE, {}),
|
||||
"IMAGE_A": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
}),
|
||||
"IMAGE_B": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
}),
|
||||
"MODE": (EnumColorMatchMode._member_names_, {
|
||||
"default": EnumColorMatchMode.REINHARD.name,
|
||||
"tooltip": "Match colors from an image or built-in (LUT), Histogram lookups or Reinhard method"
|
||||
@@ -129,27 +132,28 @@ Adjust the color scheme of one image to match another with the Color Match Node.
|
||||
"default": EnumColorMatchMap.USER_MAP.name,
|
||||
"tooltip": "Custom image that will be transformed into a LUT or a built-in cv2 LUT"
|
||||
}),
|
||||
Lexicon.COLORMAP: (EnumColorMap._member_names_, {
|
||||
"default": EnumColorMap.HSV.name
|
||||
"COLORMAP": (EnumColorMap._member_names_, {
|
||||
"default": EnumColorMap.HSV.name,
|
||||
"tooltip": "One of two dozen CV2 Built-in Colormap LUT (Look Up Table) Presets"
|
||||
}),
|
||||
"VAL": ("INT", {"default": 255, "min": 0, "max": 255, "tooltip":"The number of colors to use from the LUT during the remap. Will quantize the LUT range."}),
|
||||
Lexicon.FLIP: ("BOOLEAN", {"default": False}),
|
||||
Lexicon.INVERT: ("BOOLEAN", {"default": False,
|
||||
"FLIP": ("BOOLEAN", {"default": False}),
|
||||
"INVERT": ("BOOLEAN", {"default": False,
|
||||
"tooltip": "Invert the color match output"}),
|
||||
"MATTE": ("VEC4INT", {"default": (0, 0, 0, 255), "rgb": True}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
pA = parse_param(kw, Lexicon.PIXEL_A, EnumConvertType.IMAGE, None)
|
||||
pB = parse_param(kw, Lexicon.PIXEL_B, EnumConvertType.IMAGE, None)
|
||||
pA = parse_param(kw, "IMAGE_A", EnumConvertType.IMAGE, None)
|
||||
pB = parse_param(kw, "IMAGE_B", EnumConvertType.IMAGE, None)
|
||||
colormatch_mode = parse_param(kw, "MODE", EnumColorMatchMode, EnumColorMatchMode.REINHARD.name)
|
||||
colormatch_map = parse_param(kw, f"MAP", EnumColorMatchMap, EnumColorMatchMap.USER_MAP.name)
|
||||
colormap = parse_param(kw, Lexicon.COLORMAP, EnumColorMap, EnumColorMap.HSV.name)
|
||||
colormap = parse_param(kw, "COLORMAP", EnumColorMap, EnumColorMap.HSV.name)
|
||||
num_colors = parse_param(kw, "VAL", EnumConvertType.INT, 255)
|
||||
flip = parse_param(kw, Lexicon.FLIP, EnumConvertType.BOOLEAN, False)
|
||||
invert = parse_param(kw, Lexicon.INVERT, EnumConvertType.BOOLEAN, False)
|
||||
flip = parse_param(kw, "FLIP", EnumConvertType.BOOLEAN, False)
|
||||
invert = parse_param(kw, "INVERT", EnumConvertType.BOOLEAN, False)
|
||||
matte = parse_param(kw, "MATTE", EnumConvertType.VEC4, [(0, 0, 0, 255)], 0, 255)
|
||||
params = list(zip_longest_fill(pA, pB, colormap, colormatch_mode, colormatch_map, num_colors, flip, invert, matte))
|
||||
images = []
|
||||
@@ -195,7 +199,7 @@ class ColorKMeansNode(CozyBaseNode):
|
||||
NAME = "COLOR MEANS (JOV) 〰️"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "JLUT", "IMAGE",)
|
||||
RETURN_NAMES = (Lexicon.IMAGE, Lexicon.PALETTE, Lexicon.GRADIENT, Lexicon.LUT, Lexicon.RGB, )
|
||||
RETURN_NAMES = ("IMAGE", "PALETTE", "GRADIENT", "LUT", "RGB", )
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"Sequence of top-K colors. Count depends on value in `VAL`.",
|
||||
"Simple Tone palette based on result top-K colors. Width is taken from input.",
|
||||
@@ -212,12 +216,14 @@ The top-k colors ordered from most->least used as a strip, tonal palette and 3D
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL: (COZY_TYPE_IMAGE, {}),
|
||||
"IMAGE": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
}),
|
||||
"VAL": ("INT", {
|
||||
"default": 12, "min": 1, "max": 255,
|
||||
"tooltip":"The top K colors to select."
|
||||
}),
|
||||
Lexicon.SIZE: ("INT", {
|
||||
"SIZE": ("INT", {
|
||||
"default": 32, "min": 1, "max": 256,
|
||||
"tooltip":"Height of the tones in the strip. Width is based on input."
|
||||
}),
|
||||
@@ -225,20 +231,20 @@ The top-k colors ordered from most->least used as a strip, tonal palette and 3D
|
||||
"default": 33, "min": 3, "max": 256,
|
||||
"tooltip":"Number of nodes to use in interpolation of full LUT (256 is every pixel)."
|
||||
}),
|
||||
Lexicon.WH: ("VEC2", {
|
||||
"WH": ("VEC2", {
|
||||
"default": (256, 256), "mij":IMAGE_SIZE_MIN, "int": True,
|
||||
"label": [Lexicon.W, Lexicon.H]
|
||||
"label": ["W", "H"]
|
||||
}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
pA = parse_param(kw, Lexicon.PIXEL, EnumConvertType.IMAGE, None)
|
||||
pA = parse_param(kw, "IMAGE", EnumConvertType.IMAGE, None)
|
||||
kcolors = parse_param(kw, "VAL", EnumConvertType.INT, 12, 1, 255)
|
||||
lut_height = parse_param(kw, Lexicon.SIZE, EnumConvertType.INT, 32, 1, 256)
|
||||
lut_height = parse_param(kw, "SIZE", EnumConvertType.INT, 32, 1, 256)
|
||||
nodes = parse_param(kw, "COUNT", EnumConvertType.INT, 33, 1, 255)
|
||||
wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, [(256, 256)], IMAGE_SIZE_MIN)
|
||||
wihi = parse_param(kw, "WH", EnumConvertType.VEC2INT, [(256, 256)], IMAGE_SIZE_MIN)
|
||||
|
||||
params = list(zip_longest_fill(pA, kcolors, nodes, lut_height, wihi))
|
||||
top_colors = []
|
||||
@@ -271,7 +277,7 @@ class ColorTheoryNode(CozyBaseNode):
|
||||
NAME = "COLOR THEORY (JOV) 🛞"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE")
|
||||
RETURN_NAMES = (Lexicon.C1, Lexicon.C2, Lexicon.C3, Lexicon.C4, Lexicon.C5)
|
||||
RETURN_NAMES = ("C1", "C2", "C3", "C4", "C5")
|
||||
SORT = 100
|
||||
DESCRIPTION = """
|
||||
Generate a color harmony based on the selected scheme.
|
||||
@@ -286,7 +292,9 @@ Users can customize the angle of separation for color calculations, offering fle
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL: (COZY_TYPE_IMAGE, {}),
|
||||
"IMAGE": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
}),
|
||||
"SCHEME": (EnumColorTheory._member_names_, {
|
||||
"default": EnumColorTheory.COMPLIMENTARY.name
|
||||
}),
|
||||
@@ -294,16 +302,16 @@ Users can customize the angle of separation for color calculations, offering fle
|
||||
"default": 45, "min": -90, "max": 90,
|
||||
"tooltip": "Custom angle of separation to use when calculating colors"
|
||||
}),
|
||||
Lexicon.INVERT: ("BOOLEAN", {"default": False})
|
||||
"INVERT": ("BOOLEAN", {"default": False})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[List[TensorType], List[TensorType]]:
|
||||
pA = parse_param(kw, Lexicon.PIXEL, EnumConvertType.IMAGE, None)
|
||||
pA = parse_param(kw, "IMAGE", EnumConvertType.IMAGE, None)
|
||||
scheme = parse_param(kw, "SCHEME", EnumColorTheory, EnumColorTheory.COMPLIMENTARY.name)
|
||||
user = parse_param(kw, "VAL", EnumConvertType.INT, 0, -180, 180)
|
||||
invert = parse_param(kw, Lexicon.INVERT, EnumConvertType.BOOLEAN, False)
|
||||
invert = parse_param(kw, "INVERT", EnumConvertType.BOOLEAN, False)
|
||||
params = list(zip_longest_fill(pA, scheme, user, invert))
|
||||
images = []
|
||||
pbar = ProgressBar(len(params))
|
||||
@@ -316,7 +324,7 @@ Users can customize the angle of separation for color calculations, offering fle
|
||||
pbar.update_absolute(idx)
|
||||
return image_stack(images)
|
||||
|
||||
class GradientMap(CozyImageNode):
|
||||
class GradientMapNode(CozyImageNode):
|
||||
NAME = "GRADIENT MAP (JOV) 🇲🇺"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
SORT = 550
|
||||
@@ -331,40 +339,42 @@ The gradient image will be translated into a single row lookup table.
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL: (COZY_TYPE_IMAGE, {
|
||||
"IMAGE": (COZY_TYPE_IMAGE, {
|
||||
"tooltip":"Image to remap with gradient input"
|
||||
}),
|
||||
Lexicon.GRADIENT: (COZY_TYPE_IMAGE, {
|
||||
"tooltip":f"Look up table (LUT) to remap the input image in `{Lexicon.PIXEL}`"
|
||||
"GRADIENT": (COZY_TYPE_IMAGE, {
|
||||
"tooltip":f"Look up table (LUT) to remap the input image in `{"IMAGE"}`"
|
||||
}),
|
||||
Lexicon.FLIP: ("BOOLEAN", {
|
||||
"FLIP": ("BOOLEAN", {
|
||||
"default":False,
|
||||
"tooltip":"Reverse the gradient from left-to-right "
|
||||
}),
|
||||
"MODE": (EnumScaleMode._member_names_, {
|
||||
"default": EnumScaleMode.MATTE.name
|
||||
"default": EnumScaleMode.MATTE.name,
|
||||
"tooltip": "If the image should be resized to fit within given dimensions or keep the original size"
|
||||
}),
|
||||
Lexicon.WH: ("VEC2", {
|
||||
"WH": ("VEC2", {
|
||||
"default": (512, 512), "mij":IMAGE_SIZE_MIN, "int": True,
|
||||
"label": [Lexicon.W, Lexicon.H]
|
||||
"label": ["W", "H"]
|
||||
}),
|
||||
Lexicon.SAMPLE: (EnumInterpolation._member_names_, {
|
||||
"default": EnumInterpolation.LANCZOS4.name
|
||||
"SAMPLE": (EnumInterpolation._member_names_, {
|
||||
"default": EnumInterpolation.LANCZOS4.name,
|
||||
"tooltip": "Sampling method for resizing images"
|
||||
}),
|
||||
"MATTE": ("VEC4", {
|
||||
"default": (0, 0, 0, 255), "rgb": True
|
||||
})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
pA = parse_param(kw, Lexicon.PIXEL, EnumConvertType.IMAGE, None)
|
||||
gradient = parse_param(kw, Lexicon.GRADIENT, EnumConvertType.IMAGE, None)
|
||||
flip = parse_param(kw, Lexicon.FLIP, EnumConvertType.BOOLEAN, False)
|
||||
pA = parse_param(kw, "IMAGE", EnumConvertType.IMAGE, None)
|
||||
gradient = parse_param(kw, "GRADIENT", EnumConvertType.IMAGE, None)
|
||||
flip = parse_param(kw, "FLIP", EnumConvertType.BOOLEAN, False)
|
||||
mode = parse_param(kw, "MODE", EnumScaleMode, EnumScaleMode.MATTE.name)
|
||||
wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)
|
||||
sample = parse_param(kw, Lexicon.SAMPLE, EnumInterpolation, EnumInterpolation.LANCZOS4.name)
|
||||
wihi = parse_param(kw, "WH", EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)
|
||||
sample = parse_param(kw, "SAMPLE", EnumInterpolation, EnumInterpolation.LANCZOS4.name)
|
||||
matte = parse_param(kw, "MATTE", EnumConvertType.VEC4INT, [(0, 0, 0, 255)], 0, 255)
|
||||
images = []
|
||||
params = list(zip_longest_fill(pA, gradient, flip, mode, sample, wihi, matte))
|
||||
|
||||
+128
-97
@@ -24,9 +24,6 @@ from cozy_comfyui.image.convert import \
|
||||
from cozy_comfyui.image.misc import \
|
||||
image_minmax, image_stack
|
||||
|
||||
from .. import \
|
||||
Lexicon
|
||||
|
||||
from ..sup.image.color import \
|
||||
pixel_eval
|
||||
|
||||
@@ -65,49 +62,56 @@ Advanced options include pixelation, quantization, and morphological operations
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL: (COZY_TYPE_IMAGE, {}),
|
||||
Lexicon.MASK: (COZY_TYPE_IMAGE, {}),
|
||||
Lexicon.FUNC: (EnumAdjustOP._member_names_, {
|
||||
"IMAGE": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
}),
|
||||
"MASK": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
}),
|
||||
"FUNCTION": (EnumAdjustOP._member_names_, {
|
||||
"default": EnumAdjustOP.BLUR.name,
|
||||
"tooltip":"Type of adjustment (e.g., blur, sharpen, invert)"}),
|
||||
Lexicon.RADIUS: ("INT", {
|
||||
"RADIUS": ("INT", {
|
||||
"default": 3, "min": 3}),
|
||||
"VAL": ("FLOAT", {"default": 1, "min": 0, "step": 0.01}),
|
||||
"VAL": ("FLOAT", {
|
||||
"default": 1, "min": 0, "step": 0.01}),
|
||||
"LoHi": ("VEC2", {
|
||||
"default": (0, 1), "mij": 0, "maj": 1,
|
||||
"label": ["Low", "HI"]}),
|
||||
"LMH": ("VEC3", {
|
||||
"default": (0, 0.5, 1), "mij": 0, "maj": 1,
|
||||
"label": ["Low", "MID", "HI"]}),
|
||||
"label": ["Low", "MID", "HI"],
|
||||
"tooltip": "Low, Middle, High"}),
|
||||
"HSV": ("VEC3",{
|
||||
"default": (0, 1, 1), "mij": 0, "maj": 1,
|
||||
"label": [Lexicon.H, Lexicon.S, Lexicon.V]}),
|
||||
Lexicon.CONTRAST: ("FLOAT", {
|
||||
"label": ["H", "S", "V"],
|
||||
"tooltip": "Hue, Saturation and Value"}),
|
||||
"CONTRAST": ("FLOAT", {
|
||||
"default": 0, "min": 0, "max": 1, "step": 0.01}),
|
||||
Lexicon.GAMMA: ("FLOAT", {
|
||||
"GAMMA": ("FLOAT", {
|
||||
"default": 1, "min": 0.00001, "max": 1, "step": 0.01}),
|
||||
"MATTE": ("VEC4", {
|
||||
"default": (0, 0, 0, 255), "rgb": True}),
|
||||
Lexicon.INVERT: ("BOOLEAN", {
|
||||
"INVERT": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Invert the mask input"})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
pA = parse_param(kw, Lexicon.PIXEL, EnumConvertType.IMAGE, None)
|
||||
mask = parse_param(kw, Lexicon.MASK, EnumConvertType.IMAGE, None)
|
||||
op = parse_param(kw, Lexicon.FUNC, EnumAdjustOP, EnumAdjustOP.BLUR.name)
|
||||
radius = parse_param(kw, Lexicon.RADIUS, EnumConvertType.INT, 3, 3)
|
||||
pA = parse_param(kw, "IMAGE", EnumConvertType.IMAGE, None)
|
||||
mask = parse_param(kw, "MASK", EnumConvertType.IMAGE, None)
|
||||
op = parse_param(kw, "FUNCTION", EnumAdjustOP, EnumAdjustOP.BLUR.name)
|
||||
radius = parse_param(kw, "RADIUS", EnumConvertType.INT, 3, 3)
|
||||
val = parse_param(kw, "VAL", EnumConvertType.FLOAT, 0, 0)
|
||||
lohi = parse_param(kw, "LoHi", EnumConvertType.VEC2, [(0, 1)], 0, 1)
|
||||
lmh = parse_param(kw, "LMH", EnumConvertType.VEC3, [(0, 0.5, 1)], 0, 1)
|
||||
hsv = parse_param(kw, "HSV", EnumConvertType.VEC3, [(0, 1, 1)], 0, 1)
|
||||
contrast = parse_param(kw, Lexicon.CONTRAST, EnumConvertType.FLOAT, 1, 0, 1)
|
||||
gamma = parse_param(kw, Lexicon.GAMMA, EnumConvertType.FLOAT, 1, 0, 1)
|
||||
contrast = parse_param(kw, "CONTRAST", EnumConvertType.FLOAT, 1, 0, 1)
|
||||
gamma = parse_param(kw, "GAMMA", EnumConvertType.FLOAT, 1, 0, 1)
|
||||
matte = parse_param(kw, "MATTE", EnumConvertType.VEC4INT, [(0, 0, 0, 255)], 0, 255)
|
||||
invert = parse_param(kw, Lexicon.INVERT, EnumConvertType.BOOLEAN, False)
|
||||
invert = parse_param(kw, "INVERT", EnumConvertType.BOOLEAN, False)
|
||||
params = list(zip_longest_fill(pA, mask, op, radius, val, lohi,
|
||||
lmh, hsv, contrast, gamma, matte, invert))
|
||||
images = []
|
||||
@@ -223,47 +227,49 @@ Combine two input images using various blending modes, such as normal, screen, m
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL_A: (COZY_TYPE_IMAGE, {
|
||||
"IMAGE_A": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Background Plate"}),
|
||||
Lexicon.PIXEL_B: (COZY_TYPE_IMAGE, {
|
||||
"IMAGE_B": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Image to Overlay on Background Plate"}),
|
||||
Lexicon.MASK: (COZY_TYPE_IMAGE, {
|
||||
"MASK": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Optional Mask to use for Alpha Blend Operation. If empty, will use the ALPHA of B"}),
|
||||
Lexicon.FUNC: (EnumBlendType._member_names_, {
|
||||
"FUNCTION": (EnumBlendType._member_names_, {
|
||||
"default": EnumBlendType.NORMAL.name,
|
||||
"tooltip": "Blending Operation"}),
|
||||
Lexicon.A: ("FLOAT", {
|
||||
"ALPHA": ("FLOAT", {
|
||||
"default": 1, "min": 0, "max": 1, "step": 0.01,
|
||||
"tooltip": "Amount of Blending to Perform on the Selected Operation"}),
|
||||
Lexicon.FLIP: ("BOOLEAN", {
|
||||
"FLIP": ("BOOLEAN", {
|
||||
"default": False}),
|
||||
Lexicon.INVERT: ("BOOLEAN", {
|
||||
"INVERT": ("BOOLEAN", {
|
||||
"default": False, "tooltip": "Invert the mask input"}),
|
||||
"MODE": (EnumScaleMode._member_names_, {
|
||||
"default": EnumScaleMode.MATTE.name}),
|
||||
Lexicon.WH: ("VEC2", {
|
||||
"default": EnumScaleMode.MATTE.name,
|
||||
"tooltip": "If the image should be resized to fit within given dimensions or keep the original size"}),
|
||||
"WH": ("VEC2", {
|
||||
"default": (512, 512), "mij":IMAGE_SIZE_MIN, "int": True,
|
||||
"label": [Lexicon.W, Lexicon.H]}),
|
||||
Lexicon.SAMPLE: (EnumInterpolation._member_names_, {
|
||||
"default": EnumInterpolation.LANCZOS4.name}),
|
||||
"label": ["W", "H"]}),
|
||||
"SAMPLE": (EnumInterpolation._member_names_, {
|
||||
"default": EnumInterpolation.LANCZOS4.name,
|
||||
"tooltip": "Sampling method for resizing images"}),
|
||||
"MATTE": ("VEC4", {
|
||||
"default": (0, 0, 0, 255), "rgb": True})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
pA = parse_param(kw, Lexicon.PIXEL_A, EnumConvertType.IMAGE, None)
|
||||
pB = parse_param(kw, Lexicon.PIXEL_B, EnumConvertType.IMAGE, None)
|
||||
mask = parse_param(kw, Lexicon.MASK, EnumConvertType.MASK, None)
|
||||
func = parse_param(kw, Lexicon.FUNC, EnumBlendType, EnumBlendType.NORMAL.name)
|
||||
alpha = parse_param(kw, Lexicon.A, EnumConvertType.FLOAT, 1, 0, 1)
|
||||
flip = parse_param(kw, Lexicon.FLIP, EnumConvertType.BOOLEAN, False)
|
||||
pA = parse_param(kw, "IMAGE_A", EnumConvertType.IMAGE, None)
|
||||
pB = parse_param(kw, "IMAGE_B", EnumConvertType.IMAGE, None)
|
||||
mask = parse_param(kw, "MASK", EnumConvertType.MASK, None)
|
||||
func = parse_param(kw, "FUNCTION", EnumBlendType, EnumBlendType.NORMAL.name)
|
||||
alpha = parse_param(kw, "ALPHA", EnumConvertType.FLOAT, 1, 0, 1)
|
||||
flip = parse_param(kw, "FLIP", EnumConvertType.BOOLEAN, False)
|
||||
mode = parse_param(kw, "MODE", EnumScaleMode, EnumScaleMode.MATTE.name)
|
||||
wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)
|
||||
sample = parse_param(kw, Lexicon.SAMPLE, EnumInterpolation, EnumInterpolation.LANCZOS4.name)
|
||||
wihi = parse_param(kw, "WH", EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)
|
||||
sample = parse_param(kw, "SAMPLE", EnumInterpolation, EnumInterpolation.LANCZOS4.name)
|
||||
matte = parse_param(kw, "MATTE", EnumConvertType.VEC4INT, [(0, 0, 0, 255)], 0, 255)
|
||||
invert = parse_param(kw, Lexicon.INVERT, EnumConvertType.BOOLEAN, False)
|
||||
invert = parse_param(kw, "INVERT", EnumConvertType.BOOLEAN, False)
|
||||
params = list(zip_longest_fill(pA, pB, mask, func, alpha, flip, mode, wihi, sample, matte, invert))
|
||||
images = []
|
||||
pbar = ProgressBar(len(params))
|
||||
@@ -327,29 +333,31 @@ Create masks based on specific color ranges within an image. Specify the color r
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL_A: (COZY_TYPE_IMAGE, {}),
|
||||
"IMAGE_A": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
}),
|
||||
"START": ("VEC3", {
|
||||
"default": (128, 128, 128), "rgb": True}),
|
||||
Lexicon.BOOLEAN: ("BOOLEAN", {
|
||||
"RANGE": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "use an end point (start->end) when calculating the filter range"}),
|
||||
"END": ("VEC3", {
|
||||
"default": (128, 128, 128), "rgb": True}),
|
||||
Lexicon.FLOAT: ("VEC3", {
|
||||
"FUZZ": ("VEC3", {
|
||||
"default": (0.5,0.5,0.5), "mij":0, "maj":1,
|
||||
"tooltip": "the fuzziness use to extend the start and end range(s)"}),
|
||||
"MATTE": ("VEC4", {
|
||||
"default": (0, 0, 0, 255), "rgb": True}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
pA = parse_param(kw, Lexicon.PIXEL_A, EnumConvertType.IMAGE, None)
|
||||
pA = parse_param(kw, "IMAGE_A", EnumConvertType.IMAGE, None)
|
||||
start = parse_param(kw, "START", EnumConvertType.VEC3INT, [(128,128,128)], 0, 255)
|
||||
use_range = parse_param(kw, Lexicon.BOOLEAN, EnumConvertType.VEC3, [(0,0,0)], 0, 255)
|
||||
use_range = parse_param(kw, "RANGE", EnumConvertType.VEC3, [(0,0,0)], 0, 255)
|
||||
end = parse_param(kw, "END", EnumConvertType.VEC3INT, [(128,128,128)], 0, 255)
|
||||
fuzz = parse_param(kw, Lexicon.FLOAT, EnumConvertType.VEC3, [(0.5,0.5,0.5)], 0, 1)
|
||||
fuzz = parse_param(kw, "FUZZ", EnumConvertType.VEC3, [(0.5,0.5,0.5)], 0, 1)
|
||||
matte = parse_param(kw, "MATTE", EnumConvertType.VEC4INT, [(0, 0, 0, 255)], 0, 255)
|
||||
params = list(zip_longest_fill(pA, start, use_range, end, fuzz, matte))
|
||||
images = []
|
||||
@@ -379,41 +387,55 @@ Combines individual color channels (red, green, blue) along with an optional mas
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL: (COZY_TYPE_IMAGE, {}),
|
||||
Lexicon.R: (COZY_TYPE_IMAGE, {}),
|
||||
Lexicon.G: (COZY_TYPE_IMAGE, {}),
|
||||
Lexicon.B: (COZY_TYPE_IMAGE, {}),
|
||||
Lexicon.A: (COZY_TYPE_IMAGE, {}),
|
||||
"IMAGE": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
}),
|
||||
"🟥": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Red"
|
||||
}),
|
||||
"🟩": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Green"
|
||||
}),
|
||||
"🟦": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Blue"
|
||||
}),
|
||||
"⬜": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Alpha"
|
||||
}),
|
||||
"MODE": (EnumScaleMode._member_names_, {
|
||||
"default": EnumScaleMode.MATTE.name}),
|
||||
Lexicon.WH: ("VEC2", {
|
||||
"default": EnumScaleMode.MATTE.name,
|
||||
"tooltip": "If the image should be resized to fit within given dimensions or keep the original size"}),
|
||||
"WH": ("VEC2", {
|
||||
"default": (512, 512), "mij":IMAGE_SIZE_MIN, "int": True,
|
||||
"label": [Lexicon.W, Lexicon.H]}),
|
||||
Lexicon.SAMPLE: (EnumInterpolation._member_names_, {
|
||||
"default": EnumInterpolation.LANCZOS4.name}),
|
||||
"label": ["W", "H"],
|
||||
"tooltip": "Width and Height"}),
|
||||
"SAMPLE": (EnumInterpolation._member_names_, {
|
||||
"default": EnumInterpolation.LANCZOS4.name,
|
||||
"tooltip": "Sampling method for resizing images"}),
|
||||
"MATTE": ("VEC4", {
|
||||
"default": (0, 0, 0, 255), "rgb": True}),
|
||||
Lexicon.FLIP: ("VEC4", {
|
||||
"FLIP": ("VEC4", {
|
||||
"default": (0,0,0,0), "mij":0, "maj":1,
|
||||
"tooltip": "Invert specific input prior to merging. R, G, B, A."}),
|
||||
Lexicon.INVERT: ("BOOLEAN", {
|
||||
"default": False, "tooltip": "Invert the final merged output"})
|
||||
"INVERT": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Invert the final merged output"})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
rgba = parse_param(kw, Lexicon.PIXEL, EnumConvertType.IMAGE, None)
|
||||
R = parse_param(kw, Lexicon.R, EnumConvertType.MASK, None)
|
||||
G = parse_param(kw, Lexicon.G, EnumConvertType.MASK, None)
|
||||
B = parse_param(kw, Lexicon.B, EnumConvertType.MASK, None)
|
||||
A = parse_param(kw, Lexicon.A, EnumConvertType.MASK, None)
|
||||
rgba = parse_param(kw, "IMAGE", EnumConvertType.IMAGE, None)
|
||||
R = parse_param(kw, "🟥", EnumConvertType.MASK, None)
|
||||
G = parse_param(kw, "🟩", EnumConvertType.MASK, None)
|
||||
B = parse_param(kw, "🟦", EnumConvertType.MASK, None)
|
||||
A = parse_param(kw, "⬜", EnumConvertType.MASK, None)
|
||||
mode = parse_param(kw, "MODE", EnumScaleMode, EnumScaleMode.MATTE.name)
|
||||
wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)
|
||||
sample = parse_param(kw, Lexicon.SAMPLE, EnumInterpolation, EnumInterpolation.LANCZOS4.name)
|
||||
wihi = parse_param(kw, "WH", EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)
|
||||
sample = parse_param(kw, "SAMPLE", EnumInterpolation, EnumInterpolation.LANCZOS4.name)
|
||||
matte = parse_param(kw, "MATTE", EnumConvertType.VEC4INT, [(0, 0, 0, 255)], 0, 255)
|
||||
flip = parse_param(kw, Lexicon.FLIP, EnumConvertType.VEC4, [(0, 0, 0, 0)], 0., 1.)
|
||||
invert = parse_param(kw, Lexicon.INVERT, EnumConvertType.BOOLEAN, False)
|
||||
flip = parse_param(kw, "FLIP", EnumConvertType.VEC4, [(0, 0, 0, 0)], 0., 1.)
|
||||
invert = parse_param(kw, "INVERT", EnumConvertType.BOOLEAN, False)
|
||||
params = list(zip_longest_fill(rgba, R, G, B, A, mode, wihi, sample, matte, flip, invert))
|
||||
images = []
|
||||
pbar = ProgressBar(len(params))
|
||||
@@ -455,7 +477,7 @@ class PixelSplitNode(CozyBaseNode):
|
||||
NAME = "PIXEL SPLIT (JOV) 💔"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = ("MASK", "MASK", "MASK", "MASK",)
|
||||
RETURN_NAMES = (Lexicon.RI, Lexicon.GI, Lexicon.BI, Lexicon.MI)
|
||||
RETURN_NAMES = ("❤️", "💚", "💙", "🤍")
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"Single channel output of Red Channel.",
|
||||
"Single channel output of Green Channel",
|
||||
@@ -472,14 +494,16 @@ Takes an input image and splits it into its individual color channels (red, gree
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL: (COZY_TYPE_IMAGE, {})
|
||||
"IMAGE": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
images = []
|
||||
pA = parse_param(kw, Lexicon.PIXEL, EnumConvertType.IMAGE, None)
|
||||
pA = parse_param(kw, "IMAGE", EnumConvertType.IMAGE, None)
|
||||
pbar = ProgressBar(len(pA))
|
||||
for idx, pA in enumerate(pA):
|
||||
pA = channel_solid(chan=EnumImageType.BGRA) if pA is None else tensor_to_cv(pA)
|
||||
@@ -500,8 +524,12 @@ Swap pixel values between two input images based on specified channel swizzle op
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL_A: (COZY_TYPE_IMAGE, {}),
|
||||
Lexicon.PIXEL_B: (COZY_TYPE_IMAGE, {}),
|
||||
"IMAGE_A": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
}),
|
||||
"IMAGE_B": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
}),
|
||||
"SWAP_R": (EnumPixelSwizzle._member_names_, {
|
||||
"default": EnumPixelSwizzle.RED_A.name,
|
||||
"tooltip": "Replace input Red channel with target channel or constant"}),
|
||||
@@ -518,11 +546,11 @@ Swap pixel values between two input images based on specified channel swizzle op
|
||||
"default": (0, 0, 0, 255), "rgb": True})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
pA = parse_param(kw, Lexicon.PIXEL_A, EnumConvertType.IMAGE, None)
|
||||
pB = parse_param(kw, Lexicon.PIXEL_B, EnumConvertType.IMAGE, None)
|
||||
pA = parse_param(kw, "IMAGE_A", EnumConvertType.IMAGE, None)
|
||||
pB = parse_param(kw, "IMAGE_B", EnumConvertType.IMAGE, None)
|
||||
swap_r = parse_param(kw, Lexicon.SWAP_R, EnumPixelSwizzle, EnumPixelSwizzle.RED_A.name)
|
||||
swap_g = parse_param(kw, Lexicon.SWAP_G, EnumPixelSwizzle, EnumPixelSwizzle.GREEN_A.name)
|
||||
swap_b = parse_param(kw, Lexicon.SWAP_B, EnumPixelSwizzle, EnumPixelSwizzle.BLUE_A.name)
|
||||
@@ -569,30 +597,32 @@ Define a range and apply it to an image for segmentation and feature extraction.
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL: (COZY_TYPE_IMAGE, {}),
|
||||
"IMAGE": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
}),
|
||||
"ADAPT": ( EnumThresholdAdapt._member_names_, {
|
||||
"default": EnumThresholdAdapt.ADAPT_NONE.name,
|
||||
"tooltip": "X-Men"}),
|
||||
Lexicon.FUNC: ( EnumThreshold._member_names_, {
|
||||
"FUNCTION": ( EnumThreshold._member_names_, {
|
||||
"default": EnumThreshold.BINARY.name}),
|
||||
Lexicon.THRESHOLD: ("FLOAT", {
|
||||
"THRESHOLD": ("FLOAT", {
|
||||
"default": 0.5, "min": 0, "max": 1, "step": 0.005}),
|
||||
Lexicon.SIZE: ("INT", {
|
||||
"SIZE": ("INT", {
|
||||
"default": 3, "min": 3, "max": 103}),
|
||||
Lexicon.INVERT: ("BOOLEAN", {
|
||||
"INVERT": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Invert the mask input"})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
pA = parse_param(kw, Lexicon.PIXEL, EnumConvertType.IMAGE, None)
|
||||
mode = parse_param(kw, Lexicon.FUNC, EnumThreshold, EnumThreshold.BINARY.name)
|
||||
pA = parse_param(kw, "IMAGE", EnumConvertType.IMAGE, None)
|
||||
mode = parse_param(kw, "FUNCTION", EnumThreshold, EnumThreshold.BINARY.name)
|
||||
adapt = parse_param(kw, "ADAPT", EnumThresholdAdapt, EnumThresholdAdapt.ADAPT_NONE.name)
|
||||
threshold = parse_param(kw, Lexicon.THRESHOLD, EnumConvertType.FLOAT, 1, 0, 1)
|
||||
block = parse_param(kw, Lexicon.SIZE, EnumConvertType.INT, 3, 3)
|
||||
invert = parse_param(kw, Lexicon.INVERT, EnumConvertType.BOOLEAN, False)
|
||||
threshold = parse_param(kw, "THRESHOLD", EnumConvertType.FLOAT, 1, 0, 1)
|
||||
block = parse_param(kw, "SIZE", EnumConvertType.INT, 3, 3)
|
||||
invert = parse_param(kw, "INVERT", EnumConvertType.BOOLEAN, False)
|
||||
params = list(zip_longest_fill(pA, mode, adapt, threshold, block, invert))
|
||||
images = []
|
||||
pbar = ProgressBar(len(params))
|
||||
@@ -610,7 +640,7 @@ class HistogramNode(JOVImageSimple):
|
||||
NAME = "HISTOGRAM (JOV) 👁🗨"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
RETURN_NAMES = (Lexicon.IMAGE,)
|
||||
RETURN_NAMES = ("IMAGE",)
|
||||
SORT = 40
|
||||
DESCRIPTION = """
|
||||
The Histogram Node generates a histogram representation of the input image, showing the distribution of pixel intensity values across different bins. This visualization is useful for understanding the overall brightness and contrast characteristics of an image. Additionally, the node performs histogram normalization, which adjusts the pixel values to enhance the contrast of the image. Histogram normalization can be helpful for improving the visual quality of images or preparing them for further image processing tasks.
|
||||
@@ -621,13 +651,14 @@ The Histogram Node generates a histogram representation of the input image, show
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL: (COZY_TYPE_IMAGE, {}),
|
||||
"IMAGE": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
pA = parse_param(kw, Lexicon.PIXEL, None), EnumConvertType.IMAGE, None)
|
||||
pA = parse_param(kw, "IMAGE", None), EnumConvertType.IMAGE, None)
|
||||
params = list(zip_longest_fill(pA,))
|
||||
images = []
|
||||
pbar = ProgressBar(len(params))
|
||||
|
||||
+160
-150
@@ -1,6 +1,5 @@
|
||||
""" Jovimetrix - Creation """
|
||||
|
||||
import torch
|
||||
import numpy as np
|
||||
from PIL import ImageFont
|
||||
from skimage.filters import gaussian
|
||||
@@ -9,12 +8,12 @@ from comfy.utils import ProgressBar
|
||||
|
||||
from cozy_comfyui import \
|
||||
IMAGE_SIZE_MIN, \
|
||||
InputType, EnumConvertType, RGBAMaskType, TensorType, \
|
||||
InputType, EnumConvertType, RGBAMaskType, \
|
||||
deep_merge, parse_param, zip_longest_fill
|
||||
|
||||
from cozy_comfyui.node import \
|
||||
COZY_TYPE_IMAGE, \
|
||||
CozyBaseNode, CozyImageNode
|
||||
CozyImageNode
|
||||
|
||||
from cozy_comfyui.image import \
|
||||
EnumImageType
|
||||
@@ -23,19 +22,15 @@ from cozy_comfyui.image.misc import \
|
||||
image_stack
|
||||
|
||||
from cozy_comfyui.image.convert import \
|
||||
image_matte, image_mask_add, image_convert, \
|
||||
image_mask_add, image_convert, \
|
||||
pil_to_cv, cv_to_tensor, cv_to_tensor_full, tensor_to_cv
|
||||
|
||||
from .. import \
|
||||
Lexicon
|
||||
|
||||
from ..sup.image.channel import \
|
||||
channel_solid
|
||||
|
||||
from ..sup.image.compose import \
|
||||
EnumShapes, \
|
||||
image_blend, shape_ellipse, shape_polygon, shape_quad, image_mask_binary, \
|
||||
image_stereogram
|
||||
image_blend, shape_ellipse, shape_polygon, shape_quad, image_mask_binary
|
||||
|
||||
from ..sup.image.adjust import \
|
||||
EnumEdge, EnumScaleMode, EnumInterpolation, \
|
||||
@@ -63,31 +58,34 @@ Generate a constant image or mask of a specified size and color. It can be used
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL: (COZY_TYPE_IMAGE, {
|
||||
"IMAGE": (COZY_TYPE_IMAGE, {
|
||||
"tooltip":"Optional Image to Matte with Selected Color"}),
|
||||
Lexicon.MASK: (COZY_TYPE_IMAGE, {
|
||||
"MASK": (COZY_TYPE_IMAGE, {
|
||||
"tooltip":"Override Image mask"}),
|
||||
Lexicon.RGBA_A: ("VEC4", {
|
||||
"COLOR": ("VEC4", {
|
||||
"default": (0, 0, 0, 255), "rgb": True,
|
||||
"tooltip": "Constant Color to Output"}),
|
||||
"MODE": (EnumScaleMode._member_names_, {"default": EnumScaleMode.MATTE.name}),
|
||||
Lexicon.WH: ("VEC2", {
|
||||
"MODE": (EnumScaleMode._member_names_, {
|
||||
"default": EnumScaleMode.MATTE.name,
|
||||
"tooltip": "If the image should be resized to fit within given dimensions or keep the original size"}),
|
||||
"WH": ("VEC2", {
|
||||
"default": (512, 512), "int": True,
|
||||
"label": [Lexicon.W, Lexicon.H],
|
||||
"label": ["W", "H"],
|
||||
"tooltip": "Desired Width and Height of the Color Output"}),
|
||||
Lexicon.SAMPLE: (EnumInterpolation._member_names_, {
|
||||
"default": EnumInterpolation.LANCZOS4.name}),
|
||||
"SAMPLE": (EnumInterpolation._member_names_, {
|
||||
"default": EnumInterpolation.LANCZOS4.name,
|
||||
"tooltip": "Sampling method for resizing images"})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
pA = parse_param(kw, Lexicon.PIXEL, EnumConvertType.IMAGE, None)
|
||||
mask = parse_param(kw, Lexicon.MASK, EnumConvertType.IMAGE, None)
|
||||
matte = parse_param(kw, Lexicon.RGBA_A, EnumConvertType.VEC4INT, [(0, 0, 0, 255)], 0, 255)
|
||||
wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)
|
||||
pA = parse_param(kw, "IMAGE", EnumConvertType.IMAGE, None)
|
||||
mask = parse_param(kw, "MASK", EnumConvertType.IMAGE, None)
|
||||
matte = parse_param(kw, "COLOR", EnumConvertType.VEC4INT, [(0, 0, 0, 255)], 0, 255)
|
||||
wihi = parse_param(kw, "WH", EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)
|
||||
mode = parse_param(kw, "MODE", EnumScaleMode, EnumScaleMode.MATTE.name)
|
||||
sample = parse_param(kw, Lexicon.SAMPLE, EnumInterpolation, EnumInterpolation.LANCZOS4.name)
|
||||
sample = parse_param(kw, "SAMPLE", EnumInterpolation, EnumInterpolation.LANCZOS4.name)
|
||||
images = []
|
||||
params = list(zip_longest_fill(pA, mask, matte, wihi, mode, sample))
|
||||
pbar = ProgressBar(len(params))
|
||||
@@ -127,21 +125,23 @@ Create n-sided polygons. These shapes can be customized by adjusting parameters
|
||||
"default": EnumShapes.CIRCLE.name}),
|
||||
"SIDES": ("INT", {
|
||||
"default": 3, "min": 3, "max": 100}),
|
||||
Lexicon.RGBA_A: ("VEC4", {
|
||||
"COLOR": ("VEC4", {
|
||||
"default": (255, 255, 255, 255), "rgb": True,
|
||||
"tooltip": "Main Shape Color"}),
|
||||
"MATTE": ("VEC4", {
|
||||
"default": (0, 0, 0, 255), "rgb": True,
|
||||
"tooltip": "Background Color"}),
|
||||
Lexicon.WH: ("VEC2", {
|
||||
"WH": ("VEC2", {
|
||||
"default": (256, 256), "mij":IMAGE_SIZE_MIN, "int": True,
|
||||
"label": [Lexicon.W, Lexicon.H]}),
|
||||
Lexicon.XY: ("VEC2", {
|
||||
"default": (0, 0,), "label": [Lexicon.X, Lexicon.Y]}),
|
||||
Lexicon.ANGLE: ("FLOAT", {
|
||||
"default": 0, "min": -180, "max": 180, "step": 0.01}),
|
||||
Lexicon.SIZE: ("VEC2", {
|
||||
"default": (1., 1.), "label": [Lexicon.X, Lexicon.Y]}),
|
||||
"label": ["W", "H"],
|
||||
"tooltip": "Width and Height"}),
|
||||
"XY": ("VEC2", {
|
||||
"default": (0, 0,), "label": ["X", "Y"]}),
|
||||
"ANGLE": ("FLOAT", {
|
||||
"default": 0, "min": -180, "max": 180, "step": 0.01,
|
||||
"tooltip": "Rotation Angle"}),
|
||||
"SIZE": ("VEC2", {
|
||||
"default": (1., 1.), "label": ["X", "Y"]}),
|
||||
"EDGE": (EnumEdge._member_names_, {
|
||||
"default": EnumEdge.CLIP.name}),
|
||||
"BLUR": ("FLOAT", {
|
||||
@@ -149,17 +149,17 @@ Create n-sided polygons. These shapes can be customized by adjusting parameters
|
||||
"tooltip": "Edge blur amount (Gaussian blur)"}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
shape = parse_param(kw, "SHAPE", EnumShapes, EnumShapes.CIRCLE.name)
|
||||
sides = parse_param(kw, "SIDES", EnumConvertType.INT, 3, 3, 100)
|
||||
angle = parse_param(kw, Lexicon.ANGLE, EnumConvertType.FLOAT, 0)
|
||||
angle = parse_param(kw, "ANGLE", EnumConvertType.FLOAT, 0)
|
||||
edge = parse_param(kw, "EDGE", EnumEdge, EnumEdge.CLIP.name)
|
||||
offset = parse_param(kw, Lexicon.XY, EnumConvertType.VEC2, [(0, 0)])
|
||||
size = parse_param(kw, Lexicon.SIZE, EnumConvertType.VEC2, [(1, 1)], zero=0.001)
|
||||
wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, [(256, 256)], IMAGE_SIZE_MIN)
|
||||
color = parse_param(kw, Lexicon.RGBA_A, EnumConvertType.VEC4INT, [(255, 255, 255, 255)], 0, 255)
|
||||
offset = parse_param(kw, "XY", EnumConvertType.VEC2, [(0, 0)])
|
||||
size = parse_param(kw, "SIZE", EnumConvertType.VEC2, [(1, 1)], zero=0.001)
|
||||
wihi = parse_param(kw, "WH", EnumConvertType.VEC2INT, [(256, 256)], IMAGE_SIZE_MIN)
|
||||
color = parse_param(kw, "COLOR", EnumConvertType.VEC4INT, [(255, 255, 255, 255)], 0, 255)
|
||||
matte = parse_param(kw, "MATTE", EnumConvertType.VEC4INT, [(0, 0, 0, 255)], 0, 255)
|
||||
blur = parse_param(kw, "BLUR", EnumConvertType.FLOAT, 0)
|
||||
params = list(zip_longest_fill(shape, sides, offset, angle, edge, size, wihi, color, matte, blur))
|
||||
@@ -199,99 +199,6 @@ Create n-sided polygons. These shapes can be customized by adjusting parameters
|
||||
pbar.update_absolute(idx)
|
||||
return image_stack(images)
|
||||
|
||||
class StereogramNode(CozyImageNode):
|
||||
NAME = "STEREOGRAM (JOV) 📻"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
DESCRIPTION = """
|
||||
Generates false perception 3D images from 2D input. Set tile divisions, noise, gamma, and shift parameters to control the stereogram's appearance.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> InputType:
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL: (COZY_TYPE_IMAGE, {}),
|
||||
"DEPTH": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Grayscale image representing a depth map"
|
||||
}),
|
||||
"TILE": ("INT", {
|
||||
"default": 8, "min": 1}),
|
||||
"NOISE": ("FLOAT", {
|
||||
"default": 0.33, "min": 0, "max": 1, "step": 0.01}),
|
||||
Lexicon.GAMMA: ("FLOAT", {
|
||||
"default": 0.33, "min": 0, "max": 1, "step": 0.01}),
|
||||
"SHIFT": ("FLOAT", {
|
||||
"default": 1., "min": -1, "max": 1, "step": 0.01}),
|
||||
Lexicon.INVERT: ("BOOLEAN", {
|
||||
"default": False}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
pA = parse_param(kw, Lexicon.PIXEL, EnumConvertType.IMAGE, None)
|
||||
depth = parse_param(kw, "DEPTH", EnumConvertType.IMAGE, None)
|
||||
divisions = parse_param(kw, "TILE", EnumConvertType.INT, 1, 1, 8)
|
||||
noise = parse_param(kw, "NOISE", EnumConvertType.FLOAT, 1, 0)
|
||||
gamma = parse_param(kw, Lexicon.GAMMA, EnumConvertType.FLOAT, 1, 0)
|
||||
shift = parse_param(kw, "SHIFT", EnumConvertType.FLOAT, 0, 1, -1)
|
||||
invert = parse_param(kw, Lexicon.INVERT, EnumConvertType.BOOLEAN, False)
|
||||
params = list(zip_longest_fill(pA, depth, divisions, noise, gamma, shift, invert))
|
||||
images = []
|
||||
pbar = ProgressBar(len(params))
|
||||
for idx, (pA, depth, divisions, noise, gamma, shift, invert) in enumerate(params):
|
||||
pA = channel_solid(chan=EnumImageType.BGRA) if pA is None else tensor_to_cv(pA)
|
||||
h, w = pA.shape[:2]
|
||||
depth = channel_solid(w, h, chan=EnumImageType.BGRA) if depth is None else tensor_to_cv(depth)
|
||||
if invert:
|
||||
depth = image_invert(depth, 1.0)
|
||||
pA = image_stereogram(pA, depth, divisions, noise, gamma, shift)
|
||||
images.append(cv_to_tensor_full(pA))
|
||||
pbar.update_absolute(idx)
|
||||
return image_stack(images)
|
||||
|
||||
class StereoscopicNode(CozyBaseNode):
|
||||
NAME = "STEREOSCOPIC (JOV) 🕶️"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
RETURN_NAMES = (Lexicon.IMAGE, )
|
||||
DESCRIPTION = """
|
||||
Simulates depth perception in images by generating stereoscopic views. It accepts an optional input image for color matte. Adjust baseline and focal length for customized depth effects.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> InputType:
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL: (COZY_TYPE_IMAGE, {
|
||||
"tooltip":"Optional Image to Matte with Selected Color"}),
|
||||
Lexicon.INT: ("FLOAT", {
|
||||
"default": 0.1, "min": 0, "max": 1, "step": 0.01,
|
||||
"tooltip":"Baseline"}),
|
||||
Lexicon.FOCAL: ("FLOAT", {
|
||||
"default": 500, "min": 0, "step": 0.01}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
|
||||
def run(self, **kw) -> tuple[TensorType]:
|
||||
pA = parse_param(kw, Lexicon.PIXEL, EnumConvertType.IMAGE, None)
|
||||
baseline = parse_param(kw, Lexicon.INT, EnumConvertType.FLOAT, 0, 0.1, 1)
|
||||
focal_length = parse_param(kw, "VAL", EnumConvertType.FLOAT, 500, 0)
|
||||
images = []
|
||||
params = list(zip_longest_fill(pA, baseline, focal_length))
|
||||
pbar = ProgressBar(len(params))
|
||||
for idx, (pA, baseline, focal_length) in enumerate(params):
|
||||
pA = tensor_to_cv(pA) if pA is not None else channel_solid(chan=EnumImageType.GRAYSCALE)
|
||||
# Convert depth image to disparity map
|
||||
disparity_map = np.divide(1.0, pA.astype(np.float32), where=pA!=0)
|
||||
# Compute disparity values based on baseline and focal length
|
||||
disparity_map *= baseline * focal_length
|
||||
images.append(cv_to_tensor(pA))
|
||||
pbar.update_absolute(idx)
|
||||
return torch.stack(images)
|
||||
|
||||
class TextNode(CozyImageNode):
|
||||
NAME = "TEXT GEN (JOV) 📝"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
@@ -306,18 +213,19 @@ Generates images containing text based on parameters such as font, size, alignme
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.STRING: ("STRING", {
|
||||
"STRING": ("STRING", {
|
||||
"default": "jovimetrix", "multiline": True,
|
||||
"dynamicPrompts": False,
|
||||
"tooltip": "Your Message"}),
|
||||
"FONT": (cls.FONT_NAMES, {
|
||||
"default": cls.FONT_NAMES[0]}),
|
||||
"LETTER": ("BOOLEAN", {
|
||||
"default": False}),
|
||||
"default": False,
|
||||
"tooltip": "If each letter be generated and output in a batch"}),
|
||||
"AUTOSIZE": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Scale based on Width & Height"}),
|
||||
Lexicon.RGBA_A: ("VEC4", {
|
||||
"COLOR": ("VEC4", {
|
||||
"default": (255, 255, 255, 255), "rgb": True,
|
||||
"tooltip": "Color of the letters"}),
|
||||
"MATTE": ("VEC4", {
|
||||
@@ -332,35 +240,39 @@ Generates images containing text based on parameters such as font, size, alignme
|
||||
"default": EnumAlignment.CENTER.name,
|
||||
"tooltip": "Top, Center or Bottom alignment"}),
|
||||
"JUSTIFY": (EnumJustify._member_names_, {
|
||||
"default": EnumJustify.CENTER.name}),
|
||||
"default": EnumJustify.CENTER.name,
|
||||
"tooltip": "How to align the text to the side margins of the canvas: Left, Right, or Centered"}),
|
||||
"MARGIN": ("INT", {
|
||||
"default": 0, "min": -1024, "max": 1024}),
|
||||
"default": 0, "min": -1024, "max": 1024,
|
||||
"tooltip": "Whitespace padding around canvas"}),
|
||||
"SPACING": ("INT", {
|
||||
"default": 0, "min": -1024, "max": 1024}),
|
||||
Lexicon.WH: ("VEC2", {
|
||||
"WH": ("VEC2", {
|
||||
"default": (256, 256), "mij":IMAGE_SIZE_MIN, "int": True,
|
||||
"label": [Lexicon.W, Lexicon.H]}),
|
||||
Lexicon.XY: ("VEC2", {
|
||||
"label": ["W", "H"],
|
||||
"tooltip": "Width and Height"}),
|
||||
"XY": ("VEC2", {
|
||||
"default": (0, 0,), "mij": -1, "maj": 1,
|
||||
"label": [Lexicon.X, Lexicon.Y],
|
||||
"label": ["X", "Y"],
|
||||
"tooltip":"Offset the position"}),
|
||||
Lexicon.ANGLE: ("FLOAT", {
|
||||
"default": 0, "step": 0.01}),
|
||||
"ANGLE": ("FLOAT", {
|
||||
"default": 0, "step": 0.01,
|
||||
"tooltip": "Rotation Angle"}),
|
||||
"EDGE": (EnumEdge._member_names_, {
|
||||
"default": EnumEdge.CLIP.name}),
|
||||
Lexicon.INVERT: ("BOOLEAN", {
|
||||
"INVERT": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Invert the mask input"})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
full_text = parse_param(kw, Lexicon.STRING, EnumConvertType.STRING, "jovimetrix")
|
||||
full_text = parse_param(kw, "STRING", EnumConvertType.STRING, "jovimetrix")
|
||||
font_idx = parse_param(kw, "FONT", EnumConvertType.STRING, self.FONT_NAMES[0])
|
||||
autosize = parse_param(kw, "AUTOSIZE", EnumConvertType.BOOLEAN, False)
|
||||
letter = parse_param(kw, "LETTER", EnumConvertType.BOOLEAN, False)
|
||||
color = parse_param(kw, Lexicon.RGBA_A, EnumConvertType.VEC4INT, [(255,255,255,255)], 0, 255)
|
||||
color = parse_param(kw, "COLOR", EnumConvertType.VEC4INT, [(255,255,255,255)], 0, 255)
|
||||
matte = parse_param(kw, "MATTE", EnumConvertType.VEC4INT, [(0,0,0,255)], 0, 255)
|
||||
columns = parse_param(kw, "COLS", EnumConvertType.INT, 0)
|
||||
font_size = parse_param(kw, "SIZE", EnumConvertType.INT, 1)
|
||||
@@ -368,11 +280,11 @@ Generates images containing text based on parameters such as font, size, alignme
|
||||
justify = parse_param(kw, "JUSTIFY", EnumJustify, EnumJustify.CENTER.name)
|
||||
margin = parse_param(kw, "MARGIN", EnumConvertType.INT, 0)
|
||||
line_spacing = parse_param(kw, "SPACING", EnumConvertType.INT, 0)
|
||||
wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)
|
||||
pos = parse_param(kw, Lexicon.XY, EnumConvertType.VEC2, [(0, 0)], -1, 1)
|
||||
angle = parse_param(kw, Lexicon.ANGLE, EnumConvertType.INT, 0)
|
||||
wihi = parse_param(kw, "WH", EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)
|
||||
pos = parse_param(kw, "XY", EnumConvertType.VEC2, [(0, 0)], -1, 1)
|
||||
angle = parse_param(kw, "ANGLE", EnumConvertType.INT, 0)
|
||||
edge = parse_param(kw, "EDGE", EnumEdge, EnumEdge.CLIP.name)
|
||||
invert = parse_param(kw, Lexicon.INVERT, EnumConvertType.BOOLEAN, False)
|
||||
invert = parse_param(kw, "INVERT", EnumConvertType.BOOLEAN, False)
|
||||
images = []
|
||||
params = list(zip_longest_fill(full_text, font_idx, autosize, letter, color,
|
||||
matte, columns, font_size, align, justify, margin,
|
||||
@@ -412,3 +324,101 @@ Generates images containing text based on parameters such as font, size, alignme
|
||||
images.append(cv_to_tensor_full(img, matte))
|
||||
pbar.update_absolute(idx)
|
||||
return image_stack(images)
|
||||
|
||||
'''
|
||||
class StereogramNode(CozyImageNode):
|
||||
NAME = "STEREOGRAM (JOV) 📻"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
DESCRIPTION = """
|
||||
Generates false perception 3D images from 2D input. Set tile divisions, noise, gamma, and shift parameters to control the stereogram's appearance.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> InputType:
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
"IMAGE": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
}),
|
||||
"DEPTH": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Grayscale image representing a depth map"
|
||||
}),
|
||||
"TILE": ("INT", {
|
||||
"default": 8, "min": 1}),
|
||||
"NOISE": ("FLOAT", {
|
||||
"default": 0.33, "min": 0, "max": 1, "step": 0.01}),
|
||||
"GAMMA": ("FLOAT", {
|
||||
"default": 0.33, "min": 0, "max": 1, "step": 0.01}),
|
||||
"SHIFT": ("FLOAT", {
|
||||
"default": 1., "min": -1, "max": 1, "step": 0.01}),
|
||||
"INVERT": ("BOOLEAN", {
|
||||
"default": False}),
|
||||
}
|
||||
})
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
pA = parse_param(kw, "IMAGE", EnumConvertType.IMAGE, None)
|
||||
depth = parse_param(kw, "DEPTH", EnumConvertType.IMAGE, None)
|
||||
divisions = parse_param(kw, "TILE", EnumConvertType.INT, 1, 1, 8)
|
||||
noise = parse_param(kw, "NOISE", EnumConvertType.FLOAT, 1, 0)
|
||||
gamma = parse_param(kw, "GAMMA", EnumConvertType.FLOAT, 1, 0)
|
||||
shift = parse_param(kw, "SHIFT", EnumConvertType.FLOAT, 0, 1, -1)
|
||||
invert = parse_param(kw, "INVERT", EnumConvertType.BOOLEAN, False)
|
||||
params = list(zip_longest_fill(pA, depth, divisions, noise, gamma, shift, invert))
|
||||
images = []
|
||||
pbar = ProgressBar(len(params))
|
||||
for idx, (pA, depth, divisions, noise, gamma, shift, invert) in enumerate(params):
|
||||
pA = channel_solid(chan=EnumImageType.BGRA) if pA is None else tensor_to_cv(pA)
|
||||
h, w = pA.shape[:2]
|
||||
depth = channel_solid(w, h, chan=EnumImageType.BGRA) if depth is None else tensor_to_cv(depth)
|
||||
if invert:
|
||||
depth = image_invert(depth, 1.0)
|
||||
pA = image_stereogram(pA, depth, divisions, noise, gamma, shift)
|
||||
images.append(cv_to_tensor_full(pA))
|
||||
pbar.update_absolute(idx)
|
||||
return image_stack(images)
|
||||
|
||||
class StereoscopicNode(CozyBaseNode):
|
||||
NAME = "STEREOSCOPIC (JOV) 🕶️"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
RETURN_NAMES = ("IMAGE", )
|
||||
DESCRIPTION = """
|
||||
Simulates depth perception in images by generating stereoscopic views. It accepts an optional input image for color matte. Adjust baseline and focal length for customized depth effects.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> InputType:
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
"IMAGE": (COZY_TYPE_IMAGE, {
|
||||
"tooltip":"Optional Image to Matte with Selected Color"}),
|
||||
"INT": ("FLOAT", {
|
||||
"default": 0.1, "min": 0, "max": 1, "step": 0.01,
|
||||
"tooltip":"Baseline"}),
|
||||
"FOCAL": ("FLOAT", {
|
||||
"default": 500, "min": 0, "step": 0.01}),
|
||||
}
|
||||
})
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[TensorType]:
|
||||
pA = parse_param(kw, "IMAGE", EnumConvertType.IMAGE, None)
|
||||
baseline = parse_param(kw, "INT", EnumConvertType.FLOAT, 0, 0.1, 1)
|
||||
focal_length = parse_param(kw, "VAL", EnumConvertType.FLOAT, 500, 0)
|
||||
images = []
|
||||
params = list(zip_longest_fill(pA, baseline, focal_length))
|
||||
pbar = ProgressBar(len(params))
|
||||
for idx, (pA, baseline, focal_length) in enumerate(params):
|
||||
pA = tensor_to_cv(pA) if pA is not None else channel_solid(chan=EnumImageType.GRAYSCALE)
|
||||
# Convert depth image to disparity map
|
||||
disparity_map = np.divide(1.0, pA.astype(np.float32), where=pA!=0)
|
||||
# Compute disparity values based on baseline and focal length
|
||||
disparity_map *= baseline * focal_length
|
||||
images.append(cv_to_tensor(pA))
|
||||
pbar.update_absolute(idx)
|
||||
return torch.stack(images)
|
||||
|
||||
'''
|
||||
+74
-66
@@ -26,9 +26,6 @@ from cozy_comfyui.image.convert import \
|
||||
from cozy_comfyui.image.misc import \
|
||||
image_stack
|
||||
|
||||
from .. import \
|
||||
Lexicon
|
||||
|
||||
from ..sup.image.adjust import \
|
||||
EnumEdge, EnumMirrorMode, EnumScaleMode, EnumInterpolation, \
|
||||
image_edge_wrap, image_mirror, image_scalefit, image_transform, \
|
||||
@@ -75,35 +72,37 @@ Extract a portion of an input image or resize it. It supports various cropping m
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL: (COZY_TYPE_IMAGE, {}),
|
||||
Lexicon.FUNC: (EnumCropMode._member_names_, {
|
||||
"IMAGE": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
}),
|
||||
"FUNCTION": (EnumCropMode._member_names_, {
|
||||
"default": EnumCropMode.CENTER.name}),
|
||||
Lexicon.XY: ("VEC2", {
|
||||
"XY": ("VEC2", {
|
||||
"default": (0, 0), "mij": 0.5, "maj": 0.5,
|
||||
"label": [Lexicon.X, Lexicon.Y]}),
|
||||
Lexicon.WH: ("VEC2", {
|
||||
"label": ["X", "Y"]}),
|
||||
"WH": ("VEC2", {
|
||||
"default": (512, 512), "mij": IMAGE_SIZE_MIN, "int": True,
|
||||
"label": [Lexicon.W, Lexicon.H]}),
|
||||
"label": ["W", "H"]}),
|
||||
"TLTR": ("VEC4", {
|
||||
"default": (0, 0, 0, 1), "mij": 0, "maj": 1,
|
||||
"label": [Lexicon.TOP, Lexicon.LEFT, Lexicon.TOP, Lexicon.RIGHT],
|
||||
"label": ["TOP", "LEFT", "TOP", "RIGHT"],
|
||||
"tooltip": "Top Left - Top Right"}),
|
||||
"BLBR": ("VEC4", {
|
||||
"default": (1, 0, 1, 1), "mij": 0, "maj": 1,
|
||||
"label": [Lexicon.BOTTOM, Lexicon.LEFT, Lexicon.BOTTOM, Lexicon.RIGHT],
|
||||
"label": ["BOTTOM", "LEFT", "BOTTOM", "RIGHT"],
|
||||
"tooltip": "Bottom Left - Bottom Right"}),
|
||||
"MATTE": ("VEC4", {
|
||||
"default": (0, 0, 0, 255), "rgb": True})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
pA = parse_param(kw, Lexicon.PIXEL, EnumConvertType.IMAGE, None)
|
||||
func = parse_param(kw, Lexicon.FUNC, EnumCropMode, EnumCropMode.CENTER.name)
|
||||
pA = parse_param(kw, "IMAGE", EnumConvertType.IMAGE, None)
|
||||
func = parse_param(kw, "FUNCTION", EnumCropMode, EnumCropMode.CENTER.name)
|
||||
# if less than 1 then use as scalar, over 1 = int(size)
|
||||
xy = parse_param(kw, Lexicon.XY, EnumConvertType.VEC2, [(0, 0,)], 0, 1)
|
||||
wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)
|
||||
xy = parse_param(kw, "XY", EnumConvertType.VEC2, [(0, 0,)], 0, 1)
|
||||
wihi = parse_param(kw, "WH", EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)
|
||||
tltr = parse_param(kw, "TLTR", EnumConvertType.VEC4, [(0, 0, 0, 1,)], 0, 1)
|
||||
blbr = parse_param(kw, "BLBR", EnumConvertType.VEC4, [(1, 0, 1, 1,)], 0, 1)
|
||||
matte = parse_param(kw, "MATTE", EnumConvertType.VEC4INT, [(0, 0, 0, 255)], 0, 255)
|
||||
@@ -138,7 +137,7 @@ Extract a portion of an input image or resize it. It supports various cropping m
|
||||
pbar.update_absolute(idx)
|
||||
return image_stack(images)
|
||||
|
||||
class Flatten(CozyImageNode):
|
||||
class FlattenNode(CozyImageNode):
|
||||
NAME = "FLATTEN (JOV) ⬇️"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
SORT = 500
|
||||
@@ -152,20 +151,22 @@ Combine multiple input images into a single image by summing their pixel values.
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
"MODE": (EnumScaleMode._member_names_, {
|
||||
"default": EnumScaleMode.MATTE.name}),
|
||||
Lexicon.WH: ("VEC2", {
|
||||
"default": EnumScaleMode.MATTE.name,
|
||||
"tooltip": "If the image should be resized to fit within given dimensions or keep the original size"}),
|
||||
"WH": ("VEC2", {
|
||||
"default": (512, 512), "mij":IMAGE_SIZE_MIN, "int": True,
|
||||
"label": [Lexicon.W, Lexicon.H]}),
|
||||
Lexicon.SAMPLE: (EnumInterpolation._member_names_, {
|
||||
"default": EnumInterpolation.LANCZOS4.name}),
|
||||
"label": ["W", "H"]}),
|
||||
"SAMPLE": (EnumInterpolation._member_names_, {
|
||||
"default": EnumInterpolation.LANCZOS4.name,
|
||||
"tooltip": "Sampling method for resizing images"}),
|
||||
"MATTE": ("VEC4", {
|
||||
"default": (0, 0, 0, 255), "rgb": True})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
imgs = parse_dynamic(kw, Lexicon.PIXEL, EnumConvertType.IMAGE, None)
|
||||
imgs = parse_dynamic(kw, "IMAGE", EnumConvertType.IMAGE, None)
|
||||
if imgs is None:
|
||||
logger.warning("no images to flatten")
|
||||
return ()
|
||||
@@ -173,8 +174,8 @@ Combine multiple input images into a single image by summing their pixel values.
|
||||
# be less dumb when merging
|
||||
pA = [tensor_to_cv(i) for i in imgs]
|
||||
mode = parse_param(kw, "MODE", EnumScaleMode, EnumScaleMode.MATTE.name)
|
||||
wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)
|
||||
sample = parse_param(kw, Lexicon.SAMPLE, EnumInterpolation, EnumInterpolation.LANCZOS4.name)
|
||||
wihi = parse_param(kw, "WH", EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)
|
||||
sample = parse_param(kw, "SAMPLE", EnumInterpolation, EnumInterpolation.LANCZOS4.name)
|
||||
matte = parse_param(kw, "MATTE", EnumConvertType.VEC4INT, [(0, 0, 0, 255)], 0, 255)
|
||||
|
||||
images = []
|
||||
@@ -206,34 +207,36 @@ The axis parameter allows for horizontal, vertical, or grid stacking of images,
|
||||
"AXIS": (EnumOrientation._member_names_, {
|
||||
"default": EnumOrientation.GRID.name,
|
||||
"tooltip":"Choose the direction in which to stack the images. Options include horizontal, vertical, or a grid layout"}),
|
||||
Lexicon.STEP: ("INT", {
|
||||
"STEP": ("INT", {
|
||||
"default": 1, "min": 0,
|
||||
"tooltip":"How many images are placed before a new row starts (stride)."}),
|
||||
"MODE": (EnumScaleMode._member_names_, {
|
||||
"default": EnumScaleMode.MATTE.name}),
|
||||
Lexicon.WH: ("VEC2", {
|
||||
"default": EnumScaleMode.MATTE.name,
|
||||
"tooltip": "If the image should be resized to fit within given dimensions or keep the original size"}),
|
||||
"WH": ("VEC2", {
|
||||
"default": (512, 512), "mij":IMAGE_SIZE_MIN, "int": True,
|
||||
"label": [Lexicon.W, Lexicon.H]}),
|
||||
Lexicon.SAMPLE: (EnumInterpolation._member_names_, {
|
||||
"default": EnumInterpolation.LANCZOS4.name}),
|
||||
"label": ["W", "H"]}),
|
||||
"SAMPLE": (EnumInterpolation._member_names_, {
|
||||
"default": EnumInterpolation.LANCZOS4.name,
|
||||
"tooltip": "Sampling method for resizing images"}),
|
||||
"MATTE": ("VEC4", {
|
||||
"default": (0, 0, 0, 255), "rgb": True})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
images = parse_dynamic(kw, Lexicon.IMAGE, EnumConvertType.IMAGE, None)
|
||||
images = parse_dynamic(kw, "IMAGE", EnumConvertType.IMAGE, None)
|
||||
if len(images) == 0:
|
||||
logger.warning("no images to stack")
|
||||
return
|
||||
|
||||
images = [tensor_to_cv(i) for i in images]
|
||||
axis = parse_param(kw, "AXIS", EnumOrientation, EnumOrientation.GRID.name)[0]
|
||||
stride = parse_param(kw, Lexicon.STEP, EnumConvertType.INT, 1)[0]
|
||||
stride = parse_param(kw, "STEP", EnumConvertType.INT, 1)[0]
|
||||
mode = parse_param(kw, "MODE", EnumScaleMode, EnumScaleMode.MATTE.name)[0]
|
||||
wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)[0]
|
||||
sample = parse_param(kw, Lexicon.SAMPLE, EnumInterpolation, EnumInterpolation.LANCZOS4.name)[0]
|
||||
wihi = parse_param(kw, "WH", EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)[0]
|
||||
sample = parse_param(kw, "SAMPLE", EnumInterpolation, EnumInterpolation.LANCZOS4.name)[0]
|
||||
matte = parse_param(kw, "MATTE", EnumConvertType.VEC4INT, [(0, 0, 0, 255)], 0, 255)[0]
|
||||
img = image_stacker(images, axis, stride) #, matte)
|
||||
if mode != EnumScaleMode.MATTE:
|
||||
@@ -255,69 +258,74 @@ Apply various geometric transformations to images, including translation, rotati
|
||||
d = super().INPUT_TYPES(prompt=True, dynprompt=True)
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL: (COZY_TYPE_IMAGE, {}),
|
||||
Lexicon.MASK: (COZY_TYPE_IMAGE, {
|
||||
"IMAGE": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
}),
|
||||
"MASK": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Override Image mask"}),
|
||||
Lexicon.XY: ("VEC2", {
|
||||
"XY": ("VEC2", {
|
||||
"default": (0., 0.,), "mij": -1., "maj": 1.,
|
||||
"label": [Lexicon.X, Lexicon.Y]}),
|
||||
Lexicon.ANGLE: ("FLOAT", {
|
||||
"default": 0, "step": 0.01}),
|
||||
Lexicon.SIZE: ("VEC2", {
|
||||
"label": ["X", "Y"]}),
|
||||
"ANGLE": ("FLOAT", {
|
||||
"default": 0, "step": 0.01,
|
||||
"tooltip": "Rotation Angle"}),
|
||||
"SIZE": ("VEC2", {
|
||||
"default": (1., 1.), "mij": 0.001,
|
||||
"label": [Lexicon.X, Lexicon.Y]}),
|
||||
"label": ["X", "Y"]}),
|
||||
"TILE": ("VEC2", {
|
||||
"default": (1., 1.), "mij": 1.,
|
||||
"label": [Lexicon.X, Lexicon.Y]}),
|
||||
"label": ["X", "Y"]}),
|
||||
"EDGE": (EnumEdge._member_names_, {
|
||||
"default": EnumEdge.CLIP.name}),
|
||||
Lexicon.MIRROR: (EnumMirrorMode._member_names_, {
|
||||
"MIRROR": (EnumMirrorMode._member_names_, {
|
||||
"default": EnumMirrorMode.NONE.name}),
|
||||
"PIVOT": ("VEC2", {
|
||||
"default": (0.5, 0.5), "step": 0.005,
|
||||
"label": [Lexicon.X, Lexicon.Y]}),
|
||||
"label": ["X", "Y"]}),
|
||||
"PROJ": (EnumProjection._member_names_, {
|
||||
"default": EnumProjection.NORMAL.name}),
|
||||
"TLTR": ("VEC4", {
|
||||
"default": (0., 0., 1., 0.), "mij": 0., "maj": 1., "step": 0.005,
|
||||
"label": [Lexicon.TOP, Lexicon.LEFT, Lexicon.TOP, Lexicon.RIGHT],
|
||||
"label": ["TOP", "LEFT", "TOP", "RIGHT"],
|
||||
"tooltip": "Top Left - Top Right"}),
|
||||
"BLBR": ("VEC4", {
|
||||
"default": (0., 1., 1., 1.), "mij": 0., "maj": 1., "step": 0.005,
|
||||
"label": [Lexicon.BOTTOM, Lexicon.LEFT, Lexicon.BOTTOM, Lexicon.RIGHT],
|
||||
"label": ["BOTTOM", "LEFT", "BOTTOM", "RIGHT"],
|
||||
"tooltip": "Bottom Left - Bottom Right"}),
|
||||
Lexicon.STRENGTH: ("FLOAT", {
|
||||
"STRENGTH": ("FLOAT", {
|
||||
"default": 1, "min": 0, "step": 0.005}),
|
||||
"MODE": (EnumScaleMode._member_names_, {
|
||||
"default": EnumScaleMode.MATTE.name}),
|
||||
Lexicon.WH: ("VEC2", {
|
||||
"default": EnumScaleMode.MATTE.name,
|
||||
"tooltip": "If the image should be resized to fit within given dimensions or keep the original size"}),
|
||||
"WH": ("VEC2", {
|
||||
"default": (512, 512), "mij":IMAGE_SIZE_MIN, "int": True,
|
||||
"label": [Lexicon.W, Lexicon.H]}),
|
||||
Lexicon.SAMPLE: (EnumInterpolation._member_names_, {
|
||||
"default": EnumInterpolation.LANCZOS4.name}),
|
||||
"label": ["W", "H"]}),
|
||||
"SAMPLE": (EnumInterpolation._member_names_, {
|
||||
"default": EnumInterpolation.LANCZOS4.name,
|
||||
"tooltip": "Sampling method for resizing images"}),
|
||||
"MATTE": ("VEC4", {
|
||||
"default": (0, 0, 0, 255), "rgb": True})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> RGBAMaskType:
|
||||
pA = parse_param(kw, Lexicon.PIXEL, EnumConvertType.IMAGE, None)
|
||||
mask = parse_param(kw, Lexicon.MASK, EnumConvertType.IMAGE, None)
|
||||
offset = parse_param(kw, Lexicon.XY, EnumConvertType.VEC2, [(0., 0.)], -2.5, 2.5)
|
||||
angle = parse_param(kw, Lexicon.ANGLE, EnumConvertType.FLOAT, 0)
|
||||
size = parse_param(kw, Lexicon.SIZE, EnumConvertType.VEC2, [(1., 1.)], 0.001)
|
||||
pA = parse_param(kw, "IMAGE", EnumConvertType.IMAGE, None)
|
||||
mask = parse_param(kw, "MASK", EnumConvertType.IMAGE, None)
|
||||
offset = parse_param(kw, "XY", EnumConvertType.VEC2, [(0., 0.)], -2.5, 2.5)
|
||||
angle = parse_param(kw, "ANGLE", EnumConvertType.FLOAT, 0)
|
||||
size = parse_param(kw, "SIZE", EnumConvertType.VEC2, [(1., 1.)], 0.001)
|
||||
edge = parse_param(kw, "EDGE", EnumEdge, EnumEdge.CLIP.name)
|
||||
mirror = parse_param(kw, Lexicon.MIRROR, EnumMirrorMode, EnumMirrorMode.NONE.name)
|
||||
mirror = parse_param(kw, "MIRROR", EnumMirrorMode, EnumMirrorMode.NONE.name)
|
||||
mirror_pivot = parse_param(kw, "PIVOT", EnumConvertType.VEC2, [(0.5, 0.5)], 0, 1)
|
||||
tile_xy = parse_param(kw, "TILE", EnumConvertType.VEC2, [(1., 1.)], 1)
|
||||
proj = parse_param(kw, "PROJ", EnumProjection, EnumProjection.NORMAL.name)
|
||||
tltr = parse_param(kw, "TLTR", EnumConvertType.VEC4, [(0., 0., 1., 0.)], 0, 1)
|
||||
blbr = parse_param(kw, "BLBR", EnumConvertType.VEC4, [(0., 1., 1., 1.)], 0, 1)
|
||||
strength = parse_param(kw, Lexicon.STRENGTH, EnumConvertType.FLOAT, 1, 0, 1)
|
||||
strength = parse_param(kw, "STRENGTH", EnumConvertType.FLOAT, 1, 0, 1)
|
||||
mode = parse_param(kw, "MODE", EnumScaleMode, EnumScaleMode.MATTE.name)
|
||||
wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)
|
||||
sample = parse_param(kw, Lexicon.SAMPLE, EnumInterpolation, EnumInterpolation.LANCZOS4.name)
|
||||
wihi = parse_param(kw, "WH", EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)
|
||||
sample = parse_param(kw, "SAMPLE", EnumInterpolation, EnumInterpolation.LANCZOS4.name)
|
||||
matte = parse_param(kw, "MATTE", EnumConvertType.VEC4INT, [(0, 0, 0, 255)], 0, 255)
|
||||
params = list(zip_longest_fill(pA, mask, offset, angle, size, edge, tile_xy, mirror, mirror_pivot, proj, strength, tltr, blbr, mode, wihi, sample, matte))
|
||||
images = []
|
||||
|
||||
+112
-138
@@ -43,14 +43,16 @@ from cozy_comfyui.api import \
|
||||
parse_reset, comfy_api_post
|
||||
|
||||
from ... import \
|
||||
ROOT, \
|
||||
Lexicon
|
||||
ROOT
|
||||
|
||||
from ...sup.image.adjust import \
|
||||
EnumScaleMode, \
|
||||
image_scalefit
|
||||
|
||||
JOV_CATEGORY = "UTILITY"
|
||||
from ...sup.image.compose import \
|
||||
image_by_size
|
||||
|
||||
JOV_CATEGORY = "UTILITY/BATCH"
|
||||
|
||||
# ==============================================================================
|
||||
# === ENUMERATION ===
|
||||
@@ -62,7 +64,6 @@ class EnumBatchMode(Enum):
|
||||
SLICE = 15
|
||||
INDEX_LIST = 20
|
||||
RANDOM = 5
|
||||
CARTESIAN = 40
|
||||
|
||||
# ==============================================================================
|
||||
# === CLASS ===
|
||||
@@ -75,20 +76,18 @@ class ContainsAnyDict(dict):
|
||||
class ArrayNode(CozyBaseNode):
|
||||
NAME = "ARRAY (JOV) 📚"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
INPUT_IS_LIST = True
|
||||
RETURN_TYPES = (COZY_TYPE_ANY, "INT", COZY_TYPE_ANY, "INT", COZY_TYPE_ANY)
|
||||
RETURN_NAMES = (Lexicon.ANY_OUT, "LENGTH", Lexicon.LIST, "FULL SIZE", Lexicon.LIST)
|
||||
OUTPUT_IS_LIST = (False, False, False, False, True)
|
||||
RETURN_TYPES = (COZY_TYPE_ANY, "INT",)
|
||||
RETURN_NAMES = ("ARRAY", "LENGTH",)
|
||||
OUTPUT_IS_LIST = (True, True,)
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"Output list from selected operation",
|
||||
"Length of output list",
|
||||
"Full list",
|
||||
"Full input list",
|
||||
"Length of all input elements",
|
||||
"The elements as a COMFYUI list output"
|
||||
)
|
||||
SORT = 50
|
||||
DESCRIPTION = """
|
||||
Processes a batch of data based on the selected mode, such as merging, picking, slicing, random selection, or indexing. Allows for flipping the order of processed items and dividing the data into chunks.
|
||||
Processes a batch of data based on the selected mode. Merge, pick, slice, random select, or index items. Can reverse the order of items and divide the data into chunks.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@@ -99,30 +98,24 @@ Processes a batch of data based on the selected mode, such as merging, picking,
|
||||
"MODE": (EnumBatchMode._member_names_, {
|
||||
"default": EnumBatchMode.MERGE.name,
|
||||
"tooltip":"Select a single index, specific range, custom index list or randomized"}),
|
||||
"INDEX": ("INT", {
|
||||
"default": 0, "min": 0,
|
||||
"tooltip":"Selected list position"}),
|
||||
"RANGE": ("VEC3", {
|
||||
"default": (0, 0, 1), "mij": 0, "int": True,
|
||||
"tooltip":"The start, end and step for the range"}),
|
||||
Lexicon.STRING: ("STRING", {
|
||||
"INDEX": ("STRING", {
|
||||
"default": "",
|
||||
"tooltip":"Comma separated list of indicies to export"}),
|
||||
"SEED": ("INT", {
|
||||
"default": 0, "min": 0, "max": sys.maxsize,
|
||||
"tooltip":"Random seed value"}),
|
||||
"COUNT": ("INT", {
|
||||
"default": 0, "min": 0, "max": sys.maxsize,
|
||||
"tooltip":"How many items to return"}),
|
||||
Lexicon.FLIP: ("BOOLEAN", {
|
||||
"REVERSE": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip":"reverse the calculated output list"}),
|
||||
"CHUNK": ("INT", {
|
||||
"default": 0, "min": 0,
|
||||
"tooltip":"How many items to put inside each 'batched' output. 0 means put all items in a single batch."}),
|
||||
"SEED": ("INT", {
|
||||
"default": 0, "min": 0, "max": sys.maxsize,
|
||||
"tooltip":"Random seed value"}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
def batched(cls, iterable, chunk_size, expand:bool=False, fill:Any=None) -> List[Any]:
|
||||
@@ -131,63 +124,60 @@ Processes a batch of data based on the selected mode, such as merging, picking,
|
||||
return zip_longest(*[iterator] * chunk_size, fillvalue=fill)
|
||||
return [iterable[i: i + chunk_size] for i in range(0, len(iterable), chunk_size)]
|
||||
|
||||
def __init__(self, *arg, **kw) -> None:
|
||||
super().__init__(*arg, **kw)
|
||||
self.__seed = None
|
||||
|
||||
def run(self, **kw) -> tuple[int, list]:
|
||||
data_list = parse_dynamic(kw, Lexicon.UNKNOWN, EnumConvertType.ANY, None)
|
||||
if data_list is None:
|
||||
logger.warn("no data for list")
|
||||
return (None, [], 0)
|
||||
|
||||
# data_list = [item for sublist in data_list for item in sublist]
|
||||
data_list = parse_dynamic(kw, "❔", EnumConvertType.ANY, None)
|
||||
mode = parse_param(kw, "MODE", EnumBatchMode, EnumBatchMode.MERGE.name)[0]
|
||||
index = parse_param(kw, "INDEX", EnumConvertType.INT, 0, 0)[0]
|
||||
slice_range = parse_param(kw, "RANGE", EnumConvertType.VEC3INT, [(0, 0, 1)])[0]
|
||||
indices = parse_param(kw, Lexicon.STRING, EnumConvertType.STRING, "")[0]
|
||||
seed = parse_param(kw, "SEED", EnumConvertType.INT, 0)[0]
|
||||
index = parse_param(kw, "INDEX", EnumConvertType.STRING, "")[0]
|
||||
count = parse_param(kw, "COUNT", EnumConvertType.INT, 0, 0, sys.maxsize)[0]
|
||||
flip = parse_param(kw, Lexicon.FLIP, EnumConvertType.BOOLEAN, False)[0]
|
||||
batch_chunk = parse_param(kw, "CHUNK", EnumConvertType.INT, 0, 0)[0]
|
||||
reverse = parse_param(kw, "REVERSE", EnumConvertType.BOOLEAN, False)[0]
|
||||
seed = parse_param(kw, "SEED", EnumConvertType.INT, 0)[0]
|
||||
|
||||
full_list = []
|
||||
data = []
|
||||
# track latents since they need to be added back to Dict['samples']
|
||||
output_is_image = False
|
||||
output_is_latent = False
|
||||
output_type = None
|
||||
for b in data_list:
|
||||
if isinstance(b, dict) and "samples" in b:
|
||||
# latents are batched in the x.samples key
|
||||
data = b["samples"]
|
||||
full_list.extend(data)
|
||||
output_is_latent = True
|
||||
if output_type and output_type != EnumConvertType.LATENT:
|
||||
raise Exception(f"Cannot mix input types {output_type} vs {EnumConvertType.LATENT}")
|
||||
data.extend(b["samples"])
|
||||
output_type = EnumConvertType.LATENT
|
||||
|
||||
elif isinstance(b, TensorType):
|
||||
# logger.debug(b.shape)
|
||||
if output_type and output_type not in (EnumConvertType.IMAGE, EnumConvertType.MASK):
|
||||
raise Exception(f"Cannot mix input types {output_type} vs {EnumConvertType.IMAGE}")
|
||||
|
||||
if b.ndim == 4:
|
||||
full_list.extend([i for i in b])
|
||||
b = [i for i in b]
|
||||
else:
|
||||
full_list.append(b)
|
||||
output_is_image = True
|
||||
elif isinstance(b, (list, set, tuple,)):
|
||||
full_list.extend(b)
|
||||
b = [b]
|
||||
|
||||
for x in b:
|
||||
if x.ndim == 2:
|
||||
x = x.unsqueeze(-1)
|
||||
data.append(x)
|
||||
|
||||
output_type = EnumConvertType.IMAGE
|
||||
|
||||
elif b is not None:
|
||||
full_list.append(b)
|
||||
idx_type = type(b)
|
||||
if output_type and output_type != idx_type:
|
||||
raise Exception(f"Cannot mix input types {output_type} vs {idx_type}")
|
||||
data.append(b)
|
||||
|
||||
if len(full_list) == 0:
|
||||
if len(data) == 0:
|
||||
logger.warning("no data for list")
|
||||
return None, 0, None, 0
|
||||
|
||||
if flip:
|
||||
full_list.reverse()
|
||||
|
||||
data = full_list.copy()
|
||||
return [], [0], [], [0]
|
||||
|
||||
if mode == EnumBatchMode.PICK:
|
||||
index = index if index < len(data) else -1
|
||||
data = [data[index]]
|
||||
start, end, step = slice_range
|
||||
start = start if start < len(data) else -1
|
||||
data = [data[start]]
|
||||
elif mode == EnumBatchMode.SLICE:
|
||||
start, end, step = slice_range
|
||||
end = len(data) if end == 0 else end
|
||||
start = abs(start)
|
||||
end = len(data) if end == 0 else abs(end+1)
|
||||
if step == 0:
|
||||
step = 1
|
||||
elif step < 0:
|
||||
@@ -195,66 +185,73 @@ Processes a batch of data based on the selected mode, such as merging, picking,
|
||||
step = abs(step)
|
||||
data = data[start:end:step]
|
||||
elif mode == EnumBatchMode.RANDOM:
|
||||
if self.__seed is None or self.__seed != seed:
|
||||
random.seed(seed)
|
||||
self.__seed = seed
|
||||
random.seed(seed)
|
||||
if count == 0:
|
||||
count = len(data)
|
||||
else:
|
||||
count = max(1, min(len(data), count))
|
||||
data = random.sample(data, k=count)
|
||||
elif mode == EnumBatchMode.INDEX_LIST:
|
||||
junk = []
|
||||
for x in indices.split(','):
|
||||
for x in index.split(','):
|
||||
if '-' in x:
|
||||
x = x.split('-')
|
||||
a = int(x[0])
|
||||
b = int(x[1])
|
||||
if a > b:
|
||||
junk = list(range(a, b-1, -1))
|
||||
else:
|
||||
junk = list(range(a, b + 1))
|
||||
else:
|
||||
junk = [int(x)]
|
||||
data = [data[i:j+1] for i, j in zip(junk, junk)]
|
||||
for idx, v in enumerate(x):
|
||||
try:
|
||||
x[idx] = max(0, min(len(data)-1, int(v)))
|
||||
except ValueError as e:
|
||||
logger.error(e)
|
||||
x[idx] = 0
|
||||
|
||||
elif mode == EnumBatchMode.CARTESIAN:
|
||||
logger.warning("NOT IMPLEMENTED - CARTESIAN")
|
||||
if x[0] > x[1]:
|
||||
tmp = list(range(x[0], x[1]-1, -1))
|
||||
else:
|
||||
tmp = list(range(x[0], x[1]+1))
|
||||
junk.extend(tmp)
|
||||
else:
|
||||
idx = max(0, min(len(data)-1, int(x)))
|
||||
junk.append(idx)
|
||||
if len(junk) > 0:
|
||||
data = [data[i] for i in junk]
|
||||
|
||||
if len(data) == 0:
|
||||
logger.warning("no data for list")
|
||||
return None, 0, None, 0
|
||||
return [], [0], [], [0]
|
||||
|
||||
if batch_chunk > 0:
|
||||
data = self.batched(data, batch_chunk)
|
||||
|
||||
size = len(data)
|
||||
if output_is_image:
|
||||
# _, w, h = image_by_size(data)
|
||||
result = []
|
||||
for d in data:
|
||||
d = tensor_to_cv(d)
|
||||
d = image_convert(d, 4)
|
||||
#d = image_matte(d, (0,0,0,0), w, h)
|
||||
# logger.debug(d.shape)
|
||||
result.append(cv_to_tensor(d))
|
||||
|
||||
if len(result) > 1:
|
||||
data = torch.stack(result)
|
||||
else:
|
||||
data = result[0].unsqueeze(0)
|
||||
size = data.shape[0]
|
||||
# reverse before?
|
||||
if reverse:
|
||||
data.reverse()
|
||||
|
||||
# cut the list down first
|
||||
if count > 0:
|
||||
data = data[0:count]
|
||||
|
||||
if not output_is_image and len(data) == 1:
|
||||
data = data[0]
|
||||
size = len(data)
|
||||
if output_type == EnumConvertType.IMAGE:
|
||||
_, w, h = image_by_size(data)
|
||||
result = []
|
||||
for d in data:
|
||||
w2, h2, cc = d.shape
|
||||
if w != w2 or h != h2 or cc != 4:
|
||||
d = tensor_to_cv(d)
|
||||
d = image_convert(d, 4)
|
||||
d = image_matte(d, (0,0,0,0), w, h)
|
||||
d = cv_to_tensor(d)
|
||||
d = d.unsqueeze(0)
|
||||
result.append(d)
|
||||
|
||||
return data, size, full_list, len(full_list), data
|
||||
size = len(result)
|
||||
data = torch.stack(result)
|
||||
else:
|
||||
data = [data]
|
||||
|
||||
return (data, [size],)
|
||||
|
||||
class QueueBaseNode(CozyBaseNode):
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = (COZY_TYPE_ANY, COZY_TYPE_ANY, "STRING", "INT", "INT", "BOOLEAN")
|
||||
RETURN_NAMES = (Lexicon.ANY_OUT, "QUEUE", "CURRENT", "INDEX", "TOTAL", Lexicon.TRIGGER, )
|
||||
RETURN_NAMES = ("🦄", "QUEUE", "CURRENT", "INDEX", "TOTAL", "TRIGGER", )
|
||||
OUTPUT_IS_LIST = (True, True, True, True, True, True,)
|
||||
VIDEO_FORMATS = ['.wav', '.mp3', '.webm', '.mp4', '.avi', '.wmv', '.mkv', '.mov', '.mxf']
|
||||
|
||||
@classmethod
|
||||
@@ -278,13 +275,13 @@ class QueueBaseNode(CozyBaseNode):
|
||||
"VALUE": ("INT", {
|
||||
"default": 0, "min": 0,
|
||||
"tooltip": "The current index for the current queue item"}),
|
||||
Lexicon.WAIT: ("BOOLEAN", {
|
||||
"HOLD": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip":"Hold the item at the current queue index"}),
|
||||
"STOP": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip":"When the Queue is out of items, send a `HALT` to ComfyUI."}),
|
||||
Lexicon.LOOP: ("BOOLEAN", {
|
||||
"LOOP": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip":"If the queue should loop. If `False` and if there are more iterations, will send the previous image."}),
|
||||
"RESET": ("BOOLEAN", {
|
||||
@@ -292,7 +289,7 @@ class QueueBaseNode(CozyBaseNode):
|
||||
"tooltip":"Reset the queue back to index 1"}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__index = 0
|
||||
@@ -402,11 +399,11 @@ class QueueBaseNode(CozyBaseNode):
|
||||
interrupt_processing()
|
||||
return self.__previous, self.__q, self.__current, self.__index_last+1, self.__len
|
||||
|
||||
if (wait := parse_param(kw, Lexicon.WAIT, EnumConvertType.BOOLEAN, False))[0] == True:
|
||||
if (wait := parse_param(kw, "HOLD", EnumConvertType.BOOLEAN, False))[0] == True:
|
||||
self.__index = self.__index_last
|
||||
|
||||
# otherwise loop around the end
|
||||
loop = parse_param(kw, Lexicon.LOOP, EnumConvertType.BOOLEAN, False)[0]
|
||||
loop = parse_param(kw, "LOOP", EnumConvertType.BOOLEAN, False)[0]
|
||||
if loop == True:
|
||||
self.__index %= self.__len
|
||||
else:
|
||||
@@ -430,8 +427,8 @@ class QueueBaseNode(CozyBaseNode):
|
||||
if mw != 0 or mh != 0 or mc != 0:
|
||||
ret = []
|
||||
mode = parse_param(kw, "MODE", EnumScaleMode, EnumScaleMode.MATTE.name)[0]
|
||||
sample = parse_param(kw, Lexicon.SAMPLE, EnumInterpolation, EnumInterpolation.LANCZOS4.name)[0]
|
||||
wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)[0]
|
||||
sample = parse_param(kw, "SAMPLE", EnumInterpolation, EnumInterpolation.LANCZOS4.name)[0]
|
||||
wihi = parse_param(kw, "WH", EnumConvertType.VEC2INT, [(512, 512)], IMAGE_SIZE_MIN)[0]
|
||||
w2, h2 = wihi
|
||||
matte = parse_param(kw, "MATTE", EnumConvertType.VEC4INT, [(0, 0, 0, 255)], 0, 255)[0]
|
||||
matte = [matte[0], matte[1], matte[2], 0]
|
||||
@@ -488,7 +485,8 @@ Manage a queue of items, such as file paths or data. Supports various formats in
|
||||
class QueueTooNode(QueueBaseNode):
|
||||
NAME = "QUEUE TOO (JOV) 🗃"
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "MASK", "STRING", "INT", "INT", "BOOLEAN")
|
||||
RETURN_NAMES = (Lexicon.IMAGE, Lexicon.RGB, Lexicon.MASK, "CURRENT", "INDEX", "TOTAL", Lexicon.TRIGGER, )
|
||||
RETURN_NAMES = ("IMAGE", "RGB", "MASK", "CURRENT", "INDEX", "TOTAL", "TRIGGER", )
|
||||
OUTPUT_IS_LIST = (False, False, False, True, True, True, True,)
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"Full channel [RGBA] image. If there is an alpha, the image will be masked out with it when using this output",
|
||||
"Three channel [RGB] image. There will be no alpha",
|
||||
@@ -508,47 +506,23 @@ Manage a queue of specific items: media files. Supports various image and video
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
"QUEUE": ("STRING", {
|
||||
"default": "./res/img/test-a.png", "multiline": True,
|
||||
"tooltip": ""}),
|
||||
"RECURSE": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Search within sub-directories"}),
|
||||
"BATCH": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip":"Load all items, if they are loadable items, i.e. batch load images from the Queue's list"}),
|
||||
"VALUE": ("INT", {
|
||||
"default": 0, "min": 0,
|
||||
"tooltip": "Current index for the current queue item"}),
|
||||
Lexicon.WAIT: ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip":"Hold the item at the current queue index"}),
|
||||
"STOP": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip":"When the Queue is out of items, send a `HALT` to ComfyUI."}),
|
||||
Lexicon.LOOP: ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip":"If the queue should loop. If `False` and there are more iterations, will send the previous image."}),
|
||||
"RESET": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip":"Reset the queue back to index 1"}),
|
||||
"MODE": (EnumScaleMode._member_names_, {
|
||||
"default": EnumScaleMode.MATTE.name,
|
||||
"tooltip": "Decide whether the images should be resized to fit"}),
|
||||
Lexicon.WH: ("VEC2", {
|
||||
"tooltip": "If the image should be resized to fit within given dimensions or keep the original size"}),
|
||||
"WH": ("VEC2", {
|
||||
"default": (512, 512), "mij":IMAGE_SIZE_MIN, "int": True,
|
||||
"label": [Lexicon.W, Lexicon.H],
|
||||
"label": ["W", "H"],
|
||||
"tooltip": "Width and Height"}),
|
||||
Lexicon.SAMPLE: (EnumInterpolation._member_names_, {
|
||||
"SAMPLE": (EnumInterpolation._member_names_, {
|
||||
"default": EnumInterpolation.LANCZOS4.name,
|
||||
"tooltip": "Method for resizing images."}),
|
||||
"tooltip": "Sampling method for resizing images"}),
|
||||
"MATTE": ("VEC4", {
|
||||
"default": (0, 0, 0, 255), "rgb": True,
|
||||
"tooltip": "Background color for padding"}),
|
||||
},
|
||||
"hidden": d.get("hidden", {})
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, ident, **kw) -> tuple[TensorType, TensorType, TensorType, str, int, int, bool]:
|
||||
data, _, current, index, total, trigger = super().run(ident, **kw)
|
||||
|
||||
+11
-16
@@ -9,11 +9,6 @@ import numpy as np
|
||||
from PIL import Image
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from comfy.utils import ProgressBar
|
||||
|
||||
from ... import \
|
||||
Lexicon
|
||||
|
||||
from cozy_comfyui import \
|
||||
IMAGE_SIZE_MIN, \
|
||||
InputType, EnumConvertType, TensorType, \
|
||||
@@ -29,7 +24,7 @@ from cozy_comfyui.image.convert import \
|
||||
from cozy_comfyui.api import \
|
||||
parse_reset
|
||||
|
||||
JOV_CATEGORY = "UTILITY"
|
||||
JOV_CATEGORY = "UTILITY/INFO"
|
||||
|
||||
# ==============================================================================
|
||||
# === SUPPORT ===
|
||||
@@ -146,7 +141,7 @@ class GraphNode(CozyBaseNode):
|
||||
CATEGORY = JOV_CATEGORY
|
||||
OUTPUT_NODE = True
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
RETURN_NAMES = (Lexicon.IMAGE,)
|
||||
RETURN_NAMES = ("IMAGE",)
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"The graphed image"
|
||||
)
|
||||
@@ -166,13 +161,13 @@ Visualize a series of data points over time. It accepts a dynamic number of valu
|
||||
"VAL": ("INT", {
|
||||
"default": 60, "min": 0,
|
||||
"tooltip":"Number of values to graph and display"}),
|
||||
Lexicon.WH: ("VEC2", {
|
||||
"WH": ("VEC2", {
|
||||
"default": (512, 512), "mij":IMAGE_SIZE_MIN, "int": True,
|
||||
"label": [Lexicon.W, Lexicon.H],
|
||||
"label": ["W", "H"],
|
||||
"tooltip":"Width and Height of the graph output"}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls) -> float:
|
||||
@@ -185,11 +180,11 @@ Visualize a series of data points over time. It accepts a dynamic number of valu
|
||||
|
||||
def run(self, ident, **kw) -> tuple[TensorType]:
|
||||
slice = parse_param(kw, "VAL", EnumConvertType.INT, 60)[0]
|
||||
wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, [(512, 512)], 1)[0]
|
||||
wihi = parse_param(kw, "WH", EnumConvertType.VEC2INT, [(512, 512)], 1)[0]
|
||||
if parse_reset(ident) > 0 or parse_param(kw, "RESET", EnumConvertType.BOOLEAN, False)[0]:
|
||||
self.__history = []
|
||||
longest_edge = 0
|
||||
dynamic = parse_dynamic(kw, Lexicon.UNKNOWN, EnumConvertType.FLOAT, 0)
|
||||
dynamic = parse_dynamic(kw, "❔", EnumConvertType.FLOAT, 0)
|
||||
dynamic = [i[0] for i in dynamic]
|
||||
self.__ax.clear()
|
||||
for idx, val in enumerate(dynamic):
|
||||
@@ -222,7 +217,7 @@ class ImageInfoNode(CozyBaseNode):
|
||||
NAME = "IMAGE INFO (JOV) 📚"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = ("INT", "INT", "INT", "INT", "VEC2", "VEC3")
|
||||
RETURN_NAMES = (Lexicon.INT, Lexicon.W, Lexicon.H, "C", Lexicon.WH, Lexicon.WHC)
|
||||
RETURN_NAMES = ("INT", "W", "H", "C", "WH", "WHC")
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"Batch count",
|
||||
"Width",
|
||||
@@ -241,14 +236,14 @@ Exports and Displays immediate information about images.
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL_A: (COZY_TYPE_IMAGE, {
|
||||
"IMAGE_A": (COZY_TYPE_IMAGE, {
|
||||
"default": None,
|
||||
"tooltip":"The image to examine"})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[int, list]:
|
||||
image = parse_param(kw, Lexicon.PIXEL_A, EnumConvertType.IMAGE, None)
|
||||
image = parse_param(kw, "IMAGE_A", EnumConvertType.IMAGE, None)
|
||||
height, width, cc = image[0].shape
|
||||
return (len(image), width, height, cc, (width, height), (width, height, cc))
|
||||
|
||||
+32
-30
@@ -31,14 +31,11 @@ from cozy_comfyui.api import \
|
||||
TimedOutException, ComfyAPIMessage, \
|
||||
comfy_api_post
|
||||
|
||||
from ... import \
|
||||
Lexicon
|
||||
|
||||
# ==============================================================================
|
||||
# === GLOBAL ===
|
||||
# ==============================================================================
|
||||
|
||||
JOV_CATEGORY = "UTILITY"
|
||||
JOV_CATEGORY = "UTILITY/IO"
|
||||
|
||||
# min amount of time before showing the cancel dialog
|
||||
JOV_DELAY_MIN = 5
|
||||
@@ -88,7 +85,7 @@ class DelayNode(CozyBaseNode):
|
||||
NAME = "DELAY (JOV) ✋🏽"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = (COZY_TYPE_ANY,)
|
||||
RETURN_NAMES = (Lexicon.PASS_OUT,)
|
||||
RETURN_NAMES = ("OUT",)
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"Pass through data when the delay ends"
|
||||
)
|
||||
@@ -102,22 +99,25 @@ Introduce pauses in the workflow that accept an optional input to pass through a
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PASS_IN: (COZY_TYPE_ANY, {"default": None,
|
||||
"tooltip":"The data that should be held until the timer completes."}),
|
||||
Lexicon.TIMER: ("INT", {"default" : 0, "min": -1,
|
||||
"tooltip":"How long to delay if enabled. 0 means no delay."}),
|
||||
"ENABLE": ("BOOLEAN", {"default": True,
|
||||
"tooltip":"Enable or disable the screensaver."})
|
||||
"IN": (COZY_TYPE_ANY, {
|
||||
"default": None,
|
||||
"tooltip":"The data that should be held until the timer completes."}),
|
||||
"TIMER": ("INT", {
|
||||
"default" : 0, "min": -1,
|
||||
"tooltip":"How long to delay if enabled. 0 means no delay."}),
|
||||
"ENABLE": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip":"Enable or disable the screensaver."})
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kw) -> float:
|
||||
return float("NaN")
|
||||
|
||||
def run(self, ident, **kw) -> tuple[Any]:
|
||||
delay = parse_param(kw, Lexicon.TIMER, EnumConvertType.INT, -1, 0, JOV_DELAY_MAX)[0]
|
||||
delay = parse_param(kw, "TIMER", EnumConvertType.INT, -1, 0, JOV_DELAY_MAX)[0]
|
||||
if delay < 0:
|
||||
delay = JOV_DELAY_MAX
|
||||
if delay > JOV_DELAY_MIN:
|
||||
@@ -139,7 +139,7 @@ Introduce pauses in the workflow that accept an optional input to pass through a
|
||||
logger.info(f"delay [continue] ({step}): {ident}")
|
||||
pbar.update_absolute(step)
|
||||
step += 1
|
||||
return kw[Lexicon.PASS_IN],
|
||||
return kw["IN"],
|
||||
|
||||
class ExportNode(CozyBaseNode):
|
||||
NAME = "EXPORT (JOV) 📽"
|
||||
@@ -156,8 +156,10 @@ Responsible for saving images or animations to disk. It supports various output
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PIXEL: (COZY_TYPE_IMAGE, {}),
|
||||
Lexicon.PASS_OUT: ("STRING", {
|
||||
"IMAGE": (COZY_TYPE_IMAGE, {
|
||||
"tooltip": "Pixel Data (RGBA, RGB or Grayscale)"
|
||||
}),
|
||||
"OUT": ("STRING", {
|
||||
"default": get_output_directory(),
|
||||
"default_top":"<comfy output dir>",
|
||||
"tooltip":"Pass through another route node to pre-populate the outputs."}),
|
||||
@@ -180,26 +182,26 @@ Responsible for saving images or animations to disk. It supports various output
|
||||
"QUALITY_M": ("INT", {"default": 100, "min": 1, "max": 100,
|
||||
"tooltip":"Pass through another route node to pre-populate the outputs."}),
|
||||
# GIF OR GIFSKI
|
||||
Lexicon.FPS: ("INT", {"default": 24, "min": 1, "max": 60,
|
||||
"FPS": ("INT", {"default": 24, "min": 1, "max": 60,
|
||||
"tooltip":"Pass through another route node to pre-populate the outputs."}),
|
||||
# GIF OR GIFSKI
|
||||
Lexicon.LOOP: ("INT", {"default": 0, "min": 0,
|
||||
"LOOP": ("INT", {"default": 0, "min": 0,
|
||||
"tooltip":"Pass through another route node to pre-populate the outputs."}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> None:
|
||||
images = parse_param(kw, Lexicon.PIXEL, EnumConvertType.IMAGE, None)
|
||||
images = parse_param(kw, "IMAGE", EnumConvertType.IMAGE, None)
|
||||
suffix = parse_param(kw, "PREFIX", EnumConvertType.STRING, uuid4().hex[:16])[0]
|
||||
output_dir = parse_param(kw, Lexicon.PASS_OUT, EnumConvertType.STRING, "")[0]
|
||||
output_dir = parse_param(kw, "OUT", EnumConvertType.STRING, "")[0]
|
||||
format = parse_param(kw, "FORMAT", EnumConvertType.STRING, "gif")[0]
|
||||
overwrite = parse_param(kw, "OVERWRITE", EnumConvertType.BOOLEAN, False)[0]
|
||||
optimize = parse_param(kw, "OPT", EnumConvertType.BOOLEAN, False)[0]
|
||||
quality = parse_param(kw, "QUALITY", EnumConvertType.INT, 90, 0, 100)[0]
|
||||
motion = parse_param(kw, "QUALITY_M", EnumConvertType.INT, 100, 0, 100)[0]
|
||||
fps = parse_param(kw, Lexicon.FPS, EnumConvertType.INT, 24, 1, 60)[0]
|
||||
loop = parse_param(kw, Lexicon.LOOP, EnumConvertType.INT, 0, 0)[0]
|
||||
fps = parse_param(kw, "FPS", EnumConvertType.INT, 24, 1, 60)[0]
|
||||
loop = parse_param(kw, "LOOP", EnumConvertType.INT, 0, 0)[0]
|
||||
output_dir = Path(output_dir)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -257,7 +259,7 @@ class RouteNode(CozyBaseNode):
|
||||
NAME = "ROUTE (JOV) 🚌"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = ("BUS",) + (COZY_TYPE_ANY,) * 127
|
||||
RETURN_NAMES = (Lexicon.ROUTE,)
|
||||
RETURN_NAMES = ("ROUTE",)
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"Pass through for Route node"
|
||||
)
|
||||
@@ -271,16 +273,16 @@ Routes the input data from the optional input ports to the output port, preservi
|
||||
d = super().INPUT_TYPES()
|
||||
e = {
|
||||
"optional": {
|
||||
Lexicon.ROUTE: ("BUS", {"default": None, "tooltip":"Pass through another route node to pre-populate the outputs."}),
|
||||
"ROUTE": ("BUS", {"default": None, "tooltip":"Pass through another route node to pre-populate the outputs."}),
|
||||
}
|
||||
}
|
||||
d = deep_merge(d, e)
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[Any, ...]:
|
||||
inout = parse_param(kw, Lexicon.ROUTE, EnumConvertType.ANY, None)
|
||||
inout = parse_param(kw, "ROUTE", EnumConvertType.ANY, None)
|
||||
vars = kw.copy()
|
||||
vars.pop(Lexicon.ROUTE, None)
|
||||
vars.pop("ROUTE", None)
|
||||
vars.pop('ident', None)
|
||||
|
||||
parsed = []
|
||||
@@ -291,7 +293,7 @@ Routes the input data from the optional input ports to the output port, preservi
|
||||
junk = *parsed,
|
||||
return inout, parsed,
|
||||
|
||||
class SaveOutput(CozyBaseNode):
|
||||
class SaveOutputNode(CozyBaseNode):
|
||||
NAME = "SAVE OUTPUT (JOV) 💾"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
OUTPUT_NODE = True
|
||||
@@ -319,7 +321,7 @@ Save the output image along with its metadata to the specified path. Supports sa
|
||||
"tooltip":"Custom user metadat to save with the file"}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> dict[str, Any]:
|
||||
image = parse_param(kw, 'image', EnumConvertType.IMAGE, None)
|
||||
|
||||
+29
-31
@@ -13,9 +13,6 @@ from cozy_comfyui.node import \
|
||||
COZY_TYPE_ANY, COZY_TYPE_NUMERICAL, COZY_TYPE_NUMBER, \
|
||||
CozyBaseNode
|
||||
|
||||
from .. import \
|
||||
Lexicon
|
||||
|
||||
JOV_CATEGORY = "VARIABLE"
|
||||
|
||||
# ==============================================================================
|
||||
@@ -26,7 +23,7 @@ class ValueNode(CozyBaseNode):
|
||||
NAME = "VALUE (JOV) 🧬"
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = (COZY_TYPE_ANY, COZY_TYPE_ANY, COZY_TYPE_ANY, COZY_TYPE_ANY, COZY_TYPE_ANY,)
|
||||
RETURN_NAMES = (Lexicon.ANY_OUT, Lexicon.X, Lexicon.Y, Lexicon.Z, Lexicon.W)
|
||||
RETURN_NAMES = ("🦄", "X", "Y", "Z", "W")
|
||||
SORT = 5
|
||||
DESCRIPTION = """
|
||||
Supplies raw or default values for various data types, supporting vector input with components for X, Y, Z, and W. It also provides a string input option.
|
||||
@@ -41,49 +38,49 @@ Supplies raw or default values for various data types, supporting vector input w
|
||||
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.IN_A: (COZY_TYPE_ANY, {
|
||||
"A": (COZY_TYPE_ANY, {
|
||||
"default": None,
|
||||
"tooltip":"Passes a raw value directly, or supplies defaults for any value inputs without connections"}),
|
||||
Lexicon.TYPE: (typ, {
|
||||
"TYPE": (typ, {
|
||||
"default": EnumConvertType.BOOLEAN.name,
|
||||
"tooltip":"Take the input and convert it into the selected type."}),
|
||||
Lexicon.X: (COZY_TYPE_NUMERICAL, {
|
||||
"X": (COZY_TYPE_NUMERICAL, {
|
||||
"default": 0, "mij": -sys.maxsize, "maj": sys.maxsize,
|
||||
"forceInput": True}),
|
||||
Lexicon.Y: (COZY_TYPE_NUMERICAL, {
|
||||
"Y": (COZY_TYPE_NUMERICAL, {
|
||||
"default": 0, "mij": -sys.maxsize, "maj": sys.maxsize,
|
||||
"forceInput": True}),
|
||||
Lexicon.Z: (COZY_TYPE_NUMERICAL, {
|
||||
"Z": (COZY_TYPE_NUMERICAL, {
|
||||
"default": 0, "mij": -sys.maxsize, "maj": sys.maxsize,
|
||||
"forceInput": True}),
|
||||
Lexicon.W: (COZY_TYPE_NUMERICAL, {
|
||||
"W": (COZY_TYPE_NUMERICAL, {
|
||||
"default": 0, "mij": -sys.maxsize, "maj": sys.maxsize,
|
||||
"forceInput": True}),
|
||||
Lexicon.IN_A+Lexicon.IN_A: ("VEC4", {
|
||||
"AA": ("VEC4", {
|
||||
"default": (0, 0, 0, 0), #"mij": -sys.maxsize, "maj": sys.maxsize,
|
||||
"label": [Lexicon.X, Lexicon.Y],
|
||||
"label": ["X", "Y"],
|
||||
"tooltip":"default value vector for A"}),
|
||||
Lexicon.IN_B+Lexicon.IN_B: ("VEC4", {
|
||||
"BB": ("VEC4", {
|
||||
"default": (1,1,1,1), #"mij": -sys.maxsize, "maj": sys.maxsize,
|
||||
"label": [Lexicon.X, Lexicon.Y, Lexicon.Z, Lexicon.W],
|
||||
"label": ["X", "Y", "Z", "W"],
|
||||
"tooltip":"default value vector for B"}),
|
||||
"SEED": ("INT", {
|
||||
"default": 0, "min": 0, "max": sys.maxsize}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[bool]:
|
||||
raw = parse_param(kw, Lexicon.IN_A, EnumConvertType.ANY, [0])
|
||||
r_x = parse_param(kw, Lexicon.X, EnumConvertType.FLOAT, None, -sys.maxsize, sys.maxsize)
|
||||
r_y = parse_param(kw, Lexicon.Y, EnumConvertType.FLOAT, None, -sys.maxsize, sys.maxsize)
|
||||
r_z = parse_param(kw, Lexicon.Z, EnumConvertType.FLOAT, None, -sys.maxsize, sys.maxsize)
|
||||
r_w = parse_param(kw, Lexicon.W, EnumConvertType.FLOAT, None, -sys.maxsize, sys.maxsize)
|
||||
typ = parse_param(kw, Lexicon.TYPE, EnumConvertType, EnumConvertType.BOOLEAN.name)
|
||||
xyzw = parse_param(kw, Lexicon.IN_A+Lexicon.IN_A, EnumConvertType.VEC4, [(0, 0, 0, 0)])
|
||||
raw = parse_param(kw, "A", EnumConvertType.ANY, [0])
|
||||
r_x = parse_param(kw, "X", EnumConvertType.FLOAT, None, -sys.maxsize, sys.maxsize)
|
||||
r_y = parse_param(kw, "Y", EnumConvertType.FLOAT, None, -sys.maxsize, sys.maxsize)
|
||||
r_z = parse_param(kw, "Z", EnumConvertType.FLOAT, None, -sys.maxsize, sys.maxsize)
|
||||
r_w = parse_param(kw, "W", EnumConvertType.FLOAT, None, -sys.maxsize, sys.maxsize)
|
||||
typ = parse_param(kw, "TYPE", EnumConvertType, EnumConvertType.BOOLEAN.name)
|
||||
xyzw = parse_param(kw, "AA", EnumConvertType.VEC4, [(0, 0, 0, 0)])
|
||||
seed = parse_param(kw, "SEED", EnumConvertType.INT, 0, 0)
|
||||
yyzw = parse_param(kw, Lexicon.IN_B+Lexicon.IN_B, EnumConvertType.VEC4, [(1, 1, 1, 1)])
|
||||
x_str = parse_param(kw, Lexicon.STRING, EnumConvertType.STRING, "")
|
||||
yyzw = parse_param(kw, "BB", EnumConvertType.VEC4, [(1, 1, 1, 1)])
|
||||
x_str = parse_param(kw, "STRING", EnumConvertType.STRING, "")
|
||||
params = list(zip_longest_fill(raw, r_x, r_y, r_z, r_w, typ, xyzw, seed, yyzw, x_str))
|
||||
results = []
|
||||
pbar = ProgressBar(len(params))
|
||||
@@ -140,6 +137,7 @@ class Vector2Node(CozyBaseNode):
|
||||
CATEGORY = JOV_CATEGORY
|
||||
RETURN_TYPES = ("VEC2",)
|
||||
RETURN_NAMES = ("VEC2",)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
OUTPUT_TOOLTIPS = (
|
||||
"Vector2 with float values",
|
||||
)
|
||||
@@ -167,7 +165,7 @@ Outputs a VECTOR2.
|
||||
"tooltip": "Default Y channel value"}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[tuple[float, ...], tuple[int, ...]]:
|
||||
x = parse_param(kw, "X", EnumConvertType.FLOAT, None, -sys.maxsize, sys.maxsize)
|
||||
@@ -216,7 +214,7 @@ Outputs a VECTOR3.
|
||||
"tooltip": "3rd channel value"}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[tuple[float, ...], tuple[int, ...]]:
|
||||
x = parse_param(kw, "X", EnumConvertType.FLOAT, 0, -sys.maxsize, sys.maxsize)
|
||||
@@ -244,7 +242,7 @@ class Vector4Node(CozyBaseNode):
|
||||
)
|
||||
SORT = 294
|
||||
DESCRIPTION = """
|
||||
Outputs a VEC4 or VEC4INT.
|
||||
Outputs a VEC4.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@@ -266,7 +264,7 @@ Outputs a VEC4 or VEC4INT.
|
||||
"tooltip": "4th channel value"}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, **kw) -> tuple[tuple[float, ...], tuple[int, ...]]:
|
||||
x = parse_param(kw, "X", EnumConvertType.FLOAT, 0, -sys.maxsize, sys.maxsize)
|
||||
@@ -301,11 +299,11 @@ class ParameterNode(CozyBaseNode):
|
||||
d = super().INPUT_TYPES()
|
||||
d = deep_merge(d, {
|
||||
"optional": {
|
||||
Lexicon.PASS_IN: (COZY_TYPE_ANY, {"default": None}),
|
||||
"IN": (COZY_TYPE_ANY, {"default": None}),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d)
|
||||
return d
|
||||
|
||||
def run(self, ident, **kw) -> tuple[Any]:
|
||||
return kw[Lexicon.PASS_IN],
|
||||
return kw["IN"],
|
||||
'''
|
||||
@@ -1,6 +1,6 @@
|
||||
/**/
|
||||
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
export const bewm = function(ex, ey) {
|
||||
//- adapted from "Anchor Click Canvas Animation" by Nick Sheffield
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { app } from "../../../scripts/app.js"
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js';
|
||||
import { nodeAddDynamic } from '../util/util_node.js'
|
||||
import { nodeAddDynamic } from '../util.js'
|
||||
|
||||
const _prefix = '📥'
|
||||
const _id = "AKASHIC (JOV) 📓"
|
||||
|
||||
+1
-38
@@ -5,8 +5,7 @@
|
||||
*/
|
||||
|
||||
import { app } from "../../../scripts/app.js"
|
||||
import { nodeFitHeight, nodeAddDynamic } from '../util/util_node.js'
|
||||
import { widgetHide, widgetShow } from '../util/util_widget.js'
|
||||
import { nodeAddDynamic } from '../util.js'
|
||||
|
||||
const _id = "ARRAY (JOV) 📚"
|
||||
const _prefix = '❔'
|
||||
@@ -19,41 +18,5 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
nodeAddDynamic(nodeType, _prefix);
|
||||
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
const me = onNodeCreated?.apply(this);
|
||||
const widget_idx = this.widgets.find(w => w.name == 'INDEX');
|
||||
const widget_range = this.widgets.find(w => w.name == 'RANGE');
|
||||
const widget_str = this.widgets.find(w => w.name == '📝');
|
||||
const widget_seed = this.widgets.find(w => w.name == 'seed');
|
||||
const widget_mode = this.widgets.find(w => w.name == 'MODE');
|
||||
const widget_count = this.widgets.find(w => w.name == 'COUNT');
|
||||
widget_mode.callback = async () => {
|
||||
widgetHide(this, widget_idx);
|
||||
widgetHide(this, widget_range);
|
||||
widgetHide(this, widget_str);
|
||||
widgetHide(this, widget_seed);
|
||||
widgetHide(this, widget_count);
|
||||
if (widget_mode.value == "PICK") {
|
||||
widgetShow(widget_idx);
|
||||
widgetShow(widget_count);
|
||||
} else if (widget_mode.value == "SLICE") {
|
||||
widgetShow(widget_range);
|
||||
} else if (widget_mode.value == "INDEX_LIST") {
|
||||
widgetShow(widget_str);
|
||||
} else if (widget_mode.value == "RANDOM") {
|
||||
widgetShow(widget_seed);
|
||||
widgetShow(widget_count);
|
||||
} else if (widget_mode.value == "MERGE") {
|
||||
// MERGE
|
||||
} else if (widget_mode.value == "CARTESIAN") {
|
||||
console.warn("NOT IMPLEMENTED! YELL AT JOVIEX!")
|
||||
}
|
||||
nodeFitHeight(this);
|
||||
}
|
||||
setTimeout(() => { widget_mode.callback(); }, 10);
|
||||
return me;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**/
|
||||
|
||||
import { app } from "../../../scripts/app.js"
|
||||
import { widgetHookAB } from '../util/util_jov.js'
|
||||
import { widgetHookAB } from '../util.js'
|
||||
|
||||
const _id = "OP BINARY (JOV) 🌟"
|
||||
|
||||
|
||||
+3
-3
@@ -2,8 +2,8 @@
|
||||
|
||||
import { api } from "../../../scripts/api.js";
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { apiJovimetrix } from '../util/util_jov.js'
|
||||
import { bubbles } from '../util/util_fun.js'
|
||||
import { apiJovimetrix } from '../util.js'
|
||||
import { bubbles } from '../fun.js'
|
||||
|
||||
const _id = "DELAY (JOV) ✋🏽"
|
||||
const EVENT_JOVI_DELAY = "jovi-delay-user";
|
||||
@@ -65,7 +65,7 @@ app.registerExtension({
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
const me = onNodeCreated?.apply(this);
|
||||
const widget_time = this.widgets.find(w => w.name == '⏱');
|
||||
const widget_time = this.widgets.find(w => w.name == 'TIME');
|
||||
const widget_enable = this.widgets.find(w => w.name == 'ENABLE');
|
||||
this.total_timeout = 0;
|
||||
let showing = false;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**/
|
||||
|
||||
import { app } from "../../../scripts/app.js"
|
||||
import { nodeAddDynamic } from '../util/util_node.js'
|
||||
import { nodeAddDynamic } from '../util.js'
|
||||
|
||||
const _id = "FLATTEN (JOV) ⬇️"
|
||||
const _prefix = '👾'
|
||||
const _prefix = 'IMAGE'
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
|
||||
+1
-2
@@ -1,8 +1,7 @@
|
||||
/**/
|
||||
|
||||
import { app } from "../../../scripts/app.js"
|
||||
import { nodeAddDynamic } from '../util/util_node.js'
|
||||
import { apiJovimetrix } from '../util/util_jov.js'
|
||||
import { apiJovimetrix, nodeAddDynamic } from '../util.js'
|
||||
|
||||
const _id = "GRAPH (JOV) 📈"
|
||||
const _prefix = '❔'
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
/**/
|
||||
|
||||
import { app } from "../../../scripts/app.js"
|
||||
import { widgetHookControl, widgetHookAB } from '../util/util_jov.js'
|
||||
import { widgetHookControl, widgetHookAB } from '../util.js'
|
||||
|
||||
const _id = "LERP (JOV) 🔰"
|
||||
|
||||
@@ -15,9 +15,9 @@ app.registerExtension({
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const me = onNodeCreated?.apply(this);
|
||||
const alpha = this.widgets.find(w => w.name == '🛟');
|
||||
widgetHookControl(this, '❓', alpha, true);
|
||||
widgetHookAB(this, '❓', false);
|
||||
const alpha = this.widgets.find(w => w.name == 'ALPHA');
|
||||
widgetHookControl(this, 'TYPE', alpha, true);
|
||||
widgetHookAB(this, 'TYPE', false);
|
||||
return me;
|
||||
}
|
||||
return nodeType;
|
||||
|
||||
+4
-9
@@ -2,10 +2,9 @@
|
||||
|
||||
import { api } from "../../../scripts/api.js";
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { ComfyWidgets } from "../../../scripts/widgets.js"
|
||||
import { flashBackgroundColor } from '../util/util_fun.js'
|
||||
import { TypeSlotEvent, TypeSlot } from '../util/util_node.js'
|
||||
import { apiJovimetrix, widgetSizeModeHook } from '../util/util_jov.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js';
|
||||
import { apiJovimetrix, TypeSlotEvent, TypeSlot } from '../util.js'
|
||||
import { flashBackgroundColor } from '../fun.js'
|
||||
|
||||
const _id1 = "QUEUE (JOV) 🗃";
|
||||
const _id2 = "QUEUE TOO (JOV) 🗃";
|
||||
@@ -20,10 +19,6 @@ app.registerExtension({
|
||||
return;
|
||||
}
|
||||
|
||||
if (nodeData.name == _id2) {
|
||||
widgetSizeModeHook(nodeType);
|
||||
}
|
||||
|
||||
function update_report(self) {
|
||||
self.widget_report.value = `[${self.data_index+1} / ${self.data_all.length}]\n${self.data_current}`;
|
||||
app.canvas.setDirty(true);
|
||||
@@ -54,7 +49,7 @@ app.registerExtension({
|
||||
|
||||
const widget_queue = this.widgets.find(w => w.name == 'Q');
|
||||
const widget_batch = this.widgets.find(w => w.name == 'BATCH');
|
||||
const widget_hold = this.widgets.find(w => w.name == '✋🏽');
|
||||
const widget_hold = this.widgets.find(w => w.name == 'HOLD');
|
||||
const widget_reset = this.widgets.find(w => w.name == 'RESET');
|
||||
|
||||
widget_queue.inputEl.addEventListener('input', function () {
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import { app } from "../../../scripts/app.js"
|
||||
import {
|
||||
TypeSlot, TypeSlotEvent, nodeFitHeight,
|
||||
nodeVirtualLinkRoot, nodeInputsClear, nodeOutputsClear
|
||||
} from '../util/util_node.js'
|
||||
} from '../util.js'
|
||||
|
||||
const _id = "ROUTE (JOV) 🚌";
|
||||
const _prefix = '🔮';
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
/**/
|
||||
|
||||
import { app } from "../../../scripts/app.js"
|
||||
import { nodeAddDynamic} from '../util/util_node.js'
|
||||
import { nodeAddDynamic} from '../util.js'
|
||||
|
||||
const _id = "STACK (JOV) ➕"
|
||||
const _prefix = '👾'
|
||||
const _prefix = 'IMAGE'
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
import { api } from "../../../scripts/api.js";
|
||||
import { app } from "../../../scripts/app.js"
|
||||
import { apiJovimetrix } from '../util/util_jov.js'
|
||||
import { apiJovimetrix } from '../util.js'
|
||||
|
||||
const _id = "TICK (JOV) ⏱";
|
||||
const EVENT_JOVI_TICK = "jovi-tick";
|
||||
|
||||
+3
-13
@@ -1,9 +1,7 @@
|
||||
/**/
|
||||
|
||||
import { app } from "../../../scripts/app.js"
|
||||
import { widgetHookAB } from '../util/util_jov.js'
|
||||
import { nodeFitHeight } from '../util/util_node.js'
|
||||
import { widgetHide, widgetProcessAny, widget_type_name } from '../util/util_widget.js'
|
||||
import { widgetHookAB, nodeFitHeight} from '../util.js'
|
||||
|
||||
const _id = "VALUE (JOV) 🧬"
|
||||
|
||||
@@ -18,26 +16,18 @@ app.registerExtension({
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const me = onNodeCreated?.apply(this);
|
||||
|
||||
const widget_str = this.widgets.find(w => w.name == '📝');
|
||||
|
||||
this.outputs[1].type = "*";
|
||||
this.outputs[2].type = "*";
|
||||
this.outputs[3].type = "*";
|
||||
this.outputs[4].type = "*";
|
||||
|
||||
widget_str.options.menu = false;
|
||||
widget_str.origComputeSize = widget_str.computeSize;
|
||||
|
||||
const ab_data = widgetHookAB(this, '❓');
|
||||
const ab_data = widgetHookAB(this, 'TYPE');
|
||||
|
||||
const oldCallback = ab_data.callback;
|
||||
ab_data.callback = () => {
|
||||
oldCallback?.apply(this, arguments);
|
||||
widgetHide(this, widget_str);
|
||||
widget_str.inputEl.className = "jov-hidden";
|
||||
widget_str.computeSize = () => [0, -4];
|
||||
|
||||
this.outputs[0].name = widget_type_name(ab_data.value);
|
||||
this.outputs[0].name = ab_data.value;
|
||||
this.outputs[0].type = ab_data.value;
|
||||
let type = ab_data.value;
|
||||
type = "FLOAT";
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
/**/
|
||||
|
||||
/*
|
||||
parse a json into a graph
|
||||
const workflow = JSON.parse(json);
|
||||
await this.loadGraphData(workflow);
|
||||
*/
|
||||
import { app } from "../../scripts/app.js"
|
||||
import { api } from "../../scripts/api.js"
|
||||
|
||||
import { app } from "../../../scripts/app.js"
|
||||
const _REGEX = /\d/;
|
||||
|
||||
export const TypeSlot = {
|
||||
Input: 1,
|
||||
@@ -18,6 +15,193 @@ export const TypeSlotEvent = {
|
||||
Disconnect: false,
|
||||
};
|
||||
|
||||
export async function apiJovimetrix(id, cmd, data=null, route="message", ) {
|
||||
try {
|
||||
const response = await api.fetchApi(`/cozy_comfyui/${route}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: id,
|
||||
cmd: cmd,
|
||||
data: data
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error: ${response.status} - ${response.statusText}`);
|
||||
}
|
||||
return response;
|
||||
|
||||
} catch (error) {
|
||||
console.error("API call to Jovimetrix failed:", error);
|
||||
throw error; // or return { success: false, message: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
//export const widgetFind = (widgets, name) => widgets.find(w => w.name == name);
|
||||
|
||||
function widgetShowVector(widget, values={}, type) {
|
||||
if (["FLOAT"].includes(type)) {
|
||||
type = "VEC1";
|
||||
} else if (["INT"].includes(type)) {
|
||||
type = "VEC1INT";
|
||||
} else if (type == "BOOLEAN") {
|
||||
type = "toggle";
|
||||
}
|
||||
|
||||
if (type !== undefined) {
|
||||
widget.type = type;
|
||||
}
|
||||
|
||||
if (widget.value === undefined) {
|
||||
widget.value = widget.options?.default || {};
|
||||
}
|
||||
|
||||
// convert widget.value to pure dict/object
|
||||
if (Array.isArray(widget.value)) {
|
||||
let new_val = {};
|
||||
for (let i = 0; i < widget.value.length; i++) {
|
||||
new_val[i] = widget.value[i];
|
||||
}
|
||||
widget.value = new_val;
|
||||
}
|
||||
|
||||
widget.options.step = 1;
|
||||
widget.options.round = 1;
|
||||
widget.options.precision = 0;
|
||||
if (widget.type != 'toggle') {
|
||||
let size = 1;
|
||||
const match = _REGEX.exec(widget.type);
|
||||
if (match) {
|
||||
size = match[0];
|
||||
}
|
||||
if (!widget.type.endsWith('INT') && widget.type != 'BOOLEAN') {
|
||||
widget.options.step = 0.01;
|
||||
widget.options.round = 0.001;
|
||||
widget.options.precision = 3;
|
||||
}
|
||||
|
||||
widget.value = {};
|
||||
for (let i = 0; i < size; i++) {
|
||||
widget.value[i] = (widget.options.precision == 0) ? Number(values[i]) : parseFloat(values[i]).toFixed(widget.options.precision);
|
||||
//widget.value[i] = !widget.type.endsWith('INT') ? Math.round(values[i]) : Number(values[i]);
|
||||
}
|
||||
} else {
|
||||
widget.value = values[0] ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
export function widgetOutputHookType(node, control_key, match_output=0) {
|
||||
const combo = node.widgets.find(w => w.name == control_key);
|
||||
const output = node.outputs[match_output];
|
||||
|
||||
if (!output || !combo) {
|
||||
throw new Error("Required widgets not found");
|
||||
}
|
||||
|
||||
const oldCallback = combo.callback;
|
||||
combo.callback = () => {
|
||||
const me = oldCallback?.apply(this, arguments);
|
||||
node.outputs[match_output].name = combo.value;
|
||||
node.outputs[match_output].type = combo.value;
|
||||
return me;
|
||||
}
|
||||
setTimeout(() => { combo.callback(); }, 10);
|
||||
}
|
||||
|
||||
/*
|
||||
* matchFloatSize forces the target to be float[n] based on its type size
|
||||
*/
|
||||
export function widgetHookAB(node, control_key, output_type_match=true) {
|
||||
|
||||
const AA = node.widgets.find(w => w.name == 'AA');
|
||||
const BB = node.widgets.find(w => w.name == 'BB');
|
||||
const combo = node.widgets.find(w => w.name == control_key);
|
||||
|
||||
if (combo === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
widgetHookControl(node, control_key, AA);
|
||||
widgetHookControl(node, control_key, BB);
|
||||
if (output_type_match) {
|
||||
widgetOutputHookType(node, control_key);
|
||||
}
|
||||
setTimeout(() => { combo.callback(); }, 5);
|
||||
|
||||
return combo;
|
||||
};
|
||||
|
||||
/*
|
||||
* matchFloatSize forces the target to be float[n] based on its type size
|
||||
*/
|
||||
export function widgetHookControl(node, control_key, target, matchFloatSize=false) {
|
||||
const initializeTrack = (widget) => {
|
||||
const track = {};
|
||||
for (let i = 0; i < 4; i++) {
|
||||
track[i] = widget.options?.default[i];
|
||||
}
|
||||
Object.assign(track, widget.value);
|
||||
return track;
|
||||
};
|
||||
|
||||
const { widgets } = node;
|
||||
const combo = widgets.find(w => w.name == control_key);
|
||||
|
||||
if (!target || !combo) {
|
||||
throw new Error("Required widgets not found");
|
||||
}
|
||||
|
||||
const data = {
|
||||
//track_xyzw: target.options?.default, //initializeTrack(target),
|
||||
track_xyzw: initializeTrack(target),
|
||||
target,
|
||||
combo
|
||||
};
|
||||
|
||||
const oldCallback = combo.callback;
|
||||
combo.callback = () => {
|
||||
const me = oldCallback?.apply(this, arguments);
|
||||
//widgetHide(node, target, "-jov");
|
||||
//if (["VEC2", "VEC2INT", "COORD2D", "VEC3", "VEC3INT", "VEC4", "VEC4INT", "BOOLEAN", "INT", "FLOAT"].includes(combo.value)) {
|
||||
if (["VEC2", "VEC3", "VEC4", "BOOLEAN", "INT", "FLOAT"].includes(combo.value)) {
|
||||
let type = combo.value;
|
||||
if (matchFloatSize) {
|
||||
type = "FLOAT";
|
||||
// if (["VEC2", "VEC2INT", "COORD2D"].includes(combo.value)) {
|
||||
if (["VEC2"].includes(combo.value)) {
|
||||
type = "VEC2";
|
||||
//} else if (["VEC3", "VEC3INT"].includes(combo.value)) {
|
||||
} else if (["VEC3"].includes(combo.value)) {
|
||||
type = "VEC3";
|
||||
//} else if (["VEC4", "VEC4INT"].includes(combo.value)) {
|
||||
} else if (["VEC4"].includes(combo.value)) {
|
||||
type = "VEC4";
|
||||
}
|
||||
}
|
||||
widgetShowVector(target, data.track_xyzw, type);
|
||||
}
|
||||
nodeFitHeight(node);
|
||||
return me;
|
||||
}
|
||||
|
||||
target.options.menu = false;
|
||||
target.callback = () => {
|
||||
if (target.type == "toggle") {
|
||||
data.track_xyzw[0] = target.value ? 1 : 0;
|
||||
} else {
|
||||
Object.keys(target.value).forEach((key) => {
|
||||
data.track_xyzw[key] = target.value[key];
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
export const nodeCleanup = (node) => {
|
||||
if (!node.widgets || !node.widgets?.[Symbol.iterator]) {
|
||||
return
|
||||
@@ -1,162 +0,0 @@
|
||||
/**/
|
||||
|
||||
import { api } from "../../../scripts/api.js"
|
||||
import { nodeFitHeight } from './util_node.js'
|
||||
import { widgetShowVector, widget_type_name, widgetHide, widgetShow } from './util_widget.js'
|
||||
|
||||
export async function apiJovimetrix(id, cmd, data=null, route="message", ) {
|
||||
try {
|
||||
const response = await api.fetchApi(`/cozy_comfyui/${route}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: id,
|
||||
cmd: cmd,
|
||||
data: data
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error: ${response.status} - ${response.statusText}`);
|
||||
}
|
||||
return response;
|
||||
|
||||
} catch (error) {
|
||||
console.error("API call to Jovimetrix failed:", error);
|
||||
throw error; // or return { success: false, message: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
export function widgetSizeModeHook(nodeType, always_wh=false) {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const me = onNodeCreated?.apply(this);
|
||||
const wh = this.widgets.find(w => w.name == '🇼🇭');
|
||||
const samp = this.widgets.find(w => w.name == '🎞️');
|
||||
const mode = this.widgets.find(w => w.name == 'MODE');
|
||||
mode.callback = () => {
|
||||
widgetHide(this, wh);
|
||||
widgetHide(this, samp);
|
||||
|
||||
if (always_wh || !['MATTE'].includes(mode.value)) {
|
||||
widgetShow(wh);
|
||||
}
|
||||
if (!['CROP', 'MATTE'].includes(mode.value)) {
|
||||
widgetShow(samp);
|
||||
}
|
||||
nodeFitHeight(this);
|
||||
}
|
||||
setTimeout(() => { mode.callback(); }, 20);
|
||||
return me;
|
||||
}
|
||||
}
|
||||
|
||||
export function widgetOutputHookType(node, control_key, match_output=0) {
|
||||
const combo = node.widgets.find(w => w.name == control_key);
|
||||
const output = node.outputs[match_output];
|
||||
|
||||
if (!output || !combo) {
|
||||
throw new Error("Required widgets not found");
|
||||
}
|
||||
|
||||
const oldCallback = combo.callback;
|
||||
combo.callback = () => {
|
||||
const me = oldCallback?.apply(this, arguments);
|
||||
node.outputs[match_output].name = widget_type_name(combo.value);
|
||||
node.outputs[match_output].type = combo.value;
|
||||
return me;
|
||||
}
|
||||
setTimeout(() => { combo.callback(); }, 10);
|
||||
}
|
||||
|
||||
/*
|
||||
* matchFloatSize forces the target to be float[n] based on its type size
|
||||
*/
|
||||
export function widgetHookAB(node, control_key, output_type_match=true) {
|
||||
|
||||
const AA = node.widgets.find(w => w.name == '🅰️🅰️');
|
||||
const BB = node.widgets.find(w => w.name == '🅱️🅱️');
|
||||
const combo = node.widgets.find(w => w.name == control_key);
|
||||
|
||||
if (combo === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
widgetHookControl(node, control_key, AA);
|
||||
widgetHookControl(node, control_key, BB);
|
||||
if (output_type_match) {
|
||||
widgetOutputHookType(node, control_key);
|
||||
}
|
||||
setTimeout(() => { combo.callback(); }, 5);
|
||||
|
||||
return combo;
|
||||
};
|
||||
|
||||
/*
|
||||
* matchFloatSize forces the target to be float[n] based on its type size
|
||||
*/
|
||||
export function widgetHookControl(node, control_key, target, matchFloatSize=false) {
|
||||
const initializeTrack = (widget) => {
|
||||
const track = {};
|
||||
for (let i = 0; i < 4; i++) {
|
||||
track[i] = widget.options?.default[i];
|
||||
}
|
||||
Object.assign(track, widget.value);
|
||||
return track;
|
||||
};
|
||||
|
||||
const { widgets } = node;
|
||||
const combo = widgets.find(w => w.name == control_key);
|
||||
|
||||
if (!target || !combo) {
|
||||
throw new Error("Required widgets not found");
|
||||
}
|
||||
|
||||
const data = {
|
||||
//track_xyzw: target.options?.default, //initializeTrack(target),
|
||||
track_xyzw: initializeTrack(target),
|
||||
target,
|
||||
combo
|
||||
};
|
||||
|
||||
const oldCallback = combo.callback;
|
||||
combo.callback = () => {
|
||||
const me = oldCallback?.apply(this, arguments);
|
||||
widgetHide(node, target, "-jov");
|
||||
//if (["VEC2", "VEC2INT", "COORD2D", "VEC3", "VEC3INT", "VEC4", "VEC4INT", "BOOLEAN", "INT", "FLOAT"].includes(combo.value)) {
|
||||
if (["VEC2", "VEC3", "VEC4", "BOOLEAN", "INT", "FLOAT"].includes(combo.value)) {
|
||||
let type = combo.value;
|
||||
if (matchFloatSize) {
|
||||
type = "FLOAT";
|
||||
// if (["VEC2", "VEC2INT", "COORD2D"].includes(combo.value)) {
|
||||
if (["VEC2"].includes(combo.value)) {
|
||||
type = "VEC2";
|
||||
//} else if (["VEC3", "VEC3INT"].includes(combo.value)) {
|
||||
} else if (["VEC3"].includes(combo.value)) {
|
||||
type = "VEC3";
|
||||
//} else if (["VEC4", "VEC4INT"].includes(combo.value)) {
|
||||
} else if (["VEC4"].includes(combo.value)) {
|
||||
type = "VEC4";
|
||||
}
|
||||
}
|
||||
widgetShowVector(target, data.track_xyzw, type);
|
||||
}
|
||||
nodeFitHeight(node);
|
||||
return me;
|
||||
}
|
||||
|
||||
target.options.menu = false;
|
||||
target.callback = () => {
|
||||
if (target.type == "toggle") {
|
||||
data.track_xyzw[0] = target.value ? 1 : 0;
|
||||
} else {
|
||||
Object.keys(target.value).forEach((key) => {
|
||||
data.track_xyzw[key] = target.value[key];
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
/**/
|
||||
|
||||
import { app } from "../../../scripts/app.js"
|
||||
import { nodeFitHeight } from "./util_node.js"
|
||||
|
||||
const _REGEX = /\d/;
|
||||
|
||||
const _MAP = {
|
||||
STRING: "📝",
|
||||
BOOLEAN: "🇴",
|
||||
INT: "🔟",
|
||||
FLOAT: "🛟",
|
||||
VEC2: "🇽🇾",
|
||||
//COORD2D: "🇽🇾",
|
||||
//VEC2INT: "🇽🇾",
|
||||
VEC3: "🇽🇾\u200c🇿",
|
||||
//VEC3INT: "🇽🇾\u200c🇿",
|
||||
VEC4: "🇽🇾\u200c🇿\u200c🇼",
|
||||
//VEC4INT: "🇽🇾\u200c🇿\u200c🇼",
|
||||
LIST: "🧾",
|
||||
DICT: "📖",
|
||||
IMAGE: "🖼️",
|
||||
MASK: "😷"
|
||||
}
|
||||
|
||||
export const CONVERTED_TYPE = "converted-widget"
|
||||
|
||||
// return the internal mapping type name
|
||||
export function widget_type_name(type) { return _MAP[type];}
|
||||
|
||||
export function widget_get_type(config) {
|
||||
// Special handling for COMBO so we restrict links based on the entries
|
||||
let type = config?.[0]
|
||||
let linkType = type
|
||||
if (type instanceof Array) {
|
||||
type = 'COMBO'
|
||||
linkType = linkType.join(',')
|
||||
}
|
||||
return { type, linkType }
|
||||
}
|
||||
|
||||
export const widgetFind = (widgets, name) => widgets.find(w => w.name == name);
|
||||
|
||||
export const widgetFindOutput = (widgets, name) => {
|
||||
for (let i = 0; i < widgets.length; i++) {
|
||||
if (widgets[i].name == name) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function widgetRemove(node, widgetOrSlot) {
|
||||
let index = 0;
|
||||
if (typeof widgetOrSlot == 'number') {
|
||||
index = widgetOrSlot;
|
||||
}
|
||||
else if (widgetOrSlot) {
|
||||
index = node.widgets.indexOf(widgetOrSlot);
|
||||
}
|
||||
if (index > -1) {
|
||||
const w = node.widgets[index];
|
||||
if (w.canvas) {
|
||||
w.canvas.remove()
|
||||
}
|
||||
if (w.inputEl) {
|
||||
w.inputEl.remove()
|
||||
}
|
||||
w.onRemoved?.()
|
||||
node.widgets.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
export function widgetRemoveAll(node) {
|
||||
if (node.widgets) {
|
||||
for (const w of node.widgets) {
|
||||
widgetRemove(node, w);
|
||||
}
|
||||
node.widgets.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function widgetHide(node, widget, suffix = '') {
|
||||
if ((widget?.hidden || false) || widget.type?.startsWith(CONVERTED_TYPE + suffix)) {
|
||||
return;
|
||||
}
|
||||
widget.origType = widget.type;
|
||||
widget.type = CONVERTED_TYPE + suffix;
|
||||
widget.hidden = true;
|
||||
|
||||
widget.origComputeSize = widget.computeSize;
|
||||
widget.computeSize = () => [0, -4];
|
||||
|
||||
// Hide any linked widgets, e.g. seed+seedControl
|
||||
if (widget.linkedWidgets) {
|
||||
for (const w of widget.linkedWidgets) {
|
||||
widgetHide(node, w, ':' + widget.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function widgetShow(widget) {
|
||||
if (widget?.origType) {
|
||||
widget.type = widget.origType;
|
||||
delete widget.origType;
|
||||
}
|
||||
|
||||
widget.computeSize = widget.origComputeSize;
|
||||
delete widget.origComputeSize;
|
||||
|
||||
if (widget.origSerializeValue) {
|
||||
widget.serializeValue = widget.origSerializeValue;
|
||||
delete widget.origSerializeValue;
|
||||
}
|
||||
|
||||
widget.hidden = false;
|
||||
if (widget?.linkedWidgets) {
|
||||
for (const w of widget.linkedWidgets) {
|
||||
widgetShow(w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function widgetShowVector(widget, values={}, type) {
|
||||
widgetShow(widget);
|
||||
if (["FLOAT"].includes(type)) {
|
||||
type = "VEC1";
|
||||
} else if (["INT"].includes(type)) {
|
||||
type = "VEC1INT";
|
||||
} else if (type == "BOOLEAN") {
|
||||
type = "toggle";
|
||||
}
|
||||
|
||||
if (type !== undefined) {
|
||||
widget.type = type;
|
||||
}
|
||||
|
||||
if (widget.value === undefined) {
|
||||
widget.value = widget.options?.default || {};
|
||||
}
|
||||
|
||||
// convert widget.value to pure dict/object
|
||||
if (Array.isArray(widget.value)) {
|
||||
let new_val = {};
|
||||
for (let i = 0; i < widget.value.length; i++) {
|
||||
new_val[i] = widget.value[i];
|
||||
}
|
||||
widget.value = new_val;
|
||||
}
|
||||
|
||||
widget.options.step = 1;
|
||||
widget.options.round = 1;
|
||||
widget.options.precision = 0;
|
||||
if (widget.type != 'toggle') {
|
||||
let size = 1;
|
||||
const match = _REGEX.exec(widget.type);
|
||||
if (match) {
|
||||
size = match[0];
|
||||
}
|
||||
if (!widget.type.endsWith('INT') && widget.type != 'BOOLEAN') {
|
||||
widget.options.step = 0.01;
|
||||
widget.options.round = 0.001;
|
||||
widget.options.precision = 3;
|
||||
}
|
||||
|
||||
widget.value = {};
|
||||
for (let i = 0; i < size; i++) {
|
||||
widget.value[i] = (widget.options.precision == 0) ? Number(values[i]) : parseFloat(values[i]).toFixed(widget.options.precision);
|
||||
//widget.value[i] = !widget.type.endsWith('INT') ? Math.round(values[i]) : Number(values[i]);
|
||||
}
|
||||
} else {
|
||||
widget.value = values[0] ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
export function widgetProcessAny(widget, subtype="FLOAT") {
|
||||
widgetShow(widget);
|
||||
//input.type = subtype;
|
||||
if (subtype == "BOOLEAN") {
|
||||
widget.type = "toggle";
|
||||
} else if (subtype == "FLOAT" || subtype == "INT") {
|
||||
widget.type = "number";
|
||||
if (widget?.options) {
|
||||
if (subtype=="FLOAT") {
|
||||
widget.options.precision = 3;
|
||||
widget.options.step = 1;
|
||||
widget.options.round = 0.1;
|
||||
} else {
|
||||
widget.options.precision = 0;
|
||||
widget.options.step = 10;
|
||||
widget.options.round = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
widget.type = subtype;
|
||||
}
|
||||
}
|
||||
|
||||
export function widgetToWidget(node, widget) {
|
||||
widgetShow(widget);
|
||||
//const sz = node.size;
|
||||
node.removeInput(node.inputs.findIndex((i) => i.widget?.name == widget.name));
|
||||
|
||||
for (const widget of node.widgets) {
|
||||
widget.last_y -= LiteGraph.NODE_SLOT_HEIGHT;
|
||||
}
|
||||
nodeFitHeight(node);
|
||||
|
||||
// Restore original size but grow if needed
|
||||
//node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])]);
|
||||
//node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])]);
|
||||
}
|
||||
|
||||
export function widgetToInput(node, widget, config) {
|
||||
widgetHide(node, widget, '-jov');
|
||||
|
||||
const { linkType } = widget_get_type(config);
|
||||
|
||||
// Add input and store widget config for creating on primitive node
|
||||
//const sz = node.size
|
||||
node.addInput(widget.name, linkType, {
|
||||
widget: { name: widget.name, config },
|
||||
})
|
||||
|
||||
for (const widget of node.widgets) {
|
||||
widget.last_y += LiteGraph.NODE_SLOT_HEIGHT;
|
||||
}
|
||||
nodeFitHeight(node);
|
||||
|
||||
// Restore original size but grow if needed
|
||||
//node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])])
|
||||
}
|
||||
|
||||
export function widgetGetHovered() {
|
||||
if (typeof app == 'undefined') return;
|
||||
|
||||
const node = app.canvas.node_over;
|
||||
if (!node || !node.widgets) return;
|
||||
|
||||
const graphPos = app.canvas.graph_mouse;
|
||||
const x = graphPos[0] - node.pos[0];
|
||||
const y = graphPos[1] - node.pos[1];
|
||||
|
||||
let pos_y;
|
||||
for (const w of node.widgets) {
|
||||
let widgetWidth, widgetHeight;
|
||||
if (w.computeSize) {
|
||||
const sz = w.computeSize();
|
||||
widgetWidth = sz[0] || 0;
|
||||
widgetHeight = sz[1] || 0;
|
||||
} else {
|
||||
widgetWidth = w.width || node.size[0] || 0;
|
||||
widgetHeight = LiteGraph.NODE_WIDGET_HEIGHT;
|
||||
}
|
||||
|
||||
if (pos_y === undefined) {
|
||||
pos_y = w.last_y || 0;
|
||||
}
|
||||
|
||||
if (widgetHeight > 0 && widgetWidth > 0 && w.last_y !== undefined && x >= 6 && x <= widgetWidth - 12 && y >= w.last_y && y <= w.last_y + widgetHeight) {
|
||||
return {
|
||||
widget: w,
|
||||
x1: 6 + node.pos[0],
|
||||
y1: node.pos[1] + w.last_y,
|
||||
x2: node.pos[0] + widgetWidth - 12,
|
||||
y2: node.pos[1] + w.last_y + widgetHeight
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user