diff --git a/README.md b/README.md index 074c96a..a72cc63 100644 --- a/README.md +++ b/README.md @@ -62,25 +62,40 @@ If those nodes have descriptions written in HTML or Markdown, they will be conve ## UPDATES -**2024/09/04**: +**2024/09/05** @1.2.35: +* `VALUE` Node defaults fixed on save +* `Colorizer` Panel is a undergoing major re-constructive surgery +* Allow slice reversal in `ARRAY` Node +* `GLSL` Nodes allow for `IMAGE or MASK` input for RGB(A) +* NOTE ADJUSTED VERSION NUMBERS TO SHOW OLDEST COMFYUI and FRONTEND VERSIONS SUPPORTED: + * ComfyUI 0.1.3+ + * ComfyUI Frontend 1.2.30+ + +**2024/09/04** @1.2.34: * Import change for chromium tab crash * Added ComfyUI default "tooltips" as last fallback for help docs -* Supports ComfyUI 0.2.1+, frontend 1.2.45+ +* Supports Versions: + * ComfyUI 0.1.3+ + * ComfyUI Frontend 1.2.30+ -**2024/09/03**: +**2024/09/03** @1.2.33: * New `QUEUE TOO` Node focused on efficient image media loading. * Better reporting in `AKASHIC` Node for core ComfyUI types. * `MODE` setting for most nodes has been defaulted to `MATTE`. The older `NONE` setting has been removed. * Thanks to [christian-byrne](https://github.com/christian-byrne) for squashing a bug in [the help sidebar!](https://github.com/Amorano/Jovimetrix/pull/55) * Thanks to [Ainaemaet](https://github.com/Ainaemaet) for cleaning up the `STREAM READER` Node device list [when no devices are present](https://github.com/Amorano/Jovimetrix/pull/53)! -* Supports ComfyUI 0.2.0+, frontend 1.2.45+ +* Supports Versions: + * ComfyUI 0.1.3+ + * ComfyUI Frontend 1.2.30+ -**2024/08/31**: +**2024/08/31** @1.2.32: * Better MASK/ALPHA support for `BLEND`, `ADJUST` and `QUEUE` * Cleaner Markdown outputs -* Supports ComfyUI 0.1.3+, frontend 1.2.41+ +* Supports Versions: + * ComfyUI 0.1.3+ + * ComfyUI Frontend 1.2.30+ -**2024/08/28**: +**2024/08/28** @1.2.31: * New `STRINGER` Node for string operations: Split, Join, Replace and Slice. @@ -94,25 +109,35 @@ If those nodes have descriptions written in HTML or Markdown, they will be conve ![QUEUE NODE](https://github.com/user-attachments/assets/9686b900-24a2-46ab-88ba-9e3c929b439c) -* Supports ComfyUI 0.1.3+, frontend 1.2.39+ +* Supports Versions: + * ComfyUI 0.1.3+ + * ComfyUI Frontend 1.2.30+ -**2024/08/25**: +**2024/08/25** @1.2.30: * Added conversion coercion for Mixlab Layer types ![Mixlab supports](https://github.com/user-attachments/assets/05a53b98-b620-4743-b7b5-26da4140d443) -* Supports ComfyUI 0.1.2+, frontend 1.2.34+ +* Supports Versions: + * ComfyUI 0.1.3+ + * ComfyUI Frontend 1.2.30+ -**2024/08/24**: +**2024/08/24** @1.2.29: * All node dynamic help * Array node -- updated to return single values as their single value, not a list -* Supports ComfyUI 0.1.2+, frontend 1.2.30+ +* Supports Versions: + * ComfyUI 0.1.3+ + * ComfyUI Frontend 1.2.30+ -**2024/08/23**: +**2024/08/23** @1.2.28: * Colorization and Help panel only work on new frontend -* Supports ComfyUI 0.1.1+, frontend 1.2.29+ +* Supports Versions: + * ComfyUI 0.1.3+ + * ComfyUI Frontend 1.2.30+ -**2024/08/20**: +**2024/08/20** @1.2.14: * Complete Wiki and Examples revamp. -* Supports ComfyUI 0.0.8+, frontend 1.2.30+ +* Supports Versions: + * ComfyUI 0.1.3+ + * ComfyUI Frontend 1.2.30+ # INSTALLATION diff --git a/__init__.py b/__init__.py index efbed9c..9cd8852 100644 --- a/__init__.py +++ b/__init__.py @@ -34,7 +34,7 @@ StreamReaderNode, StreamWriterNode, SpoutWriter, AkashicNode, ArrayNode, BatchLoadNode, DynamicNode, ValueGraphNode, ExportNode, QueueNode, RouteNode, SaveOutputNode -@version: 1.2.32 +@version: 1.2.35 """ import os diff --git a/core/calc.py b/core/calc.py index cef4c83..8a2b82e 100644 --- a/core/calc.py +++ b/core/calc.py @@ -969,7 +969,6 @@ def run(self, **kw) -> Tuple[bool]: 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): - # logger.debug((raw, r_x, r_y, r_z, r_w, typ, xyzw, seed, yyzw, x_str)) typ = EnumConvertType[typ] default = [x_str] default2 = None @@ -1016,6 +1015,8 @@ def run(self, **kw) -> Tuple[bool]: ret.extend(extra) results.append(ret) pbar.update_absolute(idx) + if len(results) < 2: + return results[0] return [x for x in zip(*results)] class WaveGeneratorNode(JOVBaseNode): diff --git a/core/create_glsl.py b/core/create_glsl.py index 3959745..97ebcbd 100644 --- a/core/create_glsl.py +++ b/core/create_glsl.py @@ -18,7 +18,7 @@ pass from comfy.utils import ProgressBar -from Jovimetrix import JOVImageNode, comfy_message, deep_merge, Lexicon, ROOT +from Jovimetrix import JOVImageNode, comfy_message, deep_merge, Lexicon, ROOT, JOV_TYPE_ANY from Jovimetrix.sup.util import load_file, parse_param, EnumConvertType, parse_value from Jovimetrix.sup.image import EnumInterpolation, EnumScaleMode, cv2tensor_full, image_convert, image_scalefit, tensor2cv, MIN_IMAGE_SIZE from Jovimetrix.sup.shader import PTYPE, shader_meta, CompileException, GLSLShader @@ -135,19 +135,24 @@ def run(self, ident, **kw) -> tuple[torch.Tensor]: step = 1. / self.__glsl.fps images = [] + vars = {} batch = max(1, batch) - pbar = ProgressBar(batch) - for idx in range(batch): - vars = {} - firstImage = None - for k, v in variables.items(): - var = v if not isinstance(v, (list, tuple,)) else v[idx % len(v)] + firstImage = None + # check if the input(s) have more than a single entry, get the max... + if batch == 1: + for k, var in variables.items(): if isinstance(var, (torch.Tensor)): - var = tensor2cv(var) - var = image_convert(var, 4) + batch = max(batch, var.shape[0]) + var = [image_convert(tensor2cv(v), 4) for v in var] if firstImage is None: - firstImage = var - vars[k] = var + firstImage = var[0] + + variables[k] = var if isinstance(var, (list, tuple,)) else [var] + + pbar = ProgressBar(batch) + for idx in range(batch): + for k, val in variables.items(): + vars[k] = val[idx % len(val)] w, h = wihi if firstImage is not None and mode == EnumScaleMode.MATTE: @@ -219,24 +224,26 @@ def INPUT_TYPES(cls) -> dict: params = {"default": None} d = None + type_name = typ.name if glsl_type != 'sampler2D': if default is not None: d = default.split(',') params['default'] = parse_value(d, typ, 0) if val_min is not None: - params['val_min'] = parse_value(val_min, EnumConvertType.FLOAT, -sys.maxsize) + params['mij'] = parse_value(val_min, EnumConvertType.FLOAT, -sys.maxsize) if val_max is not None: - params['val_max'] = parse_value(val_max, EnumConvertType.FLOAT, sys.maxsize) + params['maj'] = parse_value(val_max, EnumConvertType.FLOAT, sys.maxsize) if val_step is not None: d = 1 if typ.name.endswith('INT') else 0.01 - params['val_step'] = parse_value(val_step, EnumConvertType.FLOAT, d) - + params['step'] = parse_value(val_step, EnumConvertType.FLOAT, d) + else: + type_name = JOV_TYPE_ANY if tooltip is not None: params["tooltips"] = tooltip - data[name] = (typ.name, params,) + data[name] = (type_name, params,) data.update(opts) original_params['optional'] = data diff --git a/core/utility.py b/core/utility.py index e1af973..1fc08a2 100644 --- a/core/utility.py +++ b/core/utility.py @@ -257,6 +257,11 @@ def run(self, **kw) -> Tuple[int, list]: elif mode == EnumBatchMode.SLICE: start, end, step = slice_range end = len(data) if end == 0 else end + if step == 0: + step = 1 + elif step < 0: + data = data[::-1] + step = abs(step) data = data[start:end:step] elif mode == EnumBatchMode.RANDOM: if self.__seed is None or self.__seed != seed: diff --git a/pyproject.toml b/pyproject.toml index 3013df5..ec87a3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "jovimetrix" description = "Integrates Webcam, MIDI, Spout and GLSL shader support. Animation via tick. Parameter manipulation with wave generator. Math operations with Unary and Binary support. Value conversion for all major types (int, string, list, dict, Image, Mask). Shape mask generation, image stacking and channel ops, batch splitting, merging and randomizing, load images and video from anywhere, dynamic bus routing with a single node, export support for GIPHY, save output anywhere! flatten, crop, transform; check colorblindness, make stereogram or stereoscopic images, or liner interpolate values and more." -version = "1.2.34" +version = "1.2.35" license = { file = "LICENSE" } dependencies = [ "aenum>=3.1.15,<4", diff --git a/sup/image.py b/sup/image.py index 64749a2..9760674 100644 --- a/sup/image.py +++ b/sup/image.py @@ -462,11 +462,11 @@ def pil2tensor(image: Image.Image) -> torch.Tensor: def tensor2cv(tensor: torch.Tensor) -> TYPE_IMAGE: """Convert a torch Tensor to a numpy ndarray.""" tensor = tensor.cpu().squeeze().numpy() - if tensor.ndim == 1: + if tensor.ndim < 3: tensor = np.expand_dims(tensor, -1) image = np.clip(255.0 * tensor, 0, 255).astype(np.uint8) if image.shape[2] == 4: - image_flatten_mask + # image_flatten_mask mask = image_mask(image) image = image_blend(image, image, mask) image = image_mask_add(image, mask) diff --git a/web/core/core_color.js b/web/core/core_color.js index 96c51bc..37f8d77 100644 --- a/web/core/core_color.js +++ b/web/core/core_color.js @@ -6,11 +6,19 @@ import { app } from '../../../scripts/app.js' import { $el } from '../../../scripts/ui.js' -import { apiPost } from '../util/util_api.js' +import { apiGet, apiPost } from '../util/util_api.js' import { colorContrast } from '../util/util.js' -import * as util_config from '../util/util_config.js' +import { setting_make } from '../util/util_config.js' import { colorPicker } from '../extern/jsColorPicker.js' +let NODE_LIST; +let CONFIG_CORE; +let CONFIG_USER; +let CONFIG_COLOR; +let CONFIG_REGEX; +let CONFIG_THEME; +const USER = 'user.default'; + // gets the CONFIG entry for name function nodeColorGet(node) { const find_me = node.type || node.name; @@ -19,7 +27,7 @@ function nodeColorGet(node) { } // First look to regex.... - for (const colors of util_config.CONFIG_REGEX) { + for (const colors of CONFIG_REGEX || []) { if (colors.regex == "") { continue } @@ -31,18 +39,20 @@ function nodeColorGet(node) { } // now look to theme - let color = util_config.CONFIG_THEME[find_me] + let color = CONFIG_THEME?.[find_me] if (color) { - return color + return color; } - color = util_config.NODE_LIST[find_me] + // console.info(find_me) + + color = NODE_LIST?.[find_me]; // now look to category theme - if (color && color.category) { + if (color && color?.category) { const segments = color.category.split('/') let k = segments.join('/') while (k) { - const found = util_config.CONFIG_THEME[k] + const found = CONFIG_THEME?.[k] if (found) { return found } @@ -50,7 +60,6 @@ function nodeColorGet(node) { k = last !== -1 ? k.substring(0, last) : '' } } - return null; } // refresh the color of a node @@ -86,9 +95,16 @@ class JovimetrixPanelColorize { } } - init() { - if (util_config.CONFIG_USER.color.overwrite) { - nodeColorAll(); + async init() { + NODE_LIST = await apiGet("/object_info"); + CONFIG_CORE = await apiGet("/jovimetrix/config"); + CONFIG_USER = CONFIG_CORE.user.default; + CONFIG_COLOR = CONFIG_USER.color; + CONFIG_REGEX = CONFIG_COLOR.regex || []; + CONFIG_THEME = CONFIG_COLOR.theme; + + if (CONFIG_USER.color.overwrite) { + nodeColorAll(NODE_LIST); } this.watchForColorInputs(); } @@ -168,25 +184,25 @@ class JovimetrixPanelColorize { let api_packet = {} if (parts.length > 2) { const idx = parts[1]; - data = util_config.CONFIG_REGEX[idx]; + data = CONFIG_REGEX[idx]; data[part] = AHEX.value - util_config.CONFIG_REGEX[idx] = data + CONFIG_REGEX[idx] = data api_packet = { - id: util_config.USER + '.color.regex', - v: util_config.CONFIG_REGEX + id: USER + '.color.regex', + v: CONFIG_REGEX } } else { - if (util_config.CONFIG_THEME[name] === undefined) { - util_config.CONFIG_THEME[name] = {} + if (CONFIG_THEME[name] === undefined) { + CONFIG_THEME[name] = {} } - util_config.CONFIG_THEME[name][part] = AHEX.value + CONFIG_THEME[name][part] = AHEX.value api_packet = { - id: util_config.USER + '.color.theme.' + name, - v: util_config.CONFIG_THEME[name] + id: USER + '.color.theme.' + name, + v: CONFIG_THEME[name] } } apiPost("/jovimetrix/config", api_packet); - if (util_config.CONFIG_COLOR.overwrite) { + if (CONFIG_COLOR.overwrite) { nodeColorAll(); } } @@ -200,10 +216,10 @@ class JovimetrixPanelColorize { } updateRegexColor = (index, key, value) => { - util_config.CONFIG_REGEX[index][key] = value + CONFIG_REGEX[index][key] = value let api_packet = { - id: util_config.USER + '.color.regex', - v: util_config.CONFIG_REGEX + id: USER + '.color.regex', + v: CONFIG_REGEX } apiPost("/jovimetrix/config", api_packet) nodeColorAll() @@ -268,7 +284,7 @@ class JovimetrixPanelColorize { // rule-sets first var idx = 0 - const rules = util_config.CONFIG_COLOR.regex || [] + const rules = CONFIG_COLOR?.regex || [] rules.forEach(entry => { const data = { idx: idx, @@ -283,7 +299,7 @@ class JovimetrixPanelColorize { // get categories to generate on the fly const category = [] - const all_nodes = Object.entries(util_config?.NODE_LIST ? util_config.NODE_LIST : []); + const all_nodes = Object.entries(NODE_LIST ? NODE_LIST : []); all_nodes.sort((a, b) => { const categoryComparison = a[1].category.toLowerCase().localeCompare(b[1].category.toLowerCase()); // Move items with empty category or starting with underscore to the end @@ -297,9 +313,9 @@ class JovimetrixPanelColorize { }); // groups + nodes - const alts = util_config.CONFIG_COLOR - const background = [alts.backA, alts.backB] - const background_title = [alts.titleA, alts.titleB] + const alts = CONFIG_COLOR + const background = [alts?.backA, alts?.backB] + const background_title = [alts?.titleA, alts?.titleB] let background_index = 0 all_nodes.forEach(entry => { var name = entry[0] @@ -313,9 +329,9 @@ class JovimetrixPanelColorize { data = { name: meow, } - if (Object.prototype.hasOwnProperty.call(util_config.CONFIG_THEME, meow)) { - data.title = util_config.CONFIG_THEME[meow].title - data.body = util_config.CONFIG_THEME[meow].body + if (Object.prototype.hasOwnProperty.call(CONFIG_THEME, meow)) { + data.title = CONFIG_THEME?.[meow].title + data.body = CONFIG_THEME?.[meow].body } colorTable.appendChild($el("tbody", this.templateColorRow(data, 'header'))) category.push(meow) @@ -328,15 +344,15 @@ class JovimetrixPanelColorize { background: background_title[background_index] || LiteGraph.WIDGET_BGCOLOR } - if (Object.prototype.hasOwnProperty.call(util_config.CONFIG_THEME, cat)) { - data.title = util_config.CONFIG_THEME[cat].title - data.body = util_config.CONFIG_THEME[cat].body + if (Object.prototype.hasOwnProperty.call(CONFIG_THEME, cat)) { + data.title = CONFIG_THEME?.[cat].title + data.body = CONFIG_THEME?.[cat].body } colorTable.appendChild($el("tbody", this.templateColorRow(data, 'header'))) category.push(cat) } - const who = util_config.CONFIG_THEME[name] || {}; + const who = CONFIG_THEME[name] || {}; data = { name: name, title: who.title, @@ -384,15 +400,15 @@ class JovimetrixPanelColorize { }, [ $el("input", { type: "checkbox", - checked: util_config.CONFIG_USER.color.overwrite, + checked: CONFIG_USER?.color?.overwrite, onclick: (cb) => { - util_config.CONFIG_USER.color.overwrite = cb.target.checked + CONFIG_USER.color.overwrite = cb.target.checked var data = { - id: util_config.USER + '.color.overwrite', - v: util_config.CONFIG_USER.color.overwrite + id: USER + '.color.overwrite', + v: CONFIG_USER?.color?.overwrite } apiPost('/jovimetrix/config', data) - if (util_config.CONFIG_USER.color.overwrite) { + if (CONFIG_USER?.color?.overwrite) { nodeColorAll() } } @@ -430,11 +446,11 @@ app.extensionManager.registerSidebarTab({ app.registerExtension({ name: "jovimetrix.color", - async setup(app) { + async setup() { // Option for user to contrast text for better readability const original_color = LiteGraph.NODE_TEXT_COLOR; - util_config.setting_make('color 🎨.contrast', 'Auto-Contrast Text', 'boolean', 'Auto-contrast the title text for all nodes for better readability', true); + setting_make('color 🎨.contrast', 'Auto-Contrast Text', 'boolean', 'Auto-contrast the title text for all nodes for better readability', true); const drawNodeShape = LGraphCanvas.prototype.drawNodeShape; LGraphCanvas.prototype.drawNodeShape = function() { @@ -451,7 +467,7 @@ app.registerExtension({ drawNodeShape.apply(this, arguments); }; - if (util_config.CONFIG_USER.color.overwrite) { + if (CONFIG_USER?.color?.overwrite) { nodeColorAll(); } }, @@ -480,7 +496,7 @@ function initializeColorPicker() { elm.style.backgroundColor = elm.color || LiteGraph.WIDGET_BGCOLOR; elm.style.color = rgb.RGBLuminance > 0.22 ? '#222' : '#ddd' }, - convertCallback: function(data) { + convertCallback: function() { let AHEX = this.patch.attributes.color; if (!AHEX) return; @@ -492,25 +508,25 @@ function initializeColorPicker() { if (parts.length > 1) { const idx = parts[1]; - let data = util_config.CONFIG_REGEX[idx]; + let data = CONFIG_REGEX[idx]; data[part] = AHEX.value; - util_config.CONFIG_REGEX[idx] = data; + CONFIG_REGEX[idx] = data; api_packet = { - id: `${util_config.USER}.color.regex`, - v: util_config.CONFIG_REGEX + id: `${USER}.color.regex`, + v: CONFIG_REGEX }; } else { - const themeConfig = util_config.CONFIG_THEME[name] || (util_config.CONFIG_THEME[name] = {}); + const themeConfig = CONFIG_THEME[name] || (CONFIG_THEME[name] = {}); themeConfig[part] = AHEX.value; api_packet = { - id: `${util_config.USER}.color.theme.${name}`, + id: `${USER}.color.theme.${name}`, v: themeConfig }; } apiPost("/jovimetrix/config", api_packet); - if (util_config.CONFIG_COLOR.overwrite) { + if (CONFIG_COLOR.overwrite) { nodeColorAll(); } } diff --git a/web/core/core_cozy_fields.js b/web/core/core_cozy_fields.js index b6996a8..89e5a8a 100644 --- a/web/core/core_cozy_fields.js +++ b/web/core/core_cozy_fields.js @@ -5,7 +5,7 @@ */ import { app } from "../../../scripts/app.js" -import * as util_config from '../util/util_config.js' +import { setting_store } from '../util/util_config.js' import { colorHex2RGB } from '../util/util.js' let g_color_style; @@ -24,7 +24,7 @@ app.registerExtension({ tooltip: "Style to draw nodes.", defaultValue: "ComfyUI Default", onChange: function(val) { - util_config.setting_store(id, val); + setting_store(id, val); g_color_style = val; }, }); @@ -125,5 +125,4 @@ app.registerExtension({ return me; } } - */ }) \ No newline at end of file diff --git a/web/util/util_config.js b/web/util/util_config.js index 00f9783..ec6be40 100644 --- a/web/util/util_config.js +++ b/web/util/util_config.js @@ -5,15 +5,7 @@ */ import { app } from "../../../scripts/app.js" -import { apiGet, apiPost } from './util_api.js' - -export let NODE_LIST = await apiGet("/object_info"); -export let CONFIG_CORE = await apiGet("/jovimetrix/config") -export let CONFIG_USER = CONFIG_CORE.user.default -export let CONFIG_COLOR = CONFIG_USER.color -export let CONFIG_REGEX = CONFIG_COLOR.regex || [] -export let CONFIG_THEME = CONFIG_COLOR.theme -export let USER = 'user.default' +import { apiPost } from './util_api.js' export async function local_get(url, d) { const v = localStorage.getItem(url) @@ -30,7 +22,7 @@ export async function local_set(url, v) { export function setting_store(id, val) { var data = { id: id, v: val } apiPost('/jovimetrix/config', data); - CONFIG_USER[id] = val; + // CONFIG_USER[id] = val; localStorage[`Comfy.Settings.${id}`] = val; } @@ -38,7 +30,8 @@ export function setting_make(id, pretty, type, tip, value,) { const key = `JOVIMETRIX πŸ”ΊπŸŸ©πŸ”΅.${id}` const setting_root = `Comfy.Settings.jov.${key}`; const local = localStorage[setting_root]; - value = local ? local : CONFIG_USER?.[key] ? CONFIG_USER[key] : value; + // CONFIG_USER?.[key] ? CONFIG_USER[key] : + value = local ? local : value; app.ui.settings.addSetting({ id: key, @@ -49,7 +42,7 @@ export function setting_make(id, pretty, type, tip, value,) { onChange(val) { var data = { id: key, v: val } apiPost('/jovimetrix/config', data); - CONFIG_USER[id] = val; + // CONFIG_USER[id] = val; localStorage[setting_root] = val; }, })