diff --git a/manim/utils/color/core.py b/manim/utils/color/core.py index 7069a80ba5..eac3127225 100644 --- a/manim/utils/color/core.py +++ b/manim/utils/color/core.py @@ -1,10 +1,9 @@ -"""Manim's (internal) color data structure and some utilities for -color conversion. +"""Manim's (internal) color data structure and some utilities for color conversion. -This module contains the implementation of :class:`.ManimColor`, -the data structure internally used to represent colors. +This module contains the implementation of :class:`.ManimColor`, the data structure +internally used to represent colors. -The preferred way of using these colors is by importing their constants from manim: +The preferred way of using these colors is by importing their constants from Manim: .. code-block:: pycon @@ -12,33 +11,54 @@ >>> print(RED) #FC6255 -Note this way uses the name of the colors in UPPERCASE. +Note that this way uses the name of the colors in UPPERCASE. .. note:: - The colors of type "C" have an alias equal to the colorname without a letter, - e.g. GREEN = GREEN_C + The colors with a ``_C`` suffix have an alias equal to the colorname without a + letter. For example, ``GREEN = GREEN_C``. =================== Custom Color Spaces =================== -Hello dear visitor, you seem to be interested in implementing a custom color class for a color space we don't currently support. +Hello, dear visitor. You seem to be interested in implementing a custom color class for +a color space we don't currently support. -The current system is using a few indirections for ensuring a consistent behavior with all other color types in manim. +The current system is using a few indirections for ensuring a consistent behavior with +all other color types in Manim. -To implement a custom color space you must subclass :class:`ManimColor` and implement three important functions +To implement a custom color space, you must subclass :class:`ManimColor` and implement +three important methods: -:attr:`~.ManimColor._internal_value` is an ``@property`` implemented on :class:`ManimColor` with the goal of keeping a consistent internal representation that can be referenced by other functions in :class:`ManimColor`. -The getter should always return a value in the format of ``[r,g,b,a]`` as a numpy array which is in accordance with the type :class:`.ManimColorInternal`. -The setter should always accept a value in the format ``[r,g,b,a]`` which can be converted to whatever attributes you need. -This property acts as a proxy to whatever representation you need in your class. + - :attr:`~.ManimColor._internal_value`: a ``@property`` implemented on + :class:`ManimColor` with the goal of keeping a consistent internal representation + which can be referenced by other functions in :class:`ManimColor`. This property acts + as a proxy to whatever representation you need in your class. -:attr:`~ManimColor._internal_space` this is a readonly ``@property`` implemented on :class:`ManimColor` with the goal of a useful representation that can be used by operators and interpolation and color transform functions. -The only constraints on this value are that it needs to be a numpy array and the last value must be the opacity in a range ``0.0`` to ``1.0``. -Additionally your ``__init__`` must support this format as initialization value without additional parameters to ensure correct functionality of all other methods in :class:`ManimColor`. + - The getter should always return a NumPy array in the format ``[r,g,b,a]``, in + accordance with the type :class:`ManimColorInternal`. -:func:`~ManimColor._from_internal` is a ``@classmethod`` that converts an ``[r,g,b,a]`` value into suitable parameters for your ``__init__`` method and calls the cls parameter. + - The setter should always accept a value in the format ``[r,g,b,a]`` which can be + converted to whatever attributes you need. + + - :attr:`~ManimColor._internal_space`: a read-only ``@property`` implemented on + :class:`ManimColor` with the goal of providing a useful representation which can be + used by operators, interpolation and color transform functions. + + The only constraints on this value are: + + - It must be a NumPy array. + + - The last value must be the opacity in a range ``0.0`` to ``1.0``. + + Additionally, your ``__init__`` must support this format as an initialization value + without additional parameters to ensure correct functionality of all other methods in + :class:`ManimColor`. + + - :meth:`~ManimColor._from_internal`: a ``@classmethod`` which converts an + ``[r,g,b,a]`` value into suitable parameters for your ``__init__`` method and calls + the ``cls`` parameter. """ from __future__ import annotations @@ -49,11 +69,11 @@ import random import re from collections.abc import Sequence -from typing import Any, TypeVar, Union, overload +from typing import TypeVar, Union, overload import numpy as np import numpy.typing as npt -from typing_extensions import Self, TypeAlias, TypeGuard, override +from typing_extensions import Self, TypeAlias, TypeIs, override from manim.typing import ( HSL_Array_Float, @@ -65,6 +85,7 @@ ManimColorDType, ManimColorInternal, ManimFloat, + Point3D, RGB_Array_Float, RGB_Array_Int, RGB_Tuple_Float, @@ -73,6 +94,7 @@ RGBA_Array_Int, RGBA_Tuple_Float, RGBA_Tuple_Int, + Vector3D, ) from ...utils.space_ops import normalize @@ -85,31 +107,35 @@ class ManimColor: """Internal representation of a color. - The ManimColor class is the main class for the representation of a color. - It's internal representation is a 4 element array of floats corresponding - to a [r,g,b,a] value where r,g,b,a can be between 0 to 1. + The :class:`ManimColor` class is the main class for the representation of a color. + Its internal representation is an array of 4 floats corresponding to a ``[r,g,b,a]`` + value where ``r,g,b,a`` can be between 0.0 and 1.0. This is done in order to reduce the amount of color inconsistencies by constantly casting between integers and floats which introduces errors. The class can accept any value of type :class:`ParsableManimColor` i.e. - ManimColor, int, str, RGB_Tuple_Int, RGB_Tuple_Float, RGBA_Tuple_Int, RGBA_Tuple_Float, RGB_Array_Int, - RGB_Array_Float, RGBA_Array_Int, RGBA_Array_Float + ``ManimColor, int, str, RGB_Tuple_Int, RGB_Tuple_Float, RGBA_Tuple_Int, RGBA_Tuple_Float, RGB_Array_Int, + RGB_Array_Float, RGBA_Array_Int, RGBA_Array_Float`` - ManimColor itself only accepts singular values and will directly interpret them into a single color if possible - Be careful when passing strings to ManimColor it can create a big overhead for the color processing. + :class:`ManimColor` itself only accepts singular values and will directly interpret + them into a single color if possible. Be careful when passing strings to + :class:`ManimColor`: it can create a big overhead for the color processing. - If you want to parse a list of colors use the function :meth:`parse` in :class:`ManimColor` which assumes that - you are going to pass a list of color so arrays will not be interpreted as a single color. + If you want to parse a list of colors, use the :meth:`parse` method, which assumes + that you're going to pass a list of colors so that arrays will not be interpreted as + a single color. .. warning:: - If you pass an array of numbers to :meth:`parse` it will interpret the r,g,b,a numbers in that array as colors - so instead of the expect singular color you get and array with 4 colors. + If you pass an array of numbers to :meth:`parse`, it will interpret the + ``r,g,b,a`` numbers in that array as colors: Instead of the expected + singular color, you will get an array with 4 colors. - For conversion behaviors see the ``_internal`` functions for further documentation + For conversion behaviors, see the ``_internal`` functions for further documentation. - You can create a ``ManimColor`` instance via its classmethods. See the respective methods for more info. + You can create a :class:`ManimColor` instance via its classmethods. See the + respective methods for more info. .. code-block:: python @@ -199,41 +225,42 @@ def __init__( @property def _internal_space(self) -> npt.NDArray[ManimFloat]: - """ - This is a readonly property which is a custom representation for color space operations. - It is used for operators and can be used when implementing a custom color space. + """This is a readonly property which is a custom representation for color space + operations. It is used for operators and can be used when implementing a custom + color space. """ return self._internal_value @property def _internal_value(self) -> ManimColorInternal: - """Returns the internal value of the current Manim color [r,g,b,a] float array + """Return the internal value of the current Manim color ``[r,g,b,a]`` float + array. Returns ------- ManimColorInternal - internal color representation + Internal color representation. """ return self.__value @_internal_value.setter def _internal_value(self, value: ManimColorInternal) -> None: - """Overwrites the internal color value of the ManimColor object + """Overwrite the internal color value of this :class:`ManimColor`. Parameters ---------- - value : ManimColorInternal - The value which will overwrite the current color + value + The value which will overwrite the current color. Raises ------ TypeError - Raises a TypeError if an invalid array is passed + If an invalid array is passed. """ if not isinstance(value, np.ndarray): - raise TypeError("value must be a numpy array") + raise TypeError("Value must be a NumPy array.") if value.shape[0] != 4: - raise TypeError("Array must have 4 values exactly") + raise TypeError("Array must have exactly 4 values.") self.__value: ManimColorInternal = value @classmethod @@ -243,9 +270,9 @@ def _construct_from_space( | tuple[float, float, float] | tuple[float, float, float, float], ) -> Self: - """ - This function is used as a proxy for constructing a color with an internal value, - this can be used by subclasses to hook into the construction of new objects using the internal value format + """This function is used as a proxy for constructing a color with an internal + value. This can be used by subclasses to hook into the construction of new + objects using the internal value format. """ return cls(_space) @@ -263,20 +290,22 @@ def _internal_from_integer(value: int, alpha: float) -> ManimColorInternal: @staticmethod def _internal_from_hex_string(hex_: str, alpha: float) -> ManimColorInternal: - """Internal function for converting a hex string into the internal representation of a ManimColor. + """Internal function for converting a hex string into the internal representation + of a :class:`ManimColor`. .. warning:: This does not accept any prefixes like # or similar in front of the hex string. - This is just intended for the raw hex part + This is just intended for the raw hex part. *For internal use only* Parameters ---------- - hex : str - hex string to be parsed - alpha : float - alpha value used for the color if the color is only 3 bytes long, if the color is 4 bytes long the parameter will not be used + hex + Hex string to be parsed. + alpha + Alpha value used for the color, if the color is only 3 bytes long. Otherwise, + if the color is 4 bytes long, this parameter will not be used. Returns ------- @@ -308,22 +337,22 @@ def _internal_from_hex_string(hex_: str, alpha: float) -> ManimColorInternal: def _internal_from_int_rgb( rgb: RGB_Tuple_Int, alpha: float = 1.0 ) -> ManimColorInternal: - """Internal function for converting a rgb tuple of integers into the internal representation of a ManimColor. + """Internal function for converting an RGB tuple of integers into the internal + representation of a :class:`ManimColor`. *For internal use only* Parameters ---------- - rgb : RGB_Tuple_Int - integer rgb tuple to be parsed - alpha : float, optional - optional alpha value, by default 1.0 + rgb + Integer RGB tuple to be parsed + alpha + Optional alpha value. Default is 1.0. Returns ------- ManimColorInternal - Internal color representation - + Internal color representation. """ value: np.ndarray = np.asarray(rgb, dtype=ManimColorDType).copy() / 255 value.resize(4, refcheck=False) @@ -334,22 +363,22 @@ def _internal_from_int_rgb( def _internal_from_rgb( rgb: RGB_Tuple_Float, alpha: float = 1.0 ) -> ManimColorInternal: - """Internal function for converting a rgb tuple of floats into the internal representation of a ManimColor. + """Internal function for converting a rgb tuple of floats into the internal + representation of a :class:`ManimColor`. *For internal use only* Parameters ---------- - rgb : RGB_Tuple_Float - float rgb tuple to be parsed - - alpha : float, optional - optional alpha value, by default 1.0 + rgb + Float RGB tuple to be parsed. + alpha + Optional alpha value. Default is 1.0. Returns ------- ManimColorInternal - Internal color representation + Internal color representation. """ value: np.ndarray = np.asarray(rgb, dtype=ManimColorDType).copy() value.resize(4, refcheck=False) @@ -358,62 +387,65 @@ def _internal_from_rgb( @staticmethod def _internal_from_int_rgba(rgba: RGBA_Tuple_Int) -> ManimColorInternal: - """Internal function for converting a rgba tuple of integers into the internal representation of a ManimColor. + """Internal function for converting an RGBA tuple of integers into the internal + representation of a :class:`ManimColor`. *For internal use only* Parameters ---------- - rgba : RGBA_Tuple_Int - int rgba tuple to be parsed + rgba + Int RGBA tuple to be parsed. Returns ------- ManimColorInternal - Internal color representation + Internal color representation. """ return np.asarray(rgba, dtype=ManimColorDType) / 255 @staticmethod def _internal_from_rgba(rgba: RGBA_Tuple_Float) -> ManimColorInternal: - """Internal function for converting a rgba tuple of floats into the internal representation of a ManimColor. + """Internal function for converting an RGBA tuple of floats into the internal + representation of a :class:`ManimColor`. *For internal use only* Parameters ---------- - rgba : RGBA_Tuple_Float - int rgba tuple to be parsed + rgba + Int RGBA tuple to be parsed. Returns ------- ManimColorInternal - Internal color representation + Internal color representation. """ return np.asarray(rgba, dtype=ManimColorDType) @staticmethod def _internal_from_string(name: str, alpha: float) -> ManimColorInternal: - """Internal function for converting a string into the internal representation of a ManimColor. - This is not used for hex strings, please refer to :meth:`_internal_from_hex` for this functionality. + """Internal function for converting a string into the internal representation of + a :class:`ManimColor`. This is not used for hex strings: please refer to + :meth:`_internal_from_hex` for this functionality. *For internal use only* Parameters ---------- - name : str - The color name to be parsed into a color. Refer to the different color Modules in the documentation Page to - find the corresponding Color names. + name + The color name to be parsed into a color. Refer to the different color + modules in the documentation page to find the corresponding color names. Returns ------- ManimColorInternal - Internal color representation + Internal color representation. Raises ------ ValueError - Raises a ValueError if the color name is not present with manim + If the color name is not present in Manim. """ from . import _all_color_dict @@ -424,107 +456,109 @@ def _internal_from_string(name: str, alpha: float) -> ManimColorInternal: raise ValueError(f"Color {name} not found") def to_integer(self) -> int: - """Converts the current ManimColor into an integer + """Convert the current :class:`ManimColor` into an integer. + + .. warning:: + This will return only the RGB part of the color. Returns ------- int - integer representation of the color - - .. warning:: - This will return only the rgb part of the color + Integer representation of the color. """ tmp = (self._internal_value[:3] * 255).astype(dtype=np.byte).tobytes() return int.from_bytes(tmp, "big") def to_rgb(self) -> RGB_Array_Float: - """Converts the current ManimColor into a rgb array of floats + """Convert the current :class:`ManimColor` into an RGB array of floats. Returns ------- RGB_Array_Float - rgb array with 3 elements of type float + RGB array of 3 floats from 0.0 to 1.0. """ return self._internal_value[:3] def to_int_rgb(self) -> RGB_Array_Int: - """Converts the current ManimColor into a rgb array of int + """Convert the current :class:`ManimColor` into an RGB array of integers. Returns ------- RGB_Array_Int - rgb array with 3 elements of type int + RGB array of 3 integers from 0 to 255. """ return (self._internal_value[:3] * 255).astype(int) def to_rgba(self) -> RGBA_Array_Float: - """Converts the current ManimColor into a rgba array of floats + """Convert the current :class:`ManimColor` into an RGBA array of floats. Returns ------- RGBA_Array_Float - rgba array with 4 elements of type float + RGBA array of 4 floats from 0.0 to 1.0. """ return self._internal_value def to_int_rgba(self) -> RGBA_Array_Int: - """Converts the current ManimColor into a rgba array of int + """Convert the current ManimColor into an RGBA array of integers. Returns ------- RGBA_Array_Int - rgba array with 4 elements of type int + RGBA array of 4 integers from 0 to 255. """ return (self._internal_value * 255).astype(int) def to_rgba_with_alpha(self, alpha: float) -> RGBA_Array_Float: - """Converts the current ManimColor into a rgba array of float as :meth:`to_rgba` but you can change the alpha - value. + """Convert the current :class:`ManimColor` into an RGBA array of floats. This is + similar to :meth:`to_rgba`, but you can change the alpha value. Parameters ---------- - alpha : float - alpha value to be used in the return value + alpha + Alpha value to be used in the return value. Returns ------- RGBA_Array_Float - rgba array with 4 elements of type float + RGBA array of 4 floats from 0.0 to 1.0. """ return np.fromiter((*self._internal_value[:3], alpha), dtype=ManimColorDType) def to_int_rgba_with_alpha(self, alpha: float) -> RGBA_Array_Int: - """Converts the current ManimColor into a rgba array of integers as :meth:`to_int_rgba` but you can change the alpha - value. + """Convert the current :class:`ManimColor` into an RGBA array of integers. This + is similar to :meth:`to_int_rgba`, but you can change the alpha value. Parameters ---------- - alpha : float - alpha value to be used for the return value. (Will automatically be scaled from 0-1 to 0-255 so just pass 0-1) + alpha + Alpha value to be used for the return value. Pass a float between 0.0 and + 1.0: it will automatically be scaled to an integer between 0 and 255. Returns ------- RGBA_Array_Int - rgba array with 4 elements of type int + RGBA array of 4 integers from 0 to 255. """ tmp = self._internal_value * 255 tmp[3] = alpha * 255 return tmp.astype(int) def to_hex(self, with_alpha: bool = False) -> str: - """Converts the manim color to a hexadecimal representation of the color + """Convert the :class:`ManimColor` to a hexadecimal representation of the color. Parameters ---------- - with_alpha : bool, optional - Changes the result from 6 to 8 values where the last 2 nibbles represent the alpha value of 0-255, - by default False + with_alpha + If ``True``, append 2 extra characters to the hex string which represent the + alpha value of the color between 0 and 255. Default is ``False``. Returns ------- str - A hex string starting with a # with either 6 or 8 nibbles depending on your input, by default 6 i.e #XXXXXX + A hex string starting with a ``#``, with either 6 or 8 nibbles depending on + the ``with_alpha`` parameter. By default, it has 6 nibbles, i.e. ``#XXXXXX``. """ tmp = ( f"#{int(self._internal_value[0] * 255):02X}" @@ -536,53 +570,57 @@ def to_hex(self, with_alpha: bool = False) -> str: return tmp def to_hsv(self) -> HSV_Array_Float: - """Converts the Manim Color to HSV array. + """Convert the :class:`ManimColor` to an HSV array. .. note:: - Be careful this returns an array in the form `[h, s, v]` where the elements are floats. - This might be confusing because rgb can also be an array of floats so you might want to annotate the usage - of this function in your code by typing the variables with :class:`HSV_Array_Float` in order to differentiate - between rgb arrays and hsv arrays + Be careful: this returns an array in the form ``[h, s, v]``, where the + elements are floats. This might be confusing, because RGB can also be an array + of floats. You might want to annotate the usage of this function in your code + by typing your HSV array variables as :class:`HSV_Array_Float` in order to + differentiate them from RGB arrays. Returns ------- HSV_Array_Float - A hsv array containing 3 elements of type float ranging from 0 to 1 + An HSV array of 3 floats from 0.0 to 1.0. """ return np.array(colorsys.rgb_to_hsv(*self.to_rgb())) def to_hsl(self) -> HSL_Array_Float: - """Converts the Manim Color to HSL array. + """Convert the :class:`ManimColor` to an HSL array. .. note:: - Be careful this returns an array in the form `[h, s, l]` where the elements are floats. - This might be confusing because rgb can also be an array of floats so you might want to annotate the usage - of this function in your code by typing the variables with :class:`HSL_Array_Float` in order to differentiate - between rgb arrays and hsl arrays + Be careful: this returns an array in the form ``[h, s, l]``, where the + elements are floats. This might be confusing, because RGB can also be an array + of floats. You might want to annotate the usage of this function in your code + by typing your HSL array variables as :class:`HSL_Array_Float` in order to + differentiate them from RGB arrays. Returns ------- HSL_Array_Float - A hsl array containing 3 elements of type float ranging from 0 to 1 + An HSL array of 3 floats from 0.0 to 1.0. """ return np.array(colorsys.rgb_to_hls(*self.to_rgb())) def invert(self, with_alpha: bool = False) -> Self: - """Returns an linearly inverted version of the color (no inplace changes) + """Return a new, linearly inverted version of this :class:`ManimColor` (no + inplace changes). Parameters ---------- - with_alpha : bool, optional - if true the alpha value will be inverted too, by default False + with_alpha + If ``True``, the alpha value will be inverted too. Default is ``False``. .. note:: - This can result in unintended behavior where objects are not displayed because their alpha - value is suddenly 0 or very low. Please keep that in mind when setting this to true + Setting ``with_alpha=True`` can result in unintended behavior where + objects are not displayed because their new alpha value is suddenly 0 or + very low. Returns ------- ManimColor - The linearly inverted ManimColor + The linearly inverted :class:`ManimColor`. """ if with_alpha: return self._construct_from_space(1.0 - self._internal_space) @@ -593,41 +631,42 @@ def invert(self, with_alpha: bool = False) -> Self: return self._construct_from_space(new) def interpolate(self, other: Self, alpha: float) -> Self: - """Interpolates between the current and the given ManimColor an returns the interpolated color + """Interpolate between the current and the given :class:`ManimColor`, and return + the result. Parameters ---------- - other : ManimColor - The other ManimColor to be used for interpolation - alpha : float - A point on the line in rgba colorspace connecting the two colors i.e. the interpolation point - - 0 corresponds to the current ManimColor and 1 corresponds to the other ManimColor + other + The other :class:`ManimColor` to be used for interpolation. + alpha + A point on the line in RGBA colorspace connecting the two colors, i.e. the + interpolation point. 0.0 corresponds to the current :class:`ManimColor` and + 1.0 corresponds to the other :class:`ManimColor`. Returns ------- ManimColor - The interpolated ManimColor + The interpolated :class:`ManimColor`. """ return self._construct_from_space( self._internal_space * (1 - alpha) + other._internal_space * alpha ) def darker(self, blend: float = 0.2) -> Self: - """Returns a new color that is darker than the current color, i.e. - interpolated with black. The opacity is unchanged. + """Return a new color that is darker than the current color, i.e. + interpolated with ``BLACK``. The opacity is unchanged. Parameters ---------- - blend : float, optional - The blend ratio for the interpolation, from 0 (the current color - unchanged) to 1 (pure black). By default 0.2 which results in a - slightly darker color + blend + The blend ratio for the interpolation, from 0.0 (the current color + unchanged) to 1.0 (pure black). Default is 0.2, which results in a + slightly darker color. Returns ------- ManimColor - The darker ManimColor + The darker :class:`ManimColor`. See Also -------- @@ -640,20 +679,20 @@ def darker(self, blend: float = 0.2) -> Self: return self.interpolate(black, blend).opacity(alpha) def lighter(self, blend: float = 0.2) -> Self: - """Returns a new color that is lighter than the current color, i.e. - interpolated with white. The opacity is unchanged. + """Return a new color that is lighter than the current color, i.e. + interpolated with ``WHITE``. The opacity is unchanged. Parameters ---------- - blend : float, optional - The blend ratio for the interpolation, from 0 (the current color - unchanged) to 1 (pure white). By default 0.2 which results in a - slightly lighter color + blend + The blend ratio for the interpolation, from 0.0 (the current color + unchanged) to 1.0 (pure white). Default is 0.2, which results in a + slightly lighter color. Returns ------- ManimColor - The lighter ManimColor + The lighter :class:`ManimColor`. See Also -------- @@ -671,28 +710,28 @@ def contrasting( light: Self | None = None, dark: Self | None = None, ) -> Self: - """Returns one of two colors, light or dark (by default white or black), + """Return one of two colors, light or dark (by default white or black), that contrasts with the current color (depending on its luminance). This is typically used to set text in a contrasting color that ensures it is readable against a background of the current color. Parameters ---------- - threshold : float, optional - The luminance threshold that dictates whether the current color is + threshold + The luminance threshold which dictates whether the current color is considered light or dark (and thus whether to return the dark or - light color, respectively), by default 0.5 - light : ManimColor, optional - The light color to return if the current color is considered dark, - by default pure white - dark : ManimColor, optional + light color, respectively). Default is 0.5. + light + The light color to return if the current color is considered dark. + Default is ``None``: in this case, pure ``WHITE`` will be returned. + dark The dark color to return if the current color is considered light, - by default pure black + Default is ``None``: in this case, pure ``BLACK`` will be returned. Returns ------- ManimColor - The contrasting ManimColor + The contrasting :class:`ManimColor`. """ from manim.utils.color.manim_colors import BLACK, WHITE @@ -707,44 +746,49 @@ def contrasting( return self._from_internal(BLACK._internal_value) def opacity(self, opacity: float) -> Self: - """Creates a new ManimColor with the given opacity and the same color value as before + """Create a new :class:`ManimColor` with the given opacity and the same color + values as before. Parameters ---------- - opacity : float - The new opacity value to be used + opacity + The new opacity value to be used. Returns ------- ManimColor - The new ManimColor with the same color value but the new opacity + The new :class:`ManimColor` with the same color values and the new opacity. """ tmp = self._internal_space.copy() tmp[-1] = opacity return self._construct_from_space(tmp) - def into(self, classtype: type[ManimColorT]) -> ManimColorT: - """Converts the current color into a different colorspace that is given without changing the _internal_value + def into(self, class_type: type[ManimColorT]) -> ManimColorT: + """Convert the current color into a different colorspace given by ``class_type``, + without changing the :attr:`_internal_value`. Parameters ---------- - classtype : type[ManimColorT] - The class that is used for conversion, it must be a subclass of ManimColor which respects the specification - HSV, RGBA, ... + class_type + The class that is used for conversion. It must be a subclass of + :class:`ManimColor` which respects the specification HSV, RGBA, ... Returns ------- ManimColorT - Color object of the type passed into classtype with the same internal value as previously + A new color object of type ``class_type`` and the same + :attr:`_internal_value` as the original color. """ - return classtype._from_internal(self._internal_value) + return class_type._from_internal(self._internal_value) @classmethod def _from_internal(cls, value: ManimColorInternal) -> Self: - """This function is intended to be overwritten by custom color space classes which are subtypes of ManimColor. + """This method is intended to be overwritten by custom color space classes + which are subtypes of :class:`ManimColor`. - The function constructs a new object of the given class by transforming the value in the internal format ``[r,g,b,a]`` - into a format which the constructor of the custom class can understand. Look at :class:`.HSV` for an example. + The method constructs a new object of the given class by transforming the value + in the internal format ``[r,g,b,a]`` into a format which the constructor of the + custom class can understand. Look at :class:`.HSV` for an example. """ return cls(value) @@ -754,24 +798,26 @@ def from_rgb( rgb: RGB_Array_Float | RGB_Tuple_Float | RGB_Array_Int | RGB_Tuple_Int, alpha: float = 1.0, ) -> Self: - """Creates a ManimColor from an RGB Array. Automagically decides which type it is int/float + """Create a ManimColor from an RGB array. Automagically decides which type it + is: ``int`` or ``float``. .. warning:: - Please make sure that your elements are not floats if you want integers. A 5.0 will result in the input - being interpreted as if it was a float rgb array with the value 5.0 and not the integer 5 + Please make sure that your elements are not floats if you want integers. A + ``5.0`` will result in the input being interpreted as if it was an RGB float + array with the value ``5.0`` and not the integer ``5``. Parameters ---------- - rgb : RGB_Array_Float | RGB_Tuple_Float | RGB_Array_Int | RGB_Tuple_Int - Any 3 Element Iterable - alpha : float, optional - alpha value to be used in the color, by default 1.0 + rgb + Any iterable of 3 floats or 3 integers. + alpha + Alpha value to be used in the color. Default is 1.0. Returns ------- ManimColor - Returns the ManimColor object + The :class:`ManimColor` which corresponds to the given ``rgb``. """ return cls._from_internal(ManimColor(rgb, alpha)._internal_value) @@ -779,39 +825,43 @@ def from_rgb( def from_rgba( cls, rgba: RGBA_Array_Float | RGBA_Tuple_Float | RGBA_Array_Int | RGBA_Tuple_Int ) -> Self: - """Creates a ManimColor from an RGBA Array. Automagically decides which type it is int/float + """Create a ManimColor from an RGBA Array. Automagically decides which type it + is: ``int`` or ``float``. .. warning:: - Please make sure that your elements are not floats if you want integers. A 5.0 will result in the input - being interpreted as if it was a float rgb array with the value 5.0 and not the integer 5 + Please make sure that your elements are not floats if you want integers. A + ``5.0`` will result in the input being interpreted as if it was a float RGB + array with the float ``5.0`` and not the integer ``5``. Parameters ---------- - rgba : RGBA_Array_Float | RGBA_Tuple_Float | RGBA_Array_Int | RGBA_Tuple_Int - Any 4 Element Iterable + rgba + Any iterable of 4 floats or 4 integers. Returns ------- ManimColor - Returns the ManimColor object + The :class:`ManimColor` corresponding to the given ``rgba``. """ return cls(rgba) @classmethod def from_hex(cls, hex_str: str, alpha: float = 1.0) -> Self: - """Creates a Manim Color from a hex string, prefixes allowed # and 0x + """Create a :class:`ManimColor` from a hex string. Parameters ---------- - hex_str : str - The hex string to be converted (currently only supports 6 nibbles) - alpha : float, optional - alpha value to be used for the hex string, by default 1.0 + hex_str + The hex string to be converted. The allowed prefixes for this string are + ``#`` and ``0x``. Currently, this method only supports 6 nibbles, i.e. only + strings in the format ``#XXXXXX`` or ``0xXXXXXX``. + alpha + Alpha value to be used for the hex string. Default is 1.0. Returns ------- ManimColor - The ManimColor represented by the hex string + The :class:`ManimColor` represented by the hex string. """ return cls._from_internal(ManimColor(hex_str, alpha)._internal_value) @@ -819,19 +869,20 @@ def from_hex(cls, hex_str: str, alpha: float = 1.0) -> Self: def from_hsv( cls, hsv: HSV_Array_Float | HSV_Tuple_Float, alpha: float = 1.0 ) -> Self: - """Creates a ManimColor from an HSV Array + """Create a :class:`ManimColor` from an HSV array. Parameters ---------- - hsv : HSV_Array_Float | HSV_Tuple_Float - Any 3 Element Iterable containing floats from 0-1 - alpha : float, optional - the alpha value to be used, by default 1.0 + hsv + Any iterable containing 3 floats from 0.0 to 1.0. + alpha + The alpha value to be used. Default is 1.0. Returns ------- ManimColor - The ManimColor with the corresponding RGB values to the HSV + The :class:`ManimColor` with the corresponding RGB values to the given HSV + array. """ rgb = colorsys.hsv_to_rgb(*hsv) return cls._from_internal(ManimColor(rgb, alpha)._internal_value) @@ -840,19 +891,20 @@ def from_hsv( def from_hsl( cls, hsl: HSL_Array_Float | HSL_Tuple_Float, alpha: float = 1.0 ) -> Self: - """Creates a ManimColor from an HSL Array + """Create a :class:`ManimColor` from an HSL array. Parameters ---------- - hsl : HSL_Array_Float | HSL_Tuple_Float - Any 3 Element Iterable containing floats from 0-1 - alpha : float, optional - the alpha value to be used, by default 1.0 + hsl + Any iterable containing 3 floats from 0.0 to 1.0. + alpha + The alpha value to be used. Default is 1.0. Returns ------- ManimColor - The ManimColor with the corresponding RGB values to the HSL + The :class:`ManimColor` with the corresponding RGB values to the given HSL + array. """ rgb = colorsys.hls_to_rgb(*hsl) return cls._from_internal(ManimColor(rgb, alpha)._internal_value) @@ -879,40 +931,43 @@ def parse( color: ParsableManimColor | Sequence[ParsableManimColor] | None, alpha: float = 1.0, ) -> Self | list[Self]: - """ - Handles the parsing of a list of colors or a single color. + """Parse one color as a :class:`ManimColor` or a sequence of colors as a list of + :class:`ManimColor`'s. Parameters ---------- color - The color or list of colors to parse. Note that this function can not accept rgba tuples. It will assume that you mean list[ManimColor] and will return a list of ManimColors. + The color or list of colors to parse. Note that this function can not accept + tuples: it will assume that you mean ``Sequence[ParsableManimColor]`` and will + return a ``list[ManimColor]``. alpha - The alpha value to use if a single color is passed. or if a list of colors is passed to set the value of all colors. + The alpha (opacity) value to use for the passed color(s). Returns ------- - ManimColor - Either a list of colors or a singular color depending on the input + ManimColor | list[ManimColor] + Either a list of colors or a singular color, depending on the input. """ - def is_sequence(colors: Any) -> TypeGuard[Sequence[ParsableManimColor]]: - return isinstance(colors, (list, tuple)) - - def is_parsable(color: Any) -> TypeGuard[ParsableManimColor]: - return not isinstance(color, (list, tuple)) + def is_sequence( + color: ParsableManimColor | Sequence[ParsableManimColor] | None, + ) -> TypeIs[Sequence[ParsableManimColor]]: + return isinstance(color, (list, tuple)) if is_sequence(color): return [ cls._from_internal(ManimColor(c, alpha)._internal_value) for c in color ] - elif is_parsable(color): - return cls._from_internal(ManimColor(color, alpha)._internal_value) else: - return cls._from_internal(ManimColor("WHITE", alpha)._internal_value) + return cls._from_internal(ManimColor(color, alpha)._internal_value) @staticmethod - def gradient(colors: list[ManimColor], length: int) -> None: - """This is not implemented by now refer to :func:`color_gradient` for a working implementation for now""" + def gradient( + colors: list[ManimColor], length: int + ) -> ManimColor | list[ManimColor]: + """This method is currently not implemented. Refer to :func:`color_gradient` for + a working implementation for now. + """ # TODO: implement proper gradient, research good implementation for this or look at 3b1b implementation raise NotImplementedError @@ -927,8 +982,8 @@ def __eq__(self, other: object) -> bool: raise TypeError( f"Cannot compare {self.__class__.__name__} with {other.__class__.__name__}" ) - value: bool = np.allclose(self._internal_value, other._internal_value) - return value + are_equal: bool = np.allclose(self._internal_value, other._internal_value) + return are_equal def __add__(self, other: int | float | Self) -> Self: if isinstance(other, (int, float)): @@ -1014,8 +1069,8 @@ def __int__(self) -> int: return self.to_integer() def __getitem__(self, index: int) -> float: - value: float = self._internal_space[index] - return value + item: float = self._internal_space[index] + return item def __and__(self, other: Self) -> Self: return self._construct_from_space( @@ -1049,8 +1104,9 @@ def __init__( alpha: float = 1.0, ) -> None: super().__init__(None) + self.__hsv: HSVA_Array_Float if len(hsv) == 3: - self.__hsv: HSVA_Array_Float = np.asarray((*hsv, alpha)) + self.__hsv = np.asarray((*hsv, alpha)) elif len(hsv) == 4: self.__hsv = np.asarray(hsv) else: @@ -1065,21 +1121,21 @@ def _from_internal(cls, value: ManimColorInternal) -> Self: @property def hue(self) -> float: - value: float = self.__hsv[0] - return value + hue: float = self.__hsv[0] + return hue @hue.setter - def hue(self, value: float) -> None: - self.__hsv[0] = value + def hue(self, hue: float) -> None: + self.__hsv[0] = hue @property def saturation(self) -> float: - value: float = self.__hsv[1] - return value + saturation: float = self.__hsv[1] + return saturation @saturation.setter - def saturation(self, value: float) -> None: - self.__hsv[1] = value + def saturation(self, saturation: float) -> None: + self.__hsv[1] = saturation @property def value(self) -> float: @@ -1092,21 +1148,21 @@ def value(self, value: float) -> None: @property def h(self) -> float: - value: float = self.__hsv[0] - return value + hue: float = self.__hsv[0] + return hue @h.setter - def h(self, value: float) -> None: - self.__hsv[0] = value + def h(self, hue: float) -> None: + self.__hsv[0] = hue @property def s(self) -> float: - value: float = self.__hsv[1] - return value + saturation: float = self.__hsv[1] + return saturation @s.setter - def s(self, value: float) -> None: - self.__hsv[1] = value + def s(self, saturation: float) -> None: + self.__hsv[1] = saturation @property def v(self) -> float: @@ -1123,12 +1179,13 @@ def _internal_space(self) -> npt.NDArray: @property def _internal_value(self) -> ManimColorInternal: - """Returns the internal value of the current Manim color [r,g,b,a] float array + """Return the internal value of the current :class:`ManimColor` as an + ``[r,g,b,a]`` float array. Returns ------- ManimColorInternal - internal color representation + Internal color representation. """ return np.array( [ @@ -1140,22 +1197,22 @@ def _internal_value(self) -> ManimColorInternal: @_internal_value.setter def _internal_value(self, value: ManimColorInternal) -> None: - """Overwrites the internal color value of the ManimColor object + """Overwrite the internal color value of this :class:`ManimColor`. Parameters ---------- - value : ManimColorInternal - The value which will overwrite the current color + value + The value which will overwrite the current color. Raises ------ TypeError - Raises a TypeError if an invalid array is passed + If an invalid array is passed. """ if not isinstance(value, np.ndarray): - raise TypeError("value must be a numpy array") + raise TypeError("Value must be a NumPy array.") if value.shape[0] != 4: - raise TypeError("Array must have 4 values exactly") + raise TypeError("Array must have exactly 4 values.") tmp = colorsys.rgb_to_hsv(value[0], value[1], value[2]) self.__hsv = np.array(tmp) self.__alpha = value[3] @@ -1175,7 +1232,7 @@ def _internal_value(self, value: ManimColorInternal) -> None: RGBA_Array_Float, ] """`ParsableManimColor` represents all the types which can be parsed -to a color in Manim. +to a :class:`ManimColor` in Manim. """ @@ -1184,69 +1241,74 @@ def _internal_value(self, value: ManimColorInternal) -> None: def color_to_rgb(color: ParsableManimColor) -> RGB_Array_Float: """Helper function for use in functional style programming. - Refer to :meth:`to_rgb` in :class:`ManimColor`. + Refer to :meth:`ManimColor.to_rgb`. Parameters ---------- - color : ParsableManimColor - A color + color + A color to convert to an RGB float array. Returns ------- RGB_Array_Float - the corresponding rgb array + The corresponding RGB float array. """ return ManimColor(color).to_rgb() -def color_to_rgba(color: ParsableManimColor, alpha: float = 1) -> RGBA_Array_Float: - """Helper function for use in functional style programming refer to :meth:`to_rgba_with_alpha` in :class:`ManimColor` +def color_to_rgba(color: ParsableManimColor, alpha: float = 1.0) -> RGBA_Array_Float: + """Helper function for use in functional style programming. Refer to + :meth:`ManimColor.to_rgba_with_alpha`. Parameters ---------- - color : ParsableManimColor - A color - alpha : float, optional - alpha value to be used in the color, by default 1 + color + A color to convert to an RGBA float array. + alpha + An alpha value between 0.0 and 1.0 to be used as opacity in the color. Default is + 1.0. Returns ------- RGBA_Array_Float - the corresponding rgba array + The corresponding RGBA float array. """ return ManimColor(color).to_rgba_with_alpha(alpha) def color_to_int_rgb(color: ParsableManimColor) -> RGB_Array_Int: - """Helper function for use in functional style programming refer to :meth:`to_int_rgb` in :class:`ManimColor` + """Helper function for use in functional style programming. Refer to + :meth:`ManimColor.to_int_rgb`. Parameters ---------- - color : ParsableManimColor - A color + color + A color to convert to an RGB integer array. Returns ------- RGB_Array_Int - the corresponding int rgb array + The corresponding RGB integer array. """ return ManimColor(color).to_int_rgb() def color_to_int_rgba(color: ParsableManimColor, alpha: float = 1.0) -> RGBA_Array_Int: - """Helper function for use in functional style programming refer to :meth:`to_int_rgba_with_alpha` in :class:`ManimColor` + """Helper function for use in functional style programming. Refer to + :meth:`ManimColor.to_int_rgba_with_alpha`. Parameters ---------- - color : ParsableManimColor - A color - alpha : float, optional - alpha value to be used in the color, by default 1.0 + color + A color to convert to an RGBA integer array. + alpha + An alpha value between 0.0 and 1.0 to be used as opacity in the color. Default is + 1.0. Returns ------- RGBA_Array_Int - the corresponding int rgba array + The corresponding RGBA integer array. """ return ManimColor(color).to_int_rgba_with_alpha(alpha) @@ -1254,17 +1316,18 @@ def color_to_int_rgba(color: ParsableManimColor, alpha: float = 1.0) -> RGBA_Arr def rgb_to_color( rgb: RGB_Array_Float | RGB_Tuple_Float | RGB_Array_Int | RGB_Tuple_Int, ) -> ManimColor: - """Helper function for use in functional style programming refer to :meth:`from_rgb` in :class:`ManimColor` + """Helper function for use in functional style programming. Refer to + :meth:`ManimColor.from_rgb`. Parameters ---------- - rgb : RGB_Array_Float | RGB_Tuple_Float - A 3 element iterable + rgb + A 3 element iterable. Returns ------- ManimColor - A ManimColor with the corresponding value + A ManimColor with the corresponding value. """ return ManimColor.from_rgb(rgb) @@ -1272,12 +1335,13 @@ def rgb_to_color( def rgba_to_color( rgba: RGBA_Array_Float | RGBA_Tuple_Float | RGBA_Array_Int | RGBA_Tuple_Int, ) -> ManimColor: - """Helper function for use in functional style programming refer to :meth:`from_rgba` in :class:`ManimColor` + """Helper function for use in functional style programming. Refer to + :meth:`ManimColor.from_rgba`. Parameters ---------- - rgba : RGBA_Array_Float | RGBA_Tuple_Float - A 4 element iterable + rgba + A 4 element iterable. Returns ------- @@ -1290,92 +1354,74 @@ def rgba_to_color( def rgb_to_hex( rgb: RGB_Array_Float | RGB_Tuple_Float | RGB_Array_Int | RGB_Tuple_Int, ) -> str: - """Helper function for use in functional style programming refer to :meth:`from_rgb` in :class:`ManimColor` + """Helper function for use in functional style programming. Refer to + :meth:`ManimColor.from_rgb` and :meth:`ManimColor.to_hex`. Parameters ---------- - rgb : RGB_Array_Float | RGB_Tuple_Float - A 3 element iterable + rgb + A 3 element iterable. Returns ------- str - A hex representation of the color, refer to :meth:`to_hex` in :class:`ManimColor` + A hex representation of the color. """ return ManimColor.from_rgb(rgb).to_hex() def hex_to_rgb(hex_code: str) -> RGB_Array_Float: - """Helper function for use in functional style programming refer to :meth:`to_hex` in :class:`ManimColor` + """Helper function for use in functional style programming. Refer to + :meth:`ManimColor.to_rgb`. Parameters ---------- - hex_code : str - A hex string representing a color + hex_code + A hex string representing a color. Returns ------- RGB_Array_Float - RGB array representing the color + An RGB array representing the color. """ return ManimColor(hex_code).to_rgb() def invert_color(color: ManimColorT) -> ManimColorT: - """Helper function for use in functional style programming refer to :meth:`invert` in :class:`ManimColor` + """Helper function for use in functional style programming. Refer to + :meth:`ManimColor.invert` Parameters ---------- - color : ManimColor - A ManimColor + color + The :class:`ManimColor` to invert. Returns ------- ManimColor - The linearly inverted ManimColor + The linearly inverted :class:`ManimColor`. """ return color.invert() -def interpolate_arrays( - arr1: npt.NDArray[Any], arr2: npt.NDArray[Any], alpha: float -) -> np.ndarray: - """Helper function used in Manim to fade between two objects smoothly - - Parameters - ---------- - arr1 : npt.NDArray[Any] - The first array of colors - arr2 : npt.NDArray[Any] - The second array of colors - alpha : float - The alpha value corresponding to the interpolation point between the two inputs - - Returns - ------- - np.ndarray - The interpolated value of the to arrays - """ - return (1 - alpha) * arr1 + alpha * arr2 - - def color_gradient( reference_colors: Sequence[ParsableManimColor], length_of_output: int, ) -> list[ManimColor] | ManimColor: - """Creates a list of colors interpolated between the input array of colors with a specific number of colors + """Create a list of colors interpolated between the input array of colors with a + specific number of colors. Parameters ---------- - reference_colors : Sequence[ParsableManimColor] - The colors to be interpolated between or spread apart - length_of_output : int - The number of colors that the output should have, ideally more than the input + reference_colors + The colors to be interpolated between or spread apart. + length_of_output + The number of colors that the output should have, ideally more than the input. Returns ------- list[ManimColor] | ManimColor - A list of ManimColor's which has the interpolated colors + A :class:`ManimColor` or a list of interpolated :class:`ManimColor`'s. """ if length_of_output == 0: return ManimColor(reference_colors[0]) @@ -1397,32 +1443,37 @@ def color_gradient( def interpolate_color( color1: ManimColorT, color2: ManimColorT, alpha: float ) -> ManimColorT: - """Standalone function to interpolate two ManimColors and get the result refer to :meth:`interpolate` in :class:`ManimColor` + """Standalone function to interpolate two ManimColors and get the result. Refer to + :meth:`ManimColor.interpolate`. Parameters ---------- - color1 : ManimColor - First ManimColor - color2 : ManimColor - Second ManimColor - alpha : float - The alpha value determining the point of interpolation between the colors + color1 + The first :class:`ManimColor`. + color2 + The second :class:`ManimColor`. + alpha + The alpha value determining the point of interpolation between the colors. Returns ------- ManimColor - The interpolated ManimColor + The interpolated ManimColor. """ return color1.interpolate(color2, alpha) def average_color(*colors: ParsableManimColor) -> ManimColor: - """Determines the Average color of the given parameters + """Determine the average color between the given parameters. + + .. note:: + This operation does not consider the alphas (opacities) of the colors. The + generated color has an alpha or opacity of 1.0. Returns ------- ManimColor - The average color of the input + The average color of the input. """ rgbs = np.array([color_to_rgb(color) for color in colors]) mean_rgb = np.apply_along_axis(np.mean, 0, rgbs) @@ -1430,31 +1481,31 @@ def average_color(*colors: ParsableManimColor) -> ManimColor: def random_bright_color() -> ManimColor: - """Returns you a random bright color + """Return a random bright color: a random color averaged with ``WHITE``. .. warning:: - This operation is very expensive please keep in mind the performance loss. + This operation is very expensive. Please keep in mind the performance loss. Returns ------- ManimColor - A bright ManimColor + A random bright :class:`ManimColor`. """ curr_rgb = color_to_rgb(random_color()) - new_rgb = interpolate_arrays(curr_rgb, np.ones(len(curr_rgb)), 0.5) + new_rgb = 0.5 * (curr_rgb + np.ones(3)) return ManimColor(new_rgb) def random_color() -> ManimColor: - """Return you a random ManimColor + """Return a random :class:`ManimColor`. .. warning:: - This operation is very expensive please keep in mind the performance loss. + This operation is very expensive. Please keep in mind the performance loss. Returns ------- ManimColor - _description_ + A random :class:`ManimColor`. """ import manim.utils.color.manim_colors as manim_colors @@ -1462,17 +1513,38 @@ def random_color() -> ManimColor: def get_shaded_rgb( - rgb: npt.NDArray[Any], - point: npt.NDArray[Any], - unit_normal_vect: npt.NDArray[Any], - light_source: npt.NDArray[Any], -) -> RGBA_Array_Float: + rgb: RGB_Array_Float, + point: Point3D, + unit_normal_vect: Vector3D, + light_source: Point3D, +) -> RGB_Array_Float: + """Add light or shadow to the ``rgb`` color of some surface which is located at a + given ``point`` in space and facing in the direction of ``unit_normal_vect``, + depending on whether the surface is facing a ``light_source`` or away from it. + + Parameters + ---------- + rgb + An RGB array of floats. + point + The location of the colored surface. + unit_normal_vect + The direction in which the colored surface is facing. + light_source + The location of a light source which might illuminate the surface. + + Returns + ------- + RGB_Array_Float + The color with added light or shadow, depending on the direction of the colored + surface. + """ to_sun = normalize(light_source - point) - factor = 0.5 * np.dot(unit_normal_vect, to_sun) ** 3 - if factor < 0: - factor *= 0.5 - result = rgb + factor - return result + light = 0.5 * np.dot(unit_normal_vect, to_sun) ** 3 + if light < 0: + light *= 0.5 + shaded_rgb: RGB_Array_Float = rgb + light + return shaded_rgb __all__ = [ @@ -1488,7 +1560,6 @@ def get_shaded_rgb( "rgb_to_hex", "hex_to_rgb", "invert_color", - "interpolate_arrays", "color_gradient", "interpolate_color", "average_color", diff --git a/manim/utils/opengl.py b/manim/utils/opengl.py index 8616dfd782..877cbc2e8f 100644 --- a/manim/utils/opengl.py +++ b/manim/utils/opengl.py @@ -15,6 +15,12 @@ from manim.typing import MatrixMN, Point3D +if TYPE_CHECKING: + from typing_extensions import TypeAlias + + from manim.typing import MatrixMN + + depth = 20 __all__ = [ @@ -31,7 +37,6 @@ "view_matrix", ] - FlattenedMatrix4x4: TypeAlias = tuple[ float, float, diff --git a/mypy.ini b/mypy.ini index 92df09b1c8..80571869be 100644 --- a/mypy.ini +++ b/mypy.ini @@ -85,6 +85,9 @@ ignore_errors = True [mypy-manim.utils.hashing.*] ignore_errors = True +[mypy-manim.utils.color.*] +ignore_errors = False + [mypy-manim.utils.iterables] warn_return_any = False