diff --git a/node_mappings.py b/node_mappings.py index ac62970..da5c6f4 100644 --- a/node_mappings.py +++ b/node_mappings.py @@ -64,13 +64,13 @@ except ImportError: print(f"{YELLOW}TenserTensor: {RED}ERROR: Loader nodes unavailable{RESET}") try: - before = set(dir()) from .nodes.postproduction.add_film_grain import TT_AddFilmGrain from .nodes.postproduction.apply_lut import TT_ApplyLut from .nodes.postproduction.image_enhancer import TT_ImageEnhancer from .nodes.postproduction.postproduction import TT_Postproduction from .nodes.postproduction.postproduction_advanced import TT_PostproductionAdvanced from .nodes.postproduction.quick_image_upscaler import TT_QuickImageUpscaler + from .nodes_postproduction import * except ImportError: print(f"{YELLOW}TenserTensor: {RED}ERROR: Postproduction nodes unavailable{RESET}") @@ -192,6 +192,13 @@ NODE_CLASS_MAPPINGS = { "TT_ImageEnhancer": TT_ImageEnhancer, "TT_Postproduction": TT_Postproduction, "TT_PostproductionAdvanced": TT_PostproductionAdvanced, + # Postproduction V3 Nodes + "TT_AddFilmGrainNode": TT_AddFilmGrainNode, + "TT_ApplyLutNode": TT_ApplyLutNode, + "TT_ImageEnhancerNode": TT_ImageEnhancerNode, + "TT_QuickImageUpscalerNode": TT_QuickImageUpscalerNode, + "TT_PostproductionNode": TT_PostproductionNode, + "TT_PostproductionAdvancedNode": TT_PostproductionAdvancedNode, # Samplers "TT_KSampler": TT_KSampler, "TT_KSamplerAdvanced": TT_KSamplerAdvanced, @@ -313,6 +320,13 @@ NODE_DISPLAY_NAME_MAPPINGS = { "TT_ImageEnhancer": "TT Image Enhancer", "TT_Postproduction": "TT Postproduction", "TT_PostproductionAdvanced": "TT Postproduction (Advanced)", + # Postproduction V3 Nodes + "TT_AddFilmGrainNode": "TT Add Film Grain", + "TT_ApplyLutNode": "TT Apply LUT", + "TT_ImageEnhancerNode": "TT Image Enhancer", + "TT_QuickImageUpscalerNode": "TT Quick Image Upscaler", + "TT_PostproductionNode": "TT Postproduction", + "TT_PostproductionAdvancedNode": "TT Postproduction (Advanced)", # Samplers "TT_KSampler": "TT KSampler", "TT_KSamplerAdvanced": "TT KSampler (Advanced)", diff --git a/nodes/postproduction/add_film_grain.py b/nodes/postproduction/add_film_grain.py index b2c811f..3e3a811 100644 --- a/nodes/postproduction/add_film_grain.py +++ b/nodes/postproduction/add_film_grain.py @@ -24,7 +24,7 @@ class TT_AddFilmGrain(): RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("IMAGE",) FUNCTION = "apply_film_grain" - CATEGORY = "TenserTensor/Postproduction" + CATEGORY = "TenserTensor/Deprecated/Postproduction" def apply_film_grain(self, **kwargs): return (apply_film_grain( diff --git a/nodes/postproduction/apply_lut.py b/nodes/postproduction/apply_lut.py index cde9c99..29e98aa 100644 --- a/nodes/postproduction/apply_lut.py +++ b/nodes/postproduction/apply_lut.py @@ -23,7 +23,7 @@ class TT_ApplyLut: RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("IMAGE",) FUNCTION = "apply_lut" - CATEGORY = "TenserTensor/Postproduction" + CATEGORY = "TenserTensor/Deprecated/Postproduction" def apply_lut(self, image, lut_file, strength, colorspace): return ( diff --git a/nodes/postproduction/image_enhancer.py b/nodes/postproduction/image_enhancer.py index 6201b74..8bd2fbd 100644 --- a/nodes/postproduction/image_enhancer.py +++ b/nodes/postproduction/image_enhancer.py @@ -24,7 +24,7 @@ class TT_ImageEnhancer(): RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("IMAGE",) FUNCTION = "enhance_image" - CATEGORY = "TenserTensor/Postproduction" + CATEGORY = "TenserTensor/Deprecated/Postproduction" def enhance_image( self, diff --git a/nodes/postproduction/postproduction.py b/nodes/postproduction/postproduction.py index d40e62a..5684b29 100644 --- a/nodes/postproduction/postproduction.py +++ b/nodes/postproduction/postproduction.py @@ -45,7 +45,7 @@ class TT_Postproduction(): RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("IMAGE",) FUNCTION = "apply_postprocessing" - CATEGORY = "TenserTensor/Postproduction" + CATEGORY = "TenserTensor/Deprecated/Postproduction" def apply_postprocessing(self, **kwargs): image = kwargs.get("image").clone() diff --git a/nodes/postproduction/postproduction_advanced.py b/nodes/postproduction/postproduction_advanced.py index ffbf389..4883733 100644 --- a/nodes/postproduction/postproduction_advanced.py +++ b/nodes/postproduction/postproduction_advanced.py @@ -55,7 +55,7 @@ class TT_PostproductionAdvanced(): RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("IMAGE",) FUNCTION = "apply_postprocessing" - CATEGORY = "TenserTensor/Postproduction" + CATEGORY = "TenserTensor/Deprecated/Postproduction" def apply_postprocessing(self, **kwargs): image = kwargs.get("image").clone() diff --git a/nodes/postproduction/quick_image_upscaler.py b/nodes/postproduction/quick_image_upscaler.py index eea0785..707ffcf 100644 --- a/nodes/postproduction/quick_image_upscaler.py +++ b/nodes/postproduction/quick_image_upscaler.py @@ -24,7 +24,7 @@ class TT_QuickImageUpscaler(): RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("IMAGE",) FUNCTION = "upscale_image" - CATEGORY = "TenserTensor/Postproduction" + CATEGORY = "TenserTensor/Deprecated/Postproduction" def upscale_image(self, image, device, upscale_model_name, tile, overlap): timage = image.clone() diff --git a/nodes_image.py b/nodes_image.py index a9ba570..7484c17 100644 --- a/nodes_image.py +++ b/nodes_image.py @@ -330,12 +330,15 @@ def load_upscale_model(upscale_model) -> ImageModelDescriptor: BUFFER_FACTOR = 384.0 -def upscale(timage, **kwargs): - device_name, upscale_tile, upscale_overlap = ( - kwargs.get("upscaler_device", None), +def upscale(**kwargs): + image, device_name, upscale_tile, upscale_overlap = ( + kwargs.get("image"), + kwargs.get("upscaler_device"), kwargs.get("upscale_tile"), kwargs.get("upscale_overlap") ) + + timage = image.clone() device = get_torch_device(device_name) model = load_upscale_model(kwargs.get("upscale_model")) i_image = timage.movedim(-1, 1).to(device) @@ -408,10 +411,11 @@ class TT_ImagePreviewUpscaleSaveNode(io.ComfyNode): @classmethod def execute(cls, **kwargs) -> io.NodeOutput: - timage = kwargs.get("image") + image = kwargs.get("image") + timage = image.clone() if kwargs.get("upscale_image"): - timage = upscale(timage, **kwargs) + timage = upscale(**kwargs) if kwargs.get("save_image"): kwargs["image"] = timage diff --git a/nodes_postproduction.py b/nodes_postproduction.py index 23fbcce..9d3da1f 100644 --- a/nodes_postproduction.py +++ b/nodes_postproduction.py @@ -1,11 +1,77 @@ # (c) TenserTensor || Apache-2.0 (apache.org/licenses/LICENSE-2.0) +from pathlib import Path from typing import override +import colour +import kornia +import torch +from torch.nn import functional + +import folder_paths from comfy_api.latest import ComfyExtension, io +from .nodes_image import DEVICES, RESIZE_METHODS, get_upscale_models, upscale CATEGORY = "TenserTensor/Postproduction" +COLORSPACES = ["linear", "logarithmic"] + + +def apply_film_grain(**kwargs): + image, seed, scale, strength, saturation, mode, antialias, toe = ( + kwargs.get("image"), + kwargs.get("seed"), + kwargs.get("scale"), + kwargs.get("strength"), + kwargs.get("saturation"), + kwargs.get("upscale_mode"), + kwargs.get("antialias"), + kwargs.get("toe"), + ) + timage = image.copy() + batch, height, width, channels = timage.shape + + if seed is not None: + torch.manual_seed(seed) + + grain = torch.rand(batch, int(height // scale), int(width // scale), 3) + ycbcr = kornia.color.rgb_to_ycbcr(grain.movedim(-1, 1)).movedim(1, -1) + + for idx, ksize in enumerate([3, 15, 11]): + sigma = (ksize / 3.0,) * 2 + kernel_size = (ksize,) * 2 + channel = ycbcr[..., idx:idx + 1].movedim(-1, 1) + ycbcr[..., idx] = kornia.filters.gaussian_blur2d( + channel, + kernel_size=kernel_size, + sigma=sigma, + border_type='reflect' + ).movedim(1, -1).squeeze(-1) + + grain = (kornia.color.ycbcr_to_rgb(ycbcr.movedim(-1, 1)).movedim(1, -1) - 0.5) * strength + grain[..., 0] *= 2 + grain[..., 2] *= 3 + grain += 1 + + mono = grain[..., 1:2].expand(-1, -1, -1, 3) + grain = grain * saturation + mono * (1 - saturation) + + grain = functional.interpolate( + grain.movedim(-1, 1), + size=(height, width), + mode=mode, + antialias=antialias if mode in ['bilinear', 'bicubic'] else False + ).movedim(1, -1) + + base = image[..., :3] + shadow_lift = 1 - toe + image[..., :3] = torch.clip( + (1 - (1 - base) * grain) * shadow_lift + toe, + 0.0, 1.0 + ) + + return image + class TT_AddFilmGrainNode(io.ComfyNode): @classmethod @@ -16,37 +82,183 @@ class TT_AddFilmGrainNode(io.ComfyNode): category=CATEGORY, description="", inputs=[ - + io.Image.Input("image"), + io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff), + io.Float.Input("scale", default=0.25, min=0.05, max=2.0, step=0.05), + io.Float.Input("strength", default=0.15, min=0.0, max=10.0, step=0.01), + io.Float.Input("saturation", default=0.2, min=0.0, max=2.0, step=0.01), + io.Float.Input("toe", default=0.0, min=0.0, max=0.5, step=0.001), + io.Combo.Input("upscale_mode", options=RESIZE_METHODS, advanced=True), + io.Boolean.Input("antialias", default=True, label_on="Antialiased", label_off="Raw", advanced=True), ], outputs=[ - + io.Image.Output("IMAGE"), ] ) @classmethod def execute(cls, **kwargs) -> io.NodeOutput: - raise NotImplementedError + image = apply_film_grain(**kwargs) + + return io.NodeOutput(image) + + +def get_lut_files_list(): + base = folder_paths.get_folder_paths("custom_nodes")[0] + lut_dir = Path(base) / "ComfyUI-TenserTensor" / "lut" + + if not lut_dir.is_dir(): + raise FileNotFoundError(f"lut dir not found: {lut_dir}") + + files = [file.name for file in lut_dir.glob("*.cube")] + + return lut_dir, sorted(files) + + +def color_correction(**kwargs): + image, is_log, lut_dir, lut_file, lut_strength = ( + kwargs.get("image"), + kwargs.get("colorspace") == "logarithmic", + kwargs.get("lut_dir"), + kwargs.get("lut_file"), + kwargs.get("lut_strength"), + ) + + timage = image.clone() + orig_np_image = timage.detach().cpu().numpy() + np_image = orig_np_image.copy() + device, dtype = timage.device, timage.dtype + + if is_log: + np_image **= 1 / 2.2 + + np_image = colour.read_LUT(str(lut_dir / lut_file)).apply(np_image) + + if is_log: + np_image **= 2.2 + + if lut_strength < 1.0: + np_image = lut_strength * np_image + (1 - lut_strength) * orig_np_image + + return torch.from_numpy(np_image).to(device, dtype) class TT_ApplyLutNode(io.ComfyNode): + LUT_DIR = None + @classmethod def define_schema(cls) -> io.Schema: + cls.LUT_DIR, LUT_FILES = get_lut_files_list() + return io.Schema( node_id="TT_ApplyLutNode", display_name="TT Apply LUT", category=CATEGORY, description="", inputs=[ - + io.Image.Input("image"), + io.Combo.Input("lut_file", options=LUT_FILES), + io.Float.Input("strength", default=1.0, min=0.0, max=1.0, step=0.01), + io.Combo.Input("colorspace", options=COLORSPACES) ], outputs=[ - + io.Image.Output("IMAGE"), ] ) @classmethod def execute(cls, **kwargs) -> io.NodeOutput: - raise NotImplementedError + kwargs["lut_dir"] = cls.LUT_DIR + image = color_correction(**kwargs) + + return io.NodeOutput(image) + + +def adjust_brightness(image, factor, clip_output=True): + timage = image.movedim(-1, 1) + + return kornia.enhance.adjust_brightness(timage, factor, clip_output).movedim(1, -1) + + +def adjust_contrast(image, factor, clip_output=True): + timage = image.movedim(-1, 1) + + return kornia.enhance.adjust_contrast(timage, factor, clip_output).movedim(1, -1) + + +def adjust_gamma(image, gamma, gain=1.0): + timage = image.movedim(-1, 1) + + return kornia.enhance.adjust_gamma(timage, gamma, gain).movedim(1, -1) + + +def adjust_hue(image, factor): + timage = image.movedim(-1, 1) + + return kornia.enhance.adjust_hue(timage, factor).movedim(1, -1) + + +def adjust_saturation(image, factor): + timage = image.movedim(-1, 1) + + return kornia.enhance.adjust_saturation(timage, factor).movedim(1, -1) + + +def sharpness(image, factor): + timage = image.movedim(-1, 1) + + return kornia.enhance.sharpness(timage, factor).movedim(1, -1) + + +def posterize(image, bits): + timage = image.movedim(-1, 1) + + return kornia.enhance.posterize(timage, bits).movedim(1, -1) + + +def solarize(image, thresholds=0.5, additions=None): + timage = image.movedim(-1, 1) + + return kornia.enhance.solarize(timage, thresholds, additions).movedim(1, -1) + + +def enhance_image(**kwargs): + image = kwargs.get("image") + timage = image.clone() + + brightness_factor = kwargs.get("brightness_factor") + if brightness_factor != 0.0: + timage = adjust_brightness(timage, brightness_factor) + + contrast_factor = kwargs.get("contrast_factor") + if contrast_factor != 1.0: + timage = adjust_contrast(timage, contrast_factor) + + gamma_factor, gamma_gain = kwargs.get("gamma_factor"), kwargs.get("gamma_gain") + if gamma_factor != 1.0: + timage = adjust_gamma(timage, gamma_factor, gamma_gain) + + hue_factor = kwargs.get("hue_factor") + if hue_factor != 0.0: + timage = adjust_hue(timage, hue_factor) + + saturation_factor = kwargs.get("saturation_factor") + if saturation_factor != 1.0: + timage = adjust_saturation(timage, saturation_factor) + + sharpness_factor = kwargs.get("sharpness_factor") + if sharpness_factor != 0.0: + timage = sharpness(timage, sharpness_factor) + + posterize_bits = kwargs.get("posterize_bits") + if posterize_bits != 8: + timage = posterize(timage, posterize_bits) + + solarize_thresholds = kwargs.get("solarize_thresholds") + if solarize_thresholds != 1.0: + timage = solarize(timage, solarize_thresholds) + + return timage class TT_ImageEnhancerNode(io.ComfyNode): @@ -58,16 +270,27 @@ class TT_ImageEnhancerNode(io.ComfyNode): category=CATEGORY, description="", inputs=[ - + io.Image.Input("image"), + io.Float.Input("brightness_factor", default=0.0, min=-1.0, max=1.0, step=0.01), + io.Float.Input("contrast_factor", default=1.0, min=0.0, max=5.0, step=0.01), + io.Float.Input("gamma_factor", default=1.0, min=0.0, max=3.0, step=0.01), + io.Float.Input("gamma_gain", default=1.0, min=0.0, max=3.0, step=0.01), + io.Float.Input("hue_factor", default=0.0, min=-torch.pi, max=torch.pi, step=0.01), + io.Float.Input("saturation_factor", default=1.0, min=0.0, max=3.0, step=0.01), + io.Float.Input("sharpness_factor", default=0.0, min=0.0, max=3.0, step=0.01), + io.Int.Input("posterize_bits", default=8, min=-0, max=8), + io.Float.Input("solarize_thresholds", default=1.0, min=0.0, max=1.0, step=0.01), ], outputs=[ - + io.Image.Output("IMAGE"), ] ) @classmethod def execute(cls, **kwargs) -> io.NodeOutput: - raise NotImplementedError + image = enhance_image(**kwargs) + + return io.NodeOutput(image) class TT_QuickImageUpscalerNode(io.ComfyNode): @@ -79,58 +302,158 @@ class TT_QuickImageUpscalerNode(io.ComfyNode): category=CATEGORY, description="", inputs=[ - + io.Image.Input("image"), + io.Combo.Input("upscaler_device", options=DEVICES, advanced=True), + io.Combo.Input("upscale_model", options=get_upscale_models(), advanced=True), + io.Int.Input("upscale_tile", default=512, min=128, max=4096, step=64, advanced=True), + io.Int.Input("upscale_overlap", default=64, min=8, max=256, step=8, advanced=True), ], outputs=[ - + io.Image.Output("IMAGE"), ] ) @classmethod def execute(cls, **kwargs) -> io.NodeOutput: - raise NotImplementedError + image = upscale(**kwargs) + + return io.NodeOutput(image) class TT_PostproductionNode(io.ComfyNode): + LUT_DIR = None + @classmethod def define_schema(cls) -> io.Schema: + cls.LUT_DIR, LUT_FILES = get_lut_files_list() + return io.Schema( node_id="TT_PostproductionNode", display_name="TT Postproduction", category=CATEGORY, description="", inputs=[ - + io.Image.Input("image"), + io.Boolean.Input("apply_lut", default=True, label_on="Apply LUT", label_off="Skip LUT"), + io.Combo.Input("lut_file", options=LUT_FILES), + io.Float.Input("lut_strength", default=1.0, min=0.0, max=1.0, step=0.01), + io.Combo.Input("colorspace", options=COLORSPACES), + io.Boolean.Input("add_film_grain", default=True, label_on="With Grain", label_off="Clean"), + io.Int.Input("seed", display_name="grain_seed", default=0, min=0, max=0xffffffffffffffff), + io.Float.Input("scale", display_name="grain_scale", default=0.25, min=0.05, max=2.0, step=0.05), + io.Float.Input("strength", display_name="grain_strength", default=0.15, min=0.0, max=10.0, step=0.01), + io.Float.Input("saturation", display_name="grain_saturation", default=0.2, min=0.0, max=2.0, step=0.01), + io.Float.Input("toe", display_name="grain_toe", default=0.0, min=0.0, max=0.5, step=0.001), + io.Combo.Input("upscale_mode", display_name="grain_upscale_mode", options=RESIZE_METHODS, advanced=True), + io.Boolean.Input("antialias", display_name="grain_antialias", default=True, label_on="Antialiased", label_off="Raw", advanced=True), + io.Boolean.Input("upscale_image", default=True, label_on="Upscale image", label_off="Keep size"), + io.Combo.Input("upscaler_device", options=DEVICES, advanced=True), + io.Combo.Input("upscale_model", options=get_upscale_models(), advanced=True), + io.Int.Input("upscale_tile", default=512, min=128, max=4096, step=64, advanced=True), + io.Int.Input("upscale_overlap", default=64, min=8, max=256, step=8, advanced=True), ], outputs=[ - + io.Image.Output("IMAGE"), ] ) @classmethod def execute(cls, **kwargs) -> io.NodeOutput: - raise NotImplementedError + image = kwargs.get("image") + kwargs["image"] = image.clone() + + apply_lut, add_film_grain, upscale_image = ( + kwargs.get("apply_lut"), + kwargs.get("add_film_grain"), + kwargs.get("upscale_image"), + ) + + if apply_lut: + kwargs["lut_dir"] = cls.LUT_DIR + kwargs["image"] = color_correction(**kwargs) + + if add_film_grain: + kwargs["image"] = apply_film_grain(**kwargs) + + if upscale_image: + kwargs["image"] = upscale(**kwargs) + + return io.NodeOutput(kwargs.get("image")) class TT_PostproductionAdvancedNode(io.ComfyNode): + LUT_DIR = None + @classmethod def define_schema(cls) -> io.Schema: + cls.LUT_DIR, LUT_FILES = get_lut_files_list() + return io.Schema( node_id="TT_PostproductionAdvancedNode", display_name="TT Postproduction (Advanced)", category=CATEGORY, description="", inputs=[ - + io.Image.Input("image"), + io.Boolean.Input("image_enhancer", default=True, label_on="Enhance", label_off="Bypass"), + io.Float.Input("brightness_factor", default=0.0, min=-1.0, max=1.0, step=0.01), + io.Float.Input("contrast_factor", default=1.0, min=0.0, max=5.0, step=0.01), + io.Float.Input("gamma_factor", default=1.0, min=0.0, max=3.0, step=0.01), + io.Float.Input("gamma_gain", default=1.0, min=0.0, max=3.0, step=0.01), + io.Float.Input("hue_factor", default=0.0, min=-torch.pi, max=torch.pi, step=0.01), + io.Float.Input("saturation_factor", default=1.0, min=0.0, max=3.0, step=0.01), + io.Float.Input("sharpness_factor", default=0.0, min=0.0, max=3.0, step=0.01), + io.Int.Input("posterize_bits", default=8, min=-0, max=8), + io.Float.Input("solarize_thresholds", default=1.0, min=0.0, max=1.0, step=0.01), + io.Boolean.Input("apply_lut", default=True, label_on="Apply LUT", label_off="Bypass"), + io.Combo.Input("lut_file", options=LUT_FILES), + io.Float.Input("lut_strength", default=1.0, min=0.0, max=1.0, step=0.01), + io.Combo.Input("colorspace", options=COLORSPACES), + io.Boolean.Input("add_film_grain", default=True, label_on="Add Grain", label_off="Bypass"), + io.Int.Input("seed", display_name="grain_seed", default=0, min=0, max=0xffffffffffffffff), + io.Float.Input("scale", display_name="grain_scale", default=0.25, min=0.05, max=2.0, step=0.05), + io.Float.Input("strength", display_name="grain_strength", default=0.15, min=0.0, max=10.0, step=0.01), + io.Float.Input("saturation", display_name="grain_saturation", default=0.2, min=0.0, max=2.0, step=0.01), + io.Float.Input("toe", display_name="grain_toe", default=0.0, min=0.0, max=0.5, step=0.001), + io.Combo.Input("upscale_mode", display_name="grain_upscale_mode", options=RESIZE_METHODS, advanced=True), + io.Boolean.Input("antialias", display_name="grain_antialias", default=True, label_on="Antialiased", label_off="Raw", advanced=True), + io.Boolean.Input("upscale_image", default=True, label_on="Upscale image", label_off="Bypass"), + io.Combo.Input("upscaler_device", options=DEVICES, advanced=True), + io.Combo.Input("upscale_model", options=get_upscale_models(), advanced=True), + io.Int.Input("upscale_tile", default=512, min=128, max=4096, step=64, advanced=True), + io.Int.Input("upscale_overlap", default=64, min=8, max=256, step=8, advanced=True), ], outputs=[ - + io.Image.Output("IMAGE"), ] ) @classmethod def execute(cls, **kwargs) -> io.NodeOutput: - raise NotImplementedError + image = kwargs.get("image") + kwargs["image"] = image.clone() + + image_enhancer, apply_lut, add_film_grain, upscale_image = ( + kwargs.get("image_enhancer"), + kwargs.get("apply_lut"), + kwargs.get("add_film_grain"), + kwargs.get("upscale_image"), + ) + + if image_enhancer: + kwargs["image"] = enhance_image(**kwargs) + + if apply_lut: + kwargs["lut_dir"] = cls.LUT_DIR + kwargs["image"] = color_correction(**kwargs) + + if add_film_grain: + kwargs["image"] = apply_film_grain(**kwargs) + + if upscale_image: + kwargs["image"] = upscale(**kwargs) + + return io.NodeOutput(kwargs.get("image")) # ============================================================================== @@ -141,7 +464,12 @@ class PostproductionNodesExtension(ComfyExtension): @override async def get_node_list(self) -> list[type[io.ComfyNode]]: return [ - # TT_Node, + TT_AddFilmGrainNode, + TT_ApplyLutNode, + TT_ImageEnhancerNode, + TT_QuickImageUpscalerNode, + TT_PostproductionNode, + TT_PostproductionAdvancedNode, ] @@ -154,5 +482,10 @@ async def comfy_entrypoint() -> PostproductionNodesExtension: # ============================================================================== __all__ = [ - # "TT_Node", + "TT_AddFilmGrainNode", + 'TT_ApplyLutNode', + 'TT_ImageEnhancerNode', + 'TT_QuickImageUpscalerNode', + 'TT_PostproductionNode', + 'TT_PostproductionAdvancedNode', ] diff --git a/nodes_vae.py b/nodes_vae.py index a9385e1..83d17e8 100644 --- a/nodes_vae.py +++ b/nodes_vae.py @@ -50,7 +50,7 @@ class TT_VaeDecodeTiledNode(io.ComfyNode): io.Latent.Input("latent"), io.Int.Input("tile_width", default=512, min=64, max=4096, step=64), io.Int.Input("tile_height", default=512, min=64, max=4096, step=64), - io.Int.Input("overlap", default=512, min=64, max=4096, step=64), + io.Int.Input("overlap", default=64, min=8, max=256, step=8), ], outputs=[ io.Image.Output("IMAGE"), @@ -109,7 +109,7 @@ class TT_VaeEncodeTiledNode(io.ComfyNode): io.Vae.Input("vae"), io.Int.Input("tile_width", default=512, min=64, max=4096, step=64), io.Int.Input("tile_height", default=512, min=64, max=4096, step=64), - io.Int.Input("overlap", default=512, min=64, max=4096, step=64), + io.Int.Input("overlap", default=64, min=8, max=256, step=8), ], outputs=[ io.Latent.Output("LATENT"), diff --git a/web/docs/TT_AddFilmGrainNode.md b/web/docs/TT_AddFilmGrainNode.md new file mode 100644 index 0000000..db531e7 --- /dev/null +++ b/web/docs/TT_AddFilmGrainNode.md @@ -0,0 +1,32 @@ +## TT Add Film Grain *TT_AddFilmGrainNode* + +*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.* + +Adds procedural film grain to an image. Grain is generated from a seed for reproducibility and can be tuned for scale, intensity, color, and shadow detail. + +### Inputs + +| Parameter | Type | Required | Description | +|----------------|---------|----------|----------------------------------------------------------| +| `image` | IMAGE | Yes | Input image | +| `seed` | INT | Yes | Grain generation seed | +| `scale` | FLOAT | No | Grain particle size, default 0.25 | +| `strength` | FLOAT | No | Grain intensity, default 0.15 | +| `saturation` | FLOAT | No | Grain color saturation, default 0.2 | +| `toe` | FLOAT | No | Shadow grain lift, default 0.0 | +| `upscale_mode` | COMBO | No | Interpolation method for grain upscaling (advanced) | +| `antialias` | BOOLEAN | No | Antialiased — smooth grain edges; Raw — sharp (advanced) | + +### Outputs + +| Parameter | Type | Description | +|-----------|-------|----------------------| +| `IMAGE` | IMAGE | Image with film grain| + +### Usage + +Lower `scale` for fine grain, higher for coarse. Increase `saturation` for colored grain typical of film stocks. Use `toe` to add grain in shadow areas only. + +--- + +[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor) diff --git a/web/docs/TT_ApplyLutNode.md b/web/docs/TT_ApplyLutNode.md new file mode 100644 index 0000000..e3cfb85 --- /dev/null +++ b/web/docs/TT_ApplyLutNode.md @@ -0,0 +1,29 @@ +## TT Apply LUT *TT_ApplyLutNode* + +*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.* + +Applies a .cube LUT file to an image for color grading. Supports linear and logarithmic colorspace workflows. + +### Inputs + +| Parameter | Type | Required | Description | +|--------------|-------|----------|---------------------------------------------------------------| +| `image` | IMAGE | Yes | Input image | +| `lut_file` | COMBO | Yes | LUT file from the node pack's `lut/` folder | +| `strength` | FLOAT | No | Blend strength between original and graded image, default 1.0 | +| `colorspace` | COMBO | No | Input colorspace — linear or logarithmic | + +### Outputs + +| Parameter | Type | Description | +|-----------|-------|--------------------| +| `IMAGE` | IMAGE | Color graded image | + +### Usage + +Select a `.cube` LUT file and set colorspace to match your image. Use `strength` below 1.0 to blend the grade with the original. Choose `colorspace` +based on your LUT file's intended working space. + +--- + +[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor) diff --git a/web/docs/TT_ImageEnhancerNode.md b/web/docs/TT_ImageEnhancerNode.md new file mode 100644 index 0000000..c438804 --- /dev/null +++ b/web/docs/TT_ImageEnhancerNode.md @@ -0,0 +1,34 @@ +## TT Image Enhancer *TT_ImageEnhancerNode* + +*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.* + +Applies a range of image enhancement operations in a single node. Each parameter has a neutral default — only active adjustments are applied. + +### Inputs + +| Parameter | Type | Required | Description | +|-----------------------|-------|----------|------------------------------------------------------------| +| `image` | IMAGE | Yes | Input image | +| `brightness_factor` | FLOAT | No | Brightness adjustment, default 0.0 (neutral) | +| `contrast_factor` | FLOAT | No | Contrast multiplier, default 1.0 (neutral) | +| `gamma_factor` | FLOAT | No | Gamma correction, default 1.0 (neutral) | +| `gamma_gain` | FLOAT | No | Gamma gain, default 1.0 (neutral) | +| `hue_factor` | FLOAT | No | Hue rotation in radians, default 0.0 (neutral) | +| `saturation_factor` | FLOAT | No | Saturation multiplier, default 1.0 (neutral) | +| `sharpness_factor` | FLOAT | No | Sharpness intensity, default 0.0 (neutral) | +| `posterize_bits` | INT | No | Color depth in bits, default 8 (neutral, no posterization) | +| `solarize_thresholds` | FLOAT | No | Solarize threshold, default 1.0 (neutral, no solarization) | + +### Outputs + +| Parameter | Type | Description | +|-----------|-------|----------------| +| `IMAGE` | IMAGE | Enhanced image | + +### Usage + +All parameters default to neutral values — adjustments are only applied when values differ from defaults. Powered by Kornia. + +--- + +[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor) diff --git a/web/docs/TT_PostproductionAdvancedNode.md b/web/docs/TT_PostproductionAdvancedNode.md new file mode 100644 index 0000000..3cb99ca --- /dev/null +++ b/web/docs/TT_PostproductionAdvancedNode.md @@ -0,0 +1,54 @@ +## TT Postproduction (Advanced) *TT_PostproductionAdvancedNode* + +*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.* + +All-in-one postproduction node combining image enhancement, LUT color grading, film grain, and upscaling in a single node. Each stage can be +independently enabled or bypassed. + +### Inputs + +| Parameter | Type | Required | Description | +|-----------------------|---------|----------|-------------------------------------------------------| +| `image` | IMAGE | Yes | Input image | +| `image_enhancer` | BOOLEAN | No | Enable image enhancement stage | +| `brightness_factor` | FLOAT | No | Brightness adjustment, default 0.0 (neutral) | +| `contrast_factor` | FLOAT | No | Contrast multiplier, default 1.0 (neutral) | +| `gamma_factor` | FLOAT | No | Gamma correction, default 1.0 (neutral) | +| `gamma_gain` | FLOAT | No | Gamma gain, default 1.0 (neutral) | +| `hue_factor` | FLOAT | No | Hue rotation in radians, default 0.0 (neutral) | +| `saturation_factor` | FLOAT | No | Saturation multiplier, default 1.0 (neutral) | +| `sharpness_factor` | FLOAT | No | Sharpness intensity, default 0.0 (neutral) | +| `posterize_bits` | INT | No | Color depth in bits, default 8 (neutral) | +| `solarize_thresholds` | FLOAT | No | Solarize threshold, default 1.0 (neutral) | +| `apply_lut` | BOOLEAN | No | Enable LUT color grading stage | +| `lut_file` | COMBO | No | LUT file from the node pack's `lut/` folder | +| `lut_strength` | FLOAT | No | LUT blend strength, default 1.0 | +| `colorspace` | COMBO | No | Input colorspace — linear or logarithmic | +| `add_film_grain` | BOOLEAN | No | Enable film grain stage | +| `grain_seed` | INT | No | Grain generation seed | +| `grain_scale` | FLOAT | No | Grain particle size, default 0.25 | +| `grain_strength` | FLOAT | No | Grain intensity, default 0.15 | +| `grain_saturation` | FLOAT | No | Grain color saturation, default 0.2 | +| `grain_toe` | FLOAT | No | Shadow grain lift, default 0.0 | +| `grain_upscale_mode` | COMBO | No | Grain interpolation method (advanced) | +| `grain_antialias` | BOOLEAN | No | Antialiased — smooth grain; Raw — sharp (advanced) | +| `upscale_image` | BOOLEAN | No | Enable upscaling stage | +| `upscaler_device` | COMBO | No | Device for upscaling inference (advanced) | +| `upscale_model` | COMBO | No | Upscale model file (advanced) | +| `upscale_tile` | INT | No | Upscale tile size in pixels, default 512 (advanced) | +| `upscale_overlap` | INT | No | Upscale tile overlap in pixels, default 64 (advanced) | + +### Outputs + +| Parameter | Type | Description | +|-----------|-------|----------------------| +| `IMAGE` | IMAGE | Post-processed image | + +### Usage + +Stages run in order: enhancement → LUT → grain → upscale. Bypass any stage with its toggle. All enhancement parameters default to neutral values — +only active adjustments are applied. + +--- + +[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor) \ No newline at end of file diff --git a/web/docs/TT_PostproductionNode.md b/web/docs/TT_PostproductionNode.md new file mode 100644 index 0000000..ea4afa0 --- /dev/null +++ b/web/docs/TT_PostproductionNode.md @@ -0,0 +1,42 @@ +## TT Postproduction *TT_PostproductionNode* + +*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.* + +Streamlined postproduction node combining LUT color grading, film grain, and upscaling. Each stage can be independently enabled or bypassed. + +### Inputs + +| Parameter | Type | Required | Description | +|----------------------|---------|----------|-------------------------------------------------------| +| `image` | IMAGE | Yes | Input image | +| `apply_lut` | BOOLEAN | No | Enable LUT color grading stage | +| `lut_file` | COMBO | No | LUT file from the node pack's `lut/` folder | +| `lut_strength` | FLOAT | No | LUT blend strength, default 1.0 | +| `colorspace` | COMBO | No | Input colorspace — linear or logarithmic | +| `add_film_grain` | BOOLEAN | No | Enable film grain stage | +| `grain_seed` | INT | No | Grain generation seed | +| `grain_scale` | FLOAT | No | Grain particle size, default 0.25 | +| `grain_strength` | FLOAT | No | Grain intensity, default 0.15 | +| `grain_saturation` | FLOAT | No | Grain color saturation, default 0.2 | +| `grain_toe` | FLOAT | No | Shadow grain lift, default 0.0 | +| `grain_upscale_mode` | COMBO | No | Grain interpolation method (advanced) | +| `grain_antialias` | BOOLEAN | No | Antialiased — smooth grain; Raw — sharp (advanced) | +| `upscale_image` | BOOLEAN | No | Enable upscaling stage | +| `upscaler_device` | COMBO | No | Device for upscaling inference (advanced) | +| `upscale_model` | COMBO | No | Upscale model file (advanced) | +| `upscale_tile` | INT | No | Upscale tile size in pixels, default 512 (advanced) | +| `upscale_overlap` | INT | No | Upscale tile overlap in pixels, default 64 (advanced) | + +### Outputs + +| Parameter | Type | Description | +|-----------|-------|----------------------| +| `IMAGE` | IMAGE | Post-processed image | + +### Usage + +Stages run in order: LUT → grain → upscale. Bypass any stage with its toggle. Use TT Postproduction (Advanced) if you need image enhancement controls. + +--- + +[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor) diff --git a/web/docs/TT_QuickImageUpscalerNode.md b/web/docs/TT_QuickImageUpscalerNode.md new file mode 100644 index 0000000..4b5fe53 --- /dev/null +++ b/web/docs/TT_QuickImageUpscalerNode.md @@ -0,0 +1,30 @@ +## TT Quick Image Upscaler *TT_QuickImageUpscalerNode* + +*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.* + +Upscales an image using a model-based upscaler. All configuration is in advanced inputs — connect an image and run. + +### Inputs + +| Parameter | Type | Required | Description | +|-------------------|-------|----------|-----------------------------------------------| +| `image` | IMAGE | Yes | Input image | +| `upscaler_device` | COMBO | No | Device for upscaling inference (advanced) | +| `upscale_model` | COMBO | No | Upscale model file (advanced) | +| `upscale_tile` | INT | No | Tile size in pixels, default 512 (advanced) | +| `upscale_overlap` | INT | No | Tile overlap in pixels, default 64 (advanced) | + +### Outputs + +| Parameter | Type | Description | +|-----------|-------|----------------| +| `IMAGE` | IMAGE | Upscaled image | + +### Usage + +Place upscale model files in `models/upscale_models`. Increase tile size for better quality at the cost of VRAM. Increase overlap to reduce visible +tile seams. + +--- + +[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor) diff --git a/web/docs/TT_VaeEncodeContextNode.md b/web/docs/TT_VaeEncodeContextNode.md index 5b37a65..9ca4306 100644 --- a/web/docs/TT_VaeEncodeContextNode.md +++ b/web/docs/TT_VaeEncodeContextNode.md @@ -24,4 +24,4 @@ for img2img or inpainting workflows. --- -[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor) \ No newline at end of file +[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor)