# -*- coding: utf-8 -*- from __future__ import unicode_literals import re import math class Color (object): def to_rgb(self): raise Exception('Must implement to_rgb() in subclasses') class RGB (Color): def __init__(self, r, g, b): self.r = r self.g = g self.b = b def to_rgb(self): return self class HSL (Color): def __init__(self, h, s, l): self.h = h self.s = s self.l = l @staticmethod def _hue_to_rgb(t1, t2, hue): if hue < 0: hue += 6 elif hue >= 6: hue -= 6 if hue < 1: return (t2 - t1) * hue + t1 elif hue < 3: return t2 elif hue < 4: return (t2 - t1) * (4 - hue) + t1 else: return t1 def to_rgb(self): hue = self.h / 60.0 if self.l <= 0.5: t2 = self.l * (self.s + 1) else: t2 = self.l + self.s - (self.l * self.s) t1 = self.l * 2 - t2 r = self._hue_to_rgb(t1, t2, hue + 2) g = self._hue_to_rgb(t1, t2, hue) b = self._hue_to_rgb(t1, t2, hue - 2) return RGB(r, g, b) class HWB (Color): def __init__(self, h, w, b): self.h = h self.w = w self.b = b @staticmethod def _hue_to_rgb(hue): if hue < 0: hue += 6 elif hue >= 6: hue -= 6 if hue < 1: return hue elif hue < 3: return 1 elif hue < 4: return (4 - hue) else: return 0 def to_rgb(self): hue = self.h / 60.0 t1 = 1 - self.w - self.b r = self._hue_to_rgb(hue + 2) * t1 + self.w g = self._hue_to_rgb(hue) * t1 + self.w b = self._hue_to_rgb(hue - 2) * t1 + self.w return RGB(r, g, b) class CMYK (Color): def __init__(self, c, m, y, k): self.c = c self.m = m self.y = y self.k = k def to_rgb(self): r = 1.0 - min(1.0, self.c + self.k) g = 1.0 - min(1.0, self.m + self.k) b = 1.0 - min(1.0, self.y + self.k) return RGB(r, g, b) class Gray (Color): def __init__(self, g): self.g = g def to_rgb(self): return RGB(g, g, g) _x11_colors = { 'aliceblue': (240, 248, 255), 'antiquewhite': (250, 235, 215), 'aqua': ( 0, 255, 255), 'aquamarine': (127, 255, 212), 'azure': (240, 255, 255), 'beige': (245, 245, 220), 'bisque': (255, 228, 196), 'black': ( 0, 0, 0), 'blanchedalmond': (255, 235, 205), 'blue': ( 0, 0, 255), 'blueviolet': (138, 43, 226), 'brown': (165, 42, 42), 'burlywood': (222, 184, 135), 'cadetblue': ( 95, 158, 160), 'chartreuse': (127, 255, 0), 'chocolate': (210, 105, 30), 'coral': (255, 127, 80), 'cornflowerblue': (100, 149, 237), 'cornsilk': (255, 248, 220), 'crimson': (220, 20, 60), 'cyan': ( 0, 255, 255), 'darkblue': ( 0, 0, 139), 'darkcyan': ( 0, 139, 139), 'darkgoldenrod': (184, 134, 11), 'darkgray': (169, 169, 169), 'darkgreen': ( 0, 100, 0), 'darkgrey': (169, 169, 169), 'darkkhaki': (189, 183, 107), 'darkmagenta': (139, 0, 139), 'darkolivegreen': ( 85, 107, 47), 'darkorange': (255, 140, 0), 'darkorchid': (153, 50, 204), 'darkred': (139, 0, 0), 'darksalmon': (233, 150, 122), 'darkseagreen': (143, 188, 143), 'darkslateblue': ( 72, 61, 139), 'darkslategray': ( 47, 79, 79), 'darkslategrey': ( 47, 79, 79), 'darkturquoise': ( 0, 206, 209), 'darkviolet': (148, 0, 211), 'deeppink': (255, 20, 147), 'deepskyblue': ( 0, 191, 255), 'dimgray': (105, 105, 105), 'dimgrey': (105, 105, 105), 'dodgerblue': ( 30, 144, 255), 'firebrick': (178, 34, 34), 'floralwhite': (255, 250, 240), 'forestgreen': ( 34, 139, 34), 'fuchsia': (255, 0, 255), 'gainsboro': (220, 220, 220), 'ghostwhite': (248, 248, 255), 'gold': (255, 215, 0), 'goldenrod': (218, 165, 32), 'gray': (128, 128, 128), 'grey': (128, 128, 128), 'green': ( 0, 128, 0), 'greenyellow': (173, 255, 47), 'honeydew': (240, 255, 240), 'hotpink': (255, 105, 180), 'indianred': (205, 92, 92), 'indigo': ( 75, 0, 130), 'ivory': (255, 255, 240), 'khaki': (240, 230, 140), 'lavender': (230, 230, 250), 'lavenderblush': (255, 240, 245), 'lawngreen': (124, 252, 0), 'lemonchiffon': (255, 250, 205), 'lightblue': (173, 216, 230), 'lightcoral': (240, 128, 128), 'lightcyan': (224, 255, 255), 'lightgoldenrodyellow': (250, 250, 210), 'lightgray': (211, 211, 211), 'lightgreen': (144, 238, 144), 'lightgrey': (211, 211, 211), 'lightpink': (255, 182, 193), 'lightsalmon': (255, 160, 122), 'lightseagreen': ( 32, 178, 170), 'lightskyblue': (135, 206, 250), 'lightslategray': (119, 136, 153), 'lightslategrey': (119, 136, 153), 'lightsteelblue': (176, 196, 222), 'lightyellow': (255, 255, 224), 'lime': ( 0, 255, 0), 'limegreen': ( 50, 205, 50), 'linen': (250, 240, 230), 'magenta': (255, 0, 255), 'maroon': (128, 0, 0), 'mediumaquamarine': (102, 205, 170), 'mediumblue': ( 0, 0, 205), 'mediumorchid': (186, 85, 211), 'mediumpurple': (147, 112, 219), 'mediumseagreen': ( 60, 179, 113), 'mediumslateblue': (123, 104, 238), 'mediumspringgreen': ( 0, 250, 154), 'mediumturquoise': ( 72, 209, 204), 'mediumvioletred': (199, 21, 133), 'midnightblue': ( 25, 25, 112), 'mintcream': (245, 255, 250), 'mistyrose': (255, 228, 225), 'moccasin': (255, 228, 181), 'navajowhite': (255, 222, 173), 'navy': ( 0, 0, 128), 'oldlace': (253, 245, 230), 'olive': (128, 128, 0), 'olivedrab': (107, 142, 35), 'orange': (255, 165, 0), 'orangered': (255, 69, 0), 'orchid': (218, 112, 214), 'palegoldenrod': (238, 232, 170), 'palegreen': (152, 251, 152), 'paleturquoise': (175, 238, 238), 'palevioletred': (219, 112, 147), 'papayawhip': (255, 239, 213), 'peachpuff': (255, 218, 185), 'peru': (205, 133, 63), 'pink': (255, 192, 203), 'plum': (221, 160, 221), 'powderblue': (176, 224, 230), 'purple': (128, 0, 128), 'red': (255, 0, 0), 'rosybrown': (188, 143, 143), 'royalblue': ( 65, 105, 225), 'saddlebrown': (139, 69, 19), 'salmon': (250, 128, 114), 'sandybrown': (244, 164, 96), 'seagreen': ( 46, 139, 87), 'seashell': (255, 245, 238), 'sienna': (160, 82, 45), 'silver': (192, 192, 192), 'skyblue': (135, 206, 235), 'slateblue': (106, 90, 205), 'slategray': (112, 128, 144), 'slategrey': (112, 128, 144), 'snow': (255, 250, 250), 'springgreen': ( 0, 255, 127), 'steelblue': ( 70, 130, 180), 'tan': (210, 180, 140), 'teal': ( 0, 128, 128), 'thistle': (216, 191, 216), 'tomato': (255, 99, 71), 'turquoise': ( 64, 224, 208), 'violet': (238, 130, 238), 'wheat': (245, 222, 179), 'white': (255, 255, 255), 'whitesmoke': (245, 245, 245), 'yellow': (255, 255, 0), 'yellowgreen': (154, 205, 50) } _ws_re = re.compile(r'\s+') _token_re = re.compile(r'[A-Za-z_][A-Za-z0-9_]*') _hex_re = re.compile(r'#([0-9a-f]{3}(?:[0-9a-f]{3})?)$') _number_re = re.compile(r'[0-9]*(\.[0-9]*)') class ColorParser (object): def __init__(self, s): self._string = s self._pos = 0 def skipws(self): m = _ws_re.match(self._string, self._pos) if m: self._pos = m.end(0) def expect(self, s, context=''): if len(self._string) - self._pos < len(s) \ or self._string[self._pos:self._pos + len(s)] != s: raise ValueError('bad color "%s" - expected "%s"%s' % (self._string, s, context)) self._pos += len(s) def expectEnd(self): if self._pos != len(self._string): raise ValueError('junk at end of color "%s"' % self._string) def getToken(self): m = _token_re.match(self._string, self._pos) if m: token = m.group(0) self._pos = m.end(0) return token return None def parseNumber(self, context=''): m = _number_re.match(self._string, self._pos) if m: self._pos = m.end(0) return float(m.group(0)) raise ValueError('bad color "%s" - expected a number%s' % (self._string, context)) def parseColor(self): self.skipws() token = self.getToken() if token: if token == 'rgb': return self.parseRGB() elif token == 'hsl': return self.parseHSL() elif token == 'hwb': return self.parseHWB() elif token == 'cmyk': return self.parseCMYK() elif token == 'gray' or token == 'grey': return self.parseGray() try: r, g, b = _x11_colors[token] except KeyError: raise ValueError('unknown color name "%s"' % token) self.expectEnd() return RGB(r / 255.0, g / 255.0, b / 255.0) m = _hex_re.match(self._string, self._pos) if m: hrgb = m.group(1) if len(hrgb) == 3: r = int('0x' + 2 * hrgb[0], 16) g = int('0x' + 2 * hrgb[1], 16) b = int('0x' + 2 * hrgb[2], 16) else: r = int('0x' + hrgb[0:2], 16) g = int('0x' + hrgb[2:4], 16) b = int('0x' + hrgb[4:6], 16) self._pos = m.end(0) self.skipws() self.expectEnd() return RGB(r / 255.0, g / 255.0, b / 255.0) raise ValueError('bad color syntax "%s"' % self._string) def parseRGB(self): self.expect('(', 'after "rgb"') self.skipws() r = self.parseValue() self.skipws() self.expect(',', 'in "rgb"') self.skipws() g = self.parseValue() self.skipws() self.expect(',', 'in "rgb"') self.skipws() b = self.parseValue() self.skipws() self.expect(')', 'at end of "rgb"') self.skipws() self.expectEnd() return RGB(r, g, b) def parseHSL(self): self.expect('(', 'after "hsl"') self.skipws() h = self.parseAngle() self.skipws() self.expect(',', 'in "hsl"') self.skipws() s = self.parseValue() self.skipws() self.expect(',', 'in "hsl"') self.skipws() l = self.parseValue() self.skipws() self.expect(')', 'at end of "hsl"') self.skipws() self.expectEnd() return HSL(h, s, l) def parseHWB(self): self.expect('(', 'after "hwb"') self.skipws() h = self.parseAngle() self.skipws() self.expect(',', 'in "hwb"') self.skipws() w = self.parseValue() self.skipws() self.expect(',', 'in "hwb"') self.skipws() b = self.parseValue() self.skipws() self.expect(')', 'at end of "hwb"') self.skipws() self.expectEnd() return HWB(h, w, b) def parseCMYK(self): self.expect('(', 'after "cmyk"') self.skipws() c = self.parseValue() self.skipws() self.expect(',', 'in "cmyk"') self.skipws() m = self.parseValue() self.skipws() self.expect(',', 'in "cmyk"') self.skipws() y = self.parseValue() self.skipws() self.expect(',', 'in "cmyk"') self.skipws() k = self.parseValue() self.skipws() self.expect(')', 'at end of "cmyk"') self.skipws() self.expectEnd() return CMYK(c, m, y, k) def parseGray(self): self.expect('(', 'after "gray"') self.skipws() g = self.parseValue() self.skipws() self.expect(')', 'at end of "gray') self.skipws() self.expectEnd() return Gray(g) def parseValue(self): n = self.parseNumber() self.skipws() if self._string[self._pos] == '%': n = n / 100.0 self.pos += 1 return n def parseAngle(self): n = self.parseNumber() self.skipws() tok = self.getToken() if tok == 'rad': n = n * 180.0 / math.pi elif tok == 'grad' or tok == 'gon': n = n * 0.9 elif tok != 'deg': raise ValueError('bad angle unit "%s"' % tok) return n _color_re = re.compile(r'\s*(#|rgb|hsl|hwb|cmyk|gray|grey|%s)' % '|'.join(_x11_colors.keys())) def isAColor(s): return _color_re.match(s) def parseColor(s): return ColorParser(s).parseColor()