Coverage for pydantic/color.py: 100.00%

194 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-15 13:26 +0000

1""" 

2Color definitions are used as per CSS3 specification: 

3http://www.w3.org/TR/css3-color/#svg-color 

4 

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

6 

7In these cases the LAST color when sorted alphabetically takes preferences, 

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

9""" 

10import math 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

11import re 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

12from colorsys import hls_to_rgb, rgb_to_hls 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

13from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union, cast 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

14 

15from pydantic.errors import ColorError 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

16from pydantic.utils import Representation, almost_equal_floats 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

17 

18if TYPE_CHECKING: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

19 from pydantic.typing import CallableGenerator, ReprArgs 

20 

21ColorTuple = Union[Tuple[int, int, int], Tuple[int, int, int, float]] 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

22ColorType = Union[ColorTuple, str] 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

23HslColorTuple = Union[Tuple[float, float, float], Tuple[float, float, float, float]] 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

24 

25 

26class RGBA: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

27 """ 

28 Internal use only as a representation of a color. 

29 """ 

30 

31 __slots__ = 'r', 'g', 'b', 'alpha', '_tuple' 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

32 

33 def __init__(self, r: float, g: float, b: float, alpha: Optional[float]): 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

34 self.r = r 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

35 self.g = g 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

36 self.b = b 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

37 self.alpha = alpha 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

38 

39 self._tuple: Tuple[float, float, float, Optional[float]] = (r, g, b, alpha) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

40 

41 def __getitem__(self, item: Any) -> Any: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

42 return self._tuple[item] 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

43 

44 

45# these are not compiled here to avoid import slowdown, they'll be compiled the first time they're used, then cached 

46r_hex_short = r'\s*(?:#|0x)?([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?\s*' 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

47r_hex_long = r'\s*(?:#|0x)?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?\s*' 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

48_r_255 = r'(\d{1,3}(?:\.\d+)?)' 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

49_r_comma = r'\s*,\s*' 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

50r_rgb = fr'\s*rgb\(\s*{_r_255}{_r_comma}{_r_255}{_r_comma}{_r_255}\)\s*' 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

51_r_alpha = r'(\d(?:\.\d+)?|\.\d+|\d{1,2}%)' 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

52r_rgba = fr'\s*rgba\(\s*{_r_255}{_r_comma}{_r_255}{_r_comma}{_r_255}{_r_comma}{_r_alpha}\s*\)\s*' 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

53_r_h = r'(-?\d+(?:\.\d+)?|-?\.\d+)(deg|rad|turn)?' 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

54_r_sl = r'(\d{1,3}(?:\.\d+)?)%' 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

55r_hsl = fr'\s*hsl\(\s*{_r_h}{_r_comma}{_r_sl}{_r_comma}{_r_sl}\s*\)\s*' 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

56r_hsla = fr'\s*hsl\(\s*{_r_h}{_r_comma}{_r_sl}{_r_comma}{_r_sl}{_r_comma}{_r_alpha}\s*\)\s*' 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

57 

58# colors where the two hex characters are the same, if all colors match this the short version of hex colors can be used 

59repeat_colors = {int(c * 2, 16) for c in '0123456789abcdef'} 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

60rads = 2 * math.pi 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

61 

62 

63class Color(Representation): 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

64 __slots__ = '_original', '_rgba' 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

65 

66 def __init__(self, value: ColorType) -> None: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

67 self._rgba: RGBA 1EFabcdefghijGHklmnopqrstIJuvwxyzABCD

68 self._original: ColorType 1EFabcdefghijGHklmnopqrstIJuvwxyzABCD

69 if isinstance(value, (tuple, list)): 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

70 self._rgba = parse_tuple(value) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

71 elif isinstance(value, str): 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

72 self._rgba = parse_str(value) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

73 elif isinstance(value, Color): 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

74 self._rgba = value._rgba 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

75 value = value._original 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

76 else: 

77 raise ColorError(reason='value must be a tuple, list or string') 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

78 

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

80 self._original = value 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

81 

82 @classmethod 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

83 def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

84 field_schema.update(type='string', format='color') 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

85 

86 def original(self) -> ColorType: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

87 """ 

88 Original value passed to Color 

89 """ 

90 return self._original 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

91 

92 def as_named(self, *, fallback: bool = False) -> str: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

93 if self._rgba.alpha is None: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

94 rgb = cast(Tuple[int, int, int], self.as_rgb_tuple()) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

95 try: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

96 return COLORS_BY_VALUE[rgb] 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

97 except KeyError as e: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

98 if fallback: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

99 return self.as_hex() 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

100 else: 

101 raise ValueError('no named color found, use fallback=True, as_hex() or as_rgb()') from e 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

102 else: 

103 return self.as_hex() 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

104 

105 def as_hex(self) -> str: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

106 """ 

107 Hex string representing the color can be 3, 4, 6 or 8 characters depending on whether the string 

108 a "short" representation of the color is possible and whether there's an alpha channel. 

109 """ 

110 values = [float_to_255(c) for c in self._rgba[:3]] 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

111 if self._rgba.alpha is not None: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

112 values.append(float_to_255(self._rgba.alpha)) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

113 

114 as_hex = ''.join(f'{v:02x}' for v in values) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

115 if all(c in repeat_colors for c in values): 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

116 as_hex = ''.join(as_hex[c] for c in range(0, len(as_hex), 2)) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

117 return '#' + as_hex 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

118 

119 def as_rgb(self) -> str: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

120 """ 

121 Color as an rgb(<r>, <g>, <b>) or rgba(<r>, <g>, <b>, <a>) string. 

122 """ 

123 if self._rgba.alpha is None: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

124 return f'rgb({float_to_255(self._rgba.r)}, {float_to_255(self._rgba.g)}, {float_to_255(self._rgba.b)})' 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

125 else: 

126 return ( 1abcdefghijklmnopqrstKLMNOuvwxyzABCD

127 f'rgba({float_to_255(self._rgba.r)}, {float_to_255(self._rgba.g)}, {float_to_255(self._rgba.b)}, ' 

128 f'{round(self._alpha_float(), 2)})' 

129 ) 

130 

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

132 """ 

133 Color as an RGB or RGBA tuple; red, green and blue are in the range 0 to 255, alpha if included is 

134 in the range 0 to 1. 

135 

136 :param alpha: whether to include the alpha channel, options are 

137 None - (default) include alpha only if it's set (e.g. not None) 

138 True - always include alpha, 

139 False - always omit alpha, 

140 """ 

141 r, g, b = (float_to_255(c) for c in self._rgba[:3]) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

142 if alpha is None: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

143 if self._rgba.alpha is None: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

144 return r, g, b 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

145 else: 

146 return r, g, b, self._alpha_float() 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

147 elif alpha: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

148 return r, g, b, self._alpha_float() 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

149 else: 

150 # alpha is False 

151 return r, g, b 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

152 

153 def as_hsl(self) -> str: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

154 """ 

155 Color as an hsl(<h>, <s>, <l>) or hsl(<h>, <s>, <l>, <a>) string. 

156 """ 

157 if self._rgba.alpha is None: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

158 h, s, li = self.as_hsl_tuple(alpha=False) # type: ignore 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

159 return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%})' 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

160 else: 

161 h, s, li, a = self.as_hsl_tuple(alpha=True) # type: ignore 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

162 return f'hsl({h * 360:0.0f}, {s:0.0%}, {li:0.0%}, {round(a, 2)})' 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

163 

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

165 """ 

166 Color as an HSL or HSLA tuple, e.g. hue, saturation, lightness and optionally alpha; all elements are in 

167 the range 0 to 1. 

168 

169 NOTE: this is HSL as used in HTML and most other places, not HLS as used in python's colorsys. 

170 

171 :param alpha: whether to include the alpha channel, options are 

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 h, l, s = rgb_to_hls(self._rgba.r, self._rgba.g, self._rgba.b) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

177 if alpha is None: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

178 if self._rgba.alpha is None: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

179 return h, s, l 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

180 else: 

181 return h, s, l, self._alpha_float() 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

182 if alpha: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

183 return h, s, l, self._alpha_float() 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

184 else: 

185 # alpha is False 

186 return h, s, l 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

187 

188 def _alpha_float(self) -> float: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

189 return 1 if self._rgba.alpha is None else self._rgba.alpha 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

190 

191 @classmethod 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

192 def __get_validators__(cls) -> 'CallableGenerator': 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

193 yield cls 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

194 

195 def __str__(self) -> str: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

196 return self.as_named(fallback=True) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

197 

198 def __repr_args__(self) -> 'ReprArgs': 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

199 return [(None, self.as_named(fallback=True))] + [('rgb', self.as_rgb_tuple())] # type: ignore 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

200 

201 def __eq__(self, other: Any) -> bool: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

202 return isinstance(other, Color) and self.as_rgb_tuple() == other.as_rgb_tuple() 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

203 

204 def __hash__(self) -> int: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

205 return hash(self.as_rgb_tuple()) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

206 

207 

208def parse_tuple(value: Tuple[Any, ...]) -> RGBA: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

209 """ 

210 Parse a tuple or list as a color. 

211 """ 

212 if len(value) == 3: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

213 r, g, b = (parse_color_value(v) for v in value) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

214 return RGBA(r, g, b, None) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

215 elif len(value) == 4: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

216 r, g, b = (parse_color_value(v) for v in value[:3]) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

217 return RGBA(r, g, b, parse_float_alpha(value[3])) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

218 else: 

219 raise ColorError(reason='tuples must have length 3 or 4') 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

220 

221 

222def parse_str(value: str) -> RGBA: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

223 """ 

224 Parse a string to an RGBA tuple, trying the following formats (in this order): 

225 * named color, see COLORS_BY_NAME below 

226 * hex short eg. `<prefix>fff` (prefix can be `#`, `0x` or nothing) 

227 * hex long eg. `<prefix>ffffff` (prefix can be `#`, `0x` or nothing) 

228 * `rgb(<r>, <g>, <b>) ` 

229 * `rgba(<r>, <g>, <b>, <a>)` 

230 """ 

231 value_lower = value.lower() 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

232 try: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

233 r, g, b = COLORS_BY_NAME[value_lower] 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

234 except KeyError: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

235 pass 1EFabcdefghijGHklmnopqrstIJuvwxyzABCD

236 else: 

237 return ints_to_rgba(r, g, b, None) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

238 

239 m = re.fullmatch(r_hex_short, value_lower) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

240 if m: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

241 *rgb, a = m.groups() 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

242 r, g, b = (int(v * 2, 16) for v in rgb) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

243 if a: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

244 alpha: Optional[float] = int(a * 2, 16) / 255 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

245 else: 

246 alpha = None 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

247 return ints_to_rgba(r, g, b, alpha) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

248 

249 m = re.fullmatch(r_hex_long, value_lower) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

250 if m: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

251 *rgb, a = m.groups() 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

252 r, g, b = (int(v, 16) for v in rgb) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

253 if a: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

254 alpha = int(a, 16) / 255 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

255 else: 

256 alpha = None 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

257 return ints_to_rgba(r, g, b, alpha) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

258 

259 m = re.fullmatch(r_rgb, value_lower) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

260 if m: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

261 return ints_to_rgba(*m.groups(), None) # type: ignore 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

262 

263 m = re.fullmatch(r_rgba, value_lower) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

264 if m: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

265 return ints_to_rgba(*m.groups()) # type: ignore 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

266 

267 m = re.fullmatch(r_hsl, value_lower) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

268 if m: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

269 h, h_units, s, l_ = m.groups() 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

270 return parse_hsl(h, h_units, s, l_) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

271 

272 m = re.fullmatch(r_hsla, value_lower) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

273 if m: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

274 h, h_units, s, l_, a = m.groups() 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

275 return parse_hsl(h, h_units, s, l_, parse_float_alpha(a)) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

276 

277 raise ColorError(reason='string not recognised as a valid color') 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

278 

279 

280def ints_to_rgba(r: Union[int, str], g: Union[int, str], b: Union[int, str], alpha: Optional[float]) -> RGBA: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

281 return RGBA(parse_color_value(r), parse_color_value(g), parse_color_value(b), parse_float_alpha(alpha)) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

282 

283 

284def parse_color_value(value: Union[int, str], max_val: int = 255) -> float: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

285 """ 

286 Parse a value checking it's a valid int in the range 0 to max_val and divide by max_val to give a number 

287 in the range 0 to 1 

288 """ 

289 try: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

290 color = float(value) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

291 except ValueError: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

292 raise ColorError(reason='color values must be a valid number') 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

293 if 0 <= color <= max_val: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

294 return color / max_val 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

295 else: 

296 raise ColorError(reason=f'color values must be in the range 0 to {max_val}') 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

297 

298 

299def parse_float_alpha(value: Union[None, str, float, int]) -> Optional[float]: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

300 """ 

301 Parse a value checking it's a valid float in the range 0 to 1 

302 """ 

303 if value is None: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

304 return None 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

305 try: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

306 if isinstance(value, str) and value.endswith('%'): 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

307 alpha = float(value[:-1]) / 100 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

308 else: 

309 alpha = float(value) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

310 except ValueError: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

311 raise ColorError(reason='alpha values must be a valid float') 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

312 

313 if almost_equal_floats(alpha, 1): 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

314 return None 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

315 elif 0 <= alpha <= 1: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

316 return alpha 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

317 else: 

318 raise ColorError(reason='alpha values must be in the range 0 to 1') 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

319 

320 

321def parse_hsl(h: str, h_units: str, sat: str, light: str, alpha: Optional[float] = None) -> RGBA: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

322 """ 

323 Parse raw hue, saturation, lightness and alpha values and convert to RGBA. 

324 """ 

325 s_value, l_value = parse_color_value(sat, 100), parse_color_value(light, 100) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

326 

327 h_value = float(h) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

328 if h_units in {None, 'deg'}: 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

329 h_value = h_value % 360 / 360 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

330 elif h_units == 'rad': 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

331 h_value = h_value % rads / rads 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

332 else: 

333 # turns 

334 h_value = h_value % 1 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

335 

336 r, g, b = hls_to_rgb(h_value, l_value, s_value) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

337 return RGBA(r, g, b, alpha) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

338 

339 

340def float_to_255(c: float) -> int: 1EFabcdefghijGHklmnopqrstKLMNPQRSTUOIJuvwxyzABCD

341 return int(round(c * 255)) 1EFabcdefghijGHklmnopqrstKLMNOIJuvwxyzABCD

342 

343 

344COLORS_BY_NAME = { 1abcdefghijklmnopqrstPQRSTUuvwxyzABCD

345 'aliceblue': (240, 248, 255), 

346 'antiquewhite': (250, 235, 215), 

347 'aqua': (0, 255, 255), 

348 'aquamarine': (127, 255, 212), 

349 'azure': (240, 255, 255), 

350 'beige': (245, 245, 220), 

351 'bisque': (255, 228, 196), 

352 'black': (0, 0, 0), 

353 'blanchedalmond': (255, 235, 205), 

354 'blue': (0, 0, 255), 

355 'blueviolet': (138, 43, 226), 

356 'brown': (165, 42, 42), 

357 'burlywood': (222, 184, 135), 

358 'cadetblue': (95, 158, 160), 

359 'chartreuse': (127, 255, 0), 

360 'chocolate': (210, 105, 30), 

361 'coral': (255, 127, 80), 

362 'cornflowerblue': (100, 149, 237), 

363 'cornsilk': (255, 248, 220), 

364 'crimson': (220, 20, 60), 

365 'cyan': (0, 255, 255), 

366 'darkblue': (0, 0, 139), 

367 'darkcyan': (0, 139, 139), 

368 'darkgoldenrod': (184, 134, 11), 

369 'darkgray': (169, 169, 169), 

370 'darkgreen': (0, 100, 0), 

371 'darkgrey': (169, 169, 169), 

372 'darkkhaki': (189, 183, 107), 

373 'darkmagenta': (139, 0, 139), 

374 'darkolivegreen': (85, 107, 47), 

375 'darkorange': (255, 140, 0), 

376 'darkorchid': (153, 50, 204), 

377 'darkred': (139, 0, 0), 

378 'darksalmon': (233, 150, 122), 

379 'darkseagreen': (143, 188, 143), 

380 'darkslateblue': (72, 61, 139), 

381 'darkslategray': (47, 79, 79), 

382 'darkslategrey': (47, 79, 79), 

383 'darkturquoise': (0, 206, 209), 

384 'darkviolet': (148, 0, 211), 

385 'deeppink': (255, 20, 147), 

386 'deepskyblue': (0, 191, 255), 

387 'dimgray': (105, 105, 105), 

388 'dimgrey': (105, 105, 105), 

389 'dodgerblue': (30, 144, 255), 

390 'firebrick': (178, 34, 34), 

391 'floralwhite': (255, 250, 240), 

392 'forestgreen': (34, 139, 34), 

393 'fuchsia': (255, 0, 255), 

394 'gainsboro': (220, 220, 220), 

395 'ghostwhite': (248, 248, 255), 

396 'gold': (255, 215, 0), 

397 'goldenrod': (218, 165, 32), 

398 'gray': (128, 128, 128), 

399 'green': (0, 128, 0), 

400 'greenyellow': (173, 255, 47), 

401 'grey': (128, 128, 128), 

402 'honeydew': (240, 255, 240), 

403 'hotpink': (255, 105, 180), 

404 'indianred': (205, 92, 92), 

405 'indigo': (75, 0, 130), 

406 'ivory': (255, 255, 240), 

407 'khaki': (240, 230, 140), 

408 'lavender': (230, 230, 250), 

409 'lavenderblush': (255, 240, 245), 

410 'lawngreen': (124, 252, 0), 

411 'lemonchiffon': (255, 250, 205), 

412 'lightblue': (173, 216, 230), 

413 'lightcoral': (240, 128, 128), 

414 'lightcyan': (224, 255, 255), 

415 'lightgoldenrodyellow': (250, 250, 210), 

416 'lightgray': (211, 211, 211), 

417 'lightgreen': (144, 238, 144), 

418 'lightgrey': (211, 211, 211), 

419 'lightpink': (255, 182, 193), 

420 'lightsalmon': (255, 160, 122), 

421 'lightseagreen': (32, 178, 170), 

422 'lightskyblue': (135, 206, 250), 

423 'lightslategray': (119, 136, 153), 

424 'lightslategrey': (119, 136, 153), 

425 'lightsteelblue': (176, 196, 222), 

426 'lightyellow': (255, 255, 224), 

427 'lime': (0, 255, 0), 

428 'limegreen': (50, 205, 50), 

429 'linen': (250, 240, 230), 

430 'magenta': (255, 0, 255), 

431 'maroon': (128, 0, 0), 

432 'mediumaquamarine': (102, 205, 170), 

433 'mediumblue': (0, 0, 205), 

434 'mediumorchid': (186, 85, 211), 

435 'mediumpurple': (147, 112, 219), 

436 'mediumseagreen': (60, 179, 113), 

437 'mediumslateblue': (123, 104, 238), 

438 'mediumspringgreen': (0, 250, 154), 

439 'mediumturquoise': (72, 209, 204), 

440 'mediumvioletred': (199, 21, 133), 

441 'midnightblue': (25, 25, 112), 

442 'mintcream': (245, 255, 250), 

443 'mistyrose': (255, 228, 225), 

444 'moccasin': (255, 228, 181), 

445 'navajowhite': (255, 222, 173), 

446 'navy': (0, 0, 128), 

447 'oldlace': (253, 245, 230), 

448 'olive': (128, 128, 0), 

449 'olivedrab': (107, 142, 35), 

450 'orange': (255, 165, 0), 

451 'orangered': (255, 69, 0), 

452 'orchid': (218, 112, 214), 

453 'palegoldenrod': (238, 232, 170), 

454 'palegreen': (152, 251, 152), 

455 'paleturquoise': (175, 238, 238), 

456 'palevioletred': (219, 112, 147), 

457 'papayawhip': (255, 239, 213), 

458 'peachpuff': (255, 218, 185), 

459 'peru': (205, 133, 63), 

460 'pink': (255, 192, 203), 

461 'plum': (221, 160, 221), 

462 'powderblue': (176, 224, 230), 

463 'purple': (128, 0, 128), 

464 'red': (255, 0, 0), 

465 'rosybrown': (188, 143, 143), 

466 'royalblue': (65, 105, 225), 

467 'saddlebrown': (139, 69, 19), 

468 'salmon': (250, 128, 114), 

469 'sandybrown': (244, 164, 96), 

470 'seagreen': (46, 139, 87), 

471 'seashell': (255, 245, 238), 

472 'sienna': (160, 82, 45), 

473 'silver': (192, 192, 192), 

474 'skyblue': (135, 206, 235), 

475 'slateblue': (106, 90, 205), 

476 'slategray': (112, 128, 144), 

477 'slategrey': (112, 128, 144), 

478 'snow': (255, 250, 250), 

479 'springgreen': (0, 255, 127), 

480 'steelblue': (70, 130, 180), 

481 'tan': (210, 180, 140), 

482 'teal': (0, 128, 128), 

483 'thistle': (216, 191, 216), 

484 'tomato': (255, 99, 71), 

485 'turquoise': (64, 224, 208), 

486 'violet': (238, 130, 238), 

487 'wheat': (245, 222, 179), 

488 'white': (255, 255, 255), 

489 'whitesmoke': (245, 245, 245), 

490 'yellow': (255, 255, 0), 

491 'yellowgreen': (154, 205, 50), 

492} 

493 

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