diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a348e50 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/__pycache__/ diff --git a/README.md b/README.md index 9bb4a3f..75e8bcb 100644 --- a/README.md +++ b/README.md @@ -1 +1,23 @@ -# ComfyUI_essentials \ No newline at end of file +# :wrench: ComfyUI Essentials + +Essential nodes that are weirdly missing from ComfyUI core. With few exceptions they are new features and not commodities. I hope this will be just a temporary repository until the nodes get included into ComfyUI. + +## Node list + +- Get Image Size +- Image Resize (adds "keep proportions" to scale image) +- Image Crop (includes auto crop to all sides and center) +- Image Flip +- Image Desaturate +- Image Posterize +- Mask Blur +- Mask Flip +- Mask Grow / Shrink (same as Mask grow but adds shrink) +- Mask Preview +- Simple Math +- Console Debug (outputs any input to console) + +- TODO: Mask Save +- TODO: documentation + +Let me know if anything's missing diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..cf2906e --- /dev/null +++ b/__init__.py @@ -0,0 +1,3 @@ +from .essentials import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] diff --git a/essentials.py b/essentials.py new file mode 100644 index 0000000..8aa1c48 --- /dev/null +++ b/essentials.py @@ -0,0 +1,423 @@ +import warnings +warnings.filterwarnings('ignore', module="torchvision") +import ast +import math +import random +import operator as op +import numpy as np +from scipy.ndimage import grey_dilation, grey_erosion + +import torch +import torch.nn.functional as F + +import torchvision.transforms.v2 as T + +from nodes import MAX_RESOLUTION, SaveImage +import folder_paths +import comfy.utils + +def p(image): + return image.permute([0,3,1,2]) +def pb(image): + return image.permute([0,2,3,1]) + +operators = { + ast.Add: op.add, + ast.Sub: op.sub, + ast.Mult: op.mul, + ast.Div: op.truediv, + ast.FloorDiv: op.floordiv, + ast.Pow: op.pow, + ast.BitXor: op.xor, + ast.USub: op.neg, + ast.Mod: op.mod, +} + +# from https://github.com/pythongosssss/ComfyUI-Custom-Scripts +class AnyType(str): + def __ne__(self, __value: object) -> bool: + return False +any = AnyType("*") + +class GetImageSize: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("IMAGE",), + } + } + + RETURN_TYPES = ("INT", "INT") + RETURN_NAMES = ("width", "height") + FUNCTION = "execute" + CATEGORY = "essentials" + + def execute(self, image): + return (image.shape[2], image.shape[1],) + +class ImageResize: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("IMAGE",), + "width": ("INT", { "default": 512, "min": 0, "max": MAX_RESOLUTION, "step": 8, "display": "number" }), + "height": ("INT", { "default": 512, "min": 0, "max": MAX_RESOLUTION, "step": 8, "display": "number" }), + "interpolation": (["nearest", "bilinear", "bicubic", "area", "nearest-exact", "lanczos"],), + "keep_proportion": ("BOOLEAN", { "default": False }), + "antialias": ("BOOLEAN", { "default": True }), + } + } + + RETURN_TYPES = ("IMAGE", "INT", "INT",) + RETURN_NAMES = ("IMAGE", "width", "height",) + FUNCTION = "execute" + CATEGORY = "essentials" + + def execute(self, image, width, height, keep_proportion, interpolation="bicubic", antialias=True): + if keep_proportion is True: + _, oh, ow, _ = image.shape + width = ow if width == 0 else width + height = oh if height == 0 else height + ratio = min(width / ow, height / oh) + width = round(ow*ratio) + height = round(oh*ratio) + + outputs = p(image) + if interpolation == "lanczos": + outputs = comfy.utils.lanczos(outputs, width, height) + else: + outputs = F.interpolate(outputs, size=(height, width), mode=interpolation) + outputs = pb(outputs) + + return(outputs, outputs.shape[2], outputs.shape[1],) + +class ImageFlip: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("IMAGE",), + "axis": (["x", "y", "xy"],), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "execute" + CATEGORY = "essentials" + + def execute(self, image, axis): + dim = () + if "y" in axis: + dim += (1,) + if "x" in axis: + dim += (2,) + image = torch.flip(image, dim) + + return(image,) + +class ImageCrop: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("IMAGE",), + "width": ("INT", { "default": 256, "min": 0, "max": MAX_RESOLUTION, "step": 8, "display": "number" }), + "height": ("INT", { "default": 256, "min": 0, "max": MAX_RESOLUTION, "step": 8, "display": "number" }), + "position": (["free", "center", "top-left", "top-center", "top-right", "right-center", "bottom-right", "bottom-center", "bottom-left", "left-center"],), + "x": ("INT", { "default": 0, "min": 0, "step": 1, "display": "number" }), + "y": ("INT", { "default": 0, "min": 0, "step": 1, "display": "number" }), + } + } + + RETURN_TYPES = ("IMAGE","INT","INT",) + RETURN_NAMES = ("IMAGE","x","y",) + FUNCTION = "execute" + CATEGORY = "essentials" + + def execute(self, image, width, height, position, x, y): + _, oh, ow, _ = image.shape + + width = min(ow, width) + height = min(oh, height) + + if x+width > ow: + width = ow-x + if y+height > oh: + height = oh-y + + if "center" in position: + x = round((ow-width) / 2) + y = round((oh-height) / 2) + if "top" in position: + y = 0 + if "bottom" in position: + y = oh-height + if "left" in position: + x = 0 + if "right" in position: + x = ow-width + + image = image[:, y:y+height, x:x+width, :] + + return(image, x, y, ) + +class ImageDesaturate: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("IMAGE",), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "execute" + CATEGORY = "essentials" + + def execute(self, image): + #image = p(image) + #image = T.Grayscale(3)(image) + #image = pb(image) + #image = image.mean(dim=3, keepdim=True) + #image = image.repeat(1, 1, 1, 3) + image = 0.299 * image[..., 0] + 0.587 * image[..., 1] + 0.114 * image[..., 2] + image = image.unsqueeze(-1).repeat(1, 1, 1, 3) + return(image,) + +class ImagePosterize: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("IMAGE",), + "threshold": ("FLOAT", { "default": 0.50, "min": 0.00, "max": 1.00, "step": 0.05, "display": "number" }), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "execute" + CATEGORY = "essentials" + + def execute(self, image, threshold): + image = 0.299 * image[..., 0] + 0.587 * image[..., 1] + 0.114 * image[..., 2] + #image = image.mean(dim=3, keepdim=True) + image = (image > threshold).float() + image = image.unsqueeze(-1).repeat(1, 1, 1, 3) + + return(image,) + +class MaskFlip: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "mask": ("MASK",), + "axis": (["x", "y", "xy"],), + } + } + + RETURN_TYPES = ("MASK",) + FUNCTION = "execute" + CATEGORY = "essentials" + + def execute(self, mask, axis): + dim = () + if "y" in axis: + dim += (0,) + if "x" in axis: + dim += (1,) + mask = torch.flip(mask, dim) + + return(mask,) + +class MaskBlur: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "mask": ("MASK",), + "size": ("INT", { "default": 5, "min": 1, "step": 1, "display": "number" }), + "sigma": ("FLOAT", { "default": 1.0, "min": 0, "step": 0.5, "display": "number" }), + } + } + + RETURN_TYPES = ("MASK",) + FUNCTION = "execute" + CATEGORY = "essentials" + + def execute(self, mask, size, sigma): + if size % 2 == 0: + size+=1 + + blurred = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3) + blurred = p(blurred) + blurred = T.GaussianBlur(size, sigma)(blurred) + blurred = pb(blurred) + blurred = blurred[0, :, :, 0] + + return(blurred,) + +class MaskPreview(SaveImage): + def __init__(self): + self.output_dir = folder_paths.get_temp_directory() + self.type = "temp" + self.prefix_append = "_temp_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5)) + + @classmethod + def INPUT_TYPES(s): + return { + "required": {"mask": ("MASK",), }, + "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, + } + + FUNCTION = "execute" + CATEGORY = "essentials" + + def execute(self, mask, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None): + preview = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3) + results = self.save_images(preview, filename_prefix, prompt, extra_pnginfo) + + return( results ) + +class GrowShrinkMask: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "mask": ("MASK",), + "amount": ("INT", {"default": 0, "min": -MAX_RESOLUTION, "max": MAX_RESOLUTION, "step": 1}), + "tapered_corners": ("BOOLEAN", {"default": True}), + }, + } + + RETURN_TYPES = ("MASK",) + CATEGORY = "essentials" + FUNCTION = "execute" + + def execute(self, mask, amount, tapered_corners): + c = 0 if tapered_corners else 1 + kernel = np.array([[c, 1, c], + [1, 1, 1], + [c, 1, c]]) + output = mask.numpy().copy() + if amount < 0: + amount = -amount + grey_action = grey_erosion + else: + grey_action = grey_dilation + + while amount > 0: + output = grey_action(output, footprint=kernel) + amount -= 1 + output = torch.from_numpy(output) + + return (output,) + +class SimpleMath: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "optional": { + "a": ("FLOAT", { "default": 0.0, "step": 1 }), + "b": ("FLOAT", { "default": 0.0, "step": 1 }), + }, + "required": { + "value": ("STRING", { "multiline": False, "default": "" }), + }, + } + + RETURN_TYPES = ("INT", "FLOAT", ) + FUNCTION = "execute" + CATEGORY = "essentials" + + def execute(self, value, a = 0.0, b = 0.0): + def eval_(node): + if isinstance(node, ast.Num): # number + return node.n + elif isinstance(node, ast.Name): # variable + if node.id == "a": + return a + if node.id == "b": + return b + elif isinstance(node, ast.BinOp): # + return operators[type(node.op)](eval_(node.left), eval_(node.right)) + elif isinstance(node, ast.UnaryOp): # e.g., -1 + return operators[type(node.op)](eval_(node.operand)) + else: + return 0 + + result = eval_(ast.parse(value, mode='eval').body) + + if math.isnan(result): + result = 0.0 + + return (round(result), result, ) + +class ConsoleDebug: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "value": (any, {}), + }, + "optional": { + "prefix": ("STRING", { "multiline": False, "default": "Value:" }) + } + } + + RETURN_TYPES = () + FUNCTION = "execute" + CATEGORY = "essentials" + OUTPUT_NODE = True + + def execute(self, value, prefix): + print(f"\033[96m{prefix} {value}\033[0m") + + return (None,) + + +NODE_CLASS_MAPPINGS = { + "GetImageSize+": GetImageSize, + + "ImageResize+": ImageResize, + "ImageCrop+": ImageCrop, + "ImageFlip+": ImageFlip, + + "ImageDesaturate+": ImageDesaturate, + "ImagePosterize+": ImagePosterize, + + "MaskBlur+": MaskBlur, + "MaskFlip+": MaskFlip, + "GrowShrinkMask+": GrowShrinkMask, + "MaskPreview+": MaskPreview, + + "SimpleMath+": SimpleMath, + "ConsoleDebug+": ConsoleDebug, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "GetImageSize+": "🔧 Get Image Size", + + "ImageResize+": "🔧 Image Resize", + "ImageCrop+": "🔧 Image Crop", + "ImageFlip+": "🔧 Image Flip", + + "ImageDesaturate+": "🔧 Image Desaturate", + "ImagePosterize+": "🔧 Image Posterize", + + "MaskBlur+": "🔧 Mask Blur", + "MaskFlip+": "🔧 Mask Flip", + "GrowShrinkMask+": "🔧 Mask Grow/Shrink", + "MaskPreview+": "🔧 Mask Preview", + + "SimpleMath+": "🔧 Simple Math", + "ConsoleDebug+": "🔧 Console Debug", +} \ No newline at end of file diff --git a/workflow_all_nodes.json b/workflow_all_nodes.json new file mode 100644 index 0000000..975b10a --- /dev/null +++ b/workflow_all_nodes.json @@ -0,0 +1,1032 @@ +{ + "last_node_id": 26, + "last_link_id": 21, + "nodes": [ + { + "id": 1, + "type": "GetImageSize+", + "pos": [ + 420, + 200 + ], + "size": { + "0": 210, + "1": 46 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 1 + } + ], + "outputs": [ + { + "name": "width", + "type": "INT", + "links": [ + 2 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "height", + "type": "INT", + "links": [ + 3 + ], + "shape": 3, + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "GetImageSize+" + } + }, + { + "id": 9, + "type": "ConsoleDebug+", + "pos": [ + 750, + 220 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 14, + "mode": 0, + "inputs": [ + { + "name": "value", + "type": "*", + "link": 3 + } + ], + "properties": { + "Node name for S&R": "ConsoleDebug+" + }, + "widgets_values": [ + "Height:" + ] + }, + { + "id": 7, + "type": "LoadImage", + "pos": [ + -40, + 320 + ], + "size": [ + 315, + 313.99999237060547 + ], + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 1, + 4, + 5, + 6, + 7, + 8 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "venere.jpg", + "image" + ] + }, + { + "id": 3, + "type": "ImageCrop+", + "pos": [ + 430, + 560 + ], + "size": { + "0": 315, + "1": 194 + }, + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 5 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 10 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "x", + "type": "INT", + "links": null, + "shape": 3 + }, + { + "name": "y", + "type": "INT", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "ImageCrop+" + }, + "widgets_values": [ + 256, + 256, + "free", + 0, + 0 + ] + }, + { + "id": 5, + "type": "ImageDesaturate+", + "pos": [ + 440, + 920 + ], + "size": { + "0": 210, + "1": 26 + }, + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 7 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 12 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "ImageDesaturate+" + } + }, + { + "id": 10, + "type": "PreviewImage", + "pos": [ + 1090, + 330 + ], + "size": [ + 210, + 246 + ], + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 9 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 11, + "type": "PreviewImage", + "pos": [ + 830, + 540 + ], + "size": [ + 210, + 246 + ], + "flags": {}, + "order": 16, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 10 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 4, + "type": "ImageFlip+", + "pos": [ + 440, + 810 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 6 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 11 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "ImageFlip+" + }, + "widgets_values": [ + "xy" + ] + }, + { + "id": 6, + "type": "ImagePosterize+", + "pos": [ + 430, + 1000 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 8 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 13 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "ImagePosterize+" + }, + "widgets_values": [ + 0.55 + ] + }, + { + "id": 14, + "type": "PreviewImage", + "pos": [ + 870, + 1160 + ], + "size": [ + 210, + 246 + ], + "flags": {}, + "order": 19, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 13 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 13, + "type": "PreviewImage", + "pos": [ + 870, + 870 + ], + "size": [ + 210, + 246 + ], + "flags": {}, + "order": 18, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 12 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 12, + "type": "PreviewImage", + "pos": [ + 1130, + 780 + ], + "size": [ + 210, + 246 + ], + "flags": {}, + "order": 17, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 11 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 20, + "type": "LoadImageMask", + "pos": [ + 1490, + 390 + ], + "size": [ + 220.70517121393095, + 240.71892144642402 + ], + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 14, + 15, + 16 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LoadImageMask" + }, + "widgets_values": [ + "cwf_inpaint_example_mask.png", + "alpha", + "image" + ] + }, + { + "id": 15, + "type": "MaskBlur+", + "pos": [ + 1770, + 250 + ], + "size": { + "0": 315, + "1": 82 + }, + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 14 + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 19 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "MaskBlur+" + }, + "widgets_values": [ + 44, + 28.5 + ] + }, + { + "id": 16, + "type": "MaskFlip+", + "pos": [ + 1770, + 390 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 15 + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 18 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "MaskFlip+" + }, + "widgets_values": [ + "xy" + ] + }, + { + "id": 21, + "type": "MaskPreview+", + "pos": [ + 2430, + 320 + ], + "size": [ + 210, + 246 + ], + "flags": {}, + "order": 21, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 18 + } + ], + "properties": { + "Node name for S&R": "MaskPreview+" + } + }, + { + "id": 22, + "type": "MaskPreview+", + "pos": [ + 2180, + 540 + ], + "size": [ + 210, + 246 + ], + "flags": {}, + "order": 22, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 17 + } + ], + "properties": { + "Node name for S&R": "MaskPreview+" + } + }, + { + "id": 17, + "type": "GrowShrinkMask+", + "pos": [ + 1780, + 510 + ], + "size": { + "0": 315, + "1": 82 + }, + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 16 + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 17 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "GrowShrinkMask+" + }, + "widgets_values": [ + -30, + true + ] + }, + { + "id": 2, + "type": "ImageResize+", + "pos": [ + 420, + 310 + ], + "size": { + "0": 315, + "1": 194 + }, + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 4 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 9 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "width", + "type": "INT", + "links": null, + "shape": 3, + "slot_index": 1 + }, + { + "name": "height", + "type": "INT", + "links": null, + "shape": 3, + "slot_index": 2 + } + ], + "properties": { + "Node name for S&R": "ImageResize+" + }, + "widgets_values": [ + 768, + 512, + "nearest", + true, + true + ] + }, + { + "id": 19, + "type": "SimpleMath+", + "pos": [ + 1730, + 1000 + ], + "size": [ + 210, + 102 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "a", + "type": "FLOAT", + "link": 20, + "widget": { + "name": "a", + "config": [ + "FLOAT", + { + "default": 0, + "step": 1 + } + ] + } + } + ], + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 21 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "FLOAT", + "type": "FLOAT", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "SimpleMath+" + }, + "widgets_values": [ + "a*100", + 0, + 0 + ] + }, + { + "id": 23, + "type": "ConsoleDebug+", + "pos": [ + 2020, + 930 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 23, + "mode": 0, + "inputs": [ + { + "name": "value", + "type": "*", + "link": 21 + } + ], + "properties": { + "Node name for S&R": "ConsoleDebug+" + }, + "widgets_values": [ + "Value:" + ] + }, + { + "id": 18, + "type": "MaskPreview+", + "pos": [ + 2180, + 90 + ], + "size": [ + 210, + 246 + ], + "flags": {}, + "order": 20, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 19 + } + ], + "properties": { + "Node name for S&R": "MaskPreview+" + } + }, + { + "id": 8, + "type": "ConsoleDebug+", + "pos": [ + 700, + 100 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "value", + "type": "*", + "link": 2 + } + ], + "properties": { + "Node name for S&R": "ConsoleDebug+" + }, + "widgets_values": [ + "Width:" + ] + }, + { + "id": 26, + "type": "SimpleMath+", + "pos": [ + 1480, + 840 + ], + "size": [ + 210, + 126 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": null, + "shape": 3 + }, + { + "name": "FLOAT", + "type": "FLOAT", + "links": [ + 20 + ], + "shape": 3, + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "SimpleMath+" + }, + "widgets_values": [ + "100", + 0, + 0 + ] + } + ], + "links": [ + [ + 1, + 7, + 0, + 1, + 0, + "IMAGE" + ], + [ + 2, + 1, + 0, + 8, + 0, + "*" + ], + [ + 3, + 1, + 1, + 9, + 0, + "*" + ], + [ + 4, + 7, + 0, + 2, + 0, + "IMAGE" + ], + [ + 5, + 7, + 0, + 3, + 0, + "IMAGE" + ], + [ + 6, + 7, + 0, + 4, + 0, + "IMAGE" + ], + [ + 7, + 7, + 0, + 5, + 0, + "IMAGE" + ], + [ + 8, + 7, + 0, + 6, + 0, + "IMAGE" + ], + [ + 9, + 2, + 0, + 10, + 0, + "IMAGE" + ], + [ + 10, + 3, + 0, + 11, + 0, + "IMAGE" + ], + [ + 11, + 4, + 0, + 12, + 0, + "IMAGE" + ], + [ + 12, + 5, + 0, + 13, + 0, + "IMAGE" + ], + [ + 13, + 6, + 0, + 14, + 0, + "IMAGE" + ], + [ + 14, + 20, + 0, + 15, + 0, + "MASK" + ], + [ + 15, + 20, + 0, + 16, + 0, + "MASK" + ], + [ + 16, + 20, + 0, + 17, + 0, + "MASK" + ], + [ + 17, + 17, + 0, + 22, + 0, + "MASK" + ], + [ + 18, + 16, + 0, + 21, + 0, + "MASK" + ], + [ + 19, + 15, + 0, + 18, + 0, + "MASK" + ], + [ + 20, + 26, + 1, + 19, + 0, + "FLOAT" + ], + [ + 21, + 19, + 0, + 23, + 0, + "*" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file