diff --git a/camera/camera.py b/camera/camera.py index 958787f9..7c6d5477 100644 --- a/camera/camera.py +++ b/camera/camera.py @@ -8,7 +8,7 @@ import aggdraw from helpers import * from mobject import Mobject, PMobject, VMobject, \ - ImageMobject, Group, BackgroundColoredVMobject + ImageMobject, Group class Camera(object): CONFIG = { @@ -176,19 +176,16 @@ class Camera(object): return self.capture_mobjects([mobject], **kwargs) def capture_mobjects(self, mobjects, **kwargs): - self.reset_aggdraw_canvas() mobjects = self.get_mobjects_to_display(mobjects, **kwargs) vmobjects = [] for mobject in mobjects: - if isinstance(mobject, VMobject) and not isinstance(mobject, BackgroundColoredVMobject): - vmobjects.append(mobject) + if isinstance(mobject, VMobject): + vmobjects.append(mobject) elif len(vmobjects) > 0: self.display_multiple_vectorized_mobjects(vmobjects) vmobjects = [] - if isinstance(mobject, BackgroundColoredVMobject): - self.display_background_colored_vmobject(mobject) - elif isinstance(mobject, PMobject): + if isinstance(mobject, PMobject): self.display_point_cloud( mobject.points, mobject.rgbas, self.adjusted_thickness(mobject.stroke_width) @@ -218,7 +215,18 @@ class Camera(object): def display_multiple_vectorized_mobjects(self, vmobjects): if len(vmobjects) == 0: 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() for vmobject in vmobjects: self.display_vectorized(vmobject, canvas) @@ -287,27 +295,20 @@ class Camera(object): result += " ".join(coord_strings) return result - def display_background_colored_vmobject(self, cvmobject): - mob_array = np.zeros( - self.pixel_array.shape, - dtype = self.pixel_array_dtype - ) - image = Image.fromarray(mob_array, mode = self.image_mode) - canvas = aggdraw.Draw(image) - self.display_vectorized(cvmobject, canvas) - canvas.flush() - cv_background = cvmobject.background_array - 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 - ) + def get_background_colored_vmobject_displayer(self): + #Quite wordy to type out a bunch + long_name = "background_colored_vmobject_displayer" + if not hasattr(self, long_name): + setattr(self, long_name, BackgroundColoredVMobjectDisplayer(self)) + return getattr(self, long_name) + + def display_multiple_background_colored_vmobject(self, cvmobjects): + displayer = self.get_background_colored_vmobject_displayer() + cvmobject_pixel_array = displayer.display(*cvmobjects) self.pixel_array[:,:] = np.maximum( - self.pixel_array, array + self.pixel_array, cvmobject_pixel_array ) + return self ## Methods for other rendering @@ -505,6 +506,75 @@ class Camera(object): 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): """ Stays in line with the height, width and position diff --git a/helpers.py b/helpers.py index d0e3c962..80ee9e33 100644 --- a/helpers.py +++ b/helpers.py @@ -226,6 +226,24 @@ def all_elements_are_instances(iterable, Class): def adjacent_pairs(objects): 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): return np.array((complex_num.real, complex_num.imag, 0)) diff --git a/mobject/__init__.py b/mobject/__init__.py index 4dbeb0f9..e87c8285 100644 --- a/mobject/__init__.py +++ b/mobject/__init__.py @@ -6,5 +6,5 @@ __all__ = [ from mobject import Mobject, Group 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 \ No newline at end of file diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index 7bb8bdf3..e732cc5b 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -17,6 +17,7 @@ class VMobject(Mobject): "propagate_style_to_family" : False, "pre_function_handle_to_anchor_scale_factor" : 0.01, "make_smooth_after_applying_functions" : False, + "background_image_file" : None, } def get_group_class(self): @@ -151,6 +152,16 @@ class VMobject(Mobject): return self.get_stroke_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 def start_at(self, point): if len(self.points) == 0: @@ -470,46 +481,4 @@ class VectorizedPoint(VMobject): def set_location(self,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) - - - - -