mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-04-14 12:32:31 +08:00
range type
This commit is contained in:
parent
b615af1c65
commit
8822627a60
@ -9,6 +9,7 @@ from comfy_api.latest._input import (
|
|||||||
CurveInput,
|
CurveInput,
|
||||||
MonotoneCubicCurve,
|
MonotoneCubicCurve,
|
||||||
LinearCurve,
|
LinearCurve,
|
||||||
|
RangeInput,
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@ -21,4 +22,5 @@ __all__ = [
|
|||||||
"CurveInput",
|
"CurveInput",
|
||||||
"MonotoneCubicCurve",
|
"MonotoneCubicCurve",
|
||||||
"LinearCurve",
|
"LinearCurve",
|
||||||
|
"RangeInput",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from .basic_types import ImageInput, AudioInput, MaskInput, LatentInput
|
from .basic_types import ImageInput, AudioInput, MaskInput, LatentInput
|
||||||
from .curve_types import CurvePoint, CurveInput, MonotoneCubicCurve, LinearCurve
|
from .curve_types import CurvePoint, CurveInput, MonotoneCubicCurve, LinearCurve
|
||||||
|
from .range_types import RangeInput
|
||||||
from .video_types import VideoInput
|
from .video_types import VideoInput
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@ -12,4 +13,5 @@ __all__ = [
|
|||||||
"CurveInput",
|
"CurveInput",
|
||||||
"MonotoneCubicCurve",
|
"MonotoneCubicCurve",
|
||||||
"LinearCurve",
|
"LinearCurve",
|
||||||
|
"RangeInput",
|
||||||
]
|
]
|
||||||
|
|||||||
70
comfy_api/latest/_input/range_types.py
Normal file
70
comfy_api/latest/_input/range_types.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RangeInput:
|
||||||
|
"""Represents a levels/range adjustment: input range [min, max] with
|
||||||
|
optional midpoint (gamma control).
|
||||||
|
|
||||||
|
Generates a 1D LUT identical to GIMP's levels mapping:
|
||||||
|
1. Normalize input to [0, 1] using [min, max]
|
||||||
|
2. Apply gamma correction: pow(value, 1/gamma)
|
||||||
|
3. Clamp to [0, 1]
|
||||||
|
|
||||||
|
The midpoint field is a position in [0, 1] representing where the
|
||||||
|
midtone falls within [min, max]. It maps to gamma via:
|
||||||
|
gamma = -log2(midpoint)
|
||||||
|
So midpoint=0.5 → gamma=1.0 (linear).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, min_val: float, max_val: float, midpoint: float | None = None):
|
||||||
|
self.min_val = min_val
|
||||||
|
self.max_val = max_val
|
||||||
|
self.midpoint = midpoint
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_raw(data) -> RangeInput:
|
||||||
|
if isinstance(data, RangeInput):
|
||||||
|
return data
|
||||||
|
if isinstance(data, dict):
|
||||||
|
return RangeInput(
|
||||||
|
min_val=float(data.get("min", 0.0)),
|
||||||
|
max_val=float(data.get("max", 1.0)),
|
||||||
|
midpoint=float(data["midpoint"]) if data.get("midpoint") is not None else None,
|
||||||
|
)
|
||||||
|
raise TypeError(f"Cannot convert {type(data)} to RangeInput")
|
||||||
|
|
||||||
|
def to_lut(self, size: int = 256) -> np.ndarray:
|
||||||
|
"""Generate a float64 lookup table mapping [0, 1] input through this
|
||||||
|
levels adjustment.
|
||||||
|
|
||||||
|
The LUT maps normalized input values (0..1) to output values (0..1),
|
||||||
|
matching the GIMP levels formula.
|
||||||
|
"""
|
||||||
|
xs = np.linspace(0.0, 1.0, size, dtype=np.float64)
|
||||||
|
|
||||||
|
in_range = self.max_val - self.min_val
|
||||||
|
if abs(in_range) < 1e-10:
|
||||||
|
return np.where(xs >= self.min_val, 1.0, 0.0).astype(np.float64)
|
||||||
|
|
||||||
|
# Normalize: map [min, max] → [0, 1]
|
||||||
|
result = (xs - self.min_val) / in_range
|
||||||
|
result = np.clip(result, 0.0, 1.0)
|
||||||
|
|
||||||
|
# Gamma correction from midpoint
|
||||||
|
if self.midpoint is not None and self.midpoint > 0 and self.midpoint != 0.5:
|
||||||
|
gamma = max(-math.log2(self.midpoint), 0.001)
|
||||||
|
inv_gamma = 1.0 / gamma
|
||||||
|
mask = result > 0
|
||||||
|
result[mask] = np.power(result[mask], inv_gamma)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
mid = f", midpoint={self.midpoint}" if self.midpoint is not None else ""
|
||||||
|
return f"RangeInput(min={self.min_val}, max={self.max_val}{mid})"
|
||||||
@ -1266,6 +1266,43 @@ class Histogram(ComfyTypeIO):
|
|||||||
Type = list[int]
|
Type = list[int]
|
||||||
|
|
||||||
|
|
||||||
|
@comfytype(io_type="RANGE")
|
||||||
|
class Range(ComfyTypeIO):
|
||||||
|
from comfy_api.input import RangeInput
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
Type = RangeInput
|
||||||
|
|
||||||
|
class Input(WidgetInput):
|
||||||
|
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None,
|
||||||
|
socketless: bool=True, default: dict=None,
|
||||||
|
display: str=None,
|
||||||
|
gradient_stops: list=None,
|
||||||
|
show_midpoint: bool=None,
|
||||||
|
midpoint_scale: str=None,
|
||||||
|
value_min: float=None,
|
||||||
|
value_max: float=None,
|
||||||
|
advanced: bool=None):
|
||||||
|
super().__init__(id, display_name, optional, tooltip, None, default, socketless, None, None, None, None, advanced)
|
||||||
|
if default is None:
|
||||||
|
self.default = {"min": 0.0, "max": 1.0}
|
||||||
|
self.display = display
|
||||||
|
self.gradient_stops = gradient_stops
|
||||||
|
self.show_midpoint = show_midpoint
|
||||||
|
self.midpoint_scale = midpoint_scale
|
||||||
|
self.value_min = value_min
|
||||||
|
self.value_max = value_max
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return super().as_dict() | prune_dict({
|
||||||
|
"display": self.display,
|
||||||
|
"gradient_stops": self.gradient_stops,
|
||||||
|
"show_midpoint": self.show_midpoint,
|
||||||
|
"midpoint_scale": self.midpoint_scale,
|
||||||
|
"value_min": self.value_min,
|
||||||
|
"value_max": self.value_max,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
DYNAMIC_INPUT_LOOKUP: dict[str, Callable[[dict[str, Any], dict[str, Any], tuple[str, dict[str, Any]], str, list[str] | None], None]] = {}
|
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]):
|
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
|
DYNAMIC_INPUT_LOOKUP[io_type] = func
|
||||||
@ -2276,5 +2313,6 @@ __all__ = [
|
|||||||
"BoundingBox",
|
"BoundingBox",
|
||||||
"Curve",
|
"Curve",
|
||||||
"Histogram",
|
"Histogram",
|
||||||
|
"Range",
|
||||||
"NodeReplace",
|
"NodeReplace",
|
||||||
]
|
]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user