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

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. 

3 

4A few colors have multiple names referring to the sames colors, eg. `grey` and `gray` or `aqua` and `cyan`. 

5 

6In these cases the _last_ color when sorted alphabetically takes preferences, 

7eg. `Color((0, 255, 255)).as_named() == 'cyan'` because "cyan" comes after "aqua". 

8 

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""" 

14 

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

19 

20from pydantic_core import CoreSchema, PydanticCustomError, core_schema 1abcdefghijklmnopqrstuvwxyzABCDEF

21from typing_extensions import deprecated 1abcdefghijklmnopqrstuvwxyzABCDEF

22 

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

27 

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

31 

32 

33class RGBA: 1abcdefghijklmnopqrstuvwxyzABCDEF

34 """Internal use only as a representation of a color.""" 

35 

36 __slots__ = 'r', 'g', 'b', 'alpha', '_tuple' 1abcdefghijklmnopqrstuvwxyzABCDEF

37 

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

43 

44 self._tuple: tuple[float, float, float, Optional[float]] = (r, g, b, alpha) 1abcdefghijklmnopqrstuvwxyzABCDEF

45 

46 def __getitem__(self, item: Any) -> Any: 1abcdefghijklmnopqrstuvwxyzABCDEF

47 return self._tuple[item] 1abcdefghijklmnopqrstuvwxyzABCDEF

48 

49 

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

66 

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

70 

71 

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.""" 

79 

80 __slots__ = '_original', '_rgba' 1abcdefghijklmnopqrstuvwxyzABCDEF

81 

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 ) 

96 

97 # if we've got here value must be a valid color 

98 self._original = value 1abcdefghijklmnopqrstuvwxyzABCDEF

99 

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

107 

108 def original(self) -> ColorType: 1abcdefghijklmnopqrstuvwxyzABCDEF

109 """Original value passed to `Color`.""" 

110 return self._original 1abcdefghijklmnopqrstuvwxyzABCDEF

111 

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`. 

115 

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. 

119 

120 Returns: 

121 The name of the color, or the hexadecimal representation of the color. 

122 

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

137 

138 def as_hex(self) -> str: 1abcdefghijklmnopqrstuvwxyzABCDEF

139 """Returns the hexadecimal representation of the color. 

140 

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. 

143 

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

150 

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

155 

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 ) 

165 

166 def as_rgb_tuple(self, *, alpha: Optional[bool] = None) -> ColorTuple: 1abcdefghijklmnopqrstuvwxyzABCDEF

167 """Returns the color as an RGB or RGBA tuple. 

168 

169 Args: 

170 alpha: Whether to include the alpha channel. There are three options for this input: 

171 

172 - `None` (default): Include alpha only if it's set. (e.g. not `None`) 

173 - `True`: Always include alpha. 

174 - `False`: Always omit alpha. 

175 

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

191 

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

200 

201 def as_hsl_tuple(self, *, alpha: Optional[bool] = None) -> HslColorTuple: 1abcdefghijklmnopqrstuvwxyzABCDEF

202 """Returns the color as an HSL or HSLA tuple. 

203 

204 Args: 

205 alpha: Whether to include the alpha channel. 

206 

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. 

210 

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. 

214 

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

229 

230 def _alpha_float(self) -> float: 1abcdefghijklmnopqrstuvwxyzABCDEF

231 return 1 if self._rgba.alpha is None else self._rgba.alpha 1abcdefghijklmnopqrstuvwxyzABCDEF

232 

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 ) 

240 

241 @classmethod 1abcdefghijklmnopqrstuvwxyzABCDEF

242 def _validate(cls, __input_value: Any, _: Any) -> 'Color': 1abcdefghijklmnopqrstuvwxyzABCDEF

243 return cls(__input_value) 1abcdefghijklmnopqrstuvwxyzABCDEF

244 

245 def __str__(self) -> str: 1abcdefghijklmnopqrstuvwxyzABCDEF

246 return self.as_named(fallback=True) 1abcdefghijklmnopqrstuvwxyzABCDEF

247 

248 def __repr_args__(self) -> '_repr.ReprArgs': 1abcdefghijklmnopqrstuvwxyzABCDEF

249 return [(None, self.as_named(fallback=True))] + [('rgb', self.as_rgb_tuple())] 1abcdefghijklmnopqrstuvwxyzABCDEF

250 

251 def __eq__(self, other: Any) -> bool: 1abcdefghijklmnopqrstuvwxyzABCDEF

252 return isinstance(other, Color) and self.as_rgb_tuple() == other.as_rgb_tuple() 1abcdefghijklmnopqrstuvwxyzABCDEF

253 

254 def __hash__(self) -> int: 1abcdefghijklmnopqrstuvwxyzABCDEF

255 return hash(self.as_rgb_tuple()) 1abcdefghijklmnopqrstuvwxyzABCDEF

256 

257 

258def parse_tuple(value: tuple[Any, ...]) -> RGBA: 1abcdefghijklmnopqrstuvwxyzABCDEF

259 """Parse a tuple or list to get RGBA values. 

260 

261 Args: 

262 value: A tuple or list. 

263 

264 Returns: 

265 An `RGBA` tuple parsed from the input tuple. 

266 

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

278 

279 

280def parse_str(value: str) -> RGBA: 1abcdefghijklmnopqrstuvwxyzABCDEF

281 """Parse a string representing a color to an RGBA tuple. 

282 

283 Possible formats for the input string include: 

284 

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>)` 

290 

291 Args: 

292 value: A string representing a color. 

293 

294 Returns: 

295 An `RGBA` tuple parsed from the input string. 

296 

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

307 

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

317 

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

327 

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

331 

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

335 

336 raise PydanticCustomError('color_error', 'value is not a valid color: string not recognised as a valid color') 1abcdefghijklmnopqrstuvwxyzABCDEF

337 

338 

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. 

341 

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. 

347 

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

352 

353 

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. 

356 

357 Args: 

358 value: An integer or string color value. 

359 max_val: Maximum range value. Defaults to 255. 

360 

361 Raises: 

362 PydanticCustomError: If the value is not a valid color. 

363 

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 ) 

379 

380 

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. 

383 

384 Args: 

385 value: The input value to parse. 

386 

387 Returns: 

388 The parsed value as a float, or `None` if the value was None or equal 1. 

389 

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

402 

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

409 

410 

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. 

413 

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. 

420 

421 Returns: 

422 An instance of `RGBA`. 

423 """ 

424 s_value, l_value = parse_color_value(sat, 100), parse_color_value(light, 100) 1abcdefghijklmnopqrstuvwxyzABCDEF

425 

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

434 

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

437 

438 

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). 

441 

442 Args: 

443 c: The float value to be converted. Must be between 0 and 1 (inclusive). 

444 

445 Returns: 

446 The integer equivalent of the given float value rounded to the nearest whole number. 

447 

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

452 

453 

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} 

603 

604COLORS_BY_VALUE = {v: k for k, v in COLORS_BY_NAME.items()} 1abcdefghijklmnopqrstuvwxyzABCDEF