diff --git a/__init__.py b/__init__.py index 39a8c6b..51a79ea 100644 --- a/__init__.py +++ b/__init__.py @@ -1,3 +1,20 @@ -from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS +from .nodes.grayscale_image import GrayscaleImage +from .nodes.brightness_transparency import BrightnessTransparency +from .nodes.binarize_image import BinarizeImage +from .nodes.antialiasing_image import AntialiasingImage + +NODE_CLASS_MAPPINGS = { + "BrightnessTransparency": BrightnessTransparency, + "BinarizeImage": BinarizeImage, + "GrayscaleImage": GrayscaleImage, + "AntialiasingImage": AntialiasingImage, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "BrightnessTransparency": "BrightnessTransparency", + "BinarizeImage": "BinarizeImage", + "GrayscaleImage": "GrayscaleImage", + "AntialiasingImage": "AntialiasingImage", +} __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/nodes.py b/nodes.py deleted file mode 100644 index 531d599..0000000 --- a/nodes.py +++ /dev/null @@ -1,255 +0,0 @@ -import numpy as np -import torch -from PIL import Image -from .utils import convert_to_grayscale - - -class BrightnessTransparency: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "images": ( - "IMAGE", - { - "tooltip": "Input images to adjust transparency. Bright areas will become transparent." - }, - ), - } - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "run" - CATEGORY = "ComfyUI-Image-Toolkit" - - def run(self, images): - is_tensor = isinstance(images, torch.Tensor) - - if is_tensor: - if images.device.type != "cpu": - images = images.cpu() - images_np = images.numpy() - else: - images_np = images - - result_images = [] - - for image in images_np: - if image.shape[2] == 3: - rgba_image = np.zeros( - (image.shape[0], image.shape[1], 4), dtype=np.float32 - ) - rgba_image[:, :, :3] = image - rgba_image[:, :, 3] = 1.0 # Range 0.0-1.0 - else: - rgba_image = image.copy() - - # Set alpha values inversely proportional to brightness (bright=transparent, dark=opaque) - brightness = np.mean(rgba_image[:, :, :3], axis=2) - alpha = 1.0 - brightness - rgba_image[:, :, 3] = alpha - - result_images.append(rgba_image) - - result_np = np.stack(result_images) - result_tensor = torch.from_numpy(result_np) - - if is_tensor and images.device.type != "cpu": - result_tensor = result_tensor.to(images.device) - - return (result_tensor,) - - -class BinarizeImage: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "images": ("IMAGE", {"tooltip": "Images to binarize."}), - "threshold": ( - "INT", - { - "default": 127, - "min": 0, - "max": 255, - "step": 1, - "tooltip": "Values above threshold become white (1.0), below become black (0.0).", - }, - ), - } - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "run" - CATEGORY = "ComfyUI-Image-Toolkit" - - def run(self, images, threshold): - is_tensor = isinstance(images, torch.Tensor) - - if is_tensor: - if images.device.type != "cpu": - images = images.cpu() - images_np = images.numpy() - else: - images_np = images - - result_images = [] - normalized_threshold = threshold / 255.0 - - for image in images_np: - processed_image = image.copy() - grayscale = np.mean(image[:, :, :3], axis=2) - binary_mask = np.where(grayscale >= normalized_threshold, 1.0, 0.0) - - for i in range(3): - processed_image[:, :, i] = binary_mask - - result_images.append(processed_image) - - result_np = np.stack(result_images) - result_tensor = torch.from_numpy(result_np) - - if is_tensor and images.device.type != "cpu": - result_tensor = result_tensor.to(images.device) - - return (result_tensor,) - - -class GrayscaleImage: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "images": ( - "IMAGE", - {"tooltip": "Converts images to grayscale."}, - ), - } - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "run" - CATEGORY = "ComfyUI-Image-Toolkit" - - def run(self, images): - is_tensor = isinstance(images, torch.Tensor) - - if is_tensor: - if images.device.type != "cpu": - images = images.cpu() - images_np = images.numpy() - else: - images_np = images - - result_images = [] - - for image in images_np: - result_image = convert_to_grayscale(image) - result_images.append(result_image) - - result_np = np.stack(result_images) - result_tensor = torch.from_numpy(result_np) - - if is_tensor and images.device.type != "cpu": - result_tensor = result_tensor.to(images.device) - - return (result_tensor,) - - -class AntialiasingImage: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "images": ( - "IMAGE", - {"tooltip": "Images to apply antialiasing effect."}, - ), - } - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "run" - CATEGORY = "ComfyUI-Image-Toolkit" - - def run(self, images): - is_tensor = isinstance(images, torch.Tensor) - device = images.device if is_tensor else None - - # 画像をCPUに移動し、NumPy配列に変換 - if is_tensor: - if images.device.type != "cpu": - images = images.cpu() - images_np = images.numpy() - else: - images_np = images - - # LANCZOSフィルターを固定で使用 - pil_filter = ( - Image.LANCZOS - if hasattr(Image, "LANCZOS") - else Image.Resampling.LANCZOS - ) - - result_images = [] - # 固定のスケール倍率を2.0に設定 - scale_factor = 2.0 - - for image in images_np: - # NumPy配列をPIL画像に変換(0-1の範囲を0-255に変換) - pil_image = Image.fromarray( - (image[:, :, :3] * 255).astype(np.uint8) - ) - - # 元のサイズを取得 - original_size = pil_image.size - - # 固定倍率2.0に拡大 - upscaled = pil_image.resize( - ( - int(original_size[0] * scale_factor), - int(original_size[1] * scale_factor), - ), - pil_filter, - ) - - # 元のサイズに縮小(アンチエイリアス効果) - processed = upscaled.resize(original_size, pil_filter) - - # PIL画像をNumPy配列に戻す(0-255の範囲を0-1に変換) - processed_np = np.array(processed).astype(np.float32) / 255.0 - - # 結果画像の作成 - result_image = np.zeros_like(image) - result_image[:, :, :3] = processed_np - - # アルファチャンネルがある場合は保持 - if image.shape[2] == 4: - result_image[:, :, 3] = image[:, :, 3] - - result_images.append(result_image) - - # 処理結果をスタックしてテンソルに変換 - result_np = np.stack(result_images) - result_tensor = torch.from_numpy(result_np) - - # 元のデバイスに戻す - if is_tensor and device and device.type != "cpu": - result_tensor = result_tensor.to(device) - - return (result_tensor,) - - -NODE_CLASS_MAPPINGS = { - "BrightnessTransparency": BrightnessTransparency, - "BinarizeImage": BinarizeImage, - "GrayscaleImage": GrayscaleImage, - "AntialiasingImage": AntialiasingImage, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "BrightnessTransparency": "BrightnessTransparency", - "BinarizeImage": "BinarizeImage", - "GrayscaleImage": "GrayscaleImage", - "AntialiasingImage": "AntialiasingImage", -} diff --git a/nodes/__init__.py b/nodes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nodes/antialiasing_image.py b/nodes/antialiasing_image.py new file mode 100644 index 0000000..f84c9d9 --- /dev/null +++ b/nodes/antialiasing_image.py @@ -0,0 +1,87 @@ +import numpy as np +import torch +from PIL import Image + + +class AntialiasingImage: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images": ( + "IMAGE", + {"tooltip": "Images to apply antialiasing effect."}, + ), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "run" + CATEGORY = "ComfyUI-Image-Toolkit" + + def run(self, images): + is_tensor = isinstance(images, torch.Tensor) + device = images.device if is_tensor else None + + # 画像をCPUに移動し、NumPy配列に変換 + if is_tensor: + if images.device.type != "cpu": + images = images.cpu() + images_np = images.numpy() + else: + images_np = images + + # LANCZOSフィルターを固定で使用 + pil_filter = ( + Image.LANCZOS + if hasattr(Image, "LANCZOS") + else Image.Resampling.LANCZOS + ) + + result_images = [] + # 固定のスケール倍率を2.0に設定 + scale_factor = 2.0 + + for image in images_np: + # NumPy配列をPIL画像に変換(0-1の範囲を0-255に変換) + pil_image = Image.fromarray( + (image[:, :, :3] * 255).astype(np.uint8) + ) + + # 元のサイズを取得 + original_size = pil_image.size + + # 固定倍率2.0に拡大 + upscaled = pil_image.resize( + ( + int(original_size[0] * scale_factor), + int(original_size[1] * scale_factor), + ), + pil_filter, + ) + + # 元のサイズに縮小(アンチエイリアス効果) + processed = upscaled.resize(original_size, pil_filter) + + # PIL画像をNumPy配列に戻す(0-255の範囲を0-1に変換) + processed_np = np.array(processed).astype(np.float32) / 255.0 + + # 結果画像の作成 + result_image = np.zeros_like(image) + result_image[:, :, :3] = processed_np + + # アルファチャンネルがある場合は保持 + if image.shape[2] == 4: + result_image[:, :, 3] = image[:, :, 3] + + result_images.append(result_image) + + # 処理結果をスタックしてテンソルに変換 + result_np = np.stack(result_images) + result_tensor = torch.from_numpy(result_np) + + # 元のデバイスに戻す + if is_tensor and device and device.type != "cpu": + result_tensor = result_tensor.to(device) + + return (result_tensor,) diff --git a/nodes/binarize_image.py b/nodes/binarize_image.py new file mode 100644 index 0000000..36f1270 --- /dev/null +++ b/nodes/binarize_image.py @@ -0,0 +1,57 @@ +import numpy as np +import torch + + +class BinarizeImage: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images": ("IMAGE", {"tooltip": "Images to binarize."}), + "threshold": ( + "INT", + { + "default": 127, + "min": 0, + "max": 255, + "step": 1, + "tooltip": "Values above threshold become white (1.0), below become black (0.0).", + }, + ), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "run" + CATEGORY = "ComfyUI-Image-Toolkit" + + def run(self, images, threshold): + is_tensor = isinstance(images, torch.Tensor) + + if is_tensor: + if images.device.type != "cpu": + images = images.cpu() + images_np = images.numpy() + else: + images_np = images + + result_images = [] + normalized_threshold = threshold / 255.0 + + for image in images_np: + processed_image = image.copy() + grayscale = np.mean(image[:, :, :3], axis=2) + binary_mask = np.where(grayscale >= normalized_threshold, 1.0, 0.0) + + for i in range(3): + processed_image[:, :, i] = binary_mask + + result_images.append(processed_image) + + result_np = np.stack(result_images) + result_tensor = torch.from_numpy(result_np) + + if is_tensor and images.device.type != "cpu": + result_tensor = result_tensor.to(images.device) + + return (result_tensor,) diff --git a/nodes/brightness_transparency.py b/nodes/brightness_transparency.py new file mode 100644 index 0000000..ebc24f1 --- /dev/null +++ b/nodes/brightness_transparency.py @@ -0,0 +1,58 @@ +import numpy as np +import torch + + +class BrightnessTransparency: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images": ( + "IMAGE", + { + "tooltip": "Input images to adjust transparency. Bright areas will become transparent." + }, + ), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "run" + CATEGORY = "ComfyUI-Image-Toolkit" + + def run(self, images): + is_tensor = isinstance(images, torch.Tensor) + + if is_tensor: + if images.device.type != "cpu": + images = images.cpu() + images_np = images.numpy() + else: + images_np = images + + result_images = [] + + for image in images_np: + if image.shape[2] == 3: + rgba_image = np.zeros( + (image.shape[0], image.shape[1], 4), dtype=np.float32 + ) + rgba_image[:, :, :3] = image + rgba_image[:, :, 3] = 1.0 # Range 0.0-1.0 + else: + rgba_image = image.copy() + + # Set alpha values inversely proportional to brightness (bright=transparent, dark=opaque) + brightness = np.mean(rgba_image[:, :, :3], axis=2) + alpha = 1.0 - brightness + rgba_image[:, :, 3] = alpha + + result_images.append(rgba_image) + + result_np = np.stack(result_images) + result_tensor = torch.from_numpy(result_np) + + if is_tensor and images.device.type != "cpu": + result_tensor = result_tensor.to(images.device) + + return (result_tensor,) diff --git a/nodes/grayscale_image.py b/nodes/grayscale_image.py new file mode 100644 index 0000000..0c1dac5 --- /dev/null +++ b/nodes/grayscale_image.py @@ -0,0 +1,44 @@ +import numpy as np +import torch +from ..utils import convert_to_grayscale + + +class GrayscaleImage: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images": ( + "IMAGE", + {"tooltip": "Converts images to grayscale."}, + ), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "run" + CATEGORY = "ComfyUI-Image-Toolkit" + + def run(self, images): + is_tensor = isinstance(images, torch.Tensor) + + if is_tensor: + if images.device.type != "cpu": + images = images.cpu() + images_np = images.numpy() + else: + images_np = images + + result_images = [] + + for image in images_np: + result_image = convert_to_grayscale(image) + result_images.append(result_image) + + result_np = np.stack(result_images) + result_tensor = torch.from_numpy(result_np) + + if is_tensor and images.device.type != "cpu": + result_tensor = result_tensor.to(images.device) + + return (result_tensor,)