1 """Color util methods."""
3 from __future__
import annotations
7 from typing
import NamedTuple
11 from .scaling
import scale_to_ranged_value
29 "aliceblue":
RGBColor(240, 248, 255),
30 "antiquewhite":
RGBColor(250, 235, 215),
32 "aquamarine":
RGBColor(127, 255, 212),
37 "blanchedalmond":
RGBColor(255, 235, 205),
39 "blueviolet":
RGBColor(138, 43, 226),
41 "burlywood":
RGBColor(222, 184, 135),
46 "cornflowerblue":
RGBColor(100, 149, 237),
52 "darkgoldenrod":
RGBColor(184, 134, 11),
56 "darkkhaki":
RGBColor(189, 183, 107),
57 "darkmagenta":
RGBColor(139, 0, 139),
58 "darkolivegreen":
RGBColor(85, 107, 47),
60 "darkorchid":
RGBColor(153, 50, 204),
62 "darksalmon":
RGBColor(233, 150, 122),
63 "darkseagreen":
RGBColor(143, 188, 143),
64 "darkslateblue":
RGBColor(72, 61, 139),
65 "darkslategray":
RGBColor(47, 79, 79),
66 "darkslategrey":
RGBColor(47, 79, 79),
67 "darkturquoise":
RGBColor(0, 206, 209),
70 "deepskyblue":
RGBColor(0, 191, 255),
73 "dodgerblue":
RGBColor(30, 144, 255),
75 "floralwhite":
RGBColor(255, 250, 240),
76 "forestgreen":
RGBColor(34, 139, 34),
78 "gainsboro":
RGBColor(220, 220, 220),
79 "ghostwhite":
RGBColor(248, 248, 255),
84 "greenyellow":
RGBColor(173, 255, 47),
93 "lavenderblush":
RGBColor(255, 240, 245),
95 "lemonchiffon":
RGBColor(255, 250, 205),
96 "lightblue":
RGBColor(173, 216, 230),
97 "lightcoral":
RGBColor(240, 128, 128),
98 "lightcyan":
RGBColor(224, 255, 255),
99 "lightgoldenrodyellow":
RGBColor(250, 250, 210),
100 "lightgray":
RGBColor(211, 211, 211),
101 "lightgreen":
RGBColor(144, 238, 144),
102 "lightgrey":
RGBColor(211, 211, 211),
103 "lightpink":
RGBColor(255, 182, 193),
104 "lightsalmon":
RGBColor(255, 160, 122),
105 "lightseagreen":
RGBColor(32, 178, 170),
106 "lightskyblue":
RGBColor(135, 206, 250),
107 "lightslategray":
RGBColor(119, 136, 153),
108 "lightslategrey":
RGBColor(119, 136, 153),
109 "lightsteelblue":
RGBColor(176, 196, 222),
110 "lightyellow":
RGBColor(255, 255, 224),
116 "mediumaquamarine":
RGBColor(102, 205, 170),
118 "mediumorchid":
RGBColor(186, 85, 211),
119 "mediumpurple":
RGBColor(147, 112, 219),
120 "mediumseagreen":
RGBColor(60, 179, 113),
121 "mediumslateblue":
RGBColor(123, 104, 238),
122 "mediumspringgreen":
RGBColor(0, 250, 154),
123 "mediumturquoise":
RGBColor(72, 209, 204),
124 "mediumvioletred":
RGBColor(199, 21, 133),
125 "midnightblue":
RGBColor(25, 25, 112),
126 "mintcream":
RGBColor(245, 255, 250),
127 "mistyrose":
RGBColor(255, 228, 225),
128 "moccasin":
RGBColor(255, 228, 181),
129 "navajowhite":
RGBColor(255, 222, 173),
134 "olivedrab":
RGBColor(107, 142, 35),
138 "palegoldenrod":
RGBColor(238, 232, 170),
139 "palegreen":
RGBColor(152, 251, 152),
140 "paleturquoise":
RGBColor(175, 238, 238),
141 "palevioletred":
RGBColor(219, 112, 147),
142 "papayawhip":
RGBColor(255, 239, 213),
143 "peachpuff":
RGBColor(255, 218, 185),
147 "powderblue":
RGBColor(176, 224, 230),
150 "rosybrown":
RGBColor(188, 143, 143),
151 "royalblue":
RGBColor(65, 105, 225),
152 "saddlebrown":
RGBColor(139, 69, 19),
154 "sandybrown":
RGBColor(244, 164, 96),
156 "seashell":
RGBColor(255, 245, 238),
160 "slateblue":
RGBColor(106, 90, 205),
161 "slategray":
RGBColor(112, 128, 144),
162 "slategrey":
RGBColor(112, 128, 144),
164 "springgreen":
RGBColor(0, 255, 127),
165 "steelblue":
RGBColor(70, 130, 180),
170 "turquoise":
RGBColor(64, 224, 208),
174 "whitesmoke":
RGBColor(245, 245, 245),
176 "yellowgreen":
RGBColor(154, 205, 50),
178 "homeassistant":
RGBColor(24, 188, 242),
184 """Represents a CIE 1931 XY coordinate pair."""
192 """Represents the Gamut of a light."""
195 red: XYPoint = attr.ib()
196 green: XYPoint = attr.ib()
197 blue: XYPoint = attr.ib()
201 """Convert color name to RGB hex value."""
204 hex_value = COLORS.get(color_name.replace(
" ",
"").lower())
206 raise ValueError(
"Unknown color")
212 iR: int, iG: int, iB: int, Gamut: GamutType |
None =
None
213 ) -> tuple[float, float]:
214 """Convert from RGB color to XY color."""
222 iR: int, iG: int, iB: int, Gamut: GamutType |
None =
None
223 ) -> tuple[float, float, int]:
224 """Convert from RGB color to XY color."""
225 if iR + iG + iB == 0:
233 R = pow((R + 0.055) / (1.0 + 0.055), 2.4)
if (R > 0.04045)
else (R / 12.92)
234 G = pow((G + 0.055) / (1.0 + 0.055), 2.4)
if (G > 0.04045)
else (G / 12.92)
235 B = pow((B + 0.055) / (1.0 + 0.055), 2.4)
if (B > 0.04045)
else (B / 12.92)
238 X = R * 0.664511 + G * 0.154324 + B * 0.162028
239 Y = R * 0.283881 + G * 0.668433 + B * 0.047685
240 Z = R * 0.000088 + G * 0.072310 + B * 0.986039
248 brightness = round(Y * 255)
258 return round(x, 3), round(y, 3), brightness
262 vX: float, vY: float, Gamut: GamutType |
None =
None
263 ) -> tuple[int, int, int]:
264 """Convert from XY to a normalized RGB."""
271 vX: float, vY: float, ibrightness: int, Gamut: GamutType |
None =
None
272 ) -> tuple[int, int, int]:
273 """Convert from XYZ to RGB."""
279 brightness = ibrightness / 255.0
280 if brightness == 0.0:
289 Z = (Y / vY) * (1 - vX - vY)
292 r = X * 1.656492 - Y * 0.354851 - Z * 0.255038
293 g = -X * 0.707196 + Y * 1.655397 + Z * 0.036152
294 b = X * 0.051713 - Y * 0.121364 + Z * 1.011530
298 12.92 * x
if (x <= 0.0031308)
else ((1.0 + 0.055) * pow(x, (1.0 / 2.4)) - 0.055)
303 r, g, b = (
max(0, x)
for x
in (r, g, b))
306 max_component =
max(r, g, b)
307 if max_component > 1:
308 r, g, b = (x / max_component
for x
in (r, g, b))
310 ir, ig, ib = (
int(x * 255)
for x
in (r, g, b))
316 """Convert a hsb into its rgb representation."""
323 f = h -
float(math.floor(h))
325 q = fB * (1 - fS * f)
326 t = fB * (1 - (fS * (1 - f)))
357 """Convert an rgb color to its hsv representation.
363 fHSV = colorsys.rgb_to_hsv(iR / 255.0, iG / 255.0, iB / 255.0)
364 return round(fHSV[0] * 360, 3), round(fHSV[1] * 100, 3), round(fHSV[2] * 100, 3)
368 """Convert an rgb color to its hs representation."""
373 """Convert an hsv color into its rgb representation.
379 fRGB = colorsys.hsv_to_rgb(iH / 360, iS / 100, iV / 100)
380 return (round(fRGB[0] * 255), round(fRGB[1] * 255), round(fRGB[2] * 255))
384 """Convert an hsv color into its rgb representation."""
389 vX: float, vY: float, Gamut: GamutType |
None =
None
390 ) -> tuple[float, float]:
391 """Convert an xy color to its hs representation."""
397 iH: float, iS: float, Gamut: GamutType |
None =
None
398 ) -> tuple[float, float]:
399 """Convert an hs color to its xy representation."""
404 input_colors: tuple[int, ...], output_colors: tuple[float, ...]
405 ) -> tuple[int, ...]:
406 """Match the maximum value of the output to the input."""
407 max_in =
max(input_colors)
408 max_out =
max(output_colors)
412 factor = max_in / max_out
413 return tuple(
int(round(i * factor))
for i
in output_colors)
417 """Convert an rgb color to an rgbw representation."""
421 rgbw = (r - w, g - w, b - w, w)
429 """Convert an rgbw color to an rgb representation."""
431 rgb = (r + w, g + w, b + w)
439 r: int, g: int, b: int, min_kelvin: int, max_kelvin: int
440 ) -> tuple[int, int, int, int, int]:
441 """Convert an rgb color to an rgbww representation."""
445 mired_range = max_mireds - min_mireds
446 mired_midpoint = min_mireds + mired_range / 2
452 r / w_r
if w_r
else 0, g / w_g
if w_g
else 0, b / w_b
if w_b
else 0
456 rgb = (r - w_r * white_level, g - w_g * white_level, b - w_b * white_level)
457 rgbww = (*rgb, round(white_level * 255), round(white_level * 255))
465 r: int, g: int, b: int, cw: int, ww: int, min_kelvin: int, max_kelvin: int
466 ) -> tuple[int, int, int]:
467 """Convert an rgbww color to an rgb representation."""
471 mired_range = max_mireds - min_mireds
473 ct_ratio = ww / (cw + ww)
474 except ZeroDivisionError:
476 color_temp_mired = min_mireds + ct_ratio * mired_range
480 color_temp_kelvin = 0
482 white_level =
max(cw, ww) / 255
485 rgb = (r + w_r * white_level, g + w_g * white_level, b + w_b * white_level)
493 """Return a RGB color from a hex color string."""
494 return f
"{round(r):02x}{round(g):02x}{round(b):02x}"
498 """Return an RGB color value list from a hex color string."""
500 int(hex_string[i : i + len(hex_string) // 3], 16)
501 for i
in range(0, len(hex_string), len(hex_string) // 3)
506 """Return an hs color from a color temperature in Kelvin."""
511 color_temperature_kelvin: float,
512 ) -> tuple[float, float, float]:
513 """Return an RGB color from a color temperature in Kelvin.
515 This is a rough approximation based on the formula provided by T. Helland
516 http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
519 if color_temperature_kelvin < 1000:
520 color_temperature_kelvin = 1000
521 elif color_temperature_kelvin > 40000:
522 color_temperature_kelvin = 40000
524 tmp_internal = color_temperature_kelvin / 100.0
532 return red, green, blue
536 temperature: int, brightness: int, min_kelvin: int, max_kelvin: int
537 ) -> tuple[int, int, int, int, int]:
538 """Convert color temperature in kelvin to rgbcw.
540 Returns a (r, g, b, cw, ww) tuple.
545 mired_range = max_mireds - min_mireds
546 cold = ((max_mireds - temperature) / mired_range) * brightness
547 warm = brightness - cold
548 return (0, 0, 0, round(cold), round(warm))
552 rgbww: tuple[int, int, int, int, int], min_kelvin: int, max_kelvin: int
553 ) -> tuple[int, int]:
554 """Convert rgbcw to color temperature in kelvin.
556 Returns a tuple (color_temperature, brightness).
558 _, _, _, cold, warm = rgbww
563 cold: int, warm: int, min_kelvin: int, max_kelvin: int
564 ) -> tuple[int, int]:
565 """Convert whites to color temperature in kelvin.
567 Returns a tuple (color_temperature, brightness).
571 brightness = warm / 255 + cold / 255
574 return (min_kelvin, 0)
577 ((cold / 255 / brightness) * (min_mireds - max_mireds)) + max_mireds
579 ),
min(255, round(brightness * 255))
583 """Convert an xy color to a color temperature in Kelvin.
585 Uses McCamy's approximation (https://doi.org/10.1002/col.5080170211),
586 close enough for uses between 2000 K and 10000 K.
588 n = (x - 0.3320) / (0.1858 - y)
589 CCT = 437 * (n**3) + 3601 * (n**2) + 6861 * n + 5517
594 def _clamp(color_component: float, minimum: float = 0, maximum: float = 255) -> float:
595 """Clamp the given color component value between the given min and max values.
597 The range defined by the minimum and maximum values is inclusive, i.e. given a
598 color_component of 0 and a minimum of 10, the returned value is 10.
600 color_component_out =
max(color_component, minimum)
601 return min(color_component_out, maximum)
605 """Get the red component of the temperature in RGB space."""
606 if temperature <= 66:
608 tmp_red = 329.698727446 * math.pow(temperature - 60, -0.1332047592)
613 """Get the green component of the given color temp in RGB space."""
614 if temperature <= 66:
615 green = 99.4708025861 * math.log(temperature) - 161.1195681661
617 green = 288.1221695283 * math.pow(temperature - 60, -0.0755148492)
622 """Get the blue component of the given color temperature in RGB space."""
623 if temperature >= 66:
625 if temperature <= 19:
627 blue = 138.5177312231 * math.log(temperature - 10) - 305.0447927307
632 """Convert absolute mired shift to degrees kelvin."""
633 return math.floor(1000000 / mired_temperature)
637 """Convert degrees kelvin to mired shift."""
638 return math.floor(1000000 / kelvin_temperature)
645 """Calculate the cross product of two XYPoints."""
646 return float(p1.x * p2.y - p1.y * p2.x)
650 """Calculate the distance between two XYPoints."""
653 return math.sqrt(dx * dx + dy * dy)
657 """Find the closest point from P to a line defined by A and B.
659 This point will be reproducible by the lamp
660 as it is on the edge of the gamut.
662 AP =
XYPoint(P.x - A.x, P.y - A.y)
663 AB =
XYPoint(B.x - A.x, B.y - A.y)
664 ab2 = AB.x * AB.x + AB.y * AB.y
665 ap_ab = AP.x * AB.x + AP.y * AB.y
673 return XYPoint(A.x + AB.x * t, A.y + AB.y * t)
677 xy_tuple: tuple[float, float], Gamut: GamutType
678 ) -> tuple[float, float]:
679 """Get the closest matching color within the gamut of the light.
681 Should only be used if the supplied color is outside of the color gamut.
683 xy_point =
XYPoint(xy_tuple[0], xy_tuple[1])
714 """Check if the provided XYPoint can be recreated by a Hue lamp."""
715 v1 =
XYPoint(Gamut.green.x - Gamut.red.x, Gamut.green.y - Gamut.red.y)
716 v2 =
XYPoint(Gamut.blue.x - Gamut.red.x, Gamut.blue.y - Gamut.red.y)
718 q =
XYPoint(p[0] - Gamut.red.x, p[1] - Gamut.red.y)
722 return (s >= 0.0)
and (t >= 0.0)
and (s + t <= 1.0)
726 """Check if the supplied gamut is valid."""
728 v1 =
XYPoint(Gamut.green.x - Gamut.red.x, Gamut.green.y - Gamut.red.y)
729 v2 =
XYPoint(Gamut.blue.x - Gamut.red.x, Gamut.blue.y - Gamut.red.y)
734 Gamut.red.x >= 0
and Gamut.red.x <= 1
and Gamut.red.y >= 0
and Gamut.red.y <= 1
738 and Gamut.green.x <= 1
739 and Gamut.green.y >= 0
740 and Gamut.green.y <= 1
744 and Gamut.blue.x <= 1
745 and Gamut.blue.y >= 0
746 and Gamut.blue.y <= 1
749 return not_on_line
and red_valid
and green_valid
and blue_valid
753 """Given a brightness_scale convert a brightness to a single value.
755 Do not include 0 if the light is off for value 0.
757 Given a brightness low_high_range of (1,100) this function
768 """Given a brightness_scale convert a single value to a brightness.
770 Do not include 0 if the light is off for value 0.
772 Given a brightness low_high_range of (1,100) this function
779 The value will be clamped between 1..255 to ensure valid value.
int color_temperature_mired_to_kelvin(float mired_temperature)
list[int] rgb_hex_to_rgb_list(str hex_string)
float _get_blue(float temperature)
float _clamp(float color_component, float minimum=0, float maximum=255)
float _get_red(float temperature)
tuple[int, int, int] color_hs_to_RGB(float iH, float iS)
tuple[int, int] _white_levels_to_color_temperature(int cold, int warm, int min_kelvin, int max_kelvin)
tuple[float, float] get_closest_point_to_point(tuple[float, float] xy_tuple, GamutType Gamut)
tuple[int, int, int] color_xy_to_RGB(float vX, float vY, GamutType|None Gamut=None)
float get_distance_between_two_points(XYPoint one, XYPoint two)
tuple[int, int, int] color_rgbw_to_rgb(int r, int g, int b, int w)
XYPoint get_closest_point_to_line(XYPoint A, XYPoint B, XYPoint P)
float brightness_to_value(tuple[float, float] low_high_range, int brightness)
tuple[int, int, int, int] color_rgb_to_rgbw(int r, int g, int b)
tuple[int, int, int] color_hsv_to_RGB(float iH, float iS, float iV)
tuple[float, float] color_RGB_to_hs(float iR, float iG, float iB)
tuple[int, int, int] color_xy_brightness_to_RGB(float vX, float vY, int ibrightness, GamutType|None Gamut=None)
RGBColor color_name_to_rgb(str color_name)
tuple[float, float] color_RGB_to_xy(int iR, int iG, int iB, GamutType|None Gamut=None)
float _get_green(float temperature)
bool check_point_in_lamps_reach(tuple[float, float] p, GamutType Gamut)
tuple[float, float] color_xy_to_hs(float vX, float vY, GamutType|None Gamut=None)
tuple[float, float] color_temperature_to_hs(float color_temperature_kelvin)
tuple[int, int, int] color_rgbww_to_rgb(int r, int g, int b, int cw, int ww, int min_kelvin, int max_kelvin)
tuple[float, float, int] color_RGB_to_xy_brightness(int iR, int iG, int iB, GamutType|None Gamut=None)
int value_to_brightness(tuple[float, float] low_high_range, float value)
tuple[int, int] rgbww_to_color_temperature(tuple[int, int, int, int, int] rgbww, int min_kelvin, int max_kelvin)
tuple[float, float] color_hs_to_xy(float iH, float iS, GamutType|None Gamut=None)
tuple[int, int, int, int, int] color_temperature_to_rgbww(int temperature, int brightness, int min_kelvin, int max_kelvin)
tuple[int, int, int] color_hsb_to_RGB(float fH, float fS, float fB)
float cross_product(XYPoint p1, XYPoint p2)
tuple[float, float, float] color_RGB_to_hsv(float iR, float iG, float iB)
int color_xy_to_temperature(float x, float y)
str color_rgb_to_hex(int r, int g, int b)
bool check_valid_gamut(GamutType Gamut)
tuple[int,...] match_max_scale(tuple[int,...] input_colors, tuple[float,...] output_colors)
tuple[float, float, float] color_temperature_to_rgb(float color_temperature_kelvin)
int color_temperature_kelvin_to_mired(float kelvin_temperature)
tuple[int, int, int, int, int] color_rgb_to_rgbww(int r, int g, int b, int min_kelvin, int max_kelvin)
float scale_to_ranged_value(tuple[float, float] source_low_high_range, tuple[float, float] target_low_high_range, float value)