From c7843f888ffd5eed7fd533582ad4801ca5e65429 Mon Sep 17 00:00:00 2001 From: Terry Jia Date: Thu, 15 Jan 2026 22:25:38 -0500 Subject: [PATCH 1/2] Boundingbox widget --- comfy_api/latest/_io.py | 20 ++++++++++++++++++++ comfy_extras/nodes_images.py | 34 +++++++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/comfy_api/latest/_io.py b/comfy_api/latest/_io.py index e6a0d1821..42dc14c1c 100644 --- a/comfy_api/latest/_io.py +++ b/comfy_api/latest/_io.py @@ -1125,6 +1125,25 @@ class ImageCompare(ComfyTypeI): def as_dict(self): return super().as_dict() +@comfytype(io_type="BOUNDINGBOX") +class BoundingBox(ComfyTypeIO): + Type = dict + + class Input(WidgetInput): + def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, + socketless: bool=True, default: dict=None, component: str=None): + super().__init__(id, display_name, optional, tooltip, None, default, socketless) + self.component = component + if default is None: + self.default = {"x": 0, "y": 0, "width": 512, "height": 512} + + def as_dict(self): + d = super().as_dict() + if self.component: + d["component"] = self.component + return d + + DYNAMIC_INPUT_LOOKUP: dict[str, Callable[[dict[str, Any], dict[str, Any], tuple[str, dict[str, Any]], str, list[str] | None], None]] = {} def register_dynamic_input_func(io_type: str, func: Callable[[dict[str, Any], dict[str, Any], tuple[str, dict[str, Any]], str, list[str] | None], None]): DYNAMIC_INPUT_LOOKUP[io_type] = func @@ -2046,4 +2065,5 @@ __all__ = [ "ImageCompare", "PriceBadgeDepends", "PriceBadge", + "BoundingBox", ] diff --git a/comfy_extras/nodes_images.py b/comfy_extras/nodes_images.py index ce21caade..8e06ba11a 100644 --- a/comfy_extras/nodes_images.py +++ b/comfy_extras/nodes_images.py @@ -26,16 +26,18 @@ class ImageCrop(IO.ComfyNode): category="image/transform", inputs=[ IO.Image.Input("image"), - IO.Int.Input("width", default=512, min=1, max=nodes.MAX_RESOLUTION, step=1), - IO.Int.Input("height", default=512, min=1, max=nodes.MAX_RESOLUTION, step=1), - IO.Int.Input("x", default=0, min=0, max=nodes.MAX_RESOLUTION, step=1), - IO.Int.Input("y", default=0, min=0, max=nodes.MAX_RESOLUTION, step=1), + IO.BoundingBox.Input("crop_region", component="ImageCrop"), ], outputs=[IO.Image.Output()], ) @classmethod - def execute(cls, image, width, height, x, y) -> IO.NodeOutput: + def execute(cls, image, crop_region) -> IO.NodeOutput: + x = crop_region.get("x", 0) + y = crop_region.get("y", 0) + width = crop_region.get("width", 512) + height = crop_region.get("height", 512) + x = min(x, image.shape[2] - 1) y = min(y, image.shape[1] - 1) to_x = width + x @@ -46,6 +48,27 @@ class ImageCrop(IO.ComfyNode): crop = execute # TODO: remove +class IntToBoundingBox(IO.ComfyNode): + @classmethod + def define_schema(cls): + return IO.Schema( + node_id="IntToBoundingBox", + display_name="INT to Bounding Box", + category="utils", + inputs=[ + IO.Int.Input("x", default=0, min=0, max=MAX_RESOLUTION), + IO.Int.Input("y", default=0, min=0, max=MAX_RESOLUTION), + IO.Int.Input("width", default=512, min=1, max=MAX_RESOLUTION), + IO.Int.Input("height", default=512, min=1, max=MAX_RESOLUTION), + ], + outputs=[IO.BoundingBox.Output(display_name="BOUNDINGBOX")], + ) + + @classmethod + def execute(cls, x, y, width, height) -> IO.NodeOutput: + return IO.NodeOutput({"x": x, "y": y, "width": width, "height": height}) + + class RepeatImageBatch(IO.ComfyNode): @classmethod def define_schema(cls): @@ -628,6 +651,7 @@ class ImagesExtension(ComfyExtension): async def get_node_list(self) -> list[type[IO.ComfyNode]]: return [ ImageCrop, + IntToBoundingBox, RepeatImageBatch, ImageFromBatch, ImageAddNoise, From 6cdd2469668aba0854a24b3b24b066a4eb0c9395 Mon Sep 17 00:00:00 2001 From: Terry Jia Date: Tue, 20 Jan 2026 21:21:27 -0500 Subject: [PATCH 2/2] code improve --- comfy_api/latest/_io.py | 2 +- comfy_extras/nodes_images.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/comfy_api/latest/_io.py b/comfy_api/latest/_io.py index 42dc14c1c..efe3a655f 100644 --- a/comfy_api/latest/_io.py +++ b/comfy_api/latest/_io.py @@ -1125,7 +1125,7 @@ class ImageCompare(ComfyTypeI): def as_dict(self): return super().as_dict() -@comfytype(io_type="BOUNDINGBOX") +@comfytype(io_type="BOUNDING_BOX") class BoundingBox(ComfyTypeIO): Type = dict diff --git a/comfy_extras/nodes_images.py b/comfy_extras/nodes_images.py index 8e06ba11a..2a3fb65e9 100644 --- a/comfy_extras/nodes_images.py +++ b/comfy_extras/nodes_images.py @@ -48,20 +48,20 @@ class ImageCrop(IO.ComfyNode): crop = execute # TODO: remove -class IntToBoundingBox(IO.ComfyNode): +class BoundingBox(IO.ComfyNode): @classmethod def define_schema(cls): return IO.Schema( - node_id="IntToBoundingBox", - display_name="INT to Bounding Box", - category="utils", + node_id="PrimitiveBoundingBox", + display_name="Bounding Box", + category="utils/primitive", inputs=[ IO.Int.Input("x", default=0, min=0, max=MAX_RESOLUTION), IO.Int.Input("y", default=0, min=0, max=MAX_RESOLUTION), IO.Int.Input("width", default=512, min=1, max=MAX_RESOLUTION), IO.Int.Input("height", default=512, min=1, max=MAX_RESOLUTION), ], - outputs=[IO.BoundingBox.Output(display_name="BOUNDINGBOX")], + outputs=[IO.BoundingBox.Output()], ) @classmethod @@ -651,7 +651,7 @@ class ImagesExtension(ComfyExtension): async def get_node_list(self) -> list[type[IO.ComfyNode]]: return [ ImageCrop, - IntToBoundingBox, + BoundingBox, RepeatImageBatch, ImageFromBatch, ImageAddNoise,