js linting pass
removed dead widgets 30% speed up on init
This commit is contained in:
+3
-1
@@ -12,4 +12,6 @@ ignore.txt
|
||||
*.bak
|
||||
checkpoints
|
||||
results
|
||||
backup
|
||||
backup
|
||||
node_modules
|
||||
*-lock.json
|
||||
+2
-2
@@ -300,7 +300,7 @@ class Session(metaclass=Singleton):
|
||||
if f.suffix != ".py" or f.stem.startswith('_'):
|
||||
continue
|
||||
if f.stem in JOV_IGNORE_NODE or f.stem+'.py' in JOV_IGNORE_NODE:
|
||||
logger.warning(f"💀 Jovimetrix.core.{f.stem}")
|
||||
logger.warning(f"💀 [IGNORED] Jovimetrix.core.{f.stem}")
|
||||
continue
|
||||
try:
|
||||
module = importlib.import_module(f"Jovimetrix.core.{f.stem}")
|
||||
@@ -313,7 +313,7 @@ class Session(metaclass=Singleton):
|
||||
try:
|
||||
for class_name, class_def in module.import_dynamic():
|
||||
setattr(module, class_name, class_def)
|
||||
logger.debug(f"shader: {class_name}")
|
||||
logger.info(f"shader: {class_name}")
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
|
||||
+6
-4
@@ -898,6 +898,7 @@ Supplies raw or default values for various data types, supporting vector input w
|
||||
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))
|
||||
old_seed = -1
|
||||
for idx, (raw, r_x, r_y, r_z, r_w, typ, xyzw, seed, yyzw, x_str) in enumerate(params):
|
||||
typ = EnumConvertType[typ]
|
||||
default = [x_str]
|
||||
@@ -924,19 +925,20 @@ Supplies raw or default values for various data types, supporting vector input w
|
||||
self.UPDATE = False
|
||||
if seed != 0 and isinstance(val, (tuple, list,)) and isinstance(val2, (tuple, list,)):
|
||||
self.UPDATE = True
|
||||
# val = list(val) if isinstance(val, (tuple, list,)) else [val]
|
||||
# val2 = list(val2) if isinstance(val2, (tuple, list,)) else [val2]
|
||||
# mutable to update
|
||||
val = list(val)
|
||||
for i in range(len(val)):
|
||||
mx = max(val[i], val2[i])
|
||||
mn = min(val[i], val2[i])
|
||||
if mn == mx:
|
||||
val[i] = mn
|
||||
else:
|
||||
random.seed(seed)
|
||||
if old_seed != seed:
|
||||
random.seed(seed)
|
||||
old_seed = seed
|
||||
if typ == EnumConvertType.VEC4:
|
||||
val[i] = mn + random.random() * (mx - mn)
|
||||
else:
|
||||
# logger.debug(f"{i}, {mx}, {mn}")
|
||||
val[i] = random.randint(mn, mx)
|
||||
|
||||
extra = parse_value(val, typ, val)
|
||||
|
||||
+8
-12
@@ -114,7 +114,7 @@ Enhance and modify images with various effects such as blurring, sharpening, col
|
||||
pA = tensor2cv(pA) if pA is not None else channel_solid(chan=EnumImageType.BGRA)
|
||||
cc = pA.shape[2] if pA.ndim == 3 else 1
|
||||
if cc == 4:
|
||||
alpha = pA[:,:,3]
|
||||
alpha = pA[..., 3]
|
||||
|
||||
match EnumAdjustOP[op]:
|
||||
case EnumAdjustOP.INVERT:
|
||||
@@ -201,7 +201,7 @@ Enhance and modify images with various effects such as blurring, sharpening, col
|
||||
mask = 255 - mask
|
||||
pA = image_blend(pA, img_new, mask)
|
||||
if cc == 4:
|
||||
pA[:,:,3] = alpha
|
||||
pA[..., 3] = alpha
|
||||
images.append(cv2tensor_full(pA, matte))
|
||||
pbar.update_absolute(idx)
|
||||
return [torch.cat(i, dim=0) for i in zip(*images)]
|
||||
@@ -245,7 +245,7 @@ Combine two input images using various blending modes, such as normal, screen, m
|
||||
mode = parse_param(kw, Lexicon.MODE, EnumConvertType.STRING, EnumScaleMode.NONE.name)
|
||||
wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, [(512, 512)], MIN_IMAGE_SIZE)
|
||||
sample = parse_param(kw, Lexicon.SAMPLE, EnumConvertType.STRING, EnumInterpolation.LANCZOS4.name)
|
||||
matte = parse_param(kw, Lexicon.MATTE, EnumConvertType.VEC3INT, [(0, 0, 0)], 0, 255)
|
||||
matte = parse_param(kw, Lexicon.MATTE, EnumConvertType.VEC4INT, [(0, 0, 0, 255)], 0, 255)
|
||||
invert = parse_param(kw, Lexicon.INVERT, EnumConvertType.BOOLEAN, False)
|
||||
params = list(zip_longest_fill(pA, pB, mask, func, alpha, flip, mode, wihi, sample, matte, invert))
|
||||
images = []
|
||||
@@ -281,11 +281,7 @@ Combine two input images using various blending modes, such as normal, screen, m
|
||||
mask = channel_solid(w, h, matte[3], EnumImageType.GRAYSCALE) if tmask is None else image_mask(tmask)
|
||||
else:
|
||||
mask = tensor2cv(mask)
|
||||
cc = mask.shape[2] if mask.ndim == 3 else 1
|
||||
if cc == 4:
|
||||
mask = mask[:,:,3]
|
||||
else:
|
||||
mask = image_grayscale(mask)
|
||||
mask = image_grayscale(mask)
|
||||
|
||||
if invert:
|
||||
mask = 255 - mask
|
||||
@@ -516,7 +512,7 @@ Extract a portion of an input image or resize it. It supports various cropping m
|
||||
pA = image_crop_polygonal(pA, points)
|
||||
if alpha is not None:
|
||||
alpha = image_crop_polygonal(alpha, points)
|
||||
pA[:,:,3] = alpha[:,:,0][:,:]
|
||||
pA[..., 3] = alpha[..., 0][:,:]
|
||||
|
||||
elif func == EnumCropMode.XY:
|
||||
pA = image_crop(pA, width, height, xy)
|
||||
@@ -571,7 +567,7 @@ Create masks based on specific color ranges within an image. Specify the color r
|
||||
if img.shape[2] == 3:
|
||||
alpha_channel = np.zeros((img.shape[0], img.shape[1], 1), dtype=img.dtype)
|
||||
img = np.concatenate((img, alpha_channel), axis=2)
|
||||
img[:,:,3] = mask[:,:]
|
||||
img[..., 3] = mask[:,:]
|
||||
images.append(cv2tensor_full(img, matte))
|
||||
pbar.update_absolute(idx)
|
||||
return [torch.cat(i, dim=0) for i in zip(*images)]
|
||||
@@ -852,10 +848,10 @@ Swap pixel values between two input images based on specified channel swizzle op
|
||||
return target
|
||||
|
||||
# logger.debug(swap_r, swap_g, swap_b, swap_a)
|
||||
out[:,:,0] = swapper(EnumPixelSwizzle.BLUE_A, swap_b)[:,:,0]
|
||||
out[..., 0] = swapper(EnumPixelSwizzle.BLUE_A, swap_b)[..., 0]
|
||||
out[:,:,1] = swapper(EnumPixelSwizzle.GREEN_A, swap_g)[:,:,1]
|
||||
out[:,:,2] = swapper(EnumPixelSwizzle.RED_A, swap_r)[:,:,2]
|
||||
out[:,:,3] = swapper(EnumPixelSwizzle.ALPHA_A, swap_a)[:,:,3]
|
||||
out[..., 3] = swapper(EnumPixelSwizzle.ALPHA_A, swap_a)[..., 3]
|
||||
images.append(cv2tensor_full(out))
|
||||
pbar.update_absolute(idx)
|
||||
return [torch.cat(i, dim=0) for i in zip(*images)]
|
||||
|
||||
+28
-36
@@ -19,9 +19,9 @@ from Jovimetrix.sup.lexicon import JOVImageNode, Lexicon
|
||||
from Jovimetrix.sup.util import parse_param, zip_longest_fill, EnumConvertType
|
||||
|
||||
from Jovimetrix.sup.image import channel_solid, cv2tensor, cv2tensor_full, \
|
||||
image_grayscale, image_invert, image_mask_add, pil2cv, \
|
||||
image_rotate, image_scalefit, image_stereogram, image_transform, \
|
||||
tensor2cv, shape_ellipse, shape_polygon, shape_quad, image_translate, \
|
||||
image_grayscale, image_invert, image_mask_add, image_mask_binary, image_matte, \
|
||||
image_rotate, image_scalefit, image_stereogram, image_transform, shape_body, \
|
||||
tensor2cv, shape_polygon, image_translate, pil2cv, \
|
||||
EnumScaleMode, EnumInterpolation, EnumEdge, EnumImageType, MIN_IMAGE_SIZE
|
||||
|
||||
from Jovimetrix.sup.text import font_names, text_autosize, text_draw, \
|
||||
@@ -50,10 +50,10 @@ Generate a constant image or mask of a specified size and color. It can be used
|
||||
d.update({
|
||||
"optional": {
|
||||
Lexicon.PIXEL: (JOV_TYPE_IMAGE, {"tooltip":"Optional Image to Matte with Selected Color"}),
|
||||
Lexicon.RGBA_A: ("VEC4", {"default": (0, 0, 0, 255), "step": 1,
|
||||
Lexicon.RGBA_A: ("VEC4INT", {"default": (0, 0, 0, 255),
|
||||
"label": [Lexicon.R, Lexicon.G, Lexicon.B, Lexicon.A],
|
||||
"rgb": True, "tooltip": "Constant Color to Output"}),
|
||||
Lexicon.WH: ("VEC2", {"default": (512, 512), "step": 1,
|
||||
Lexicon.WH: ("VEC2INT", {"default": (512, 512),
|
||||
"label": [Lexicon.W, Lexicon.H],
|
||||
"tooltip": "Desired Width and Height of the Color Output"}),
|
||||
Lexicon.MODE: (EnumScaleMode._member_names_, {"default": EnumScaleMode.NONE.name}),
|
||||
@@ -100,15 +100,14 @@ Create n-sided polygons. These shapes can be customized by adjusting parameters
|
||||
"optional": {
|
||||
Lexicon.SHAPE: (EnumShapes._member_names_, {"default": EnumShapes.CIRCLE.name}),
|
||||
Lexicon.SIDES: ("INT", {"default": 3, "min": 3, "max": 100, "step": 1}),
|
||||
Lexicon.RGBA_A: ("VEC4", {"default": (255, 255, 255, 255), "step": 1,
|
||||
Lexicon.RGBA_A: ("VEC4INT", {"default": (255, 255, 255, 255), "step": 1,
|
||||
"label": [Lexicon.R, Lexicon.G, Lexicon.B, Lexicon.A],
|
||||
"rgb": True, "tooltip": "Main Shape Color"}),
|
||||
Lexicon.MATTE: ("VEC4", {"default": (0, 0, 0, 255), "step": 1,
|
||||
Lexicon.MATTE: ("VEC4INT", {"default": (0, 0, 0, 255), "step": 1,
|
||||
"label": [Lexicon.R, Lexicon.G, Lexicon.B, Lexicon.A],
|
||||
"rgb": True, "tooltip": "Background Color"}),
|
||||
Lexicon.WH: ("VEC2", {"default": (256, 256),
|
||||
"step": 1, "min":MIN_IMAGE_SIZE,
|
||||
"label": [Lexicon.W, Lexicon.H]}),
|
||||
Lexicon.WH: ("VEC2INT", {"default": (256, 256),
|
||||
"min":MIN_IMAGE_SIZE, "label": [Lexicon.W, Lexicon.H]}),
|
||||
Lexicon.XY: ("VEC2", {"default": (0, 0,), "step": 0.01, "precision": 4,
|
||||
"round": 0.00001, "label": [Lexicon.X, Lexicon.Y]}),
|
||||
Lexicon.ANGLE: ("FLOAT", {"default": 0, "min": -180, "max": 180,
|
||||
@@ -141,41 +140,34 @@ Create n-sided polygons. These shapes can be customized by adjusting parameters
|
||||
sizeX, sizeY = size
|
||||
edge = EnumEdge[edge]
|
||||
shape = EnumShapes[shape]
|
||||
alpha_m = int(matte[3])
|
||||
fill = color[:3][::-1]
|
||||
back = matte[:3]
|
||||
|
||||
match shape:
|
||||
case EnumShapes.SQUARE:
|
||||
pA = shape_quad(width, height, sizeX, sizeX, fill=color[:3], back=matte[:3])
|
||||
mask = shape_quad(width, height, sizeX, sizeX, fill=alpha_m)
|
||||
case EnumShapes.SQUARE | EnumShapes.RECTANGLE:
|
||||
# pA = shape_quad(width, height, sizeX, sizeX, fill=fill, back=back)
|
||||
pA = shape_body('rectangle', width, height, sizeX=sizeX, sizeY=sizeY, fill=fill, back=back)
|
||||
|
||||
case EnumShapes.ELLIPSE:
|
||||
pA = shape_ellipse(width, height, sizeX, sizeY, fill=color[:3], back=matte[:3])
|
||||
mask = shape_ellipse(width, height, sizeX, sizeY, fill=alpha_m)
|
||||
|
||||
case EnumShapes.RECTANGLE:
|
||||
pA = shape_quad(width, height, sizeX, sizeY, fill=color[:3], back=matte[:3])
|
||||
mask = shape_quad(width, height, sizeX, sizeY, fill=alpha_m)
|
||||
case EnumShapes.CIRCLE | EnumShapes.ELLIPSE:
|
||||
pA = shape_body('ellipse', width, height, sizeX=sizeX, sizeY=sizeY, fill=fill, back=back)
|
||||
|
||||
case EnumShapes.POLYGON:
|
||||
pA = shape_polygon(width, height, sizeX, sides, fill=color[:3], back=matte[:3])
|
||||
mask = shape_polygon(width, height, sizeX, sides, fill=alpha_m)
|
||||
|
||||
case EnumShapes.CIRCLE:
|
||||
pA = shape_ellipse(width, height, sizeX, sizeX, fill=color[:3], back=matte[:3])
|
||||
mask = shape_ellipse(width, height, sizeX, sizeX, fill=alpha_m)
|
||||
pA = shape_polygon(width, height, sizeX, sides, fill=fill, back=back)
|
||||
|
||||
pA = pil2cv(pA)
|
||||
mask = pil2cv(mask)
|
||||
mask = image_grayscale(mask)
|
||||
pA = image_transform(pA, offset, angle, (1,1), edge=edge)
|
||||
mask = image_transform(mask, offset, angle, (1,1), edge=edge)
|
||||
pB = image_mask_add(pA, mask)
|
||||
pA = image_transform(pA, offset, angle, edge=edge)
|
||||
if blur > 0:
|
||||
# @TODO: Do blur on larger canvas to remove wrap bleed.
|
||||
pA = (gaussian(pA, sigma=blur, channel_axis=2) * 255).astype(np.uint8)
|
||||
pB = (gaussian(pB, sigma=blur, channel_axis=2) * 255).astype(np.uint8)
|
||||
mask = (gaussian(mask, sigma=blur, channel_axis=2) * 255).astype(np.uint8)
|
||||
pA = image_matte(pA, matte)
|
||||
|
||||
images.append([cv2tensor(pB), cv2tensor(pA), cv2tensor(mask, True)])
|
||||
mask = image_mask_binary(pA) # * float(color[3]) / 255.
|
||||
pB = image_mask_add(pA, mask)
|
||||
matte = image_matte(pB, matte)
|
||||
# matte = np.full((height, width, 4), matte, dtype=np.uint8)
|
||||
# matte[:, :] = pB
|
||||
|
||||
images.append([cv2tensor(pB), cv2tensor(matte), cv2tensor(mask, True)])
|
||||
pbar.update_absolute(idx)
|
||||
return [torch.cat(i, dim=0) for i in zip(*images)]
|
||||
|
||||
@@ -402,7 +394,7 @@ The Wave Graph node visualizes audio waveforms as bars. Adjust parameters like t
|
||||
thick = parse_param(kw, Lexicon.THICK, EnumConvertType.FLOAT, 0.75, 0, 1)
|
||||
wihi = parse_param(kw, Lexicon.WH, EnumConvertType.VEC2INT, [(512, 512)], MIN_IMAGE_SIZE)
|
||||
rgb_a = parse_param(kw, Lexicon.RGBA_A, EnumConvertType.VEC4INT, [(196, 0, 196)], 0, 255)
|
||||
matte = parse_param(kw, Lexicon.MATTE, EnumConvertType.VEC4INT, [(42, 12, 42)], 0, 255)
|
||||
matte = parse_param(kw, Lexicon.MATTE, EnumConvertType.VEC4INT, [(42, 12, 42, 255)], 0, 255)
|
||||
params = list(zip_longest_fill(wave, bars, wihi, thick, rgb_a, matte))
|
||||
images = []
|
||||
pbar = ProgressBar(len(params))
|
||||
|
||||
+17
-9
@@ -7,6 +7,7 @@ import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Tuple
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from loguru import logger
|
||||
|
||||
@@ -129,6 +130,7 @@ class GLSLNodeBase(JOVImageNode):
|
||||
self.__glsl.program(self.VERTEX, self.FRAGMENT)
|
||||
except CompileException as e:
|
||||
comfy_message(ident, "jovi-glsl-error", {"id": ident, "e": str(e)})
|
||||
logger.error(self.NAME)
|
||||
logger.error(e)
|
||||
return
|
||||
|
||||
@@ -199,14 +201,25 @@ class GLSLNodeDynamic(GLSLNodeBase):
|
||||
# parameter list first...
|
||||
data = {}
|
||||
if cls.PARAM is not None:
|
||||
for glsl_type, name, default, tooltip in cls.PARAM:
|
||||
for glsl_type, name, default, tooltip, val_min, val_max, val_step in cls.PARAM:
|
||||
typ = PTYPE[glsl_type]
|
||||
d = None
|
||||
if glsl_type != 'sampler2D' and default is not None:
|
||||
d = default.split(',')
|
||||
d = parse_value(d, typ, 0)
|
||||
data[name] = (typ.name, {"default": d, "min": -2147483647, "nax":2147483647, "step": 0.01, "tooltip": tooltip},)
|
||||
print(name, (typ.name, {"default": d},))
|
||||
|
||||
#val_min = -2147483647
|
||||
#val_max = 2147483647
|
||||
#val_step = 1
|
||||
entry = (typ.name, {})
|
||||
match typ:
|
||||
case EnumConvertType.INT | EnumConvertType.VEC2INT | EnumConvertType.VEC3INT | EnumConvertType.VEC4INT:
|
||||
entry = (typ.name, {"default": d, "min": val_min, "max":val_max, "step": val_step, "tooltip": tooltip},)
|
||||
case EnumConvertType.FLOAT | EnumConvertType.VEC2 | EnumConvertType.VEC3 | EnumConvertType.VEC4:
|
||||
entry = (typ.name, {"default": d, "min": val_min, "max":val_max, "step": val_step, "precision": 6, "round": 0.0001, "tooltip": tooltip},)
|
||||
case EnumConvertType.IMAGE:
|
||||
entry = (typ.name, {"default": d, "tooltip": tooltip},)
|
||||
data[name] = entry
|
||||
|
||||
data.update(opts)
|
||||
original_params['optional'] = data
|
||||
@@ -221,6 +234,7 @@ def import_dynamic() -> Tuple[str,...]:
|
||||
continue
|
||||
|
||||
meta = shader_meta(shader)
|
||||
|
||||
name = meta.get('name', name.split('.')[0])
|
||||
class_name = f'GLSLNode_{name.title()}'
|
||||
class_def = type(class_name, (GLSLNodeDynamic,), {
|
||||
@@ -229,11 +243,5 @@ def import_dynamic() -> Tuple[str,...]:
|
||||
"FRAGMENT": shader,
|
||||
"PARAM": meta.get('_', []),
|
||||
})
|
||||
|
||||
#def init_method(self, *arg, **kw) -> None:
|
||||
# super(class_def, self).__init__(*arg, **kw)
|
||||
# self.FRAGMENT = shader
|
||||
|
||||
#class_def.__init__ = init_method
|
||||
ret.append((class_name, class_def,))
|
||||
return ret
|
||||
|
||||
+25
-1
@@ -648,7 +648,8 @@ Manage a queue of items, such as file paths or data. It supports various formats
|
||||
data = torch.cat(ret, dim=0)
|
||||
else:
|
||||
data = process(self.__q[self.__index])
|
||||
data = cv2tensor(data)
|
||||
if isinstance(data[0], (np.ndarray,)):
|
||||
data = cv2tensor(data)
|
||||
self.__index += 1
|
||||
|
||||
self.__previous = data
|
||||
@@ -763,6 +764,29 @@ Save the output image along with its metadata to the specified path. Supports sa
|
||||
pbar.update_absolute(idx)
|
||||
return ()
|
||||
|
||||
class Terminate(JOVBaseNode):
|
||||
NAME = "TERMINATE COMFYUI (JOV) "
|
||||
CATEGORY = f"JOVIMETRIX 🔺🟩🔵/{JOV_CATEGORY}"
|
||||
OUTPUT_NODE = True
|
||||
RETURN_TYPES = ()
|
||||
SORT = 115
|
||||
DESCRIPTION = """
|
||||
Terminate a running ComfyUI server.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> dict:
|
||||
d = super().INPUT_TYPES(True, True)
|
||||
d.update({
|
||||
"optional": {
|
||||
Lexicon.TRIGGER: ("TRIGGER",),
|
||||
}
|
||||
})
|
||||
return Lexicon._parse(d, cls)
|
||||
|
||||
def run(self, **kw) -> dict[str, Any]:
|
||||
exit()
|
||||
|
||||
'''
|
||||
class RESTNode:
|
||||
"""Make requests and process the responses."""
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import globals from "globals";
|
||||
import pluginJs from "@eslint/js";
|
||||
import pluginVue from "eslint-plugin-vue";
|
||||
|
||||
|
||||
export default [
|
||||
{files: ["**/*.{js,mjs,cjs,vue}"]},
|
||||
{languageOptions: { globals: globals.browser }},
|
||||
pluginJs.configs.recommended,
|
||||
...pluginVue.configs["flat/essential"],
|
||||
];
|
||||
+3
-1
@@ -14,8 +14,9 @@
|
||||
"FILTER MASK (JOV) \ud83e\udd3f": "Create masks based on specific color ranges within an image",
|
||||
"FLATTEN (JOV) \u2b07\ufe0f": "Combine multiple input images into a single image by summing their pixel values",
|
||||
"GLSL (JOV) \ud83c\udf69": "Execute custom GLSL (OpenGL Shading Language) fragment shaders to generate images or apply effects",
|
||||
"GLSL BLEND (JOV) \ud83e\uddd9\ud83c\udffd": "Simple linear blend between two images",
|
||||
"GLSL BLEND_LINEAR (JOV) \ud83e\uddd9\ud83c\udffd": "Simple linear blend between two images",
|
||||
"GLSL GRAYSCALE (JOV) \ud83e\uddd9\ud83c\udffd": "Convert input to grayscale",
|
||||
"GLSL NORMAL (JOV) \ud83e\uddd9\ud83c\udffd": "Convert input into a Normal map",
|
||||
"GRADIENT MAP (JOV) \ud83c\uddf2\ud83c\uddfa": "Remaps an input image using a gradient lookup table (LUT)",
|
||||
"GRAPH (JOV) \ud83d\udcc8": "Visualize a series of data points over time",
|
||||
"IMAGE INFO (JOV) \ud83d\udcda": "Exports and Displays immediate information about images",
|
||||
@@ -40,6 +41,7 @@
|
||||
"STREAM READER (JOV) \ud83d\udcfa": "Capture frames from various sources such as URLs, cameras, monitors, windows, or Spout streams",
|
||||
"STREAM WRITER (JOV) \ud83c\udf9e\ufe0f": "Sends frames to a specified route, typically for live streaming or recording purposes",
|
||||
"SWIZZLE (JOV) \ud83d\ude35": "Swap components between two vectors based on specified swizzle patterns and values",
|
||||
"TERMINATE COMFYUI (JOV) ": "Terminate a running ComfyUI server",
|
||||
"TEXT GEN (JOV) \ud83d\udcdd": "Generates images containing text based on parameters such as font, size, alignment, color, and position",
|
||||
"THRESHOLD (JOV) \ud83d\udcc9": "Define a range and apply it to an image for segmentation and feature extraction",
|
||||
"TICK (JOV) \u23f1": "A timer and frame counter, emitting pulses or signals based on time intervals",
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "jovimetrix",
|
||||
"version": "1.0.0",
|
||||
"description": "<h2><p align=\"center\">THIS ENTIRE PROJECT IS DONATIONWARE.<br>PLEASE FEEL FREE TO CONTRIBUTE IN ANYWAY YOU THINK YOU CAN</p></h2>\r <picture>\r <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://github.com/Amorano/Jovimetrix-examples/blob/master/res/logo-jovimetrix.png\">\r <source media=\"(prefers-color-scheme: light)\" srcset=\"https://github.com/Amorano/Jovimetrix-examples/blob/master/res/logo-jovimetrix-light.png\">\r <img alt=\"ComfyUI Nodes for procedural masking, live composition and video manipulation\">\r </picture>",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.7.0",
|
||||
"eslint": "^9.7.0",
|
||||
"eslint-plugin-vue": "^9.27.0",
|
||||
"globals": "^15.8.0"
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
// default grayscale using NTSC conversion weights
|
||||
uniform sampler2D image;
|
||||
uniform vec3 conversion; // 0.299, 0.587, 0.114
|
||||
uniform vec3 conversion; // 0.299, 0.587, 0.114;0;1;0.01 | Scalar for each channel
|
||||
|
||||
void mainImage( out vec4 fragColor, vec2 fragCoord ) {
|
||||
vec2 uv = fragCoord.xy / iResolution.xy;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// name: BLEND
|
||||
// name: BLEND_LINEAR
|
||||
// desc: Simple linear blend between two images
|
||||
//
|
||||
|
||||
uniform sampler2D imageA;
|
||||
uniform sampler2D imageB;
|
||||
uniform float blend_amt; // 0.5
|
||||
uniform float blend_amt; // 0.5;0;1;0.01
|
||||
|
||||
void mainImage( out vec4 fragColor, vec2 fragCoord ) {
|
||||
vec2 uv = fragCoord.xy / iResolution.xy;
|
||||
vec4 col_a = texture2D(imageA, uv);
|
||||
vec4 col_b = texture2D(imageB, uv);
|
||||
fragColor = mix(col_a, col_b, blend_amt);
|
||||
fragColor = mix(col_b, col_a, blend_amt);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// name: NORMAL
|
||||
// desc: Convert input into a Normal map
|
||||
//
|
||||
|
||||
uniform sampler2D image; // | Input image to convert into a normal map
|
||||
uniform float scalar; // 0.25 | Intensity of depth
|
||||
|
||||
const mat3 scharr_x = mat3(
|
||||
3.0, 10.0, 3.0,
|
||||
0.0, 0.0, 0.0,
|
||||
-3.0, -10.0, -3.0
|
||||
);
|
||||
|
||||
const mat3 scharr_y = mat3(
|
||||
3.0, 0.0, -3.0,
|
||||
10.0, 0.0, -10.0,
|
||||
3.0, 0.0, -3.0
|
||||
);
|
||||
|
||||
vec3 scharr(sampler2D tex, vec2 uv) {
|
||||
vec3 result = vec3(0.0);
|
||||
vec2 texelSize = 1.0 / iResolution.xy;
|
||||
|
||||
for (int i = -1; i <= 1; i++) {
|
||||
for (int j = -1; j <= 1; j++) {
|
||||
vec2 offset = vec2(float(i), float(j)) * texelSize;
|
||||
vec3 color = texture(tex, uv + offset).rgb;
|
||||
float luminance = dot(color, vec3(0.299, 0.587, 0.114));
|
||||
result.x += luminance * scharr_x[i+1][j+1];
|
||||
result.y += luminance * scharr_y[i+1][j+1];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void mainImage( out vec4 fragColor, in vec2 fragCoord )
|
||||
{
|
||||
vec2 uv = fragCoord / iResolution.xy;
|
||||
vec3 normal;
|
||||
normal.xy = scharr(image, uv).yx * scalar;
|
||||
normal.x *= -1.0;
|
||||
normal.z = 1.0;
|
||||
normal = normalize(normal);
|
||||
fragColor = vec4(normal * 0.5 + 0.5, 1.0);
|
||||
}
|
||||
+170
-123
@@ -349,7 +349,7 @@ def cv2tensor(image: TYPE_IMAGE, mask:bool=False) -> torch.Tensor:
|
||||
"""Convert a CV2 image to a torch tensor."""
|
||||
if mask or image.ndim < 3 or (image.ndim == 3 and image.shape[2] == 1):
|
||||
mask = True
|
||||
image = image_grayscale(image)[:,:]
|
||||
image = image_grayscale(image)
|
||||
ret = torch.from_numpy(image.astype(np.float32) / 255.0).unsqueeze(0)
|
||||
if mask and ret.ndim == 4:
|
||||
ret = ret.squeeze(-1)
|
||||
@@ -357,8 +357,8 @@ def cv2tensor(image: TYPE_IMAGE, mask:bool=False) -> torch.Tensor:
|
||||
|
||||
def cv2tensor_full(image: TYPE_IMAGE, matte:TYPE_PIXEL=0) -> Tuple[torch.Tensor, ...]:
|
||||
image = image_convert(image, 4)
|
||||
mask = image_mask(image)[:,:,0][:,:]
|
||||
image[:,:,3] = mask
|
||||
mask = image_mask(image)
|
||||
image[..., 3] = mask
|
||||
rgb = image_matte(image, matte)
|
||||
rgb = image_convert(image, 3)
|
||||
image = torch.from_numpy(image.astype(np.float32) / 255.0).unsqueeze(0)
|
||||
@@ -579,7 +579,7 @@ def channel_merge(channel:List[TYPE_IMAGE]) -> TYPE_IMAGE:
|
||||
if ch.shape[:2] != (max_height, max_width):
|
||||
ch = cv2.resize(ch, (max_width, max_height))
|
||||
if ch.ndim > 2:
|
||||
ch = ch[:,:,0]
|
||||
ch = ch[..., 0]
|
||||
img[:,:,i] = ch
|
||||
|
||||
if len(channel) == 3:
|
||||
@@ -616,12 +616,6 @@ def shape_body(func: str, width: int, height: int, sizeX:float=1., sizeY:float=1
|
||||
func(xy, fill=fill)
|
||||
return image
|
||||
|
||||
def shape_ellipse(width: int, height: int, sizeX:float=1., sizeY:float=1., fill:TYPE_PIXEL=255, back:TYPE_PIXEL=0) -> Image:
|
||||
return shape_body('ellipse', width, height, sizeX=sizeX, sizeY=sizeY, fill=fill, back=back)
|
||||
|
||||
def shape_quad(width: int, height: int, sizeX:float=1., sizeY:float=1., fill:TYPE_PIXEL=255, back:TYPE_PIXEL=0) -> Image:
|
||||
return shape_body('rectangle', width, height, sizeX=sizeX, sizeY=sizeY, fill=fill, back=back)
|
||||
|
||||
def shape_polygon(width: int, height: int, size: float=1., sides: int=3, fill:TYPE_PIXEL=255, back:TYPE_PIXEL=0) -> Image:
|
||||
size = max(0.00001, size)
|
||||
r = min(width, height) * size * 0.5
|
||||
@@ -655,16 +649,16 @@ def image_blend(imageA: TYPE_IMAGE, imageB: TYPE_IMAGE, mask:Optional[TYPE_IMAGE
|
||||
h2 = min(h, h2)
|
||||
imageB = image_crop_center(imageB, w2, h2)
|
||||
imageB = image_matte(imageB, (0,0,0,0), w, h)
|
||||
old_mask = image_mask(imageB)[:,:,0]
|
||||
old_mask = image_mask(imageB)
|
||||
if len(old_mask.shape) > 2:
|
||||
old_mask = old_mask[:,:,0][:,:]
|
||||
old_mask = old_mask[..., 0][:,:]
|
||||
if mask is not None:
|
||||
mask = image_crop_center(mask, w, h)
|
||||
mask = image_matte(mask, (0,0,0,0), w, h)
|
||||
if len(mask.shape) > 2:
|
||||
mask = mask[:,:,0][:,:]
|
||||
mask = mask[..., 0][:,:]
|
||||
old_mask = cv2.bitwise_and(mask, old_mask)
|
||||
imageB[:,:,3] = old_mask
|
||||
imageB[..., 3] = old_mask
|
||||
imageB = cv2pil(imageB)
|
||||
image = blendLayers(imageA, imageB, blendOp.value, np.clip(alpha, 0, 1))
|
||||
image = pil2cv(image)
|
||||
@@ -711,13 +705,15 @@ def image_convert(image: TYPE_IMAGE, channels: int) -> TYPE_IMAGE:
|
||||
"""Force image format to number of channels chosen."""
|
||||
if len(image.shape) < 3:
|
||||
image = np.expand_dims(image, -1).astype(dtype=np.uint8)
|
||||
ncc = max(1, min(4, channels))
|
||||
if ncc < 3:
|
||||
|
||||
channels = max(1, min(4, channels))
|
||||
if channels < 3:
|
||||
return image_grayscale(image)
|
||||
|
||||
cc = image.shape[2] if image.ndim == 3 else 1
|
||||
if ncc == cc:
|
||||
if channels == cc:
|
||||
return image
|
||||
if ncc == 3:
|
||||
if channels == 3:
|
||||
if cc == 1:
|
||||
return cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
|
||||
elif cc == 4:
|
||||
@@ -733,7 +729,7 @@ def image_crop_polygonal(image: TYPE_IMAGE, points: List[TYPE_COORD]) -> TYPE_IM
|
||||
points = np.array(points, np.int32).reshape((-1, 1, 2))
|
||||
point_mask = cv2.fillPoly(point_mask, [points], 255)
|
||||
x, y, w, h = cv2.boundingRect(point_mask)
|
||||
cropped_image = cv2.resize(image[y:y+h, x:x+w], (w, h))
|
||||
cropped_image = cv2.resize(image[y:y+h, x:x+w], (w, h)).astype(np.uint8)
|
||||
# Apply the mask to the cropped image
|
||||
point_mask_cropped = cv2.resize(point_mask[y:y+h, x:x+w], (w, h))
|
||||
if cc == 4:
|
||||
@@ -1046,7 +1042,7 @@ def image_filter(image:TYPE_IMAGE, start:Tuple[int]=(128,128,128), end:Tuple[int
|
||||
image: torch.tensor = cv2tensor(image)
|
||||
cc = image.shape[2]
|
||||
if cc == 4:
|
||||
old_alpha = image[:,:,3]
|
||||
old_alpha = image[..., 3]
|
||||
new_image = image[:, :, :3]
|
||||
elif cc == 1:
|
||||
new_image = np.repeat(image, 3, axis=2)
|
||||
@@ -1142,7 +1138,7 @@ def image_gradient_map(image:TYPE_IMAGE, gradient_map:TYPE_IMAGE, reverse:bool=F
|
||||
cmap = cmap[0,:,:].reshape((256, 1, 3)).astype(np.uint8)
|
||||
return cv2.applyColorMap(grey, cmap)
|
||||
|
||||
def image_grayscale(image: np.ndarray) -> np.ndarray:
|
||||
def image_grayscale(image: TYPE_IMAGE) -> TYPE_IMAGE:
|
||||
"""
|
||||
Convert an image to grayscale, preserving alpha if present.
|
||||
|
||||
@@ -1152,7 +1148,7 @@ def image_grayscale(image: np.ndarray) -> np.ndarray:
|
||||
- Already grayscale images
|
||||
|
||||
Args:
|
||||
image (np.ndarray): Input image. Can be 2D (grayscale) or 3D (RGB/RGBA) array.
|
||||
image (TYPE_IMAGE): Input image. Can be 2D (grayscale) or 3D (RGB/RGBA) array.
|
||||
|
||||
Returns:
|
||||
np.ndarray: Grayscale image with alpha channel preserved if present in input.
|
||||
@@ -1166,29 +1162,15 @@ def image_grayscale(image: np.ndarray) -> np.ndarray:
|
||||
image = cv2.normalize(image, None, 0, 255, cv2.NORM_MINMAX)
|
||||
image = image.astype(np.uint8)
|
||||
|
||||
# Extract alpha channel if present
|
||||
has_alpha = image.shape[-1] == 4 if image.ndim == 3 else False
|
||||
alpha = image[..., 3] if has_alpha else None
|
||||
# already grayscale
|
||||
if image.ndim < 3 or image.shape[2] == 1:
|
||||
return np.expand_dims(image, axis=-1)
|
||||
|
||||
# Convert to grayscale
|
||||
if image.ndim == 3:
|
||||
if image.shape[2] in [3, 4]:
|
||||
gray = cv2.cvtColor(image[..., :3], cv2.COLOR_BGR2GRAY)
|
||||
else:
|
||||
raise ValueError(f"Unexpected number of channels: {image.shape[2]}")
|
||||
elif image.ndim == 2:
|
||||
gray = image
|
||||
if image.shape[2] == 4:
|
||||
image = cv2.cvtColor(image, cv2.COLOR_BGRA2GRAY)
|
||||
else:
|
||||
raise ValueError(f"Unexpected number of dimensions: {image.ndim}")
|
||||
|
||||
# Ensure output is 3D
|
||||
gray = np.expand_dims(gray, axis=-1)
|
||||
|
||||
# Apply alpha if present
|
||||
if has_alpha:
|
||||
gray = np.dstack((gray, alpha[..., np.newaxis]))
|
||||
|
||||
return gray
|
||||
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
||||
return np.expand_dims(image, axis=-1)
|
||||
|
||||
def image_grid(data: List[TYPE_IMAGE], width: int, height: int) -> TYPE_IMAGE:
|
||||
#@TODO: makes poor assumption all images are the same dimensions.
|
||||
@@ -1277,51 +1259,68 @@ def image_lerp(imageA:TYPE_IMAGE, imageB:TYPE_IMAGE, mask:TYPE_IMAGE=None,
|
||||
imageA = (imageA * 255).astype(np.uint8)
|
||||
return np.clip(imageA, 0, 255)
|
||||
|
||||
def image_levels(image:torch.Tensor, black_point:int=0, white_point=255,
|
||||
mid_point=128, gamma=1.0) -> TYPE_IMAGE:
|
||||
def image_levels(image:np.ndarray, black_point:int=0, white_point=255,
|
||||
mid_point=128, gamma=1.0) -> np.ndarray:
|
||||
"""
|
||||
Adjusts the levels of an image including black, white, midpoints, and gamma correction.
|
||||
|
||||
Args:
|
||||
image (numpy.ndarray): Input image tensor in RGB(A) format.
|
||||
black_point (int): The black point to adjust shadows. Default is 0.
|
||||
white_point (int): The white point to adjust highlights. Default is 255.
|
||||
mid_point (int): The mid point for mid-tone adjustment. Default is 128.
|
||||
gamma (float): Gamma correction value. Default is 1.0.
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: Adjusted image tensor.
|
||||
"""
|
||||
|
||||
image, alpha, cc = image2bgr(image)
|
||||
black = np.array([black_point] * 3, dtype=np.float32)
|
||||
white = np.array([white_point] * 3, dtype=np.float32)
|
||||
mid = np.array([mid_point] * 3, dtype=np.float32)
|
||||
inGamma = np.array([gamma] * 3, dtype=np.float32)
|
||||
|
||||
# Convert points and gamma to float32 for calculations
|
||||
black = np.array([black_point] * 3, dtype=np.float32)
|
||||
white = np.array([white_point] * 3, dtype=np.float32)
|
||||
mid = np.array([mid_point] * 3, dtype=np.float32)
|
||||
inGamma = np.array([gamma] * 3, dtype=np.float32)
|
||||
outBlack = np.array([0, 0, 0], dtype=np.float32)
|
||||
outWhite = np.array([255, 255, 255], dtype=np.float32)
|
||||
image = np.clip( (image - black) / (white - black), 0, 255 )
|
||||
image = (image ** (1/inGamma) ) * (outWhite - outBlack) + outBlack
|
||||
|
||||
# Apply levels adjustment
|
||||
image = np.clip((image - black) / (white - black), 0, 1)
|
||||
image = (image - mid) / (1.0 - mid)
|
||||
image = (image ** (1 / inGamma)) * (outWhite - outBlack) + outBlack
|
||||
image = np.clip(image, 0, 255).astype(np.uint8)
|
||||
return bgr2image(image, alpha, cc == 1)
|
||||
|
||||
def image_load(url: str) -> Tuple[TYPE_IMAGE, TYPE_IMAGE]:
|
||||
"""
|
||||
if img.format == 'PSD':
|
||||
images = [pil2cv(frame.copy()) for frame in ImageSequence.Iterator(img)]
|
||||
# logger.debug(f"#PSD {len(images)}")
|
||||
"""
|
||||
def image_load(url: str) -> Tuple[TYPE_IMAGE, ...]:
|
||||
try:
|
||||
img = cv2.imread(url, cv2.IMREAD_UNCHANGED)
|
||||
if img is None:
|
||||
raise ValueError()
|
||||
raise ValueError(f"Image at {url} could not be loaded.")
|
||||
|
||||
if img.ndim == 3:
|
||||
if img.shape[2] == 4:
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA)
|
||||
else:
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
||||
if img.ndim < 3:
|
||||
elif img.ndim < 3:
|
||||
img = np.expand_dims(img, axis=2)
|
||||
#if img.shape[2] == 1:
|
||||
# img = image_convert(img, 3)
|
||||
except Exception as _:
|
||||
|
||||
except Exception:
|
||||
try:
|
||||
img = Image.open(url)
|
||||
img = ImageOps.exif_transpose(img)
|
||||
img = pil2cv(img)
|
||||
img = np.array(img)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
raise Exception(f"Error loading image: {e}")
|
||||
|
||||
if img is None:
|
||||
raise Exception(f"no file {url}")
|
||||
raise Exception(f"No file found at {url}")
|
||||
|
||||
if img.dtype != np.uint8:
|
||||
img = np.clip(np.array(img * 255), 0, 255).astype(dtype=np.uint8)
|
||||
|
||||
return img, image_mask(img)
|
||||
|
||||
def image_load_data(data: str) -> TYPE_IMAGE:
|
||||
@@ -1359,66 +1358,88 @@ def image_load_from_url(url:str) -> TYPE_IMAGE:
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
|
||||
def image_normalize(image: TYPE_IMAGE) -> TYPE_IMAGE:
|
||||
image = image.astype(np.float32)
|
||||
img_min = np.min(image)
|
||||
img_max = np.max(image)
|
||||
if img_min == img_max:
|
||||
return np.zeros_like(image, dtype=np.float32)
|
||||
image = (image - img_min) / (img_max - img_min)
|
||||
return (image * 255).astype(np.uint8)
|
||||
|
||||
def image_mask(image:TYPE_IMAGE, color:TYPE_PIXEL=255) -> TYPE_IMAGE:
|
||||
"""Returns a mask from an image or a default mask with the color."""
|
||||
cc = image.shape[2] if image.ndim == 3 else 1
|
||||
height, width = image.shape[:2]
|
||||
if cc == 4:
|
||||
return np.expand_dims(image[:,:,3], -1)
|
||||
return channel_solid(width, height, color, EnumImageType.GRAYSCALE)
|
||||
"""Create a mask from the image, preserving transparency."""
|
||||
if image.ndim == 3 and image.shape[2] == 4:
|
||||
return image[..., 3]
|
||||
return np.ones_like(image, dtype=np.uint8) * color
|
||||
|
||||
def image_mask_add(image:TYPE_IMAGE, mask:TYPE_IMAGE=None) -> TYPE_IMAGE:
|
||||
"""Places a default or custom mask into an image.
|
||||
def image_mask_add(image:TYPE_IMAGE, mask:TYPE_IMAGE=None, alpha:float=255) -> TYPE_IMAGE:
|
||||
"""Put custom mask into an image. If there is no mask, alpha is applied.
|
||||
Images are expanded to 4 channels.
|
||||
Existing 4 channel images with no mask input just return themselves.
|
||||
"""
|
||||
h, w = image.shape[:2]
|
||||
image = image_convert(image, 4)
|
||||
if mask is None:
|
||||
mask = image_mask(image)
|
||||
mask = np.full_like(image, alpha, np.uint8)
|
||||
else:
|
||||
mask = image_grayscale(mask)
|
||||
mask = image_scalefit(mask, w, h, EnumScaleMode.CROP)
|
||||
image[:,:,3] = mask[:,:,0]
|
||||
mask = image_convert(image, 1)
|
||||
image[..., 3] = mask[...,0]
|
||||
return image
|
||||
|
||||
def image_matte(image:TYPE_IMAGE, color:TYPE_PIXEL=(0,0,0,255),
|
||||
width:int=None, height:int=None, imageB:TYPE_IMAGE=None) -> TYPE_IMAGE:
|
||||
"""Puts an image atop a colored matte."""
|
||||
cc = image.shape[2] if image.ndim == 3 else 1
|
||||
h, w = image.shape[:2]
|
||||
width = width if width is not None else w
|
||||
height = height if height is not None else h
|
||||
width = max(w, width)
|
||||
height = max(h, height)
|
||||
y1 = max(0, (height - h) // 2)
|
||||
y2 = min(height, y1 + h)
|
||||
x1 = max(0, (width - w) // 2)
|
||||
x2 = min(width, x1 + w)
|
||||
if cc != 4:
|
||||
image = image_convert(image, 4)
|
||||
# save the old alpha channel
|
||||
mask_chan = image_mask(image)[:,:,0]
|
||||
if imageB is not None:
|
||||
matte = image_scalefit(matte, width, height, EnumScaleMode.FIT)
|
||||
matte = image_convert(imageB, 4)
|
||||
def image_mask_binary(image: TYPE_IMAGE) -> TYPE_IMAGE:
|
||||
"""
|
||||
Convert an image to a binary mask where non-black pixels are 1 and black pixels are 0.
|
||||
Supports BGR, single-channel grayscale, and RGBA images.
|
||||
|
||||
Args:
|
||||
image (TYPE_IMAGE): Input image in BGR, grayscale, or RGBA format.
|
||||
|
||||
Returns:
|
||||
TYPE_IMAGE: Binary mask with the same width and height as the input image, where
|
||||
pixels are 1 for non-black and 0 for black.
|
||||
"""
|
||||
if image.ndim == 2:
|
||||
# Grayscale image
|
||||
gray = image
|
||||
elif image.shape[2] == 3:
|
||||
# BGR image
|
||||
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
||||
elif image.shape[2] == 4:
|
||||
# RGBA image
|
||||
alpha_channel = image[..., 3]
|
||||
# Create a mask from the alpha channel where alpha > 0
|
||||
alpha_mask = alpha_channel > 0
|
||||
# Convert RGB to grayscale
|
||||
gray = cv2.cvtColor(image[:, :, :3], cv2.COLOR_BGR2GRAY)
|
||||
# Apply the alpha mask to the grayscale image
|
||||
gray = cv2.bitwise_and(gray, gray, mask=alpha_mask.astype(np.uint8))
|
||||
else:
|
||||
matte = channel_solid(width, height, color, EnumImageType.BGRA)
|
||||
matte[y1:y2, x1:x2, 3] = mask_chan
|
||||
alpha = cv2.bitwise_not(mask_chan)
|
||||
alpha = cv2.cvtColor(alpha, cv2.COLOR_GRAY2BGRA) / 255.0
|
||||
matte[y1:y2, x1:x2] = cv2.convertScaleAbs(image * (1 - alpha) + matte[y1:y2, x1:x2] * alpha)
|
||||
if cc == 4:
|
||||
matte[y1:y2, x1:x2, 3] = mask_chan
|
||||
raise ValueError("Unsupported image format")
|
||||
|
||||
# Create a binary mask where any non-black pixel is set to 1
|
||||
_, mask = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)
|
||||
return mask.astype(np.uint8)
|
||||
|
||||
def image_matte(image:TYPE_IMAGE, color:TYPE_PIXEL=(0, 0, 0, 255), width:int=None, height:int=None) -> TYPE_IMAGE:
|
||||
"""
|
||||
Puts an image atop a colored matte with the same dimensions as the image.
|
||||
|
||||
Args:
|
||||
image (TYPE_IMAGE): The input image.
|
||||
color (TYPE_PIXEL): The color of the matte as a tuple (R, G, B, A).
|
||||
|
||||
Returns:
|
||||
TYPE_IMAGE: The composited image on a matte.
|
||||
"""
|
||||
|
||||
# Determine the dimensions of the matte
|
||||
image_height, image_width = image.shape[:2]
|
||||
width = width or image_width
|
||||
height = height or image_height
|
||||
|
||||
# solid matte
|
||||
matte = np.full((height, width, 4), color, dtype=np.uint8)
|
||||
|
||||
# Position the image in the center of the matte
|
||||
x_offset = (width - image_width) // 2
|
||||
y_offset = (height - image_height) // 2
|
||||
|
||||
# everything 4 channel...
|
||||
image = image_convert(image, 4)
|
||||
|
||||
# Composite the image onto the matte
|
||||
matte[y_offset:y_offset + image_height, x_offset:x_offset + image_width] = image
|
||||
return matte
|
||||
|
||||
def image_merge(imageA: TYPE_IMAGE, imageB: TYPE_IMAGE, axis: int=0, flip: bool=False) -> TYPE_IMAGE:
|
||||
@@ -1496,6 +1517,15 @@ def image_mirror_mandela(imageA: np.ndarray, imageB: np.ndarray) -> Tuple[np.nda
|
||||
imageB = np.vstack([top, bottom])
|
||||
return imageA, imageB
|
||||
|
||||
def image_normalize(image: TYPE_IMAGE) -> TYPE_IMAGE:
|
||||
image = image.astype(np.float32)
|
||||
img_min = np.min(image)
|
||||
img_max = np.max(image)
|
||||
if img_min == img_max:
|
||||
return np.zeros_like(image, dtype=np.float32)
|
||||
image = (image - img_min) / (img_max - img_min)
|
||||
return (image * 255).astype(np.uint8)
|
||||
|
||||
def image_pixelate(image: TYPE_IMAGE, amount:float=1.)-> TYPE_IMAGE:
|
||||
|
||||
h, w = image.shape[:2]
|
||||
@@ -1754,17 +1784,34 @@ def image_threshold(image:TYPE_IMAGE, threshold:float=0.5,
|
||||
_, image = cv2.threshold(image, threshold, 255, mode.value)
|
||||
return bgr2image(image, alpha, cc == 1)
|
||||
|
||||
def image_translate(image: TYPE_IMAGE, offset:TYPE_COORD=(0.0, 0.0), edge:EnumEdge=EnumEdge.CLIP) -> TYPE_IMAGE:
|
||||
def image_translate(image: TYPE_IMAGE, offset: TYPE_COORD = (0.0, 0.0), edge: EnumEdge = EnumEdge.CLIP) -> TYPE_IMAGE:
|
||||
"""
|
||||
Translates an image by a given offset. Supports various edge handling methods.
|
||||
|
||||
Args:
|
||||
image (TYPE_IMAGE): Input image as a numpy array.
|
||||
offset (TYPE_COORD): Tuple (offset_x, offset_y) representing the translation offset.
|
||||
edge (EnumEdge): Enum representing edge handling method. Options are 'CLIP', 'WRAP', 'WRAPX', 'WRAPY'.
|
||||
|
||||
Returns:
|
||||
TYPE_IMAGE: Translated image.
|
||||
"""
|
||||
|
||||
def translate(img: TYPE_IMAGE) -> TYPE_IMAGE:
|
||||
height, width = img.shape[:2]
|
||||
scalarX = 0.333 if edge in [EnumEdge.WRAP, EnumEdge.WRAPX] else 1.
|
||||
scalarY = 0.333 if edge in [EnumEdge.WRAP, EnumEdge.WRAPY] else 1.
|
||||
scalarX = 0.333 if edge in [EnumEdge.WRAP, EnumEdge.WRAPX] else 1.0
|
||||
scalarY = 0.333 if edge in [EnumEdge.WRAP, EnumEdge.WRAPY] else 1.0
|
||||
|
||||
M = np.float32([[1, 0, offset[0] * width * scalarX], [0, 1, offset[1] * height * scalarY]])
|
||||
return cv2.warpAffine(img, M, (width, height), flags=cv2.INTER_LINEAR)
|
||||
if edge == EnumEdge.CLIP:
|
||||
border_mode = cv2.BORDER_CONSTANT
|
||||
border_value = 0 # You can change this value to suit your needs
|
||||
else:
|
||||
border_mode = cv2.BORDER_WRAP
|
||||
|
||||
return image_affine_edge(image, translate, edge)
|
||||
return cv2.warpAffine(img, M, (width, height), flags=cv2.INTER_LINEAR, borderMode=border_mode, borderValue=border_value)
|
||||
|
||||
return translate(image)
|
||||
|
||||
def image_transform(image: TYPE_IMAGE, offset:TYPE_COORD=(0.0, 0.0), angle:float=0, scale:TYPE_COORD=(1.0, 1.0), sample:EnumInterpolation=EnumInterpolation.LANCZOS4, edge:EnumEdge=EnumEdge.CLIP) -> TYPE_IMAGE:
|
||||
sX, sY = scale
|
||||
@@ -1889,7 +1936,7 @@ def color_match_histogram(image: TYPE_IMAGE, usermap: TYPE_IMAGE) -> TYPE_IMAGE:
|
||||
image = image_blend(usermap, image, blendOp=BlendType.LUMINOSITY)
|
||||
image = image_convert(image, cc)
|
||||
if cc == 4:
|
||||
image[:,:,3] = alpha[:,:,0]
|
||||
image[..., 3] = alpha[..., 0]
|
||||
return image
|
||||
|
||||
def color_match_reinhard(image: TYPE_IMAGE, target: TYPE_IMAGE) -> TYPE_IMAGE:
|
||||
@@ -1918,7 +1965,7 @@ def color_match_lut(image: TYPE_IMAGE, colormap:int=cv2.COLORMAP_JET,
|
||||
image = cv2.addWeighted(image, 0.5, image, 0.5, 0)
|
||||
image = image_convert(image, cc)
|
||||
if cc == 4:
|
||||
image[:,:,3] = alpha[:,:,0]
|
||||
image[..., 3] = alpha[..., 0]
|
||||
return image
|
||||
|
||||
def color_mean(image: TYPE_IMAGE) -> TYPE_IMAGE:
|
||||
@@ -1930,7 +1977,7 @@ def color_mean(image: TYPE_IMAGE) -> TYPE_IMAGE:
|
||||
else:
|
||||
# each channel....
|
||||
color = [
|
||||
int(np.mean(image[:,:,0])),
|
||||
int(np.mean(image[..., 0])),
|
||||
int(np.mean(image[:,:,1])),
|
||||
int(np.mean(image[:,:,2])) ]
|
||||
return color
|
||||
@@ -2093,7 +2140,7 @@ def remap_fisheye(image: TYPE_IMAGE, distort: float) -> TYPE_IMAGE:
|
||||
map_x, map_y = coord_fisheye(width, height, distort)
|
||||
image = cv2.remap(image, map_x, map_y, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT)
|
||||
#if cc == 1:
|
||||
# image = image[:,:,0]
|
||||
# image = image[..., 0]
|
||||
return image
|
||||
|
||||
def remap_perspective(image: TYPE_IMAGE, pts: list) -> TYPE_IMAGE:
|
||||
@@ -2104,7 +2151,7 @@ def remap_perspective(image: TYPE_IMAGE, pts: list) -> TYPE_IMAGE:
|
||||
pts = coord_perspective(width, height, pts)
|
||||
image = cv2.warpPerspective(image, pts, (width, height))
|
||||
#if cc == 1:
|
||||
# image = image[:,:,0]
|
||||
# image = image[..., 0]
|
||||
return image
|
||||
|
||||
def remap_polar(image: TYPE_IMAGE) -> TYPE_IMAGE:
|
||||
|
||||
+7
-11
@@ -47,8 +47,8 @@ PTYPE = {
|
||||
'sampler2D': EnumConvertType.IMAGE
|
||||
}
|
||||
|
||||
RE_VARIABLE = re.compile(r"uniform\s*(\w*)\s*(\w*);(?:.*\/{2}\s*([A-Za-z0-9\-\.,\s]+)){0,1}(\|[A-Za-z0-9\s]+)?$", re.MULTILINE)
|
||||
RE_SHADER_META = re.compile(r"\/\/\s(name|desc):\s([A-Za-z\s]+)$", re.MULTILINE)
|
||||
RE_VARIABLE = re.compile(r"uniform\s+(\w+)\s+(\w+);\s*\/\/\s*([0-9.,\s]*)\s*(?:;\s*([0-9.-]+))?\s*(?:;\s*([0-9.-]+))?\s*(?:;\s*([0-9.-]+))?\s*(?:\|\s*(.*))?$", re.MULTILINE)
|
||||
RE_SHADER_META = re.compile(r"\/\/\s?([A-Za-z\_]{3,}):\s?([A-Za-z\_\s]+)$", re.MULTILINE)
|
||||
|
||||
# =============================================================================
|
||||
|
||||
@@ -98,13 +98,11 @@ void main()
|
||||
def __init__(self, vertex:str=None, fragment:str=None, width:int=IMAGE_SIZE_DEFAULT, height:int=IMAGE_SIZE_DEFAULT, fps:int=30) -> None:
|
||||
if not glfw.init():
|
||||
raise RuntimeError("GLFW did not init")
|
||||
glfw.window_hint(glfw.VISIBLE, glfw.FALSE) # hidden
|
||||
glfw.window_hint(glfw.VISIBLE, glfw.FALSE)
|
||||
self.__window = glfw.create_window(width, height, "hidden", None, None)
|
||||
if not self.__window:
|
||||
raise RuntimeError("GLFW did not init window")
|
||||
glfw.make_context_current(self.__window)
|
||||
#gl.glEnable(gl.GL_BLEND)
|
||||
#gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
|
||||
|
||||
self.__size_changed = False
|
||||
self.__size: Tuple[int, int] = (max(width, IMAGE_SIZE_MIN), max(height, IMAGE_SIZE_MIN))
|
||||
@@ -131,7 +129,7 @@ void main()
|
||||
gl.glCompileShader(shader)
|
||||
if gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS) != gl.GL_TRUE:
|
||||
raise CompileException(gl.glGetShaderInfoLog(shader))
|
||||
logger.debug(f"{shader_type} compiled")
|
||||
# logger.debug(f"{shader_type} compiled")
|
||||
return shader
|
||||
|
||||
def __framebuffer(self) -> None:
|
||||
@@ -305,11 +303,11 @@ void main()
|
||||
self.__userVar = {}
|
||||
# read the fragment and setup the vars....
|
||||
for match in RE_VARIABLE.finditer(fragment):
|
||||
typ, name, default, tooltip = match.groups()
|
||||
typ, name, default, val_min, val_max, val_step, tooltip = match.groups()
|
||||
tex_loc = None
|
||||
if typ in ['sampler2D']:
|
||||
tex_loc = gl.glGenTextures(1)
|
||||
logger.debug(f"{name}.{typ}: {default}")
|
||||
logger.debug(f"{name}.{typ}: {default} {val_min} {val_max} {val_step} {tooltip}")
|
||||
self.__userVar[name] = [
|
||||
# type
|
||||
typ,
|
||||
@@ -321,9 +319,7 @@ void main()
|
||||
tex_loc
|
||||
]
|
||||
|
||||
logger.info("program changed")
|
||||
self.render()
|
||||
self.render()
|
||||
logger.info("program compiled")
|
||||
|
||||
def render(self, time_delta:float=0., **kw) -> np.ndarray:
|
||||
glfw.make_context_current(self.__window)
|
||||
|
||||
+3
-5
@@ -201,9 +201,9 @@ def parse_value(val:Any, typ:EnumConvertType, default: Any,
|
||||
d = default[idx] if isinstance(default, (list, tuple, set, dict, torch.Tensor)) and idx < len(default) else 0
|
||||
v = d if val is None else val[idx] if idx < len(val) else d
|
||||
if isinstance(v, (str, )):
|
||||
v = v.strip('\n').strip()
|
||||
if v == '':
|
||||
v = 0
|
||||
v = v.strip('\n')
|
||||
try:
|
||||
if typ in [EnumConvertType.FLOAT, EnumConvertType.VEC2, EnumConvertType.VEC3, EnumConvertType.VEC4]:
|
||||
v = round(float(v), 16)
|
||||
@@ -308,10 +308,8 @@ def parse_param(data:dict, key:str, typ:EnumConvertType, default: Any,
|
||||
elif isinstance(val, (torch.Tensor,)):
|
||||
if val.ndim > 3:
|
||||
val = [t for t in val]
|
||||
else:
|
||||
while (val.ndim < 3):
|
||||
val = val.unsqueeze(-1)
|
||||
val = [val]
|
||||
elif val.ndim == 3:
|
||||
val = [v.unsqueeze(-1) for v in val]
|
||||
elif isinstance(val, (list, tuple, set)):
|
||||
if len(val) == 0:
|
||||
val = [None]
|
||||
|
||||
@@ -72,11 +72,11 @@ app.registerExtension({
|
||||
multipleInstances: false,
|
||||
appendTo: this.config_dialog.element,
|
||||
noAlpha: false,
|
||||
init: function(elm, rgb) {
|
||||
init: function(elm, rgb) {
|
||||
elm.style.backgroundColor = elm.color || LiteGraph.WIDGET_BGCOLOR;
|
||||
elm.style.color = rgb.RGBLuminance > 0.22 ? '#222' : '#ddd'
|
||||
},
|
||||
convertCallback: function(data, options) {
|
||||
convertCallback: function(data) {
|
||||
var AHEX = this.patch.attributes.color
|
||||
if (AHEX === undefined) return
|
||||
var name = this.patch.attributes.name.value
|
||||
@@ -114,7 +114,7 @@ app.registerExtension({
|
||||
node_color_all();
|
||||
}
|
||||
},
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
async beforeRegisterNodeDef(nodeType) {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
const me = onNodeCreated?.apply(this, arguments);
|
||||
|
||||
@@ -57,11 +57,13 @@ const templateColorRegex = ({ idx, name, background, title, body }) => (
|
||||
])
|
||||
);
|
||||
|
||||
/*
|
||||
const colorClear = (name) => {
|
||||
api_post("/jovimetrix/config/clear", { name });
|
||||
delete util_config.CONFIG_THEME[name];
|
||||
if (util_config.CONFIG_COLOR.overwrite) node_color_all();
|
||||
};
|
||||
*/
|
||||
|
||||
export class JovimetrixConfigDialog extends ComfyDialog {
|
||||
constructor() {
|
||||
|
||||
@@ -14,10 +14,10 @@ const JDataBucket = (app, name, opts) => {
|
||||
type: "JDATABUCKET",
|
||||
hidden: true,
|
||||
options: options,
|
||||
draw: function (ctx, node, width, Y, height) {
|
||||
draw: function () {
|
||||
return;
|
||||
},
|
||||
computeSize: function (width) {
|
||||
computeSize: function () {
|
||||
return [0, 0];
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ const JDataBucket = (app, name, opts) => {
|
||||
|
||||
app.registerExtension({
|
||||
name: "jovimetrix.data.bucket",
|
||||
async getCustomWidgets(app) {
|
||||
async getCustomWidgets() {
|
||||
return {
|
||||
JDATABUCKET: (node, inputName, inputData, app) => ({
|
||||
widget: node.addCustomWidget(JDataBucket(app, inputName, inputData[1]))
|
||||
|
||||
@@ -58,7 +58,7 @@ app.registerExtension({
|
||||
},
|
||||
});
|
||||
},
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType) {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
const me = onNodeCreated.apply(this, arguments);
|
||||
@@ -74,7 +74,7 @@ app.registerExtension({
|
||||
},
|
||||
async nodeCreated(node) {
|
||||
const onDrawForeground = node.onDrawForeground;
|
||||
node.onDrawForeground = async function (ctx, area) {
|
||||
node.onDrawForeground = async function (ctx) {
|
||||
const me = onDrawForeground?.apply(this, arguments);
|
||||
if (this.widgets) {
|
||||
ctx.save();
|
||||
@@ -85,7 +85,9 @@ app.registerExtension({
|
||||
try {
|
||||
color = hex2rgb(g_highlight);
|
||||
color = g_highlight
|
||||
} catch { }
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
if (g_color_style == "Round Highlight") {
|
||||
const thick = Math.max(1, Math.min(3, g_thickness));
|
||||
|
||||
@@ -8,7 +8,7 @@ import { CONVERTED_TYPE, convertToInput } from '../util/util_widget.js'
|
||||
|
||||
app.registerExtension({
|
||||
name: "jovimetrix.cozy.menu",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (!nodeData.name.includes("(JOV)")) {
|
||||
return;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ app.registerExtension({
|
||||
(widget.options?.forceInput === undefined || widget.options?.forceInput === false) &&
|
||||
widget.options?.menu !== false) {
|
||||
const convertToInputObject = {
|
||||
content: `Convsert ${widget.name} to input`,
|
||||
content: `Convert ${widget.name} to input`,
|
||||
callback: () => convertToInput(this, widget, widgetType)
|
||||
};
|
||||
convertToInputArray.push(convertToInputObject);
|
||||
|
||||
@@ -16,10 +16,10 @@ const JTooltipWidget = (app, name, opts) => {
|
||||
type: "JTOOLTIP",
|
||||
hidden: true,
|
||||
options: options,
|
||||
draw: function (ctx, node, width, Y, height) {
|
||||
draw: function () {
|
||||
return;
|
||||
},
|
||||
computeSize: function (width) {
|
||||
computeSize: function () {
|
||||
return [0, 0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +344,7 @@ app.registerExtension({
|
||||
|
||||
// ? clicked
|
||||
const mouseDown = nodeType.prototype.onMouseDown
|
||||
nodeType.prototype.onMouseDown = function (e, localPos, canvas) {
|
||||
nodeType.prototype.onMouseDown = function (e, localPos) {
|
||||
const r = mouseDown ? mouseDown.apply(this, arguments) : undefined
|
||||
const iconX = this.size[0] - iconSize - iconMargin
|
||||
const iconY = iconSize - 34
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ const _id = "ADJUST (JOV) 🕸️"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -50,13 +50,8 @@ app.registerExtension({
|
||||
if (message.text != null) {
|
||||
let new_val = message.text.map((txt, index) => `${index}: ${txt}`).join('\n');
|
||||
this.message.value = new_val;
|
||||
for (let char of new_val) {
|
||||
if (char === '\n') {
|
||||
lineCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
//fitHeight(this);
|
||||
// fitHeight(this);
|
||||
return me;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ const _prefix = '❔'
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ const _id = "BLEND (JOV) ⚗️"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const _id = "OP BINARY (JOV) 🌟"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange
|
||||
nodeType.prototype.onConnectionsChange = function (slotType, slot, event, link_info, data) {
|
||||
nodeType.prototype.onConnectionsChange = function (slotType) {
|
||||
if (slotType === TypeSlot.Input) {
|
||||
const widget_combo = this.widgets.find(w => w.name === '❓');
|
||||
setTimeout(() => { widget_combo.callback(); }, 10);
|
||||
|
||||
@@ -12,7 +12,7 @@ const _id = "COLOR MATCH (JOV) 💞"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const _id = "COLOR THEORY (JOV) 🛞"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const _id = "CONSTANT (JOV) 🟪"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ const _id = "CROP (JOV) ✂️"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
|
||||
+3
-3
@@ -67,21 +67,21 @@ app.registerExtension({
|
||||
// app.canvas.setDirty(true);
|
||||
}
|
||||
|
||||
async function python_delay_update(event) {
|
||||
async function python_delay_update() {
|
||||
}
|
||||
|
||||
api.addEventListener(EVENT_JOVI_DELAY, python_delay_user);
|
||||
api.addEventListener(EVENT_JOVI_UPDATE, python_delay_update);
|
||||
|
||||
this.onDestroy = () => {
|
||||
api.removeEventListener(EVENT_JOVI_DELAY, python_glsl_error);
|
||||
api.removeEventListener(EVENT_JOVI_DELAY, python_delay_user);
|
||||
api.removeEventListener(EVENT_JOVI_UPDATE, python_delay_update);
|
||||
};
|
||||
return me;
|
||||
}
|
||||
|
||||
const onExecutionStart = nodeType.prototype.onExecutionStart
|
||||
nodeType.prototype.onExecutionStart = function (message) {
|
||||
nodeType.prototype.onExecutionStart = function() {
|
||||
onExecutionStart?.apply(this, arguments);
|
||||
self.total_timeout = 0;
|
||||
}
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ const _id = "EXPORT (JOV) 📽"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const _id = "FILTER MASK (JOV) 🤿"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ const _prefix = '👾'
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
|
||||
+2
-3
@@ -8,14 +8,13 @@ import { api } from "../../../scripts/api.js";
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { fitHeight } from '../util/util.js'
|
||||
import { widget_hide, widget_show } from '../util/util_widget.js';
|
||||
import { api_post, api_cmd_jovian } from '../util/util_api.js';
|
||||
import { api_cmd_jovian } from '../util/util_api.js';
|
||||
import { flashBackgroundColor } from '../util/util_fun.js';
|
||||
|
||||
const _id = "GLSL (JOV) 🍩";
|
||||
const EVENT_JOVI_GLSL_ERROR = "jovi-glsl-error";
|
||||
const EVENT_JOVI_GLSL_TIME = "jovi-glsl-time";
|
||||
const EVENT_JOVI_GLSL_REGISTER = "jovi-register-glsl";
|
||||
const RE_VARIABLE = /uniform\s*(\w*)\s*(\w*);(?:.*\/{2}\s*([A-Za-z0-9\-\.,\s]+)){0,1}\s*$/gm
|
||||
const RE_VARIABLE = /uniform\s+(\w+)\s+(\w+);\s*\/\/\s*([0-9.,\s]*)\s*(?:;\s*([0-9.-]+))?\s*(?:;\s*([0-9.-]+))?\s*(?:;\s*([0-9.-]+))?\s*(?:\|\s*(.*))?$/gm
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
|
||||
@@ -12,7 +12,6 @@ import { api_cmd_jovian } from '../util/util_api.js';
|
||||
|
||||
const _id = "GLSL DYNAMIC (JOV) 🧙🏽";
|
||||
const EVENT_JOVI_GLSL_TIME = "jovi-glsl-time";
|
||||
const RE_VARIABLE = /uniform\s*(\w*)\s*(\w*);(?:.*\/{2}\s*([A-Za-z0-9\-\.,\s]+)){0,1}\s*$/gm
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
|
||||
@@ -11,7 +11,7 @@ const _id = "GRADIENT MAP (JOV) 🇲🇺"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
|
||||
+2
-2
@@ -33,7 +33,7 @@ app.registerExtension({
|
||||
const me = onNodeCreated?.apply(this);
|
||||
const self = this;
|
||||
const widget_reset = this.widgets.find(w => w.name === 'RESET');
|
||||
widget_reset.callback = async (e) => {
|
||||
widget_reset.callback = async() => {
|
||||
widget_reset.value = false;
|
||||
api_cmd_jovian(self.id, "reset");
|
||||
}
|
||||
@@ -41,7 +41,7 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange
|
||||
nodeType.prototype.onConnectionsChange = function (slotType, slot, event, link_info, data) {
|
||||
nodeType.prototype.onConnectionsChange = function (slotType, slot, event, link_info) {
|
||||
const me = onConnectionsChange?.apply(this, arguments);
|
||||
if (!link_info || slot == this.inputs.length) {
|
||||
return;
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ const _id = "LERP (JOV) 🔰"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const _id = "PIXEL MERGE (JOV) 🫂"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const _id = "PIXEL SWAP (JOV) 🔃"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return
|
||||
}
|
||||
|
||||
+38
-41
@@ -49,7 +49,7 @@ app.registerExtension({
|
||||
const widget_hold = this.widgets.find(w => w.name === '✋🏽');
|
||||
const widget_reset = this.widgets.find(w => w.name === 'RESET');
|
||||
const widget_value = this.widgets.find(w => w.name === 'VAL');
|
||||
widget_value.callback = async (e) => {
|
||||
widget_value.callback = async() => {
|
||||
widget_hide(this, widget_hold, '-jov');
|
||||
widget_hide(this, widget_reset, '-jov');
|
||||
if (widget_value.value == 0) {
|
||||
@@ -59,12 +59,12 @@ app.registerExtension({
|
||||
fitHeight(this);
|
||||
}
|
||||
|
||||
widget_queue?.inputEl.addEventListener('input', function (event) {
|
||||
widget_queue?.inputEl.addEventListener('input', function () {
|
||||
const value = widget_queue.value.split('\n');
|
||||
update_list(self, value);
|
||||
});
|
||||
|
||||
widget_reset.callback = async (e) => {
|
||||
widget_reset.callback = async() => {
|
||||
widget_reset.value = false;
|
||||
api_cmd_jovian(self.id, "reset");
|
||||
}
|
||||
@@ -92,9 +92,11 @@ app.registerExtension({
|
||||
if (event.detail.id != self.id) {
|
||||
return;
|
||||
}
|
||||
/*
|
||||
let centerX = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
||||
let centerY = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
|
||||
// util_fun.bewm(centerX / 2, centerY / 3);
|
||||
util_fun.bewm(centerX / 2, centerY / 3);
|
||||
*/
|
||||
await flashBackgroundColor(self.widget_queue.inputEl, 650, 4, "#995242CC");
|
||||
}
|
||||
|
||||
@@ -111,52 +113,47 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
const onConnectOutput = nodeType.prototype.onConnectOutput;
|
||||
nodeType.prototype.onConnectOutput = function(outputIndex, inputType, inputSlot, inputNode, inputIndex) {
|
||||
if (outputIndex == 0) {
|
||||
if (inputType == "COMBO") {
|
||||
// can link the "same" list -- user breaks it past that, their problem atm.
|
||||
const widget = inputNode.widgets.find(w => w.name === inputSlot.name);
|
||||
if (this.outputs[0].name != _prefix && this.widget_queue.value != widget.options.values.join('\n')) {
|
||||
return false;
|
||||
}
|
||||
nodeType.prototype.onConnectOutput = function(outputIndex, inputType, inputSlot, inputNode) {
|
||||
if (outputIndex == 0 && inputType == "COMBO") {
|
||||
// can link the "same" list -- user breaks it past that, their problem atm.
|
||||
|
||||
const widget_queue = this.widgets.find(w => w.name === 'Q');
|
||||
const widget = inputNode.widgets.find(w => w.name === inputSlot.name);
|
||||
const values = widget.options.values.join('\n');
|
||||
if (this.outputs[0].name != _prefix && widget_queue.value != values) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return onConnectOutput?.apply(this, arguments);
|
||||
}
|
||||
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
||||
nodeType.prototype.onConnectionsChange = function (slotType, slot, event, link_info, data)
|
||||
nodeType.prototype.onConnectionsChange = function (slotType, slot, event, link_info)
|
||||
//side, slot, connected, link_info
|
||||
{
|
||||
if (slotType === TypeSlot.Output && slot == 0) {
|
||||
if (link_info){
|
||||
if (event === TypeSlotEvent.Connect) {
|
||||
const node = app.graph.getNodeById(link_info.target_id);
|
||||
if (node === undefined || node.inputs === undefined) {
|
||||
return;
|
||||
}
|
||||
const target = node.inputs[link_info.target_slot];
|
||||
if (target === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const widget = node.widgets?.find(w => w.name === target.name);
|
||||
if (widget === undefined) {
|
||||
return;
|
||||
}
|
||||
this.outputs[0].name = widget.name;
|
||||
if (widget?.origType == "combo" || widget.type == "COMBO") {
|
||||
const values = widget.options.values;
|
||||
// remove all connections that don't match the list?
|
||||
this.widget_queue.value = values.join('\n');
|
||||
update_list(this, values);
|
||||
}
|
||||
} else {
|
||||
this.outputs[0].name = _prefix;
|
||||
}
|
||||
} else {
|
||||
this.outputs[0].name = _prefix;
|
||||
if (slotType === TypeSlot.Output && slot == 0 && link_info && event === TypeSlotEvent.Connect) {
|
||||
const node = app.graph.getNodeById(link_info.target_id);
|
||||
if (node === undefined || node.inputs === undefined) {
|
||||
return;
|
||||
}
|
||||
const target = node.inputs[link_info.target_slot];
|
||||
if (target === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const widget = node.widgets?.find(w => w.name === target.name);
|
||||
if (widget === undefined) {
|
||||
return;
|
||||
}
|
||||
this.outputs[0].name = widget.name;
|
||||
if (widget?.origType == "combo" || widget.type == "COMBO") {
|
||||
const values = widget.options.values;
|
||||
const widget_queue = this.widgets.find(w => w.name === 'Q');
|
||||
// remove all connections that don't match the list?
|
||||
widget_queue.value = values.join('\n');
|
||||
update_list(this, values);
|
||||
}
|
||||
this.outputs[0].name = _prefix;
|
||||
}
|
||||
return onConnectionsChange?.apply(this, arguments);
|
||||
};
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ const _prefix = '🔮'
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ app.registerExtension({
|
||||
nodeType = node_add_dynamic_route(nodeType, _prefix);
|
||||
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange
|
||||
nodeType.prototype.onConnectionsChange = function (slotType, slot_idx, event, link_info, node_slot) {
|
||||
nodeType.prototype.onConnectionsChange = function (slotType, slot_idx, event, link_info) {
|
||||
const me = onConnectionsChange?.apply(this, arguments);
|
||||
if (slot_idx == 0) {
|
||||
if (event === TypeSlotEvent.Connect && slotType === TypeSlot.Input && link_info) {
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ const _prefix = '❔'
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ app.registerExtension({
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const me = onNodeCreated?.apply(this)
|
||||
const widget_reset = this.widgets.find(w => w.name === 'RESET');
|
||||
widget_reset.callback = async (e) => {
|
||||
widget_reset.callback = async() => {
|
||||
widget_reset.value = false;
|
||||
api_cmd_jovian(self.id, "reset");
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const _id = "SHAPE GEN (JOV) ✨"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange
|
||||
nodeType.prototype.onConnectionsChange = function (slotType, slot, event, link_info, data) {
|
||||
nodeType.prototype.onConnectionsChange = function (slotType, slot) {
|
||||
if (slotType === TypeSlot.Input && slot.name == 'SHAPE') {
|
||||
const widget_combo = this.widgets.find(w => w.name === 'SHAPE');
|
||||
setTimeout(() => { widget_combo.callback(); }, 10);
|
||||
@@ -43,7 +43,7 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
nodeType.prototype.onExecuted = function () {
|
||||
const widget_combo = this.widgets.find(w => w.name === 'SHAPE');
|
||||
if (widget_combo.value == 'SHAPE') {
|
||||
setTimeout(() => { widget_combo.callback(); }, 10);
|
||||
|
||||
@@ -11,7 +11,7 @@ const _id = "SPOUT WRITER (JOV) 🎥"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ const _prefix = '👾'
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ const _id = "STREAM READER (JOV) 📺"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const _id = "STREAM WRITER (JOV) 🎞️"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ const _id = "SWIZZLE (JOV) 😵"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const _id = "TEXT GEN (JOV) 📝"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return
|
||||
}
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ const EVENT_JOVI_TICK = "jovi-tick";
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
@@ -22,7 +22,7 @@ app.registerExtension({
|
||||
const me = onNodeCreated?.apply(this);
|
||||
const self = this;
|
||||
const widget_reset = this.widgets.find(w => w.name === 'RESET');
|
||||
widget_reset.callback = async (e) => {
|
||||
widget_reset.callback = async() => {
|
||||
widget_reset.value = false;
|
||||
api_cmd_jovian(self.id, "reset");
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ const _id = "TRANSFORM (JOV) 🏝️"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
|
||||
+2
-3
@@ -6,14 +6,14 @@
|
||||
|
||||
import { app } from "../../../scripts/app.js"
|
||||
import { hook_widget_AB } from '../util/util_jov.js'
|
||||
import { fitHeight, TypeSlot } from '../util/util.js'
|
||||
import { fitHeight } from '../util/util.js'
|
||||
import { widget_hide, process_any, widget_type_name } from '../util/util_widget.js'
|
||||
|
||||
const _id = "VALUE (JOV) 🧬"
|
||||
|
||||
app.registerExtension({
|
||||
name: 'jovimetrix.node.' + _id,
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== _id) {
|
||||
return;
|
||||
}
|
||||
@@ -22,7 +22,6 @@ app.registerExtension({
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const me = onNodeCreated?.apply(this);
|
||||
|
||||
const widget_rng = this.widgets.find(w => w.name === 'seed');
|
||||
const widget_str = this.widgets.find(w => w.name === '📝');
|
||||
|
||||
this.outputs[1].type = "*";
|
||||
|
||||
+1
-21
@@ -89,7 +89,7 @@ export function node_add_dynamic(nodeType, prefix, dynamic_type='*', index_start
|
||||
while (idx < self.inputs.length-1) {
|
||||
const slot = self.inputs[idx];
|
||||
const parts = slot.name.split('_');
|
||||
if (parts.length == 2) {
|
||||
if (parts.length == 2 && self.graph) {
|
||||
if (slot.link == null) {
|
||||
if (match_output) {
|
||||
self.removeOutput(idx);
|
||||
@@ -290,23 +290,3 @@ export function showModal(innerHTML, eventCallback, timeout=null) {
|
||||
//}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* wraps a single text line into maxWidth chunks
|
||||
*/
|
||||
function wrapText(text, maxWidth = 145) {
|
||||
const words = text.split(' ');
|
||||
const lines = [];
|
||||
let currentLine = '';
|
||||
for (const word of words) {
|
||||
const potentialLine = currentLine ? `${currentLine} ${word}` : word;
|
||||
if (potentialLine.length <= maxWidth) {
|
||||
currentLine = potentialLine;
|
||||
} else {
|
||||
if (currentLine) lines.push(currentLine);
|
||||
currentLine = word;
|
||||
}
|
||||
}
|
||||
if (currentLine) lines.push(currentLine);
|
||||
return lines;
|
||||
}
|
||||
@@ -29,7 +29,7 @@ export function hook_widget_size_mode(node, wh_hide=true) {
|
||||
|
||||
export function hook_widget_size_mode2(nodeType, wh_hide=true) {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
nodeType.prototype.onNodeCreated = function (node) {
|
||||
const me = onNodeCreated?.apply(this);
|
||||
const wh = widget_find(node.widgets, '🇼🇭');
|
||||
const samp = widget_find(node.widgets, '🎞️');
|
||||
@@ -84,7 +84,7 @@ export function hook_widget_AB(node, control_key, match_output=-1) {
|
||||
widget.options.menu = false;
|
||||
widget.callback = () => {
|
||||
if (widget.type === "toggle") {
|
||||
trackKey[0] = 1 ? widget.value : 0;
|
||||
trackKey[0] = widget.value ? 1 : 0;
|
||||
} else {
|
||||
Object.keys(widget.value).forEach((key) => {
|
||||
trackKey[key] = widget.value[key];
|
||||
|
||||
@@ -77,7 +77,7 @@ export function widget_remove_all(node) {
|
||||
for (const w of node.widgets) {
|
||||
widget_remove(node, w);
|
||||
}
|
||||
who.widgets.length = 0;
|
||||
node.widgets.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
/**
|
||||
File: widget_jimage.js
|
||||
Project: Jovimetrix
|
||||
|
||||
pythongossss to the rescue again
|
||||
original: https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/web/js/betterCombos.js
|
||||
*/
|
||||
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { ComfyWidgets } from "../../../scripts/widgets.js";
|
||||
import { $el } from "../../../scripts/ui.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "jovimetrix.widget.combo",
|
||||
init() {
|
||||
const splitBy = /\//;
|
||||
|
||||
$el("style", {
|
||||
textContent: `
|
||||
.litemenu-entry:hover .pysssss-combo-image {
|
||||
display: block;
|
||||
}
|
||||
.pysssss-combo-image {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
transform: translate(-100%, 0);
|
||||
width: 384px;
|
||||
height: 384px;
|
||||
background-size: contain;
|
||||
background-position: top right;
|
||||
background-repeat: no-repeat;
|
||||
filter: brightness(65%);
|
||||
}
|
||||
`,
|
||||
parent: document.body,
|
||||
});
|
||||
|
||||
function buildMenu(widget, values) {
|
||||
const lookup = {
|
||||
"": { options: [] },
|
||||
};
|
||||
|
||||
// Split paths into menu structure
|
||||
for (let value of values) {
|
||||
value = String(value);
|
||||
const split = value.split(splitBy);
|
||||
let path = "";
|
||||
for (let i = 0; i < split.length; i++) {
|
||||
const s = split[i];
|
||||
const last = i === split.length - 1;
|
||||
if (last) {
|
||||
// Leaf node, manually add handler that sets the lora
|
||||
lookup[path].options.push({
|
||||
...value,
|
||||
title: s,
|
||||
callback: () => {
|
||||
widget.value = value;
|
||||
widget.callback(value);
|
||||
app.graph.setDirtyCanvas(true);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const prevPath = path;
|
||||
path += s + splitBy;
|
||||
if (!lookup[path]) {
|
||||
const sub = {
|
||||
title: s,
|
||||
submenu: {
|
||||
options: [],
|
||||
title: s,
|
||||
},
|
||||
};
|
||||
|
||||
// Add to tree
|
||||
lookup[path] = sub.submenu;
|
||||
lookup[prevPath].options.push(sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return lookup[""].options;
|
||||
}
|
||||
|
||||
// Override COMBO widgets to patch their values
|
||||
const combo = ComfyWidgets["COMBO"];
|
||||
ComfyWidgets["COMBO"] = function (node) {
|
||||
const res = combo.apply(this, arguments);
|
||||
let value = res.widget.value;
|
||||
return res;
|
||||
if (value !== 'combo+') {
|
||||
return res;
|
||||
}
|
||||
let values = res.widget.options.values;
|
||||
res.widget.value = values[0];
|
||||
let menu = null;
|
||||
// Override the option values to check if we should render a menu structure
|
||||
Object.defineProperty(res.widget.options, "values", {
|
||||
get() {
|
||||
let v = values;
|
||||
if (!menu) {
|
||||
// Only build the menu once
|
||||
menu = buildMenu(res.widget, values);
|
||||
}
|
||||
v = menu;
|
||||
|
||||
const valuesIncludes = v.includes;
|
||||
v.includes = function (searchElement) {
|
||||
const includesFromMenuItem = function (item) {
|
||||
return includesFromMenuItems(item.submenu.options)
|
||||
}
|
||||
const includesFromMenuItems = function (items) {
|
||||
for (const item of items) {
|
||||
if (includesFromMenuItem(item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const includes = valuesIncludes.apply(this, arguments) || includesFromMenuItems(this);
|
||||
return includes;
|
||||
}
|
||||
|
||||
return v;
|
||||
},
|
||||
set(v) {
|
||||
// Options are changing (refresh) so reset the menu so it can be rebuilt if required
|
||||
values = v;
|
||||
menu = null;
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(res.widget, "value", {
|
||||
get() {
|
||||
return value;
|
||||
},
|
||||
set(v) {
|
||||
if (v?.submenu) {
|
||||
// Dont allow selection of submenus
|
||||
return;
|
||||
}
|
||||
value = v;
|
||||
},
|
||||
});
|
||||
return res;
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* File: widget_jimage.js
|
||||
* Project: Jovimetrix
|
||||
*/
|
||||
|
||||
import { app } from "../../../scripts/app.js"
|
||||
import { offsetDOMWidget } from '../util/util_dom.js'
|
||||
|
||||
export const JImageWidget = (app, name, value) => {
|
||||
const w = {
|
||||
name: name,
|
||||
type: "JIMAGE",
|
||||
value: value,
|
||||
draw: function (ctx, node, widgetWidth, widgetY, height) {
|
||||
const [cw, ch] = this.computeSize(widgetWidth)
|
||||
offsetDOMWidget(this, ctx, node, widgetWidth, widgetY, ch)
|
||||
},
|
||||
computeSize: function (width) {
|
||||
const ratio = this.inputRatio || 1
|
||||
if (width) {
|
||||
return [width, width / ratio + 4]
|
||||
}
|
||||
return [128, 128]
|
||||
},
|
||||
onRemoved: function () {
|
||||
if (this.inputEl) {
|
||||
this.inputEl.remove()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
w.inputEl = document.createElement('img')
|
||||
w.inputEl.src = w.value
|
||||
w.inputEl.onload = function () {
|
||||
w.inputRatio = w.inputEl.naturalWidth / w.inputEl.naturalHeight
|
||||
}
|
||||
document.body.appendChild(w.inputEl)
|
||||
return w
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "jovimetrix.widget.jimage",
|
||||
async getCustomWidgets(app) {
|
||||
return {
|
||||
JIMAGE: (node, inputName, inputData, app) => ({
|
||||
widget: node.addCustomWidget(JImageWidget(app, inputName, inputData[0])),
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* File: widget_jlabel.js
|
||||
* Project: Jovimetrix
|
||||
*/
|
||||
|
||||
import { app } from "../../../scripts/app.js"
|
||||
// import * as util from '../util/util.js'
|
||||
// import { offsetDOMWidget } from '../util/util_dom.js'
|
||||
|
||||
export const JLabelWidget = (label) => {
|
||||
const widget = {
|
||||
value: label,
|
||||
type: "JLABEL",
|
||||
options: {
|
||||
serialize: false,
|
||||
}
|
||||
};
|
||||
|
||||
widget.draw = function(ctx, node, widget_width, y, widget_height) {
|
||||
|
||||
}
|
||||
|
||||
widget.mouse = function(event, pos, node) {
|
||||
|
||||
},
|
||||
|
||||
widget.computeSize = function() {
|
||||
return [0, 20];
|
||||
}
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "jovimetrix.widget.jlabel",
|
||||
async getCustomWidgets(app) {
|
||||
return {
|
||||
JLABEL: (node, inputName, inputData, app) => ({
|
||||
widget: node.addCustomWidget(JLabelWidget(app, inputName, inputData[0])),
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,99 +0,0 @@
|
||||
/**
|
||||
* File: widget_jstring.js
|
||||
* Project: Jovimetrix
|
||||
*/
|
||||
|
||||
import { app } from "../../../scripts/app.js"
|
||||
import { offsetDOMWidget } from '../util/util_dom.js'
|
||||
|
||||
const withFont = (ctx, font, cb) => {
|
||||
const oldFont = ctx.font
|
||||
ctx.font = font
|
||||
cb()
|
||||
ctx.font = oldFont
|
||||
}
|
||||
|
||||
const calculateTextDimensions = (ctx, value, width, fontSize = 12) => {
|
||||
const words = value.split(' ')
|
||||
const lines = []
|
||||
let currentLine = ''
|
||||
for (const word of words) {
|
||||
const testLine = currentLine.length === 0 ? word : `${currentLine} ${word}`
|
||||
const testWidth = ctx.measureText(testLine).width
|
||||
if (testWidth > width) {
|
||||
lines.push(currentLine)
|
||||
currentLine = word
|
||||
} else {
|
||||
currentLine = testLine
|
||||
}
|
||||
}
|
||||
if (lines.length === 0) lines.push(value)
|
||||
const textHeight = (lines.length + 1) * fontSize
|
||||
const maxLineWidth = lines.reduce(
|
||||
(maxWidth, line) => Math.max(maxWidth, ctx.measureText(line).width),
|
||||
0
|
||||
)
|
||||
return { textHeight, maxLineWidth }
|
||||
}
|
||||
|
||||
export const JStringWidget = (app, name, value) => {
|
||||
const fontSize = 16
|
||||
const w = {
|
||||
name: name,
|
||||
type: "JSTRING",
|
||||
value: value,
|
||||
draw: function (ctx, node, widgetWidth, widgetY, height) {
|
||||
offsetDOMWidget(this, ctx, node, widgetWidth, widgetY, height)
|
||||
},
|
||||
computeSize(width) {
|
||||
if (!this.value) {
|
||||
return [32, 32]
|
||||
}
|
||||
if (!width) {
|
||||
console.error(`No width ${this.parent.size}`)
|
||||
}
|
||||
let dimensions
|
||||
withFont(app.ctx, `${fontSize}px`, () => {
|
||||
dimensions = calculateTextDimensions(app.ctx, this.value, width)
|
||||
})
|
||||
const widgetWidth = Math.max(width || this.width || 32, dimensions.maxLineWidth)
|
||||
const widgetHeight = dimensions.textHeight * 1.5
|
||||
return [widgetWidth, widgetHeight]
|
||||
},
|
||||
onRemoved: function () {
|
||||
if (this.inputEl) {
|
||||
this.inputEl.remove()
|
||||
}
|
||||
},
|
||||
get value() {
|
||||
return this.inputEl.innerHTML
|
||||
},
|
||||
set value(val) {
|
||||
this.inputEl.innerHTML = val
|
||||
this.parent?.setSize?.(this.parent?.computeSize())
|
||||
},
|
||||
}
|
||||
|
||||
w.inputEl = document.createElement('p')
|
||||
w.inputEl.style = `
|
||||
text-align: center;
|
||||
font-size: ${fontSize}px;
|
||||
color: var(--input-text);
|
||||
line-height: 0;
|
||||
font-family: monospace;
|
||||
`
|
||||
// w.value = val
|
||||
document.body.appendChild(w.inputEl)
|
||||
return w
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "jovimetrix.widget.jstring",
|
||||
async getCustomWidgets(app) {
|
||||
return {
|
||||
JSTRING: (node, inputName, inputData, app) => ({
|
||||
widget: node.addCustomWidget(JStringWidget(app, inputName, inputData[0])),
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { app } from "../../../scripts/app.js"
|
||||
import { CONVERTED_TYPE, convertToInput } from '../util/util_widget.js'
|
||||
import { convertToInput } from '../util/util_widget.js'
|
||||
import { inner_value_change } from '../util/util_dom.js'
|
||||
import { hex2rgb, rgb2hex } from '../util/util_color.js'
|
||||
import { $el } from "../../../scripts/ui.js"
|
||||
@@ -167,7 +167,9 @@ export const VectorWidget = (app, inputName, options, initial, desc='') => {
|
||||
if (/^[0-9+\-*/()\s]+|\d+\.\d+$/.test(v)) {
|
||||
try {
|
||||
v = eval(v);
|
||||
} catch (err) {}
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
}
|
||||
if (this.value[idx] != v) {
|
||||
setTimeout(
|
||||
@@ -219,6 +221,15 @@ app.registerExtension({
|
||||
}),
|
||||
VEC4: (node, inputName, inputData, app) => ({
|
||||
widget: node.addCustomWidget(VectorWidget(app, inputName, inputData, [0, 0, 0, 0])),
|
||||
}),
|
||||
VEC2INT: (node, inputName, inputData, app) => ({
|
||||
widget: node.addCustomWidget(VectorWidget(app, inputName, inputData, [0, 0])),
|
||||
}),
|
||||
VEC3INT: (node, inputName, inputData, app) => ({
|
||||
widget: node.addCustomWidget(VectorWidget(app, inputName, inputData, [0, 0, 0])),
|
||||
}),
|
||||
VEC4INT: (node, inputName, inputData, app) => ({
|
||||
widget: node.addCustomWidget(VectorWidget(app, inputName, inputData, [0, 0, 0, 0])),
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user