Source code for inkex.colors

# coding=utf-8
#
# Copyright (C) 2006 Jos Hirth, kaioa.com
# Copyright (C) 2007 Aaron C. Spike
# Copyright (C) 2009 Monash University
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
"""
Basic color controls
"""

# All the names that get added to the inkex API itself.
__all__ = ('Color',)

SVG_COLOR = {
    'aliceblue': '#f0f8ff',
    'antiquewhite': '#faebd7',
    'aqua': '#00ffff',
    'aquamarine': '#7fffd4',
    'azure': '#f0ffff',
    'beige': '#f5f5dc',
    'bisque': '#ffe4c4',
    'black': '#000000',
    'blanchedalmond': '#ffebcd',
    'blue': '#0000ff',
    'blueviolet': '#8a2be2',
    'brown': '#a52a2a',
    'burlywood': '#deb887',
    'cadetblue': '#5f9ea0',
    'chartreuse': '#7fff00',
    'chocolate': '#d2691e',
    'coral': '#ff7f50',
    'cornflowerblue': '#6495ed',
    'cornsilk': '#fff8dc',
    'crimson': '#dc143c',
    'cyan': '#00ffff',
    'darkblue': '#00008b',
    'darkcyan': '#008b8b',
    'darkgoldenrod': '#b8860b',
    'darkgray': '#a9a9a9',
    'darkgreen': '#006400',
    'darkgrey': '#a9a9a9',
    'darkkhaki': '#bdb76b',
    'darkmagenta': '#8b008b',
    'darkolivegreen': '#556b2f',
    'darkorange': '#ff8c00',
    'darkorchid': '#9932cc',
    'darkred': '#8b0000',
    'darksalmon': '#e9967a',
    'darkseagreen': '#8fbc8f',
    'darkslateblue': '#483d8b',
    'darkslategray': '#2f4f4f',
    'darkslategrey': '#2f4f4f',
    'darkturquoise': '#00ced1',
    'darkviolet': '#9400d3',
    'deeppink': '#ff1493',
    'deepskyblue': '#00bfff',
    'dimgray': '#696969',
    'dimgrey': '#696969',
    'dodgerblue': '#1e90ff',
    'firebrick': '#b22222',
    'floralwhite': '#fffaf0',
    'forestgreen': '#228b22',
    'fuchsia': '#ff00ff',
    'gainsboro': '#dcdcdc',
    'ghostwhite': '#f8f8ff',
    'gold': '#ffd700',
    'goldenrod': '#daa520',
    'gray': '#808080',
    'grey': '#808080',
    'green': '#008000',
    'greenyellow': '#adff2f',
    'honeydew': '#f0fff0',
    'hotpink': '#ff69b4',
    'indianred': '#cd5c5c',
    'indigo': '#4b0082',
    'ivory': '#fffff0',
    'khaki': '#f0e68c',
    'lavender': '#e6e6fa',
    'lavenderblush': '#fff0f5',
    'lawngreen': '#7cfc00',
    'lemonchiffon': '#fffacd',
    'lightblue': '#add8e6',
    'lightcoral': '#f08080',
    'lightcyan': '#e0ffff',
    'lightgoldenrodyellow': '#fafad2',
    'lightgray': '#d3d3d3',
    'lightgreen': '#90ee90',
    'lightgrey': '#d3d3d3',
    'lightpink': '#ffb6c1',
    'lightsalmon': '#ffa07a',
    'lightseagreen': '#20b2aa',
    'lightskyblue': '#87cefa',
    'lightslategray': '#778899',
    'lightslategrey': '#778899',
    'lightsteelblue': '#b0c4de',
    'lightyellow': '#ffffe0',
    'lime': '#00ff00',
    'limegreen': '#32cd32',
    'linen': '#faf0e6',
    'magenta': '#ff00ff',
    'maroon': '#800000',
    'mediumaquamarine': '#66cdaa',
    'mediumblue': '#0000cd',
    'mediumorchid': '#ba55d3',
    'mediumpurple': '#9370db',
    'mediumseagreen': '#3cb371',
    'mediumslateblue': '#7b68ee',
    'mediumspringgreen': '#00fa9a',
    'mediumturquoise': '#48d1cc',
    'mediumvioletred': '#c71585',
    'midnightblue': '#191970',
    'mintcream': '#f5fffa',
    'mistyrose': '#ffe4e1',
    'moccasin': '#ffe4b5',
    'navajowhite': '#ffdead',
    'navy': '#000080',
    'oldlace': '#fdf5e6',
    'olive': '#808000',
    'olivedrab': '#6b8e23',
    'orange': '#ffa500',
    'orangered': '#ff4500',
    'orchid': '#da70d6',
    'palegoldenrod': '#eee8aa',
    'palegreen': '#98fb98',
    'paleturquoise': '#afeeee',
    'palevioletred': '#db7093',
    'papayawhip': '#ffefd5',
    'peachpuff': '#ffdab9',
    'peru': '#cd853f',
    'pink': '#ffc0cb',
    'plum': '#dda0dd',
    'powderblue': '#b0e0e6',
    'purple': '#800080',
    'rebeccapurple': '#663399',
    'red': '#ff0000',
    'rosybrown': '#bc8f8f',
    'royalblue': '#4169e1',
    'saddlebrown': '#8b4513',
    'salmon': '#fa8072',
    'sandybrown': '#f4a460',
    'seagreen': '#2e8b57',
    'seashell': '#fff5ee',
    'sienna': '#a0522d',
    'silver': '#c0c0c0',
    'skyblue': '#87ceeb',
    'slateblue': '#6a5acd',
    'slategray': '#708090',
    'slategrey': '#708090',
    'snow': '#fffafa',
    'springgreen': '#00ff7f',
    'steelblue': '#4682b4',
    'tan': '#d2b48c',
    'teal': '#008080',
    'thistle': '#d8bfd8',
    'tomato': '#ff6347',
    'turquoise': '#40e0d0',
    'violet': '#ee82ee',
    'wheat': '#f5deb3',
    'white': '#ffffff',
    'whitesmoke': '#f5f5f5',
    'yellow': '#ffff00',
    'yellowgreen': '#9acd32',
    'none': None,
}
COLOR_SVG = dict([(value, name) for name, value in SVG_COLOR.items()])

def is_color(color):
    """Determine if its a color we can use. If not, leave it unchanged."""
    try:
        return bool(Color(color))
    except ColorError:
        return False


class ColorError(KeyError):
    """Specific color parsing error"""


[docs]class Color(list): """An RGB array for the color""" red = property(lambda self: self.to_rgb()[0]) red = red.setter(lambda self, value: self._set(0, value)) green = property(lambda self: self.to_rgb()[1]) green = green.setter(lambda self, value: self._set(1, value)) blue = property(lambda self: self.to_rgb()[2]) blue = blue.setter(lambda self, value: self._set(2, value)) alpha = property(lambda self: self.to_rgba()[3]) alpha = alpha.setter(lambda self, value: self._set(3, value, ('rgba',))) hue = property(lambda self: self.to_hsl()[0]) hue = hue.setter(lambda self, value: self._set(0, value, ('hsl',))) saturation = property(lambda self: self.to_hsl()[1]) saturation = saturation.setter(lambda self, value: self._set(1, value, ('hsl',))) lightness = property(lambda self: self.to_hsl()[2]) lightness = lightness.setter(lambda self, value: self._set(2, value, ('hsl',))) def __init__(self, color=None, space='rgb'): super(Color, self).__init__() if isinstance(color, str): space, color = self.parse_str(color) # Empty list means 'none', or no color if color is None: color = [] if not isinstance(color, (list, tuple)): raise ColorError("Not a known a color value") self.space = space for val in color: self.append(val) def _set(self, index, value, spaces=('rgb', 'rgba')): """Set the color value in place, limits setter to specific color space""" # Named colors are just rgb, so dump name memory if self.space == 'named': self.space = 'rgb' if not self.space in spaces: if index == 3 and self.space == 'rgb': # Special, add alpha, don't convert back to rgb self.space = 'rgba' self.append(value) return else: # Set in other colour space and convert back and forth target = getattr(self, 'to_' + spaces[0])() target[index] = value self[:] = getattr(target, 'to_' + self.space)() self[index] = value
[docs] def append(self, val): """Append a value to the local list""" if len(self) == len(self.space): raise ValueError("Can't add any more values to color.") if isinstance(val, str): val = val.strip() if val.endswith('%'): val = float(val.strip('%')) / 100 else: val = float(val) end_type = int if len(self) == 3: # Alpha value val = min([1.0, val]) end_type = float elif isinstance(val, float) and val <= 1.0: val *= 255 if isinstance(val, (int, float)): super(Color, self).append(max(end_type(val), 0))
[docs] @staticmethod def parse_str(color): """Creates a rgb int array""" # Handle pre-defined svg color values if color and color.lower() in SVG_COLOR: return 'named', Color.parse_str(SVG_COLOR[color.lower()])[1] if color is None: return 'rgb', None # Next handle short colors (css: #abc -> #aabbcc) if color.startswith('#'): # Remove any icc or ilab directives # FUTURE: We could use icc or ilab information col = color.split(' ')[0] if len(col) == 4: col = '#{1}{1}{2}{2}{3}{3}'.format(*col) # Convert hex to integers return 'rgb', (int(col[1:3], 16), int(col[3:5], 16), int(col[5:], 16)) # Handle other css color values elif '(' in color and ')' in color: space, values = color.lower().strip().strip(')').split('(') return space, values.split(',') raise ColorError("Unknown color format: {}".format(color))
def __str__(self): """int array to #rrggbb""" if not self: return 'none' if self.space == 'named': rgbhex = '#{0:02x}{1:02x}{2:02x}'.format(*self) if rgbhex in COLOR_SVG: return COLOR_SVG[rgbhex] self.space = 'rgb' if self.space == 'rgb': return '#{0:02x}{1:02x}{2:02x}'.format(*self) if self.space == 'rgba': if self[3] == 1.0: return 'rgb({:g}, {:g}, {:g})'.format(*self[:3]) return 'rgba({:g}, {:g}, {:g}, {:g})'.format(*self) elif self.space == 'hsl': return 'hsl({0:g}, {1:g}, {2:g})'.format(*self) raise ColorError("Can't print colour space '{}'".format(self.space))
[docs] def to_hsl(self): """Turn this color into a Hue/Saturation/Lightness colour space""" if not self and self.space in ('rgb', 'named'): return self.to_rgb().to_hsl() if self.space == 'hsl': return self elif self.space == 'rgb': return Color(rgb_to_hsl(*self.to_floats()), space='hsl') raise ColorError("Unknown color conversion {}->hsl".format(self.space))
[docs] def to_rgb(self): """Turn this color into a Red/Green/Blue colour space""" if not self and self.space in ('rgb', 'named'): return Color([0, 0, 0]) if self.space == 'rgb': return self if self.space in ('rgba', 'named'): return Color(self[:3], space='rgb') elif self.space == 'hsl': return Color(hsl_to_rgb(*self.to_floats()), space='rgb') raise ColorError("Unknown color conversion {}->rgb".format(self.space))
[docs] def to_rgba(self, alpha=1.0): """Turn this color isn't an RGB with Alpha colour space""" if self.space == 'rgba': return self return Color(self.to_rgb() + [alpha], 'rgba')
[docs] def to_floats(self): """Returns the colour values as percentage floats (0.0 - 1.0)""" return [val / 255.0 for val in self]
def rgb_to_hsl(red, green, blue): """RGB to HSL colour conversion""" rgb_max = max(red, green, blue) rgb_min = min(red, green, blue) delta = rgb_max - rgb_min hsl = [0.0, 0.0, (rgb_max + rgb_min) / 2.0] if delta != 0: if hsl[2] <= 0.5: hsl[1] = delta / (rgb_max + rgb_min) else: hsl[1] = delta / (2 - rgb_max - rgb_min) if red == rgb_max: hsl[0] = (green - blue) / delta elif green == rgb_max: hsl[0] = 2.0 + (blue - red) / delta elif blue == rgb_max: hsl[0] = 4.0 + (red - green) / delta hsl[0] /= 6.0 if hsl[0] < 0: hsl[0] += 1 if hsl[0] > 1: hsl[0] -= 1 return hsl def hsl_to_rgb(hue, sat, light): """HSL to RGB Color Conversion""" if sat == 0: return [light, light, light] # Gray if light < 0.5: val2 = light * (1 + sat) else: val2 = light + sat - light * sat val1 = 2 * light - val2 return [_hue_to_rgb(val1, val2, hue * 6 + 2.0), _hue_to_rgb(val1, val2, hue * 6), _hue_to_rgb(val1, val2, hue * 6 - 2.0)] def _hue_to_rgb(val1, val2, hue): if hue < 0: hue += 6.0 if hue > 6: hue -= 6.0 if hue < 1: return val1 + (val2 - val1) * hue if hue < 3: return val2 if hue < 4: return val1 + (val2 - val1) * (4 - hue) return val1