Source code for compas.utilities.colors


from __future__ import print_function
from __future__ import absolute_import
from __future__ import division

import re

try:
    basestring
except NameError:
    basestring = str


__all__ = [
    'i_to_rgb',
    'i_to_red',
    'i_to_green',
    'i_to_blue',
    'i_to_white',
    'i_to_black',
    'is_color_rgb',
    'is_color_hex',
    'is_color_light',

    'rgb_to_hex',
    'rgb_to_rgb',
    'hex_to_rgb',
    'color_to_colordict',
    'color_to_rgb',

    'Colormap',

    'red',
    'green',
    'blue',
    'yellow',
    'cyan',
    'white',
    'black',
]


red = 255, 0, 0
orange = 255, 125, 0
yellow = 255, 255, 0
yellowish = 125, 255, 0
green = 0, 255, 0
greenish = 0, 255, 125
cyan = 0, 255, 255
cyanish = 0, 125, 255
blue = 0, 0, 255

white = 255, 255, 255
black = 0, 0, 0


BASE16 = '0123456789abcdef'


try:
    HEX_DEC = {v: int(v, base=16) for v in [x + y for x in BASE16 for y in BASE16]}
except Exception:
    HEX_DEC = {v: int(v, 16) for v in [x + y for x in BASE16 for y in BASE16]}


[docs]def i_to_rgb(i, normalize=False): """Convert a number between 0.0 and 1.0 to an equivalent RGB tuple. Parameters ---------- i : float A number between `0.0` and `1.0`. normalize : bool, optional Normalize the resulting RGB values. Default is to return integer values ranging from 0 to 255. Returns ------- tuple The RGB values of the color corresponding to the provided number. If `normalize` is true, the RGB values are normalized to values between 0.0 and 1.0. If `normalize` is false, the RGB values are integers between 0 and 255. Examples -------- >>> i_to_rgb(1.0) (255, 0, 0) >>> i_to_rgb(0.75) (255, 255, 0) >>> i_to_rgb(0.5) (0, 255, 0) >>> i_to_rgb(0.25) (0, 255, 255) >>> i_to_rgb(0.0) (0, 0, 255) >>> i_to_rgb(1.0, True) (1.0, 0.0, 0.0) >>> i_to_rgb(0.75, True) (1.0, 1.0, 0.0) >>> i_to_rgb(0.5, True) (0.0, 1.0, 0.0) >>> i_to_rgb(0.25, True) (0.0, 1.0, 1.0) >>> i_to_rgb(0.0, True) (0.0, 0.0, 1.0) """ i = max(i, 0.0) i = min(i, 1.0) if i == 0.0: r, g, b = 0, 0, 255 elif 0.0 < i < 0.25: r, g, b = 0, int(255 * (4 * i)), 255 elif i == 0.25: r, g, b = 0, 255, 255 elif 0.25 < i < 0.5: r, g, b = 0, 255, int(255 - 255 * 4 * (i - 0.25)) elif i == 0.5: r, g, b = 0, 255, 0 elif 0.5 < i < 0.75: r, g, b = int(0 + 255 * 4 * (i - 0.5)), 255, 0 elif i == 0.75: r, g, b = 255, 255, 0 elif 0.75 < i < 1.0: r, g, b, = 255, int(255 - 255 * 4 * (i - 0.75)), 0 elif i == 1.0: r, g, b = 255, 0, 0 else: r, g, b = 0, 0, 0 if not normalize: return r, g, b return r / 255.0, g / 255.0, b / 255.0
[docs]def i_to_red(i, normalize=False): """Convert a number between 0.0 and 1.0 to a shade of red. Parameters ---------- i : float A number between 0.0 and 1.0. normalize : bool, optional Normalize the resulting RGB values. Default is to return integer values ranging from 0 to 255. Returns ------- tuple The RGB values of the color corresponding to the provided number. If `normalize` is true, the RGB values are normalized to values between 0.0 and 1.0. If `normalize` is false, the RGB values are integers between 0 and 255. Examples -------- >>> i_to_red(1.0) (255, 0, 0) >>> i_to_red(0.0) (255, 255, 255) """ i = max(i, 0.0) i = min(i, 1.0) g = b = min((1 - i) * 255, 255) if not normalize: return 255, int(g), int(b) return 1.0, g / 255, b / 255
[docs]def i_to_green(i, normalize=False): """Convert a number between 0.0 and 1.0 to a shade of green. Parameters ---------- i : float A number between 0.0 and 1.0. normalize : bool, optional Normalize the resulting RGB values. Default is to return integer values ranging from 0 to 255. Returns ------- tuple The RGB values of the color corresponding to the provided number. If `normalize` is true, the RGB values are normalized to values between 0.0 and 1.0. If `normalize` is false, the RGB values are integers between 0 and 255. Examples -------- >>> i_to_green(1.0) (0, 255, 0) >>> i_to_green(0.0) (255, 255, 255) """ i = max(i, 0.0) i = min(i, 1.0) r = b = min((1 - i) * 255, 255) if not normalize: return int(r), 255, int(b) return r / 255, 1.0, b / 255
[docs]def i_to_blue(i, normalize=False): """Convert a number between 0.0 and 1.0 to a shade of blue. Parameters ---------- i : float A number between 0.0 and 1.0. normalize : bool, optional Normalize the resulting RGB values. Default is to return integer values ranging from 0 to 255. Returns ------- tuple The RGB values of the color corresponding to the provided number. If `normalize` is true, the RGB values are normalized to values between 0.0 and 1.0. If `normalize` is false, the RGB values are integers between 0 and 255. Examples -------- >>> i_to_blue(1.0) (0, 0, 255) >>> i_to_blue(0.0) (255, 255, 255) """ i = max(i, 0.0) i = min(i, 1.0) r = g = min((1 - i) * 255, 255) if not normalize: return int(r), int(g), 255 return r / 255, g / 255, 1.0
[docs]def i_to_white(i, normalize=False): """Convert a number between 0.0 and 1.0 to a shade of white. Parameters ---------- i : float A number between 0.0 and 1.0. normalize : bool, optional Normalize the resulting RGB values. Default is to return integer values ranging from 0 to 255. Returns ------- tuple The RGB values of the color corresponding to the provided number. If `normalize` is true, the RGB values are normalized to values between 0.0 and 1.0. If `normalize` is false, the RGB values are integers between 0 and 255. Examples -------- >>> i_to_white(1.0) (255, 255, 255) >>> i_to_white(0.0) (0, 0, 0) """ i = max(i, 0.0) i = min(i, 1.0) rgb = min(i * 255, 255) if not normalize: return int(rgb), int(rgb), int(rgb) rgb = rgb / 255 return rgb, rgb, rgb
[docs]def i_to_black(i, normalize=False): """Convert a number between 0.0 and 1.0 to a shade of black. Parameters ---------- i : float A number between 0.0 and 1.0. normalize : bool, optional Normalize the resulting RGB values. Default is to return integer values ranging from 0 to 255. Returns ------- tuple The RGB values of the color corresponding to the provided number. If `normalize` is true, the RGB values are normalized to values between 0.0 and 1.0. If `normalize` is false, the RGB values are integers between 0 and 255. Examples -------- >>> i_to_black(1.0) (0, 0, 0) >>> i_to_black(0.0) (255, 255, 255) """ i = max(i, 0.0) i = min(i, 1.0) rgb = min((1 - i) * 255, 255) if not normalize: return int(rgb), int(rgb), int(rgb) rgb = rgb / 255 return rgb, rgb, rgb
class Colormap(object): """Convenience class for converting a data range into a corresponding RGB color range. Parameters ---------- data : list A list of data points. spec : {'rgb', 'red', 'green', 'blue', 'white', 'black'} A color specification. Examples -------- >>> data = list(range(10)) >>> cmap = Colormap(data, 'rgb') >>> for d in data: ... cmap(d) ... (0, 0, 255) (0, 113, 255) (0, 226, 255) (0, 255, 170) (0, 255, 56) (56, 255, 0) (169, 255, 0) (255, 226, 0) (255, 113, 0) (255, 0, 0) """ colorfuncs = { 'rgb': i_to_rgb, 'red': i_to_red, 'green': i_to_green, 'blue': i_to_blue, 'white': i_to_white, 'black': i_to_black } def __init__(self, data, spec): self.data = data self.dmin = min(data) self.dmax = max(data) self.dspan = self.dmax - self.dmin self.colorfunc = Colormap.colorfuncs[spec] def __call__(self, value): i = (value - self.dmin) / (self.dspan) return self.colorfunc(i) def is_color_rgb(color): """Is a color in a valid RGB format. Parameters ---------- color : obj The color object. Returns ------- bool True, if the color object is in RGB format. False, otherwise. Examples -------- >>> color = (255, 0, 0) >>> is_color_rgb(color) True >>> color = (1.0, 0.0, 0.0) >>> is_color_rgb(color) True >>> color = (1.0, 0, 0) >>> is_color_rgb(color) False >>> color = (255, 0.0, 0.0) >>> is_color_rgb(color) False >>> color = (256, 0, 0) >>> is_color_rgb(color) False """ if isinstance(color, (tuple, list)): if len(color) == 3: if all(isinstance(c, float) for c in color): if all(c >= 0.0 and c <= 1.0 for c in color): return True elif all(isinstance(c, int) for c in color): if all(c >= 0 and c <= 255 for c in color): return True return False def is_color_hex(color): """Is a color in a valid HEX format. Parameters ---------- color : obj The color object. Returns ------- bool True, if the color object is in HEX format. False, otherwise. Examples -------- >>> is_color_hex("#ff0000") True >>> is_color_hex("#f00") True >>> is_color_hex("#f000") False >>> is_color_hex("#ff000") False """ if isinstance(color, basestring): match = re.search(r'^#(?:[0-9a-fA-F]{3}){1,2}$', color) if match: return True return False return False def rgb_to_rgb(rgb, g=None, b=None): """Convert an RGB color specification to an integer-based RGB color specification. Parameters ---------- rgb : int or float or tuple or list A full RGB color specification, or an integer or a float representing the value of the red component. g : int or float, optional The green component. This parameter is ignored if `rgb` is a full color specification. b : int or float, optional The blue component. This parameter is ignored if `rgb` is a full color specification. Returns ------- tuple Three RGB color components in integer format, each in the range of 0-255. Examples -------- >>> rgb_to_rgb((255, 0.0, 0.0)) (255, 0, 0) >>> rgb_to_rgb((255, 0, 0)) (255, 0, 0) >>> rgb_to_rgb((1.0, 0, 0)) (255, 0, 0) >>> rgb_to_rgb((1, 0, 0)) (1, 0, 0) """ if g is None and b is None: r, g, b = rgb else: r = rgb r = max(0, min(r, 255)) g = max(0, min(g, 255)) b = max(0, min(b, 255)) if any(isinstance(c, float) for c in (r, g, b)) and all(c <= 1.0 for c in (r, g, b)): r = r * 255.0 g = g * 255.0 b = b * 255.0 if all(c > 0.0 and c < 1.0 for c in (r, g, b)): r = r * 255.0 g = g * 255.0 b = b * 255.0 return int(r), int(g), int(b)
[docs]def rgb_to_hex(rgb, g=None, b=None): """Convert an RGB color specification to HEX. Parameters ---------- rgb : int or float or tuple or list A full RGB color specification, or an integer or a float representing the value of the red component. g : int or float, optional The green component. This parameter is ignored if `rgb` is a full color specification. b : int or float, optional The blue component. This parameter is ignored if `rgb` is a full color specification. Returns ------- str The corresponding HEX color. Examples -------- >>> rgb_to_hex((255, 0.0, 0.0)) '#ff0000' >>> rgb_to_hex((255, 0, 0)) '#ff0000' >>> rgb_to_hex((1.0, 0, 0)) '#ff0000' >>> rgb_to_hex((1, 0, 0)) '#010000' """ r, g, b = rgb_to_rgb(rgb, g=g, b=b) return '#{0:02x}{1:02x}{2:02x}'.format(r, g, b)
def hex_to_rgb(value, normalize=False): """Convert a HEX color to the corresponding RGB format. Parameters ---------- value : str A HEX color. normalize : bool, optional Normalize the RGB components if true. Returns ------- tuple The RGB color. Examples -------- >>> hex_to_rgb('#ff0000') (255, 0, 0) >>> hex_to_rgb('#ff0000', normalize=True) (1.0, 0.0, 0.0) """ value = value.lstrip('#').lower() r = HEX_DEC[value[0:2]] g = HEX_DEC[value[2:4]] b = HEX_DEC[value[4:6]] if normalize: return r / 255.0, g / 255.0, b / 255.0 return r, g, b
[docs]def color_to_rgb(color, normalize=False): """Convert a HEX or RGB color to RGB. Parameters ---------- color : str or tuple or float The color. normalize : bool, optional If true, normalize the resulting RGB color components. Returns ------- tuple The corresponding RGB color specification. Examples -------- >>> color_to_rgb('#ff0000') (255, 0, 0) >>> color_to_rgb('#ff0000', normalize=True) (1.0, 0.0, 0.0) >>> color_to_rgb(1.0, normalize=True) (1.0, 0.0, 0.0) >>> color_to_rgb(1.0) (255, 0, 0) >>> color_to_rgb((255, 0, 0)) (255, 0, 0) >>> color_to_rgb((255, 0, 0), normalize=True) (1.0, 0.0, 0.0) """ if isinstance(color, basestring): r, g, b = hex_to_rgb(color) elif isinstance(color, float): r, g, b = i_to_rgb(color) else: r, g, b = color if not normalize: return r, g, b if isinstance(r, float): return r, g, b return r / 255., g / 255., b / 255.
[docs]def color_to_colordict(color, keys, default=None, colorformat='rgb', normalize=False): """Convert a color specification to a dict of colors. Parameters ---------- color : str or tuple or list or dict The base color specification. This can be a single color (as HEX or RGB), a list of colors, or a dict of colors. keys : list The keys of the color dict. default : str or tuple, optional A valid color specification (HEX or RGB). colorformat : {'hex', 'rgb'}, optional The format of the colors in the color dict. Default is `'rgb'`. normalize : bool, optional Normalize the color components, if true and `colorformat` is `'rgb'`. Returns ------- dict A dictionary mapping the provided keys to the provided color(s). Raises ------ Exception If the value of `color`, or the value of `colorformat` is not valid. Examples -------- >>> color_to_colordict('#ff0000', [0, 1, 2]) {0: '#ff0000', 1: '#ff0000', 2: '#ff0000'} >>> color_to_colordict('#ff0000', [0, 1, 2], colorformat='rgb') {0: (255, 0, 0), 1: (255, 0, 0), 2: (255, 0, 0)} >>> color_to_colordict('#ff0000', [0, 1, 2], colorformat='rgb', normalize=True) {0: (1.0, 0.0, 0.0), 1: (1.0, 0.0, 0.0), 2: (1.0, 0.0, 0.0)} """ color = color or default if color is None: return {key: None for key in keys} # if input is hex if isinstance(color, basestring): # and output should be rgb if colorformat == 'rgb': color = hex_to_rgb(color, normalize=normalize) return {key: color for key in keys} # if input is rgb if isinstance(color, (tuple, list)) and len(color) == 3: # and output should be hex if colorformat == 'hex': color = rgb_to_hex(color, normalize=normalize) # and output should be rgb # else: # color = rgb_to_rgb(color, normalize=normalize) return {key: color for key in keys} if isinstance(color, dict): for k, c in color.items(): # if input is hex if isinstance(c, basestring): # and output should be rgb if colorformat == 'rgb': color[k] = hex_to_rgb(c, normalize=normalize) # if input is rgb elif isinstance(c, (tuple, list)) and len(c) == 3: # and output should be hex if colorformat == 'hex': color[k] = rgb_to_hex(c, normalize=normalize) # and output should be rgb # else: # color[k] = rgb_to_rgb(c, normalize=normalize) return {key: (default if key not in color else color[key]) for key in keys} raise Exception('This is not a valid color format: {0}'.format(type(color)))
def is_color_light(color): r"""Is a color 'light'. Parameters ---------- color: str or tuple The color specification in HEX or RGB format. Returns ------- bool True, if the color is light. False, otherwise. Examples -------- >>> Notes ----- A color is considered 'light' if the following is True for normalized RGB components: .. math:: 0.2126 \frac{r + 0.055}{1.055}^2.4 + 0.7152 \frac{g + 0.055}{1.055}^2.4 + 0.0722 \frac{b + 0.055}{1.055}^2.4 > 0.179 """ if is_color_hex(color): rgb = hex_to_rgb(color) else: rgb = color r, g, b = rgb_to_rgb(rgb) r = r / 255.0 g = g / 255.0 b = b / 255.0 r = ((r + 0.055) / 1.055) ** 2.4 g = ((g + 0.055) / 1.055) ** 2.4 b = ((b + 0.055) / 1.055) ** 2.4 L = 0.2126 * r + 0.7152 * g + 0.0722 * b return L > 0.179 # ============================================================================== # Main # ============================================================================== if __name__ == '__main__': import doctest doctest.testmod(globs=globals())