first pass on cleaning all emoji from input/output

This commit is contained in:
Alexander G. Morano
2025-05-03 04:19:28 -04:00
parent 152e91de7c
commit 70ab1e942b
27 changed files with 1153 additions and 1557 deletions
+18 -159
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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;
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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";
+190 -6
View File
@@ -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
-162
View File
@@ -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;
}
-269
View File
@@ -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
};
}
}
}