diff --git a/comfy_api/latest/_io.py b/comfy_api/latest/_io.py index 6c293b22d..4c062d8aa 100644 --- a/comfy_api/latest/_io.py +++ b/comfy_api/latest/_io.py @@ -1451,6 +1451,27 @@ class VideoEdit(ComfyTypeIO): }) +@comfytype(io_type="RESOLUTION_PREVIEW") +class ResolutionPreview(ComfyTypeIO): + Type = dict + + class Input(WidgetInput): + def __init__(self, id: str, display_name: str=None, optional=True, tooltip: str=None, + socketless: bool=True, advanced: bool=None, + ratio_widget: str="aspect_ratio", megapixels_widget: str="megapixels", multiple_widget: str="multiple"): + super().__init__(id, display_name, optional, tooltip, None, None, socketless, None, None, None, None, advanced) + self.ratio_widget = ratio_widget + self.megapixels_widget = megapixels_widget + self.multiple_widget = multiple_widget + + def as_dict(self): + return super().as_dict() | prune_dict({ + "ratio_widget": self.ratio_widget, + "megapixels_widget": self.megapixels_widget, + "multiple_widget": self.multiple_widget, + }) + + @comfytype(io_type="HISTOGRAM") class Histogram(ComfyTypeIO): """A histogram represented as a list of bin counts.""" @@ -2529,5 +2550,6 @@ __all__ = [ "Histogram", "Range", "VideoEdit", + "ResolutionPreview", "NodeReplace", ] diff --git a/comfy_extras/nodes_resolution.py b/comfy_extras/nodes_resolution.py index 083e47ae4..945a05538 100644 --- a/comfy_extras/nodes_resolution.py +++ b/comfy_extras/nodes_resolution.py @@ -52,6 +52,10 @@ class ResolutionSelector(io.ComfyNode): step=0.1, tooltip="Target total megapixels. 1.0 MP ≈ 1024x1024 for square.", ), + io.ResolutionPreview.Input( + "preview", + tooltip="Live preview of the calculated output resolution.", + ), io.Int.Input( id="multiple", default=8, @@ -73,7 +77,7 @@ class ResolutionSelector(io.ComfyNode): ) @classmethod - def execute(cls, aspect_ratio: str, megapixels: float, multiple: int) -> io.NodeOutput: + def execute(cls, aspect_ratio: str, megapixels: float, multiple: int, preview=None) -> io.NodeOutput: w_ratio, h_ratio = ASPECT_RATIOS[aspect_ratio] total_pixels = megapixels * 1024 * 1024 scale = math.sqrt(total_pixels / (w_ratio * h_ratio)) diff --git a/tests-unit/comfy_api_test/resolution_preview_serialization_test.py b/tests-unit/comfy_api_test/resolution_preview_serialization_test.py new file mode 100644 index 000000000..97a4633e3 --- /dev/null +++ b/tests-unit/comfy_api_test/resolution_preview_serialization_test.py @@ -0,0 +1,61 @@ +from comfy_api.latest._io import ResolutionPreview +from comfy_extras.nodes_resolution import ResolutionSelector + + +def test_resolution_preview_defaults_optional_and_socketless(): + preview = ResolutionPreview.Input("preview") + + assert preview.optional is True + assert preview.socketless is True + + +def test_resolution_preview_serializes_default_widget_names(): + serialized = ResolutionPreview.Input("preview").as_dict() + + assert serialized["socketless"] is True + assert serialized["ratio_widget"] == "aspect_ratio" + assert serialized["megapixels_widget"] == "megapixels" + assert serialized["multiple_widget"] == "multiple" + + +def test_resolution_preview_serializes_custom_widget_names(): + serialized = ResolutionPreview.Input( + "preview", + ratio_widget="ratio", + megapixels_widget="mp", + multiple_widget="resolution_steps", + ).as_dict() + + assert serialized["ratio_widget"] == "ratio" + assert serialized["megapixels_widget"] == "mp" + assert serialized["multiple_widget"] == "resolution_steps" + + +def test_resolution_preview_carries_no_default_value(): + serialized = ResolutionPreview.Input("preview").as_dict() + + assert "default" not in serialized + + +def test_resolution_selector_schema_exposes_optional_preview(): + schema = ResolutionSelector.define_schema() + preview = next(i for i in schema.inputs if i.id == "preview") + + assert preview.optional is True + assert str(preview.get_io_type()) == "RESOLUTION_PREVIEW" + + +def test_resolution_selector_executes_without_preview(): + """The frontend never sends the preview value in the API prompt.""" + output = ResolutionSelector.execute("16:9 (Widescreen)", 1.0, 8) + + assert output.result == (1368, 768) + + +def test_resolution_selector_ignores_preview_value(): + baseline = ResolutionSelector.execute("3:4 (Portrait Standard)", 2.0, 32) + with_preview = ResolutionSelector.execute( + "3:4 (Portrait Standard)", 2.0, 32, preview={"stale": True} + ) + + assert baseline.result == with_preview.result == (1248, 1664)