Source code for ooodev.utils.color

# coding: utf-8
"""
Various color conversions utilities.
"""
from __future__ import annotations
import math
import colorsys
from typing import Union, NamedTuple, overload, NewType

from ooodev.utils import gen_util as mGenUtil
import random

# ref: https://gist.github.com/mathebox/e0805f72e7db3269ec22

# see Also: https://github.com/LibreOffice/core/blob/f4a568fc0553603fbf05477e0942af4e8466fba0/oox/source/drawingml/color.cxx

MAX_COLOR = 255
"""Max Color Value"""
MIN_COLOR = 0
"""Min Color Value"""

Color = NewType("Color", int)
"""Color Type. Int RGB Value"""


[docs]class StandardColor(NamedTuple): """ Standard palette Colors .. versionchanged:: 0.10.4 Added ``AUTO_COLOR`` """ # Standard Palette WHITE = Color(0xFFFFFF) BLACK = Color(0) GRAY = Color(0x808080) GRAY_LIGHT1 = Color(0x999999) GRAY_LIGHT2 = Color(0xB2B2B2) GRAY_LIGHT3 = Color(0xCCCCCC) GRAY_LIGHT4 = Color(0xDDDDDD) GRAY_LIGHT5 = Color(0xEEEEEE) GRAY_DARK1 = Color(0x666666) GRAY_DARK2 = Color(0x333333) GRAY_DARK3 = Color(0x1C1C1C) GRAY_DARK4 = Color(0x111111) YELLOW = Color(0xFFFF00) YELLOW_LIGHT1 = Color(0xFFFF38) YELLOW_LIGHT2 = Color(0xFFFF6D) YELLOW_LIGHT3 = Color(0xFFFFA6) YELLOW_LIGHT4 = Color(0xFFFFD7) YELLOW_DARK1 = Color(0xE6E905) YELLOW_DARK2 = Color(0xACB20C) YELLOW_DARK3 = Color(0x706E0C) YELLOW_DARK4 = Color(0x443205) GOLD = Color(0xFFBF00) GOLD_LIGHT1 = Color(0xFFD428) GOLD_LIGHT2 = Color(0xFFDE59) GOLD_LIGHT3 = Color(0xFFE994) GOLD_LIGHT4 = Color(0xFFF5CE) GOLD_DARK1 = Color(0xE8A202) GOLD_DARK2 = Color(0xB47804) GOLD_DARK3 = Color(0x784B04) GOLD_DARK4 = Color(0x472702) ORANGE = Color(0xFF8000) ORANGE_LIGHT1 = Color(0xFF860D) ORANGE_LIGHT2 = Color(0xFF972F) ORANGE_LIGHT3 = Color(0xFFB66C) ORANGE_LIGHT4 = Color(0xFFDBB6) ORANGE_DARK1 = Color(0xEA7500) ORANGE_DARK2 = Color(0xB85C00) ORANGE_DARK3 = Color(0x7B3D00) ORANGE_DARK4 = Color(0x492300) BRICK = Color(0xFF4000) BRICK_LIGHT1 = Color(0xFF5429) BRICK_LIGHT2 = Color(0xFF7B59) BRICK_LIGHT3 = Color(0xFFAA95) BRICK_LIGHT4 = Color(0xFFD8CE) BRICK_DARK1 = Color(0xED4C05) BRICK_DARK2 = Color(0xBE480A) BRICK_DARK3 = Color(0x813709) BRICK_DARK4 = Color(0x4B2204) RED = Color(0xFF0000) RED_LIGHT1 = Color(0xFF3838) RED_LIGHT2 = Color(0xFF6D6D) RED_LIGHT3 = Color(0xFFA6A6) RED_LIGHT4 = Color(0xFFD7D7) RED_DARK1 = Color(0xF10D0C) RED_DARK2 = Color(0xC9211E) RED_DARK3 = Color(0x8D281E) RED_DARK4 = Color(0x50200C) MAGENTA = Color(0xBF0041) MAGENTA_LIGHT1 = Color(0xD62E4E) MAGENTA_LIGHT2 = Color(0xE16173) MAGENTA_LIGHT3 = Color(0xEC9BA4) MAGENTA_LIGHT4 = Color(0xF7D1D5) MAGENTA_DARK1 = Color(0xA7074B) MAGENTA_DARK2 = Color(0x861141) MAGENTA_DARK3 = Color(0x611729) MAGENTA_DARK4 = Color(0x41190D) PURPLE = Color(0x800080) PURPLE_LIGHT1 = Color(0x8D1D75) PURPLE_LIGHT2 = Color(0xA1467E) PURPLE_LIGHT3 = Color(0xBF819E) PURPLE_LIGHT4 = Color(0xE0C2CD) PURPLE_DARK1 = Color(0x780373) PURPLE_DARK2 = Color(0x650953) PURPLE_DARK3 = Color(0x4E102D) PURPLE_DARK4 = Color(0x3B160E) INDIGO = Color(0x55308D) INDIGO_LIGHT1 = Color(0x6B5E9B) INDIGO_LIGHT2 = Color(0x8E86AE) INDIGO_LIGHT3 = Color(0xB7B3CA) INDIGO_LIGHT4 = Color(0xDEDCE6) INDIGO_DARK1 = Color(0x5B277D) INDIGO_DARK2 = Color(0x55215B) INDIGO_DARK3 = Color(0x481D32) INDIGO_DARK4 = Color(0x3A1A0F) BLUE = Color(0x2A6099) BLUE_LIGHT1 = Color(0x5983B0) BLUE_LIGHT2 = Color(0x729FCF) BLUE_LIGHT3 = Color(0xB4C7DC) BLUE_LIGHT4 = Color(0xDEE6EF) BLUE_DARK1 = Color(0x3465A4) BLUE_DARK2 = Color(0x355269) BLUE_DARK3 = Color(0x383D3C) BLUE_DARK4 = Color(0x362413) TEAL = Color(0x158466) TEAL_LIGHT1 = Color(0x50938A) TEAL_LIGHT2 = Color(0x81ACA6) TEAL_LIGHT3 = Color(0xB3CAC7) TEAL_LIGHT4 = Color(0xDEE7E5) TEAL_DARK1 = Color(0x168253) TEAL_DARK2 = Color(0x1E6A39) TEAL_DARK3 = Color(0x28471F) TEAL_DARK4 = Color(0x302709) GREEN = Color(0x00A933) GREEN_LIGHT1 = Color(0x3FAF46) GREEN_LIGHT2 = Color(0x77BC65) GREEN_LIGHT3 = Color(0xAFD095) GREEN_LIGHT4 = Color(0xDDE8CB) GREEN_DARK1 = Color(0x069A2E) GREEN_DARK2 = Color(0x127622) GREEN_DARK3 = Color(0x224B12) GREEN_DARK4 = Color(0x2E2706) LIME = Color(0x81D41A) LIME_LIGHT1 = Color(0xBBE33D) LIME_LIGHT2 = Color(0xD4EA6B) LIME_LIGHT3 = Color(0xE8F2A1) LIME_LIGHT4 = Color(0xF6F9D4) LIME_DARK1 = Color(0x5EB91E) LIME_DARK2 = Color(0x468A1A) LIME_DARK3 = Color(0x395511) LIME_DARK4 = Color(0x342A06) # Defaults DEFAULT_BLUE = BLUE_LIGHT2 # LibreOffice Default Shape background color AUTO_COLOR = Color(-1) """ Automatic Color Value. In most cases, LibreOffice API will use -1 for automatic or no color. This is the same as the value of `Color(-1)`. """
[docs] @staticmethod def get_random_color() -> Color: """Gets a random Standard Palette color""" attrs = [ x for x in dir(StandardColor) if x.isupper() and not x.startswith("DEFAULT") and not x.startswith("AUTO") ] i = random.randrange(start=0, stop=len(attrs) - 1) return getattr(StandardColor, attrs[i])
[docs]class CommonColor(NamedTuple): """ Named Colors. See Also: - `Wikipedia Web colors <https://en.wikipedia.org/wiki/Web_colors>`_ - `Hex Color Chart <https://www.quackit.com/css/color/charts/hex_color_chart.cfm>`_ Example: .. code-block:: python def set_chart(sheet: XSpreadsheet, range_addr: CellRangeAddress) -> None: chart = Chart2.insert_chart(sheet, range_addr, "A22", 20, 11, Chart2.ChartLookup.Column.TEMPLATE_PERCENT.COLUMN_DEEP_3D) Chart2.set_background_colors(chart, CommonColor.LIGHT_GREEN, CommonColor.BROWN) .. versionchanged:: 0.10.4 Added ``AUTO_COLOR`` """ # https://en.wikipedia.org/wiki/Web_colors # https://www.quackit.com/css/color/charts/hex_color_chart.cfm # some hex values for commonly used colors # Pink colors DEEP_PINK = Color(0xFF1493) HOT_PINK = Color(0xFF69B4) LIGHT_PINK = Color(0xFFB6C1) MEDIUM_VIOLET_RED = Color(0xC71585) PALE_VIOLET_RED = Color(0xDB7093) PINK = Color(0xFFC0CB) # red colors CRIMSON = Color(0xDC143C) DARK_RED = Color(0x8B0000) DARK_SALMON = Color(0xE9967A) FIRE_BRICK = Color(0xB22222) INDIAN_RED = Color(0xCD5C5C) LIGHT_CORAL = Color(0xF08080) LIGHT_SALMON = Color(0xFFA07A) RED = Color(0xFF0000) SALMON = Color(0xFA8072) # Oranges CORAL = Color(0xFF7F50) DARK_ORANGE = Color(0xFF8C00) ORANGE = Color(0xFFA500) ORANGE_RED = Color(0xFF4500) TOMATO = Color(0xFF6347) # Yellow colors DARK_KHAKI = Color(0xBDB76B) GOLD = Color(0xFFD700) KHAKI = Color(0xF0E68C) LEMON_CHIFFON = Color(0xFFFACD) LIGHT_GOLDENROD_YELLOW = Color(0xFAFAD2) LIGHT_YELLOW = Color(0xFFFFE0) MOCCASIN = Color(0xFFE4B5) PALE_GOLDENROD = Color(0xEEE8AA) PAPAYA_WHIP = Color(0xFFEFD5) PEACH_PUFF = Color(0xFFDAB9) YELLOW = Color(0xFFFF00) # Purples BLUE_VIOLET = Color(0x8A2BE2) DARK_MAGENTA = Color(0x8B008B) DARK_ORCHID = Color(0x9932CC) DARK_SLATE_BLUE = Color(0x483D8B) DARK_VIOLET = Color(0x9400D3) FUCHSIA = Color(0xFF00FF) INDIGO = Color(0x4B0082) LAVENDER = Color(0xE6E6FA) MAGENTA = Color(0xFF00FF) MEDIUM_ORCHID = Color(0xBA55D3) MEDIUM_PURPLE = Color(0x9370DB) MEDIUM_SLATE_BLUE = Color(0x7B68EE) ORCHID = Color(0xDA70D6) PLUM = Color(0xDDA0DD) PURPLE = Color(0x800080) REBECCA_PURPLE = Color(0x663399) SLATE_BLUE = Color(0x6A5ACD) THISTLE = Color(0xD8BFD8) VIOLET = Color(0xEE82EE) # Greens CHARTREUSE = Color(0x7FFF00) DARK_CYAN = Color(0x008B8B) DARK_GREEN = Color(0x006400) DARK_OLIVE_GREEN = Color(0x556B2F) DARK_SEA_GREEN = Color(0x8FBC8F) FOREST_GREEN = Color(0x228B22) GREEN = Color(0x008000) GREEN_YELLOW = Color(0xADFF2F) LAWN_GREEN = Color(0x7CFC00) LIGHT_GREEN = Color(0x90EE90) LIGHT_SEA_GREEN = Color(0x20B2AA) LIME = Color(0x00FF00) LIME_GREEN = Color(0x32CD32) MEDIUM_AQUAMARINE = Color(0x66CDAA) MEDIUM_SEA_GREEN = Color(0x3CB371) MEDIUM_SPRING_GREEN = Color(0x00FA9A) OLIVE = Color(0x808000) OLIVE_DRAB = Color(0x6B8E23) PALE_GREEN = Color(0x98FB98) SEA_GREEN = Color(0x2E8B57) SPRING_GREEN = Color(0x00FF7F) TEAL = Color(0x008080) YELLOW_GREEN = Color(0x9ACD32) # Blues/Cyans AQUA = Color(0x00FFFF) AQUAMARINE = Color(0x7FFFD4) BLUE = Color(0x0000FF) CADET_BLUE = Color(0x5F9EA0) CORNFLOWER_BLUE = Color(0x6495ED) CYAN = Color(0x00FFFF) DARK_BLUE = Color(0x00008B) DARK_TURQUOISE = Color(0x00CED1) DEEP_SKY_BLUE = Color(0x00BFFF) DODGER_BLUE = Color(0x1E90FF) LIGHT_BLUE = Color(0xADD8E6) LIGHT_CYAN = Color(0xE0FFFF) LIGHT_SKY_BLUE = Color(0x87CEFA) LIGHT_STEEL_BLUE = Color(0xB0C4DE) MEDIUM_BLUE = Color(0x0000CD) MEDIUM_TURQUOISE = Color(0x48D1CC) MIDNIGHT_BLUE = Color(0x191970) NAVY = Color(0x000080) PALE_TURQUOISE = Color(0xAFEEEE) POWDER_BLUE = Color(0xB0E0E6) ROYAL_BLUE = Color(0x4169E1) SKY_BLUE = Color(0x87CEEB) STEEL_BLUE = Color(0x4682B4) TURQUOISE = Color(0x40E0D0) # Browns BISQUE = Color(0xFFE4C4) BLANCHED_ALMOND = Color(0xFFEBCD) BROWN = Color(0xA52A2A) BURLY_WOOD = Color(0xDEB887) CHOCOLATE = Color(0xD2691E) CORNSILK = Color(0xFFF8DC) DARK_GOLDENROD = Color(0xB8860B) GOLDENROD = Color(0xDAA520) MAROON = Color(0x800000) NAVAJO_WHITE = Color(0xFFDEAD) PERU = Color(0xCD853F) ROSY_BROWN = Color(0xBC8F8F) SADDLE_BROWN = Color(0x8B4513) SANDY_BROWN = Color(0xF4A460) SIENNA = Color(0xA0522D) TAN = Color(0xD2B48C) WHEAT = Color(0xF5DEB3) # Whites ALICE_BLUE = Color(0xF0F8FF) ANTIQUE_WHITE = Color(0xFAEBD7) AZURE = Color(0xF0FFFF) BEIGE = Color(0xF5F5DC) FLORAL_WHITE = Color(0xFFFAF0) GHOST_WHITE = Color(0xF8F8FF) HONEYDEW = Color(0xF0FFF0) IVORY = Color(0xFFFFF0) LAVENDER_BLUSH = Color(0xFFF0F5) LINEN = Color(0xFAF0E6) MINT_CREAM = Color(0xF5FFFA) MISTY_ROSE = Color(0xFFE4E1) OLD_LACE = Color(0xFDF5E6) SEASHELL = Color(0xFFF5EE) SNOW = Color(0xFFFAFA) WHITE = Color(0xFFFFFF) WHITE_SMOKE = Color(0xF5F5F5) # Greys BLACK = Color(0x000000) DARK_GRAY = Color(0xA9A9A9) DARK_GREY = Color(0xA9A9A9) DARK_SLATE_GRAY = Color(0x2F4F4F) DARK_SLATE_GREY = Color(0x2F4F4F) DIM_GRAY = Color(0x696969) DIM_GREY = Color(0x696969) GAINSBORO = Color(0xDCDCDC) GRAY = Color(0x808080) GREY = Color(0x808080) LIGHT_GRAY = Color(0xD3D3D3) LIGHT_GREY = Color(0xD3D3D3) LIGHT_SLATE_GRAY = Color(0x778899) LIGHT_SLATE_GREY = Color(0x778899) SILVER = Color(0xC0C0C0) SLATE_GRAY = Color(0x708090) SLATE_GREY = Color(0x708090) # other PALE_BLUE = Color(0xD6EBFF) # Defaults DEFAULT_BLUE = StandardColor.BLUE_LIGHT2 # LibreOffice Default Shape background color AUTO_COLOR = Color(-1) """ Automatic Color Value. In most cases, LibreOffice API will use -1 for automatic or no color. This is the same as the value of `Color(-1)`. """
[docs] @staticmethod def get_random_color() -> Color: """Gets a random common color""" attrs = [ x for x in dir(CommonColor) if x.isupper() and not x.startswith("DEFAULT") and not x.startswith("AUTO") ] i = random.randrange(start=0, stop=len(attrs) - 1) return getattr(CommonColor, attrs[i])
[docs] @classmethod def from_str(cls, str_color: str) -> Color: """ Convert string value to a Color value ``str_color`` can be a hex value, a integer value as string or any named value in ``CommonColor`` ``str_color`` can contain single spaces or ``_`` or ``-``. ``MEDIUM_SEA_GREEN``, ``MEDIUM-SEA-GREEN``, ``MEDIUM SEA GREEN``, ``medium sea green``, ``mediumSeaGreen`` , ``MediumSeaGreen`` are all equivalent. Args: str_color (str): Value to convert. Case insensitive Raises: ValueError: If unable to convert. Returns: ~ooodev.utils.color.Color: Convert value as Color """ if not str_color: raise ValueError("str_color contains no value to convert to Color") if str_color.isalnum(): try: # try base 10 i = int(str_color) except ValueError: # try hex try: i = int(str_color, 16) except ValueError: pass else: return Color(abs(i)) else: return Color(abs(i)) c_str = mGenUtil.Util.to_single_space(str_color).replace("-", "_").replace(" ", "_") if "_" in c_str: c_str = c_str.upper() else: # handle pascalCase and CamelCase words c_str = mGenUtil.Util.to_snake_case_upper(c_str) try: return Color(int(getattr(cls, c_str))) except AttributeError as e: raise ValueError("str_color is not a valid color") from e
[docs]class RGBA(NamedTuple): red: int """Red color as int""" green: int """Green color as int""" blue: int """Blue color as int""" alpha: int
[docs] @staticmethod def from_int(value: int) -> RGBA: """ Gets RGAB from int value Args: value (int): Value that represents a RGBA Returns: RGBA: Instance converted from integer. """ blue = value & 255 green = (value >> 8) & 255 red = (value >> 16) & 255 alpha = (value >> 24) & 255 return RGBA(red=red, green=green, blue=blue, alpha=alpha)
[docs] def to_int(self) -> int: """ Gets integer representing RGBA value """ return (self.alpha << 24) + (self.red << 16) + (self.green << 8) + self.blue
def __int__(self) -> int: return self.to_int()
[docs]class RGB(NamedTuple): red: int """Red color as int""" green: int """Green color as int""" blue: int """Blue color as int"""
[docs] def to_int(self) -> int: """ Gets instance as rgb int Returns: int: red, green, blue encoded as int. """ return rgb_to_int(self)
[docs] def to_color(self) -> Color: """ Gets instance as rgb Color Returns: ~ooodev.utils.color.Color: red, green, blue encoded as Color. """ return Color(self.to_int())
[docs] def to_hex(self) -> str: """ Gets instance as hex string in format of ``ff3322`` Returns: str: red, green, blue encoded as hex string. """ return rgb_to_hex(self)
[docs] def isvalid(self) -> bool: """ Gets if the value of red, green and blue are valid. Returns: bool: ``True`` if valid; Otherwise, ``False`` """ # sourcery skip: inline-immediately-returned-variable, use-any, use-next result = True for i in self: if i < MIN_COLOR or i > MAX_COLOR: result = False break return result
[docs] @staticmethod def from_int(rgb_int: int) -> "RGB": """ Gets a color instance from int that represents a rgb color. Args: rgb_int (int): int that contains rgb color data. Returns: RGB: Color information as RGB struct. """ return int_to_rgb(rgb_int=rgb_int)
[docs] @classmethod def from_color(cls, c: Color) -> "RGB": """ Gets a color instance from input color that represents a rgb color. Args: c (~ooodev.utils.color.Color): Color that contains rgb color data. Returns: RGB: Color information as RGB struct. """ return cls.from_int(int(c))
[docs] @staticmethod def from_hex(rgb_hex: str) -> "RGB": """ Gets a color instance from int that represents a rgb color. Args: rgb_int (int): int that contains rgb color data. Returns: RGB: Color information as RGB struct. """ return int_to_rgb(rgb_int=int(rgb_hex, 16))
[docs] def get_luminance(self) -> float: """ Gets luminance value for current color Returns: float: luminance value """ # http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef RsRGB = self.red / 255 GsRGB = self.green / 255 BsRGB = self.blue / 255 if RsRGB <= 0.03928: R = RsRGB / 12.92 else: R = math.pow(((RsRGB + 0.055) / 1.055), 2.4) if GsRGB <= 0.03928: G = GsRGB / 12.92 else: G = math.pow(((GsRGB + 0.055) / 1.055), 2.4) if BsRGB <= 0.03928: B = BsRGB / 12.92 else: B = math.pow(((BsRGB + 0.055) / 1.055), 2.4) return (0.2126 * R) + (0.7152 * G) + (0.0722 * B)
[docs] def get_brightness(self) -> int: """ Gets brightness from 0 (dark) to 255 (light) Returns: int: brightness level """ # http://www.w3.org/TR/AERT#color-contrast return round(((self.red * 299) + (self.green * 587) + (self.blue * 114)) / 1000)
[docs] def is_dark(self) -> bool: """ Get is current color is dark. If color has a brightness less than ``128`` it is considered dark. Returns: bool: True if color is dark; Otherwise, False """ return self.get_brightness() < 128
[docs] def is_light(self) -> bool: """ Get is current color is light. If color has a brightness Greater than ``128`` it is considered light. Returns: bool: True if color is light; Otherwise, False """ return not self.is_dark()
def __str__(self) -> str: return f"rgb({round(self.red)}, {round(self.green)}, {round(self.blue)})" def __int__(self) -> int: return self.to_int()
[docs]class HSL(NamedTuple): hue: float saturation: float lightness: float def __str__(self) -> str: # sourcery skip: use-fstring-for-concatenation return "hls(" + f"{self.hue:.6f}" + ", " + f"{self.saturation:.6f}" + ", " + f"{self.lightness:.6f}" + ")"
[docs]class HSV(NamedTuple): hue: float saturation: float value: float def __str__(self) -> str: # sourcery skip: use-fstring-for-concatenation return "hlv(" + f"{self.hue:.6f}" + ", " + f"{self.saturation:.6f}" + ", " + f"{self.value:.6f}" + ")"
[docs]def clamp(value: float, min_value: float, max_value: float) -> float: """ Constrains a value to a min and an max value Args: value (float): Value to constrain min_value (float): Min allowed value max_value (float): Max allowed value Returns: float: constrained value if value is outside of min_value or max_value; Otherwise, value. """ return max(min_value, min(max_value, value))
[docs]def clamp01(value: float) -> float: """ Gets a value that is constrained between 0.0 and 1.0 Args: value (float): Value Returns: float: A value that is no less than 0.0 and no greater then 1.0 """ return clamp(value, 0.0, 1.0)
[docs]def hue_to_rgb(h: float) -> RGB: """ Converts a hue to instance of red, green, blue Args: h (float): hue to convert Returns: rgb: instance containing red, green, blue """ r = abs(h * 6.0 - 3.0) - 1.0 g = 2.0 - abs(h * 6.0 - 2.0) b = 2.0 - abs(h * 6.0 - 4.0) return RGB(red=round(clamp01(r)), green=round(clamp01(g)), blue=round(clamp01(b)))
[docs]def hsl_to_rgb(c: HSL) -> RGB: """ Converts hue, saturation, lightness to red, green, blue Args: c (hsv): instance containing hue, saturation, lightness Returns: rgb: instance containing red, green, blue """ hue = c.hue lightness = c.lightness saturation = c.saturation t = colorsys.hls_to_rgb(h=hue, l=lightness, s=saturation) return RGB( red=round(t[0] * MAX_COLOR), green=round(t[1] * MAX_COLOR), blue=round(t[2] * MAX_COLOR), )
[docs]def rgb_to_hsv(c: RGB) -> HSV: """ Converts red, green, blue to hue, saturation, value Args: c (RGB): instance containing red, green, blue Returns: HSV: instance containing hue, saturation, value """ r = float(c.red / MAX_COLOR) g = float(c.green / MAX_COLOR) b = float(c.blue / MAX_COLOR) t = colorsys.rgb_to_hsv(r=r, g=g, b=b) return HSV(hue=t[0], saturation=t[1], value=t[2])
[docs]def hsv_to_rgb(c: HSV) -> RGB: """ Converts hue, saturation, value to red, green, blue Args: c (hsv): instance containing hue, saturation, value Returns: rgb: instance containing red, green, blue """ h = c.hue s = c.saturation v = c.value t = colorsys.hsv_to_rgb(h=h, s=s, v=v) return RGB( red=round(t[0] * MAX_COLOR), green=round(t[1] * MAX_COLOR), blue=round(t[2] * MAX_COLOR), )
[docs]def rgb_to_hsl(c: RGB) -> HSL: """ Converts red, green, blue to hue, saturation, value Args: c (RGB): instance containing red, green, blue Returns: HSL: instance containing hue, saturation, lightness """ r = float(c.red / MAX_COLOR) g = float(c.green / MAX_COLOR) b = float(c.blue / MAX_COLOR) t = colorsys.rgb_to_hls(r=r, g=g, b=b) return HSL(hue=t[0], saturation=t[2], lightness=t[1])
[docs]def hsv_to_hsl(c: HSV) -> HSL: """ Convert hue, saturation, value to hue, saturation, lightness Args: c (HSV): instance containing hue, saturation, value Returns: HSL: instance containing hue, saturation, lightness """ hue = c.hue saturation = c.saturation value = c.value lightness = 0.5 * value * (2 - saturation) saturation = value * saturation / (1 - math.fabs(2 * lightness - 1)) return HSL(hue, saturation, lightness)
[docs]def hsl_to_hsv(c: HSL) -> HSV: """ Convert hue, saturation, lightness to hue, saturation, value Args: c (HSL): instance containing hue, saturation, lightness Returns: HSV: instance containing hue, saturation, value """ hue = c.hue saturation = c.saturation lightness = c.lightness v = (2 * lightness + saturation * (1 - math.fabs(2 * lightness - 1))) / 2 saturation = 2 * (v - lightness) / v return HSV(hue, saturation, v)
[docs]def rgb_to_hex(rgb: RGB) -> str: """ Converts rgb colors to int Args: rgb (color): Tuple of int with values from 0 to 255 Returns: str: rgb as hex string """ if len(rgb) != 3: raise ValueError("rgb must be a tuple of 3 integers") for i in rgb: if i < 0: raise ValueError("rgb contains a negative value") if i > MAX_COLOR: raise ValueError("rgb contains a value that is greater than MAX_COLOR") return "%02x%02x%02x" % rgb
[docs]def rgb_to_int(rgb: RGB) -> int: """ Converts rgb colors to int Args: rgb (color): Tuple of int with values from 0 to 255 Returns: int: rgb as int """ return int(rgb_to_hex(rgb), 16)
[docs]def int_to_rgb(rgb_int: int) -> RGB: """ Converts an integer that represents a rgb color into rgb object. Args: rgb_int (int): int that represents rgb color Returns: rgb: rgb with red, green and blue properties. """ blue = rgb_int & MAX_COLOR green = (rgb_int >> 8) & MAX_COLOR red = (rgb_int >> 16) & MAX_COLOR return RGB(red, green, blue)
@overload def lighten(rgb_color: int, percent: int | float) -> RGB: ... @overload def lighten(rgb_color: RGB, percent: int | float) -> RGB: ...
[docs]def lighten(rgb_color: Union[RGB, int], percent: int | float) -> RGB: """ Lightens an RGB instance Args: rgb_color (RGB | int): instance containing data percent (Number): Amount between 0 and 100 int lighten rgb by. Raises: ValueError: if percent is out of range Returns: RGB: RGB instance with lightened values applied. """ if percent < 0 or percent > 100: raise ValueError("percent is expected to be between 0 and 100") # https://mdigi.tools/lighten-color # https://pastebin.com/KBAbAPh0 _rgb = int_to_rgb(rgb_color) if isinstance(rgb_color, int) else rgb_color c_hsl = rgb_to_hsl(_rgb) amt = (percent * ((1 - c_hsl.lightness) * 100)) / 100 lightness = c_hsl.lightness lightness += amt / 100 increase = clamp(lightness, 0, 1) c2_hsl = HSL(c_hsl.hue, c_hsl.saturation, increase) return hsl_to_rgb(c2_hsl)
@overload def darken(rgb_color: int, percent: int | float) -> RGB: ... @overload def darken(rgb_color: RGB, percent: int | float) -> RGB: ...
[docs]def darken(rgb_color: Union[RGB, int], percent: int | float) -> RGB: """ Darkens an rgb instance Args: rgb_color (rgb | int): instance containing data percent (Number): Amount between 0 and 100 int darken rgb by. Raises: ValueError: if percent is out of range Returns: rgb: rgb instance with darkened values applied. """ if percent < 0 or percent > 100: raise ValueError("percent is expected to be between 0 and 100") # https://mdigi.tools/lighten-color # https://pastebin.com/KBAbAPh0 _rgb = int_to_rgb(rgb_color) if isinstance(rgb_color, int) else rgb_color c_hsl = rgb_to_hsl(_rgb) amt = (percent * (c_hsl.lightness * 100)) / 100 lightness = c_hsl.lightness lightness -= amt / 100 decrease = clamp(lightness, 0, 1) c2_hsl = HSL(c_hsl.hue, c_hsl.saturation, decrease) return hsl_to_rgb(c2_hsl)
[docs]def get_gray_rgb(percent: int, rgb: RGB | None = None) -> RGB: """ Gets a Gray RGB. The higher the percent the lighter the color. ``100`` percent returns RGB of White color. ``0`` percent return Black color Args: percent (int): Percent from ``0`` to ``100`` rgb (RGB, optional): Optional RGB used for calculations. Raises: ValueError: If percent is out of range. Returns: RGB: RGB representing red, green blue. Note: The returned RGB has all channels are set to the same value. .. versionadded:: 0.9.0 """ # same functionality as see in # https://github.com/LibreOffice/core/blob/7c3ea0abeff6e0cb9e2893cec8ed63025a274117/oox/source/export/drawingml.cxx#L664 if percent < 0 or percent > 100: raise ValueError("percent must br form 0 to 100") if percent == 0: return RGB(0, 0, 0) # black if percent == 100: return RGB(255, 255, 255) # white if rgb is None: # convert from white #ffffff r, g, b = 255, 255, 255 else: r = rgb.red g = rgb.green b = rgb.blue r = 0.2989 * r g = 0.5870 * g b = 0.1140 * b gs = r + g + b gs_per = (percent / 100) * gs if percent > 0 else gs gs_int = round(gs_per) return RGB(red=gs_int, green=gs_int, blue=gs_int)