mirror of
https://github.com/3b1b/manim.git
synced 2025-08-01 08:54:38 +08:00
145 lines
4.7 KiB
Python
145 lines
4.7 KiB
Python
import numpy as np
|
|
import itertools as it
|
|
import os
|
|
from PIL import Image
|
|
from random import random
|
|
|
|
from tex_utils import *
|
|
from mobject import *
|
|
|
|
class ImageMobject(Mobject2D):
|
|
"""
|
|
Automatically filters out black pixels
|
|
"""
|
|
# SHOULD_BUFF_POINTS = False
|
|
def __init__(self,
|
|
image,
|
|
filter_color = "black",
|
|
invert = True,
|
|
*args, **kwargs):
|
|
#TODO, Make sure you always convert to RGB
|
|
self.filter_rgb = 255 * np.array(Color(filter_color).get_rgb()).astype('uint8')
|
|
if isinstance(image, str):
|
|
self.name = to_cammel_case(
|
|
os.path.split(image)[-1].split(".")[0]
|
|
)
|
|
possible_paths = [
|
|
image,
|
|
os.path.join(IMAGE_DIR, image),
|
|
os.path.join(IMAGE_DIR, image + ".jpg"),
|
|
os.path.join(IMAGE_DIR, image + ".png"),
|
|
]
|
|
found = False
|
|
for path in possible_paths:
|
|
if os.path.exists(path):
|
|
image = Image.open(path).convert('RGB')
|
|
found = True
|
|
if not found:
|
|
raise IOError("File not Found")
|
|
if invert:
|
|
image = invert_image(image)
|
|
self.image_array = np.array(image)
|
|
Mobject2D.__init__(self, *args, **kwargs)
|
|
|
|
def generate_points(self):
|
|
height, width = self.image_array.shape[:2]
|
|
#Flatten array, and find indices where rgb is not filter_rgb
|
|
array = self.image_array.reshape((height * width, 3))
|
|
ones = np.ones(height * width, dtype = 'bool')
|
|
for i in range(3):
|
|
ones *= (array[:,i] != self.filter_rgb[i])
|
|
indices = np.arange(height * width, dtype = 'int')[ones]
|
|
rgbs = array[indices, :].astype('float') / 255.0
|
|
|
|
points = np.zeros((indices.size, 3), dtype = 'float64')
|
|
points[:,0] = indices%width - width/2
|
|
points[:,1] = -indices/width + height/2
|
|
|
|
height, width = map(float, (height, width))
|
|
if height / width > float(DEFAULT_HEIGHT) / DEFAULT_WIDTH:
|
|
points *= 2 * SPACE_HEIGHT / height
|
|
else:
|
|
points *= 2 * SPACE_WIDTH / width
|
|
self.add_points(points, rgbs = rgbs)
|
|
|
|
def should_buffer_points(self):
|
|
# potentially changed in subclasses
|
|
return False
|
|
|
|
class SpeechBubble(ImageMobject):
|
|
def __init__(self, direction = LEFT, *args, **kwargs):
|
|
ImageMobject.__init__(self, "speech_bubble", *args, **kwargs)
|
|
self.direction = direction
|
|
self.scale(0.4)
|
|
self.center()
|
|
if direction[0] > 0:
|
|
self.rotate(np.pi, UP)
|
|
self.reload_tip()
|
|
|
|
def reload_tip(self):
|
|
#TODO, perhaps make this resiliant to different point orderings
|
|
self.tip = self.points[-1]
|
|
|
|
def speak_from(self, mobject):
|
|
dest = mobject.get_center()
|
|
dest += self.direction * mobject.get_width()/2
|
|
dest += UP * mobject.get_height()/2
|
|
self.shift(dest - self.tip)
|
|
self.reload_tip()
|
|
return self
|
|
|
|
def write(self, text):
|
|
smidgeon = 0.1*UP + 0.2*self.direction
|
|
self.clear()
|
|
self.text = text_mobject(text)
|
|
self.text.scale(0.75*self.get_width() / self.text.get_width())
|
|
self.text.shift(self.get_center() + smidgeon)
|
|
self.add(self.text)
|
|
|
|
def clear(self):
|
|
if not hasattr(self, "text"):
|
|
return
|
|
num_text_points = self.text.points.shape[0]
|
|
self.points = self.points[:num_text_points]
|
|
self.rgbs = self.rgbs[:num_text_points]
|
|
self.text = Mobject()
|
|
|
|
class ThoughtBubble(ImageMobject):
|
|
def __init__(self, *args, **kwargs):
|
|
ImageMobject.__init__(self, "thought_bubble", *args, **kwargs)
|
|
self.scale(0.5)
|
|
self.center()
|
|
|
|
class Face(ImageMobject):
|
|
def __init__(self, mode = "simple", *args, **kwargs):
|
|
"""
|
|
Mode can be "simple", "talking", "straight"
|
|
"""
|
|
ImageMobject.__init__(self, mode + "_face", *args, **kwargs)
|
|
self.scale(0.5)
|
|
self.center()
|
|
|
|
class VideoIcon(ImageMobject):
|
|
def __init__(self, *args, **kwargs):
|
|
ImageMobject.__init__(self, "video_icon", *args, **kwargs)
|
|
self.scale(0.3)
|
|
self.center()
|
|
|
|
def text_mobject(text, size = "\\Large"):
|
|
return tex_mobject(text, size, TEMPLATE_TEXT_FILE)
|
|
|
|
def tex_mobject(expression,
|
|
size = "\\Huge",
|
|
template_tex_file = TEMPLATE_TEX_FILE):
|
|
images = tex_to_image(expression, size, template_tex_file)
|
|
if isinstance(images, list):
|
|
#TODO, is checking listiness really the best here?
|
|
return CompoundMobject(*map(ImageMobject, images)).center()
|
|
else:
|
|
return ImageMobject(images).center()
|
|
|
|
|
|
|
|
|
|
|