# Path: uchroma/util.py
# def autocast_decorator(type_hint, fix_arg_func):
#     """
#     Decorator which will invoke fix_arg_func for any
#     arguments annotated with type_hint. The decorated
#     function will then be called with the result.
# 
#     :param type_hint: A PEP484 type hint
#     :param fix_arg_func: Function to invoke
# 
#     :return: decorator
#     """
#     @decorator
#     def wrapper(wrapped, instance, args, kwargs):
#         hinted_args = names = None
#         cache_key = '%s-%s-%s' % (wrapped.__class__.__name__,
#                                   wrapped.__name__, str(type_hint))
# 
#         if cache_key in AUTOCAST_CACHE:
#             hinted_args, names = AUTOCAST_CACHE[cache_key]
#         else:
#             sig = inspect.signature(wrapped)
#             names = list(sig.parameters.keys())
#             hinted_args = [x[0] for x in typing.get_type_hints(wrapped).items() \
#                     if x[1] == type_hint or x[1] == typing.Union[type_hint, None]]
#             AUTOCAST_CACHE[cache_key] = hinted_args, names
# 
#         if len(hinted_args) == 0:
#             raise ValueError("No arguments with %s hint found" % type_hint)
# 
#         new_args = list(args)
#         for hinted_arg in hinted_args:
#             if hinted_arg in kwargs:
#                 kwargs[hinted_arg] = fix_arg_func(kwargs[hinted_arg])
# 
#             elif hinted_arg in names:
#                 idx = names.index(hinted_arg)
#                 if idx < len(new_args):
#                     new_args[idx] = fix_arg_func(new_args[idx])
# 
#         return wrapped(*new_args, **kwargs)
# 
#     return wrapper
# 
# def clamp(value, min_, max_):
#     """
#     Constrain a value to the specified range
# 
#     :param value: Input value
#     :param min_: Range minimum
#     :param max_: Range maximum
# 
#     :return: The constrained value
#     """
#     return max(min_, min(value, max_))
# 
# def lerp(start: float, end: float, amount: float) -> float:
#     """
#     Linear interpolation
# 
#     Return a value between start and stop at the requested percentage
# 
#     :param start: Range start
#     :param end: Range end
#     :param amount: Position in range (0.0 - 1.0)
# 
#     :return: The interpolated value
#     """
#     return start + (end - start) * amount
# 
# def lerp_degrees(start: float, end: float, amount: float) -> float:
#     """
#     Linear interpolation between angles
# 
#     :param start: Range start angle in degrees
#     :param end: Range end angle in degrees
#     :param amount: Angle in range (0.0 - 1.0)
# 
#     :return: The interpolated angle in degrees
#     """
#     start_r = math.radians(start)
#     end_r = math.radians(end)
#     delta = math.atan2(math.sin(end_r - start_r), math.cos(end_r - start_r))
#     return (math.degrees(start_r + delta * amount) + 360.0) % 360.0


import itertools
import math
import random
import re
import numpy as np
from enum import Enum
from typing import Iterable, List, Union
from grapefruit import Color
from hsluv import hsluv_to_rgb, rgb_to_hsluv
from skimage.util import dtype
from uchroma.util import autocast_decorator, clamp, lerp, lerp_degrees
        return rgb_to_int_tuple(arg)

    raise TypeError('Unable to parse color from \'%s\' (%s)' % (arg, type(arg)))


"""
Decorator to parse various color representations

Invokes to_color on any arguments listed in decls. This will cause
the listed arguments to be resolved to grapefruit.Color objects from
the various different representations that might be in use.

Example:

@colorarg
def frobizzle(self, speed, color1: ColorType=None, color2: ColorType=None)
"""
colorarg = autocast_decorator(ColorType, to_color)



class ColorUtils(object):
    """
    Various helpers and utilities for working with colors
    """

    @staticmethod
    def _circular_interp(start, end, amount: float) -> tuple:
        h = lerp_degrees(start[0], end[0], amount)
        s = lerp(start[1], end[1], amount)

# Path: uchroma/util.py
# def autocast_decorator(type_hint, fix_arg_func):
#     """
#     Decorator which will invoke fix_arg_func for any
#     arguments annotated with type_hint. The decorated
#     function will then be called with the result.
# 
#     :param type_hint: A PEP484 type hint
#     :param fix_arg_func: Function to invoke
# 
#     :return: decorator
#     """
#     @decorator
#     def wrapper(wrapped, instance, args, kwargs):
#         hinted_args = names = None
#         cache_key = '%s-%s-%s' % (wrapped.__class__.__name__,
#                                   wrapped.__name__, str(type_hint))
# 
#         if cache_key in AUTOCAST_CACHE:
#             hinted_args, names = AUTOCAST_CACHE[cache_key]
#         else:
#             sig = inspect.signature(wrapped)
#             names = list(sig.parameters.keys())
#             hinted_args = [x[0] for x in typing.get_type_hints(wrapped).items() \
#                     if x[1] == type_hint or x[1] == typing.Union[type_hint, None]]
#             AUTOCAST_CACHE[cache_key] = hinted_args, names
# 
#         if len(hinted_args) == 0:
#             raise ValueError("No arguments with %s hint found" % type_hint)
# 
#         new_args = list(args)
#         for hinted_arg in hinted_args:
#             if hinted_arg in kwargs:
#                 kwargs[hinted_arg] = fix_arg_func(kwargs[hinted_arg])
# 
#             elif hinted_arg in names:
#                 idx = names.index(hinted_arg)
#                 if idx < len(new_args):
#                     new_args[idx] = fix_arg_func(new_args[idx])
# 
#         return wrapped(*new_args, **kwargs)
# 
#     return wrapper
# 
# def clamp(value, min_, max_):
#     """
#     Constrain a value to the specified range
# 
#     :param value: Input value
#     :param min_: Range minimum
#     :param max_: Range maximum
# 
#     :return: The constrained value
#     """
#     return max(min_, min(value, max_))
# 
# def lerp(start: float, end: float, amount: float) -> float:
#     """
#     Linear interpolation
# 
#     Return a value between start and stop at the requested percentage
# 
#     :param start: Range start
#     :param end: Range end
#     :param amount: Position in range (0.0 - 1.0)
# 
#     :return: The interpolated value
#     """
#     return start + (end - start) * amount
# 
# def lerp_degrees(start: float, end: float, amount: float) -> float:
#     """
#     Linear interpolation between angles
# 
#     :param start: Range start angle in degrees
#     :param end: Range end angle in degrees
#     :param amount: Angle in range (0.0 - 1.0)
# 
#     :return: The interpolated angle in degrees
#     """
#     start_r = math.radians(start)
#     end_r = math.radians(end)
#     delta = math.atan2(math.sin(end_r - start_r), math.cos(end_r - start_r))
#     return (math.degrees(start_r + delta * amount) + 360.0) % 360.0


# Path: uchroma/color.py
import itertools
import math
import random
import re
import numpy as np
from enum import Enum
from typing import Iterable, List, Union
from grapefruit import Color
from hsluv import hsluv_to_rgb, rgb_to_hsluv
from skimage.util import dtype
from uchroma.util import autocast_decorator, clamp, lerp, lerp_degrees

        return rgb_to_int_tuple(arg)

    raise TypeError('Unable to parse color from \'%s\' (%s)' % (arg, type(arg)))


"""
Decorator to parse various color representations

Invokes to_color on any arguments listed in decls. This will cause
the listed arguments to be resolved to grapefruit.Color objects from
the various different representations that might be in use.

Example:

@colorarg
def frobizzle(self, speed, color1: ColorType=None, color2: ColorType=None)
"""
colorarg = autocast_decorator(ColorType, to_color)



class ColorUtils(object):
    """
    Various helpers and utilities for working with colors
    """

    @staticmethod
    def _circular_interp(start, end, amount: float) -> tuple:
        h = lerp_degrees(start[0], end[0], amount)
        s = lerp(start[1], end[1], amount)

        v = lerp(start[2], end[2], amount)