Source code for enaml.colors

#------------------------------------------------------------------------------
#  Copyright (c) 2011, Enthought, Inc.
#  All rights reserved.
#------------------------------------------------------------------------------
""" A utility module for dealing with colors.

"""
from colorsys import hls_to_rgb
import re


#: Regex sub-expressions used for building more complex expression.
_int = r'\s*((?:\+|\-)?[0-9]+)\s*'
_real = r'\s*((?:\+|\-)?[0-9]*(?:\.[0-9]+)?)\s*'
_perc = r'\s*((?:\+|\-)?[0-9]*(?:\.[0-9]+)?)%\s*'

#: Regular expressions used by the parsing routines.
_HEX_RE = re.compile(r'^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$', re.UNICODE)
_RGB_NUM_RE = re.compile(r'^rgb\(%s,%s,%s\)$' % (_int, _int, _int), re.UNICODE)
_RGB_PER_RE = re.compile(r'^rgb\(%s,%s,%s\)$' % (_perc, _perc, _perc), re.UNICODE)
_RGBA_NUM_RE = re.compile(r'^rgba\(%s,%s,%s,%s\)$' % (_int, _int, _int, _real), re.UNICODE)
_RGBA_PER_RE = re.compile(r'^rgba\(%s,%s,%s,%s\)$' % (_perc, _perc, _perc, _real), re.UNICODE)
_HSL_RE = re.compile(r'^hsl\(%s,%s,%s\)$' % (_real, _perc, _perc), re.UNICODE)
_HSLA_RE = re.compile(r'^hsla\(%s,%s,%s,%s\)$' % (_real, _perc, _perc, _real), re.UNICODE)


#: A table of all 147 named SVG colors supported by CSS3. These values
#: will be converted to floating point rgba values color table immediately
#: after its definition.
_SVG_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), 
}


#: Convert the svg color table into a floating point rgba color table.
_COLOR_TABLE = {}
for key, value in _SVG_COLORS.iteritems():
    r, g, b = value
    r /= 255.0
    g /= 255.0
    b /= 255.0
    _COLOR_TABLE[key] = (r, g, b, 1.0)


def _parse_hex_color(color):
    """ Parse a CSS color string which starts with the '#' character.

    """
    int_ = int
    match = _HEX_RE.match(color)
    if match is not None:
        hex_str = match.group(1)
        if len(hex_str) == 3:
            r = int_(hex_str[0], 16)
            r |= (r << 4)
            g = int_(hex_str[1], 16)
            g |= (g << 4)
            b = int_(hex_str[2], 16)
            b |= (b << 4)
        else:
            r = int_(hex_str[:2], 16)
            g = int_(hex_str[2:4], 16)
            b = int_(hex_str[4:6], 16)
        return (r / 255.0, g / 255.0, b / 255.0, 1.0) 


def _parse_rgb_color(color):
    """ Parse a CSS color string which starts with the 'r' character.

    """
    int_ = int
    min_ = min
    max_ = max
    match = _RGB_NUM_RE.match(color)
    if match is not None:
        rs, gs, bs = match.groups()
        r = max_(0, min_(255, int_(rs))) / 255.0
        g = max_(0, min_(255, int_(gs))) / 255.0
        b = max_(0, min_(255, int_(bs))) / 255.0
        return (r, g, b, 1.0) 

    float_ = float
    match = _RGB_PER_RE.match(color)
    if match is not None:
        rs, gs, bs = match.groups()
        r = max_(0.0, min_(100.0, float_(rs))) / 100.0 
        g = max_(0.0, min_(100.0, float_(gs))) / 100.0
        b = max_(0.0, min_(100.0, float_(bs))) / 100.0
        return (r, g, b, 1.0) 

    match = _RGBA_NUM_RE.match(color)
    if match is not None:
        rs, gs, bs, as_ = match.groups()
        r = max_(0, min_(255, int_(rs))) / 255.0
        g = max_(0, min_(255, int_(gs))) / 255.0
        b = max_(0, min_(255, int_(bs))) / 255.0
        a = max_(0.0, min_(1.0, float_(as_)))
        return (r, g, b, a)

    match = _RGBA_PER_RE.match(color)
    if match is not None:
        rs, gs, bs, as_ = match.groups()
        r = max_(0.0, min_(100.0, float_(rs))) / 100.0 
        g = max_(0.0, min_(100.0, float_(gs))) / 100.0
        b = max_(0.0, min_(100.0, float_(bs))) / 100.0
        a = max_(0.0, min_(1.0, float_(as_)))
        return (r, g, b, a)


def _parse_hsl_color(color):
    """ Parse a CSS color string that starts with the 'h' character.

    """
    float_ = float
    min_ = min
    max_ = max
    match = _HSL_RE.match(color)
    if match is not None:
        hs, ss, ls = match.groups()
        h = ((float_(hs) % 360.0 + 360.0) % 360.0) / 360.0
        s = max_(0.0, min_(100.0, float_(ss))) / 100.0
        l = max_(0.0, min_(100.0, float_(ls))) / 100.0
        r, g, b = hls_to_rgb(h, l, s)
        return (r, g, b, 1.0)

    match = _HSLA_RE.match(color)
    if match is not None:
        hs, ss, ls, as_ = match.groups()
        h = ((float_(hs) % 360.0 + 360.0) % 360.0) / 360.0
        s = max_(0.0, min_(100.0, float_(ss))) / 100.0
        l = max_(0.0, min_(100.0, float_(ls))) / 100.0
        a = max_(0.0, min_(1.0, float_(as_)))
        r, g, b = hls_to_rgb(h, l, s)
        return (r, g, b, a)


#: A dispatch table of color parser functions.
_COLOR_PARSERS = {
    '#': _parse_hex_color,
    'r': _parse_rgb_color,
    'h': _parse_hsl_color,
}


[docs]def parse_color(color): """ Parse a color string into a tuple of RGBA values. Parameters ---------- color : string A CSS3 string representation of the color. Returns ------- result : tuple or None A tuple of RGBA values. All values are floats in the range 0.0 - 1.0. If the string is invalid, None will be returned. """ if color in _COLOR_TABLE: return _COLOR_TABLE[color] color = color.strip() if color: key = color[0] if key in _COLOR_PARSERS: return _COLOR_PARSERS[key](color)
[docs]def composite_colors(first, second): """ Composite two colors together using their given alpha. The first color will be composited on top of the second color. Parameters ---------- first : tuple The rgba tuple of the first color. All values are floats in the range 0.0 - 1.0. second : tuple The rgba tuple of the second color. The format of this tuple is the same as the first color. Returns ------- result : tuple The composited rgba color tuple. """ r1, g1, b1, a1 = first r2, g2, b2, a2 = second y = a2 * (1.0 - a1) ro = r1 * a1 + r2 * y go = g1 * a1 + g2 * y bo = b1 * a1 + b2 * y ao = a1 + y return (ro, go, bo, ao)