Skip to content

Commit fac0aa5

Browse files
hchargoisbehackl
andauthored
Add darker, lighter and contrasting methods to ManimColor (#3992)
* Add darker, lighter and contrasting methods to ManimColor * Fixes --------- Co-authored-by: Benjamin Hackl <[email protected]>
1 parent faecdd3 commit fac0aa5

File tree

3 files changed

+129
-9
lines changed

3 files changed

+129
-9
lines changed

manim/utils/color/core.py

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ def invert(self, with_alpha=False) -> Self:
585585
new[-1] = alpha
586586
return self._construct_from_space(new)
587587

588-
def interpolate(self, other: ManimColor, alpha: float) -> Self:
588+
def interpolate(self, other: Self, alpha: float) -> Self:
589589
"""Interpolates between the current and the given ManimColor an returns the interpolated color
590590
591591
Parameters
@@ -606,6 +606,99 @@ def interpolate(self, other: ManimColor, alpha: float) -> Self:
606606
self._internal_space * (1 - alpha) + other._internal_space * alpha
607607
)
608608

609+
def darker(self, blend: float = 0.2) -> Self:
610+
"""Returns a new color that is darker than the current color, i.e.
611+
interpolated with black. The opacity is unchanged.
612+
613+
Parameters
614+
----------
615+
blend : float, optional
616+
The blend ratio for the interpolation, from 0 (the current color
617+
unchanged) to 1 (pure black). By default 0.2 which results in a
618+
slightly darker color
619+
620+
Returns
621+
-------
622+
ManimColor
623+
The darker ManimColor
624+
625+
See Also
626+
--------
627+
:meth:`lighter`
628+
"""
629+
from manim.utils.color.manim_colors import BLACK
630+
631+
alpha = self._internal_space[3]
632+
black = self._from_internal(BLACK._internal_value)
633+
return self.interpolate(black, blend).opacity(alpha)
634+
635+
def lighter(self, blend: float = 0.2) -> Self:
636+
"""Returns a new color that is lighter than the current color, i.e.
637+
interpolated with white. The opacity is unchanged.
638+
639+
Parameters
640+
----------
641+
blend : float, optional
642+
The blend ratio for the interpolation, from 0 (the current color
643+
unchanged) to 1 (pure white). By default 0.2 which results in a
644+
slightly lighter color
645+
646+
Returns
647+
-------
648+
ManimColor
649+
The lighter ManimColor
650+
651+
See Also
652+
--------
653+
:meth:`darker`
654+
"""
655+
from manim.utils.color.manim_colors import WHITE
656+
657+
alpha = self._internal_space[3]
658+
white = self._from_internal(WHITE._internal_value)
659+
return self.interpolate(white, blend).opacity(alpha)
660+
661+
def contrasting(
662+
self,
663+
threshold: float = 0.5,
664+
light: Self | None = None,
665+
dark: Self | None = None,
666+
) -> Self:
667+
"""Returns one of two colors, light or dark (by default white or black),
668+
that contrasts with the current color (depending on its luminance).
669+
This is typically used to set text in a contrasting color that ensures
670+
it is readable against a background of the current color.
671+
672+
Parameters
673+
----------
674+
threshold : float, optional
675+
The luminance threshold that dictates whether the current color is
676+
considered light or dark (and thus whether to return the dark or
677+
light color, respectively), by default 0.5
678+
light : ManimColor, optional
679+
The light color to return if the current color is considered dark,
680+
by default pure white
681+
dark : ManimColor, optional
682+
The dark color to return if the current color is considered light,
683+
by default pure black
684+
685+
Returns
686+
-------
687+
ManimColor
688+
The contrasting ManimColor
689+
"""
690+
from manim.utils.color.manim_colors import BLACK, WHITE
691+
692+
luminance, _, _ = colorsys.rgb_to_yiq(*self.to_rgb())
693+
if luminance < threshold:
694+
if light is not None:
695+
return light
696+
return self._from_internal(WHITE._internal_value)
697+
else:
698+
if dark is not None:
699+
return dark
700+
return self._from_internal(BLACK._internal_value)
701+
609702
def opacity(self, opacity: float) -> Self:
610703
"""Creates a new ManimColor with the given opacity and the same color value as before
611704
@@ -1282,7 +1375,7 @@ def color_gradient(
12821375

12831376

12841377
def interpolate_color(
1285-
color1: ManimColorT, color2: ManimColor, alpha: float
1378+
color1: ManimColorT, color2: ManimColorT, alpha: float
12861379
) -> ManimColorT:
12871380
"""Standalone function to interpolate two ManimColors and get the result refer to :meth:`interpolate` in :class:`ManimColor`
12881381

manim/utils/color/manim_colors.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,22 @@ def subnames(name):
4646
for line, char in zip(color_groups[0], "abcde"):
4747
color_groups.add(Text(char).scale(0.6).next_to(line, LEFT, buff=0.2))
4848
49-
def named_lines_group(length, colors, names, text_colors, align_to_block):
49+
def named_lines_group(length, color_names, labels, align_to_block):
50+
colors = [getattr(Colors, color.upper()) for color in color_names]
5051
lines = VGroup(
5152
*[
5253
Line(
5354
ORIGIN,
5455
RIGHT * length,
5556
stroke_width=55,
56-
color=getattr(Colors, color.upper()),
57+
color=color,
5758
)
5859
for color in colors
5960
]
6061
).arrange_submobjects(buff=0.6, direction=DOWN)
6162
62-
for line, name, color in zip(lines, names, text_colors):
63-
line.add(Text(name, color=color).scale(0.6).move_to(line))
63+
for line, name, color in zip(lines, labels, colors):
64+
line.add(Text(name, color=color.contrasting()).scale(0.6).move_to(line))
6465
lines.next_to(color_groups, DOWN, buff=0.5).align_to(
6566
color_groups[align_to_block], LEFT
6667
)
@@ -79,7 +80,6 @@ def named_lines_group(length, colors, names, text_colors, align_to_block):
7980
3.2,
8081
other_colors,
8182
other_colors,
82-
[BLACK] * 4 + [WHITE] * 2,
8383
0,
8484
)
8585
@@ -95,7 +95,6 @@ def named_lines_group(length, colors, names, text_colors, align_to_block):
9595
"darker_gray / gray_e",
9696
"black",
9797
],
98-
[BLACK] * 3 + [WHITE] * 4,
9998
2,
10099
)
101100
@@ -109,7 +108,6 @@ def named_lines_group(length, colors, names, text_colors, align_to_block):
109108
3.2,
110109
pure_colors,
111110
pure_colors,
112-
[BLACK, BLACK, WHITE],
113111
6,
114112
)
115113

tests/module/utils/test_manim_color.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,32 @@ def test_hsv_init() -> None:
173173

174174
def test_into_HSV() -> None:
175175
nt.assert_equal(RED.into(HSV).into(ManimColor), RED)
176+
177+
178+
def test_contrasting() -> None:
179+
nt.assert_equal(BLACK.contrasting(), WHITE)
180+
nt.assert_equal(WHITE.contrasting(), BLACK)
181+
nt.assert_equal(RED.contrasting(0.1), BLACK)
182+
nt.assert_equal(RED.contrasting(0.9), WHITE)
183+
nt.assert_equal(BLACK.contrasting(dark=GREEN, light=RED), RED)
184+
nt.assert_equal(WHITE.contrasting(dark=GREEN, light=RED), GREEN)
185+
186+
187+
def test_lighter() -> None:
188+
c = RED.opacity(0.42)
189+
cl = c.lighter(0.2)
190+
nt.assert_array_equal(
191+
cl._internal_value[:3],
192+
0.8 * c._internal_value[:3] + 0.2 * WHITE._internal_value[:3],
193+
)
194+
nt.assert_equal(cl[-1], c[-1])
195+
196+
197+
def test_darker() -> None:
198+
c = RED.opacity(0.42)
199+
cd = c.darker(0.2)
200+
nt.assert_array_equal(
201+
cd._internal_value[:3],
202+
0.8 * c._internal_value[:3] + 0.2 * BLACK._internal_value[:3],
203+
)
204+
nt.assert_equal(cd[-1], c[-1])

0 commit comments

Comments
 (0)