Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add darker, lighter and contrasting methods to ManimColor #3992

Merged
merged 3 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions manim/utils/color/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,92 @@ def interpolate(self, other: ManimColor, alpha: float) -> Self:
self._internal_space * (1 - alpha) + other._internal_space * alpha
)

def darker(self, factor: float = 0.2) -> Self:
"""Returns a new color that is darker than the current color, i.e.
interpolated with black. The opacity is unchanged.

Parameters
----------
factor : float, optional
behackl marked this conversation as resolved.
Show resolved Hide resolved
The factor by which the color should be darker, by default 0.2,
which results in a slightly darker color
behackl marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
ManimColor
The darker ManimColor

See Also
--------
:meth:`lighter`
"""
alpha = self._internal_space[3]
black = self.from_rgb((0.0, 0.0, 0.0))
behackl marked this conversation as resolved.
Show resolved Hide resolved
behackl marked this conversation as resolved.
Show resolved Hide resolved
return self.interpolate(black, factor).opacity(alpha)

def lighter(self, factor: float = 0.2) -> Self:
"""Returns a new color that is lighter than the current color, i.e.
interpolated with white. The opacity is unchanged.

Parameters
----------
factor : float, optional
The factor by which the color should be lighter, by default 0.2,
which results in a slightly lighter color
behackl marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
ManimColor
The lighter ManimColor

See Also
--------
:meth:`darker`
"""
alpha = self._internal_space[3]
white = self.from_rgb((1.0, 1.0, 1.0))
behackl marked this conversation as resolved.
Show resolved Hide resolved
return self.interpolate(white, factor).opacity(alpha)

def contrasting(
self,
threshold: float = 0.5,
light: Self | None = None,
dark: Self | None = None,
) -> Self:
"""Returns one of two colors, light or dark (by default white or black),
that contrasts with the current color (depending on its luminance).
This is typically used to set text in a contrasting color that ensures
it is readable against a background of the current color.

Parameters
----------
threshold : float, optional
The luminance threshold that dictates whether the current color is
considered light or dark (and thus whether to return the dark or
light color, respectively), by default 0.5
light : ManimColor, optional
The light color to return if the current color is considered dark,
by default pure white
dark : ManimColor, optional
The dark color to return if the current color is considered light,
by default pure black

Returns
-------
ManimColor
The contrasting ManimColor
"""
r, g, b = self.to_rgb()
behackl marked this conversation as resolved.
Show resolved Hide resolved
luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
behackl marked this conversation as resolved.
Show resolved Hide resolved
if luminance < threshold:
if light is not None:
return light
return self.from_rgb((1.0, 1.0, 1.0), 1.0)
else:
if dark is not None:
return dark
return self.from_rgb((0.0, 0.0, 0.0), 1.0)

def opacity(self, opacity: float) -> Self:
"""Creates a new ManimColor with the given opacity and the same color value as before

Expand Down
12 changes: 5 additions & 7 deletions manim/utils/color/manim_colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,22 @@ def subnames(name):
for line, char in zip(color_groups[0], "abcde"):
color_groups.add(Text(char).scale(0.6).next_to(line, LEFT, buff=0.2))

def named_lines_group(length, colors, names, text_colors, align_to_block):
def named_lines_group(length, color_names, labels, align_to_block):
colors = [getattr(Colors, color.upper()) for color in color_names]
lines = VGroup(
*[
Line(
ORIGIN,
RIGHT * length,
stroke_width=55,
color=getattr(Colors, color.upper()),
color=color,
)
for color in colors
]
).arrange_submobjects(buff=0.6, direction=DOWN)

for line, name, color in zip(lines, names, text_colors):
line.add(Text(name, color=color).scale(0.6).move_to(line))
for line, name, color in zip(lines, labels, colors):
line.add(Text(name, color=color.contrasting()).scale(0.6).move_to(line))
lines.next_to(color_groups, DOWN, buff=0.5).align_to(
color_groups[align_to_block], LEFT
)
Expand All @@ -79,7 +80,6 @@ def named_lines_group(length, colors, names, text_colors, align_to_block):
3.2,
other_colors,
other_colors,
[BLACK] * 4 + [WHITE] * 2,
0,
)

Expand All @@ -95,7 +95,6 @@ def named_lines_group(length, colors, names, text_colors, align_to_block):
"darker_gray / gray_e",
"black",
],
[BLACK] * 3 + [WHITE] * 4,
2,
)

Expand All @@ -109,7 +108,6 @@ def named_lines_group(length, colors, names, text_colors, align_to_block):
3.2,
pure_colors,
pure_colors,
[BLACK, BLACK, WHITE],
6,
)

Expand Down
29 changes: 29 additions & 0 deletions tests/module/utils/test_manim_color.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,32 @@ def test_hsv_init() -> None:

def test_into_HSV() -> None:
nt.assert_equal(RED.into(HSV).into(ManimColor), RED)


def test_contrasting() -> None:
nt.assert_equal(BLACK.contrasting(), WHITE)
nt.assert_equal(WHITE.contrasting(), BLACK)
nt.assert_equal(RED.contrasting(0.1), BLACK)
nt.assert_equal(RED.contrasting(0.9), WHITE)
nt.assert_equal(BLACK.contrasting(dark=GREEN, light=RED), RED)
nt.assert_equal(WHITE.contrasting(dark=GREEN, light=RED), GREEN)


def test_lighter() -> None:
c = RED.opacity(0.42)
cl = c.lighter(0.2)
nt.assert_array_equal(
cl._internal_value[:3],
0.8 * c._internal_value[:3] + 0.2 * WHITE._internal_value[:3],
)
nt.assert_equal(cl[-1], c[-1])


def test_darker() -> None:
c = RED.opacity(0.42)
cd = c.darker(0.2)
nt.assert_array_equal(
cd._internal_value[:3],
0.8 * c._internal_value[:3] + 0.2 * BLACK._internal_value[:3],
)
nt.assert_equal(cd[-1], c[-1])
Loading