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.
Files changed (163) hide show
  1. manim/__init__.py +11 -6
  2. manim/__main__.py +62 -19
  3. manim/_config/__init__.py +10 -9
  4. manim/_config/cli_colors.py +26 -9
  5. manim/_config/default.cfg +1 -3
  6. manim/_config/logger_utils.py +23 -13
  7. manim/_config/utils.py +662 -468
  8. manim/animation/animation.py +164 -18
  9. manim/animation/changing.py +34 -23
  10. manim/animation/composition.py +265 -67
  11. manim/animation/creation.py +208 -26
  12. manim/animation/fading.py +16 -18
  13. manim/animation/growing.py +35 -15
  14. manim/animation/indication.py +150 -76
  15. manim/animation/movement.py +56 -22
  16. manim/animation/numbers.py +64 -6
  17. manim/animation/rotation.py +78 -7
  18. manim/animation/specialized.py +6 -7
  19. manim/animation/speedmodifier.py +13 -10
  20. manim/animation/transform.py +14 -11
  21. manim/animation/transform_matching_parts.py +3 -4
  22. manim/animation/updaters/mobject_update_utils.py +152 -30
  23. manim/animation/updaters/update.py +10 -7
  24. manim/camera/camera.py +182 -118
  25. manim/camera/mapping_camera.py +34 -3
  26. manim/camera/moving_camera.py +95 -74
  27. manim/camera/multi_camera.py +23 -15
  28. manim/camera/three_d_camera.py +70 -52
  29. manim/cli/__init__.py +17 -0
  30. manim/cli/cfg/group.py +76 -44
  31. manim/cli/checkhealth/checks.py +192 -0
  32. manim/cli/checkhealth/commands.py +90 -0
  33. manim/cli/default_group.py +158 -25
  34. manim/cli/init/commands.py +33 -25
  35. manim/cli/plugins/commands.py +16 -3
  36. manim/cli/render/commands.py +72 -60
  37. manim/cli/render/ease_of_access_options.py +4 -3
  38. manim/cli/render/global_options.py +59 -17
  39. manim/cli/render/output_options.py +6 -5
  40. manim/cli/render/render_options.py +98 -33
  41. manim/constants.py +109 -59
  42. manim/data_structures.py +31 -0
  43. manim/mobject/frame.py +8 -5
  44. manim/mobject/geometry/__init__.py +1 -0
  45. manim/mobject/geometry/arc.py +277 -135
  46. manim/mobject/geometry/boolean_ops.py +32 -31
  47. manim/mobject/geometry/labeled.py +376 -0
  48. manim/mobject/geometry/line.py +192 -87
  49. manim/mobject/geometry/polygram.py +224 -58
  50. manim/mobject/geometry/shape_matchers.py +61 -25
  51. manim/mobject/geometry/tips.py +122 -48
  52. manim/mobject/graph.py +1027 -419
  53. manim/mobject/graphing/coordinate_systems.py +533 -278
  54. manim/mobject/graphing/functions.py +53 -32
  55. manim/mobject/graphing/number_line.py +123 -65
  56. manim/mobject/graphing/probability.py +88 -62
  57. manim/mobject/graphing/scale.py +33 -19
  58. manim/mobject/logo.py +118 -28
  59. manim/mobject/matrix.py +87 -83
  60. manim/mobject/mobject.py +912 -442
  61. manim/mobject/opengl/dot_cloud.py +16 -5
  62. manim/mobject/opengl/opengl_compatibility.py +4 -2
  63. manim/mobject/opengl/opengl_geometry.py +254 -153
  64. manim/mobject/opengl/opengl_image_mobject.py +3 -1
  65. manim/mobject/opengl/opengl_mobject.py +779 -482
  66. manim/mobject/opengl/opengl_point_cloud_mobject.py +41 -14
  67. manim/mobject/opengl/opengl_surface.py +14 -92
  68. manim/mobject/opengl/opengl_three_dimensions.py +12 -8
  69. manim/mobject/opengl/opengl_vectorized_mobject.py +98 -100
  70. manim/mobject/svg/brace.py +173 -41
  71. manim/mobject/svg/svg_mobject.py +139 -53
  72. manim/mobject/table.py +61 -68
  73. manim/mobject/text/code_mobject.py +193 -539
  74. manim/mobject/text/numbers.py +81 -34
  75. manim/mobject/text/tex_mobject.py +130 -78
  76. manim/mobject/text/text_mobject.py +288 -164
  77. manim/mobject/three_d/polyhedra.py +111 -13
  78. manim/mobject/three_d/three_d_utils.py +17 -8
  79. manim/mobject/three_d/three_dimensions.py +239 -106
  80. manim/mobject/types/image_mobject.py +50 -30
  81. manim/mobject/types/point_cloud_mobject.py +120 -75
  82. manim/mobject/types/vectorized_mobject.py +841 -408
  83. manim/mobject/value_tracker.py +105 -38
  84. manim/mobject/vector_field.py +50 -31
  85. manim/opengl/__init__.py +3 -3
  86. manim/plugins/__init__.py +14 -1
  87. manim/plugins/plugins_flags.py +10 -14
  88. manim/renderer/cairo_renderer.py +65 -50
  89. manim/renderer/opengl_renderer.py +89 -69
  90. manim/renderer/opengl_renderer_window.py +39 -18
  91. manim/renderer/shader.py +123 -87
  92. manim/renderer/shader_wrapper.py +44 -28
  93. manim/renderer/vectorized_mobject_rendering.py +38 -10
  94. manim/scene/moving_camera_scene.py +32 -3
  95. manim/scene/scene.py +507 -242
  96. manim/scene/scene_file_writer.py +371 -220
  97. manim/scene/section.py +20 -16
  98. manim/scene/three_d_scene.py +14 -22
  99. manim/scene/vector_space_scene.py +223 -129
  100. manim/scene/zoomed_scene.py +46 -41
  101. manim/typing.py +990 -0
  102. manim/utils/bezier.py +1823 -371
  103. manim/utils/caching.py +12 -5
  104. manim/utils/color/AS2700.py +236 -0
  105. manim/utils/color/BS381.py +318 -0
  106. manim/utils/color/DVIPSNAMES.py +96 -0
  107. manim/utils/color/SVGNAMES.py +179 -0
  108. manim/utils/color/X11.py +533 -0
  109. manim/utils/color/XKCD.py +952 -0
  110. manim/utils/color/__init__.py +61 -0
  111. manim/utils/color/core.py +1667 -0
  112. manim/utils/color/manim_colors.py +218 -0
  113. manim/utils/commands.py +48 -20
  114. manim/utils/config_ops.py +39 -19
  115. manim/utils/debug.py +8 -7
  116. manim/utils/deprecation.py +86 -39
  117. manim/utils/docbuild/__init__.py +17 -0
  118. manim/utils/docbuild/autoaliasattr_directive.py +236 -0
  119. manim/utils/docbuild/autocolor_directive.py +99 -0
  120. manim/utils/docbuild/manim_directive.py +94 -41
  121. manim/utils/docbuild/module_parsing.py +245 -0
  122. manim/utils/exceptions.py +6 -0
  123. manim/utils/family.py +5 -3
  124. manim/utils/family_ops.py +17 -4
  125. manim/utils/file_ops.py +27 -17
  126. manim/utils/hashing.py +55 -45
  127. manim/utils/images.py +13 -7
  128. manim/utils/ipython_magic.py +13 -7
  129. manim/utils/iterables.py +163 -120
  130. manim/utils/module_ops.py +66 -24
  131. manim/utils/opengl.py +77 -24
  132. manim/utils/parameter_parsing.py +32 -0
  133. manim/utils/paths.py +30 -33
  134. manim/utils/polylabel.py +235 -0
  135. manim/utils/qhull.py +218 -0
  136. manim/utils/rate_functions.py +98 -32
  137. manim/utils/simple_functions.py +25 -33
  138. manim/utils/sounds.py +7 -1
  139. manim/utils/space_ops.py +188 -115
  140. manim/utils/testing/__init__.py +17 -0
  141. manim/utils/testing/_frames_testers.py +13 -8
  142. manim/utils/testing/_show_diff.py +5 -3
  143. manim/utils/testing/_test_class_makers.py +34 -18
  144. manim/utils/testing/frames_comparison.py +37 -19
  145. manim/utils/tex.py +130 -198
  146. manim/utils/tex_file_writing.py +77 -47
  147. manim/utils/tex_templates.py +2 -1
  148. manim/utils/unit.py +6 -5
  149. {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/METADATA +64 -65
  150. manim-0.19.1.dist-info/RECORD +220 -0
  151. {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/WHEEL +1 -1
  152. manim-0.19.1.dist-info/entry_points.txt +3 -0
  153. {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE.community +1 -1
  154. manim/cli/new/group.py +0 -189
  155. manim/communitycolors.py +0 -9
  156. manim/gui/__init__.py +0 -0
  157. manim/gui/gui.py +0 -82
  158. manim/plugins/import_plugins.py +0 -43
  159. manim/utils/color.py +0 -552
  160. manim-0.17.0.dist-info/RECORD +0 -206
  161. manim-0.17.0.dist-info/entry_points.txt +0 -4
  162. /manim/cli/{new → checkhealth}/__init__.py +0 -0
  163. {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
+ ]