# Copyright (c) 2005 Gavin E. Crooks # # This software is distributed under the MIT Open Source License. # # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ Color specifications using CSS2 (Cascading Style Sheet) syntax.""" from typing import List, Any class Color(object): """ Color specifications using CSS2 (Cascading Style Sheet) syntax. http://www.w3.org/TR/REC-CSS2/syndata.html#color-units Usage: red = Color(255,0,0) red = Color(1., 0., 0.) red = Color.by_name("red") red = Color.from_rgb(1.,0.,0.) red = Color.from_rgb(255,0,0) red = Color.from_hsl(0.,1., 0.5) red = Color.from_string("red") red = Color.from_string("RED") red = Color.from_string("#F00") red = Color.from_string("#FF0000") red = Color.from_string("rgb(255, 0, 0)") red = Color.from_string("rgb(100%, 0%, 0%)") red = Color.from_string("hsl(0, 100%, 50%)") """ def __init__(self, red: float, green: float, blue: float) -> None: if not (type(red) == type(green) == type(blue)): raise TypeError("Mixed floats and integers?") # Convert integer RBG values in [0, 255] to floats in [0, 1] if isinstance(red, int): red /= 255. if isinstance(green, int): green /= 255. if isinstance(blue, int): blue /= 255. # Clip RBG values to [0, 1] self.red = max(0., min(red, 1.0)) self.green = max(0., min(green, 1.0)) self.blue = max(0., min(blue, 1.0)) @staticmethod def names() -> List[str]: "Return a list of standard color names." return list(_std_colors.keys()) @classmethod def from_rgb(cls, r: float, g: float, b: float) -> "Color": return cls(r, g, b) @classmethod def from_hsl(cls, hue_angle: float, saturation: float, lightness: float) -> "Color": def hue_to_rgb(v1: float, v2: float, vH: float) -> float: if vH < 0.0: vH += 1.0 if vH > 1.0: vH -= 1.0 # pragma: no cover (Grandfathered in) if vH * 6.0 < 1.0: return (v1 + (v2 - v1) * 6.0 * vH) if vH * 2.0 < 1.0: return v2 if vH * 3.0 < 2.0: return (v1 + (v2 - v1) * ((2.0 / 3.0) - vH) * 6.0) # pragma: no cover return v1 hue = (((hue_angle % 360.) + 360.) % 360.) / 360. if not (saturation >= 0.0 and saturation <= 1.0): raise ValueError("Out-of-range saturation %f" % saturation) # pragma: no cover if not (lightness >= 0.0 and lightness <= 1.0): raise ValueError("Out-of-range lightness %f" % lightness) # pragma: no cover if saturation == 0: # greyscale return cls.from_rgb(lightness, lightness, lightness) if lightness < 0.5: v2 = lightness * (1.0 + saturation) else: v2 = (lightness + saturation) - (saturation * lightness) v1 = 2.0 * lightness - v2 r = hue_to_rgb(v1, v2, hue + (1. / 3.)) g = hue_to_rgb(v1, v2, hue) b = hue_to_rgb(v1, v2, hue - (1. / 3.)) return cls(r, g, b) @staticmethod def by_name(string: str) -> "Color": s = string.strip().lower().replace(' ', '') try: return _std_colors[s] except KeyError: raise ValueError("Unknown color name: %s" % s) @classmethod def from_string(cls, string: str) -> "Color": def to_frac(string: str) -> float: # string can be "255" or "100%" if string[-1] == '%': return float(string[0:-1]) / 100. else: return float(string) / 255. s = string.strip().lower().replace(' ', '').replace('_', '') if s in _std_colors: # "red" return _std_colors[s] if s[0] == "#": # "#fef" if len(s) == 4: r = int(s[1] + s[1], 16) g = int(s[2] + s[2], 16) b = int(s[3] + s[3], 16) return cls(r, g, b) elif len(s) == 7: # "#ff00aa" r = int(s[1:3], 16) g = int(s[3:5], 16) b = int(s[5:7], 16) return cls(r, g, b) else: raise ValueError("Cannot parse string: %s" % s) if s[0:4] == 'rgb(' and s[-1] == ')': rgb = s[4:-1].split(",") if len(rgb) != 3: raise ValueError("Cannot parse string a: %s" % s) return cls(to_frac(rgb[0]), to_frac(rgb[1]), to_frac(rgb[2])) if s[0:4] == 'hsl(' and s[-1] == ')': hsl = s[4:-1].split(",") if len(hsl) != 3: raise ValueError("Cannot parse string a: %s" % s) return cls.from_hsl(int(hsl[0]), to_frac(hsl[1]), to_frac(hsl[2])) raise ValueError("Cannot parse string: %s" % s) def __eq__(self, other: Any) -> bool: if not isinstance(other, self.__class__): return False req = int(0.5 + 255. * self.red) == int(0.5 + 255. * other.red) beq = int(0.5 + 255. * self.blue) == int(0.5 + 255. * other.blue) geq = int(0.5 + 255. * self.green) == int(0.5 + 255. * other.green) return req and beq and geq def __repr__(self) -> str: return "Color(%f,%f,%f)" % (self.red, self.green, self.blue) _std_colors = dict( aliceblue=Color(240, 248, 255), # f0f8ff antiquewhite=Color(250, 235, 215), # faebd7 aqua=Color(0, 255, 255), # 00ffff aquamarine=Color(127, 255, 212), # 7fffd4 azure=Color(240, 255, 255), # f0ffff beige=Color(245, 245, 220), # f5f5dc bisque=Color(255, 228, 196), # ffe4c4 black=Color(0, 0, 0), # 000000 blanchedalmond=Color(255, 235, 205), # ffebcd blue=Color(0, 0, 255), # 0000ff blueviolet=Color(138, 43, 226), # 8a2be2 brown=Color(165, 42, 42), # a52a2a burlywood=Color(222, 184, 135), # deb887 cadetblue=Color(95, 158, 160), # 5f9ea0 chartreuse=Color(127, 255, 0), # 7fff00 chocolate=Color(210, 105, 30), # d2691e coral=Color(255, 127, 80), # ff7f50 cornflowerblue=Color(100, 149, 237), # 6495ed cornsilk=Color(255, 248, 220), # fff8dc crimson=Color(220, 20, 60), # dc143c cyan=Color(0, 255, 255), # 00ffff darkblue=Color(0, 0, 139), # 00008b darkcyan=Color(0, 139, 139), # 008b8b darkgoldenrod=Color(184, 134, 11), # b8860b darkgray=Color(169, 169, 169), # a9a9a9 darkgreen=Color(0, 100, 0), # 006400 darkgrey=Color(169, 169, 169), # a9a9a9 darkkhaki=Color(189, 183, 107), # bdb76b darkmagenta=Color(139, 0, 139), # 8b008b darkolivegreen=Color(85, 107, 47), # 556b2f darkorange=Color(255, 140, 0), # ff8c00 darkorchid=Color(153, 50, 204), # 9932cc darkred=Color(139, 0, 0), # 8b0000 darksalmon=Color(233, 150, 122), # e9967a darkseagreen=Color(143, 188, 143), # 8fbc8f darkslateblue=Color(72, 61, 139), # 483d8b darkslategray=Color(47, 79, 79), # 2f4f4f darkslategrey=Color(47, 79, 79), # 2f4f4f darkturquoise=Color(0, 206, 209), # 00ced1 darkviolet=Color(148, 0, 211), # 9400d3 deeppink=Color(255, 20, 147), # ff1493 deepskyblue=Color(0, 191, 255), # 00bfff dimgray=Color(105, 105, 105), # 696969 dimgrey=Color(105, 105, 105), # 696969 dodgerblue=Color(30, 144, 255), # 1e90ff firebrick=Color(178, 34, 34), # b22222 floralwhite=Color(255, 250, 240), # fffaf0 forestgreen=Color(34, 139, 34), # 228b22 fuchsia=Color(255, 0, 255), # ff00ff gainsboro=Color(220, 220, 220), # dcdcdc ghostwhite=Color(248, 248, 255), # f8f8ff gold=Color(255, 215, 0), # ffd700 goldenrod=Color(218, 165, 32), # daa520 gray=Color(128, 128, 128), # 808080 green=Color(0, 128, 0), # 008000 greenyellow=Color(173, 255, 47), # adff2f grey=Color(128, 128, 128), # 808080 honeydew=Color(240, 255, 240), # f0fff0 hotpink=Color(255, 105, 180), # ff69b4 indianred=Color(205, 92, 92), # cd5c5c indigo=Color(75, 0, 130), # 4b0082 ivory=Color(255, 255, 240), # fffff0 khaki=Color(240, 230, 140), # f0e68c lavender=Color(230, 230, 250), # e6e6fa lavenderblush=Color(255, 240, 245), # fff0f5 lawngreen=Color(124, 252, 0), # 7cfc00 lemonchiffon=Color(255, 250, 205), # fffacd lightblue=Color(173, 216, 230), # add8e6 lightcoral=Color(240, 128, 128), # f08080 lightcyan=Color(224, 255, 255), # e0ffff lightgoldenrodyellow=Color(250, 250, 210), # fafad2 lightgray=Color(211, 211, 211), # d3d3d3 lightgreen=Color(144, 238, 144), # 90ee90 lightgrey=Color(211, 211, 211), # d3d3d3 lightpink=Color(255, 182, 193), # ffb6c1 lightsalmon=Color(255, 160, 122), # ffa07a lightseagreen=Color(32, 178, 170), # 20b2aa lightskyblue=Color(135, 206, 250), # 87cefa lightslategray=Color(119, 136, 153), # 778899 lightslategrey=Color(119, 136, 153), # 778899 lightsteelblue=Color(176, 196, 222), # b0c4de lightyellow=Color(255, 255, 224), # ffffe0 lime=Color(0, 255, 0), # 00ff00 limegreen=Color(50, 205, 50), # 32cd32 linen=Color(250, 240, 230), # faf0e6 magenta=Color(255, 0, 255), # ff00ff maroon=Color(128, 0, 0), # 800000 mediumaquamarine=Color(102, 205, 170), # 66cdaa mediumblue=Color(0, 0, 205), # 0000cd mediumorchid=Color(186, 85, 211), # ba55d3 mediumpurple=Color(147, 112, 219), # 9370db mediumseagreen=Color(60, 179, 113), # 3cb371 mediumslateblue=Color(123, 104, 238), # 7b68ee mediumspringgreen=Color(0, 250, 154), # 00fa9a mediumturquoise=Color(72, 209, 204), # 48d1cc mediumvioletred=Color(199, 21, 133), # c71585 midnightblue=Color(25, 25, 112), # 191970 mintcream=Color(245, 255, 250), # f5fffa mistyrose=Color(255, 228, 225), # ffe4e1 moccasin=Color(255, 228, 181), # ffe4b5 navajowhite=Color(255, 222, 173), # ffdead navy=Color(0, 0, 128), # 000080 oldlace=Color(253, 245, 230), # fdf5e6 olive=Color(128, 128, 0), # 808000 olivedrab=Color(107, 142, 35), # 6b8e23 orange=Color(255, 165, 0), # ffa500 orangered=Color(255, 69, 0), # ff4500 orchid=Color(218, 112, 214), # da70d6 palegoldenrod=Color(238, 232, 170), # eee8aa palegreen=Color(152, 251, 152), # 98fb98 paleturquoise=Color(175, 238, 238), # afeeee palevioletred=Color(219, 112, 147), # db7093 papayawhip=Color(255, 239, 213), # ffefd5 peachpuff=Color(255, 218, 185), # ffdab9 peru=Color(205, 133, 63), # cd853f pink=Color(255, 192, 203), # ffc0cb plum=Color(221, 160, 221), # dda0dd powderblue=Color(176, 224, 230), # b0e0e6 purple=Color(128, 0, 128), # 800080 red=Color(255, 0, 0), # ff0000 rosybrown=Color(188, 143, 143), # bc8f8f royalblue=Color(65, 105, 225), # 4169e1 saddlebrown=Color(139, 69, 19), # 8b4513 salmon=Color(250, 128, 114), # fa8072 sandybrown=Color(244, 164, 96), # f4a460 seagreen=Color(46, 139, 87), # 2e8b57 seashell=Color(255, 245, 238), # fff5ee sienna=Color(160, 82, 45), # a0522d silver=Color(192, 192, 192), # c0c0c0 skyblue=Color(135, 206, 235), # 87ceeb slateblue=Color(106, 90, 205), # 6a5acd slategray=Color(112, 128, 144), # 708090 slategrey=Color(112, 128, 144), # 708090 snow=Color(255, 250, 250), # fffafa springgreen=Color(0, 255, 127), # 00ff7f steelblue=Color(70, 130, 180), # 4682b4 tan=Color(210, 180, 140), # d2b48c teal=Color(0, 128, 128), # 008080 thistle=Color(216, 191, 216), # d8bfd8 tomato=Color(255, 99, 71), # ff6347 turquoise=Color(64, 224, 208), # 40e0d0 violet=Color(238, 130, 238), # ee82ee wheat=Color(245, 222, 179), # f5deb3 white=Color(255, 255, 255), # ffffff whitesmoke=Color(245, 245, 245), # f5f5f5 yellow=Color(255, 255, 0), # ffff00 yellowgreen=Color(154, 205, 50) # 9acd32 )