Source code for gd.color

import colorsys

from gd.text_utils import make_repr
from gd.typing import Any, Dict, Iterator, List, Optional, Tuple, Union

__all__ = ("COLOR_1", "COLOR_2", "Color")

BYTE = 0xFF
SIZE = BYTE.bit_length()
DOUBLE_SIZE = SIZE * 2


def float_to_byte_channel(value: float) -> int:
    return int(value * BYTE)


[docs]class Color: """Represents a Color. .. container:: operations .. describe:: x == y Check if two colors are equal. .. describe:: x != y Check if two colors are not equal. .. describe:: str(x) Return hex of the color, e.g. ``#ffffff``. .. describe:: repr(x) Return representation of the color, useful for debugging. .. describe:: hash(x) Returns ``hash(self.value)``. Attributes ---------- value: :class:`int` The raw integer colour value. """ ID_TO_COLOR = { 0: 0x7DFF00, 1: 0x00FF00, 2: 0x00FF7D, 3: 0x00FFFF, 4: 0x007DFF, 5: 0x0000FF, 6: 0x7D00FF, 7: 0xFF00FF, 8: 0xFF007D, 9: 0xFF0000, 10: 0xFF7D00, 11: 0xFFFF00, 12: 0xFFFFFF, 13: 0xB900FF, 14: 0xFFB900, 15: 0x000000, 16: 0x00C8FF, 17: 0xAFAFAF, 18: 0x5A5A5A, 19: 0xFF7D7D, 20: 0x00AF4B, 21: 0x007D7D, 22: 0x004BAF, 23: 0x4B00AF, 24: 0x7D007D, 25: 0xAF004B, 26: 0xAF4B00, 27: 0x7D7D00, 28: 0x4BAF00, 29: 0xFF4B00, 30: 0x963200, 31: 0x966400, 32: 0x649600, 33: 0x009664, 34: 0x006496, 35: 0x640096, 36: 0x960064, 37: 0x960000, 38: 0x009600, 39: 0x000096, 40: 0x7DFFAF, 41: 0x7D7DAF, } COLOR_TO_ID = {color: id for id, color in ID_TO_COLOR.items()} def __init__(self, value: int = 0) -> None: if not isinstance(value, int): raise TypeError(f"Expected int value, received {self.__class__.__name__!r}.") self.value = value def get_byte(self, byte_index: int) -> int: return (self.value >> (SIZE * byte_index)) & BYTE def __eq__(self, other: Any) -> bool: if not isinstance(other, self.__class__): return NotImplemented return self.value == other.value def __ne__(self, other: Any) -> bool: if not isinstance(other, self.__class__): return NotImplemented return self.value != other.value def __str__(self) -> str: return self.to_hex() def __repr__(self) -> str: info = { "hex": self.to_hex(), "value": self.value, "id": self.id, } return make_repr(self, info) def __hash__(self) -> int: return hash(self.value) def __json__(self) -> Dict[str, Optional[Union[Tuple[int, int, int], int, str]]]: return dict(rgb=self.to_rgb(), hex=self.to_hex(), value=self.value, id=self.id) @property def id(self) -> Optional[int]: """Optional[:class:`int`]: Returns ID that represents position of the color. ``None`` if not a default one. """ return self.COLOR_TO_ID.get(self.value) @property def r(self) -> int: """:class:`int`: Returns the red component of the colour.""" return self.get_byte(2) @property def g(self) -> int: """:class:`int`: Returns the green component of the colour.""" return self.get_byte(1) @property def b(self) -> int: """:class:`int`: Returns the blue component of the colour.""" return self.get_byte(0)
[docs] def to_hex(self) -> str: """:class:`str`: Returns the colour in hex format.""" return f"#{self.value:0>6x}"
[docs] def to_rgb(self) -> Tuple[int, int, int]: """Return a :class:`tuple` representing the color. Returns ------- Tuple[:class:`int`, :class:`int`, :class:`int`] ``(r, g, b)`` :class:`tuple` representing the color. """ return (self.r, self.g, self.b)
[docs] def to_rgba(self, alpha: int = BYTE) -> Tuple[int, int, int, int]: """Same as :meth:`gd.Color.to_rgb`, but contains ``alpha`` component. Parameters ---------- alpha: :class:`int` Value of an alpha channel to use. Defaults to ``255``, meaning full value. Returns ------ Tuple[:class:`int`, :class:`int`, :class:`int`, :class:`int`] ``(r, g, b, a)`` :class:`tuple` representing the color. """ return (self.r, self.g, self.b, alpha & BYTE)
def get_ansi_start(self) -> str: return f"\x1b[38;2;{self.r};{self.g};{self.b}m" def get_ansi_end(self) -> str: return "\x1b[0m"
[docs] def ansi_escape(self, string: Optional[str] = None) -> str: """Color ``string`` using ANSI representation of the color. If not given, :meth:`~gd.Color.to_hex` is used. """ if string is None: string = self.to_hex() return f"{self.get_ansi_start()}{string}{self.get_ansi_end()}"
paint = ansi_escape
[docs] @classmethod def from_hex(cls, hex_str: str) -> "Color": """Constructs :class:`~gd.Color` from hex string, e.g. ``0x7289da`` or ``#000000``.""" return cls(int(hex_str.replace("#", ""), 16))
[docs] @classmethod def from_rgb(cls, r: int, g: int, b: int) -> "Color": """Constructs a :class:`~gd.Color` from an RGB tuple.""" return cls((r << DOUBLE_SIZE) + (g << SIZE) + b)
[docs] @classmethod def from_hsv(cls, h: float, s: float, v: float) -> "Color": """Constructs a :class:`~gd.Color` from an HSV (HSB) tuple.""" rgb = colorsys.hsv_to_rgb(h, s, v) return cls.from_rgb(*map(float_to_byte_channel, rgb))
[docs] @classmethod def from_rgb_string(cls, string: str, delim: str = ",") -> "Color": """Constructs a :class:`~gd.Color` from RGB string, e.g. ``255,255,255``.""" return cls.from_rgb(*map(int, string.split(delim)))
[docs] @classmethod def with_id(cls, id: int, default: Optional["Color"] = None) -> "Color": """Creates a :class:`~gd.Color` with in-game ID of ``id``.""" color = cls.ID_TO_COLOR.get(id) if color is None: if default is None: raise ValueError(f"ID is not present: {id}.") return default return Color(color)
[docs] @classmethod def iter_colors(cls) -> Iterator["Color"]: """Returns an iterator over all in-game colors.""" for value in cls.COLOR_TO_ID: yield cls(value)
[docs] @classmethod def list_colors(cls) -> List["Color"]: """Same as :meth:`~gd.Color.iter_colors`, but returns a list.""" return list(cls.iter_colors())
COLOR_1 = Color.with_id(0) COLOR_2 = Color.with_id(3)