Painter Node¶
Documentation¶
- Class name:
PainterNode
- Category:
AlekPet Nodes/image
- Output node:
False
The PainterNode is designed to facilitate image processing and manipulation within a custom node environment. It enables the dynamic updating of images based on user input, supports asynchronous communication with a server for image updates, and manages node-specific settings for personalized image processing workflows.
Input types¶
Required¶
image
- The primary image input for processing. It serves as the base for any transformations or updates applied by the node.
- Comfy dtype:
COMBO[STRING]
- Python dtype:
torch.Tensor
Optional¶
images
- An optional list of images to be processed or updated. These images can be used for batch processing or as additional inputs for the node's operations.
- Comfy dtype:
IMAGE
- Python dtype:
List[torch.Tensor]
update_node
- A flag indicating whether the node should update its internal state based on the provided images. This affects how images are processed and integrated into the node's workflow.
- Comfy dtype:
COMBO[BOOLEAN]
- Python dtype:
bool
Output types¶
image
- Comfy dtype:
IMAGE
- The processed image after applying the node's operations, ready for further use or display.
- Python dtype:
torch.Tensor
- Comfy dtype:
mask
- Comfy dtype:
MASK
- An optional mask generated during the image processing, which can be used for further image manipulation or analysis.
- Python dtype:
torch.Tensor
- Comfy dtype:
Usage tips¶
- Infra type:
GPU
- Common nodes:
Source code¶
class PainterNode(object):
@classmethod
def INPUT_TYPES(self):
self.canvas_set = False
work_dir = folder_paths.get_input_directory()
imgs = [
img
for img in os.listdir(work_dir)
if os.path.isfile(os.path.join(work_dir, img))
]
return {
"required": {"image": (sorted(imgs),)},
"hidden": {"unique_id": "UNIQUE_ID"},
"optional": {"images": ("IMAGE",), "update_node": (([True, False],))},
}
RETURN_TYPES = ("IMAGE", "MASK")
FUNCTION = "painter_execute"
CATEGORY = "AlekPet Nodes/image"
def painter_execute(self, image, unique_id, update_node=True, images=None):
# Piping image input
if unique_id not in PAINTER_DICT:
PAINTER_DICT[unique_id] = self
if update_node == True and images is not None:
input_images = []
for imgs in images:
i = 255.0 * imgs.cpu().numpy()
i = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
input_images.append(toBase64ImgUrl(i))
PAINTER_DICT[unique_id].canvas_set = False
PromptServer.instance.send_sync(
"alekpet_get_image", {"unique_id": unique_id, "images": input_images}
)
if not asyncio.run(wait_canvas_change(unique_id)):
print(f"Painter_{unique_id}: Failed to get image!")
else:
print(f"Painter_{unique_id}: Image received, canvas changed!")
# end - Piping image input
image_path = folder_paths.get_annotated_filepath(image)
i = Image.open(image_path)
i = ImageOps.exif_transpose(i)
image = i.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
if "A" in i.getbands():
mask = np.array(i.getchannel("A")).astype(np.float32) / 255.0
mask = 1.0 - torch.from_numpy(mask)
else:
mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu")
return (image, mask.unsqueeze(0))
@classmethod
def IS_CHANGED(self, image, unique_id, update_node=True, images=None):
image_path = folder_paths.get_annotated_filepath(image)
m = hashlib.sha256()
with open(image_path, "rb") as f:
m.update(f.read())
return m.digest().hex()
@classmethod
def VALIDATE_INPUTS(self, image, unique_id, update_node=True, images=None):
if not folder_paths.exists_annotated_filepath(image):
return "Invalid image file: {}".format(image)
return True