diff --git a/WAS_ConditioningBlend.py b/ConditioningBlend.py similarity index 97% rename from WAS_ConditioningBlend.py rename to ConditioningBlend.py index 4279f7c..b76524d 100644 --- a/WAS_ConditioningBlend.py +++ b/ConditioningBlend.py @@ -1,189 +1,189 @@ -import torch -import math - -def normalize(latent, target_min=None, target_max=None): - """ - Normalize a tensor `latent` between `target_min` and `target_max`. - - Args: - latent (torch.Tensor): The input tensor to be normalized. - target_min (float, optional): The minimum value after normalization. - - When `None` min will be tensor min range value. - target_max (float, optional): The maximum value after normalization. - - When `None` max will be tensor max range value. - - Returns: - torch.Tensor: The normalized tensor - """ - min_val = latent.min() - max_val = latent.max() - - if target_min is None: - target_min = min_val - if target_max is None: - target_max = max_val - - normalized = (latent - min_val) / (max_val - min_val) - scaled = normalized * (target_max - target_min) + target_min - return scaled - -def slerp(a, b, t): - """ - Perform Spherical Linear Interpolation (SLERP) between two tensors. - - This function interpolates between two input tensors `a` and `b` using SLERP, - which is a method for smoothly transitioning between orientations or vectors - represented as tensors. - - Args: - a (tensor): The first input tensor. - b (tensor): The second input tensor. - t (float): The blending factor, a value between 0 and 1 that controls the interpolation. - - Returns: - tensor: The result of SLERP interpolation between `a` and `b`. - - Note: - SLERP provides a smooth, shortest-path interpolation between two orientations or vectors - represented as tensors. It's commonly used in applications like 3D graphics and robotics. - """ - if a.shape != b.shape: - raise ValueError("Input tensors a and b must have the same shape.") - - a = torch.nn.functional.normalize(a, dim=-1) - b = torch.nn.functional.normalize(b, dim=-1) - - dot_product = torch.sum(a * b, dim=-1).clamp(-1.0, 1.0) - angle = torch.acos(dot_product) - - slerp_result = ( - (a * torch.sin((1 - t) * angle) + b * torch.sin(t * angle)) / - torch.sin(angle) - ) - - slerp_result = normalize(slerp_result) - - return slerp_result - -def hslerp(a, b, t): - """ - Perform Hybrid Spherical Linear Interpolation (HSLERP) between two tensors. - - This function combines two input tensors `a` and `b` using HSLERP, which is a specialized - interpolation method for smooth transitions between orientations or colors. - - Args: - a (tensor): The first input tensor. - b (tensor): The second input tensor. - t (float): The blending factor, a value between 0 and 1 that controls the interpolation. - - Returns: - tensor: The result of HSLERP interpolation between `a` and `b`. - - Note: - HSLERP provides smooth transitions between orientations or colors, particularly useful - in applications like image processing and 3D graphics. - """ - if a.shape != b.shape: - raise ValueError("Input tensors a and b must have the same shape.") - - num_channels = a.size(1) - - interpolation_tensor = torch.zeros(1, num_channels, 1, 1, device=a.device, dtype=a.dtype) - interpolation_tensor[0, 0, 0, 0] = 1.0 - - result = (1 - t) * a + t * b - - if t < 0.5: - result += (torch.norm(b - a, dim=1, keepdim=True) / 6) * interpolation_tensor - else: - result -= (torch.norm(b - a, dim=1, keepdim=True) / 6) * interpolation_tensor - - return result - -import torch - -blending_modes = { - # Linearly combines the two input tensors a and b using the parameter t. - 'add': lambda a, b, t: (a * t + b * (1 - t)), - - # Interpolates between tensors a and b using normalized linear interpolation. - 'bislerp': lambda a, b, t: (a * (1 - t) + b * t), - - # Interpolates between tensors a and b using cosine interpolation. - 'cosine interp': lambda a, b, t: (a + b - (a - b) * torch.cos(t * torch.tensor(math.pi))) / 2, - - # Interpolates between tensors a and b using cubic interpolation. - 'cuberp': lambda a, b, t: a + (b - a) * (3 * t ** 2 - 2 * t ** 3), - - # Computes the absolute difference between tensors a and b, scaled by t. - 'difference': lambda a, b, t: (abs(a - b) * t), - - # Combines tensors a and b using an exclusion formula, scaled by t. - 'exclusion': lambda a, b, t: ((a + b - 2 * a * b) * t), - - # Interpolates between tensors a and b using normalized linear interpolation, - # with a twist when t is greater than or equal to 0.5. - 'hslerp': lambda a, b, t: (a * (1 - t) + b * t) if t < 0.5 else (a * t + b * (1 - t)), - - # Adds tensor b to tensor a, scaled by t. - 'inject': lambda a, b, t: (a + b * t), - - # Interpolates between tensors a and b using linear interpolation. - 'lerp': lambda a, b, t: (a * (1 - t) + b * t), - - # Generates random values and combines tensors a and b with random weights, scaled by t. - 'random': lambda a, b, t: (a + (torch.rand_like(b) * b - a) * t), - - # Interpolates between tensors a and b using spherical linear interpolation (SLERP). - 'slerp': lambda a, b, t: (a * (1 - t) + b * t), - - # Subtracts tensor b from tensor a, scaled by t. - 'subtract': lambda a, b, t: (a * t - b * t), -} - -class WAS_ConditioningBlend: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "conditioning_a": ("CONDITIONING", ), - "conditioning_b": ("CONDITIONING", ), - "blending_mode": (list(blending_modes.keys()), ), - "blending_strength": ("FLOAT", {"default": 0.5, "min": -10.0, "max": 10.0, "step": 0.001}), - "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), - } - } - - RETURN_TYPES = ("CONDITIONING",) - RETURN_NAMES = ("conditioning",) - FUNCTION = "combine" - - CATEGORY = "conditioning" - - def combine(self, conditioning_a, conditioning_b, blending_mode, blending_strength, seed): - - if seed > 0: - torch.manual_seed(seed) - - a = conditioning_a[0][0].clone() - b = conditioning_b[0][0].clone() - - pa = conditioning_a[0][1]["pooled_output"].clone() - pb = conditioning_b[0][1]["pooled_output"].clone() - - cond = normalize(blending_modes[blending_mode](a, b, 1 - blending_strength)) - pooled = normalize(blending_modes[blending_mode](pa, pb, 1 - blending_strength)) - - conditioning = [[cond, {"pooled_output": pooled}]] - - return (conditioning, ) - - -NODE_CLASS_MAPPINGS = { - "ConditioningBlend": WAS_ConditioningBlend, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "ConditioningBlend": "Conditioning (Blend)", -} +import torch +import math + +def normalize(latent, target_min=None, target_max=None): + """ + Normalize a tensor `latent` between `target_min` and `target_max`. + + Args: + latent (torch.Tensor): The input tensor to be normalized. + target_min (float, optional): The minimum value after normalization. + - When `None` min will be tensor min range value. + target_max (float, optional): The maximum value after normalization. + - When `None` max will be tensor max range value. + + Returns: + torch.Tensor: The normalized tensor + """ + min_val = latent.min() + max_val = latent.max() + + if target_min is None: + target_min = min_val + if target_max is None: + target_max = max_val + + normalized = (latent - min_val) / (max_val - min_val) + scaled = normalized * (target_max - target_min) + target_min + return scaled + +def slerp(a, b, t): + """ + Perform Spherical Linear Interpolation (SLERP) between two tensors. + + This function interpolates between two input tensors `a` and `b` using SLERP, + which is a method for smoothly transitioning between orientations or vectors + represented as tensors. + + Args: + a (tensor): The first input tensor. + b (tensor): The second input tensor. + t (float): The blending factor, a value between 0 and 1 that controls the interpolation. + + Returns: + tensor: The result of SLERP interpolation between `a` and `b`. + + Note: + SLERP provides a smooth, shortest-path interpolation between two orientations or vectors + represented as tensors. It's commonly used in applications like 3D graphics and robotics. + """ + if a.shape != b.shape: + raise ValueError("Input tensors a and b must have the same shape.") + + a = torch.nn.functional.normalize(a, dim=-1) + b = torch.nn.functional.normalize(b, dim=-1) + + dot_product = torch.sum(a * b, dim=-1).clamp(-1.0, 1.0) + angle = torch.acos(dot_product) + + slerp_result = ( + (a * torch.sin((1 - t) * angle) + b * torch.sin(t * angle)) / + torch.sin(angle) + ) + + slerp_result = normalize(slerp_result) + + return slerp_result + +def hslerp(a, b, t): + """ + Perform Hybrid Spherical Linear Interpolation (HSLERP) between two tensors. + + This function combines two input tensors `a` and `b` using HSLERP, which is a specialized + interpolation method for smooth transitions between orientations or colors. + + Args: + a (tensor): The first input tensor. + b (tensor): The second input tensor. + t (float): The blending factor, a value between 0 and 1 that controls the interpolation. + + Returns: + tensor: The result of HSLERP interpolation between `a` and `b`. + + Note: + HSLERP provides smooth transitions between orientations or colors, particularly useful + in applications like image processing and 3D graphics. + """ + if a.shape != b.shape: + raise ValueError("Input tensors a and b must have the same shape.") + + num_channels = a.size(1) + + interpolation_tensor = torch.zeros(1, num_channels, 1, 1, device=a.device, dtype=a.dtype) + interpolation_tensor[0, 0, 0, 0] = 1.0 + + result = (1 - t) * a + t * b + + if t < 0.5: + result += (torch.norm(b - a, dim=1, keepdim=True) / 6) * interpolation_tensor + else: + result -= (torch.norm(b - a, dim=1, keepdim=True) / 6) * interpolation_tensor + + return result + +import torch + +blending_modes = { + # Linearly combines the two input tensors a and b using the parameter t. + 'add': lambda a, b, t: (a * t + b * (1 - t)), + + # Interpolates between tensors a and b using normalized linear interpolation. + 'bislerp': lambda a, b, t: (a * (1 - t) + b * t), + + # Interpolates between tensors a and b using cosine interpolation. + 'cosine interp': lambda a, b, t: (a + b - (a - b) * torch.cos(t * torch.tensor(math.pi))) / 2, + + # Interpolates between tensors a and b using cubic interpolation. + 'cuberp': lambda a, b, t: a + (b - a) * (3 * t ** 2 - 2 * t ** 3), + + # Computes the absolute difference between tensors a and b, scaled by t. + 'difference': lambda a, b, t: (abs(a - b) * t), + + # Combines tensors a and b using an exclusion formula, scaled by t. + 'exclusion': lambda a, b, t: ((a + b - 2 * a * b) * t), + + # Interpolates between tensors a and b using normalized linear interpolation, + # with a twist when t is greater than or equal to 0.5. + 'hslerp': lambda a, b, t: (a * (1 - t) + b * t) if t < 0.5 else (a * t + b * (1 - t)), + + # Adds tensor b to tensor a, scaled by t. + 'inject': lambda a, b, t: (a + b * t), + + # Interpolates between tensors a and b using linear interpolation. + 'lerp': lambda a, b, t: (a * (1 - t) + b * t), + + # Generates random values and combines tensors a and b with random weights, scaled by t. + 'random': lambda a, b, t: (a + (torch.rand_like(b) * b - a) * t), + + # Interpolates between tensors a and b using spherical linear interpolation (SLERP). + 'slerp': lambda a, b, t: (a * (1 - t) + b * t), + + # Subtracts tensor b from tensor a, scaled by t. + 'subtract': lambda a, b, t: (a * t - b * t), +} + +class WAS_ConditioningBlend: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "conditioning_a": ("CONDITIONING", ), + "conditioning_b": ("CONDITIONING", ), + "blending_mode": (list(blending_modes.keys()), ), + "blending_strength": ("FLOAT", {"default": 0.5, "min": -10.0, "max": 10.0, "step": 0.001}), + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + } + } + + RETURN_TYPES = ("CONDITIONING",) + RETURN_NAMES = ("conditioning",) + FUNCTION = "combine" + + CATEGORY = "conditioning" + + def combine(self, conditioning_a, conditioning_b, blending_mode, blending_strength, seed): + + if seed > 0: + torch.manual_seed(seed) + + a = conditioning_a[0][0].clone() + b = conditioning_b[0][0].clone() + + pa = conditioning_a[0][1]["pooled_output"].clone() + pb = conditioning_b[0][1]["pooled_output"].clone() + + cond = normalize(blending_modes[blending_mode](a, b, 1 - blending_strength)) + pooled = normalize(blending_modes[blending_mode](pa, pb, 1 - blending_strength)) + + conditioning = [[cond, {"pooled_output": pooled}]] + + return (conditioning, ) + + +NODE_CLASS_MAPPINGS = { + "ConditioningBlend": WAS_ConditioningBlend, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "ConditioningBlend": "Conditioning (Blend)", +} diff --git a/WAS_VAEEncodeForInpaint.py b/VAEEncodeForInpaint.py similarity index 97% rename from WAS_VAEEncodeForInpaint.py rename to VAEEncodeForInpaint.py index 2d14e04..2989dbc 100644 --- a/WAS_VAEEncodeForInpaint.py +++ b/VAEEncodeForInpaint.py @@ -1,61 +1,61 @@ -# Provides demonstration of PR https://github.com/comfyanonymous/ComfyUI/pull/1574/commits/297d1cff422198806cda40e4b6d71a6e6aa05453 - -import torch - -class WAS_VAEEncodeForInpaint: - @classmethod - def INPUT_TYPES(s): - return {"required": { "pixels": ("IMAGE", ), "vae": ("VAE", ), "mask": ("MASK", ), "mask_offset": ("INT", {"default": 6, "min": -128, "max": 128, "step": 1}),}} - RETURN_TYPES = ("LATENT",) - FUNCTION = "encode" - - CATEGORY = "latent/inpaint" - - def encode(self, vae, pixels, mask, mask_offset=6): - x = (pixels.shape[1] // 8) * 8 - y = (pixels.shape[2] // 8) * 8 - mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(pixels.shape[1], pixels.shape[2]), mode="bilinear") - - pixels = pixels.clone() - if pixels.shape[1] != x or pixels.shape[2] != y: - x_offset = (pixels.shape[1] % 8) // 2 - y_offset = (pixels.shape[2] % 8) // 2 - pixels = pixels[:,x_offset:x + x_offset, y_offset:y + y_offset,:] - mask = mask[:,:,x_offset:x + x_offset, y_offset:y + y_offset] - - mask_erosion = self.modify_mask(mask, mask_offset) - - m = (1.0 - mask_erosion.round()).squeeze(1) - for i in range(3): - pixels[:,:,:,i] -= 0.5 - pixels[:,:,:,i] *= m - pixels[:,:,:,i] += 0.5 - t = vae.encode(pixels) - - return ({"samples":t, "noise_mask": (mask_erosion[:,:,:x,:y].round())}, ) - - def modify_mask(self, mask, modify_by): - if modify_by == 0: - return mask - if modify_by > 0: - kernel_size = 2 * modify_by + 1 - kernel_tensor = torch.ones((1, 1, kernel_size, kernel_size)) - padding = modify_by - modified_mask = torch.clamp(torch.nn.functional.conv2d(mask.round(), kernel_tensor, padding=padding), 0, 1) - else: - kernel_size = 2 * abs(modify_by) + 1 - kernel_tensor = torch.ones((1, 1, kernel_size, kernel_size)) - padding = abs(modify_by) - eroded_mask = torch.nn.functional.conv2d(1 - mask.round(), kernel_tensor, padding=padding) - modified_mask = torch.clamp(1 - eroded_mask, 0, 1) - return modified_mask - - - -NODE_CLASS_MAPPINGS = { - "VAEEncodeForInpaint (WAS)": WAS_VAEEncodeForInpaint, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "VAEEncodeForInpaint (WAS)": "Inpainting VAE Encode (WAS)", +# Provides demonstration of PR https://github.com/comfyanonymous/ComfyUI/pull/1574/commits/297d1cff422198806cda40e4b6d71a6e6aa05453 + +import torch + +class WAS_VAEEncodeForInpaint: + @classmethod + def INPUT_TYPES(s): + return {"required": { "pixels": ("IMAGE", ), "vae": ("VAE", ), "mask": ("MASK", ), "mask_offset": ("INT", {"default": 6, "min": -128, "max": 128, "step": 1}),}} + RETURN_TYPES = ("LATENT",) + FUNCTION = "encode" + + CATEGORY = "latent/inpaint" + + def encode(self, vae, pixels, mask, mask_offset=6): + x = (pixels.shape[1] // 8) * 8 + y = (pixels.shape[2] // 8) * 8 + mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(pixels.shape[1], pixels.shape[2]), mode="bilinear") + + pixels = pixels.clone() + if pixels.shape[1] != x or pixels.shape[2] != y: + x_offset = (pixels.shape[1] % 8) // 2 + y_offset = (pixels.shape[2] % 8) // 2 + pixels = pixels[:,x_offset:x + x_offset, y_offset:y + y_offset,:] + mask = mask[:,:,x_offset:x + x_offset, y_offset:y + y_offset] + + mask_erosion = self.modify_mask(mask, mask_offset) + + m = (1.0 - mask_erosion.round()).squeeze(1) + for i in range(3): + pixels[:,:,:,i] -= 0.5 + pixels[:,:,:,i] *= m + pixels[:,:,:,i] += 0.5 + t = vae.encode(pixels) + + return ({"samples":t, "noise_mask": (mask_erosion[:,:,:x,:y].round())}, ) + + def modify_mask(self, mask, modify_by): + if modify_by == 0: + return mask + if modify_by > 0: + kernel_size = 2 * modify_by + 1 + kernel_tensor = torch.ones((1, 1, kernel_size, kernel_size)) + padding = modify_by + modified_mask = torch.clamp(torch.nn.functional.conv2d(mask.round(), kernel_tensor, padding=padding), 0, 1) + else: + kernel_size = 2 * abs(modify_by) + 1 + kernel_tensor = torch.ones((1, 1, kernel_size, kernel_size)) + padding = abs(modify_by) + eroded_mask = torch.nn.functional.conv2d(1 - mask.round(), kernel_tensor, padding=padding) + modified_mask = torch.clamp(1 - eroded_mask, 0, 1) + return modified_mask + + + +NODE_CLASS_MAPPINGS = { + "VAEEncodeForInpaint (WAS)": WAS_VAEEncodeForInpaint, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "VAEEncodeForInpaint (WAS)": "Inpainting VAE Encode (WAS)", } \ No newline at end of file diff --git a/WAS_VividSharpen.py b/VividSharpen.py similarity index 96% rename from WAS_VividSharpen.py rename to VividSharpen.py index db2bca7..7e5afc3 100644 --- a/WAS_VividSharpen.py +++ b/VividSharpen.py @@ -1,101 +1,101 @@ -import torch -import numpy as np -from PIL import Image, ImageOps, ImageFilter, ImageEnhance - -# Tensor to PIL -def tensor2pil(image): - return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8)) - -# PIL to Tensor -def pil2tensor(image): - return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0) - -# Vivid Light and Overlay methods adopted from layeris (an overlooked gem) -# https://github.com/subwaymatch/layer-is-python -def vivid_light(A, B, opacity=1.0): - with np.errstate(divide='ignore', invalid='ignore'): - b = np.where(B > 0, 1 - (1 - A) / (2 * B), 0) - d = np.where(B < 1, A / (2 * (1 - B)), 1) - - result = np.clip(np.where(B <= 0.5, b, d), 0, 1) - return alpha_blend(A, result, opacity) - -def overlay(A, B, opacity=1.0): - B = rgb_float_if_hex(B) - d1 = (2 * A) * B - d2 = 1 - 2 * (1 - A) * (1 - B) - result = np.where(A <= 0.5, d1, d2) - return alpha_blend(A, result, opacity) - -def alpha_blend(base, blend, opacity): - if opacity < 1.0: - return base * (1.0 - opacity) + blend * opacity - return blend - -def hex_to_rgb_float(hex_string): - return np.array(list((int(hex_string.lstrip('#')[i:i + 2], 16) / 255) for i in (0, 2, 4))) - -def rgb_float_if_hex(blend_data): - if isinstance(blend_data, str): - return hex_to_rgb_float(blend_data) - return blend_data - -def vivid_sharpen(image, radius=5, strength=1.0): - original = image.copy() - sg = Image.new('RGB', original.size, (255, 255, 255)) - sg.paste(original, (0, 0)) - sg = ImageOps.invert(sg) - sg = sg.filter(ImageFilter.GaussianBlur(radius=radius)) - - original_data = np.array(original).astype(float) / 255.0 - sg_data = np.array(sg).astype(float) / 255.0 - - result_data = vivid_light(original_data, sg_data, 1.0) - result_data = overlay(original_data, result_data, 1.0) - - result_image = Image.fromarray((result_data * 255).astype('uint8')) - result_image = Image.blend(original, result_image, strength) - - return result_image - -class VividSharpen: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "images": ("IMAGE",), - "radius": ("FLOAT", {"default": 1.5, "min": 0.01, "max": 64.0, "step": 0.01}), - "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - }, - } - - RETURN_TYPES = ("IMAGE",) - RETURN_NAMES = ("images",) - - FUNCTION = "sharpen" - - CATEGORY = "image/postprocessing" - - def sharpen(self, images, radius, strength): - - results = [] - if images.size(0) > 1: - for image in images: - image = tensor2pil(image) - results.append(pil2tensor(vivid_sharpen(image, radius=radius, strength=strength))) - results = torch.cat(results, dim=0) - else: - results = pil2tensor(vivid_sharpen(tensor2pil(images), radius=radius, strength=strength)) - - return (results,) - -NODE_CLASS_MAPPINGS = { - "VividSharpen": VividSharpen, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "VividSharpen": "VividSharpen", -} +import torch +import numpy as np +from PIL import Image, ImageOps, ImageFilter, ImageEnhance + +# Tensor to PIL +def tensor2pil(image): + return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8)) + +# PIL to Tensor +def pil2tensor(image): + return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0) + +# Vivid Light and Overlay methods adopted from layeris (an overlooked gem) +# https://github.com/subwaymatch/layer-is-python +def vivid_light(A, B, opacity=1.0): + with np.errstate(divide='ignore', invalid='ignore'): + b = np.where(B > 0, 1 - (1 - A) / (2 * B), 0) + d = np.where(B < 1, A / (2 * (1 - B)), 1) + + result = np.clip(np.where(B <= 0.5, b, d), 0, 1) + return alpha_blend(A, result, opacity) + +def overlay(A, B, opacity=1.0): + B = rgb_float_if_hex(B) + d1 = (2 * A) * B + d2 = 1 - 2 * (1 - A) * (1 - B) + result = np.where(A <= 0.5, d1, d2) + return alpha_blend(A, result, opacity) + +def alpha_blend(base, blend, opacity): + if opacity < 1.0: + return base * (1.0 - opacity) + blend * opacity + return blend + +def hex_to_rgb_float(hex_string): + return np.array(list((int(hex_string.lstrip('#')[i:i + 2], 16) / 255) for i in (0, 2, 4))) + +def rgb_float_if_hex(blend_data): + if isinstance(blend_data, str): + return hex_to_rgb_float(blend_data) + return blend_data + +def vivid_sharpen(image, radius=5, strength=1.0): + original = image.copy() + sg = Image.new('RGB', original.size, (255, 255, 255)) + sg.paste(original, (0, 0)) + sg = ImageOps.invert(sg) + sg = sg.filter(ImageFilter.GaussianBlur(radius=radius)) + + original_data = np.array(original).astype(float) / 255.0 + sg_data = np.array(sg).astype(float) / 255.0 + + result_data = vivid_light(original_data, sg_data, 1.0) + result_data = overlay(original_data, result_data, 1.0) + + result_image = Image.fromarray((result_data * 255).astype('uint8')) + result_image = Image.blend(original, result_image, strength) + + return result_image + +class VividSharpen: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "images": ("IMAGE",), + "radius": ("FLOAT", {"default": 1.5, "min": 0.01, "max": 64.0, "step": 0.01}), + "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + }, + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("images",) + + FUNCTION = "sharpen" + + CATEGORY = "image/postprocessing" + + def sharpen(self, images, radius, strength): + + results = [] + if images.size(0) > 1: + for image in images: + image = tensor2pil(image) + results.append(pil2tensor(vivid_sharpen(image, radius=radius, strength=strength))) + results = torch.cat(results, dim=0) + else: + results = pil2tensor(vivid_sharpen(tensor2pil(images), radius=radius, strength=strength)) + + return (results,) + +NODE_CLASS_MAPPINGS = { + "VividSharpen": VividSharpen, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "VividSharpen": "VividSharpen", +} diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..159d650 --- /dev/null +++ b/__init__.py @@ -0,0 +1,38 @@ +import importlib +import time + +extras = [ + ".ConditioningBlend", + ".VAEEncodeForInpaint", + ".VividSharpen", +] + +NODE_CLASS_MAPPINGS = {} +NODE_DISPLAY_NAME_MAPPINGS = {} +module_timings = {} + +print("[\033[94m\033[1mWAS Extras\033[0m] Loading extra custom nodes...") + +for module_name in extras: + start_time = time.time() + + success = True + try: + module = importlib.import_module(module_name, package=__name__) + except Exception: + success = False + pass + + end_time = time.time() + timing = end_time - start_time + + module_timings[module.__file__] = (timing, success) + + NODE_CLASS_MAPPINGS.update(getattr(module, 'NODE_CLASS_MAPPINGS', {})) + NODE_DISPLAY_NAME_MAPPINGS.update(getattr(module, 'NODE_DISPLAY_NAME_MAPPINGS', {})) + +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] + +print("[\033[94m\033[1mWAS Extras\033[0m] Import times for extras:") +for module, (timing, success) in module_timings.items(): + print(f" {timing:.1f} seconds{('' if success else ' (IMPORT FAILED)')}: {module}") diff --git a/__pycache__/ConditioningBlend.cpython-310.pyc b/__pycache__/ConditioningBlend.cpython-310.pyc new file mode 100644 index 0000000..b47c409 Binary files /dev/null and b/__pycache__/ConditioningBlend.cpython-310.pyc differ diff --git a/__pycache__/VAEEncodeForInpaint.cpython-310.pyc b/__pycache__/VAEEncodeForInpaint.cpython-310.pyc new file mode 100644 index 0000000..332785c Binary files /dev/null and b/__pycache__/VAEEncodeForInpaint.cpython-310.pyc differ diff --git a/__pycache__/VividSharpen.cpython-310.pyc b/__pycache__/VividSharpen.cpython-310.pyc new file mode 100644 index 0000000..2128972 Binary files /dev/null and b/__pycache__/VividSharpen.cpython-310.pyc differ diff --git a/__pycache__/__init__.cpython-310.pyc b/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..a23a726 Binary files /dev/null and b/__pycache__/__init__.cpython-310.pyc differ