mirror of
https://github.com/3b1b/manim.git
synced 2025-09-01 00:48:45 +00:00
Merge branch 'master' of github.com:3b1b/manim into uncertainty
This commit is contained in:
commit
04fab833a8
4 changed files with 127 additions and 70 deletions
124
camera/camera.py
124
camera/camera.py
|
@ -8,7 +8,7 @@ import aggdraw
|
||||||
|
|
||||||
from helpers import *
|
from helpers import *
|
||||||
from mobject import Mobject, PMobject, VMobject, \
|
from mobject import Mobject, PMobject, VMobject, \
|
||||||
ImageMobject, Group, BackgroundColoredVMobject
|
ImageMobject, Group
|
||||||
|
|
||||||
class Camera(object):
|
class Camera(object):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
|
@ -176,19 +176,16 @@ class Camera(object):
|
||||||
return self.capture_mobjects([mobject], **kwargs)
|
return self.capture_mobjects([mobject], **kwargs)
|
||||||
|
|
||||||
def capture_mobjects(self, mobjects, **kwargs):
|
def capture_mobjects(self, mobjects, **kwargs):
|
||||||
self.reset_aggdraw_canvas()
|
|
||||||
mobjects = self.get_mobjects_to_display(mobjects, **kwargs)
|
mobjects = self.get_mobjects_to_display(mobjects, **kwargs)
|
||||||
vmobjects = []
|
vmobjects = []
|
||||||
for mobject in mobjects:
|
for mobject in mobjects:
|
||||||
if isinstance(mobject, VMobject) and not isinstance(mobject, BackgroundColoredVMobject):
|
if isinstance(mobject, VMobject):
|
||||||
vmobjects.append(mobject)
|
vmobjects.append(mobject)
|
||||||
elif len(vmobjects) > 0:
|
elif len(vmobjects) > 0:
|
||||||
self.display_multiple_vectorized_mobjects(vmobjects)
|
self.display_multiple_vectorized_mobjects(vmobjects)
|
||||||
vmobjects = []
|
vmobjects = []
|
||||||
|
|
||||||
if isinstance(mobject, BackgroundColoredVMobject):
|
if isinstance(mobject, PMobject):
|
||||||
self.display_background_colored_vmobject(mobject)
|
|
||||||
elif isinstance(mobject, PMobject):
|
|
||||||
self.display_point_cloud(
|
self.display_point_cloud(
|
||||||
mobject.points, mobject.rgbas,
|
mobject.points, mobject.rgbas,
|
||||||
self.adjusted_thickness(mobject.stroke_width)
|
self.adjusted_thickness(mobject.stroke_width)
|
||||||
|
@ -218,7 +215,18 @@ class Camera(object):
|
||||||
def display_multiple_vectorized_mobjects(self, vmobjects):
|
def display_multiple_vectorized_mobjects(self, vmobjects):
|
||||||
if len(vmobjects) == 0:
|
if len(vmobjects) == 0:
|
||||||
return
|
return
|
||||||
#More efficient to bundle together in one "canvas"
|
batches = batch_by_property(
|
||||||
|
vmobjects,
|
||||||
|
lambda vm : vm.get_background_image_file()
|
||||||
|
)
|
||||||
|
for batch in batches:
|
||||||
|
if batch[0].get_background_image_file():
|
||||||
|
self.display_multiple_background_colored_vmobject(batch)
|
||||||
|
else:
|
||||||
|
self.display_multiple_non_background_colored_vmobjects(batch)
|
||||||
|
|
||||||
|
def display_multiple_non_background_colored_vmobjects(self, vmobjects):
|
||||||
|
self.reset_aggdraw_canvas()
|
||||||
canvas = self.get_aggdraw_canvas()
|
canvas = self.get_aggdraw_canvas()
|
||||||
for vmobject in vmobjects:
|
for vmobject in vmobjects:
|
||||||
self.display_vectorized(vmobject, canvas)
|
self.display_vectorized(vmobject, canvas)
|
||||||
|
@ -287,27 +295,20 @@ class Camera(object):
|
||||||
result += " ".join(coord_strings)
|
result += " ".join(coord_strings)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def display_background_colored_vmobject(self, cvmobject):
|
def get_background_colored_vmobject_displayer(self):
|
||||||
mob_array = np.zeros(
|
#Quite wordy to type out a bunch
|
||||||
self.pixel_array.shape,
|
long_name = "background_colored_vmobject_displayer"
|
||||||
dtype = self.pixel_array_dtype
|
if not hasattr(self, long_name):
|
||||||
)
|
setattr(self, long_name, BackgroundColoredVMobjectDisplayer(self))
|
||||||
image = Image.fromarray(mob_array, mode = self.image_mode)
|
return getattr(self, long_name)
|
||||||
canvas = aggdraw.Draw(image)
|
|
||||||
self.display_vectorized(cvmobject, canvas)
|
def display_multiple_background_colored_vmobject(self, cvmobjects):
|
||||||
canvas.flush()
|
displayer = self.get_background_colored_vmobject_displayer()
|
||||||
cv_background = cvmobject.background_array
|
cvmobject_pixel_array = displayer.display(*cvmobjects)
|
||||||
if not np.all(self.pixel_array.shape == cv_background):
|
|
||||||
cvmobject.resize_background_array_to_match(self.pixel_array)
|
|
||||||
cv_background = cvmobject.background_array
|
|
||||||
array = np.array(
|
|
||||||
(np.array(mob_array).astype('float')/255.)*\
|
|
||||||
np.array(cv_background),
|
|
||||||
dtype = self.pixel_array_dtype
|
|
||||||
)
|
|
||||||
self.pixel_array[:,:] = np.maximum(
|
self.pixel_array[:,:] = np.maximum(
|
||||||
self.pixel_array, array
|
self.pixel_array, cvmobject_pixel_array
|
||||||
)
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
## Methods for other rendering
|
## Methods for other rendering
|
||||||
|
|
||||||
|
@ -505,6 +506,75 @@ class Camera(object):
|
||||||
|
|
||||||
return centered_space_coords
|
return centered_space_coords
|
||||||
|
|
||||||
|
class BackgroundColoredVMobjectDisplayer(object):
|
||||||
|
def __init__(self, camera):
|
||||||
|
self.camera = camera
|
||||||
|
self.file_name_to_pixel_array_map = {}
|
||||||
|
self.init_canvas()
|
||||||
|
|
||||||
|
def init_canvas(self):
|
||||||
|
self.pixel_array = np.zeros(
|
||||||
|
self.camera.pixel_array.shape,
|
||||||
|
dtype = self.camera.pixel_array_dtype,
|
||||||
|
)
|
||||||
|
self.reset_canvas()
|
||||||
|
|
||||||
|
def reset_canvas(self):
|
||||||
|
image = Image.fromarray(self.pixel_array, mode = self.camera.image_mode)
|
||||||
|
self.canvas = aggdraw.Draw(image)
|
||||||
|
|
||||||
|
def resize_background_array(
|
||||||
|
self, background_array,
|
||||||
|
new_width, new_height,
|
||||||
|
mode = "RGBA"
|
||||||
|
):
|
||||||
|
image = Image.fromarray(background_array, mode = mode)
|
||||||
|
resized_image = image.resize((new_width, new_height))
|
||||||
|
return np.array(resized_image)
|
||||||
|
|
||||||
|
def resize_background_array_to_match(self, background_array, pixel_array):
|
||||||
|
height, width = pixel_array.shape[:2]
|
||||||
|
mode = "RGBA" if pixel_array.shape[2] == 4 else "RGB"
|
||||||
|
return self.resize_background_array(background_array, width, height, mode)
|
||||||
|
|
||||||
|
def get_background_array(self, cvmobject):
|
||||||
|
file_name = cvmobject.get_background_image_file()
|
||||||
|
if file_name in self.file_name_to_pixel_array_map:
|
||||||
|
return self.file_name_to_pixel_array_map[file_name]
|
||||||
|
full_path = get_full_raster_image_path(file_name)
|
||||||
|
image = Image.open(full_path)
|
||||||
|
array = np.array(image)
|
||||||
|
|
||||||
|
camera = self.camera
|
||||||
|
if not np.all(camera.pixel_array.shape == array.shape):
|
||||||
|
array = self.resize_background_array_to_match(array, camera.pixel_array)
|
||||||
|
|
||||||
|
self.file_name_to_pixel_array_map[file_name] = array
|
||||||
|
return array
|
||||||
|
|
||||||
|
def display(self, *cvmobjects):
|
||||||
|
batches = batch_by_property(
|
||||||
|
cvmobjects, lambda cv : cv.get_background_image_file()
|
||||||
|
)
|
||||||
|
curr_array = None
|
||||||
|
for batch in batches:
|
||||||
|
background_array = self.get_background_array(batch[0])
|
||||||
|
for cvmobject in batch:
|
||||||
|
self.camera.display_vectorized(cvmobject, self.canvas)
|
||||||
|
self.canvas.flush()
|
||||||
|
new_array = np.array(
|
||||||
|
(background_array*self.pixel_array.astype('float')/255),
|
||||||
|
dtype = self.camera.pixel_array_dtype
|
||||||
|
)
|
||||||
|
if curr_array is None:
|
||||||
|
curr_array = new_array
|
||||||
|
else:
|
||||||
|
curr_array = np.maximum(curr_array, new_array)
|
||||||
|
self.pixel_array[:,:] = 0
|
||||||
|
self.reset_canvas()
|
||||||
|
return curr_array
|
||||||
|
|
||||||
|
|
||||||
class MovingCamera(Camera):
|
class MovingCamera(Camera):
|
||||||
"""
|
"""
|
||||||
Stays in line with the height, width and position
|
Stays in line with the height, width and position
|
||||||
|
|
18
helpers.py
18
helpers.py
|
@ -226,6 +226,24 @@ def all_elements_are_instances(iterable, Class):
|
||||||
def adjacent_pairs(objects):
|
def adjacent_pairs(objects):
|
||||||
return zip(objects, list(objects[1:])+[objects[0]])
|
return zip(objects, list(objects[1:])+[objects[0]])
|
||||||
|
|
||||||
|
def batch_by_property(items, property_func):
|
||||||
|
batches = []
|
||||||
|
def add_batch(batch):
|
||||||
|
if len(batch) > 0:
|
||||||
|
batches.append(batch)
|
||||||
|
curr_batch = []
|
||||||
|
curr_prop = None
|
||||||
|
for item in items:
|
||||||
|
prop = property_func(item)
|
||||||
|
if prop != curr_prop:
|
||||||
|
add_batch(curr_batch)
|
||||||
|
curr_prop = prop
|
||||||
|
curr_batch = [item]
|
||||||
|
else:
|
||||||
|
curr_batch.append(item)
|
||||||
|
add_batch(curr_batch)
|
||||||
|
return batches
|
||||||
|
|
||||||
def complex_to_R3(complex_num):
|
def complex_to_R3(complex_num):
|
||||||
return np.array((complex_num.real, complex_num.imag, 0))
|
return np.array((complex_num.real, complex_num.imag, 0))
|
||||||
|
|
||||||
|
|
|
@ -6,5 +6,5 @@ __all__ = [
|
||||||
|
|
||||||
from mobject import Mobject, Group
|
from mobject import Mobject, Group
|
||||||
from point_cloud_mobject import Point, Mobject1D, Mobject2D, PMobject
|
from point_cloud_mobject import Point, Mobject1D, Mobject2D, PMobject
|
||||||
from vectorized_mobject import VMobject, VGroup, BackgroundColoredVMobject
|
from vectorized_mobject import VMobject, VGroup
|
||||||
from image_mobject import ImageMobject
|
from image_mobject import ImageMobject
|
|
@ -17,6 +17,7 @@ class VMobject(Mobject):
|
||||||
"propagate_style_to_family" : False,
|
"propagate_style_to_family" : False,
|
||||||
"pre_function_handle_to_anchor_scale_factor" : 0.01,
|
"pre_function_handle_to_anchor_scale_factor" : 0.01,
|
||||||
"make_smooth_after_applying_functions" : False,
|
"make_smooth_after_applying_functions" : False,
|
||||||
|
"background_image_file" : None,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_group_class(self):
|
def get_group_class(self):
|
||||||
|
@ -151,6 +152,16 @@ class VMobject(Mobject):
|
||||||
return self.get_stroke_color()
|
return self.get_stroke_color()
|
||||||
return self.get_fill_color()
|
return self.get_fill_color()
|
||||||
|
|
||||||
|
def color_using_background_image(self, background_image_file):
|
||||||
|
self.background_image_file = background_image_file
|
||||||
|
self.highlight(WHITE)
|
||||||
|
for submob in self.submobjects:
|
||||||
|
submob.color_using_background_image(background_image_file)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_background_image_file(self):
|
||||||
|
return self.background_image_file
|
||||||
|
|
||||||
## Drawing
|
## Drawing
|
||||||
def start_at(self, point):
|
def start_at(self, point):
|
||||||
if len(self.points) == 0:
|
if len(self.points) == 0:
|
||||||
|
@ -470,46 +481,4 @@ class VectorizedPoint(VMobject):
|
||||||
def set_location(self,new_loc):
|
def set_location(self,new_loc):
|
||||||
self.set_points(np.array([new_loc]))
|
self.set_points(np.array([new_loc]))
|
||||||
|
|
||||||
class BackgroundColoredVMobject(VMobject):
|
|
||||||
CONFIG = {
|
|
||||||
# Can be set to None, using set_background_array to initialize instead
|
|
||||||
"background_image_file" : "color_background",
|
|
||||||
"stroke_color" : WHITE,
|
|
||||||
"fill_color" : WHITE,
|
|
||||||
}
|
|
||||||
def __init__(self, vmobject, **kwargs):
|
|
||||||
# Note: At the moment, this does nothing to mimic
|
|
||||||
# the full family of the vmobject passed in.
|
|
||||||
VMobject.__init__(self, **kwargs)
|
|
||||||
|
|
||||||
#Match properties of vmobject
|
|
||||||
self.points = np.array(vmobject.points)
|
|
||||||
self.set_stroke(WHITE, vmobject.get_stroke_width())
|
|
||||||
self.set_fill(WHITE, vmobject.get_fill_opacity())
|
|
||||||
for submob in vmobject.submobjects:
|
|
||||||
self.add(BackgroundColoredVMobject(submob, **kwargs))
|
|
||||||
|
|
||||||
if self.background_image_file != None:
|
|
||||||
#Initialize background array
|
|
||||||
path = get_full_raster_image_path(self.background_image_file)
|
|
||||||
image = Image.open(path)
|
|
||||||
self.set_background_array(np.array(image))
|
|
||||||
|
|
||||||
def set_background_array(self, background_array):
|
|
||||||
self.background_array = background_array
|
|
||||||
|
|
||||||
def resize_background_array(self, new_width, new_height, mode = "RGBA"):
|
|
||||||
image = Image.fromarray(self.background_array, mode = mode)
|
|
||||||
resized_image = image.resize((new_width, new_height))
|
|
||||||
self.background_array = np.array(resized_image)
|
|
||||||
|
|
||||||
def resize_background_array_to_match(self, pixel_array):
|
|
||||||
height, width = pixel_array.shape[:2]
|
|
||||||
mode = "RGBA" if pixel_array.shape[2] == 4 else "RGB"
|
|
||||||
self.resize_background_array(width, height, mode)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue