Coverage for pydantic/color.py: 100.00%
196 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-13 19:35 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-13 19:35 +0000
1"""Color definitions are used as per the CSS3
2[CSS Color Module Level 3](http://www.w3.org/TR/css3-color/#svg-color) specification.
4A few colors have multiple names referring to the sames colors, eg. `grey` and `gray` or `aqua` and `cyan`.
6In these cases the _last_ color when sorted alphabetically takes preferences,
7eg. `Color((0, 255, 255)).as_named() == 'cyan'` because "cyan" comes after "aqua".
9Warning: Deprecated
10 The `Color` class is deprecated, use `pydantic_extra_types` instead.
11 See [`pydantic-extra-types.Color`](../usage/types/extra_types/color_types.md)
12 for more information.
13"""
15import math 1abcdefghijklmnopqrstuvwxyzABCDEF
16import re 1abcdefghijklmnopqrstuvwxyzABCDEF
17from colorsys import hls_to_rgb, rgb_to_hls 1abcdefghijklmnopqrstuvwxyzABCDEF
18from typing import Any, Callable, Optional, Union, cast 1abcdefghijklmnopqrstuvwxyzABCDEF
20from pydantic_core import CoreSchema, PydanticCustomError, core_schema 1abcdefghijklmnopqrstuvwxyzABCDEF
21from typing_extensions import deprecated 1abcdefghijklmnopqrstuvwxyzABCDEF
23from ._internal import _repr 1abcdefghijklmnopqrstuvwxyzABCDEF
24from ._internal._schema_generation_shared import GetJsonSchemaHandler as _GetJsonSchemaHandler 1abcdefghijklmnopqrstuvwxyzABCDEF
25from .json_schema import JsonSchemaValue 1abcdefghijklmnopqrstuvwxyzABCDEF
26from .warnings import PydanticDeprecatedSince20 1abcdefghijklmnopqrstuvwxyzABCDEF
28ColorTuple = Union[tuple[int, int, int], tuple[int, int, int, float]] 1abcdefghijklmnopqrstuvwxyzABCDEF
29ColorType = Union[ColorTuple, str] 1abcdefghijklmnopqrstuvwxyzABCDEF
30HslColorTuple = Union[tuple[float, float, float], tuple[float, float, float, float]] 1abcdefghijklmnopqrstuvwxyzABCDEF
33class RGBA: 1abcdefghijklmnopqrstuvwxyzABCDEF
34 """Internal use only as a representation of a color."""
36 __slots__ = 'r', 'g', 'b', 'alpha', '_tuple' 1abcdefghijklmnopqrstuvwxyzABCDEF
38 def __init__(self, r: float, g: float, b: float, alpha: Optional[float]): 1abcdefghijklmnopqrstuvwxyzABCDEF
39 self.r = r 1abcdefghijklmnopqrstuvwxyzABCDEF
40 self.g = g 1abcdefghijklmnopqrstuvwxyzABCDEF
41 self.b = b 1abcdefghijklmnopqrstuvwxyzABCDEF
42 self.alpha = alpha 1abcdefghijklmnopqrstuvwxyzABCDEF
44 self._tuple: tuple[float, float, float, Optional[float]] = (r, g, b, alpha) 1abcdefghijklmnopqrstuvwxyzABCDEF
46 def __getitem__(self, item: Any) -> Any: 1abcdefghijklmnopqrstuvwxyzABCDEF
47 return self._tuple[item] 1abcdefghijklmnopqrstuvwxyzABCDEF
50# these are not compiled here to avoid import slowdown, they'll be compiled the first time they're used, then cached
51_r_255 = r'(\d{1,3}(?:\.\d+)?)' 1abcdefghijklmnopqrstuvwxyzABCDEF
52_r_comma = r'\s*,\s*' 1abcdefghijklmnopqrstuvwxyzABCDEF
53_r_alpha = r'(\d(?:\.\d+)?|\.\d+|\d{1,2}%)' 1abcdefghijklmnopqrstuvwxyzABCDEF
54_r_h = r'(-?\d+(?:\.\d+)?|-?\.\d+)(deg|rad|turn)?' 1abcdefghijklmnopqrstuvwxyzABCDEF
55_r_sl = r'(\d{1,3}(?:\.\d+)?)%' 1abcdefghijklmnopqrstuvwxyzABCDEF
56r_hex_short = r'\s*(?:#|0x)?([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?\s*' 1abcdefghijklmnopqrstuvwxyzABCDEF
57r_hex_long = r'\s*(?:#|0x)?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?\s*' 1abcdefghijklmnopqrstuvwxyzABCDEF
58# CSS3 RGB examples: rgb(0, 0, 0), rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 50%)
59r_rgb = rf'\s*rgba?\(\s*{_r_255}{_r_comma}{_r_255}{_r_comma}{_r_255}(?:{_r_comma}{_r_alpha})?\s*\)\s*' 1abcdefghijklmnopqrstuvwxyzABCDEF
60# CSS3 HSL examples: hsl(270, 60%, 50%), hsla(270, 60%, 50%, 0.5), hsla(270, 60%, 50%, 50%)
61r_hsl = rf'\s*hsla?\(\s*{_r_h}{_r_comma}{_r_sl}{_r_comma}{_r_sl}(?:{_r_comma}{_r_alpha})?\s*\)\s*' 1abcdefghijklmnopqrstuvwxyzABCDEF
62# CSS4 RGB examples: rgb(0 0 0), rgb(0 0 0 / 0.5), rgb(0 0 0 / 50%), rgba(0 0 0 / 50%)
63r_rgb_v4_style = rf'\s*rgba?\(\s*{_r_255}\s+{_r_255}\s+{_r_255}(?:\s*/\s*{_r_alpha})?\s*\)\s*' 1abcdefghijklmnopqrstuvwxyzABCDEF
64# CSS4 HSL examples: hsl(270 60% 50%), hsl(270 60% 50% / 0.5), hsl(270 60% 50% / 50%), hsla(270 60% 50% / 50%)
65r_hsl_v4_style = rf'\s*hsla?\(\s*{_r_h}\s+{_r_sl}\s+{_r_sl}(?:\s*/\s*{_r_alpha})?\s*\)\s*' 1abcdefghijklmnopqrstuvwxyzABCDEF
67# colors where the two hex characters are the same, if all colors match this the short version of hex colors can be used
68repeat_colors = {int(c * 2, 16) for c in '0123456789abcdef'} 1abcdefghijklmnopqrstuvwxyzABCDEF
69rads = 2 * math.pi 1abcdefghijklmnopqrstuvwxyzABCDEF
72@deprecated( 1abcdefghijklmnopqrstuvwxyzABCDEF
73 'The `Color` class is deprecated, use `pydantic_extra_types` instead. '
74 'See https://docs.pydantic.dev/latest/api/pydantic_extra_types_color/.',
75 category=PydanticDeprecatedSince20,
76)
77class Color(_repr.Representation): 1abcdefghijklmnopqrstuvwxyzABCDEF
78 """Represents a color."""
80 __slots__ = '_original', '_rgba' 1abcdefghijklmnopqrstuvwxyzABCDEF
82 def __init__(self, value: ColorType) -> None: 1abcdefghijklmnopqrstuvwxyzABCDEF
83 self._rgba: RGBA 1abcdefghijklmnopqrstuvwxyzABCDEF
84 self._original: ColorType 1abcdefghijklmnopqrstuvwxyzABCDEF
85 if isinstance(value, (tuple, list)): 1abcdefghijklmnopqrstuvwxyzABCDEF
86 self._rgba = parse_tuple(value) 1abcdefghijklmnopqrstuvwxyzABCDEF
87 elif isinstance(value, str): 1abcdefghijklmnopqrstuvwxyzABCDEF
88 self._rgba = parse_str(value) 1abcdefghijklmnopqrstuvwxyzABCDEF
89 elif isinstance(value, Color): 1abcdefghijklmnopqrstuvwxyzABCDEF
90 self._rgba = value._rgba 1abcdefghijklmnopqrstuvwxyzABCDEF
91 value = value._original 1abcdefghijklmnopqrstuvwxyzABCDEF
92 else:
93 raise PydanticCustomError( 1abcdefghijklmnopqrstuvwxyzABCDEF
94 'color_error', 'value is not a valid color: value must be a tuple, list or string'
95 )
97 # if we've got here value must be a valid color
98 self._original = value 1abcdefghijklmnopqrstuvwxyzABCDEF
100 @classmethod 1abcdefghijklmnopqrstuvwxyzABCDEF
101 def __get_pydantic_json_schema__( 1abcdefghijklmnopqrstuvwxyzABCDEF
102 cls, core_schema: core_schema.CoreSchema, handler: _GetJsonSchemaHandler
103 ) -> JsonSchemaValue:
104 field_schema = {} 1abcdefghijklmnopqrstuvwxyzABCDEF
105 field_schema.update(type='string', format='color') 1abcdefghijklmnopqrstuvwxyzABCDEF
106 return field_schema 1abcdefghijklmnopqrstuvwxyzABCDEF
108 def original(self) -> ColorType: 1abcdefghijklmnopqrstuvwxyzABCDEF
109 """Original value passed to `Color`."""
110 return self._original 1abcdefghijklmnopqrstuvwxyzABCDEF
112 def as_named(self, *, fallback: bool = False) -> str: 1abcdefghijklmnopqrstuvwxyzABCDEF
113 """Returns the name of the color if it can be found in `COLORS_BY_VALUE` dictionary,
114 otherwise returns the hexadecimal representation of the color or raises `ValueError`.
116 Args:
117 fallback: If True, falls back to returning the hexadecimal representation of
118 the color instead of raising a ValueError when no named color is found.
120 Returns:
121 The name of the color, or the hexadecimal representation of the color.
123 Raises:
124 ValueError: When no named color is found and fallback is `False`.
125 """
126 if self._rgba.alpha is None: 1abcdefghijklmnopqrstuvwxyzABCDEF
127 rgb = cast(tuple[int, int, int], self.as_rgb_tuple()) 1abcdefghijklmnopqrstuvwxyzABCDEF
128 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
129 return COLORS_BY_VALUE[rgb] 1abcdefghijklmnopqrstuvwxyzABCDEF
130 except KeyError as e: 1abcdefghijklmnopqrstuvwxyzABCDEF
131 if fallback: 1abcdefghijklmnopqrstuvwxyzABCDEF
132 return self.as_hex() 1abcdefghijklmnopqrstuvwxyzABCDEF
133 else:
134 raise ValueError('no named color found, use fallback=True, as_hex() or as_rgb()') from e 1abcdefghijklmnopqrstuvwxyzABCDEF
135 else:
136 return self.as_hex() 1abcdefghijklmnopqrstuvwxyzABCDEF
138 def as_hex(self) -> str: 1abcdefghijklmnopqrstuvwxyzABCDEF
139 """Returns the hexadecimal representation of the color.
141 Hex string representing the color can be 3, 4, 6, or 8 characters depending on whether the string
142 a "short" representation of the color is possible and whether there's an alpha channel.
144 Returns:
145 The hexadecimal representation of the color.
146 """
147 values = [float_to_255(c) for c in self._rgba[:3]] 1abcdefghijklmnopqrstuvwxyzABCDEF
148 if self._rgba.alpha is not None: 1abcdefghijklmnopqrstuvwxyzABCDEF
149 values.append(float_to_255(self._rgba.alpha)) 1abcdefghijklmnopqrstuvwxyzABCDEF
151 as_hex = ''.join(f'{v:02x}' for v in values) 1abcdefghijklmnopqrstuvwxyzABCDEF
152 if all(c in repeat_colors for c in values): 1abcdefghijklmnopqrstuvwxyzABCDEF
153 as_hex = ''.join(as_hex[c] for c in range(0, len(as_hex), 2)) 1abcdefghijklmnopqrstuvwxyzABCDEF
154 return '#' + as_hex 1abcdefghijklmnopqrstuvwxyzABCDEF
156 def as_rgb(self) -> str: 1abcdefghijklmnopqrstuvwxyzABCDEF
157 """Color as an `rgb(<r>, <g>, <b>)` or `rgba(<r>, <g>, <b>, <a>)` string."""
158 if self._rgba.alpha is None: 1abcdefghijklmnopqrstuvwxyzABCDEF
159 return f'rgb({float_to_255(self._rgba.r)}, {float_to_255(self._rgba.g)}, {float_to_255(self._rgba.b)})' 1abcdefghijklmnopqrstuvwxyzABCDEF
160 else:
161 return ( 1abcdefghijklmnopqrstuvwxyzABCDEF
162 f'rgba({float_to_255(self._rgba.r)}, {float_to_255(self._rgba.g)}, {float_to_255(self._rgba.b)}, '
163 f'{round(self._alpha_float(), 2)})'
164 )
166 def as_rgb_tuple(self, *, alpha: Optional[bool] = None) -> ColorTuple: 1abcdefghijklmnopqrstuvwxyzABCDEF
167 """Returns the color as an RGB or RGBA tuple.
169 Args:
170 alpha: Whether to include the alpha channel. There are three options for this input:
172 - `None` (default): Include alpha only if it's set. (e.g. not `None`)
173 - `True`: Always include alpha.
174 - `False`: Always omit alpha.
176 Returns:
177 A tuple that contains the values of the red, green, and blue channels in the range 0 to 255.
178 If alpha is included, it is in the range 0 to 1.
179 """
180 r, g, b = (float_to_255(c) for c in self._rgba[:3]) 1abcdefghijklmnopqrstuvwxyzABCDEF
181 if alpha is None: 1abcdefghijklmnopqrstuvwxyzABCDEF
182 if self._rgba.alpha is None: 1abcdefghijklmnopqrstuvwxyzABCDEF
183 return r, g, b 1abcdefghijklmnopqrstuvwxyzABCDEF
184 else:
185 return r, g, b, self._alpha_float() 1abcdefghijklmnopqrstuvwxyzABCDEF
186 elif alpha: 1abcdefghijklmnopqrstuvwxyzABCDEF
187 return r, g, b, self._alpha_float() 1abcdefghijklmnopqrstuvwxyzABCDEF
188 else:
189 # alpha is False
190 return r, g, b 1abcdefghijklmnopqrstuvwxyzABCDEF
192 def as_hsl(self) -> str: 1abcdefghijklmnopqrstuvwxyzABCDEF
193 """Color as an `hsl(<h>, <s>, <l>)` or `hsl(<h>, <s>, <l>, <a>)` string."""
194 if self._rgba.alpha is None: 1abcdefghijklmnopqrstuvwxyzABCDEF
195 h, s, li = self.as_hsl_tuple(alpha=False) # type: ignore 1abcdefghijklmnopqrstuvwxyzABCDEF
196 return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%})' 1abcdefghijklmnopqrstuvwxyzABCDEF
197 else:
198 h, s, li, a = self.as_hsl_tuple(alpha=True) # type: ignore 1abcdefghijklmnopqrstuvwxyzABCDEF
199 return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%}, {round(a, 2)})' 1abcdefghijklmnopqrstuvwxyzABCDEF
201 def as_hsl_tuple(self, *, alpha: Optional[bool] = None) -> HslColorTuple: 1abcdefghijklmnopqrstuvwxyzABCDEF
202 """Returns the color as an HSL or HSLA tuple.
204 Args:
205 alpha: Whether to include the alpha channel.
207 - `None` (default): Include the alpha channel only if it's set (e.g. not `None`).
208 - `True`: Always include alpha.
209 - `False`: Always omit alpha.
211 Returns:
212 The color as a tuple of hue, saturation, lightness, and alpha (if included).
213 All elements are in the range 0 to 1.
215 Note:
216 This is HSL as used in HTML and most other places, not HLS as used in Python's `colorsys`.
217 """
218 h, l, s = rgb_to_hls(self._rgba.r, self._rgba.g, self._rgba.b) # noqa: E741 1abcdefghijklmnopqrstuvwxyzABCDEF
219 if alpha is None: 1abcdefghijklmnopqrstuvwxyzABCDEF
220 if self._rgba.alpha is None: 1abcdefghijklmnopqrstuvwxyzABCDEF
221 return h, s, l 1abcdefghijklmnopqrstuvwxyzABCDEF
222 else:
223 return h, s, l, self._alpha_float() 1abcdefghijklmnopqrstuvwxyzABCDEF
224 if alpha: 1abcdefghijklmnopqrstuvwxyzABCDEF
225 return h, s, l, self._alpha_float() 1abcdefghijklmnopqrstuvwxyzABCDEF
226 else:
227 # alpha is False
228 return h, s, l 1abcdefghijklmnopqrstuvwxyzABCDEF
230 def _alpha_float(self) -> float: 1abcdefghijklmnopqrstuvwxyzABCDEF
231 return 1 if self._rgba.alpha is None else self._rgba.alpha 1abcdefghijklmnopqrstuvwxyzABCDEF
233 @classmethod 1abcdefghijklmnopqrstuvwxyzABCDEF
234 def __get_pydantic_core_schema__( 1abcdefghijklmnopqrstuvwxyzABCDEF
235 cls, source: type[Any], handler: Callable[[Any], CoreSchema]
236 ) -> core_schema.CoreSchema:
237 return core_schema.with_info_plain_validator_function( 1abcdefghijklmnopqrstuvwxyzABCDEF
238 cls._validate, serialization=core_schema.to_string_ser_schema()
239 )
241 @classmethod 1abcdefghijklmnopqrstuvwxyzABCDEF
242 def _validate(cls, __input_value: Any, _: Any) -> 'Color': 1abcdefghijklmnopqrstuvwxyzABCDEF
243 return cls(__input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF
245 def __str__(self) -> str: 1abcdefghijklmnopqrstuvwxyzABCDEF
246 return self.as_named(fallback=True) 1abcdefghijklmnopqrstuvwxyzABCDEF
248 def __repr_args__(self) -> '_repr.ReprArgs': 1abcdefghijklmnopqrstuvwxyzABCDEF
249 return [(None, self.as_named(fallback=True))] + [('rgb', self.as_rgb_tuple())] 1abcdefghijklmnopqrstuvwxyzABCDEF
251 def __eq__(self, other: Any) -> bool: 1abcdefghijklmnopqrstuvwxyzABCDEF
252 return isinstance(other, Color) and self.as_rgb_tuple() == other.as_rgb_tuple() 1abcdefghijklmnopqrstuvwxyzABCDEF
254 def __hash__(self) -> int: 1abcdefghijklmnopqrstuvwxyzABCDEF
255 return hash(self.as_rgb_tuple()) 1abcdefghijklmnopqrstuvwxyzABCDEF
258def parse_tuple(value: tuple[Any, ...]) -> RGBA: 1abcdefghijklmnopqrstuvwxyzABCDEF
259 """Parse a tuple or list to get RGBA values.
261 Args:
262 value: A tuple or list.
264 Returns:
265 An `RGBA` tuple parsed from the input tuple.
267 Raises:
268 PydanticCustomError: If tuple is not valid.
269 """
270 if len(value) == 3: 1abcdefghijklmnopqrstuvwxyzABCDEF
271 r, g, b = (parse_color_value(v) for v in value) 1abcdefghijklmnopqrstuvwxyzABCDEF
272 return RGBA(r, g, b, None) 1abcdefghijklmnopqrstuvwxyzABCDEF
273 elif len(value) == 4: 1abcdefghijklmnopqrstuvwxyzABCDEF
274 r, g, b = (parse_color_value(v) for v in value[:3]) 1abcdefghijklmnopqrstuvwxyzABCDEF
275 return RGBA(r, g, b, parse_float_alpha(value[3])) 1abcdefghijklmnopqrstuvwxyzABCDEF
276 else:
277 raise PydanticCustomError('color_error', 'value is not a valid color: tuples must have length 3 or 4') 1abcdefghijklmnopqrstuvwxyzABCDEF
280def parse_str(value: str) -> RGBA: 1abcdefghijklmnopqrstuvwxyzABCDEF
281 """Parse a string representing a color to an RGBA tuple.
283 Possible formats for the input string include:
285 * named color, see `COLORS_BY_NAME`
286 * hex short eg. `<prefix>fff` (prefix can be `#`, `0x` or nothing)
287 * hex long eg. `<prefix>ffffff` (prefix can be `#`, `0x` or nothing)
288 * `rgb(<r>, <g>, <b>)`
289 * `rgba(<r>, <g>, <b>, <a>)`
291 Args:
292 value: A string representing a color.
294 Returns:
295 An `RGBA` tuple parsed from the input string.
297 Raises:
298 ValueError: If the input string cannot be parsed to an RGBA tuple.
299 """
300 value_lower = value.lower() 1abcdefghijklmnopqrstuvwxyzABCDEF
301 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
302 r, g, b = COLORS_BY_NAME[value_lower] 1abcdefghijklmnopqrstuvwxyzABCDEF
303 except KeyError: 1abcdefghijklmnopqrstuvwxyzABCDEF
304 pass 1abcdefghijklmnopqrstuvwxyzABCDEF
305 else:
306 return ints_to_rgba(r, g, b, None) 1abcdefghijklmnopqrstuvwxyzABCDEF
308 m = re.fullmatch(r_hex_short, value_lower) 1abcdefghijklmnopqrstuvwxyzABCDEF
309 if m: 1abcdefghijklmnopqrstuvwxyzABCDEF
310 *rgb, a = m.groups() 1abcdefghijklmnopqrstuvwxyzABCDEF
311 r, g, b = (int(v * 2, 16) for v in rgb) 1abcdefghijklmnopqrstuvwxyzABCDEF
312 if a: 1abcdefghijklmnopqrstuvwxyzABCDEF
313 alpha: Optional[float] = int(a * 2, 16) / 255 1abcdefghijklmnopqrstuvwxyzABCDEF
314 else:
315 alpha = None 1abcdefghijklmnopqrstuvwxyzABCDEF
316 return ints_to_rgba(r, g, b, alpha) 1abcdefghijklmnopqrstuvwxyzABCDEF
318 m = re.fullmatch(r_hex_long, value_lower) 1abcdefghijklmnopqrstuvwxyzABCDEF
319 if m: 1abcdefghijklmnopqrstuvwxyzABCDEF
320 *rgb, a = m.groups() 1abcdefghijklmnopqrstuvwxyzABCDEF
321 r, g, b = (int(v, 16) for v in rgb) 1abcdefghijklmnopqrstuvwxyzABCDEF
322 if a: 1abcdefghijklmnopqrstuvwxyzABCDEF
323 alpha = int(a, 16) / 255 1abcdefghijklmnopqrstuvwxyzABCDEF
324 else:
325 alpha = None 1abcdefghijklmnopqrstuvwxyzABCDEF
326 return ints_to_rgba(r, g, b, alpha) 1abcdefghijklmnopqrstuvwxyzABCDEF
328 m = re.fullmatch(r_rgb, value_lower) or re.fullmatch(r_rgb_v4_style, value_lower) 1abcdefghijklmnopqrstuvwxyzABCDEF
329 if m: 1abcdefghijklmnopqrstuvwxyzABCDEF
330 return ints_to_rgba(*m.groups()) # type: ignore 1abcdefghijklmnopqrstuvwxyzABCDEF
332 m = re.fullmatch(r_hsl, value_lower) or re.fullmatch(r_hsl_v4_style, value_lower) 1abcdefghijklmnopqrstuvwxyzABCDEF
333 if m: 1abcdefghijklmnopqrstuvwxyzABCDEF
334 return parse_hsl(*m.groups()) # type: ignore 1abcdefghijklmnopqrstuvwxyzABCDEF
336 raise PydanticCustomError('color_error', 'value is not a valid color: string not recognised as a valid color') 1abcdefghijklmnopqrstuvwxyzABCDEF
339def ints_to_rgba(r: Union[int, str], g: Union[int, str], b: Union[int, str], alpha: Optional[float] = None) -> RGBA: 1abcdefghijklmnopqrstuvwxyzABCDEF
340 """Converts integer or string values for RGB color and an optional alpha value to an `RGBA` object.
342 Args:
343 r: An integer or string representing the red color value.
344 g: An integer or string representing the green color value.
345 b: An integer or string representing the blue color value.
346 alpha: A float representing the alpha value. Defaults to None.
348 Returns:
349 An instance of the `RGBA` class with the corresponding color and alpha values.
350 """
351 return RGBA(parse_color_value(r), parse_color_value(g), parse_color_value(b), parse_float_alpha(alpha)) 1abcdefghijklmnopqrstuvwxyzABCDEF
354def parse_color_value(value: Union[int, str], max_val: int = 255) -> float: 1abcdefghijklmnopqrstuvwxyzABCDEF
355 """Parse the color value provided and return a number between 0 and 1.
357 Args:
358 value: An integer or string color value.
359 max_val: Maximum range value. Defaults to 255.
361 Raises:
362 PydanticCustomError: If the value is not a valid color.
364 Returns:
365 A number between 0 and 1.
366 """
367 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
368 color = float(value) 1abcdefghijklmnopqrstuvwxyzABCDEF
369 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEF
370 raise PydanticCustomError('color_error', 'value is not a valid color: color values must be a valid number') 1abcdefghijklmnopqrstuvwxyzABCDEF
371 if 0 <= color <= max_val: 1abcdefghijklmnopqrstuvwxyzABCDEF
372 return color / max_val 1abcdefghijklmnopqrstuvwxyzABCDEF
373 else:
374 raise PydanticCustomError( 1abcdefghijklmnopqrstuvwxyzABCDEF
375 'color_error',
376 'value is not a valid color: color values must be in the range 0 to {max_val}',
377 {'max_val': max_val},
378 )
381def parse_float_alpha(value: Union[None, str, float, int]) -> Optional[float]: 1abcdefghijklmnopqrstuvwxyzABCDEF
382 """Parse an alpha value checking it's a valid float in the range 0 to 1.
384 Args:
385 value: The input value to parse.
387 Returns:
388 The parsed value as a float, or `None` if the value was None or equal 1.
390 Raises:
391 PydanticCustomError: If the input value cannot be successfully parsed as a float in the expected range.
392 """
393 if value is None: 1abcdefghijklmnopqrstuvwxyzABCDEF
394 return None 1abcdefghijklmnopqrstuvwxyzABCDEF
395 try: 1abcdefghijklmnopqrstuvwxyzABCDEF
396 if isinstance(value, str) and value.endswith('%'): 1abcdefghijklmnopqrstuvwxyzABCDEF
397 alpha = float(value[:-1]) / 100 1abcdefghijklmnopqrstuvwxyzABCDEF
398 else:
399 alpha = float(value) 1abcdefghijklmnopqrstuvwxyzABCDEF
400 except ValueError: 1abcdefghijklmnopqrstuvwxyzABCDEF
401 raise PydanticCustomError('color_error', 'value is not a valid color: alpha values must be a valid float') 1abcdefghijklmnopqrstuvwxyzABCDEF
403 if math.isclose(alpha, 1): 1abcdefghijklmnopqrstuvwxyzABCDEF
404 return None 1abcdefghijklmnopqrstuvwxyzABCDEF
405 elif 0 <= alpha <= 1: 1abcdefghijklmnopqrstuvwxyzABCDEF
406 return alpha 1abcdefghijklmnopqrstuvwxyzABCDEF
407 else:
408 raise PydanticCustomError('color_error', 'value is not a valid color: alpha values must be in the range 0 to 1') 1abcdefghijklmnopqrstuvwxyzABCDEF
411def parse_hsl(h: str, h_units: str, sat: str, light: str, alpha: Optional[float] = None) -> RGBA: 1abcdefghijklmnopqrstuvwxyzABCDEF
412 """Parse raw hue, saturation, lightness, and alpha values and convert to RGBA.
414 Args:
415 h: The hue value.
416 h_units: The unit for hue value.
417 sat: The saturation value.
418 light: The lightness value.
419 alpha: Alpha value.
421 Returns:
422 An instance of `RGBA`.
423 """
424 s_value, l_value = parse_color_value(sat, 100), parse_color_value(light, 100) 1abcdefghijklmnopqrstuvwxyzABCDEF
426 h_value = float(h) 1abcdefghijklmnopqrstuvwxyzABCDEF
427 if h_units in {None, 'deg'}: 1abcdefghijklmnopqrstuvwxyzABCDEF
428 h_value = h_value % 360 / 360 1abcdefghijklmnopqrstuvwxyzABCDEF
429 elif h_units == 'rad': 1abcdefghijklmnopqrstuvwxyzABCDEF
430 h_value = h_value % rads / rads 1abcdefghijklmnopqrstuvwxyzABCDEF
431 else:
432 # turns
433 h_value = h_value % 1 1abcdefghijklmnopqrstuvwxyzABCDEF
435 r, g, b = hls_to_rgb(h_value, l_value, s_value) 1abcdefghijklmnopqrstuvwxyzABCDEF
436 return RGBA(r, g, b, parse_float_alpha(alpha)) 1abcdefghijklmnopqrstuvwxyzABCDEF
439def float_to_255(c: float) -> int: 1abcdefghijklmnopqrstuvwxyzABCDEF
440 """Converts a float value between 0 and 1 (inclusive) to an integer between 0 and 255 (inclusive).
442 Args:
443 c: The float value to be converted. Must be between 0 and 1 (inclusive).
445 Returns:
446 The integer equivalent of the given float value rounded to the nearest whole number.
448 Raises:
449 ValueError: If the given float value is outside the acceptable range of 0 to 1 (inclusive).
450 """
451 return int(round(c * 255)) 1abcdefghijklmnopqrstuvwxyzABCDEF
454COLORS_BY_NAME = { 1abcdefghijklmnopqrstuvwxyzABCDEF
455 'aliceblue': (240, 248, 255),
456 'antiquewhite': (250, 235, 215),
457 'aqua': (0, 255, 255),
458 'aquamarine': (127, 255, 212),
459 'azure': (240, 255, 255),
460 'beige': (245, 245, 220),
461 'bisque': (255, 228, 196),
462 'black': (0, 0, 0),
463 'blanchedalmond': (255, 235, 205),
464 'blue': (0, 0, 255),
465 'blueviolet': (138, 43, 226),
466 'brown': (165, 42, 42),
467 'burlywood': (222, 184, 135),
468 'cadetblue': (95, 158, 160),
469 'chartreuse': (127, 255, 0),
470 'chocolate': (210, 105, 30),
471 'coral': (255, 127, 80),
472 'cornflowerblue': (100, 149, 237),
473 'cornsilk': (255, 248, 220),
474 'crimson': (220, 20, 60),
475 'cyan': (0, 255, 255),
476 'darkblue': (0, 0, 139),
477 'darkcyan': (0, 139, 139),
478 'darkgoldenrod': (184, 134, 11),
479 'darkgray': (169, 169, 169),
480 'darkgreen': (0, 100, 0),
481 'darkgrey': (169, 169, 169),
482 'darkkhaki': (189, 183, 107),
483 'darkmagenta': (139, 0, 139),
484 'darkolivegreen': (85, 107, 47),
485 'darkorange': (255, 140, 0),
486 'darkorchid': (153, 50, 204),
487 'darkred': (139, 0, 0),
488 'darksalmon': (233, 150, 122),
489 'darkseagreen': (143, 188, 143),
490 'darkslateblue': (72, 61, 139),
491 'darkslategray': (47, 79, 79),
492 'darkslategrey': (47, 79, 79),
493 'darkturquoise': (0, 206, 209),
494 'darkviolet': (148, 0, 211),
495 'deeppink': (255, 20, 147),
496 'deepskyblue': (0, 191, 255),
497 'dimgray': (105, 105, 105),
498 'dimgrey': (105, 105, 105),
499 'dodgerblue': (30, 144, 255),
500 'firebrick': (178, 34, 34),
501 'floralwhite': (255, 250, 240),
502 'forestgreen': (34, 139, 34),
503 'fuchsia': (255, 0, 255),
504 'gainsboro': (220, 220, 220),
505 'ghostwhite': (248, 248, 255),
506 'gold': (255, 215, 0),
507 'goldenrod': (218, 165, 32),
508 'gray': (128, 128, 128),
509 'green': (0, 128, 0),
510 'greenyellow': (173, 255, 47),
511 'grey': (128, 128, 128),
512 'honeydew': (240, 255, 240),
513 'hotpink': (255, 105, 180),
514 'indianred': (205, 92, 92),
515 'indigo': (75, 0, 130),
516 'ivory': (255, 255, 240),
517 'khaki': (240, 230, 140),
518 'lavender': (230, 230, 250),
519 'lavenderblush': (255, 240, 245),
520 'lawngreen': (124, 252, 0),
521 'lemonchiffon': (255, 250, 205),
522 'lightblue': (173, 216, 230),
523 'lightcoral': (240, 128, 128),
524 'lightcyan': (224, 255, 255),
525 'lightgoldenrodyellow': (250, 250, 210),
526 'lightgray': (211, 211, 211),
527 'lightgreen': (144, 238, 144),
528 'lightgrey': (211, 211, 211),
529 'lightpink': (255, 182, 193),
530 'lightsalmon': (255, 160, 122),
531 'lightseagreen': (32, 178, 170),
532 'lightskyblue': (135, 206, 250),
533 'lightslategray': (119, 136, 153),
534 'lightslategrey': (119, 136, 153),
535 'lightsteelblue': (176, 196, 222),
536 'lightyellow': (255, 255, 224),
537 'lime': (0, 255, 0),
538 'limegreen': (50, 205, 50),
539 'linen': (250, 240, 230),
540 'magenta': (255, 0, 255),
541 'maroon': (128, 0, 0),
542 'mediumaquamarine': (102, 205, 170),
543 'mediumblue': (0, 0, 205),
544 'mediumorchid': (186, 85, 211),
545 'mediumpurple': (147, 112, 219),
546 'mediumseagreen': (60, 179, 113),
547 'mediumslateblue': (123, 104, 238),
548 'mediumspringgreen': (0, 250, 154),
549 'mediumturquoise': (72, 209, 204),
550 'mediumvioletred': (199, 21, 133),
551 'midnightblue': (25, 25, 112),
552 'mintcream': (245, 255, 250),
553 'mistyrose': (255, 228, 225),
554 'moccasin': (255, 228, 181),
555 'navajowhite': (255, 222, 173),
556 'navy': (0, 0, 128),
557 'oldlace': (253, 245, 230),
558 'olive': (128, 128, 0),
559 'olivedrab': (107, 142, 35),
560 'orange': (255, 165, 0),
561 'orangered': (255, 69, 0),
562 'orchid': (218, 112, 214),
563 'palegoldenrod': (238, 232, 170),
564 'palegreen': (152, 251, 152),
565 'paleturquoise': (175, 238, 238),
566 'palevioletred': (219, 112, 147),
567 'papayawhip': (255, 239, 213),
568 'peachpuff': (255, 218, 185),
569 'peru': (205, 133, 63),
570 'pink': (255, 192, 203),
571 'plum': (221, 160, 221),
572 'powderblue': (176, 224, 230),
573 'purple': (128, 0, 128),
574 'red': (255, 0, 0),
575 'rosybrown': (188, 143, 143),
576 'royalblue': (65, 105, 225),
577 'saddlebrown': (139, 69, 19),
578 'salmon': (250, 128, 114),
579 'sandybrown': (244, 164, 96),
580 'seagreen': (46, 139, 87),
581 'seashell': (255, 245, 238),
582 'sienna': (160, 82, 45),
583 'silver': (192, 192, 192),
584 'skyblue': (135, 206, 235),
585 'slateblue': (106, 90, 205),
586 'slategray': (112, 128, 144),
587 'slategrey': (112, 128, 144),
588 'snow': (255, 250, 250),
589 'springgreen': (0, 255, 127),
590 'steelblue': (70, 130, 180),
591 'tan': (210, 180, 140),
592 'teal': (0, 128, 128),
593 'thistle': (216, 191, 216),
594 'tomato': (255, 99, 71),
595 'turquoise': (64, 224, 208),
596 'violet': (238, 130, 238),
597 'wheat': (245, 222, 179),
598 'white': (255, 255, 255),
599 'whitesmoke': (245, 245, 245),
600 'yellow': (255, 255, 0),
601 'yellowgreen': (154, 205, 50),
602}
604COLORS_BY_VALUE = {v: k for k, v in COLORS_BY_NAME.items()} 1abcdefghijklmnopqrstuvwxyzABCDEF