diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9f03892
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+__pycache__/
+*.py[cod]
+/output/
+/input/
+!/input/example.png
+/models/
+/temp/
+/custom_nodes/
+!custom_nodes/example_node.py.example
+extra_model_paths.yaml
+/.vs
+.idea/
+venv/
+/web/extensions/*
+!/web/extensions/logging.js.example
+!/web/extensions/core/
+/tests-ui/data/object_info.json
+/user/
\ No newline at end of file
diff --git a/README.MD b/README.MD
new file mode 100644
index 0000000..d23c6ec
--- /dev/null
+++ b/README.MD
@@ -0,0 +1,44 @@
+# ComfyUI Light Gradient
+
+A set of nodes for ComfyUI Light Gradient.
+
+
+
+*this workflow (workflow.png) is in the example directory.
+
+
+## How to install
+
+* Recommended use ComfyUI Manager for installation.
+
+* Or open the cmd window in the plugin directory of ComfyUI, like ```ComfyUI\custom_nodes```,type
+```
+git clone https://github.com/huagetai/ComfyUI_LightGradient.git
+```
+* Or download the zip file and extracted, copy the resulting folder to ```ComfyUI\custom_ Nodes```
+
+* Restart ComfyUI.
+
+
+## Nodes
+
+1. **Image Gradient Node**
+* input
+ * width - width of Mask
+ * height - height of Mask
+ * light_position - position of Light
+ * multiplier - strength of Light
+ * start_color - start color of gradient
+ * end_color - end color of gradient
+* output
+ * IMAGE
+
+2. **Mask Gradient Node**
+* input
+ * width - width of Mask
+ * height - height of Mask
+ * light_position - position of Light
+ * multiplier - strength of Light
+
+* output
+ * Mask
\ No newline at end of file
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..b0dbf9b
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,2 @@
+from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
+__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
\ No newline at end of file
diff --git a/example/workflow.png b/example/workflow.png
new file mode 100644
index 0000000..a636676
Binary files /dev/null and b/example/workflow.png differ
diff --git a/nodes.py b/nodes.py
new file mode 100644
index 0000000..7a048ee
--- /dev/null
+++ b/nodes.py
@@ -0,0 +1,238 @@
+import torch
+import random
+import numpy as np
+from enum import Enum
+from nodes import MAX_RESOLUTION
+
+# Images generated by combination of different creases need to be normalized
+def normalize_img(img):
+ return (img / img.max()) * 255
+
+# cross = True if cross crease is wanted
+def create_uneven_array(low, up, steps, spacing=1, cross=False):
+ span = up - low
+ dx = 1.0 / steps
+ if cross:
+ arr = np.array([low + (i*dx)**spacing*span for i in range(steps//2)])
+ return np.append(arr, arr[::-1])
+ else :
+ arr = np.array([low + (i*dx)**spacing*span for i in range(steps)])
+ return arr
+
+def parabolic_crease(spacing, c, scale=100, corner=1, resolution = 1000):
+ """
+ Parameters:
+
+ spacing = controls how close the intermediate values will be to lower value
+ c = higher the c more spread out the gradient will be
+ scale = lesser the scale more concentrated is gradient towards the corner
+ """
+ img = np.zeros((resolution, resolution))
+
+ # Varying the scaling parameter of create_uneven_array will give the parabolic gradient transition
+ for i in range(resolution):
+ img[i] = create_uneven_array(255, 0, resolution, spacing + c*i/scale)
+
+ if corner == 1:
+ return img
+ elif corner == 2:
+ return img[::-1]
+ elif corner == 3:
+ return img.T
+ else:
+ return img.T[::-1]
+
+# If cross=1, then cross crease else linear crease is returned
+def cross_crease(spacing, cross=1, resolution = 1000):
+ a = create_uneven_array(255, 0, resolution, spacing, cross=True)
+ img = np.tile(a, (resolution, 1))
+ return normalize_img(img*img.T) if cross else img
+
+# Final function to return some random crease from 8 different types
+def custom_crease():
+ spacing = random.uniform(1, 1.5)
+ scale = random.randint(100, 300)
+ corner = random.randint(1, 4)
+
+ # constant determines the type of crease and also is used to scale spacing in parabolic_crease
+ constant = random.randint(1, 10)
+
+ # Returning those creases which are based on parabolic
+ parabolic = parabolic_crease(spacing, constant, scale, corner)
+ if constant == 1:
+ return parabolic
+ elif constant == 2:
+ return normalize_img(parabolic*parabolic.T*parabolic[::-1]*parabolic.T[::-1])
+
+ # Returning those creases which are based on parabolic and cross
+ cross = cross_crease(spacing)
+ if constant == 3:
+ return cross
+ elif constant == 4:
+ return normalize_img(parabolic * cross)
+ elif constant == 5:
+ return normalize_img(cross * parabolic * parabolic.T)
+
+ # Returning those creases which are based on parabolic and linear
+ linear = cross_crease(spacing, 0)
+ if constant == 6:
+ return linear
+ elif constant == 7:
+ return linear.T
+ else:
+ return normalize_img(linear * parabolic)
+
+
+
+class LightPosition(Enum):
+ LEFT = "Left Light"
+ RIGHT = "Right Light"
+ TOP = "Top Light"
+ BOTTOM = "Bottom Light"
+ TOP_LEFT = "Top Left Light"
+ TOP_RIGHT = "Top Right Light"
+ BOTTOM_LEFT = "Bottom Left Light"
+ BOTTOM_RIGHT = "Bottom Right Light"
+
+def toRgb(color):
+ if color.startswith('#') and len(color) == 7: # e.g. "#RRGGBB"
+ color_rgb =tuple(int(color[i:i+2], 16) for i in (1, 3, 5))
+ else: # e.g. "255,255,255"
+ color_rgb = tuple(int(i) for i in color.split(','))
+ return color_rgb
+
+def rgb_to_int(rgb):
+ r, g, b = rgb
+ return (r << 16) + (g << 8) + b
+
+def generate_gradient_image(width:int, height:int, start_color:tuple=(255,255,255), end_color:tuple=(0,0,0), multiplier:float=1.0, lightPosition:LightPosition=LightPosition.LEFT):
+ """
+ Generate a gradient image with a light source effect.
+
+ Parameters:
+ width (int): Width of the image.
+ height (int): Height of the image.
+ start_color: Starting color RGB of the gradient.
+ end_color: Ending color RGB of the gradient.
+ multiplier: Weight of light.
+ lightPosition (LightPosition): Position of the light source.
+
+ Returns:
+ np.array: 2D gradient image array.
+ """
+ # Create a gradient from 0 to 1 and apply multiplier
+ if lightPosition == LightPosition.LEFT:
+ gradient = np.tile(np.linspace(0, 1, width)**multiplier, (height, 1))
+ elif lightPosition == LightPosition.RIGHT:
+ gradient = np.tile(np.linspace(1, 0, width)**multiplier, (height, 1))
+ elif lightPosition == LightPosition.TOP:
+ gradient = np.tile(np.linspace(0, 1, height)**multiplier, (width, 1)).T
+ elif lightPosition == LightPosition.BOTTOM:
+ gradient = np.tile(np.linspace(1, 0, height)**multiplier, (width, 1)).T
+ elif lightPosition == LightPosition.BOTTOM_RIGHT:
+ x = np.linspace(1, 0, width)**multiplier
+ y = np.linspace(1, 0, height)**multiplier
+ x_mesh, y_mesh = np.meshgrid(x, y)
+ gradient = np.sqrt(x_mesh**2 + y_mesh**2) / np.sqrt(2.0)
+ elif lightPosition == LightPosition.BOTTOM_LEFT:
+ x = np.linspace(0, 1, width)**multiplier
+ y = np.linspace(1, 0, height)**multiplier
+ x_mesh, y_mesh = np.meshgrid(x, y)
+ gradient = np.sqrt(x_mesh**2 + y_mesh**2) / np.sqrt(2.0)
+ elif lightPosition == LightPosition.TOP_RIGHT:
+ x = np.linspace(1, 0, width)**multiplier
+ y = np.linspace(0, 1, height)**multiplier
+ x_mesh, y_mesh = np.meshgrid(x, y)
+ gradient = np.sqrt(x_mesh**2 + y_mesh**2) / np.sqrt(2.0)
+ elif lightPosition == LightPosition.TOP_LEFT:
+ x = np.linspace(0, 1, width)**multiplier
+ y = np.linspace(0, 1, height)**multiplier
+ x_mesh, y_mesh = np.meshgrid(x, y)
+ gradient = np.sqrt(x_mesh**2 + y_mesh**2) / np.sqrt(2.0)
+ else:
+ raise ValueError(f"Unsupported position. Choose from {', '.join([member.value for member in LightPosition])}.")
+
+ # Interpolate between start_color and end_color based on the gradient
+ gradient_img = np.zeros((height, width, 3), dtype=np.float32)
+ for i in range(3):
+ gradient_img[..., i] = start_color[i] + (end_color[i] - start_color[i]) * gradient
+
+ gradient_img = np.clip(gradient_img, 0, 255).astype(np.uint8)
+ return gradient_img
+
+
+def numpy_to_tensor(array: np.ndarray) -> torch.Tensor:
+ """Convert a numpy array to a tensor and scale its values from 0-255 to 0-1."""
+ array = array.astype(np.float32) / 255.0
+ return torch.from_numpy(array)[None,]
+
+
+class ImageGradient:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "light_position": ([member.value for member in LightPosition],),
+ "multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
+ "start_color": ("STRING", {"default": "#FFFFFF"}),
+ "end_color": ("STRING", {"default": "#000000"}),
+ "width": ("INT", { "default": 512, "min": 0, "max": MAX_RESOLUTION, "step": 8, }),
+ "height": ("INT", { "default": 512, "min": 0, "max": MAX_RESOLUTION, "step": 8, })
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "execute"
+ CATEGORY = "LightGradient"
+ DESCRIPTION = """Simple Light Gradient"""
+
+ def execute(self, light_position, multiplier, start_color, end_color, width, height):
+ lightPosition = LightPosition(light_position)
+ start_color_rgb = toRgb(start_color)
+ end_color_rgb = toRgb(end_color)
+
+ image = generate_gradient_image(width, height, start_color_rgb, end_color_rgb, multiplier, lightPosition)
+ image = numpy_to_tensor(image)
+
+ mask = generate_gradient_image(width, height, multiplier=multiplier, lightPosition=lightPosition)
+ mask = numpy_to_tensor(mask)
+ mask = mask[:, :, :, 0]
+
+ return (image,mask,)
+
+class MaskGradient:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "light_position": ([member.value for member in LightPosition],),
+ "multiplier": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}),
+ "width": ("INT", { "default": 512, "min": 0, "max": MAX_RESOLUTION, "step": 8, }),
+ "height": ("INT", { "default": 512, "min": 0, "max": MAX_RESOLUTION, "step": 8, })
+ }
+ }
+
+ RETURN_TYPES = ("MASK",)
+ FUNCTION = "execute"
+ CATEGORY = "LightGradient"
+ DESCRIPTION = """Mask Gradient"""
+
+ def execute(self, light_position, multiplier, width, height):
+ lightPosition = LightPosition(light_position)
+
+ mask = generate_gradient_image(width, height, multiplier=multiplier, lightPosition=lightPosition)
+ mask = numpy_to_tensor(mask)
+ mask = mask[:, :, :, 0]
+
+ return (mask,)
+
+
+NODE_CLASS_MAPPINGS = {
+ "ImageGradient": ImageGradient,
+ "MaskGradient": MaskGradient
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "ImageGradient": "Image Gradient",
+ "MaskGradient": "Mask Gradient"
+}