Files
agarzon-ComfyUI-Imagent/imagent/params.py
T
Alexander Garzon 6d3d2fca46 feat: initial public release of ComfyUI-Imagent
BYOK OpenAI gpt-image generation and editing nodes for ComfyUI — calls go
directly to the OpenAI API with your own key (no proxy, no credit markup).

- Two nodes: "Imagent: OpenAI Image" (text-to-image) and "Imagent: OpenAI
  Image Edit" (edit / inpaint / multi-reference compose).
- Models gpt-image-2 / gpt-image-1.5 / gpt-image-1 with a per-model dynamic UI
  (comfy_api DynamicCombo): size, background, custom WxH, and input_fidelity
  are revealed only for the models that support them.
- Edit node accepts an auto-growing set of reference images (up to 16) plus an
  optional inpaint mask; generate supports batches up to 8.
- API key via OPENAI_API_KEY or a local config.json.

Requires ComfyUI >= 0.23.0.
2026-06-08 09:39:21 -04:00

117 lines
5.0 KiB
Python

"""Parameter constants, per-model capabilities, and size validation.
Public entry points: capabilities_for(), validate_custom_dimensions(), resolve_size(),
resolve_background(); plus the SIZE_PRESETS / QUALITIES / BACKGROUNDS / FORMATS /
INPUT_FIDELITIES / MODERATIONS lists and the CUSTOM_DIM_* widget bounds.
"""
from __future__ import annotations
import logging
log = logging.getLogger("imagent")
# Size presets. The base sizes work on every gpt-image model; the high-res presets
# and 'custom' are gpt-image-2 only (gated in resolve_size to stay cross-model safe).
_BASE_SIZES = ["auto", "1024x1024", "1024x1536", "1536x1024"]
_GPT2_SIZES = ["2048x2048", "2048x1152", "1152x2048", "3840x2160", "2160x3840"]
_GPT2_ONLY_SIZES = set(_GPT2_SIZES)
SIZE_PRESETS = [*_BASE_SIZES, *_GPT2_SIZES, "custom"]
# Per-model size option lists for the IO DynamicCombo (what each model can pick).
SIZES_BASE = list(_BASE_SIZES) # gpt-image-1 / 1.5
SIZES_GPT2 = [*_BASE_SIZES, *_GPT2_SIZES, "custom"] # gpt-image-2
# custom_width / custom_height INT-widget bounds (mirror the official node).
CUSTOM_DIM_MIN, CUSTOM_DIM_MAX, CUSTOM_DIM_STEP = 1024, 3840, 16
# Per-model background option lists (gpt-image-2 cannot do transparent).
BACKGROUNDS = ["auto", "opaque", "transparent"]
BACKGROUNDS_GPT2 = ["auto", "opaque"]
QUALITIES = ["auto", "low", "medium", "high"]
FORMATS = ["png", "jpeg", "webp"]
INPUT_FIDELITIES = ["high", "low"]
MODERATIONS = ["auto", "low"]
# Widget bounds shared by both nodes' IO schemas.
N_MIN, N_MAX = 1, 8
# Per-model capability flags. Keys (all default False for unknown models):
# custom_size - arbitrary WxH sizes (gpt-image-2 only)
# input_fidelity - edit input_fidelity param (gpt-image-1.x)
# transparent_background - background="transparent" (NOT gpt-image-2, which errors)
_DEFAULT_CAPS = {
"custom_size": False, "input_fidelity": False, "transparent_background": False,
}
CAPABILITIES: dict[str, dict[str, bool]] = {
"gpt-image-2": {"custom_size": True, "input_fidelity": False, "transparent_background": False},
"gpt-image-1.5": {"custom_size": False, "input_fidelity": True, "transparent_background": True},
"gpt-image-1": {"custom_size": False, "input_fidelity": True, "transparent_background": True},
}
# gpt-image-2 custom-resolution constraints (mirror the official node, 2026-06-08).
_MAX_EDGE = 3840
_MIN_TOTAL_PX = 655_360
_MAX_TOTAL_PX = 8_294_400
def capabilities_for(model: str) -> dict[str, bool]:
"""Return a copy of the capability flags for *model*, defaulting to all-False."""
return dict(CAPABILITIES.get(model, _DEFAULT_CAPS))
def validate_custom_dimensions(width: int, height: int) -> str:
"""Validate gpt-image-2 custom WxH dimensions; return the normalized 'WxH'.
Rules match the official node: multiples of 16, max edge 3840, aspect <= 3:1,
and total pixels between 655,360 and 8,294,400.
"""
try:
w, h = int(width), int(height)
except (TypeError, ValueError):
raise ValueError(f"custom dimensions must be integers, got {width!r}x{height!r}") from None
if w <= 0 or h <= 0:
raise ValueError(f"custom dimensions must be positive, got {w}x{h}")
if w % 16 or h % 16:
raise ValueError(f"custom width and height must be multiples of 16, got {w}x{h}")
if max(w, h) > _MAX_EDGE:
raise ValueError(f"custom max edge must be <= {_MAX_EDGE}, got {w}x{h}")
if max(w, h) / min(w, h) > 3:
raise ValueError(f"custom aspect ratio must not exceed 3:1, got {w}x{h}")
total = w * h
if not (_MIN_TOTAL_PX <= total <= _MAX_TOTAL_PX):
raise ValueError(
f"custom total pixels must be between {_MIN_TOTAL_PX} and {_MAX_TOTAL_PX}, got {total}")
return f"{w}x{h}"
def resolve_size(size: str, custom_width: int, custom_height: int, model: str) -> str:
"""Resolve the size parameter to a value the selected model accepts.
'custom' and the high-res presets are gpt-image-2 only; for other models they
fall back to 'auto' so switching models never sends an unsupported size.
"""
caps = capabilities_for(model)
if size == "custom":
if not caps["custom_size"]:
log.info("imagent: %s does not support custom sizes; using 'auto'.", model)
return "auto"
return validate_custom_dimensions(custom_width, custom_height)
if size in _GPT2_ONLY_SIZES and not caps["custom_size"]:
log.info("imagent: size %s is gpt-image-2 only; using 'auto' for %s.", size, model)
return "auto"
return size
def resolve_background(background: str, model: str) -> str:
"""Drop an unsupported transparent background to 'auto'.
gpt-image-2 errors on background='transparent'; only gpt-image-1.x supports
it. Falling back to 'auto' keeps the node compatible across model switches.
"""
if background == "transparent" and not capabilities_for(model)["transparent_background"]:
log.info("imagent: %s does not support a transparent background; using 'auto'.", model)
return "auto"
return background