Initial commit: Image Aspect Size and Multi Switch nodes
- ImageAspectSize: reads input image dimensions and outputs width/height scaled to a target longest-side, snapped to multiples of 8, with a flip toggle for portrait/landscape rotation - MultiSwitch: any-type switch node with dynamic slot pairs (JS-driven add/remove), colour-coded active/inactive sides, and clean labelling
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,43 @@
|
||||
import torch
|
||||
|
||||
|
||||
class ImageAspectSize:
|
||||
TITLE = "Image Aspect Size"
|
||||
CATEGORY = "JezzWTF/image"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"target_size": ("INT", {
|
||||
"default": 1024,
|
||||
"min": 64,
|
||||
"max": 8192,
|
||||
"step": 8,
|
||||
"tooltip": "Longest side in pixels. The other dimension is calculated to preserve aspect ratio, snapped to multiples of 8.",
|
||||
}),
|
||||
"flip": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"label_on": "Flipped (portrait↔landscape)",
|
||||
"label_off": "Normal",
|
||||
"tooltip": "Swap width and height before scaling — useful for rotating orientation without changing the image.",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "INT", "INT")
|
||||
RETURN_NAMES = ("IMAGE", "WIDTH", "HEIGHT")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, image: torch.Tensor, target_size: int, flip: bool) -> tuple[torch.Tensor, int, int]:
|
||||
_, H, W, _ = image.shape
|
||||
|
||||
if flip:
|
||||
W, H = H, W
|
||||
|
||||
scale = target_size / max(W, H)
|
||||
width = round(W * scale / 8) * 8
|
||||
height = round(H * scale / 8) * 8
|
||||
|
||||
return (image, width, height)
|
||||
@@ -0,0 +1,58 @@
|
||||
MAX_SLOTS = 6
|
||||
|
||||
|
||||
class AnyType(str):
|
||||
"""Compares equal to every type so ComfyUI accepts any connection."""
|
||||
|
||||
def __ne__(self, other: object) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
ANY = AnyType("*")
|
||||
|
||||
|
||||
class ContainsAnyDict(dict):
|
||||
"""An empty dict that reports containing any key.
|
||||
Empty → ComfyUI renders no input slots from the Python definition.
|
||||
__contains__ always True → any JS-added input name passes validation.
|
||||
dict subclass → JSON serialisable (serialises as {})."""
|
||||
|
||||
def __contains__(self, key: object) -> bool:
|
||||
return True
|
||||
|
||||
def __getitem__(self, key: str) -> tuple:
|
||||
return (ANY, {})
|
||||
|
||||
|
||||
class MultiSwitch:
|
||||
TITLE = "Multi Switch"
|
||||
CATEGORY = "JezzWTF/utils"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"switch": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"label_on": "true",
|
||||
"label_off": "false",
|
||||
"tooltip": "Option A → false_N inputs pass through. Option B → true_N inputs pass through.",
|
||||
}),
|
||||
},
|
||||
"optional": ContainsAnyDict(),
|
||||
"hidden": {
|
||||
"num_slots": ("INT", {"default": 2}),
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, input_types):
|
||||
return True
|
||||
|
||||
RETURN_TYPES = (ANY,) * MAX_SLOTS
|
||||
RETURN_NAMES = tuple(f"output_{i}" for i in range(1, MAX_SLOTS + 1))
|
||||
FUNCTION = "execute"
|
||||
|
||||
def execute(self, switch: bool, num_slots: int = 2, **kwargs) -> tuple:
|
||||
side = "true" if switch else "false"
|
||||
return tuple(kwargs.get(f"{side}_{i}", None) for i in range(1, MAX_SLOTS + 1))
|
||||
Reference in New Issue
Block a user