Source code for pigment

"""Python utilities for colors"""

__version__ = "0.5.0"

from colorsys import hls_to_rgb, hsv_to_rgb, rgb_to_hls, rgb_to_hsv
from math import sqrt
from random import randint

from averager import average

from .exceptions import InvalidRGBValue, WrongLengthError
from .vars import CSS_COLORS


[docs]def normalize_hex(hex_code: str) -> str: """Normalizes a hex color code Removes the leading ``#`` if there is one, expands 3-character hex codes, and lowercases the hex code Args: hex_code (:class:`str`): A hex code to normalize Returns: :class:`str`: The normalized hex code Raises: :exception:`~pigment.exceptions.WrongLengthError`: The provided hex code had the wrong length """ hex_code = hex_code.lstrip("#").lower() if len(hex_code) == 3: hex_code = "".join(c * 2 for c in hex_code) if len(hex_code) != 6: raise WrongLengthError("hex_code") return hex_code
[docs]class Color: """Represents a color Args: red (:class:`int`): The color's red RGB component (0-255) green (:class:`int`): The color's green RGB component (0-255) blue (:class:`int`): The color's blue RGB component (0-255) Note: The :attr:`~cmyk`, :attr:`~hex_code`, :attr:`~hls`, :attr:`~hsv`, and :attr:`~rgb` properties can be set to update the color object. """ def __init__(self, red: int, green: int, blue: int): self.rgb = (red, green, blue) def __repr__(self): return "<pigment.Color r=%r g=%r b=%r>" % self.rgb def __eq__(self, other): if isinstance(other, Color): return self.rgb == other.rgb return self.rgb == other @property def rgb(self): """The color as an RGB tuple * Red (0-255) * Green (0-255) * Blue (0-255) """ return self._rgb @rgb.setter def rgb(self, value): if len(value) != 3: raise WrongLengthError() for c in value: if c not in range(256): raise InvalidRGBValue() self._rgb = value @property def hex_code(self): """The color's hex code""" return "%02x%02x%02x" % (self.rgb[0], self.rgb[1], self.rgb[2]) @hex_code.setter def hex_code(self, value): self.rgb = tuple( int(normalize_hex(value)[i : i + 2], 16) for i in (0, 2, 4) ) @property def hsv(self): """The color as an HSV tuple * Hue: color (0-360) * Saturation: amount of gray vs. color (0-100) * Value: amount of black vs. color (0-100) """ res = rgb_to_hsv( self.rgb[0] / 255, self.rgb[1] / 255, self.rgb[2] / 255 ) return ( round(res[0] * 360), round(res[1] * 100), round(res[2] * 100), ) @hsv.setter def hsv(self, value): self.rgb = tuple( round(x * 255) for x in hsv_to_rgb(value[0] / 360, value[1] / 100, value[2] / 100) ) @property def hue(self): """The color's hue (0-360)""" return self.hsv[0] @hue.setter def hue(self, value): self.hsv = (value, *self.hsv[1:]) @property def hls(self): """The color as an HLS tuple * Hue: color (0-360) * Lightness: amount of white vs. color (0-100) * Saturation: amount of gray vs. color (0-100) """ res = rgb_to_hls( self.rgb[0] / 255, self.rgb[1] / 255, self.rgb[2] / 255 ) return ( round(res[0] * 360), round(res[1] * 100), round(res[2] * 100), ) @hls.setter def hls(self, value): self.rgb = tuple( round(x * 255) for x in hls_to_rgb(value[0] / 360, value[1] / 100, value[2] / 100) ) @property def cmyk(self): """The color as a CMYK tuple * Cyan (0-100) * Magenta (0-100) * Yellow (0-100) * Key/Black (0-100) """ if self.rgb == (0, 0, 0): return (0, 0, 0, 100) c = 1 - self.rgb[0] / 255 m = 1 - self.rgb[1] / 255 y = 1 - self.rgb[2] / 255 min_cmy = min(c, m, y) c = (c - min_cmy) / (1 - min_cmy) m = (m - min_cmy) / (1 - min_cmy) y = (y - min_cmy) / (1 - min_cmy) k = min_cmy return (round(c * 100), round(m * 100), round(y * 100), round(k * 100)) @cmyk.setter def cmyk(self, value): cyan, magenta, yellow, key = value self.rgb = ( round(255 * (1 - cyan / 100) * (1 - key / 100)), round(255 * (1 - magenta / 100) * (1 - key / 100)), round(255 * (1 - yellow / 100) * (1 - key / 100)), )
[docs] @classmethod def from_css_name(cls, css_color: str): """Gets a color from a CSS color name Args: css_color (:class:`str`): The name of the CSS color Returns: :class:`~pigment.Color` """ return Color(*CSS_COLORS[css_color])
[docs] @classmethod def random( cls, red: tuple = (0, 255), green: tuple = (0, 255), blue: tuple = (0, 255), ): """Generates a random color This works by generating random red, green, and blue values using :func:`random.randint` from the standard library using the min/max values specified if any Args: red (:class:`tuple`): The two arguments to pass for the red value green (:class:`tuple`): The two arguments to pass for the green value blue (:class:`tuple`): The two arguments to pass for the blue value Returns: :class:`~pigment.Color` """ return Color(randint(*red), randint(*green), randint(*blue))
[docs]def blend(color1: Color, color2: Color) -> Color: """Blends two colors together Args: color1 (:class:`~pigment.Color`): The first color color2 (:class:`~pigment.Color`): The second color Returns: :class:`~pigment.Color` """ colors = (color1.rgb, color2.rgb) return Color( *(round(sqrt(average([c[i] ** 2 for c in colors]))) for i in range(3)) )