From 675e9fd788fcb5384f2dc05196fa9bbf4264c263 Mon Sep 17 00:00:00 2001 From: bigcat88 Date: Fri, 25 Jul 2025 17:27:15 +0300 Subject: [PATCH] restore nodes order as it in the V1 version for smaller git diff --- comfy_extras/v3/nodes_mask.py | 445 +++++++++++++++++----------------- 1 file changed, 224 insertions(+), 221 deletions(-) diff --git a/comfy_extras/v3/nodes_mask.py b/comfy_extras/v3/nodes_mask.py index 3fc736d40..3ea3a0431 100644 --- a/comfy_extras/v3/nodes_mask.py +++ b/comfy_extras/v3/nodes_mask.py @@ -57,6 +57,161 @@ def composite(destination, source, x, y, mask=None, multiplier=8, resize_source= return destination +class LatentCompositeMasked(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="LatentCompositeMasked_V3", + display_name="Latent Composite Masked _V3", + category="latent", + inputs=[ + io.Latent.Input("destination"), + io.Latent.Input("source"), + io.Int.Input("x", default=0, min=0, max=nodes.MAX_RESOLUTION, step=8), + io.Int.Input("y", default=0, min=0, max=nodes.MAX_RESOLUTION, step=8), + io.Boolean.Input("resize_source", default=False), + io.Mask.Input("mask", optional=True), + ], + outputs=[io.Latent.Output()], + ) + + @classmethod + def execute(cls, destination, source, x, y, resize_source, mask=None) -> io.NodeOutput: + output = destination.copy() + destination_samples = destination["samples"].clone() + source_samples = source["samples"] + output["samples"] = composite(destination_samples, source_samples, x, y, mask, 8, resize_source) + return io.NodeOutput(output) + + +class ImageCompositeMasked(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="ImageCompositeMasked_V3", + display_name="Image Composite Masked _V3", + category="image", + inputs=[ + io.Image.Input("destination"), + io.Image.Input("source"), + io.Int.Input("x", default=0, min=0, max=nodes.MAX_RESOLUTION), + io.Int.Input("y", default=0, min=0, max=nodes.MAX_RESOLUTION), + io.Boolean.Input("resize_source", default=False), + io.Mask.Input("mask", optional=True), + ], + outputs=[io.Image.Output()], + ) + + @classmethod + def execute(cls, destination, source, x, y, resize_source, mask=None) -> io.NodeOutput: + destination, source = node_helpers.image_alpha_fix(destination, source) + destination = destination.clone().movedim(-1, 1) + output = composite(destination, source.movedim(-1, 1), x, y, mask, 1, resize_source).movedim(1, -1) + return io.NodeOutput(output) + + +class MaskToImage(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="MaskToImage_V3", + display_name="Convert Mask to Image _V3", + category="mask", + inputs=[ + io.Mask.Input("mask"), + ], + outputs=[io.Image.Output()], + ) + + @classmethod + def execute(cls, mask) -> io.NodeOutput: + return io.NodeOutput(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3)) + + +class ImageToMask(io.ComfyNode): + CHANNELS = ["red", "green", "blue", "alpha"] + + @classmethod + def define_schema(cls): + return io.Schema( + node_id="ImageToMask_V3", + display_name="Convert Image to Mask _V3", + category="mask", + inputs=[ + io.Image.Input("image"), + io.Combo.Input("channel", options=cls.CHANNELS), + ], + outputs=[io.Mask.Output()], + ) + + @classmethod + def execute(cls, image, channel) -> io.NodeOutput: + return io.NodeOutput(image[:, :, :, cls.CHANNELS.index(channel)]) + + +class ImageColorToMask(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="ImageColorToMask_V3", + display_name="Image Color to Mask _V3", + category="mask", + inputs=[ + io.Image.Input("image"), + io.Int.Input("color", default=0, min=0, max=0xFFFFFF), + ], + outputs=[io.Mask.Output()], + ) + + @classmethod + def execute(cls, image, color) -> io.NodeOutput: + temp = (torch.clamp(image, 0, 1.0) * 255.0).round().to(torch.int) + temp = ( + torch.bitwise_left_shift(temp[:, :, :, 0], 16) + + torch.bitwise_left_shift(temp[:, :, :, 1], 8) + + temp[:, :, :, 2] + ) + return io.NodeOutput(torch.where(temp == color, 1.0, 0).float()) + + +class SolidMask(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="SolidMask_V3", + display_name="Solid Mask _V3", + category="mask", + inputs=[ + io.Float.Input("value", default=1.0, min=0.0, max=1.0, step=0.01), + io.Int.Input("width", default=512, min=1, max=nodes.MAX_RESOLUTION), + io.Int.Input("height", default=512, min=1, max=nodes.MAX_RESOLUTION), + ], + outputs=[io.Mask.Output()], + ) + + @classmethod + def execute(cls, value, width, height) -> io.NodeOutput: + return io.NodeOutput(torch.full((1, height, width), value, dtype=torch.float32, device="cpu")) + + +class InvertMask(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="InvertMask_V3", + display_name="Invert Mask _V3", + category="mask", + inputs=[ + io.Mask.Input("mask"), + ], + outputs=[io.Mask.Output()], + ) + + @classmethod + def execute(cls, mask) -> io.NodeOutput: + return io.NodeOutput(1.0 - mask) + + class CropMask(io.ComfyNode): @classmethod def define_schema(cls): @@ -80,6 +235,66 @@ class CropMask(io.ComfyNode): return io.NodeOutput(mask[:, y : y + height, x : x + width]) +class MaskComposite(io.ComfyNode): + @classmethod + def define_schema(cls): + return io.Schema( + node_id="MaskComposite_V3", + display_name="Mask Composite _V3", + category="mask", + inputs=[ + io.Mask.Input("destination"), + io.Mask.Input("source"), + io.Int.Input("x", default=0, min=0, max=nodes.MAX_RESOLUTION), + io.Int.Input("y", default=0, min=0, max=nodes.MAX_RESOLUTION), + io.Combo.Input("operation", options=["multiply", "add", "subtract", "and", "or", "xor"]), + ], + outputs=[io.Mask.Output()], + ) + + @classmethod + def execute(cls, destination, source, x, y, operation) -> io.NodeOutput: + output = destination.reshape((-1, destination.shape[-2], destination.shape[-1])).clone() + source = source.reshape((-1, source.shape[-2], source.shape[-1])) + + left, top = ( + x, + y, + ) + right, bottom = ( + min(left + source.shape[-1], destination.shape[-1]), + min(top + source.shape[-2], destination.shape[-2]), + ) + visible_width, visible_height = ( + right - left, + bottom - top, + ) + + source_portion = source[:, :visible_height, :visible_width] + destination_portion = output[:, top:bottom, left:right] + + if operation == "multiply": + output[:, top:bottom, left:right] = destination_portion * source_portion + elif operation == "add": + output[:, top:bottom, left:right] = destination_portion + source_portion + elif operation == "subtract": + output[:, top:bottom, left:right] = destination_portion - source_portion + elif operation == "and": + output[:, top:bottom, left:right] = torch.bitwise_and( + destination_portion.round().bool(), source_portion.round().bool() + ).float() + elif operation == "or": + output[:, top:bottom, left:right] = torch.bitwise_or( + destination_portion.round().bool(), source_portion.round().bool() + ).float() + elif operation == "xor": + output[:, top:bottom, left:right] = torch.bitwise_xor( + destination_portion.round().bool(), source_portion.round().bool() + ).float() + + return io.NodeOutput(torch.clamp(output, 0.0, 1.0)) + + class FeatherMask(io.ComfyNode): @classmethod def define_schema(cls): @@ -158,183 +373,28 @@ class GrowMask(io.ComfyNode): return io.NodeOutput(torch.stack(out, dim=0)) -class ImageColorToMask(io.ComfyNode): +class ThresholdMask(io.ComfyNode): @classmethod def define_schema(cls): return io.Schema( - node_id="ImageColorToMask_V3", - display_name="Image Color to Mask _V3", - category="mask", - inputs=[ - io.Image.Input("image"), - io.Int.Input("color", default=0, min=0, max=0xFFFFFF), - ], - outputs=[io.Mask.Output()], - ) - - @classmethod - def execute(cls, image, color) -> io.NodeOutput: - temp = (torch.clamp(image, 0, 1.0) * 255.0).round().to(torch.int) - temp = ( - torch.bitwise_left_shift(temp[:, :, :, 0], 16) - + torch.bitwise_left_shift(temp[:, :, :, 1], 8) - + temp[:, :, :, 2] - ) - return io.NodeOutput(torch.where(temp == color, 1.0, 0).float()) - - -class ImageCompositeMasked(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="ImageCompositeMasked_V3", - display_name="Image Composite Masked _V3", - category="image", - inputs=[ - io.Image.Input("destination"), - io.Image.Input("source"), - io.Int.Input("x", default=0, min=0, max=nodes.MAX_RESOLUTION), - io.Int.Input("y", default=0, min=0, max=nodes.MAX_RESOLUTION), - io.Boolean.Input("resize_source", default=False), - io.Mask.Input("mask", optional=True), - ], - outputs=[io.Image.Output()], - ) - - @classmethod - def execute(cls, destination, source, x, y, resize_source, mask=None) -> io.NodeOutput: - destination, source = node_helpers.image_alpha_fix(destination, source) - destination = destination.clone().movedim(-1, 1) - output = composite(destination, source.movedim(-1, 1), x, y, mask, 1, resize_source).movedim(1, -1) - return io.NodeOutput(output) - - -class ImageToMask(io.ComfyNode): - CHANNELS = ["red", "green", "blue", "alpha"] - - @classmethod - def define_schema(cls): - return io.Schema( - node_id="ImageToMask_V3", - display_name="Convert Image to Mask _V3", - category="mask", - inputs=[ - io.Image.Input("image"), - io.Combo.Input("channel", options=cls.CHANNELS), - ], - outputs=[io.Mask.Output()], - ) - - @classmethod - def execute(cls, image, channel) -> io.NodeOutput: - return io.NodeOutput(image[:, :, :, cls.CHANNELS.index(channel)]) - - -class InvertMask(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="InvertMask_V3", - display_name="Invert Mask _V3", + node_id="ThresholdMask_V3", + display_name="Threshold Mask _V3", category="mask", inputs=[ io.Mask.Input("mask"), + io.Float.Input("value", default=0.5, min=0.0, max=1.0, step=0.01), ], outputs=[io.Mask.Output()], ) @classmethod - def execute(cls, mask) -> io.NodeOutput: - return io.NodeOutput(1.0 - mask) - - -class LatentCompositeMasked(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="LatentCompositeMasked_V3", - display_name="Latent Composite Masked _V3", - category="latent", - inputs=[ - io.Latent.Input("destination"), - io.Latent.Input("source"), - io.Int.Input("x", default=0, min=0, max=nodes.MAX_RESOLUTION, step=8), - io.Int.Input("y", default=0, min=0, max=nodes.MAX_RESOLUTION, step=8), - io.Boolean.Input("resize_source", default=False), - io.Mask.Input("mask", optional=True), - ], - outputs=[io.Latent.Output()], - ) - - @classmethod - def execute(cls, destination, source, x, y, resize_source, mask=None) -> io.NodeOutput: - output = destination.copy() - destination_samples = destination["samples"].clone() - source_samples = source["samples"] - output["samples"] = composite(destination_samples, source_samples, x, y, mask, 8, resize_source) - return io.NodeOutput(output) - - -class MaskComposite(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="MaskComposite_V3", - display_name="Mask Composite _V3", - category="mask", - inputs=[ - io.Mask.Input("destination"), - io.Mask.Input("source"), - io.Int.Input("x", default=0, min=0, max=nodes.MAX_RESOLUTION), - io.Int.Input("y", default=0, min=0, max=nodes.MAX_RESOLUTION), - io.Combo.Input("operation", options=["multiply", "add", "subtract", "and", "or", "xor"]), - ], - outputs=[io.Mask.Output()], - ) - - @classmethod - def execute(cls, destination, source, x, y, operation) -> io.NodeOutput: - output = destination.reshape((-1, destination.shape[-2], destination.shape[-1])).clone() - source = source.reshape((-1, source.shape[-2], source.shape[-1])) - - left, top = ( - x, - y, - ) - right, bottom = ( - min(left + source.shape[-1], destination.shape[-1]), - min(top + source.shape[-2], destination.shape[-2]), - ) - visible_width, visible_height = ( - right - left, - bottom - top, - ) - - source_portion = source[:, :visible_height, :visible_width] - destination_portion = output[:, top:bottom, left:right] - - if operation == "multiply": - output[:, top:bottom, left:right] = destination_portion * source_portion - elif operation == "add": - output[:, top:bottom, left:right] = destination_portion + source_portion - elif operation == "subtract": - output[:, top:bottom, left:right] = destination_portion - source_portion - elif operation == "and": - output[:, top:bottom, left:right] = torch.bitwise_and( - destination_portion.round().bool(), source_portion.round().bool() - ).float() - elif operation == "or": - output[:, top:bottom, left:right] = torch.bitwise_or( - destination_portion.round().bool(), source_portion.round().bool() - ).float() - elif operation == "xor": - output[:, top:bottom, left:right] = torch.bitwise_xor( - destination_portion.round().bool(), source_portion.round().bool() - ).float() - - return io.NodeOutput(torch.clamp(output, 0.0, 1.0)) + def execute(cls, mask, value) -> io.NodeOutput: + return io.NodeOutput((mask > value).float()) +# Mask Preview - original implement from +# https://github.com/cubiq/ComfyUI_essentials/blob/9d9f4bedfc9f0321c19faf71855e228c93bd0dc9/mask.py#L81 +# upstream requested in https://github.com/Kosinkadink/rfcs/blob/main/rfcs/0000-corenodes.md#preview-nodes class MaskPreview(io.ComfyNode): """Mask Preview - original implement in ComfyUI_essentials. @@ -360,63 +420,6 @@ class MaskPreview(io.ComfyNode): return io.NodeOutput(ui=ui.PreviewMask(masks)) -class MaskToImage(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="MaskToImage_V3", - display_name="Convert Mask to Image _V3", - category="mask", - inputs=[ - io.Mask.Input("mask"), - ], - outputs=[io.Image.Output()], - ) - - @classmethod - def execute(cls, mask) -> io.NodeOutput: - return io.NodeOutput(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3)) - - -class SolidMask(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="SolidMask_V3", - display_name="Solid Mask _V3", - category="mask", - inputs=[ - io.Float.Input("value", default=1.0, min=0.0, max=1.0, step=0.01), - io.Int.Input("width", default=512, min=1, max=nodes.MAX_RESOLUTION), - io.Int.Input("height", default=512, min=1, max=nodes.MAX_RESOLUTION), - ], - outputs=[io.Mask.Output()], - ) - - @classmethod - def execute(cls, value, width, height) -> io.NodeOutput: - return io.NodeOutput(torch.full((1, height, width), value, dtype=torch.float32, device="cpu")) - - -class ThresholdMask(io.ComfyNode): - @classmethod - def define_schema(cls): - return io.Schema( - node_id="ThresholdMask_V3", - display_name="Threshold Mask _V3", - category="mask", - inputs=[ - io.Mask.Input("mask"), - io.Float.Input("value", default=0.5, min=0.0, max=1.0, step=0.01), - ], - outputs=[io.Mask.Output()], - ) - - @classmethod - def execute(cls, mask, value) -> io.NodeOutput: - return io.NodeOutput((mask > value).float()) - - NODES_LIST: list[type[io.ComfyNode]] = [ CropMask, FeatherMask,