3b1b-manim/manimlib/utils/color.py

142 lines
3.5 KiB
Python
Raw Normal View History

2022-04-12 11:13:05 +08:00
from __future__ import annotations
from colour import Color
2022-04-12 11:13:05 +08:00
from colour import hex2rgb
from colour import rgb2hex
import numpy as np
from manimlib.constants import COLORMAP_3B1B
2022-04-12 19:19:59 +08:00
from manimlib.constants import WHITE
from manimlib.utils.bezier import interpolate
from manimlib.utils.iterables import resize_with_interpolation
2022-04-12 19:19:59 +08:00
from typing import TYPE_CHECKING
if TYPE_CHECKING:
2022-12-17 17:29:49 -08:00
from typing import Iterable, Sequence
from manimlib.typing import ManimColor, Vect3, Vect4, Vect3Array
2022-04-12 11:13:05 +08:00
2022-12-17 17:29:49 -08:00
def color_to_rgb(color: ManimColor) -> Vect3:
if isinstance(color, str):
return hex_to_rgb(color)
elif isinstance(color, Color):
return np.array(color.get_rgb())
else:
raise Exception("Invalid color type")
2022-12-17 17:29:49 -08:00
def color_to_rgba(color: ManimColor, alpha: float = 1.0) -> Vect4:
2018-08-15 17:30:24 -07:00
return np.array([*color_to_rgb(color), alpha])
2022-12-17 17:29:49 -08:00
def rgb_to_color(rgb: Vect3 | Sequence[float]) -> Color:
try:
2022-04-12 11:13:05 +08:00
return Color(rgb=tuple(rgb))
2020-02-22 13:20:37 -08:00
except ValueError:
return Color(WHITE)
2022-12-17 17:29:49 -08:00
def rgba_to_color(rgba: Vect4) -> Color:
return rgb_to_color(rgba[:3])
2022-12-17 17:29:49 -08:00
def rgb_to_hex(rgb: Vect3 | Sequence[float]) -> str:
2022-04-12 11:13:05 +08:00
return rgb2hex(rgb, force_long=True).upper()
2022-12-17 17:29:49 -08:00
def hex_to_rgb(hex_code: str) -> Vect3:
2022-04-12 11:26:19 +08:00
return np.array(hex2rgb(hex_code))
2022-04-12 11:13:05 +08:00
def invert_color(color: ManimColor) -> Color:
return rgb_to_color(1.0 - color_to_rgb(color))
2022-12-17 17:29:49 -08:00
def color_to_int_rgb(color: ManimColor) -> np.ndarray[int, np.dtype[np.uint8]]:
return (255 * color_to_rgb(color)).astype('uint8')
2022-12-17 17:29:49 -08:00
def color_to_int_rgba(color: ManimColor, opacity: float = 1.0) -> np.ndarray[int, np.dtype[np.uint8]]:
alpha = int(255 * opacity)
2022-12-17 17:29:49 -08:00
return np.array([*color_to_int_rgb(color), alpha], dtype=np.uint8)
def color_to_hex(color: ManimColor) -> str:
2022-12-19 18:03:53 -08:00
return Color(color).hex.upper()
def hex_to_int(rgb_hex: str) -> int:
return int(rgb_hex[1:], 16)
def int_to_hex(rgb_int: int) -> str:
return f"#{rgb_int:06x}".upper()
2022-04-12 11:13:05 +08:00
def color_gradient(
reference_colors: Iterable[ManimColor],
length_of_output: int
) -> list[Color]:
if length_of_output == 0:
2022-04-12 11:13:05 +08:00
return []
2018-08-09 17:56:05 -07:00
rgbs = list(map(color_to_rgb, reference_colors))
alphas = np.linspace(0, (len(rgbs) - 1), length_of_output)
floors = alphas.astype('int')
alphas_mod1 = alphas % 1
# End edge case
alphas_mod1[-1] = 1
floors[-1] = len(rgbs) - 2
return [
rgb_to_color(interpolate(rgbs[i], rgbs[i + 1], alpha))
for i, alpha in zip(floors, alphas_mod1)
]
2022-04-12 11:13:05 +08:00
def interpolate_color(
color1: ManimColor,
color2: ManimColor,
alpha: float
) -> Color:
rgb = interpolate(color_to_rgb(color1), color_to_rgb(color2), alpha)
return rgb_to_color(rgb)
2022-04-12 11:13:05 +08:00
def average_color(*colors: ManimColor) -> Color:
2018-08-09 17:56:05 -07:00
rgbs = np.array(list(map(color_to_rgb, colors)))
2020-02-19 23:43:33 -08:00
return rgb_to_color(rgbs.mean(0))
2022-04-12 11:13:05 +08:00
def random_color() -> Color:
return Color(rgb=tuple(np.random.random(3)))
2022-04-12 11:13:05 +08:00
def random_bright_color() -> Color:
color = random_color()
return average_color(color, Color(WHITE))
2022-04-12 11:13:05 +08:00
def get_colormap_list(
map_name: str = "viridis",
n_colors: int = 9
2022-12-17 17:29:49 -08:00
) -> Vect3Array:
"""
Options for map_name:
3b1b_colormap
magma
inferno
plasma
viridis
cividis
twilight
twilight_shifted
turbo
"""
from matplotlib.cm import get_cmap
if map_name == "3b1b_colormap":
2022-12-17 17:29:49 -08:00
rgbs = np.array([color_to_rgb(color) for color in COLORMAP_3B1B])
else:
rgbs = get_cmap(map_name).colors # Make more general?
return resize_with_interpolation(np.array(rgbs), n_colors)