TexMobjects are looking pretty good

This commit is contained in:
Grant Sanderson 2016-04-17 12:59:53 -07:00
parent 0d4e928b6e
commit f5e4c3b334
7 changed files with 119 additions and 62 deletions

View file

@ -15,14 +15,9 @@ class Transform(Animation):
"path_func" : straight_path "path_func" : straight_path
} }
def __init__(self, mobject, ending_mobject, **kwargs): def __init__(self, mobject, ending_mobject, **kwargs):
mobject =mobject
#Copy ending_mobject so as to not mess with caller #Copy ending_mobject so as to not mess with caller
ending_mobject = ending_mobject.copy() ending_mobject = ending_mobject.copy()
digest_config(self, kwargs, locals()) digest_config(self, kwargs, locals())
count1, count2 = mobject.get_num_points(), ending_mobject.get_num_points()
if count2 == 0:
ending_mobject = mobject.get_point_mobject()
count2 = ending_mobject.get_num_points()
mobject.align_data(ending_mobject) mobject.align_data(ending_mobject)
Animation.__init__(self, mobject, **kwargs) Animation.__init__(self, mobject, **kwargs)

View file

@ -1,11 +1,9 @@
import numpy as np import numpy as np
import itertools as it import itertools as it
import os import os
import sys
from PIL import Image from PIL import Image
import cv2
from colour import Color from colour import Color
import progressbar
import aggdraw import aggdraw
from helpers import * from helpers import *
@ -71,15 +69,22 @@ class Camera(object):
mob.nonempty_family_members() mob.nonempty_family_members()
for mob in mobjects for mob in mobjects
]) ])
vmobjects = []
for mobject in mobjects: for mobject in mobjects:
if isinstance(mobject, VMobject): if isinstance(mobject, VMobject):
self.display_vectorized(mobject) vmobjects.append(mobject)
elif isinstance(mobject, PMobject): elif isinstance(mobject, PMobject):
self.display_point_cloud( self.display_point_cloud(
mobject.points, mobject.rgbs, mobject.points, mobject.rgbs,
self.adjusted_thickness(mobject.stroke_width) self.adjusted_thickness(mobject.stroke_width)
) )
#TODO, more? Call out if it's unknown? #TODO, more? Call out if it's unknown?
image = Image.fromarray(self.pixel_array, mode = "RGB")
canvas = aggdraw.Draw(image)
for vmobject in vmobjects:
self.display_vectorized(vmobject, canvas)
canvas.flush()
self.pixel_array[:,:] = np.array(image)
def display_region(self, region): def display_region(self, region):
@ -95,19 +100,16 @@ class Camera(object):
self.pixel_array[covered] = rgb self.pixel_array[covered] = rgb
def display_vectorized(self, vmobject): def display_vectorized(self, vmobject, canvas):
if vmobject.is_subpath: if vmobject.is_subpath:
#Subpath vectorized mobjects are taken care #Subpath vectorized mobjects are taken care
#of by their parent #of by their parent
return return
im = Image.fromarray(self.pixel_array, mode = "RGB")
canvas = aggdraw.Draw(im)
pen, fill = self.get_pen_and_fill(vmobject) pen, fill = self.get_pen_and_fill(vmobject)
pathstring = self.get_pathstring(vmobject) pathstring = self.get_pathstring(vmobject)
symbol = aggdraw.Symbol(pathstring) symbol = aggdraw.Symbol(pathstring)
canvas.symbol((0, 0), symbol, pen, fill) canvas.symbol((0, 0), symbol, pen, fill)
canvas.flush()
self.pixel_array[:,:] = np.array(im)
def get_pen_and_fill(self, vmobject): def get_pen_and_fill(self, vmobject):
pen = aggdraw.Pen( pen = aggdraw.Pen(
@ -122,7 +124,7 @@ class Camera(object):
def get_pathstring(self, vmobject): def get_pathstring(self, vmobject):
result = "" result = ""
for mob in [vmobject]+vmobject.subpath_mobjects: for mob in [vmobject]+vmobject.get_subpath_mobjects():
points = mob.points points = mob.points
if len(points) == 0: if len(points) == 0:
continue continue
@ -166,12 +168,11 @@ class Camera(object):
indices = np.dot(pixel_coords, flattener)[:,0] indices = np.dot(pixel_coords, flattener)[:,0]
indices = indices.astype('int') indices = indices.astype('int')
# new_array = np.zeros((pw*ph, 3), dtype = 'uint8')
# new_array[indices, :] = rgbs
new_pa = self.pixel_array.reshape((ph*pw, 3)) new_pa = self.pixel_array.reshape((ph*pw, 3))
new_pa[indices] = rgbs new_pa[indices] = rgbs
self.pixel_array = new_pa.reshape((ph, pw, 3)) self.pixel_array = new_pa.reshape((ph, pw, 3))
def align_points_to_camera(self, points): def align_points_to_camera(self, points):
## This is where projection should live ## This is where projection should live
return points - self.space_center return points - self.space_center

View file

@ -452,16 +452,24 @@ class Mobject(object):
def push_self_into_submobjects(self): def push_self_into_submobjects(self):
copy = self.copy() copy = self.copy()
copy.submobjects = [] copy.submobjects = []
self.points = np.zeros((0, self.dim)) self.init_points()
self.add(copy) self.add(copy)
return self return self
def add_n_more_submobjects(self, n): def add_n_more_submobjects(self, n):
if n > 0 and len(self.submobjects) == 0: curr = len(self.submobjects)
if n > 0 and curr == 0:
self.add(self.copy()) self.add(self.copy())
n = n-1 n -= 1
for i in range(n): curr += 1
self.add(self.submobjects[i].copy()) indices = curr*np.arange(curr+n)/(curr+n)
new_submobjects = []
for index in indices:
submob = self.submobjects[index]
if submob in new_submobjects:
submob = submob.copy()
new_submobjects.append(submob)
self.submobjects = new_submobjects
return self return self
def interpolate(self, mobject1, mobject2, alpha, path_func): def interpolate(self, mobject1, mobject2, alpha, path_func):

View file

@ -5,7 +5,7 @@ from vectorized_mobject import VMobject
from topics.geometry import Rectangle, Circle from topics.geometry import Rectangle, Circle
from helpers import * from helpers import *
SVG_SCALE_VALUE = 0.1 SVG_SCALE_VALUE = 0.05
class SVGMobject(VMobject): class SVGMobject(VMobject):
CONFIG = { CONFIG = {
@ -150,8 +150,7 @@ class VMobjectFromSVGPathstring(VMobject):
new_points = self.string_to_points(coord_string) new_points = self.string_to_points(coord_string)
if command == "M": #moveto if command == "M": #moveto
if len(points) > 0: if len(points) > 0:
self.add_subpath(new_points) self.growing_path = self.add_subpath(new_points)
self.growing_path = self.subpath_mobjects[-1]
else: else:
self.growing_path.start_at(new_points[0]) self.growing_path.start_at(new_points[0])
return return

View file

@ -1,3 +1,4 @@
from vectorized_mobject import VMobject
from svg_mobject import SVGMobject from svg_mobject import SVGMobject
from helpers import * from helpers import *
@ -5,16 +6,44 @@ class TexMobject(SVGMobject):
CONFIG = { CONFIG = {
"template_tex_file" : TEMPLATE_TEX_FILE, "template_tex_file" : TEMPLATE_TEX_FILE,
"color" : WHITE, "color" : WHITE,
"stroke_width" : 1, "stroke_width" : 0,
"should_center" : True, "should_center" : True,
"next_to_direction" : RIGHT,
"next_to_buff" : 0.2,
} }
def __init__(self, expression, **kwargs): def __init__(self, expression, **kwargs):
digest_config(self, kwargs, locals()) digest_config(self, kwargs, locals())
image_file = tex_to_image_file( VMobject.__init__(self, **kwargs)
self.expression, if self.should_center:
self.center()
def generate_points(self):
if isinstance(self.expression, list):
self.handle_list_expression()
else:
self.svg_file = tex_to_svg_file(
"".join(self.expression),
self.template_tex_file self.template_tex_file
) )
SVGMobject.__init__(self, image_file, **kwargs) SVGMobject.generate_points(self)
self.init_colors()
def handle_list_expression(self):
#TODO, next_to not sufficient?
subs = [
TexMobject(expr)
for expr in self.expression
]
for sm1, sm2 in zip(subs, subs[1:]):
sm2.next_to(
sm1,
self.next_to_direction,
self.next_to_buff
)
self.submobjects = subs
return self
class TextMobject(TexMobject): class TextMobject(TexMobject):
@ -42,13 +71,13 @@ class Brace(TexMobject):
def tex_hash(expression): def tex_hash(expression):
return str(hash(expression)) return str(hash(expression))
def tex_to_image_file(expression, template_tex_file): def tex_to_svg_file(expression, template_tex_file):
image_dir = os.path.join(TEX_IMAGE_DIR, tex_hash(expression)) image_dir = os.path.join(TEX_IMAGE_DIR, tex_hash(expression))
if os.path.exists(image_dir): if os.path.exists(image_dir):
return get_sorted_image_list(image_dir) return get_sorted_image_list(image_dir)
tex_file = generate_tex_file(expression, template_tex_file) tex_file = generate_tex_file(expression, template_tex_file)
pdf_file = tex_to_pdf(tex_file) dvi_file = tex_to_dvi(tex_file)
return pdf_to_svg(pdf_file) return dvi_to_svg(dvi_file)
def generate_tex_file(expression, template_tex_file): def generate_tex_file(expression, template_tex_file):
@ -64,11 +93,11 @@ def generate_tex_file(expression, template_tex_file):
outfile.write(body) outfile.write(body)
return result return result
def tex_to_pdf(tex_file): def tex_to_dvi(tex_file):
result = tex_file.replace(".tex", ".pdf") result = tex_file.replace(".tex", ".dvi")
if not os.path.exists(result) or True: if not os.path.exists(result):
commands = [ commands = [
"pdflatex", "latex",
"-interaction=batchmode", "-interaction=batchmode",
"-output-directory=" + TEX_DIR, "-output-directory=" + TEX_DIR,
tex_file, tex_file,
@ -78,26 +107,30 @@ def tex_to_pdf(tex_file):
os.system(" ".join(commands)) os.system(" ".join(commands))
return result return result
def pdf_to_svg(pdf_file, regen_if_exists = False): def dvi_to_svg(dvi_file, regen_if_exists = False):
""" """
Converts a dvi, which potentially has multiple slides, into a Converts a dvi, which potentially has multiple slides, into a
directory full of enumerated pngs corresponding with these slides. directory full of enumerated pngs corresponding with these slides.
Returns a list of PIL Image objects for these images sorted as they Returns a list of PIL Image objects for these images sorted as they
where in the dvi where in the dvi
""" """
result = pdf_file.replace(".pdf", ".svg") result = dvi_file.replace(".dvi", ".svg")
if not os.path.exists(result) or True: if not os.path.exists(result):
commands = [ commands = [
"pdf2svg", "dvisvgm",
pdf_file, dvi_file,
"-n",
"-v",
"0",
"-o",
result, result,
# "> /dev/null" "> /dev/null"
] ]
os.system(" ".join(commands)) os.system(" ".join(commands))
return result return result
# directory, filename = os.path.split(pdf_file) # directory, filename = os.path.split(dvi_file)
# name = filename.replace(".dvi", "") # name = filename.replace(".dvi", "")
# images_dir = os.path.join(TEX_IMAGE_DIR, name) # images_dir = os.path.join(TEX_IMAGE_DIR, name)
# if not os.path.exists(images_dir): # if not os.path.exists(images_dir):
@ -107,7 +140,7 @@ def pdf_to_svg(pdf_file, regen_if_exists = False):
# "convert", # "convert",
# "-density", # "-density",
# str(PDF_DENSITY), # str(PDF_DENSITY),
# pdf_file, # dvi_file,
# "-size", # "-size",
# str(DEFAULT_WIDTH) + "x" + str(DEFAULT_HEIGHT), # str(DEFAULT_WIDTH) + "x" + str(DEFAULT_HEIGHT),
# os.path.join(images_dir, name + ".png") # os.path.join(images_dir, name + ".png")

View file

@ -15,7 +15,6 @@ class VMobject(Mobject):
"mark_paths_closed" : False, "mark_paths_closed" : False,
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.subpath_mobjects = []
Mobject.__init__(self, *args, **kwargs) Mobject.__init__(self, *args, **kwargs)
## Colors ## Colors
@ -53,7 +52,10 @@ class VMobject(Mobject):
return self.fill_opacity return self.fill_opacity
def get_stroke_color(self): def get_stroke_color(self):
try:
return Color(rgb = self.stroke_rgb) return Color(rgb = self.stroke_rgb)
except:
return Color(rgb = 0.99*self.stroke_rgb)
#TODO, get color? Specify if stroke or fill #TODO, get color? Specify if stroke or fill
#is the predominant color attribute? #is the predominant color attribute?
@ -148,9 +150,14 @@ class VMobject(Mobject):
is_subpath = True is_subpath = True
) )
subpath_mobject.set_points(points) subpath_mobject.set_points(points)
self.subpath_mobjects.append(subpath_mobject)
self.add(subpath_mobject) self.add(subpath_mobject)
return self return subpath_mobject
def get_subpath_mobjects(self):
return filter(
lambda m : m.is_subpath,
self.submobjects
)
## Information about line ## Information about line
@ -181,28 +188,40 @@ class VMobject(Mobject):
def align_points_with_larger(self, larger_mobject): def align_points_with_larger(self, larger_mobject):
assert(isinstance(larger_mobject, VMobject)) assert(isinstance(larger_mobject, VMobject))
num_anchors = self.get_num_anchor_points() self.insert_n_anchor_points(
if num_anchors <= 1: larger_mobject.get_num_anchor_points()-\
point = self.points[0] if len(self.points) else np.zeros(3) self.get_num_anchor_points()
self.points = np.zeros(larger_mobject.points.shape) )
self.points[:,:] = point is_subpath = self.is_subpath or larger_mobject.is_subpath
self.is_subpath = larger_mobject.is_subpath = is_subpath
mark_closed = self.mark_paths_closed and larger_mobject.mark_paths_closed
self.mark_paths_closed = larger_mobject.mark_paths_closed = mark_closed
return self
def insert_n_anchor_points(self, n):
curr = self.get_num_anchor_points()
if curr == 0:
self.points = np.zeros((1, 3))
n = n-1
if curr == 1:
self.points = np.repeat(self.points, n+1)
return self return self
points = np.array([self.points[0]]) points = np.array([self.points[0]])
target_len = larger_mobject.get_num_anchor_points()-1 num_curves = curr-1
num_curves = self.get_num_anchor_points()-1
#Curves in self are buckets, and we need to know #Curves in self are buckets, and we need to know
#how many new anchor points to put into each one. #how many new anchor points to put into each one.
#Each element of index_allocation is like a bucket, #Each element of index_allocation is like a bucket,
#and its value tells you the appropriate index of #and its value tells you the appropriate index of
#the smaller curve. #the smaller curve.
index_allocation = (np.arange(target_len) * num_curves)/target_len index_allocation = (np.arange(curr+n-1) * num_curves)/(curr+n-1)
for index in range(num_curves): for index in range(num_curves):
curr_bezier_points = self.points[3*index:3*index+4] curr_bezier_points = self.points[3*index:3*index+4]
num_inter_curves = sum(index_allocation == index) num_inter_curves = sum(index_allocation == index)
step = 1./num_inter_curves alphas = np.arange(0, num_inter_curves+1)/float(num_inter_curves)
alphas = np.arange(0, 1+step, step)
for a, b in zip(alphas, alphas[1:]): for a, b in zip(alphas, alphas[1:]):
new_points = partial_bezier_points(curr_bezier_points, a, b) new_points = partial_bezier_points(
curr_bezier_points, a, b
)
points = np.append( points = np.append(
points, new_points[1:], axis = 0 points, new_points[1:], axis = 0
) )

View file

@ -9,6 +9,7 @@
\usepackage{tipa} \usepackage{tipa}
\usepackage{relsize} \usepackage{relsize}
\mode<presentation> \mode<presentation>
{ {
\usetheme{default} % or try Darmstadt, Madrid, Warsaw, ... \usetheme{default} % or try Darmstadt, Madrid, Warsaw, ...
@ -17,6 +18,7 @@
\setbeamertemplate{navigation symbols}{} \setbeamertemplate{navigation symbols}{}
\setbeamertemplate{caption}[numbered] \setbeamertemplate{caption}[numbered]
} }
\begin{document} \begin{document}
\centering \centering