manim 0.17.0__py3-none-any.whl → 0.19.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- manim/__init__.py +11 -6
- manim/__main__.py +62 -19
- manim/_config/__init__.py +10 -9
- manim/_config/cli_colors.py +26 -9
- manim/_config/default.cfg +1 -3
- manim/_config/logger_utils.py +23 -13
- manim/_config/utils.py +662 -468
- manim/animation/animation.py +164 -18
- manim/animation/changing.py +34 -23
- manim/animation/composition.py +265 -67
- manim/animation/creation.py +208 -26
- manim/animation/fading.py +16 -18
- manim/animation/growing.py +35 -15
- manim/animation/indication.py +150 -76
- manim/animation/movement.py +56 -22
- manim/animation/numbers.py +64 -6
- manim/animation/rotation.py +78 -7
- manim/animation/specialized.py +6 -7
- manim/animation/speedmodifier.py +13 -10
- manim/animation/transform.py +14 -11
- manim/animation/transform_matching_parts.py +3 -4
- manim/animation/updaters/mobject_update_utils.py +152 -30
- manim/animation/updaters/update.py +10 -7
- manim/camera/camera.py +182 -118
- manim/camera/mapping_camera.py +34 -3
- manim/camera/moving_camera.py +95 -74
- manim/camera/multi_camera.py +23 -15
- manim/camera/three_d_camera.py +70 -52
- manim/cli/__init__.py +17 -0
- manim/cli/cfg/group.py +76 -44
- manim/cli/checkhealth/checks.py +192 -0
- manim/cli/checkhealth/commands.py +90 -0
- manim/cli/default_group.py +158 -25
- manim/cli/init/commands.py +33 -25
- manim/cli/plugins/commands.py +16 -3
- manim/cli/render/commands.py +72 -60
- manim/cli/render/ease_of_access_options.py +4 -3
- manim/cli/render/global_options.py +59 -17
- manim/cli/render/output_options.py +6 -5
- manim/cli/render/render_options.py +98 -33
- manim/constants.py +109 -59
- manim/data_structures.py +31 -0
- manim/mobject/frame.py +8 -5
- manim/mobject/geometry/__init__.py +1 -0
- manim/mobject/geometry/arc.py +277 -135
- manim/mobject/geometry/boolean_ops.py +32 -31
- manim/mobject/geometry/labeled.py +376 -0
- manim/mobject/geometry/line.py +192 -87
- manim/mobject/geometry/polygram.py +224 -58
- manim/mobject/geometry/shape_matchers.py +61 -25
- manim/mobject/geometry/tips.py +122 -48
- manim/mobject/graph.py +1027 -419
- manim/mobject/graphing/coordinate_systems.py +533 -278
- manim/mobject/graphing/functions.py +53 -32
- manim/mobject/graphing/number_line.py +123 -65
- manim/mobject/graphing/probability.py +88 -62
- manim/mobject/graphing/scale.py +33 -19
- manim/mobject/logo.py +118 -28
- manim/mobject/matrix.py +87 -83
- manim/mobject/mobject.py +912 -442
- manim/mobject/opengl/dot_cloud.py +16 -5
- manim/mobject/opengl/opengl_compatibility.py +4 -2
- manim/mobject/opengl/opengl_geometry.py +254 -153
- manim/mobject/opengl/opengl_image_mobject.py +3 -1
- manim/mobject/opengl/opengl_mobject.py +779 -482
- manim/mobject/opengl/opengl_point_cloud_mobject.py +41 -14
- manim/mobject/opengl/opengl_surface.py +14 -92
- manim/mobject/opengl/opengl_three_dimensions.py +12 -8
- manim/mobject/opengl/opengl_vectorized_mobject.py +98 -100
- manim/mobject/svg/brace.py +173 -41
- manim/mobject/svg/svg_mobject.py +139 -53
- manim/mobject/table.py +61 -68
- manim/mobject/text/code_mobject.py +193 -539
- manim/mobject/text/numbers.py +81 -34
- manim/mobject/text/tex_mobject.py +130 -78
- manim/mobject/text/text_mobject.py +288 -164
- manim/mobject/three_d/polyhedra.py +111 -13
- manim/mobject/three_d/three_d_utils.py +17 -8
- manim/mobject/three_d/three_dimensions.py +239 -106
- manim/mobject/types/image_mobject.py +50 -30
- manim/mobject/types/point_cloud_mobject.py +120 -75
- manim/mobject/types/vectorized_mobject.py +841 -408
- manim/mobject/value_tracker.py +105 -38
- manim/mobject/vector_field.py +50 -31
- manim/opengl/__init__.py +3 -3
- manim/plugins/__init__.py +14 -1
- manim/plugins/plugins_flags.py +10 -14
- manim/renderer/cairo_renderer.py +65 -50
- manim/renderer/opengl_renderer.py +89 -69
- manim/renderer/opengl_renderer_window.py +39 -18
- manim/renderer/shader.py +123 -87
- manim/renderer/shader_wrapper.py +44 -28
- manim/renderer/vectorized_mobject_rendering.py +38 -10
- manim/scene/moving_camera_scene.py +32 -3
- manim/scene/scene.py +507 -242
- manim/scene/scene_file_writer.py +371 -220
- manim/scene/section.py +20 -16
- manim/scene/three_d_scene.py +14 -22
- manim/scene/vector_space_scene.py +223 -129
- manim/scene/zoomed_scene.py +46 -41
- manim/typing.py +990 -0
- manim/utils/bezier.py +1823 -371
- manim/utils/caching.py +12 -5
- manim/utils/color/AS2700.py +236 -0
- manim/utils/color/BS381.py +318 -0
- manim/utils/color/DVIPSNAMES.py +96 -0
- manim/utils/color/SVGNAMES.py +179 -0
- manim/utils/color/X11.py +533 -0
- manim/utils/color/XKCD.py +952 -0
- manim/utils/color/__init__.py +61 -0
- manim/utils/color/core.py +1667 -0
- manim/utils/color/manim_colors.py +218 -0
- manim/utils/commands.py +48 -20
- manim/utils/config_ops.py +39 -19
- manim/utils/debug.py +8 -7
- manim/utils/deprecation.py +86 -39
- manim/utils/docbuild/__init__.py +17 -0
- manim/utils/docbuild/autoaliasattr_directive.py +236 -0
- manim/utils/docbuild/autocolor_directive.py +99 -0
- manim/utils/docbuild/manim_directive.py +94 -41
- manim/utils/docbuild/module_parsing.py +245 -0
- manim/utils/exceptions.py +6 -0
- manim/utils/family.py +5 -3
- manim/utils/family_ops.py +17 -4
- manim/utils/file_ops.py +27 -17
- manim/utils/hashing.py +55 -45
- manim/utils/images.py +13 -7
- manim/utils/ipython_magic.py +13 -7
- manim/utils/iterables.py +163 -120
- manim/utils/module_ops.py +66 -24
- manim/utils/opengl.py +77 -24
- manim/utils/parameter_parsing.py +32 -0
- manim/utils/paths.py +30 -33
- manim/utils/polylabel.py +235 -0
- manim/utils/qhull.py +218 -0
- manim/utils/rate_functions.py +98 -32
- manim/utils/simple_functions.py +25 -33
- manim/utils/sounds.py +7 -1
- manim/utils/space_ops.py +188 -115
- manim/utils/testing/__init__.py +17 -0
- manim/utils/testing/_frames_testers.py +13 -8
- manim/utils/testing/_show_diff.py +5 -3
- manim/utils/testing/_test_class_makers.py +34 -18
- manim/utils/testing/frames_comparison.py +37 -19
- manim/utils/tex.py +130 -198
- manim/utils/tex_file_writing.py +77 -47
- manim/utils/tex_templates.py +2 -1
- manim/utils/unit.py +6 -5
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/METADATA +64 -65
- manim-0.19.1.dist-info/RECORD +220 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/WHEEL +1 -1
- manim-0.19.1.dist-info/entry_points.txt +3 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE.community +1 -1
- manim/cli/new/group.py +0 -189
- manim/communitycolors.py +0 -9
- manim/gui/__init__.py +0 -0
- manim/gui/gui.py +0 -82
- manim/plugins/import_plugins.py +0 -43
- manim/utils/color.py +0 -552
- manim-0.17.0.dist-info/RECORD +0 -206
- manim-0.17.0.dist-info/entry_points.txt +0 -4
- /manim/cli/{new → checkhealth}/__init__.py +0 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,1667 @@
|
|
|
1
|
+
"""Manim's (internal) color data structure and some utilities for color conversion.
|
|
2
|
+
|
|
3
|
+
This module contains the implementation of :class:`.ManimColor`, the data structure
|
|
4
|
+
internally used to represent colors.
|
|
5
|
+
|
|
6
|
+
The preferred way of using these colors is by importing their constants from Manim:
|
|
7
|
+
|
|
8
|
+
.. code-block:: pycon
|
|
9
|
+
|
|
10
|
+
>>> from manim import RED, GREEN, BLUE
|
|
11
|
+
>>> print(RED)
|
|
12
|
+
#FC6255
|
|
13
|
+
|
|
14
|
+
Note that this way uses the name of the colors in UPPERCASE.
|
|
15
|
+
|
|
16
|
+
.. note::
|
|
17
|
+
|
|
18
|
+
The colors with a ``_C`` suffix have an alias equal to the colorname without a
|
|
19
|
+
letter. For example, ``GREEN = GREEN_C``.
|
|
20
|
+
|
|
21
|
+
===================
|
|
22
|
+
Custom Color Spaces
|
|
23
|
+
===================
|
|
24
|
+
|
|
25
|
+
Hello, dear visitor. You seem to be interested in implementing a custom color class for
|
|
26
|
+
a color space we don't currently support.
|
|
27
|
+
|
|
28
|
+
The current system is using a few indirections for ensuring a consistent behavior with
|
|
29
|
+
all other color types in Manim.
|
|
30
|
+
|
|
31
|
+
To implement a custom color space, you must subclass :class:`ManimColor` and implement
|
|
32
|
+
three important methods:
|
|
33
|
+
|
|
34
|
+
- :attr:`~.ManimColor._internal_value`: a ``@property`` implemented on
|
|
35
|
+
:class:`ManimColor` with the goal of keeping a consistent internal representation
|
|
36
|
+
which can be referenced by other functions in :class:`ManimColor`. This property acts
|
|
37
|
+
as a proxy to whatever representation you need in your class.
|
|
38
|
+
|
|
39
|
+
- The getter should always return a NumPy array in the format ``[r,g,b,a]``, in
|
|
40
|
+
accordance with the type :class:`ManimColorInternal`.
|
|
41
|
+
|
|
42
|
+
- The setter should always accept a value in the format ``[r,g,b,a]`` which can be
|
|
43
|
+
converted to whatever attributes you need.
|
|
44
|
+
|
|
45
|
+
- :attr:`~ManimColor._internal_space`: a read-only ``@property`` implemented on
|
|
46
|
+
:class:`ManimColor` with the goal of providing a useful representation which can be
|
|
47
|
+
used by operators, interpolation and color transform functions.
|
|
48
|
+
|
|
49
|
+
The only constraints on this value are:
|
|
50
|
+
|
|
51
|
+
- It must be a NumPy array.
|
|
52
|
+
|
|
53
|
+
- The last value must be the opacity in a range ``0.0`` to ``1.0``.
|
|
54
|
+
|
|
55
|
+
Additionally, your ``__init__`` must support this format as an initialization value
|
|
56
|
+
without additional parameters to ensure correct functionality of all other methods in
|
|
57
|
+
:class:`ManimColor`.
|
|
58
|
+
|
|
59
|
+
- :meth:`~ManimColor._from_internal`: a ``@classmethod`` which converts an
|
|
60
|
+
``[r,g,b,a]`` value into suitable parameters for your ``__init__`` method and calls
|
|
61
|
+
the ``cls`` parameter.
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
from __future__ import annotations
|
|
66
|
+
|
|
67
|
+
import colorsys
|
|
68
|
+
|
|
69
|
+
# logger = _config.logger
|
|
70
|
+
import random
|
|
71
|
+
import re
|
|
72
|
+
from collections.abc import Iterable, Sequence
|
|
73
|
+
from typing import TypeVar, Union, overload
|
|
74
|
+
|
|
75
|
+
import numpy as np
|
|
76
|
+
import numpy.typing as npt
|
|
77
|
+
from typing_extensions import Self, TypeAlias, TypeIs, override
|
|
78
|
+
|
|
79
|
+
from manim.typing import (
|
|
80
|
+
FloatHSL,
|
|
81
|
+
FloatHSLLike,
|
|
82
|
+
FloatHSV,
|
|
83
|
+
FloatHSVA,
|
|
84
|
+
FloatHSVALike,
|
|
85
|
+
FloatHSVLike,
|
|
86
|
+
FloatRGB,
|
|
87
|
+
FloatRGBA,
|
|
88
|
+
FloatRGBALike,
|
|
89
|
+
FloatRGBLike,
|
|
90
|
+
IntRGB,
|
|
91
|
+
IntRGBA,
|
|
92
|
+
IntRGBALike,
|
|
93
|
+
IntRGBLike,
|
|
94
|
+
ManimColorDType,
|
|
95
|
+
ManimColorInternal,
|
|
96
|
+
ManimFloat,
|
|
97
|
+
Point3D,
|
|
98
|
+
Vector3D,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
from ...utils.space_ops import normalize
|
|
102
|
+
|
|
103
|
+
# import manim._config as _config
|
|
104
|
+
|
|
105
|
+
re_hex = re.compile("((?<=#)|(?<=0x))[A-F0-9]{3,8}", re.IGNORECASE)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ManimColor:
|
|
109
|
+
"""Internal representation of a color.
|
|
110
|
+
|
|
111
|
+
The :class:`ManimColor` class is the main class for the representation of a color.
|
|
112
|
+
Its internal representation is an array of 4 floats corresponding to a ``[r,g,b,a]``
|
|
113
|
+
value where ``r,g,b,a`` can be between 0.0 and 1.0.
|
|
114
|
+
|
|
115
|
+
This is done in order to reduce the amount of color inconsistencies by constantly
|
|
116
|
+
casting between integers and floats which introduces errors.
|
|
117
|
+
|
|
118
|
+
The class can accept any value of type :class:`ParsableManimColor` i.e.
|
|
119
|
+
|
|
120
|
+
``ManimColor, int, str, RGB_Tuple_Int, RGB_Tuple_Float, RGBA_Tuple_Int, RGBA_Tuple_Float, RGB_Array_Int,
|
|
121
|
+
RGB_Array_Float, RGBA_Array_Int, RGBA_Array_Float``
|
|
122
|
+
|
|
123
|
+
:class:`ManimColor` itself only accepts singular values and will directly interpret
|
|
124
|
+
them into a single color if possible. Be careful when passing strings to
|
|
125
|
+
:class:`ManimColor`: it can create a big overhead for the color processing.
|
|
126
|
+
|
|
127
|
+
If you want to parse a list of colors, use the :meth:`parse` method, which assumes
|
|
128
|
+
that you're going to pass a list of colors so that arrays will not be interpreted as
|
|
129
|
+
a single color.
|
|
130
|
+
|
|
131
|
+
.. warning::
|
|
132
|
+
If you pass an array of numbers to :meth:`parse`, it will interpret the
|
|
133
|
+
``r,g,b,a`` numbers in that array as colors: Instead of the expected
|
|
134
|
+
singular color, you will get an array with 4 colors.
|
|
135
|
+
|
|
136
|
+
For conversion behaviors, see the ``_internal`` functions for further documentation.
|
|
137
|
+
|
|
138
|
+
You can create a :class:`ManimColor` instance via its classmethods. See the
|
|
139
|
+
respective methods for more info.
|
|
140
|
+
|
|
141
|
+
.. code-block:: python
|
|
142
|
+
|
|
143
|
+
mycolor = ManimColor.from_rgb((0, 1, 0.4, 0.5))
|
|
144
|
+
myothercolor = ManimColor.from_rgb((153, 255, 255))
|
|
145
|
+
|
|
146
|
+
You can also convert between different color spaces:
|
|
147
|
+
|
|
148
|
+
.. code-block:: python
|
|
149
|
+
|
|
150
|
+
mycolor_hex = mycolor.to_hex()
|
|
151
|
+
myoriginalcolor = ManimColor.from_hex(mycolor_hex).to_hsv()
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
value
|
|
156
|
+
Some representation of a color (e.g., a string or
|
|
157
|
+
a suitable tuple). The default ``None`` is ``BLACK``.
|
|
158
|
+
alpha
|
|
159
|
+
The opacity of the color. By default, colors are
|
|
160
|
+
fully opaque (value 1.0).
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
def __init__(
|
|
164
|
+
self,
|
|
165
|
+
value: ParsableManimColor | None,
|
|
166
|
+
alpha: float = 1.0,
|
|
167
|
+
) -> None:
|
|
168
|
+
if value is None:
|
|
169
|
+
self._internal_value = np.array((0, 0, 0, alpha), dtype=ManimColorDType)
|
|
170
|
+
elif isinstance(value, ManimColor):
|
|
171
|
+
# logger.info(
|
|
172
|
+
# "ManimColor was passed another ManimColor. This is probably not what "
|
|
173
|
+
# "you want. Created a copy of the passed ManimColor instead."
|
|
174
|
+
# )
|
|
175
|
+
self._internal_value = value._internal_value
|
|
176
|
+
elif isinstance(value, int):
|
|
177
|
+
self._internal_value = ManimColor._internal_from_integer(value, alpha)
|
|
178
|
+
elif isinstance(value, str):
|
|
179
|
+
result = re_hex.search(value)
|
|
180
|
+
if result is not None:
|
|
181
|
+
self._internal_value = ManimColor._internal_from_hex_string(
|
|
182
|
+
result.group(), alpha
|
|
183
|
+
)
|
|
184
|
+
else:
|
|
185
|
+
# This is not expected to be called on module initialization time
|
|
186
|
+
# It can be horribly slow to convert a string to a color because
|
|
187
|
+
# it has to access the dictionary of colors and find the right color
|
|
188
|
+
self._internal_value = ManimColor._internal_from_string(value, alpha)
|
|
189
|
+
elif isinstance(value, (list, tuple, np.ndarray)):
|
|
190
|
+
length = len(value)
|
|
191
|
+
if all(isinstance(x, float) for x in value):
|
|
192
|
+
if length == 3:
|
|
193
|
+
self._internal_value = ManimColor._internal_from_rgb(value, alpha)
|
|
194
|
+
elif length == 4:
|
|
195
|
+
self._internal_value = ManimColor._internal_from_rgba(value)
|
|
196
|
+
else:
|
|
197
|
+
raise ValueError(
|
|
198
|
+
f"ManimColor only accepts lists/tuples/arrays of length 3 or 4, not {length}"
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
if length == 3:
|
|
202
|
+
self._internal_value = ManimColor._internal_from_int_rgb(
|
|
203
|
+
value, alpha
|
|
204
|
+
)
|
|
205
|
+
elif length == 4:
|
|
206
|
+
self._internal_value = ManimColor._internal_from_int_rgba(value)
|
|
207
|
+
else:
|
|
208
|
+
raise ValueError(
|
|
209
|
+
f"ManimColor only accepts lists/tuples/arrays of length 3 or 4, not {length}"
|
|
210
|
+
)
|
|
211
|
+
elif hasattr(value, "get_hex") and callable(value.get_hex):
|
|
212
|
+
result = re_hex.search(value.get_hex())
|
|
213
|
+
if result is None:
|
|
214
|
+
raise ValueError(f"Failed to parse a color from {value}")
|
|
215
|
+
self._internal_value = ManimColor._internal_from_hex_string(
|
|
216
|
+
result.group(), alpha
|
|
217
|
+
)
|
|
218
|
+
else:
|
|
219
|
+
# logger.error(f"Invalid color value: {value}")
|
|
220
|
+
raise TypeError(
|
|
221
|
+
"ManimColor only accepts int, str, list[int, int, int], "
|
|
222
|
+
"list[int, int, int, int], list[float, float, float], "
|
|
223
|
+
f"list[float, float, float, float], not {type(value)}"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def _internal_space(self) -> npt.NDArray[ManimFloat]:
|
|
228
|
+
"""This is a readonly property which is a custom representation for color space
|
|
229
|
+
operations. It is used for operators and can be used when implementing a custom
|
|
230
|
+
color space.
|
|
231
|
+
"""
|
|
232
|
+
return self._internal_value
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def _internal_value(self) -> ManimColorInternal:
|
|
236
|
+
"""Return the internal value of the current Manim color ``[r,g,b,a]`` float
|
|
237
|
+
array.
|
|
238
|
+
|
|
239
|
+
Returns
|
|
240
|
+
-------
|
|
241
|
+
ManimColorInternal
|
|
242
|
+
Internal color representation.
|
|
243
|
+
"""
|
|
244
|
+
return self.__value
|
|
245
|
+
|
|
246
|
+
@_internal_value.setter
|
|
247
|
+
def _internal_value(self, value: ManimColorInternal) -> None:
|
|
248
|
+
"""Overwrite the internal color value of this :class:`ManimColor`.
|
|
249
|
+
|
|
250
|
+
Parameters
|
|
251
|
+
----------
|
|
252
|
+
value
|
|
253
|
+
The value which will overwrite the current color.
|
|
254
|
+
|
|
255
|
+
Raises
|
|
256
|
+
------
|
|
257
|
+
TypeError
|
|
258
|
+
If an invalid array is passed.
|
|
259
|
+
"""
|
|
260
|
+
if not isinstance(value, np.ndarray):
|
|
261
|
+
raise TypeError("Value must be a NumPy array.")
|
|
262
|
+
if value.shape[0] != 4:
|
|
263
|
+
raise TypeError("Array must have exactly 4 values.")
|
|
264
|
+
self.__value: ManimColorInternal = value
|
|
265
|
+
|
|
266
|
+
@classmethod
|
|
267
|
+
def _construct_from_space(
|
|
268
|
+
cls,
|
|
269
|
+
_space: npt.NDArray[ManimFloat]
|
|
270
|
+
| tuple[float, float, float]
|
|
271
|
+
| tuple[float, float, float, float],
|
|
272
|
+
) -> Self:
|
|
273
|
+
"""This function is used as a proxy for constructing a color with an internal
|
|
274
|
+
value. This can be used by subclasses to hook into the construction of new
|
|
275
|
+
objects using the internal value format.
|
|
276
|
+
"""
|
|
277
|
+
return cls(_space)
|
|
278
|
+
|
|
279
|
+
@staticmethod
|
|
280
|
+
def _internal_from_integer(value: int, alpha: float) -> ManimColorInternal:
|
|
281
|
+
return np.asarray(
|
|
282
|
+
(
|
|
283
|
+
((value >> 16) & 0xFF) / 255,
|
|
284
|
+
((value >> 8) & 0xFF) / 255,
|
|
285
|
+
((value >> 0) & 0xFF) / 255,
|
|
286
|
+
alpha,
|
|
287
|
+
),
|
|
288
|
+
dtype=ManimColorDType,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
@staticmethod
|
|
292
|
+
def _internal_from_hex_string(hex_: str, alpha: float) -> ManimColorInternal:
|
|
293
|
+
"""Internal function for converting a hex string into the internal representation
|
|
294
|
+
of a :class:`ManimColor`.
|
|
295
|
+
|
|
296
|
+
.. warning::
|
|
297
|
+
This does not accept any prefixes like # or similar in front of the hex string.
|
|
298
|
+
This is just intended for the raw hex part.
|
|
299
|
+
|
|
300
|
+
*For internal use only*
|
|
301
|
+
|
|
302
|
+
Parameters
|
|
303
|
+
----------
|
|
304
|
+
hex
|
|
305
|
+
Hex string to be parsed.
|
|
306
|
+
alpha
|
|
307
|
+
Alpha value used for the color, if the color is only 3 bytes long. Otherwise,
|
|
308
|
+
if the color is 4 bytes long, this parameter will not be used.
|
|
309
|
+
|
|
310
|
+
Returns
|
|
311
|
+
-------
|
|
312
|
+
ManimColorInternal
|
|
313
|
+
Internal color representation
|
|
314
|
+
"""
|
|
315
|
+
if len(hex_) in (3, 4):
|
|
316
|
+
hex_ = "".join([x * 2 for x in hex_])
|
|
317
|
+
if len(hex_) == 6:
|
|
318
|
+
hex_ += "FF"
|
|
319
|
+
elif len(hex_) == 8:
|
|
320
|
+
alpha = (int(hex_, 16) & 0xFF) / 255
|
|
321
|
+
else:
|
|
322
|
+
raise ValueError(
|
|
323
|
+
"Hex colors must be specified with either 0x or # as prefix and contain 6 or 8 hexadecimal numbers"
|
|
324
|
+
)
|
|
325
|
+
tmp = int(hex_, 16)
|
|
326
|
+
return np.asarray(
|
|
327
|
+
(
|
|
328
|
+
((tmp >> 24) & 0xFF) / 255,
|
|
329
|
+
((tmp >> 16) & 0xFF) / 255,
|
|
330
|
+
((tmp >> 8) & 0xFF) / 255,
|
|
331
|
+
alpha,
|
|
332
|
+
),
|
|
333
|
+
dtype=ManimColorDType,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
@staticmethod
|
|
337
|
+
def _internal_from_int_rgb(
|
|
338
|
+
rgb: IntRGBLike, alpha: float = 1.0
|
|
339
|
+
) -> ManimColorInternal:
|
|
340
|
+
"""Internal function for converting an RGB tuple of integers into the internal
|
|
341
|
+
representation of a :class:`ManimColor`.
|
|
342
|
+
|
|
343
|
+
*For internal use only*
|
|
344
|
+
|
|
345
|
+
Parameters
|
|
346
|
+
----------
|
|
347
|
+
rgb
|
|
348
|
+
Integer RGB tuple to be parsed
|
|
349
|
+
alpha
|
|
350
|
+
Optional alpha value. Default is 1.0.
|
|
351
|
+
|
|
352
|
+
Returns
|
|
353
|
+
-------
|
|
354
|
+
ManimColorInternal
|
|
355
|
+
Internal color representation.
|
|
356
|
+
"""
|
|
357
|
+
value: np.ndarray = np.asarray(rgb, dtype=ManimColorDType).copy() / 255
|
|
358
|
+
value.resize(4, refcheck=False)
|
|
359
|
+
value[3] = alpha
|
|
360
|
+
return value
|
|
361
|
+
|
|
362
|
+
@staticmethod
|
|
363
|
+
def _internal_from_rgb(rgb: FloatRGBLike, alpha: float = 1.0) -> ManimColorInternal:
|
|
364
|
+
"""Internal function for converting a rgb tuple of floats into the internal
|
|
365
|
+
representation of a :class:`ManimColor`.
|
|
366
|
+
|
|
367
|
+
*For internal use only*
|
|
368
|
+
|
|
369
|
+
Parameters
|
|
370
|
+
----------
|
|
371
|
+
rgb
|
|
372
|
+
Float RGB tuple to be parsed.
|
|
373
|
+
alpha
|
|
374
|
+
Optional alpha value. Default is 1.0.
|
|
375
|
+
|
|
376
|
+
Returns
|
|
377
|
+
-------
|
|
378
|
+
ManimColorInternal
|
|
379
|
+
Internal color representation.
|
|
380
|
+
"""
|
|
381
|
+
value: np.ndarray = np.asarray(rgb, dtype=ManimColorDType).copy()
|
|
382
|
+
value.resize(4, refcheck=False)
|
|
383
|
+
value[3] = alpha
|
|
384
|
+
return value
|
|
385
|
+
|
|
386
|
+
@staticmethod
|
|
387
|
+
def _internal_from_int_rgba(rgba: IntRGBALike) -> ManimColorInternal:
|
|
388
|
+
"""Internal function for converting an RGBA tuple of integers into the internal
|
|
389
|
+
representation of a :class:`ManimColor`.
|
|
390
|
+
|
|
391
|
+
*For internal use only*
|
|
392
|
+
|
|
393
|
+
Parameters
|
|
394
|
+
----------
|
|
395
|
+
rgba
|
|
396
|
+
Int RGBA tuple to be parsed.
|
|
397
|
+
|
|
398
|
+
Returns
|
|
399
|
+
-------
|
|
400
|
+
ManimColorInternal
|
|
401
|
+
Internal color representation.
|
|
402
|
+
"""
|
|
403
|
+
return np.asarray(rgba, dtype=ManimColorDType) / 255
|
|
404
|
+
|
|
405
|
+
@staticmethod
|
|
406
|
+
def _internal_from_rgba(rgba: FloatRGBALike) -> ManimColorInternal:
|
|
407
|
+
"""Internal function for converting an RGBA tuple of floats into the internal
|
|
408
|
+
representation of a :class:`ManimColor`.
|
|
409
|
+
|
|
410
|
+
*For internal use only*
|
|
411
|
+
|
|
412
|
+
Parameters
|
|
413
|
+
----------
|
|
414
|
+
rgba
|
|
415
|
+
Int RGBA tuple to be parsed.
|
|
416
|
+
|
|
417
|
+
Returns
|
|
418
|
+
-------
|
|
419
|
+
ManimColorInternal
|
|
420
|
+
Internal color representation.
|
|
421
|
+
"""
|
|
422
|
+
return np.asarray(rgba, dtype=ManimColorDType)
|
|
423
|
+
|
|
424
|
+
@staticmethod
|
|
425
|
+
def _internal_from_string(name: str, alpha: float) -> ManimColorInternal:
|
|
426
|
+
"""Internal function for converting a string into the internal representation of
|
|
427
|
+
a :class:`ManimColor`. This is not used for hex strings: please refer to
|
|
428
|
+
:meth:`_internal_from_hex` for this functionality.
|
|
429
|
+
|
|
430
|
+
*For internal use only*
|
|
431
|
+
|
|
432
|
+
Parameters
|
|
433
|
+
----------
|
|
434
|
+
name
|
|
435
|
+
The color name to be parsed into a color. Refer to the different color
|
|
436
|
+
modules in the documentation page to find the corresponding color names.
|
|
437
|
+
|
|
438
|
+
Returns
|
|
439
|
+
-------
|
|
440
|
+
ManimColorInternal
|
|
441
|
+
Internal color representation.
|
|
442
|
+
|
|
443
|
+
Raises
|
|
444
|
+
------
|
|
445
|
+
ValueError
|
|
446
|
+
If the color name is not present in Manim.
|
|
447
|
+
"""
|
|
448
|
+
from . import _all_color_dict
|
|
449
|
+
|
|
450
|
+
if tmp := _all_color_dict.get(name.upper()):
|
|
451
|
+
tmp._internal_value[3] = alpha
|
|
452
|
+
return tmp._internal_value.copy()
|
|
453
|
+
else:
|
|
454
|
+
raise ValueError(f"Color {name} not found")
|
|
455
|
+
|
|
456
|
+
def to_integer(self) -> int:
|
|
457
|
+
"""Convert the current :class:`ManimColor` into an integer.
|
|
458
|
+
|
|
459
|
+
.. warning::
|
|
460
|
+
This will return only the RGB part of the color.
|
|
461
|
+
|
|
462
|
+
Returns
|
|
463
|
+
-------
|
|
464
|
+
int
|
|
465
|
+
Integer representation of the color.
|
|
466
|
+
"""
|
|
467
|
+
tmp = (self._internal_value[:3] * 255).astype(dtype=np.byte).tobytes()
|
|
468
|
+
return int.from_bytes(tmp, "big")
|
|
469
|
+
|
|
470
|
+
def to_rgb(self) -> FloatRGB:
|
|
471
|
+
"""Convert the current :class:`ManimColor` into an RGB array of floats.
|
|
472
|
+
|
|
473
|
+
Returns
|
|
474
|
+
-------
|
|
475
|
+
RGB_Array_Float
|
|
476
|
+
RGB array of 3 floats from 0.0 to 1.0.
|
|
477
|
+
"""
|
|
478
|
+
return self._internal_value[:3]
|
|
479
|
+
|
|
480
|
+
def to_int_rgb(self) -> IntRGB:
|
|
481
|
+
"""Convert the current :class:`ManimColor` into an RGB array of integers.
|
|
482
|
+
|
|
483
|
+
Returns
|
|
484
|
+
-------
|
|
485
|
+
RGB_Array_Int
|
|
486
|
+
RGB array of 3 integers from 0 to 255.
|
|
487
|
+
"""
|
|
488
|
+
return (self._internal_value[:3] * 255).astype(int)
|
|
489
|
+
|
|
490
|
+
def to_rgba(self) -> FloatRGBA:
|
|
491
|
+
"""Convert the current :class:`ManimColor` into an RGBA array of floats.
|
|
492
|
+
|
|
493
|
+
Returns
|
|
494
|
+
-------
|
|
495
|
+
RGBA_Array_Float
|
|
496
|
+
RGBA array of 4 floats from 0.0 to 1.0.
|
|
497
|
+
"""
|
|
498
|
+
return self._internal_value
|
|
499
|
+
|
|
500
|
+
def to_int_rgba(self) -> IntRGBA:
|
|
501
|
+
"""Convert the current ManimColor into an RGBA array of integers.
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
Returns
|
|
505
|
+
-------
|
|
506
|
+
RGBA_Array_Int
|
|
507
|
+
RGBA array of 4 integers from 0 to 255.
|
|
508
|
+
"""
|
|
509
|
+
return (self._internal_value * 255).astype(int)
|
|
510
|
+
|
|
511
|
+
def to_rgba_with_alpha(self, alpha: float) -> FloatRGBA:
|
|
512
|
+
"""Convert the current :class:`ManimColor` into an RGBA array of floats. This is
|
|
513
|
+
similar to :meth:`to_rgba`, but you can change the alpha value.
|
|
514
|
+
|
|
515
|
+
Parameters
|
|
516
|
+
----------
|
|
517
|
+
alpha
|
|
518
|
+
Alpha value to be used in the return value.
|
|
519
|
+
|
|
520
|
+
Returns
|
|
521
|
+
-------
|
|
522
|
+
RGBA_Array_Float
|
|
523
|
+
RGBA array of 4 floats from 0.0 to 1.0.
|
|
524
|
+
"""
|
|
525
|
+
return np.fromiter((*self._internal_value[:3], alpha), dtype=ManimColorDType)
|
|
526
|
+
|
|
527
|
+
def to_int_rgba_with_alpha(self, alpha: float) -> IntRGBA:
|
|
528
|
+
"""Convert the current :class:`ManimColor` into an RGBA array of integers. This
|
|
529
|
+
is similar to :meth:`to_int_rgba`, but you can change the alpha value.
|
|
530
|
+
|
|
531
|
+
Parameters
|
|
532
|
+
----------
|
|
533
|
+
alpha
|
|
534
|
+
Alpha value to be used for the return value. Pass a float between 0.0 and
|
|
535
|
+
1.0: it will automatically be scaled to an integer between 0 and 255.
|
|
536
|
+
|
|
537
|
+
Returns
|
|
538
|
+
-------
|
|
539
|
+
RGBA_Array_Int
|
|
540
|
+
RGBA array of 4 integers from 0 to 255.
|
|
541
|
+
"""
|
|
542
|
+
tmp = self._internal_value * 255
|
|
543
|
+
tmp[3] = alpha * 255
|
|
544
|
+
return tmp.astype(int)
|
|
545
|
+
|
|
546
|
+
def to_hex(self, with_alpha: bool = False) -> str:
|
|
547
|
+
"""Convert the :class:`ManimColor` to a hexadecimal representation of the color.
|
|
548
|
+
|
|
549
|
+
Parameters
|
|
550
|
+
----------
|
|
551
|
+
with_alpha
|
|
552
|
+
If ``True``, append 2 extra characters to the hex string which represent the
|
|
553
|
+
alpha value of the color between 0 and 255. Default is ``False``.
|
|
554
|
+
|
|
555
|
+
Returns
|
|
556
|
+
-------
|
|
557
|
+
str
|
|
558
|
+
A hex string starting with a ``#``, with either 6 or 8 nibbles depending on
|
|
559
|
+
the ``with_alpha`` parameter. By default, it has 6 nibbles, i.e. ``#XXXXXX``.
|
|
560
|
+
"""
|
|
561
|
+
tmp = (
|
|
562
|
+
f"#{int(self._internal_value[0] * 255):02X}"
|
|
563
|
+
f"{int(self._internal_value[1] * 255):02X}"
|
|
564
|
+
f"{int(self._internal_value[2] * 255):02X}"
|
|
565
|
+
)
|
|
566
|
+
if with_alpha:
|
|
567
|
+
tmp += f"{int(self._internal_value[3] * 255):02X}"
|
|
568
|
+
return tmp
|
|
569
|
+
|
|
570
|
+
def to_hsv(self) -> FloatHSV:
|
|
571
|
+
"""Convert the :class:`ManimColor` to an HSV array.
|
|
572
|
+
|
|
573
|
+
.. note::
|
|
574
|
+
Be careful: this returns an array in the form ``[h, s, v]``, where the
|
|
575
|
+
elements are floats. This might be confusing, because RGB can also be an array
|
|
576
|
+
of floats. You might want to annotate the usage of this function in your code
|
|
577
|
+
by typing your HSV array variables as :class:`HSV_Array_Float` in order to
|
|
578
|
+
differentiate them from RGB arrays.
|
|
579
|
+
|
|
580
|
+
Returns
|
|
581
|
+
-------
|
|
582
|
+
HSV_Array_Float
|
|
583
|
+
An HSV array of 3 floats from 0.0 to 1.0.
|
|
584
|
+
"""
|
|
585
|
+
return np.array(colorsys.rgb_to_hsv(*self.to_rgb()))
|
|
586
|
+
|
|
587
|
+
def to_hsl(self) -> FloatHSL:
|
|
588
|
+
"""Convert the :class:`ManimColor` to an HSL array.
|
|
589
|
+
|
|
590
|
+
.. note::
|
|
591
|
+
Be careful: this returns an array in the form ``[h, s, l]``, where the
|
|
592
|
+
elements are floats. This might be confusing, because RGB can also be an array
|
|
593
|
+
of floats. You might want to annotate the usage of this function in your code
|
|
594
|
+
by typing your HSL array variables as :class:`HSL_Array_Float` in order to
|
|
595
|
+
differentiate them from RGB arrays.
|
|
596
|
+
|
|
597
|
+
Returns
|
|
598
|
+
-------
|
|
599
|
+
HSL_Array_Float
|
|
600
|
+
An HSL array of 3 floats from 0.0 to 1.0.
|
|
601
|
+
"""
|
|
602
|
+
hls = colorsys.rgb_to_hls(*self.to_rgb())
|
|
603
|
+
return np.array([hls[0], hls[2], hls[1]])
|
|
604
|
+
|
|
605
|
+
def invert(self, with_alpha: bool = False) -> Self:
|
|
606
|
+
"""Return a new, linearly inverted version of this :class:`ManimColor` (no
|
|
607
|
+
inplace changes).
|
|
608
|
+
|
|
609
|
+
Parameters
|
|
610
|
+
----------
|
|
611
|
+
with_alpha
|
|
612
|
+
If ``True``, the alpha value will be inverted too. Default is ``False``.
|
|
613
|
+
|
|
614
|
+
.. note::
|
|
615
|
+
Setting ``with_alpha=True`` can result in unintended behavior where
|
|
616
|
+
objects are not displayed because their new alpha value is suddenly 0 or
|
|
617
|
+
very low.
|
|
618
|
+
|
|
619
|
+
Returns
|
|
620
|
+
-------
|
|
621
|
+
ManimColor
|
|
622
|
+
The linearly inverted :class:`ManimColor`.
|
|
623
|
+
"""
|
|
624
|
+
if with_alpha:
|
|
625
|
+
return self._construct_from_space(1.0 - self._internal_space)
|
|
626
|
+
else:
|
|
627
|
+
alpha = self._internal_space[3]
|
|
628
|
+
new = 1.0 - self._internal_space
|
|
629
|
+
new[-1] = alpha
|
|
630
|
+
return self._construct_from_space(new)
|
|
631
|
+
|
|
632
|
+
def interpolate(self, other: Self, alpha: float) -> Self:
|
|
633
|
+
"""Interpolate between the current and the given :class:`ManimColor`, and return
|
|
634
|
+
the result.
|
|
635
|
+
|
|
636
|
+
Parameters
|
|
637
|
+
----------
|
|
638
|
+
other
|
|
639
|
+
The other :class:`ManimColor` to be used for interpolation.
|
|
640
|
+
alpha
|
|
641
|
+
A point on the line in RGBA colorspace connecting the two colors, i.e. the
|
|
642
|
+
interpolation point. 0.0 corresponds to the current :class:`ManimColor` and
|
|
643
|
+
1.0 corresponds to the other :class:`ManimColor`.
|
|
644
|
+
|
|
645
|
+
Returns
|
|
646
|
+
-------
|
|
647
|
+
ManimColor
|
|
648
|
+
The interpolated :class:`ManimColor`.
|
|
649
|
+
"""
|
|
650
|
+
return self._construct_from_space(
|
|
651
|
+
self._internal_space * (1 - alpha) + other._internal_space * alpha
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
def darker(self, blend: float = 0.2) -> Self:
|
|
655
|
+
"""Return a new color that is darker than the current color, i.e.
|
|
656
|
+
interpolated with ``BLACK``. The opacity is unchanged.
|
|
657
|
+
|
|
658
|
+
Parameters
|
|
659
|
+
----------
|
|
660
|
+
blend
|
|
661
|
+
The blend ratio for the interpolation, from 0.0 (the current color
|
|
662
|
+
unchanged) to 1.0 (pure black). Default is 0.2, which results in a
|
|
663
|
+
slightly darker color.
|
|
664
|
+
|
|
665
|
+
Returns
|
|
666
|
+
-------
|
|
667
|
+
ManimColor
|
|
668
|
+
The darker :class:`ManimColor`.
|
|
669
|
+
|
|
670
|
+
See Also
|
|
671
|
+
--------
|
|
672
|
+
:meth:`lighter`
|
|
673
|
+
"""
|
|
674
|
+
from manim.utils.color.manim_colors import BLACK
|
|
675
|
+
|
|
676
|
+
alpha = self._internal_space[3]
|
|
677
|
+
black = self._from_internal(BLACK._internal_value)
|
|
678
|
+
return self.interpolate(black, blend).opacity(alpha)
|
|
679
|
+
|
|
680
|
+
def lighter(self, blend: float = 0.2) -> Self:
|
|
681
|
+
"""Return a new color that is lighter than the current color, i.e.
|
|
682
|
+
interpolated with ``WHITE``. The opacity is unchanged.
|
|
683
|
+
|
|
684
|
+
Parameters
|
|
685
|
+
----------
|
|
686
|
+
blend
|
|
687
|
+
The blend ratio for the interpolation, from 0.0 (the current color
|
|
688
|
+
unchanged) to 1.0 (pure white). Default is 0.2, which results in a
|
|
689
|
+
slightly lighter color.
|
|
690
|
+
|
|
691
|
+
Returns
|
|
692
|
+
-------
|
|
693
|
+
ManimColor
|
|
694
|
+
The lighter :class:`ManimColor`.
|
|
695
|
+
|
|
696
|
+
See Also
|
|
697
|
+
--------
|
|
698
|
+
:meth:`darker`
|
|
699
|
+
"""
|
|
700
|
+
from manim.utils.color.manim_colors import WHITE
|
|
701
|
+
|
|
702
|
+
alpha = self._internal_space[3]
|
|
703
|
+
white = self._from_internal(WHITE._internal_value)
|
|
704
|
+
return self.interpolate(white, blend).opacity(alpha)
|
|
705
|
+
|
|
706
|
+
def contrasting(
|
|
707
|
+
self,
|
|
708
|
+
threshold: float = 0.5,
|
|
709
|
+
light: Self | None = None,
|
|
710
|
+
dark: Self | None = None,
|
|
711
|
+
) -> Self:
|
|
712
|
+
"""Return one of two colors, light or dark (by default white or black),
|
|
713
|
+
that contrasts with the current color (depending on its luminance).
|
|
714
|
+
This is typically used to set text in a contrasting color that ensures
|
|
715
|
+
it is readable against a background of the current color.
|
|
716
|
+
|
|
717
|
+
Parameters
|
|
718
|
+
----------
|
|
719
|
+
threshold
|
|
720
|
+
The luminance threshold which dictates whether the current color is
|
|
721
|
+
considered light or dark (and thus whether to return the dark or
|
|
722
|
+
light color, respectively). Default is 0.5.
|
|
723
|
+
light
|
|
724
|
+
The light color to return if the current color is considered dark.
|
|
725
|
+
Default is ``None``: in this case, pure ``WHITE`` will be returned.
|
|
726
|
+
dark
|
|
727
|
+
The dark color to return if the current color is considered light,
|
|
728
|
+
Default is ``None``: in this case, pure ``BLACK`` will be returned.
|
|
729
|
+
|
|
730
|
+
Returns
|
|
731
|
+
-------
|
|
732
|
+
ManimColor
|
|
733
|
+
The contrasting :class:`ManimColor`.
|
|
734
|
+
"""
|
|
735
|
+
from manim.utils.color.manim_colors import BLACK, WHITE
|
|
736
|
+
|
|
737
|
+
luminance, _, _ = colorsys.rgb_to_yiq(*self.to_rgb())
|
|
738
|
+
if luminance < threshold:
|
|
739
|
+
if light is not None:
|
|
740
|
+
return light
|
|
741
|
+
return self._from_internal(WHITE._internal_value)
|
|
742
|
+
else:
|
|
743
|
+
if dark is not None:
|
|
744
|
+
return dark
|
|
745
|
+
return self._from_internal(BLACK._internal_value)
|
|
746
|
+
|
|
747
|
+
def opacity(self, opacity: float) -> Self:
|
|
748
|
+
"""Create a new :class:`ManimColor` with the given opacity and the same color
|
|
749
|
+
values as before.
|
|
750
|
+
|
|
751
|
+
Parameters
|
|
752
|
+
----------
|
|
753
|
+
opacity
|
|
754
|
+
The new opacity value to be used.
|
|
755
|
+
|
|
756
|
+
Returns
|
|
757
|
+
-------
|
|
758
|
+
ManimColor
|
|
759
|
+
The new :class:`ManimColor` with the same color values and the new opacity.
|
|
760
|
+
"""
|
|
761
|
+
tmp = self._internal_space.copy()
|
|
762
|
+
tmp[-1] = opacity
|
|
763
|
+
return self._construct_from_space(tmp)
|
|
764
|
+
|
|
765
|
+
def into(self, class_type: type[ManimColorT]) -> ManimColorT:
|
|
766
|
+
"""Convert the current color into a different colorspace given by ``class_type``,
|
|
767
|
+
without changing the :attr:`_internal_value`.
|
|
768
|
+
|
|
769
|
+
Parameters
|
|
770
|
+
----------
|
|
771
|
+
class_type
|
|
772
|
+
The class that is used for conversion. It must be a subclass of
|
|
773
|
+
:class:`ManimColor` which respects the specification HSV, RGBA, ...
|
|
774
|
+
|
|
775
|
+
Returns
|
|
776
|
+
-------
|
|
777
|
+
ManimColorT
|
|
778
|
+
A new color object of type ``class_type`` and the same
|
|
779
|
+
:attr:`_internal_value` as the original color.
|
|
780
|
+
"""
|
|
781
|
+
return class_type._from_internal(self._internal_value)
|
|
782
|
+
|
|
783
|
+
@classmethod
|
|
784
|
+
def _from_internal(cls, value: ManimColorInternal) -> Self:
|
|
785
|
+
"""This method is intended to be overwritten by custom color space classes
|
|
786
|
+
which are subtypes of :class:`ManimColor`.
|
|
787
|
+
|
|
788
|
+
The method constructs a new object of the given class by transforming the value
|
|
789
|
+
in the internal format ``[r,g,b,a]`` into a format which the constructor of the
|
|
790
|
+
custom class can understand. Look at :class:`.HSV` for an example.
|
|
791
|
+
"""
|
|
792
|
+
return cls(value)
|
|
793
|
+
|
|
794
|
+
@classmethod
|
|
795
|
+
def from_rgb(
|
|
796
|
+
cls,
|
|
797
|
+
rgb: FloatRGBLike | IntRGBLike,
|
|
798
|
+
alpha: float = 1.0,
|
|
799
|
+
) -> Self:
|
|
800
|
+
"""Create a ManimColor from an RGB array. Automagically decides which type it
|
|
801
|
+
is: ``int`` or ``float``.
|
|
802
|
+
|
|
803
|
+
.. warning::
|
|
804
|
+
Please make sure that your elements are not floats if you want integers. A
|
|
805
|
+
``5.0`` will result in the input being interpreted as if it was an RGB float
|
|
806
|
+
array with the value ``5.0`` and not the integer ``5``.
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
Parameters
|
|
810
|
+
----------
|
|
811
|
+
rgb
|
|
812
|
+
Any iterable of 3 floats or 3 integers.
|
|
813
|
+
alpha
|
|
814
|
+
Alpha value to be used in the color. Default is 1.0.
|
|
815
|
+
|
|
816
|
+
Returns
|
|
817
|
+
-------
|
|
818
|
+
ManimColor
|
|
819
|
+
The :class:`ManimColor` which corresponds to the given ``rgb``.
|
|
820
|
+
"""
|
|
821
|
+
return cls._from_internal(ManimColor(rgb, alpha)._internal_value)
|
|
822
|
+
|
|
823
|
+
@classmethod
|
|
824
|
+
def from_rgba(cls, rgba: FloatRGBALike | IntRGBALike) -> Self:
|
|
825
|
+
"""Create a ManimColor from an RGBA Array. Automagically decides which type it
|
|
826
|
+
is: ``int`` or ``float``.
|
|
827
|
+
|
|
828
|
+
.. warning::
|
|
829
|
+
Please make sure that your elements are not floats if you want integers. A
|
|
830
|
+
``5.0`` will result in the input being interpreted as if it was a float RGB
|
|
831
|
+
array with the float ``5.0`` and not the integer ``5``.
|
|
832
|
+
|
|
833
|
+
Parameters
|
|
834
|
+
----------
|
|
835
|
+
rgba
|
|
836
|
+
Any iterable of 4 floats or 4 integers.
|
|
837
|
+
|
|
838
|
+
Returns
|
|
839
|
+
-------
|
|
840
|
+
ManimColor
|
|
841
|
+
The :class:`ManimColor` corresponding to the given ``rgba``.
|
|
842
|
+
"""
|
|
843
|
+
return cls(rgba)
|
|
844
|
+
|
|
845
|
+
@classmethod
|
|
846
|
+
def from_hex(cls, hex_str: str, alpha: float = 1.0) -> Self:
|
|
847
|
+
"""Create a :class:`ManimColor` from a hex string.
|
|
848
|
+
|
|
849
|
+
Parameters
|
|
850
|
+
----------
|
|
851
|
+
hex_str
|
|
852
|
+
The hex string to be converted. The allowed prefixes for this string are
|
|
853
|
+
``#`` and ``0x``. Currently, this method only supports 6 nibbles, i.e. only
|
|
854
|
+
strings in the format ``#XXXXXX`` or ``0xXXXXXX``.
|
|
855
|
+
alpha
|
|
856
|
+
Alpha value to be used for the hex string. Default is 1.0.
|
|
857
|
+
|
|
858
|
+
Returns
|
|
859
|
+
-------
|
|
860
|
+
ManimColor
|
|
861
|
+
The :class:`ManimColor` represented by the hex string.
|
|
862
|
+
"""
|
|
863
|
+
return cls._from_internal(ManimColor(hex_str, alpha)._internal_value)
|
|
864
|
+
|
|
865
|
+
@classmethod
|
|
866
|
+
def from_hsv(cls, hsv: FloatHSVLike, alpha: float = 1.0) -> Self:
|
|
867
|
+
"""Create a :class:`ManimColor` from an HSV array.
|
|
868
|
+
|
|
869
|
+
Parameters
|
|
870
|
+
----------
|
|
871
|
+
hsv
|
|
872
|
+
Any iterable containing 3 floats from 0.0 to 1.0.
|
|
873
|
+
alpha
|
|
874
|
+
The alpha value to be used. Default is 1.0.
|
|
875
|
+
|
|
876
|
+
Returns
|
|
877
|
+
-------
|
|
878
|
+
ManimColor
|
|
879
|
+
The :class:`ManimColor` with the corresponding RGB values to the given HSV
|
|
880
|
+
array.
|
|
881
|
+
"""
|
|
882
|
+
rgb = colorsys.hsv_to_rgb(*hsv)
|
|
883
|
+
return cls._from_internal(ManimColor(rgb, alpha)._internal_value)
|
|
884
|
+
|
|
885
|
+
@classmethod
|
|
886
|
+
def from_hsl(cls, hsl: FloatHSLLike, alpha: float = 1.0) -> Self:
|
|
887
|
+
"""Create a :class:`ManimColor` from an HSL array.
|
|
888
|
+
|
|
889
|
+
Parameters
|
|
890
|
+
----------
|
|
891
|
+
hsl
|
|
892
|
+
Any iterable containing 3 floats from 0.0 to 1.0.
|
|
893
|
+
alpha
|
|
894
|
+
The alpha value to be used. Default is 1.0.
|
|
895
|
+
|
|
896
|
+
Returns
|
|
897
|
+
-------
|
|
898
|
+
ManimColor
|
|
899
|
+
The :class:`ManimColor` with the corresponding RGB values to the given HSL
|
|
900
|
+
array.
|
|
901
|
+
"""
|
|
902
|
+
rgb = colorsys.hls_to_rgb(hsl[0], hsl[2], hsl[1])
|
|
903
|
+
return cls._from_internal(ManimColor(rgb, alpha)._internal_value)
|
|
904
|
+
|
|
905
|
+
@overload
|
|
906
|
+
@classmethod
|
|
907
|
+
def parse(
|
|
908
|
+
cls,
|
|
909
|
+
color: ParsableManimColor | None,
|
|
910
|
+
alpha: float = ...,
|
|
911
|
+
) -> Self: ...
|
|
912
|
+
|
|
913
|
+
@overload
|
|
914
|
+
@classmethod
|
|
915
|
+
def parse(
|
|
916
|
+
cls,
|
|
917
|
+
color: Sequence[ParsableManimColor],
|
|
918
|
+
alpha: float = ...,
|
|
919
|
+
) -> list[Self]: ...
|
|
920
|
+
|
|
921
|
+
@classmethod
|
|
922
|
+
def parse(
|
|
923
|
+
cls,
|
|
924
|
+
color: ParsableManimColor | Sequence[ParsableManimColor] | None,
|
|
925
|
+
alpha: float = 1.0,
|
|
926
|
+
) -> Self | list[Self]:
|
|
927
|
+
"""Parse one color as a :class:`ManimColor` or a sequence of colors as a list of
|
|
928
|
+
:class:`ManimColor`'s.
|
|
929
|
+
|
|
930
|
+
Parameters
|
|
931
|
+
----------
|
|
932
|
+
color
|
|
933
|
+
The color or list of colors to parse. Note that this function can not accept
|
|
934
|
+
tuples: it will assume that you mean ``Sequence[ParsableManimColor]`` and will
|
|
935
|
+
return a ``list[ManimColor]``.
|
|
936
|
+
alpha
|
|
937
|
+
The alpha (opacity) value to use for the passed color(s).
|
|
938
|
+
|
|
939
|
+
Returns
|
|
940
|
+
-------
|
|
941
|
+
ManimColor | list[ManimColor]
|
|
942
|
+
Either a list of colors or a singular color, depending on the input.
|
|
943
|
+
"""
|
|
944
|
+
|
|
945
|
+
def is_sequence(
|
|
946
|
+
color: ParsableManimColor | Sequence[ParsableManimColor] | None,
|
|
947
|
+
) -> TypeIs[Sequence[ParsableManimColor]]:
|
|
948
|
+
return isinstance(color, (list, tuple))
|
|
949
|
+
|
|
950
|
+
if is_sequence(color):
|
|
951
|
+
return [
|
|
952
|
+
cls._from_internal(ManimColor(c, alpha)._internal_value) for c in color
|
|
953
|
+
]
|
|
954
|
+
else:
|
|
955
|
+
return cls._from_internal(ManimColor(color, alpha)._internal_value)
|
|
956
|
+
|
|
957
|
+
@staticmethod
|
|
958
|
+
def gradient(
|
|
959
|
+
colors: list[ManimColor], length: int
|
|
960
|
+
) -> ManimColor | list[ManimColor]:
|
|
961
|
+
"""This method is currently not implemented. Refer to :func:`color_gradient` for
|
|
962
|
+
a working implementation for now.
|
|
963
|
+
"""
|
|
964
|
+
# TODO: implement proper gradient, research good implementation for this or look at 3b1b implementation
|
|
965
|
+
raise NotImplementedError
|
|
966
|
+
|
|
967
|
+
def __repr__(self) -> str:
|
|
968
|
+
return f"{self.__class__.__name__}('{self.to_hex()}')"
|
|
969
|
+
|
|
970
|
+
def __str__(self) -> str:
|
|
971
|
+
return f"{self.to_hex()}"
|
|
972
|
+
|
|
973
|
+
def __eq__(self, other: object) -> bool:
|
|
974
|
+
if not isinstance(other, ManimColor):
|
|
975
|
+
raise TypeError(
|
|
976
|
+
f"Cannot compare {self.__class__.__name__} with {other.__class__.__name__}"
|
|
977
|
+
)
|
|
978
|
+
are_equal: bool = np.allclose(self._internal_value, other._internal_value)
|
|
979
|
+
return are_equal
|
|
980
|
+
|
|
981
|
+
def __add__(self, other: int | float | Self) -> Self:
|
|
982
|
+
if isinstance(other, (int, float)):
|
|
983
|
+
return self._construct_from_space(self._internal_space + other)
|
|
984
|
+
else:
|
|
985
|
+
return self._construct_from_space(
|
|
986
|
+
self._internal_space + other._internal_space
|
|
987
|
+
)
|
|
988
|
+
|
|
989
|
+
def __radd__(self, other: int | float | Self) -> Self:
|
|
990
|
+
return self + other
|
|
991
|
+
|
|
992
|
+
def __sub__(self, other: int | float | Self) -> Self:
|
|
993
|
+
if isinstance(other, (int, float)):
|
|
994
|
+
return self._construct_from_space(self._internal_space - other)
|
|
995
|
+
else:
|
|
996
|
+
return self._construct_from_space(
|
|
997
|
+
self._internal_space - other._internal_space
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
def __rsub__(self, other: int | float | Self) -> Self:
|
|
1001
|
+
return self - other
|
|
1002
|
+
|
|
1003
|
+
def __mul__(self, other: int | float | Self) -> Self:
|
|
1004
|
+
if isinstance(other, (int, float)):
|
|
1005
|
+
return self._construct_from_space(self._internal_space * other)
|
|
1006
|
+
else:
|
|
1007
|
+
return self._construct_from_space(
|
|
1008
|
+
self._internal_space * other._internal_space
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
def __rmul__(self, other: int | float | Self) -> Self:
|
|
1012
|
+
return self * other
|
|
1013
|
+
|
|
1014
|
+
def __truediv__(self, other: int | float | Self) -> Self:
|
|
1015
|
+
if isinstance(other, (int, float)):
|
|
1016
|
+
return self._construct_from_space(self._internal_space / other)
|
|
1017
|
+
else:
|
|
1018
|
+
return self._construct_from_space(
|
|
1019
|
+
self._internal_space / other._internal_space
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
def __rtruediv__(self, other: int | float | Self) -> Self:
|
|
1023
|
+
return self / other
|
|
1024
|
+
|
|
1025
|
+
def __floordiv__(self, other: int | float | Self) -> Self:
|
|
1026
|
+
if isinstance(other, (int, float)):
|
|
1027
|
+
return self._construct_from_space(self._internal_space // other)
|
|
1028
|
+
else:
|
|
1029
|
+
return self._construct_from_space(
|
|
1030
|
+
self._internal_space // other._internal_space
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
def __rfloordiv__(self, other: int | float | Self) -> Self:
|
|
1034
|
+
return self // other
|
|
1035
|
+
|
|
1036
|
+
def __mod__(self, other: int | float | Self) -> Self:
|
|
1037
|
+
if isinstance(other, (int, float)):
|
|
1038
|
+
return self._construct_from_space(self._internal_space % other)
|
|
1039
|
+
else:
|
|
1040
|
+
return self._construct_from_space(
|
|
1041
|
+
self._internal_space % other._internal_space
|
|
1042
|
+
)
|
|
1043
|
+
|
|
1044
|
+
def __rmod__(self, other: int | float | Self) -> Self:
|
|
1045
|
+
return self % other
|
|
1046
|
+
|
|
1047
|
+
def __pow__(self, other: int | float | Self) -> Self:
|
|
1048
|
+
if isinstance(other, (int, float)):
|
|
1049
|
+
return self._construct_from_space(self._internal_space**other)
|
|
1050
|
+
else:
|
|
1051
|
+
return self._construct_from_space(
|
|
1052
|
+
self._internal_space**other._internal_space
|
|
1053
|
+
)
|
|
1054
|
+
|
|
1055
|
+
def __rpow__(self, other: int | float | Self) -> Self:
|
|
1056
|
+
return self**other
|
|
1057
|
+
|
|
1058
|
+
def __invert__(self) -> Self:
|
|
1059
|
+
return self.invert()
|
|
1060
|
+
|
|
1061
|
+
def __int__(self) -> int:
|
|
1062
|
+
return self.to_integer()
|
|
1063
|
+
|
|
1064
|
+
def __getitem__(self, index: int) -> float:
|
|
1065
|
+
item: float = self._internal_space[index]
|
|
1066
|
+
return item
|
|
1067
|
+
|
|
1068
|
+
def __and__(self, other: Self) -> Self:
|
|
1069
|
+
return self._construct_from_space(
|
|
1070
|
+
self._internal_from_integer(self.to_integer() & int(other), 1.0)
|
|
1071
|
+
)
|
|
1072
|
+
|
|
1073
|
+
def __or__(self, other: Self) -> Self:
|
|
1074
|
+
return self._construct_from_space(
|
|
1075
|
+
self._internal_from_integer(self.to_integer() | int(other), 1.0)
|
|
1076
|
+
)
|
|
1077
|
+
|
|
1078
|
+
def __xor__(self, other: Self) -> Self:
|
|
1079
|
+
return self._construct_from_space(
|
|
1080
|
+
self._internal_from_integer(self.to_integer() ^ int(other), 1.0)
|
|
1081
|
+
)
|
|
1082
|
+
|
|
1083
|
+
def __hash__(self) -> int:
|
|
1084
|
+
return hash(self.to_hex(with_alpha=True))
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
RGBA = ManimColor
|
|
1088
|
+
"""RGBA Color Space"""
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
|
+
class HSV(ManimColor):
|
|
1092
|
+
"""HSV Color Space"""
|
|
1093
|
+
|
|
1094
|
+
def __init__(
|
|
1095
|
+
self,
|
|
1096
|
+
hsv: FloatHSVLike | FloatHSVALike,
|
|
1097
|
+
alpha: float = 1.0,
|
|
1098
|
+
) -> None:
|
|
1099
|
+
super().__init__(None)
|
|
1100
|
+
self.__hsv: FloatHSVA
|
|
1101
|
+
if len(hsv) == 3:
|
|
1102
|
+
self.__hsv = np.asarray((*hsv, alpha))
|
|
1103
|
+
elif len(hsv) == 4:
|
|
1104
|
+
self.__hsv = np.asarray(hsv)
|
|
1105
|
+
else:
|
|
1106
|
+
raise ValueError("HSV Color must be an array of 3 values")
|
|
1107
|
+
|
|
1108
|
+
@classmethod
|
|
1109
|
+
@override
|
|
1110
|
+
def _from_internal(cls, value: ManimColorInternal) -> Self:
|
|
1111
|
+
hsv = colorsys.rgb_to_hsv(*value[:3])
|
|
1112
|
+
hsva = [*hsv, value[-1]]
|
|
1113
|
+
return cls(np.array(hsva))
|
|
1114
|
+
|
|
1115
|
+
@property
|
|
1116
|
+
def hue(self) -> float:
|
|
1117
|
+
hue: float = self.__hsv[0]
|
|
1118
|
+
return hue
|
|
1119
|
+
|
|
1120
|
+
@hue.setter
|
|
1121
|
+
def hue(self, hue: float) -> None:
|
|
1122
|
+
self.__hsv[0] = hue
|
|
1123
|
+
|
|
1124
|
+
@property
|
|
1125
|
+
def saturation(self) -> float:
|
|
1126
|
+
saturation: float = self.__hsv[1]
|
|
1127
|
+
return saturation
|
|
1128
|
+
|
|
1129
|
+
@saturation.setter
|
|
1130
|
+
def saturation(self, saturation: float) -> None:
|
|
1131
|
+
self.__hsv[1] = saturation
|
|
1132
|
+
|
|
1133
|
+
@property
|
|
1134
|
+
def value(self) -> float:
|
|
1135
|
+
value: float = self.__hsv[2]
|
|
1136
|
+
return value
|
|
1137
|
+
|
|
1138
|
+
@value.setter
|
|
1139
|
+
def value(self, value: float) -> None:
|
|
1140
|
+
self.__hsv[2] = value
|
|
1141
|
+
|
|
1142
|
+
@property
|
|
1143
|
+
def h(self) -> float:
|
|
1144
|
+
hue: float = self.__hsv[0]
|
|
1145
|
+
return hue
|
|
1146
|
+
|
|
1147
|
+
@h.setter
|
|
1148
|
+
def h(self, hue: float) -> None:
|
|
1149
|
+
self.__hsv[0] = hue
|
|
1150
|
+
|
|
1151
|
+
@property
|
|
1152
|
+
def s(self) -> float:
|
|
1153
|
+
saturation: float = self.__hsv[1]
|
|
1154
|
+
return saturation
|
|
1155
|
+
|
|
1156
|
+
@s.setter
|
|
1157
|
+
def s(self, saturation: float) -> None:
|
|
1158
|
+
self.__hsv[1] = saturation
|
|
1159
|
+
|
|
1160
|
+
@property
|
|
1161
|
+
def v(self) -> float:
|
|
1162
|
+
value: float = self.__hsv[2]
|
|
1163
|
+
return value
|
|
1164
|
+
|
|
1165
|
+
@v.setter
|
|
1166
|
+
def v(self, value: float) -> None:
|
|
1167
|
+
self.__hsv[2] = value
|
|
1168
|
+
|
|
1169
|
+
@property
|
|
1170
|
+
def _internal_space(self) -> npt.NDArray:
|
|
1171
|
+
return self.__hsv
|
|
1172
|
+
|
|
1173
|
+
@property
|
|
1174
|
+
def _internal_value(self) -> ManimColorInternal:
|
|
1175
|
+
"""Return the internal value of the current :class:`ManimColor` as an
|
|
1176
|
+
``[r,g,b,a]`` float array.
|
|
1177
|
+
|
|
1178
|
+
Returns
|
|
1179
|
+
-------
|
|
1180
|
+
ManimColorInternal
|
|
1181
|
+
Internal color representation.
|
|
1182
|
+
"""
|
|
1183
|
+
return np.array(
|
|
1184
|
+
[
|
|
1185
|
+
*colorsys.hsv_to_rgb(self.__hsv[0], self.__hsv[1], self.__hsv[2]),
|
|
1186
|
+
self.__alpha,
|
|
1187
|
+
],
|
|
1188
|
+
dtype=ManimColorDType,
|
|
1189
|
+
)
|
|
1190
|
+
|
|
1191
|
+
@_internal_value.setter
|
|
1192
|
+
def _internal_value(self, value: ManimColorInternal) -> None:
|
|
1193
|
+
"""Overwrite the internal color value of this :class:`ManimColor`.
|
|
1194
|
+
|
|
1195
|
+
Parameters
|
|
1196
|
+
----------
|
|
1197
|
+
value
|
|
1198
|
+
The value which will overwrite the current color.
|
|
1199
|
+
|
|
1200
|
+
Raises
|
|
1201
|
+
------
|
|
1202
|
+
TypeError
|
|
1203
|
+
If an invalid array is passed.
|
|
1204
|
+
"""
|
|
1205
|
+
if not isinstance(value, np.ndarray):
|
|
1206
|
+
raise TypeError("Value must be a NumPy array.")
|
|
1207
|
+
if value.shape[0] != 4:
|
|
1208
|
+
raise TypeError("Array must have exactly 4 values.")
|
|
1209
|
+
tmp = colorsys.rgb_to_hsv(value[0], value[1], value[2])
|
|
1210
|
+
self.__hsv = np.array(tmp)
|
|
1211
|
+
self.__alpha = value[3]
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
ParsableManimColor: TypeAlias = Union[
|
|
1215
|
+
ManimColor,
|
|
1216
|
+
int,
|
|
1217
|
+
str,
|
|
1218
|
+
IntRGBLike,
|
|
1219
|
+
FloatRGBLike,
|
|
1220
|
+
IntRGBALike,
|
|
1221
|
+
FloatRGBALike,
|
|
1222
|
+
]
|
|
1223
|
+
"""`ParsableManimColor` represents all the types which can be parsed
|
|
1224
|
+
to a :class:`ManimColor` in Manim.
|
|
1225
|
+
"""
|
|
1226
|
+
|
|
1227
|
+
|
|
1228
|
+
ManimColorT = TypeVar("ManimColorT", bound=ManimColor)
|
|
1229
|
+
|
|
1230
|
+
|
|
1231
|
+
def color_to_rgb(color: ParsableManimColor) -> FloatRGB:
|
|
1232
|
+
"""Helper function for use in functional style programming.
|
|
1233
|
+
Refer to :meth:`ManimColor.to_rgb`.
|
|
1234
|
+
|
|
1235
|
+
Parameters
|
|
1236
|
+
----------
|
|
1237
|
+
color
|
|
1238
|
+
A color to convert to an RGB float array.
|
|
1239
|
+
|
|
1240
|
+
Returns
|
|
1241
|
+
-------
|
|
1242
|
+
RGB_Array_Float
|
|
1243
|
+
The corresponding RGB float array.
|
|
1244
|
+
"""
|
|
1245
|
+
return ManimColor(color).to_rgb()
|
|
1246
|
+
|
|
1247
|
+
|
|
1248
|
+
def color_to_rgba(color: ParsableManimColor, alpha: float = 1.0) -> FloatRGBA:
|
|
1249
|
+
"""Helper function for use in functional style programming. Refer to
|
|
1250
|
+
:meth:`ManimColor.to_rgba_with_alpha`.
|
|
1251
|
+
|
|
1252
|
+
Parameters
|
|
1253
|
+
----------
|
|
1254
|
+
color
|
|
1255
|
+
A color to convert to an RGBA float array.
|
|
1256
|
+
alpha
|
|
1257
|
+
An alpha value between 0.0 and 1.0 to be used as opacity in the color. Default is
|
|
1258
|
+
1.0.
|
|
1259
|
+
|
|
1260
|
+
Returns
|
|
1261
|
+
-------
|
|
1262
|
+
RGBA_Array_Float
|
|
1263
|
+
The corresponding RGBA float array.
|
|
1264
|
+
"""
|
|
1265
|
+
return ManimColor(color).to_rgba_with_alpha(alpha)
|
|
1266
|
+
|
|
1267
|
+
|
|
1268
|
+
def color_to_int_rgb(color: ParsableManimColor) -> IntRGB:
|
|
1269
|
+
"""Helper function for use in functional style programming. Refer to
|
|
1270
|
+
:meth:`ManimColor.to_int_rgb`.
|
|
1271
|
+
|
|
1272
|
+
Parameters
|
|
1273
|
+
----------
|
|
1274
|
+
color
|
|
1275
|
+
A color to convert to an RGB integer array.
|
|
1276
|
+
|
|
1277
|
+
Returns
|
|
1278
|
+
-------
|
|
1279
|
+
RGB_Array_Int
|
|
1280
|
+
The corresponding RGB integer array.
|
|
1281
|
+
"""
|
|
1282
|
+
return ManimColor(color).to_int_rgb()
|
|
1283
|
+
|
|
1284
|
+
|
|
1285
|
+
def color_to_int_rgba(color: ParsableManimColor, alpha: float = 1.0) -> IntRGBA:
|
|
1286
|
+
"""Helper function for use in functional style programming. Refer to
|
|
1287
|
+
:meth:`ManimColor.to_int_rgba_with_alpha`.
|
|
1288
|
+
|
|
1289
|
+
Parameters
|
|
1290
|
+
----------
|
|
1291
|
+
color
|
|
1292
|
+
A color to convert to an RGBA integer array.
|
|
1293
|
+
alpha
|
|
1294
|
+
An alpha value between 0.0 and 1.0 to be used as opacity in the color. Default is
|
|
1295
|
+
1.0.
|
|
1296
|
+
|
|
1297
|
+
Returns
|
|
1298
|
+
-------
|
|
1299
|
+
RGBA_Array_Int
|
|
1300
|
+
The corresponding RGBA integer array.
|
|
1301
|
+
"""
|
|
1302
|
+
return ManimColor(color).to_int_rgba_with_alpha(alpha)
|
|
1303
|
+
|
|
1304
|
+
|
|
1305
|
+
def rgb_to_color(rgb: FloatRGBLike | IntRGBLike) -> ManimColor:
|
|
1306
|
+
"""Helper function for use in functional style programming. Refer to
|
|
1307
|
+
:meth:`ManimColor.from_rgb`.
|
|
1308
|
+
|
|
1309
|
+
Parameters
|
|
1310
|
+
----------
|
|
1311
|
+
rgb
|
|
1312
|
+
A 3 element iterable.
|
|
1313
|
+
|
|
1314
|
+
Returns
|
|
1315
|
+
-------
|
|
1316
|
+
ManimColor
|
|
1317
|
+
A ManimColor with the corresponding value.
|
|
1318
|
+
"""
|
|
1319
|
+
return ManimColor.from_rgb(rgb)
|
|
1320
|
+
|
|
1321
|
+
|
|
1322
|
+
def rgba_to_color(rgba: FloatRGBALike | IntRGBALike) -> ManimColor:
|
|
1323
|
+
"""Helper function for use in functional style programming. Refer to
|
|
1324
|
+
:meth:`ManimColor.from_rgba`.
|
|
1325
|
+
|
|
1326
|
+
Parameters
|
|
1327
|
+
----------
|
|
1328
|
+
rgba
|
|
1329
|
+
A 4 element iterable.
|
|
1330
|
+
|
|
1331
|
+
Returns
|
|
1332
|
+
-------
|
|
1333
|
+
ManimColor
|
|
1334
|
+
A ManimColor with the corresponding value
|
|
1335
|
+
"""
|
|
1336
|
+
return ManimColor.from_rgba(rgba)
|
|
1337
|
+
|
|
1338
|
+
|
|
1339
|
+
def rgb_to_hex(rgb: FloatRGBLike | IntRGBLike) -> str:
|
|
1340
|
+
"""Helper function for use in functional style programming. Refer to
|
|
1341
|
+
:meth:`ManimColor.from_rgb` and :meth:`ManimColor.to_hex`.
|
|
1342
|
+
|
|
1343
|
+
Parameters
|
|
1344
|
+
----------
|
|
1345
|
+
rgb
|
|
1346
|
+
A 3 element iterable.
|
|
1347
|
+
|
|
1348
|
+
Returns
|
|
1349
|
+
-------
|
|
1350
|
+
str
|
|
1351
|
+
A hex representation of the color.
|
|
1352
|
+
"""
|
|
1353
|
+
return ManimColor.from_rgb(rgb).to_hex()
|
|
1354
|
+
|
|
1355
|
+
|
|
1356
|
+
def hex_to_rgb(hex_code: str) -> FloatRGB:
|
|
1357
|
+
"""Helper function for use in functional style programming. Refer to
|
|
1358
|
+
:meth:`ManimColor.to_rgb`.
|
|
1359
|
+
|
|
1360
|
+
Parameters
|
|
1361
|
+
----------
|
|
1362
|
+
hex_code
|
|
1363
|
+
A hex string representing a color.
|
|
1364
|
+
|
|
1365
|
+
Returns
|
|
1366
|
+
-------
|
|
1367
|
+
RGB_Array_Float
|
|
1368
|
+
An RGB array representing the color.
|
|
1369
|
+
"""
|
|
1370
|
+
return ManimColor(hex_code).to_rgb()
|
|
1371
|
+
|
|
1372
|
+
|
|
1373
|
+
def invert_color(color: ManimColorT) -> ManimColorT:
|
|
1374
|
+
"""Helper function for use in functional style programming. Refer to
|
|
1375
|
+
:meth:`ManimColor.invert`
|
|
1376
|
+
|
|
1377
|
+
Parameters
|
|
1378
|
+
----------
|
|
1379
|
+
color
|
|
1380
|
+
The :class:`ManimColor` to invert.
|
|
1381
|
+
|
|
1382
|
+
Returns
|
|
1383
|
+
-------
|
|
1384
|
+
ManimColor
|
|
1385
|
+
The linearly inverted :class:`ManimColor`.
|
|
1386
|
+
"""
|
|
1387
|
+
return color.invert()
|
|
1388
|
+
|
|
1389
|
+
|
|
1390
|
+
def color_gradient(
|
|
1391
|
+
reference_colors: Iterable[ParsableManimColor],
|
|
1392
|
+
length_of_output: int,
|
|
1393
|
+
) -> list[ManimColor]:
|
|
1394
|
+
"""Create a list of colors interpolated between the input array of colors with a
|
|
1395
|
+
specific number of colors.
|
|
1396
|
+
|
|
1397
|
+
Parameters
|
|
1398
|
+
----------
|
|
1399
|
+
reference_colors
|
|
1400
|
+
The colors to be interpolated between or spread apart.
|
|
1401
|
+
length_of_output
|
|
1402
|
+
The number of colors that the output should have, ideally more than the input.
|
|
1403
|
+
|
|
1404
|
+
Returns
|
|
1405
|
+
-------
|
|
1406
|
+
list[ManimColor]
|
|
1407
|
+
A list of interpolated :class:`ManimColor`'s.
|
|
1408
|
+
"""
|
|
1409
|
+
if length_of_output == 0:
|
|
1410
|
+
return []
|
|
1411
|
+
parsed_colors = [ManimColor(color) for color in reference_colors]
|
|
1412
|
+
num_colors = len(parsed_colors)
|
|
1413
|
+
if num_colors == 0:
|
|
1414
|
+
raise ValueError("Expected 1 or more reference colors. Got 0 colors.")
|
|
1415
|
+
if num_colors == 1:
|
|
1416
|
+
return parsed_colors * length_of_output
|
|
1417
|
+
|
|
1418
|
+
rgbs = [color.to_rgb() for color in parsed_colors]
|
|
1419
|
+
alphas = np.linspace(0, (num_colors - 1), length_of_output)
|
|
1420
|
+
floors = alphas.astype("int")
|
|
1421
|
+
alphas_mod1 = alphas % 1
|
|
1422
|
+
# End edge case
|
|
1423
|
+
alphas_mod1[-1] = 1
|
|
1424
|
+
floors[-1] = num_colors - 2
|
|
1425
|
+
return [
|
|
1426
|
+
rgb_to_color((rgbs[i] * (1 - alpha)) + (rgbs[i + 1] * alpha))
|
|
1427
|
+
for i, alpha in zip(floors, alphas_mod1)
|
|
1428
|
+
]
|
|
1429
|
+
|
|
1430
|
+
|
|
1431
|
+
def interpolate_color(
|
|
1432
|
+
color1: ManimColorT, color2: ManimColorT, alpha: float
|
|
1433
|
+
) -> ManimColorT:
|
|
1434
|
+
"""Standalone function to interpolate two ManimColors and get the result. Refer to
|
|
1435
|
+
:meth:`ManimColor.interpolate`.
|
|
1436
|
+
|
|
1437
|
+
Parameters
|
|
1438
|
+
----------
|
|
1439
|
+
color1
|
|
1440
|
+
The first :class:`ManimColor`.
|
|
1441
|
+
color2
|
|
1442
|
+
The second :class:`ManimColor`.
|
|
1443
|
+
alpha
|
|
1444
|
+
The alpha value determining the point of interpolation between the colors.
|
|
1445
|
+
|
|
1446
|
+
Returns
|
|
1447
|
+
-------
|
|
1448
|
+
ManimColor
|
|
1449
|
+
The interpolated ManimColor.
|
|
1450
|
+
"""
|
|
1451
|
+
return color1.interpolate(color2, alpha)
|
|
1452
|
+
|
|
1453
|
+
|
|
1454
|
+
def average_color(*colors: ParsableManimColor) -> ManimColor:
|
|
1455
|
+
"""Determine the average color between the given parameters.
|
|
1456
|
+
|
|
1457
|
+
.. note::
|
|
1458
|
+
This operation does not consider the alphas (opacities) of the colors. The
|
|
1459
|
+
generated color has an alpha or opacity of 1.0.
|
|
1460
|
+
|
|
1461
|
+
Returns
|
|
1462
|
+
-------
|
|
1463
|
+
ManimColor
|
|
1464
|
+
The average color of the input.
|
|
1465
|
+
"""
|
|
1466
|
+
rgbs = np.array([color_to_rgb(color) for color in colors])
|
|
1467
|
+
mean_rgb = np.apply_along_axis(np.mean, 0, rgbs)
|
|
1468
|
+
return rgb_to_color(mean_rgb)
|
|
1469
|
+
|
|
1470
|
+
|
|
1471
|
+
def random_bright_color() -> ManimColor:
|
|
1472
|
+
"""Return a random bright color: a random color averaged with ``WHITE``.
|
|
1473
|
+
|
|
1474
|
+
.. warning::
|
|
1475
|
+
This operation is very expensive. Please keep in mind the performance loss.
|
|
1476
|
+
|
|
1477
|
+
Returns
|
|
1478
|
+
-------
|
|
1479
|
+
ManimColor
|
|
1480
|
+
A random bright :class:`ManimColor`.
|
|
1481
|
+
"""
|
|
1482
|
+
curr_rgb = color_to_rgb(random_color())
|
|
1483
|
+
new_rgb = 0.5 * (curr_rgb + np.ones(3))
|
|
1484
|
+
return ManimColor(new_rgb)
|
|
1485
|
+
|
|
1486
|
+
|
|
1487
|
+
def random_color() -> ManimColor:
|
|
1488
|
+
"""Return a random :class:`ManimColor`.
|
|
1489
|
+
|
|
1490
|
+
Returns
|
|
1491
|
+
-------
|
|
1492
|
+
ManimColor
|
|
1493
|
+
A random :class:`ManimColor`.
|
|
1494
|
+
"""
|
|
1495
|
+
return RandomColorGenerator._random_color()
|
|
1496
|
+
|
|
1497
|
+
|
|
1498
|
+
class RandomColorGenerator:
|
|
1499
|
+
_singleton: RandomColorGenerator | None = None
|
|
1500
|
+
"""A generator for producing random colors from a given list of Manim colors,
|
|
1501
|
+
optionally in a reproducible sequence using a seed value.
|
|
1502
|
+
|
|
1503
|
+
When initialized with a specific seed, this class produces a deterministic
|
|
1504
|
+
sequence of :class:`.ManimColor` instances. If no seed is provided, the selection is
|
|
1505
|
+
non-deterministic using Python’s global random state.
|
|
1506
|
+
|
|
1507
|
+
Parameters
|
|
1508
|
+
----------
|
|
1509
|
+
seed
|
|
1510
|
+
A seed value to initialize the internal random number generator.
|
|
1511
|
+
If ``None`` (the default), colors are chosen using the global random state.
|
|
1512
|
+
|
|
1513
|
+
sample_colors
|
|
1514
|
+
A custom list of Manim colors to sample from. Defaults to the full Manim
|
|
1515
|
+
color palette.
|
|
1516
|
+
|
|
1517
|
+
Examples
|
|
1518
|
+
--------
|
|
1519
|
+
Without a seed (non-deterministic)::
|
|
1520
|
+
|
|
1521
|
+
>>> from manim import RandomColorGenerator, ManimColor, RED, GREEN, BLUE
|
|
1522
|
+
>>> rnd = RandomColorGenerator()
|
|
1523
|
+
>>> isinstance(rnd.next(), ManimColor)
|
|
1524
|
+
True
|
|
1525
|
+
|
|
1526
|
+
With a seed (deterministic sequence)::
|
|
1527
|
+
|
|
1528
|
+
>>> rnd = RandomColorGenerator(42)
|
|
1529
|
+
>>> rnd.next()
|
|
1530
|
+
ManimColor('#ECE7E2')
|
|
1531
|
+
>>> rnd.next()
|
|
1532
|
+
ManimColor('#BBBBBB')
|
|
1533
|
+
>>> rnd.next()
|
|
1534
|
+
ManimColor('#BBBBBB')
|
|
1535
|
+
|
|
1536
|
+
Re-initializing with the same seed gives the same sequence::
|
|
1537
|
+
|
|
1538
|
+
>>> rnd2 = RandomColorGenerator(42)
|
|
1539
|
+
>>> rnd2.next()
|
|
1540
|
+
ManimColor('#ECE7E2')
|
|
1541
|
+
>>> rnd2.next()
|
|
1542
|
+
ManimColor('#BBBBBB')
|
|
1543
|
+
>>> rnd2.next()
|
|
1544
|
+
ManimColor('#BBBBBB')
|
|
1545
|
+
|
|
1546
|
+
Using a custom color list::
|
|
1547
|
+
|
|
1548
|
+
>>> custom_palette = [RED, GREEN, BLUE]
|
|
1549
|
+
>>> rnd_custom = RandomColorGenerator(1, sample_colors=custom_palette)
|
|
1550
|
+
>>> rnd_custom.next() in custom_palette
|
|
1551
|
+
True
|
|
1552
|
+
>>> rnd_custom.next() in custom_palette
|
|
1553
|
+
True
|
|
1554
|
+
|
|
1555
|
+
Without a seed and custom palette (non-deterministic)::
|
|
1556
|
+
|
|
1557
|
+
>>> rnd_nodet = RandomColorGenerator(sample_colors=[RED])
|
|
1558
|
+
>>> rnd_nodet.next()
|
|
1559
|
+
ManimColor('#FC6255')
|
|
1560
|
+
"""
|
|
1561
|
+
|
|
1562
|
+
def __init__(
|
|
1563
|
+
self,
|
|
1564
|
+
seed: int | None = None,
|
|
1565
|
+
sample_colors: list[ManimColor] | None = None,
|
|
1566
|
+
) -> None:
|
|
1567
|
+
self.choice = random.choice if seed is None else random.Random(seed).choice
|
|
1568
|
+
|
|
1569
|
+
from manim.utils.color.manim_colors import _all_manim_colors
|
|
1570
|
+
|
|
1571
|
+
self.colors = _all_manim_colors if sample_colors is None else sample_colors
|
|
1572
|
+
|
|
1573
|
+
def next(self) -> ManimColor:
|
|
1574
|
+
"""Returns the next color from the configured color list.
|
|
1575
|
+
|
|
1576
|
+
Returns
|
|
1577
|
+
-------
|
|
1578
|
+
ManimColor
|
|
1579
|
+
A randomly selected color from the specified color list.
|
|
1580
|
+
|
|
1581
|
+
Examples
|
|
1582
|
+
--------
|
|
1583
|
+
Usage::
|
|
1584
|
+
|
|
1585
|
+
>>> from manim import RandomColorGenerator, RED
|
|
1586
|
+
>>> rnd = RandomColorGenerator(sample_colors=[RED])
|
|
1587
|
+
>>> rnd.next()
|
|
1588
|
+
ManimColor('#FC6255')
|
|
1589
|
+
"""
|
|
1590
|
+
return self.choice(self.colors)
|
|
1591
|
+
|
|
1592
|
+
@classmethod
|
|
1593
|
+
def _random_color(cls) -> ManimColor:
|
|
1594
|
+
"""Internal method to generate a random color using the singleton instance of
|
|
1595
|
+
`RandomColorGenerator`.
|
|
1596
|
+
It will be used by proxy method `random_color` publicly available
|
|
1597
|
+
and makes it backwards compatible.
|
|
1598
|
+
|
|
1599
|
+
Returns
|
|
1600
|
+
-------
|
|
1601
|
+
ManimColor:
|
|
1602
|
+
A randomly selected color from the configured color list of
|
|
1603
|
+
the singleton instance.
|
|
1604
|
+
"""
|
|
1605
|
+
if cls._singleton is None:
|
|
1606
|
+
cls._singleton = cls()
|
|
1607
|
+
return cls._singleton.next()
|
|
1608
|
+
|
|
1609
|
+
|
|
1610
|
+
def get_shaded_rgb(
|
|
1611
|
+
rgb: FloatRGB,
|
|
1612
|
+
point: Point3D,
|
|
1613
|
+
unit_normal_vect: Vector3D,
|
|
1614
|
+
light_source: Point3D,
|
|
1615
|
+
) -> FloatRGB:
|
|
1616
|
+
"""Add light or shadow to the ``rgb`` color of some surface which is located at a
|
|
1617
|
+
given ``point`` in space and facing in the direction of ``unit_normal_vect``,
|
|
1618
|
+
depending on whether the surface is facing a ``light_source`` or away from it.
|
|
1619
|
+
|
|
1620
|
+
Parameters
|
|
1621
|
+
----------
|
|
1622
|
+
rgb
|
|
1623
|
+
An RGB array of floats.
|
|
1624
|
+
point
|
|
1625
|
+
The location of the colored surface.
|
|
1626
|
+
unit_normal_vect
|
|
1627
|
+
The direction in which the colored surface is facing.
|
|
1628
|
+
light_source
|
|
1629
|
+
The location of a light source which might illuminate the surface.
|
|
1630
|
+
|
|
1631
|
+
Returns
|
|
1632
|
+
-------
|
|
1633
|
+
RGB_Array_Float
|
|
1634
|
+
The color with added light or shadow, depending on the direction of the colored
|
|
1635
|
+
surface.
|
|
1636
|
+
"""
|
|
1637
|
+
to_sun = normalize(light_source - point)
|
|
1638
|
+
light = 0.5 * np.dot(unit_normal_vect, to_sun) ** 3
|
|
1639
|
+
if light < 0:
|
|
1640
|
+
light *= 0.5
|
|
1641
|
+
shaded_rgb: FloatRGB = rgb + light
|
|
1642
|
+
return shaded_rgb
|
|
1643
|
+
|
|
1644
|
+
|
|
1645
|
+
__all__ = [
|
|
1646
|
+
"ManimColor",
|
|
1647
|
+
"ManimColorDType",
|
|
1648
|
+
"ParsableManimColor",
|
|
1649
|
+
"color_to_rgb",
|
|
1650
|
+
"color_to_rgba",
|
|
1651
|
+
"color_to_int_rgb",
|
|
1652
|
+
"color_to_int_rgba",
|
|
1653
|
+
"rgb_to_color",
|
|
1654
|
+
"rgba_to_color",
|
|
1655
|
+
"rgb_to_hex",
|
|
1656
|
+
"hex_to_rgb",
|
|
1657
|
+
"invert_color",
|
|
1658
|
+
"color_gradient",
|
|
1659
|
+
"interpolate_color",
|
|
1660
|
+
"average_color",
|
|
1661
|
+
"random_bright_color",
|
|
1662
|
+
"random_color",
|
|
1663
|
+
"RandomColorGenerator",
|
|
1664
|
+
"get_shaded_rgb",
|
|
1665
|
+
"HSV",
|
|
1666
|
+
"RGBA",
|
|
1667
|
+
]
|