Update FSL nodes and add new alpha tools, switcher, and assets, update and add workflows and README.md documents

This commit is contained in:
Fred LeFevre
2025-05-08 19:06:56 -04:00
parent 620072bceb
commit 752bd938d6
18 changed files with 327 additions and 57 deletions
+64
View File
@@ -0,0 +1,64 @@
# fsl_nodes.py
# 8-Way Image Switch Node for ComfyUI
class Image_Switch_8Way:
CATEGORY = "FSL Nodes/Switches" # This controls sidebar grouping in ComfyUI
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("img_out",)
FUNCTION = "get_image"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"select": ("INT", {"default": 1, "min": 1, "max": 8}),
},
"optional": {
"img_1": ("IMAGE",),
"img_2": ("IMAGE",),
"img_3": ("IMAGE",),
"img_4": ("IMAGE",),
"img_5": ("IMAGE",),
"img_6": ("IMAGE",),
"img_7": ("IMAGE",),
"img_8": ("IMAGE",),
}
}
def get_image(self, select, img_1=None, img_2=None, img_3=None, img_4=None,
img_5=None, img_6=None, img_7=None, img_8=None):
# Default to img_1 if selection is invalid or missing
images = [img_1, img_2, img_3, img_4, img_5, img_6, img_7, img_8]
idx = max(1, min(select, 8)) - 1 # Clamp select to 1-8, convert to 0-based index
img_out = images[idx]
return (img_out,)
# Example: Another custom node for demonstration (can be removed or replaced)
class FSL_Image_Passthrough:
CATEGORY = "FSL Nodes/Switches"
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("img_out",)
FUNCTION = "passthrough"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"img_in": ("IMAGE",),
}
}
def passthrough(self, img_in):
return (img_in,)
# Node registration dictionaries
NODE_CLASS_MAPPINGS = {
"FSL Image Switch 8Way": Image_Switch_8Way,
"FSL Image Passthrough": FSL_Image_Passthrough,
}
NODE_DISPLAY_NAME_MAPPINGS = {
# Optional: Controls how the node appears in the sidebar (can use emojis, etc.)
"FSL Image Switch 8Way": "🔀 8-Way Image Switch (FSL)",
"FSL Image Passthrough": "➡️ Image Passthrough (FSL)",
}
+7 -1
View File
@@ -4,14 +4,20 @@ import os
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
# Automatically load all .py files in this folder except __init__.py
# Load all node modules
current_dir = os.path.dirname(__file__)
for filename in os.listdir(current_dir):
if filename.endswith(".py") and filename != "__init__.py":
module_name = filename[:-3]
module = importlib.import_module(f".{module_name}", __package__)
if hasattr(module, "NODE_CLASS_MAPPINGS"):
# Add category override here
for cls_name, cls in module.NODE_CLASS_MAPPINGS.items():
cls.CATEGORY = "FSL Nodes" # Your unified category name
NODE_CLASS_MAPPINGS.update(module.NODE_CLASS_MAPPINGS)
if hasattr(module, "NODE_DISPLAY_NAME_MAPPINGS"):
NODE_DISPLAY_NAME_MAPPINGS.update(module.NODE_DISPLAY_NAME_MAPPINGS)
@@ -0,0 +1,93 @@
import torch
import numpy as np
from PIL import Image
import os
class FSL_CompositeWithMask:
CATEGORY = "FSL Nodes/Utils"
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("composited_rgb",)
FUNCTION = "process"
@classmethod
def INPUT_TYPES(cls):
return {
"optional": {
"foreground": ("IMAGE",),
"mask": ("MASK",),
"background": ("IMAGE",),
"filename": ("STRING", {"default": "composited_rgb.png"}),
"save_dir": ("STRING", {"default": "./outputs"}),
}
}
def process(self, foreground=None, mask=None, background=None, filename="composited_rgb.png", save_dir="./outputs"):
# Ensure filename has valid extension
valid_extensions = [".png", ".jpg", ".jpeg", ".webp"]
if not any(filename.lower().endswith(ext) for ext in valid_extensions):
filename += ".png" # Default to PNG
# Set default size
DEFAULT_SIZE = (512, 512)
# Create default background if not provided (black)
if background is None:
bg_np = np.zeros((DEFAULT_SIZE[1], DEFAULT_SIZE[0], 3), dtype=np.uint8)
else:
bg_np = (background.cpu().numpy()[0] * 255).astype(np.uint8)
if bg_np.shape[-1] == 4:
bg_np = bg_np[..., :3]
h, w = bg_np.shape[:2]
# Create default mask if not provided (all white)
if mask is None:
mask_np = np.ones((h, w), dtype=np.uint8) * 255
else:
mask_np = (mask.cpu().numpy()[0] * 255).astype(np.uint8)
if mask_np.shape != (h, w):
mask_np = np.array(Image.fromarray(mask_np).resize((w, h), Image.BILINEAR))
# Prepare mask for blending (0.0-1.0 float, shape HxWx1)
mask_f = (mask_np.astype(np.float32) / 255.0)[..., None]
# If foreground is provided, blend; else just use background
if foreground is not None:
fg_np = (foreground.cpu().numpy()[0] * 255).astype(np.uint8)
if fg_np.shape[-1] == 4:
fg_np = fg_np[..., :3]
if fg_np.shape[:2] != (h, w):
fg_np = np.array(Image.fromarray(fg_np).resize((w, h), Image.BILINEAR))
composited = fg_np.astype(np.float32) * mask_f + bg_np.astype(np.float32) * (1.0 - mask_f)
else:
composited = bg_np.astype(np.float32) * (1.0 - mask_f)
composited = composited.astype(np.uint8)
# Convert composited image and mask to PIL
pil_img = Image.fromarray(composited, mode="RGB")
pil_mask = Image.fromarray(mask_np, mode="L")
# Calculate bounding box of mask (nonzero areas)
bbox = pil_mask.getbbox()
if bbox:
pil_img = pil_img.crop(bbox)
# Save cropped RGB image
os.makedirs(save_dir, exist_ok=True)
save_path = os.path.join(save_dir, filename)
pil_img.save(save_path)
# Convert back to tensor
out_np = np.array(pil_img).astype(np.float32) / 255.0
out_tensor = torch.from_numpy(out_np).unsqueeze(0)
return (out_tensor,)
NODE_CLASS_MAPPINGS = {
"FSL Composite With Mask": FSL_CompositeWithMask,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"FSL Composite With Mask": "🖼️ Composite With Mask Cropped (FSL)",
}
@@ -0,0 +1,45 @@
import torch
import numpy as np
from PIL import Image
import os
class FSL_SaveAndStripAlpha:
CATEGORY = "FSL Nodes/Utils"
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image_rgb",)
FUNCTION = "process"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image_rgba": ("IMAGE",),
"filename": ("STRING", {"default": "output_rgb.png"}),
"save_dir": ("STRING", {"default": "./outputs"}),
}
}
def process(self, image_rgba, filename, save_dir):
# Convert tensor to numpy array
np_img = (image_rgba.cpu().numpy()[0] * 255).astype(np.uint8) # shape: (H, W, 4)
pil_img = Image.fromarray(np_img, mode="RGBA")
pil_rgb = pil_img.convert("RGB") # Remove alpha channel[5]
# Ensure save directory exists
os.makedirs(save_dir, exist_ok=True)
save_path = os.path.join(save_dir, filename)
pil_rgb.save(save_path)
# Convert back to tensor for ComfyUI workflow (float32, 0-1, shape [1, H, W, 3])
rgb_np = np.array(pil_rgb).astype(np.float32) / 255.0
rgb_tensor = torch.from_numpy(rgb_np).unsqueeze(0)
return (rgb_tensor,)
NODE_CLASS_MAPPINGS = {
"FSL Save And Strip Alpha": FSL_SaveAndStripAlpha,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"FSL Save And Strip Alpha": "💾 Save & Strip Alpha (FSL)",
}
+13 -14
View File
@@ -1,21 +1,25 @@
#You’ll need to create a folder in custom_nodes/ and place this as manual_alpha_mask_painter.py.
"""
CustomManualAlphaMaskPainter for Tshirt Designer Workflow
ManualAlphaMaskPainter for FSL Nodes
Version: 1.0.0
Author: Fred LeFevre
Date: 2025-04-27
Description: Custom MaskPainter Node to convert a black mask to alpha.
"""
__version__ = "1.0.0"
import numpy as np
from PIL import Image
import torch
class ManualAlphaMaskPainter:
class FSL_ManualAlphaMaskPainter:
CATEGORY = "FSL Nodes/Masking"
RETURN_TYPES = ("IMAGE", "MASK")
RETURN_NAMES = ("image_out", "mask_out")
FUNCTION = "paint_mask"
@classmethod
def INPUT_TYPES(cls):
return {
@@ -25,25 +29,20 @@ class ManualAlphaMaskPainter:
}
}
RETURN_TYPES = ("IMAGE", "MASK")
FUNCTION = "paint_mask"
CATEGORY = "image/masking"
def paint_mask(self, image, mask):
# Convert tensors to numpy arrays
image_np = image.cpu().numpy()[0] * 255
image_np = image_np.astype(np.uint8)
mask_np = mask.cpu().numpy()[0] * 255
mask_np = mask_np.astype(np.uint8)
# Create RGBA image
img_rgba = Image.fromarray(image_np).convert("RGBA")
rgba_array = np.array(img_rgba)
# Apply mask to alpha channel
rgba_array[:, :, 3] = mask_np
# Convert back to tensor format
result_image = torch.from_numpy(rgba_array.astype(np.float32) / 255.0).unsqueeze(0)
result_mask = torch.from_numpy(mask_np.astype(np.float32) / 255.0).unsqueeze(0)
@@ -51,9 +50,9 @@ class ManualAlphaMaskPainter:
return (result_image, result_mask)
NODE_CLASS_MAPPINGS = {
"ManualAlphaMaskPainter": ManualAlphaMaskPainter
"FSL Manual Alpha Mask Painter": FSL_ManualAlphaMaskPainter,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ManualAlphaMaskPainter": "Manual Alpha Mask Painter"
"FSL Manual Alpha Mask Painter": "🖌️ Manual Alpha Mask Painter (FSL)",
}
+2
View File
@@ -0,0 +1,2 @@
pillow
numpy
+10 -19
View File
@@ -1,17 +1,13 @@
# ─────────────────────────────────────────────────────────────────────────────
# Sampler Config Hub Node
# Created by: Fred & Mycroft (ChatGPT)
# Date: 2025-04-21
# Purpose: Outputs a consistent sampler config for KSampler nodes.
# Workflow: Used in Tshirt-SDXL-Gold-*.json workflows.
# ─────────────────────────────────────────────────────────────────────────────
class SamplerConfigHub:
"""
Custom node that emits a unified sampler config dictionary.
This allows centralized control over sampler settings when using multiple
KSampler nodes in a workflow (e.g., for SDXL base and refiner).
"""
CATEGORY = "FSL Nodes/Utils" # Changed to match your preferred category
RETURN_TYPES = ("SAMPLER_CONFIG",)
RETURN_NAMES = ("sampler_config",)
FUNCTION = "build_config"
@classmethod
def INPUT_TYPES(cls):
@@ -26,17 +22,12 @@ class SamplerConfigHub:
"ddim", "plms"
],),
"scheduler": (["normal", "karras", "exponential", "sgm_uniform"],),
"steps": (int, {"default": 30, "min": 1, "max": 150}),
"cfg": (float, {"default": 7.5, "min": 0.0, "max": 20.0}),
"seed": (int, {"default": 42}),
"steps": ("INT", {"default": 30, "min": 1, "max": 150}),
"cfg": ("FLOAT", {"default": 7.5, "min": 0.0, "max": 20.0}),
"seed": ("INT", {"default": 42}),
}
}
RETURN_TYPES = ("SAMPLER_CONFIG",)
RETURN_NAMES = ("sampler_config",)
FUNCTION = "build_config"
CATEGORY = "utils"
def build_config(self, sampler_name, scheduler, steps, cfg, seed):
return ({
"sampler_name": sampler_name,
@@ -47,9 +38,9 @@ class SamplerConfigHub:
},)
NODE_CLASS_MAPPINGS = {
"SamplerConfigHub": SamplerConfigHub
"FSL SamplerConfigHub": SamplerConfigHub, # Added prefix and comma
}
NODE_DISPLAY_NAME_MAPPINGS = {
"SamplerConfigHub": "Sampler Config Hub"
}
"FSL SamplerConfigHub": "🎛️ Sampler Config Hub (FSL)", # Added prefix
}
+10 -12
View File
@@ -1,16 +1,21 @@
"""
CustomSamplerNode for Tshirt Designer Workflow
FSL Sampler & Scheduler Config Node
Version: 1.0.0
Author: Fred LeFevre
Date: 2025-04-27
Description: Custom Sampler Node to feed settings to metadata.
"""
__version__ = "1.0.0"
class FSL_SamplerSchedulerConfig:
CATEGORY = "FSL Nodes/Utils"
RETURN_TYPES = ("SAMPLER", "SCHEDULER")
RETURN_NAMES = ("sampler", "scheduler")
FUNCTION = "bundle"
class SamplerSchedulerConfig:
@classmethod
def INPUT_TYPES(cls):
return {
@@ -33,20 +38,13 @@ class SamplerSchedulerConfig:
}
}
RETURN_TYPES = ("SAMPLER", "SCHEDULER")
RETURN_NAMES = ("sampler", "scheduler")
FUNCTION = "bundle"
CATEGORY = "utils"
def bundle(self, sampler_name, scheduler_name):
return (sampler_name, scheduler_name)
NODE_CLASS_MAPPINGS = {
"SamplerSchedulerConfig": SamplerSchedulerConfig
"FSL Sampler & Scheduler Config": FSL_SamplerSchedulerConfig,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"SamplerSchedulerConfig": "Sampler & Scheduler Config"
"FSL Sampler & Scheduler Config": "🛠️ Sampler & Scheduler Config (FSL)",
}
+13 -5
View File
@@ -6,20 +6,28 @@ A collection of custom ComfyUI workflows and nodes created by **Fred LeFevre**.
### 🧩 Custom Nodes: Comfyui_FSL_Nodes
- **sampler_config_hub.py**
One of two custom nodes to configure sampler and scheduler settings for workflows.
One of two custom nodes to configure sampler and scheduler settings for workflows. Used to feed data to the meta node
- **sampler_scheduler_config.py**
Two of two custom nodes to configure sampler and scheduler settings for workflows.
Two of two custom nodes to configure sampler and scheduler settings for workflows. Used to feed data to the meta node
- **manual_alpha_mask_painter.py**
Custom node to manually convert black mask areas into alpha transparency.
- **place this node folder into the custom_nodes folder - no additional action is necessary. These two nodes support the ability to feed data to the meta node.
Custom node to manually convert black mask areas into alpha transparency. Useful with GRIPTAPE when inpainting and creating the mask by right clicking on the loaded image and select load mask editor. Griptape requires an alpha channel mask.
- **8WayImgSwitch**
Image switch with eight inputs
- **fsl_composite_with_mask_cropped**
Designed to remove the Alpha Channel But Keep Transparent Parts as Background Color (not currently used)
- **fsl_save_and_strip_alpha**
Strips alpha channel from RGBA image. (not currently used)
- **How to use**
place this node folder into the custom_nodes folder - no additional action is necessary. Nodes can be found in the comfyui onscreen nodes folder by searching for FSL
Located in the [`Comfyui_FSL_Nodes/`](./Comfyui_FSL_Nodes) directory.
---
### 🎨 Workflow: T-shirt Designer
- **Tshirt-Designer-ver_1.0.json**
- **Tshirt-Designer-ver_2.0.json**
Workflow designed for generating masked transparent images for T-shirt and object printing.
- Many corrections and updates
- Full workflow details and notes are available in [`tshirt_designer/`](./tshirt_designer).
---
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

+16
View File
@@ -0,0 +1,16 @@
# Repos Mask Workflow
This is a simple workflow that allows you to reposition either the mask or the image to change what parts of the image are exposed by the mask.
## License
This project and all sub-projects are licensed under the terms of the [LICENSE](./LICENSE) provided at the repository root.
---
## Author
Created and maintained by **Fred LeFevre**.
File diff suppressed because one or more lines are too long
+53 -6
View File
@@ -1,4 +1,6 @@
# Tshirt Designer Workflow v1.0 for ComfyUI
# Tshirt Designer Workflow v2git clone https://github.com/comfyanonymous/ComfyUI.git
git clone https://github.com/comfyanonymous/ComfyUI.git
.0 for ComfyUI
## Overview
This project provides a specialized **ComfyUI** workflow designed to create **masked images** for T-shirts, apparel, and other printable products.
@@ -16,15 +18,17 @@ Originally developed for internal use, the workflow has grown into a versatile s
- **Supports Griptape API** (OpenAI + Black Forest Labs) for automated image generation.
- **Designed for upscaling** (e.g., with Topaz Photo AI).
- **Ready-to-print transparent PNG output**.
- **Automatic masking based on mask selection**
- **Upscaling module is included**
---
## Requirements
- **ComfyUI** installed and operational.
- **Custom nodes included** in this repository:
- `CustomSamplerNode`
- `CustomSchedulerNode`
- (List any others you included)
- **FSL Custom nodes included** in this workflow:
- **`Sampler Config Hub`** - supports sampler data to metadata node
- **`Manual Alpha Mask Painter`** - when creating a mask directly in the "load image" node the Mask Painter node converts the mask to Alpha hannel (required by Griptape).
- **`8 Way Image Switch`**
- **API keys** for:
- **OpenAI**
@@ -35,8 +39,25 @@ Originally developed for internal use, the workflow has grown into a versatile s
---
## Griptape Groups Explained
- **Griptape Loader**
- Text to image prompt using Griptape nodes. It is set for the Open AI driver but there are several other options available.
- **Griptape Variations**
- This standard Griptape group allows for minor variations in an image. I am not yet sure about it's value but have included it.
- **GT Image w/Mask - Inpainting**
- Use this group when doing inpainting with an image where the mask has been prepared. Make sure to use an alpha-channel mask for Griptape.
- **GT Create Mask - Inpainting**
- Use this group to upload an image and then create a mask. Right click on the uploaded image and select 'open in maskeditor'. This will create a black mask on the image. The custom node - 'Manual Alpha Mask Painter' will convert the mask to an alpha channel.
- **NOTE OF CAUTION**
- Be sure disable any API groups you aren't using to avoid using API tokens unnecessarily.
---
## Installation
1. Clone or download this repository.
1. Clone or download this repository.git clone https://github.com/comfyanonymous/ComfyUI.git
2. Copy the `custom_nodes` into your ComfyUI `custom_nodes` directory.
3. Copy the workflow `.json` file into your `users/default/workflows` directory.
4. Configure your Griptape API keys inside ComfyUI Settings -> Griptape -> API Keys.
@@ -58,6 +79,32 @@ Originally developed for internal use, the workflow has grown into a versatile s
---
## Workflow Switches Explained
![alt text](image.png)
This switch determined what path is used for image creation. For example item 1 bypassed Griptape groups and uses the image loaded in the Image Path Group. This makes it a pure SDXL workflow.
![](image-1.png)
This switch determines whether an image or a prompt will be used (or both together). It is used in conjunction with the denoise setting as defined in this table below -
![](image-2.png)
![
](image-3.png)
This switch determines if a Griptape image should be processed through the SDXL workflow for further refinement or being reinserted downstream to start the masking process.
![alt text](image-4.png)
This switch determines which image gets processed through the upscaler.
---
## License
[MIT License](LICENSE)
You are free to use, modify, and distribute this workflow with attribution.
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB