mirror of
https://github.com/3b1b/manim.git
synced 2025-07-31 14:03:59 +08:00
First implementation of Camera to replace disp
This commit is contained in:
200
camera.py
Normal file
200
camera.py
Normal file
@ -0,0 +1,200 @@
|
||||
import numpy as np
|
||||
import itertools as it
|
||||
import os
|
||||
import sys
|
||||
from PIL import Image
|
||||
import cv2
|
||||
from colour import Color
|
||||
import progressbar
|
||||
|
||||
from helpers import *
|
||||
|
||||
class Camera(object):
|
||||
DEFAULT_CONFIG = {
|
||||
#background of a different shape will overwrite these
|
||||
"pixel_width" : DEFAULT_WIDTH,
|
||||
"pixel_height" : DEFAULT_HEIGHT,
|
||||
"background_color" : BLACK,
|
||||
#
|
||||
"space_height" : SPACE_HEIGHT,
|
||||
"space_center" : ORIGIN,
|
||||
}
|
||||
|
||||
def __init__(self, background = None, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
self.init_background()
|
||||
self.reset()
|
||||
|
||||
width_to_height = float(self.pixel_width) / self.pixel_height
|
||||
self.space_width = self.space_height * width_to_height
|
||||
|
||||
def init_background(self):
|
||||
if self.background:
|
||||
shape = self.background.shape[:2]
|
||||
self.pixel_height, self.pixel_width = shape
|
||||
else:
|
||||
background_color = Color(self.background_color)
|
||||
background_rgb = (255*np.array(
|
||||
background_color.get_rgb()
|
||||
)).astype('uint8')
|
||||
ones = np.ones(
|
||||
(self.pixel_height, self.pixel_width, 1),
|
||||
dtype = 'uint8'
|
||||
)
|
||||
self.background = np.dot(
|
||||
ones, background_rgb.reshape((1, 3))
|
||||
)
|
||||
|
||||
def get_image(self):
|
||||
return np.array(self.pixel_array)
|
||||
|
||||
def set_image(self, pixel_array):
|
||||
self.pixel_array = np.array(pixel_array)
|
||||
|
||||
def reset(self):
|
||||
self.set_image(np.array(self.background))
|
||||
|
||||
# def paint_region(region, image_array = None, color = None):
|
||||
# pixels = get_pixels(image_array)
|
||||
# assert region.shape == pixels.shape[:2]
|
||||
# if color is None:
|
||||
# #Random dark color
|
||||
# rgb = 0.5 * np.random.random(3)
|
||||
# else:
|
||||
# rgb = np.array(Color(color).get_rgb())
|
||||
# pixels[region.bool_grid] = (255*rgb).astype('uint8')
|
||||
# return pixels
|
||||
|
||||
def capture_mobject(self, mobject):
|
||||
return self.capture_mobjects([mobject])
|
||||
|
||||
def capture_mobjects(self, mobjects, include_sub_mobjects = True):
|
||||
if include_sub_mobjects:
|
||||
all_families = [
|
||||
mob.submobject_family()
|
||||
for mob in mobjects
|
||||
]
|
||||
mobjects = reduce(op.add, all_families, [])
|
||||
|
||||
for mobject in mobjects:
|
||||
self.display_points(
|
||||
mobject.points, mobject.rgbs,
|
||||
self.adjusted_thickness(mobject.point_thickness)
|
||||
)
|
||||
|
||||
def display_points(self, points, rgbs, thickness):
|
||||
if len(points) == 0:
|
||||
return
|
||||
points = self.align_points_to_camera(points)
|
||||
pixel_coords = self.points_to_pixel_coords(points)
|
||||
pixel_coords = self.thickened_coordinates(
|
||||
pixel_coords, thickness
|
||||
)
|
||||
|
||||
rgbs = (255*rgbs).astype('uint8')
|
||||
target_len = len(pixel_coords)
|
||||
factor = target_len/len(rgbs)
|
||||
rgbs = np.array([rgbs]*factor).reshape((target_len, 3))
|
||||
|
||||
on_screen_indices = self.on_screen_pixels(pixel_coords)
|
||||
pixel_coords = pixel_coords[on_screen_indices]
|
||||
rgbs = rgbs[on_screen_indices]
|
||||
|
||||
flattener = np.array([1, self.pixel_width], dtype = 'int')
|
||||
flattener = flattener.reshape((2, 1))
|
||||
indices = np.dot(pixel_coords, flattener)[:,0]
|
||||
indices = indices.astype('int')
|
||||
|
||||
pw, ph = self.pixel_width, self.pixel_height
|
||||
# new_array = np.zeros((pw*ph, 3), dtype = 'uint8')
|
||||
# new_array[indices, :] = rgbs
|
||||
new_pa = self.pixel_array.reshape((ph*pw, 3))
|
||||
new_pa[indices] = rgbs
|
||||
self.pixel_array = new_pa.reshape((ph, pw, 3))
|
||||
|
||||
|
||||
def align_points_to_camera(self, points):
|
||||
## This is where projection should live
|
||||
return points - self.space_center
|
||||
|
||||
def points_to_pixel_coords(self, points):
|
||||
result = np.zeros((len(points), 2))
|
||||
width_mult = self.pixel_width/self.space_width/2
|
||||
width_add = self.pixel_width/2
|
||||
height_mult = self.pixel_height/self.space_height/2
|
||||
height_add = self.pixel_height/2
|
||||
#Flip on y-axis as you go
|
||||
height_mult *= -1
|
||||
|
||||
result[:,0] = points[:,0]*width_mult + width_add
|
||||
result[:,1] = points[:,1]*height_mult + height_add
|
||||
return result.astype('int')
|
||||
|
||||
def on_screen_pixels(self, pixel_coords):
|
||||
return reduce(op.and_, [
|
||||
pixel_coords[:,0] >= 0,
|
||||
pixel_coords[:,0] < self.pixel_width,
|
||||
pixel_coords[:,1] >= 0,
|
||||
pixel_coords[:,1] < self.pixel_height,
|
||||
])
|
||||
|
||||
# def add_thickness(pixel_indices_and_rgbs, thickness, width, height):
|
||||
# """
|
||||
# Imagine dragging each pixel around like a paintbrush in
|
||||
# a plus-sign-shaped pixel arrangement surrounding it.
|
||||
|
||||
# Pass rgb = None to do nothing to them
|
||||
# """
|
||||
# thickness = adjusted_thickness(thickness, width, height)
|
||||
# original = np.array(pixel_indices_and_rgbs)
|
||||
# n_extra_columns = pixel_indices_and_rgbs.shape[1] - 2
|
||||
# for nudge in range(-thickness/2+1, thickness/2+1):
|
||||
# if nudge == 0:
|
||||
# continue
|
||||
# for x, y in [[nudge, 0], [0, nudge]]:
|
||||
# pixel_indices_and_rgbs = np.append(
|
||||
# pixel_indices_and_rgbs,
|
||||
# original+([x, y] + [0]*n_extra_columns),
|
||||
# axis = 0
|
||||
# )
|
||||
# admissibles = (pixel_indices_and_rgbs[:,0] >= 0) & \
|
||||
# (pixel_indices_and_rgbs[:,0] < width) & \
|
||||
# (pixel_indices_and_rgbs[:,1] >= 0) & \
|
||||
# (pixel_indices_and_rgbs[:,1] < height)
|
||||
# return pixel_indices_and_rgbs[admissibles]
|
||||
|
||||
def adjusted_thickness(self, thickness):
|
||||
big_width = PRODUCTION_QUALITY_DISPLAY_CONFIG["width"]
|
||||
big_height = PRODUCTION_QUALITY_DISPLAY_CONFIG["height"]
|
||||
factor = (big_width + big_height) / \
|
||||
(self.pixel_width + self.pixel_height)
|
||||
return 1 + (thickness-1)/factor
|
||||
|
||||
def get_thickening_nudges(self, thickness):
|
||||
_range = range(-thickness/2+1, thickness/2+1)
|
||||
return np.array(list(it.product(*[_range]*2)))
|
||||
|
||||
def thickened_coordinates(self, pixel_coords, thickness):
|
||||
nudges = self.get_thickening_nudges(thickness)
|
||||
pixel_coords = np.array([
|
||||
pixel_coords + nudge
|
||||
for nudge in nudges
|
||||
])
|
||||
size = pixel_coords.size
|
||||
return pixel_coords.reshape((size/2, 2))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -84,6 +84,8 @@ MAX_LEN_FOR_HUGE_TEX_FONT = 25
|
||||
|
||||
LOGO_PATH = os.path.join(IMAGE_DIR, "logo.png")
|
||||
|
||||
FFMPEG_BIN = "ffmpeg"
|
||||
|
||||
|
||||
### Colors ###
|
||||
|
||||
|
267
displayer.py
267
displayer.py
@ -1,6 +1,5 @@
|
||||
import numpy as np
|
||||
import itertools as it
|
||||
import subprocess as sp
|
||||
import os
|
||||
import sys
|
||||
from PIL import Image
|
||||
@ -10,40 +9,76 @@ import progressbar
|
||||
|
||||
from helpers import *
|
||||
|
||||
FFMPEG_BIN = "ffmpeg"
|
||||
class Camera(object):
|
||||
DEFAULT_CONFIG = {
|
||||
"pixel_width" : DEFAULT_WIDTH,
|
||||
"pixel_height" : DEFAULT_HEIGHT,
|
||||
"space_height" : SPACE_HEIGHT,
|
||||
"space_center" : ORIGIN,
|
||||
"background_color" : BLACK,
|
||||
}
|
||||
|
||||
def get_pixels(image_array): #TODO, FIX WIDTH/HEIGHT PROBLEM HERE
|
||||
if image_array is None:
|
||||
return np.zeros(
|
||||
(DEFAULT_HEIGHT, DEFAULT_WIDTH, 3),
|
||||
def __init__(self, background = None, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
self.init_background()
|
||||
self.reset()
|
||||
|
||||
width_to_height = float(self.pixel_width) / self.pixel_height
|
||||
self.space_width = self.space_height * width_to_height
|
||||
|
||||
def init_background(self):
|
||||
if self.background:
|
||||
shape = self.background.shape[:2]
|
||||
self.pixel_height, self.pixel_width = shape
|
||||
else:
|
||||
background_color = Color(self.background_color)
|
||||
background_rgb = np.array(background_color.get_rgb())
|
||||
ones = np.ones(
|
||||
(self.pixel_height, self.pixel_width, 1),
|
||||
dtype = 'uint8'
|
||||
)
|
||||
else:
|
||||
pixels = np.array(image_array).astype('uint8')
|
||||
assert len(pixels.shape) == 3 and pixels.shape[2] == 3
|
||||
return pixels
|
||||
self.background = np.dot(
|
||||
ones, background_rgb.reshape((1, 3))
|
||||
)
|
||||
|
||||
def paint_region(region, image_array = None, color = None):
|
||||
pixels = get_pixels(image_array)
|
||||
assert region.shape == pixels.shape[:2]
|
||||
if color is None:
|
||||
#Random dark color
|
||||
rgb = 0.5 * np.random.random(3)
|
||||
else:
|
||||
rgb = np.array(Color(color).get_rgb())
|
||||
pixels[region.bool_grid] = (255*rgb).astype('uint8')
|
||||
return pixels
|
||||
def get_image(self):
|
||||
return self.pixel_array
|
||||
|
||||
def paint_mobject(mobject, image_array = None):
|
||||
return paint_mobjects([mobject], image_array)
|
||||
def reset(self):
|
||||
self.pixel_array = np.array(self.background)
|
||||
|
||||
def paint_mobjects(mobjects, image_array = None, include_sub_mobjects = True):
|
||||
pixels = get_pixels(image_array)
|
||||
height = pixels.shape[0]
|
||||
width = pixels.shape[1]
|
||||
space_height = SPACE_HEIGHT
|
||||
space_width = SPACE_HEIGHT * width / height
|
||||
pixels = pixels.reshape((pixels.size/3, 3)).astype('uint8')
|
||||
# def get_pixels(image_array): #TODO, FIX WIDTH/HEIGHT PROBLEM HERE
|
||||
# if image_array is None:
|
||||
# return np.zeros(
|
||||
# (DEFAULT_HEIGHT, DEFAULT_WIDTH, 3),
|
||||
# dtype = 'uint8'
|
||||
# )
|
||||
# else:
|
||||
# pixels = np.array(image_array).astype('uint8')
|
||||
# assert len(pixels.shape) == 3 and pixels.shape[2] == 3
|
||||
# return pixels
|
||||
|
||||
# def paint_region(region, image_array = None, color = None):
|
||||
# pixels = get_pixels(image_array)
|
||||
# assert region.shape == pixels.shape[:2]
|
||||
# if color is None:
|
||||
# #Random dark color
|
||||
# rgb = 0.5 * np.random.random(3)
|
||||
# else:
|
||||
# rgb = np.array(Color(color).get_rgb())
|
||||
# pixels[region.bool_grid] = (255*rgb).astype('uint8')
|
||||
# return pixels
|
||||
|
||||
def capture_mobject(self, mobject):
|
||||
return self.capture_mobjects([mobject])
|
||||
|
||||
def capture_mobjects(self, mobjects, include_sub_mobjects = True):
|
||||
# pixels = get_pixels(image_array)
|
||||
# height = pixels.shape[0]
|
||||
# width = pixels.shape[1]
|
||||
# space_height = SPACE_HEIGHT
|
||||
# space_width = SPACE_HEIGHT * width / height
|
||||
# pixels = pixels.reshape((pixels.size/3, 3)).astype('uint8')
|
||||
|
||||
if include_sub_mobjects:
|
||||
all_families = [
|
||||
@ -53,122 +88,82 @@ def paint_mobjects(mobjects, image_array = None, include_sub_mobjects = True):
|
||||
mobjects = reduce(op.add, all_families, [])
|
||||
|
||||
for mobject in mobjects:
|
||||
if mobject.get_num_points() == 0:
|
||||
continue
|
||||
#bunch these together so rgbs never get lost from points
|
||||
points_and_rgbs = np.append(
|
||||
mobject.points,
|
||||
255*mobject.rgbs,
|
||||
axis = 1
|
||||
self.display_points(
|
||||
mobject.points, mobject.rgbs,
|
||||
mobject.point_thickness
|
||||
)
|
||||
points_and_rgbs = place_on_screen(
|
||||
points_and_rgbs,
|
||||
space_width, space_height
|
||||
)
|
||||
#Map points to pixel space, which requires rescaling and shifting
|
||||
#Remember, 2*space_height -> height
|
||||
points_and_rgbs[:,0] = points_and_rgbs[:,0]*width/space_width/2 + width/2
|
||||
|
||||
def display_points(self, points, rgbs, thickness):
|
||||
points = self.project_onto_screen(points)
|
||||
pixel_coordinates = self.pixel_coordinates_of_points(points)
|
||||
rgbs = (255*rgbs).astype('uint8')
|
||||
on_screen_indices = self.on_screen_pixels(pixel_coordinates)
|
||||
pixel_coordinates = pixel_coordinates[on_screen_indices]
|
||||
rgbs = rgbs[on_screen_indices]
|
||||
|
||||
flattener = np.array([1, self.width], dtype = 'int')
|
||||
flattener = flattener.reshape((2, 1))
|
||||
indices = np.dot(pixel_coordinates, flattener)[:,0]
|
||||
|
||||
pw, ph = self.pixel_width, self.pixel_height
|
||||
self.pixel_array.reshape((pw*ph, 3))
|
||||
self.pixel_array[indices] = rgbs.astype('uint8')
|
||||
self.pixel_array.reshape((ph, pw, 3))
|
||||
|
||||
def project_onto_screen(points):
|
||||
## TODO
|
||||
points[:,2] = 0
|
||||
|
||||
def pixel_coordinates_of_points(self, points):
|
||||
result = np.zeros((len(points), 2))
|
||||
width_mult = self.pixel_width/self.space_width/2
|
||||
width_add = self.pixel_width/2
|
||||
height_mult = self.pixel_height/self.space_height/2
|
||||
height_add = self.pixel_height/2
|
||||
#Flip on y-axis as you go
|
||||
points_and_rgbs[:,1] = -1*points_and_rgbs[:,1]*height/space_height/2 + height/2
|
||||
points_and_rgbs = add_thickness(
|
||||
points_and_rgbs.astype('int'),
|
||||
mobject.point_thickness,
|
||||
width, height
|
||||
)
|
||||
height_mult *= -1
|
||||
|
||||
points, rgbs = points_and_rgbs[:,:2], points_and_rgbs[:,2:]
|
||||
flattener = np.array([[1], [width]], dtype = 'int')
|
||||
indices = np.dot(points, flattener)[:,0]
|
||||
pixels[indices] = rgbs.astype('uint8')
|
||||
return pixels.reshape((height, width, 3))
|
||||
result[:,0] = points[:,0]*width_mult + width_add
|
||||
result[:,1] = points[:,1]*height_mult + height_add
|
||||
return result
|
||||
|
||||
def add_thickness(pixel_indices_and_rgbs, thickness, width, height):
|
||||
"""
|
||||
Imagine dragging each pixel around like a paintbrush in
|
||||
a plus-sign-shaped pixel arrangement surrounding it.
|
||||
def on_screen_pixels(self, pixel_coordinates):
|
||||
return (pixel_coordinates[:,0] < 0) | \
|
||||
(pixel_coordinates[:,0] >= width) | \
|
||||
(pixel_coordinates[:,1] < 0) | \
|
||||
(pixel_coordinates[:,1] >= height)
|
||||
|
||||
Pass rgb = None to do nothing to them
|
||||
"""
|
||||
thickness = adjusted_thickness(thickness, width, height)
|
||||
original = np.array(pixel_indices_and_rgbs)
|
||||
n_extra_columns = pixel_indices_and_rgbs.shape[1] - 2
|
||||
for nudge in range(-thickness/2+1, thickness/2+1):
|
||||
if nudge == 0:
|
||||
continue
|
||||
for x, y in [[nudge, 0], [0, nudge]]:
|
||||
pixel_indices_and_rgbs = np.append(
|
||||
pixel_indices_and_rgbs,
|
||||
original+([x, y] + [0]*n_extra_columns),
|
||||
axis = 0
|
||||
)
|
||||
admissibles = (pixel_indices_and_rgbs[:,0] >= 0) & \
|
||||
(pixel_indices_and_rgbs[:,0] < width) & \
|
||||
(pixel_indices_and_rgbs[:,1] >= 0) & \
|
||||
(pixel_indices_and_rgbs[:,1] < height)
|
||||
return pixel_indices_and_rgbs[admissibles]
|
||||
|
||||
def adjusted_thickness(thickness, width, height):
|
||||
# def add_thickness(pixel_indices_and_rgbs, thickness, width, height):
|
||||
# """
|
||||
# Imagine dragging each pixel around like a paintbrush in
|
||||
# a plus-sign-shaped pixel arrangement surrounding it.
|
||||
|
||||
# Pass rgb = None to do nothing to them
|
||||
# """
|
||||
# thickness = adjusted_thickness(thickness, width, height)
|
||||
# original = np.array(pixel_indices_and_rgbs)
|
||||
# n_extra_columns = pixel_indices_and_rgbs.shape[1] - 2
|
||||
# for nudge in range(-thickness/2+1, thickness/2+1):
|
||||
# if nudge == 0:
|
||||
# continue
|
||||
# for x, y in [[nudge, 0], [0, nudge]]:
|
||||
# pixel_indices_and_rgbs = np.append(
|
||||
# pixel_indices_and_rgbs,
|
||||
# original+([x, y] + [0]*n_extra_columns),
|
||||
# axis = 0
|
||||
# )
|
||||
# admissibles = (pixel_indices_and_rgbs[:,0] >= 0) & \
|
||||
# (pixel_indices_and_rgbs[:,0] < width) & \
|
||||
# (pixel_indices_and_rgbs[:,1] >= 0) & \
|
||||
# (pixel_indices_and_rgbs[:,1] < height)
|
||||
# return pixel_indices_and_rgbs[admissibles]
|
||||
|
||||
def adjusted_thickness(thickness, width, height):
|
||||
big_width = PRODUCTION_QUALITY_DISPLAY_CONFIG["width"]
|
||||
big_height = PRODUCTION_QUALITY_DISPLAY_CONFIG["height"]
|
||||
factor = (big_width + big_height) / (width + height)
|
||||
return 1 + (thickness-1)/factor
|
||||
|
||||
def place_on_screen(points_and_rgbs, space_width, space_height):
|
||||
"""
|
||||
Projects points to 2d space and remove those outside a
|
||||
the space constraints.
|
||||
|
||||
Pass rbgs = None to do nothing to them.
|
||||
"""
|
||||
# Remove 3rd column
|
||||
points_and_rgbs = np.append(
|
||||
points_and_rgbs[:, :2],
|
||||
points_and_rgbs[:, 3:],
|
||||
axis = 1
|
||||
)
|
||||
|
||||
#Removes points out of space
|
||||
to_keep = (abs(points_and_rgbs[:,0]) < space_width) & \
|
||||
(abs(points_and_rgbs[:,1]) < space_height)
|
||||
return points_and_rgbs[to_keep]
|
||||
|
||||
def get_file_path(name, extension):
|
||||
file_path = os.path.join(MOVIE_DIR, name)
|
||||
if not file_path.endswith(extension):
|
||||
file_path += extension
|
||||
directory = os.path.split(file_path)[0]
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
return file_path
|
||||
|
||||
def write_to_movie(scene, name):
|
||||
file_path = get_file_path(name, ".mp4")
|
||||
print "Writing to %s"%file_path
|
||||
|
||||
fps = int(1/scene.frame_duration)
|
||||
dim = (scene.width, scene.height)
|
||||
|
||||
command = [
|
||||
FFMPEG_BIN,
|
||||
'-y', # overwrite output file if it exists
|
||||
'-f', 'rawvideo',
|
||||
'-vcodec','rawvideo',
|
||||
'-s', '%dx%d'%dim, # size of one frame
|
||||
'-pix_fmt', 'rgb24',
|
||||
'-r', str(fps), # frames per second
|
||||
'-i', '-', # The imput comes from a pipe
|
||||
'-an', # Tells FFMPEG not to expect any audio
|
||||
'-vcodec', 'mpeg',
|
||||
'-c:v', 'libx264',
|
||||
'-pix_fmt', 'yuv420p',
|
||||
'-loglevel', 'error',
|
||||
file_path,
|
||||
]
|
||||
process = sp.Popen(command, stdin=sp.PIPE)
|
||||
for frame in scene.frames:
|
||||
process.stdin.write(frame.tostring())
|
||||
process.stdin.close()
|
||||
process.wait()
|
||||
return 1 + (thickness-1)/facto
|
||||
|
||||
|
||||
|
||||
|
147
scene/scene.py
147
scene/scene.py
@ -8,13 +8,13 @@ import os
|
||||
import copy
|
||||
from tqdm import tqdm as ProgressDisplay
|
||||
import inspect
|
||||
import subprocess as sp
|
||||
|
||||
from helpers import *
|
||||
|
||||
import displayer as disp
|
||||
from camera import Camera
|
||||
from tk_scene import TkSceneRoot
|
||||
from mobject import Mobject
|
||||
from animation.transform import ApplyMethod
|
||||
|
||||
class Scene(object):
|
||||
DEFAULT_CONFIG = {
|
||||
@ -27,16 +27,11 @@ class Scene(object):
|
||||
}
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
if self.background is not None:
|
||||
self.original_background = np.array(self.background)
|
||||
#TODO, Error checking?
|
||||
else:
|
||||
self.original_background = np.zeros(
|
||||
(self.height, self.width, 3),
|
||||
dtype = 'uint8'
|
||||
self.camera = Camera(
|
||||
pixel_width = self.width,
|
||||
pixel_height = self.height,
|
||||
background = self.background
|
||||
)
|
||||
self.background = self.original_background
|
||||
self.curr_frame = self.background
|
||||
self.frames = []
|
||||
self.mobjects = []
|
||||
self.num_animations = 0
|
||||
@ -57,11 +52,7 @@ class Scene(object):
|
||||
return self
|
||||
|
||||
def get_frame(self):
|
||||
return self.curr_frame
|
||||
|
||||
def set_frame(self, frame):
|
||||
self.curr_frame = frame
|
||||
return self
|
||||
return self.camera.get_image()
|
||||
|
||||
def add(self, *mobjects):
|
||||
"""
|
||||
@ -70,9 +61,9 @@ class Scene(object):
|
||||
"""
|
||||
if not all_elements_are_instances(mobjects, Mobject):
|
||||
raise Exception("Adding something which is not a mobject")
|
||||
self.set_frame(disp.paint_mobjects(mobjects, self.get_frame()))
|
||||
old_mobjects = filter(lambda m : m not in mobjects, self.mobjects)
|
||||
self.mobjects = old_mobjects + list(mobjects)
|
||||
self.camera.capture_mobjects(mobjects)
|
||||
return self
|
||||
|
||||
def add_mobjects_among(self, values):
|
||||
@ -101,56 +92,17 @@ class Scene(object):
|
||||
def bring_to_back(self, mobject):
|
||||
self.remove(mobject)
|
||||
self.mobjects = [mobject] + self.mobjects
|
||||
self.repaint_mojects()
|
||||
return self
|
||||
|
||||
def clear(self):
|
||||
self.reset_background()
|
||||
self.mobjects = []
|
||||
self.set_frame(self.background)
|
||||
return self
|
||||
|
||||
def highlight_region(self, region, color = None):
|
||||
self.background = disp.paint_region(
|
||||
region,
|
||||
image_array = self.background,
|
||||
color = color,
|
||||
)
|
||||
self.repaint_mojects()
|
||||
return self
|
||||
|
||||
def highlight_region_over_time_range(self, region, time_range = None, color = "black"):
|
||||
if time_range:
|
||||
frame_range = map(lambda t : t / self.frame_duration, time_range)
|
||||
frame_range[0] = max(frame_range[0], 0)
|
||||
frame_range[1] = min(frame_range[1], len(self.frames))
|
||||
else:
|
||||
frame_range = (0, len(self.frames))
|
||||
for index in range(frame_range[0], frame_range[1]):
|
||||
self.frames[index] = disp.paint_region(
|
||||
region,
|
||||
image_array = self.frames[index],
|
||||
color = color
|
||||
)
|
||||
|
||||
def reset_background(self):
|
||||
self.background = self.original_background
|
||||
return self
|
||||
|
||||
def repaint_mojects(self):
|
||||
self.set_frame(disp.paint_mobjects(self.mobjects, self.background))
|
||||
self.camera.reset()
|
||||
self.camera.capture_mobjects(self.mobjects)
|
||||
return self
|
||||
|
||||
def paint_into_background(self, *mobjects):
|
||||
#This way current mobjects don't have to be redrawn with
|
||||
#every change, and one can later call "apply" without worrying
|
||||
#about it applying to these mobjects
|
||||
self.background = disp.paint_mobjects(mobjects, self.background)
|
||||
return self
|
||||
|
||||
def set_frame_as_background(self):
|
||||
self.background = self.get_frame()
|
||||
|
||||
def play(self, *animations, **kwargs):
|
||||
self.num_animations += 1
|
||||
if "run_time" in kwargs:
|
||||
@ -170,11 +122,12 @@ class Scene(object):
|
||||
lambda m : m not in moving_mobjects,
|
||||
bundle.submobject_family()
|
||||
)
|
||||
background = disp.paint_mobjects(
|
||||
self.camera.reset()
|
||||
self.camera.capture_mobjects(
|
||||
static_mobjects,
|
||||
self.background,
|
||||
include_sub_mobjects = False
|
||||
)
|
||||
static_image = self.camera.get_image()
|
||||
|
||||
times = np.arange(0, run_time, self.frame_duration)
|
||||
time_progression = ProgressDisplay(times)
|
||||
@ -186,15 +139,15 @@ class Scene(object):
|
||||
for t in time_progression:
|
||||
for animation in animations:
|
||||
animation.update(t / animation.run_time)
|
||||
new_frame = disp.paint_mobjects(
|
||||
[anim.mobject for anim in animations],
|
||||
background
|
||||
)
|
||||
self.frames.append(new_frame)
|
||||
self.camera.capture_mobjects([
|
||||
anim.mobject
|
||||
for anim in animations
|
||||
])
|
||||
self.frames.append(self.camera.get_image())
|
||||
self.camera.set_image(static_image)
|
||||
for animation in animations:
|
||||
animation.clean_up()
|
||||
self.add(*[anim.mobject for anim in animations])
|
||||
self.repaint_mojects()
|
||||
return self
|
||||
|
||||
def play_over_time_range(self, t0, t1, *animations):
|
||||
@ -215,14 +168,14 @@ class Scene(object):
|
||||
for animation in animations:
|
||||
animation.update((t-t0)/(t1 - t0))
|
||||
index = int(t/self.frame_duration)
|
||||
self.frames[index] = disp.paint_mobjects(moving_mobjects, self.frames[index])
|
||||
self.camera.set_image(self.frames[index])
|
||||
self.camera.capture_mobjects(moving_mobjects)
|
||||
self.frames[index] = self.camera.get_image()
|
||||
for animation in animations:
|
||||
animation.clean_up()
|
||||
self.repaint_mojects()
|
||||
return self
|
||||
|
||||
def apply(self, mobject_method, *args, **kwargs):
|
||||
self.play(ApplyMethod(mobject_method, *args, **kwargs))
|
||||
|
||||
def dither(self, duration = DEFAULT_DITHER_TIME):
|
||||
self.frames += [self.get_frame()]*int(duration / self.frame_duration)
|
||||
return self
|
||||
@ -243,14 +196,6 @@ class Scene(object):
|
||||
]
|
||||
return self
|
||||
|
||||
def write_to_movie(self, name = None):
|
||||
if len(self.frames) == 0:
|
||||
print "No frames, I'll just save an image instead"
|
||||
self.show_frame()
|
||||
self.save_image(name = name)
|
||||
return
|
||||
disp.write_to_movie(self, name or str(self))
|
||||
|
||||
def show_frame(self):
|
||||
Image.fromarray(self.get_frame()).show()
|
||||
|
||||
@ -265,6 +210,52 @@ class Scene(object):
|
||||
os.makedirs(path)
|
||||
Image.fromarray(self.get_frame()).save(full_path)
|
||||
|
||||
def get_movie_file_path(self, name, extension):
|
||||
file_path = os.path.join(MOVIE_DIR, name)
|
||||
if not file_path.endswith(extension):
|
||||
file_path += extension
|
||||
directory = os.path.split(file_path)[0]
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
return file_path
|
||||
|
||||
def write_to_movie(self, name = None):
|
||||
if len(self.frames) == 0:
|
||||
print "No frames, I'll just save an image instead"
|
||||
self.show_frame()
|
||||
self.save_image(name = name)
|
||||
return
|
||||
if name is None:
|
||||
name = str(self)
|
||||
|
||||
file_path = self.get_movie_file_path(name, ".mp4")
|
||||
print "Writing to %s"%file_path
|
||||
|
||||
fps = int(1/self.frame_duration)
|
||||
dim = (self.width, self.height)
|
||||
|
||||
command = [
|
||||
FFMPEG_BIN,
|
||||
'-y', # overwrite output file if it exists
|
||||
'-f', 'rawvideo',
|
||||
'-vcodec','rawvideo',
|
||||
'-s', '%dx%d'%dim, # size of one frame
|
||||
'-pix_fmt', 'rgb24',
|
||||
'-r', str(fps), # frames per second
|
||||
'-i', '-', # The imput comes from a pipe
|
||||
'-an', # Tells FFMPEG not to expect any audio
|
||||
'-vcodec', 'mpeg',
|
||||
'-c:v', 'libx264',
|
||||
'-pix_fmt', 'yuv420p',
|
||||
'-loglevel', 'error',
|
||||
file_path,
|
||||
]
|
||||
process = sp.Popen(command, stdin=sp.PIPE)
|
||||
for frame in self.frames:
|
||||
process.stdin.write(frame.tostring())
|
||||
process.stdin.close()
|
||||
process.wait()
|
||||
|
||||
# To list possible args that subclasses have
|
||||
# Elements should always be a tuple
|
||||
args_list = []
|
||||
|
@ -27,8 +27,6 @@ class PiCreature(Mobject):
|
||||
'mouth',
|
||||
]
|
||||
WHITE_PART_NAMES = ['left_eye', 'right_eye', 'mouth']
|
||||
MOUTH_NAMES = ["smile", "frown", "straight_mouth"]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
Mobject.__init__(self, **kwargs)
|
||||
for part_name in self.PART_NAMES:
|
||||
@ -39,43 +37,26 @@ class PiCreature(Mobject):
|
||||
if part_name not in self.WHITE_PART_NAMES:
|
||||
mob.highlight(self.color)
|
||||
setattr(self, part_name, mob)
|
||||
self.add(mob)
|
||||
self.eyes = Mobject(self.left_eye, self.right_eye)
|
||||
self.legs = Mobject(self.left_leg, self.right_leg)
|
||||
mouth_center = self.get_mouth_center()
|
||||
self.mouth.center()
|
||||
self.smile = self.mouth
|
||||
self.frown = self.mouth.copy().rotate(np.pi, RIGHT)
|
||||
self.straight_mouth = TexMobject("-").scale(0.7)
|
||||
for mouth in self.smile, self.frown, self.straight_mouth:
|
||||
mouth.sort_points(lambda p : p[0])
|
||||
mouth.highlight(self.color) ##to blend into background
|
||||
mouth.shift(mouth_center)
|
||||
self.digest_mobject_attrs()
|
||||
self.give_smile()
|
||||
self.mouth.center().shift(self.get_mouth_center())
|
||||
self.add(self.mouth)
|
||||
self.scale(PI_CREATURE_SCALE_VAL)
|
||||
|
||||
|
||||
def get_parts(self):
|
||||
return [getattr(self, pn) for pn in self.PART_NAMES]
|
||||
|
||||
def get_white_parts(self):
|
||||
return [
|
||||
getattr(self, pn)
|
||||
for pn in self.WHITE_PART_NAMES+self.MOUTH_NAMES
|
||||
for pn in self.WHITE_PART_NAMES
|
||||
]
|
||||
|
||||
def get_mouth_center(self):
|
||||
result = self.body.get_center()
|
||||
result[0] = self.eyes.get_center()[0]
|
||||
return result
|
||||
# left_center = self.left_eye.get_center()
|
||||
# right_center = self.right_eye.get_center()
|
||||
# l_to_r = right_center-left_center
|
||||
# eyes_to_mouth = rotate_vector(l_to_r, -np.pi/2, OUT)
|
||||
# eyes_to_mouth /= np.linalg.norm(eyes_to_mouth)
|
||||
# return left_center/2 + right_center/2 + \
|
||||
# PI_CREATURE_MOUTH_TO_EYES_DISTANCE*eyes_to_mouth
|
||||
|
||||
def highlight(self, color, condition = None):
|
||||
for part in set(self.get_parts()).difference(self.get_white_parts()):
|
||||
@ -86,25 +67,6 @@ class PiCreature(Mobject):
|
||||
self.shift(destination-self.get_bottom())
|
||||
return self
|
||||
|
||||
def change_mouth_to(self, mouth_name):
|
||||
#TODO, This is poorly implemented
|
||||
self.mouth = getattr(self, mouth_name)
|
||||
self.sub_mobjects = list_update(
|
||||
self.sub_mobjects,
|
||||
self.get_parts()
|
||||
)
|
||||
self.mouth.highlight(WHITE)
|
||||
return self
|
||||
|
||||
def give_smile(self):
|
||||
return self.change_mouth_to("smile")
|
||||
|
||||
def give_frown(self):
|
||||
return self.change_mouth_to("frown")
|
||||
|
||||
def give_straight_face(self):
|
||||
return self.change_mouth_to("straight_mouth")
|
||||
|
||||
def get_eye_center(self):
|
||||
return self.eyes.get_center()
|
||||
|
||||
|
Reference in New Issue
Block a user