1"""
2Functions which convert between various types of color values.
3
4"""
5
6# SPDX-License-Identifier: BSD-3-Clause
7
8from ._definitions import CSS3, _get_hex_to_name_map, _get_name_to_hex_map
9from ._normalization import (
10 _percent_to_integer,
11 normalize_hex,
12 normalize_integer_triplet,
13 normalize_percent_triplet,
14)
15from ._types import IntegerRGB, IntTuple, PercentRGB, PercentTuple
16
17# Conversions from color names to other formats.
18# --------------------------------------------------------------------------------
19
20
21def name_to_hex(name: str, spec: str = CSS3) -> str:
22 """
23 Convert a color name to a normalized hexadecimal color value.
24
25 The color name will be normalized to lower-case before being looked up.
26
27 Examples:
28
29 .. doctest::
30
31 >>> name_to_hex("white")
32 '#ffffff'
33 >>> name_to_hex("navy")
34 '#000080'
35 >>> name_to_hex("goldenrod")
36 '#daa520'
37 >>> name_to_hex("goldenrod", spec=HTML4)
38 Traceback (most recent call last):
39 ...
40 ValueError: "goldenrod" is not defined as a named color in html4.
41
42 :param name: The color name to convert.
43 :param spec: The specification from which to draw the list of color names. Default
44 is :data:`CSS3`.
45 :raises ValueError: when the given name has no definition in the given spec.
46
47 """
48 color_map = _get_name_to_hex_map(spec)
49 if hex_value := color_map.get(name.lower()):
50 return hex_value
51 raise ValueError(f'"{name}" is not defined as a named color in {spec}')
52
53
54def name_to_rgb(name: str, spec: str = CSS3) -> IntegerRGB:
55 """
56 Convert a color name to a 3-:class:`tuple` of :class:`int` suitable for use in
57 an ``rgb()`` triplet specifying that color.
58
59 The color name will be normalized to lower-case before being looked up.
60
61 Examples:
62
63 .. doctest::
64
65 >>> name_to_rgb("white")
66 IntegerRGB(red=255, green=255, blue=255)
67 >>> name_to_rgb("navy")
68 IntegerRGB(red=0, green=0, blue=128)
69 >>> name_to_rgb("goldenrod")
70 IntegerRGB(red=218, green=165, blue=32)
71
72 :param name: The color name to convert.
73 :param spec: The specification from which to draw the list of color names. Default
74 is :data:`CSS3.`
75 :raises ValueError: when the given name has no definition in the given spec.
76
77 """
78 return hex_to_rgb(name_to_hex(name, spec=spec))
79
80
81def name_to_rgb_percent(name: str, spec: str = CSS3) -> PercentRGB:
82 """
83 Convert a color name to a 3-:class:`tuple` of percentages suitable for use in an
84 ``rgb()`` triplet specifying that color.
85
86 The color name will be normalized to lower-case before being looked up.
87
88 Examples:
89
90 .. doctest::
91
92 >>> name_to_rgb_percent("white")
93 PercentRGB(red='100%', green='100%', blue='100%')
94 >>> name_to_rgb_percent("navy")
95 PercentRGB(red='0%', green='0%', blue='50%')
96 >>> name_to_rgb_percent("goldenrod")
97 PercentRGB(red='85.49%', green='64.71%', blue='12.5%')
98
99 :param name: The color name to convert.
100 :param spec: The specification from which to draw the list of color names. Default
101 is :data:`CSS3`.
102 :raises ValueError: when the given name has no definition in the given spec.
103
104 """
105 return rgb_to_rgb_percent(name_to_rgb(name, spec=spec))
106
107
108# Conversions from hexadecimal color values to other formats.
109# --------------------------------------------------------------------------------
110
111
112def hex_to_name(hex_value: str, spec: str = CSS3) -> str:
113 """
114 Convert a hexadecimal color value to its corresponding normalized color name, if
115 any such name exists.
116
117 The hexadecimal value will be normalized before being looked up.
118
119 .. note:: **Spelling variants**
120
121 Some values representing named gray colors can map to either of two names in
122 CSS3, because it supports both ``"gray"`` and ``"grey"`` spelling variants for
123 those colors. This function will always return the variant spelled ``"gray"``
124 (such as ``"lightgray"`` instead of ``"lightgrey"``). See :ref:`the documentation
125 on name conventions <color-name-conventions>` for details.
126
127 Examples:
128
129 .. doctest::
130
131 >>> hex_to_name("#ffffff")
132 'white'
133 >>> hex_to_name("#fff")
134 'white'
135 >>> hex_to_name("#000080")
136 'navy'
137 >>> hex_to_name("#daa520")
138 'goldenrod'
139 >>> hex_to_name("#daa520", spec=HTML4)
140 Traceback (most recent call last):
141 ...
142 ValueError: "#daa520" has no defined color name in html4.
143
144 :param hex_value: The hexadecimal color value to convert.
145 :param spec: The specification from which to draw the list of color names. Default
146 is :data:`CSS3`.
147 :raises ValueError: when the given color has no name in the given spec, or when the
148 supplied hex value is invalid.
149
150 """
151 color_map = _get_hex_to_name_map(spec)
152 if name := color_map.get(normalize_hex(hex_value)):
153 return name
154 raise ValueError(f'"{hex_value}" has no defined color name in {spec}.')
155
156
157def hex_to_rgb(hex_value: str) -> IntegerRGB:
158 """
159 Convert a hexadecimal color value to a 3-:class:`tuple` of :class:`int` suitable
160 for use in an ``rgb()`` triplet specifying that color.
161
162 The hexadecimal value will be normalized before being converted.
163
164 Examples:
165
166 .. doctest::
167
168 >>> hex_to_rgb("#fff")
169 IntegerRGB(red=255, green=255, blue=255)
170 >>> hex_to_rgb("#000080")
171 IntegerRGB(red=0, green=0, blue=128)
172
173 :param hex_value: The hexadecimal color value to convert.
174 :raises ValueError: when the supplied hex value is invalid.
175
176 """
177 int_value = int(normalize_hex(hex_value)[1:], 16)
178 return IntegerRGB(int_value >> 16, int_value >> 8 & 0xFF, int_value & 0xFF)
179
180
181def hex_to_rgb_percent(hex_value: str) -> PercentRGB:
182 """
183 Convert a hexadecimal color value to a 3-:class:`tuple` of percentages suitable
184 for use in an ``rgb()`` triplet representing that color.
185
186 The hexadecimal value will be normalized before being converted.
187
188 Examples:
189
190 .. doctest::
191
192 >>> hex_to_rgb_percent("#ffffff")
193 PercentRGB(red='100%', green='100%', blue='100%')
194 >>> hex_to_rgb_percent("#000080")
195 PercentRGB(red='0%', green='0%', blue='50%')
196
197 :param hex_value: The hexadecimal color value to convert.
198 :raises ValueError: when the supplied hex value is invalid.
199
200 """
201 return rgb_to_rgb_percent(hex_to_rgb(hex_value))
202
203
204# Conversions from integer rgb() triplets to other formats.
205# --------------------------------------------------------------------------------
206
207
208def rgb_to_name(rgb_triplet: IntTuple, spec: str = CSS3) -> str:
209 """
210 Convert a 3-:class:`tuple` of :class:`int`, suitable for use in an ``rgb()``
211 color triplet, to its corresponding normalized color name, if any such name exists.
212
213 To determine the name, the triplet will be converted to a normalized hexadecimal
214 value.
215
216 .. note:: **Spelling variants**
217
218 Some values representing named gray colors can map to either of two names in
219 CSS3, because it supports both ``"gray"`` and ``"grey"`` spelling variants for
220 those colors. This function will always return the variant spelled ``"gray"``
221 (such as ``"lightgray"`` instead of ``"lightgrey"``). See :ref:`the documentation
222 on name conventions <color-name-conventions>` for details.
223
224 Examples:
225
226 .. doctest::
227
228 >>> rgb_to_name((255, 255, 255))
229 'white'
230 >>> rgb_to_name((0, 0, 128))
231 'navy'
232
233 :param rgb_triplet: The ``rgb()`` triplet.
234 :param spec: The specification from which to draw the list of color names. Default
235 is :data:`CSS3`.
236 :raises ValueError: when the given color has no name in the given spec.
237
238 """
239 return hex_to_name(rgb_to_hex(normalize_integer_triplet(rgb_triplet)), spec=spec)
240
241
242def rgb_to_hex(rgb_triplet: IntTuple) -> str:
243 """
244 Convert a 3-:class:`tuple` of :class:`int`, suitable for use in an ``rgb()``
245 color triplet, to a normalized hexadecimal value for that color.
246
247 Examples:
248
249 .. doctest::
250
251 >>> rgb_to_hex((255, 255, 255))
252 '#ffffff'
253 >>> rgb_to_hex((0, 0, 128))
254 '#000080'
255
256 :param rgb_triplet: The ``rgb()`` triplet.
257
258 """
259 red, green, blue = normalize_integer_triplet(rgb_triplet)
260 return f"#{red:02x}{green:02x}{blue:02x}"
261
262
263def rgb_to_rgb_percent(rgb_triplet: IntTuple) -> PercentRGB:
264 """
265 Convert a 3-:class:`tuple` of :class:`int`, suitable for use in an ``rgb()``
266 color triplet, to a 3-:class:`tuple` of percentages suitable for use in representing
267 that color.
268
269 .. note:: **Floating-point precision**
270
271 This function makes some trade-offs in terms of the accuracy of the final
272 representation. For some common integer values, special-case logic is used to
273 ensure a precise result (e.g., integer 128 will always convert to ``"50%"``,
274 integer 32 will always convert to ``"12.5%"``), but for all other values a
275 standard Python :class:`float` is used and rounded to two decimal places, which
276 may result in a loss of precision for some values due to the inherent imprecision
277 of `IEEE floating-point numbers <https://en.wikipedia.org/wiki/IEEE_754>`_.
278
279 Examples:
280
281 .. doctest::
282
283 >>> rgb_to_rgb_percent((255, 255, 255))
284 PercentRGB(red='100%', green='100%', blue='100%')
285 >>> rgb_to_rgb_percent((0, 0, 128))
286 PercentRGB(red='0%', green='0%', blue='50%')
287 >>> rgb_to_rgb_percent((218, 165, 32))
288 PercentRGB(red='85.49%', green='64.71%', blue='12.5%')
289
290 :param rgb_triplet: The ``rgb()`` triplet.
291
292 """
293 # In order to maintain precision for common values,
294 # special-case them.
295 specials = {
296 255: "100%",
297 128: "50%",
298 64: "25%",
299 32: "12.5%",
300 16: "6.25%",
301 0: "0%",
302 }
303 return PercentRGB._make(
304 specials.get(d, f"{d / 255.0 * 100:.02f}%")
305 for d in normalize_integer_triplet(rgb_triplet)
306 )
307
308
309# Conversions from percentage rgb() triplets to other formats.
310# --------------------------------------------------------------------------------
311
312
313def rgb_percent_to_name(rgb_percent_triplet: PercentTuple, spec: str = CSS3) -> str:
314 """
315 Convert a 3-:class:`tuple` of percentages, suitable for use in an ``rgb()``
316 color triplet, to its corresponding normalized color name, if any such name exists.
317
318 To determine the name, the triplet will be converted to a normalized hexadecimal
319 value.
320
321 .. note:: **Spelling variants**
322
323 Some values representing named gray colors can map to either of two names in
324 CSS3, because it supports both ``"gray"`` and ``"grey"`` spelling variants for
325 those colors. This function will always return the variant spelled ``"gray"``
326 (such as ``"lightgray"`` instead of ``"lightgrey"``). See :ref:`the documentation
327 on name conventions <color-name-conventions>` for details.
328
329 Examples:
330
331 .. doctest::
332
333 >>> rgb_percent_to_name(("100%", "100%", "100%"))
334 'white'
335 >>> rgb_percent_to_name(("0%", "0%", "50%"))
336 'navy'
337 >>> rgb_percent_to_name(("85.49%", "64.71%", "12.5%"))
338 'goldenrod'
339
340 :param rgb_percent_triplet: The ``rgb()`` triplet.
341 :param spec: The specification from which to draw the list of color names. Default
342 is :data:`CSS3`.
343 :raises ValueError: when the given color has no name in the given spec.
344
345 """
346 return rgb_to_name(
347 rgb_percent_to_rgb(normalize_percent_triplet(rgb_percent_triplet)),
348 spec=spec,
349 )
350
351
352def rgb_percent_to_hex(rgb_percent_triplet: PercentTuple) -> str:
353 """
354 Convert a 3-:class:`tuple` of percentages, suitable for use in an ``rgb()``
355 color triplet, to a normalized hexadecimal color value for that color.
356
357 Examples:
358
359 .. doctest::
360
361 >>> rgb_percent_to_hex(("100%", "100%", "0%"))
362 '#ffff00'
363 >>> rgb_percent_to_hex(("0%", "0%", "50%"))
364 '#000080'
365 >>> rgb_percent_to_hex(("85.49%", "64.71%", "12.5%"))
366 '#daa520'
367
368 :param rgb_percent_triplet: The ``rgb()`` triplet.
369
370 """
371 return rgb_to_hex(
372 rgb_percent_to_rgb(normalize_percent_triplet(rgb_percent_triplet))
373 )
374
375
376def rgb_percent_to_rgb(rgb_percent_triplet: PercentTuple) -> IntegerRGB:
377 """
378 Convert a 3-:class:`tuple` of percentages, suitable for use in an ``rgb()``
379 color triplet, to a 3-:class:`tuple` of :class:`int` suitable for use in
380 representing that color.
381
382 Some precision may be lost in this conversion. See the note regarding precision for
383 :func:`~webcolors.rgb_to_rgb_percent` for details.
384
385 Examples:
386
387 .. doctest::
388
389 >>> rgb_percent_to_rgb(("100%", "100%", "100%"))
390 IntegerRGB(red=255, green=255, blue=255)
391 >>> rgb_percent_to_rgb(("0%", "0%", "50%"))
392 IntegerRGB(red=0, green=0, blue=128)
393 >>> rgb_percent_to_rgb(("85.49%", "64.71%", "12.5%"))
394 IntegerRGB(red=218, green=165, blue=32)
395
396 :param rgb_percent_triplet: The ``rgb()`` triplet.
397
398 """
399 return IntegerRGB._make(
400 map(
401 _percent_to_integer, # pylint: disable=protected-access
402 normalize_percent_triplet(rgb_percent_triplet),
403 )
404 )