js linting pass

removed dead widgets
30% speed up on init
This commit is contained in:
Alexander G. Morano
2024-07-25 22:44:10 -07:00
parent 130d605473
commit 87d294a8ff
64 changed files with 464 additions and 681 deletions
+3 -1
View File
@@ -12,4 +12,6 @@ ignore.txt
*.bak
checkpoints
results
backup
backup
node_modules
*-lock.json
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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."""
+11
View File
@@ -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
View File
@@ -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",
+17
View File
@@ -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"
}
}
+1 -1
View File
@@ -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;
+3 -3
View File
@@ -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);
}
+45
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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]
+3 -3
View File
@@ -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);
+2
View File
@@ -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() {
+3 -3
View File
@@ -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]))
+5 -3
View File
@@ -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));
+2 -2
View File
@@ -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);
+2 -2
View File
@@ -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];
}
}
+1 -1
View File
@@ -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
View File
@@ -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
}
+1 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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
}
+1 -1
View File
@@ -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
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+1 -1
View File
@@ -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
}
+1 -1
View File
@@ -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
View File
@@ -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,
-1
View File
@@ -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,
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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");
}
+3 -3
View File
@@ -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);
+1 -1
View File
@@ -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
View File
@@ -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;
}
+1 -1
View File
@@ -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
}
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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
}
+1 -1
View File
@@ -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
View File
@@ -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");
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+2 -2
View File
@@ -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];
+1 -1
View File
@@ -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;
}
}
-149
View File
@@ -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;
};
},
});
-50
View File
@@ -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])),
})
}
}
})
-43
View File
@@ -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])),
})
}
}
})
-99
View File
@@ -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])),
})
}
}
})
+13 -2
View File
@@ -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])),
})
}
},