mirror of
https://github.com/3b1b/manim.git
synced 2025-07-31 22:13:30 +08:00
tex mobjects implemented
This commit is contained in:
20
Tex/template.tex
Normal file
20
Tex/template.tex
Normal file
@ -0,0 +1,20 @@
|
||||
\documentclass[12pt]{beamer}
|
||||
|
||||
\usepackage[english]{babel}
|
||||
\usepackage{mathtools}
|
||||
|
||||
\mode<presentation>
|
||||
{
|
||||
\usetheme{default} % or try Darmstadt, Madrid, Warsaw, ...
|
||||
\usecolortheme{default} % or try albatross, beaver, crane, ...
|
||||
\usefonttheme[onlymath]{serif} % or try serif, structurebold, ...
|
||||
\setbeamertemplate{navigation symbols}{}
|
||||
\setbeamertemplate{caption}[numbered]
|
||||
}
|
||||
\begin{document}
|
||||
\begin{frame}
|
||||
\centering
|
||||
SizeHere
|
||||
$YourTextHere$
|
||||
\end{frame}
|
||||
\end{document}
|
16
animate.py
16
animate.py
@ -16,19 +16,19 @@ import displayer as disp
|
||||
|
||||
class Animation(object):
|
||||
def __init__(self,
|
||||
mobject,
|
||||
mobject_or_animation,
|
||||
run_time = DEFAULT_ANIMATION_RUN_TIME,
|
||||
alpha_func = high_inflection_0_to_1,
|
||||
name = None):
|
||||
if isinstance(mobject, type) and issubclass(mobject, Mobject):
|
||||
self.mobject = mobject()
|
||||
self.starting_mobject = mobject()
|
||||
elif isinstance(mobject, Mobject):
|
||||
self.mobject = mobject
|
||||
self.starting_mobject = copy.deepcopy(mobject)
|
||||
self.embedded_animation = None
|
||||
if isinstance(mobject_or_animation, type) and issubclass(mobject_or_animation, Mobject):
|
||||
self.mobject = mobject_or_animation()
|
||||
elif isinstance(mobject_or_animation, Mobject):
|
||||
self.mobject = mobject_or_animation
|
||||
else:
|
||||
raise Exception("Invalid mobject parameter, must be \
|
||||
raise Exception("Invalid mobject_or_animation parameter, must be \
|
||||
subclass or instance of Mobject")
|
||||
self.starting_mobject = copy.deepcopy(self.mobject)
|
||||
self.reference_mobjects = [self.starting_mobject]
|
||||
self.alpha_func = alpha_func or (lambda x : x)
|
||||
self.run_time = run_time
|
||||
|
36
constants.py
36
constants.py
@ -1,12 +1,12 @@
|
||||
import os
|
||||
|
||||
PRODUCTION_QUALITY = True
|
||||
PRODUCTION_QUALITY = False
|
||||
|
||||
DEFAULT_POINT_DENSITY_2D = 25 if PRODUCTION_QUALITY else 20
|
||||
DEFAULT_POINT_DENSITY_1D = 200 if PRODUCTION_QUALITY else 50
|
||||
DEFAULT_POINT_DENSITY_2D = 25 #if PRODUCTION_QUALITY else 20
|
||||
DEFAULT_POINT_DENSITY_1D = 200 #if PRODUCTION_QUALITY else 50
|
||||
|
||||
HEIGHT = 1024#1440 if PRODUCTION_QUALITY else 480
|
||||
WIDTH = 1024#2560 if PRODUCTION_QUALITY else 640
|
||||
DEFAULT_HEIGHT = 1440 #if PRODUCTION_QUALITY else 480
|
||||
DEFAULT_WIDTH = 2560 #if PRODUCTION_QUALITY else 640
|
||||
#All in seconds
|
||||
DEFAULT_FRAME_DURATION = 0.04 if PRODUCTION_QUALITY else 0.1
|
||||
DEFAULT_ANIMATION_RUN_TIME = 3.0
|
||||
@ -15,22 +15,28 @@ DEFAULT_DITHER_TIME = 1.0
|
||||
|
||||
GENERALLY_BUFF_POINTS = PRODUCTION_QUALITY
|
||||
|
||||
BACKGROUND_COLOR = "black" #TODO, this is never actually enforced anywhere.
|
||||
|
||||
DEFAULT_NUM_STARS = 1000
|
||||
|
||||
SPACE_HEIGHT = 4.0
|
||||
SPACE_WIDTH = WIDTH * SPACE_HEIGHT / HEIGHT
|
||||
SPACE_WIDTH = DEFAULT_WIDTH * SPACE_HEIGHT / DEFAULT_HEIGHT
|
||||
|
||||
PDF_DENSITY = 400
|
||||
|
||||
IMAGE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "images")
|
||||
GIF_DIR = os.path.join(os.getenv("HOME"), "Desktop", "math_gifs")
|
||||
MOVIE_DIR = os.path.join(os.getenv("HOME"), "Desktop", "math_movies")
|
||||
PDF_DIR = os.path.join(os.getenv("HOME"), "Documents", "Tex", "Animations")
|
||||
|
||||
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
IMAGE_DIR = os.path.join(THIS_DIR, "images")
|
||||
GIF_DIR = os.path.join(THIS_DIR, "gifs")
|
||||
MOVIE_DIR = os.path.join(THIS_DIR, "movies")
|
||||
TEX_DIR = os.path.join(THIS_DIR, "Tex")
|
||||
TEX_IMAGE_DIR = os.path.join(IMAGE_DIR, "Tex")
|
||||
TMP_IMAGE_DIR = "/tmp/animation_images/"
|
||||
for folder in [IMAGE_DIR, GIF_DIR, MOVIE_DIR, TMP_IMAGE_DIR]:
|
||||
for folder in [IMAGE_DIR, GIF_DIR, MOVIE_DIR, TEX_DIR, TMP_IMAGE_DIR, TEX_IMAGE_DIR]:
|
||||
if not os.path.exists(folder):
|
||||
os.mkdir(folder)
|
||||
|
||||
LOGO_PATH = os.path.join(IMAGE_DIR, "logo.png")
|
||||
PDF_DENSITY = 400
|
||||
SIZE_TO_REPLACE = "SizeHere"
|
||||
TEX_TEXT_TO_REPLACE = "YourTextHere"
|
||||
TEMPLATE_TEX_FILE = os.path.join(TEX_DIR, "template.tex")
|
||||
|
||||
LOGO_PATH = os.path.join(IMAGE_DIR, "logo.png")
|
||||
|
||||
|
89
displayer.py
89
displayer.py
@ -2,39 +2,47 @@ import numpy as np
|
||||
import itertools as it
|
||||
import os
|
||||
from PIL import Image
|
||||
import subprocess
|
||||
import cv2
|
||||
|
||||
from animate import *
|
||||
|
||||
def paint_mobject(mobject, image_array = None):
|
||||
if image_array is None:
|
||||
pixels = np.zeros((DEFAULT_HEIGHT, DEFAULT_WIDTH, 3))
|
||||
else:
|
||||
pixels = np.array(image_array)
|
||||
assert len(pixels.shape) == 3 and pixels.shape[2] == 3
|
||||
height = pixels.shape[0]
|
||||
width = pixels.shape[1]
|
||||
space_height = SPACE_HEIGHT
|
||||
space_width = SPACE_HEIGHT * width / height
|
||||
|
||||
def get_image(points, rgbs):
|
||||
return Image.fromarray(get_pixels(points, rgbs))
|
||||
|
||||
def get_pixels(points, rgbs):
|
||||
#TODO, Let z add a depth componenet?
|
||||
points = points[:, :2]
|
||||
points = np.array(mobject.points[:, :2])
|
||||
#Flips y-axis
|
||||
points[:,1] *= -1
|
||||
#Map points to pixel space, then create pixel array first in terms
|
||||
#of its flattened version
|
||||
points += np.array(
|
||||
[SPACE_WIDTH, SPACE_HEIGHT]*points.shape[0]
|
||||
[space_width, space_height]*points.shape[0]
|
||||
).reshape(points.shape)
|
||||
points *= np.array(
|
||||
[HEIGHT / (2.0 * SPACE_HEIGHT), WIDTH / (2.0 * SPACE_WIDTH)]*\
|
||||
[width / (2.0 * space_width), height / (2.0 * space_height)]*\
|
||||
points.shape[0]
|
||||
).reshape(points.shape)
|
||||
points = points.astype('int')
|
||||
flattener = np.array([1, WIDTH], dtype = 'int').reshape((2, 1))
|
||||
flattener = np.array([1, width], dtype = 'int').reshape((2, 1))
|
||||
indices = np.dot(points, flattener)
|
||||
indices = indices.reshape(indices.size)
|
||||
admissibles = (indices < HEIGHT * WIDTH) * (indices > 0)
|
||||
admissibles = (indices < height * width) * (indices > 0)
|
||||
indices = indices[admissibles]
|
||||
rgbs = rgbs[admissibles]
|
||||
rgbs = mobject.rgbs[admissibles]
|
||||
rgbs = (rgbs * 255).astype(int)
|
||||
pixels = np.zeros((HEIGHT * WIDTH, 3))
|
||||
pixels = pixels.reshape((height * width, 3))
|
||||
pixels[indices] = rgbs
|
||||
return pixels.reshape((HEIGHT, WIDTH, 3)).astype('uint8')
|
||||
pixels = pixels.reshape((height, width, 3)).astype('uint8')
|
||||
return pixels
|
||||
|
||||
def write_to_gif(scene, name):
|
||||
#TODO, find better means of compression
|
||||
@ -43,29 +51,31 @@ def write_to_gif(scene, name):
|
||||
filepath = os.path.join(GIF_DIR, name)
|
||||
temppath = os.path.join(GIF_DIR, "Temp.gif")
|
||||
print "Writing " + name + "..."
|
||||
writeGif(temppath, scene.frames, scene.frame_duration)
|
||||
images = [Image.fromarray(frame) for frame in scene.frames]
|
||||
writeGif(temppath, images, scene.frame_duration)
|
||||
print "Compressing..."
|
||||
os.system("gifsicle -O " + temppath + " > " + filepath)
|
||||
os.system("rm " + temppath)
|
||||
|
||||
def write_to_movie(scene, name):
|
||||
#TODO, incorporate pause time
|
||||
frames = scene.frames
|
||||
progress_bar = progressbar.ProgressBar(maxval=len(frames))
|
||||
progress_bar.start()
|
||||
print "writing " + name + "..."
|
||||
|
||||
filepath = os.path.join(MOVIE_DIR, name)
|
||||
filedir = "/".join(filepath.split("/")[:-1])
|
||||
if not os.path.exists(filedir):
|
||||
os.makedirs(filedir)
|
||||
rate = int(1/scene.frame_duration)
|
||||
|
||||
tmp_stem = os.path.join(TMP_IMAGE_DIR, name.replace("/", "_"))
|
||||
suffix = "-%04d.png"
|
||||
image_files = []
|
||||
for frame, count in zip(frames, it.count()):
|
||||
progress_bar.update(int(0.9 * count))
|
||||
frame.save(tmp_stem + suffix%count)
|
||||
Image.fromarray(frame).save(tmp_stem + suffix%count)
|
||||
image_files.append(tmp_stem + suffix%count)
|
||||
filepath = os.path.join(MOVIE_DIR, name + ".mp4")
|
||||
filedir = "/".join(filepath.split("/")[:-1])
|
||||
if not os.path.exists(filedir):
|
||||
os.makedirs(filedir)
|
||||
commands = [
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
@ -77,7 +87,7 @@ def write_to_movie(scene, name):
|
||||
"libx264",
|
||||
"-vf",
|
||||
"fps=%d,format=yuv420p"%int(1/scene.frame_duration),
|
||||
filepath
|
||||
filepath + ".mp4"
|
||||
]
|
||||
os.system(" ".join(commands))
|
||||
for image_file in image_files:
|
||||
@ -85,10 +95,17 @@ def write_to_movie(scene, name):
|
||||
progress_bar.finish()
|
||||
|
||||
|
||||
# vs = VideoSink(scene.shape, filepath, rate)
|
||||
# for frame in frames:
|
||||
# vs.run(frame)
|
||||
# vs.close()
|
||||
# progress_bar.finish()
|
||||
|
||||
|
||||
# filepath = os.path.join(MOVIE_DIR, name + ".mov")
|
||||
# fourcc = cv2.cv.FOURCC(*"8bps")
|
||||
# out = cv2.VideoWriter(
|
||||
# filepath, fourcc, 1.0/animation.frame_duration, (WIDTH, HEIGHT), True
|
||||
# filepath, fourcc, 1.0/scene.frame_duration, (DEFAULT_WIDTH, DEFAULT_HEIGHT), True
|
||||
# )
|
||||
# progress = 0
|
||||
# for frame in frames:
|
||||
@ -103,7 +120,35 @@ def write_to_movie(scene, name):
|
||||
# progress_bar.finish()
|
||||
|
||||
|
||||
|
||||
class VideoSink(object):
|
||||
def __init__(self, size, filename="output", rate=10, byteorder="bgra") :
|
||||
self.size = size
|
||||
cmdstring = [
|
||||
'mencoder',
|
||||
'/dev/stdin',
|
||||
'-demuxer', 'rawvideo',
|
||||
'-rawvideo', 'w=%i:h=%i'%size[::-1]+":fps=%i:format=%s"%(rate,byteorder),
|
||||
'-o', filename+'.mp4',
|
||||
'-ovc', 'lavc',
|
||||
]
|
||||
self.p = subprocess.Popen(cmdstring, stdin=subprocess.PIPE, shell=False)
|
||||
|
||||
def run(self, image):
|
||||
"""
|
||||
Image comes in as HEIGHTxWIDTHx3 numpy array, order rgb
|
||||
"""
|
||||
assert image.shape == self.size + (3,)
|
||||
r, g, b = [image[:,:,i].astype('uint32') for i in range(3)]
|
||||
a = np.ones(image.shape[:2], dtype = 'uint32')
|
||||
#hacky
|
||||
image = sum([
|
||||
arr << 8**i
|
||||
for arr, i in zip(range(4), [a, r, g, b])
|
||||
])
|
||||
self.p.stdin.write(image.tostring())
|
||||
|
||||
def close(self):
|
||||
self.p.stdin.close()
|
||||
|
||||
|
||||
|
||||
|
47
mobject.py
47
mobject.py
@ -5,9 +5,10 @@ from PIL import Image
|
||||
from random import random
|
||||
|
||||
from animate import *
|
||||
from tex_image_utils import NAME_TO_IMAGE_FILE
|
||||
from tex_utils import *
|
||||
import displayer as disp
|
||||
|
||||
|
||||
class Mobject(object):
|
||||
"""
|
||||
Mathematical Object
|
||||
@ -40,10 +41,10 @@ class Mobject(object):
|
||||
return self.name
|
||||
|
||||
def display(self):
|
||||
disp.get_image(self.points, self.rgbs).show()
|
||||
Image.fromarray(disp.paint_mobject(self)).show()
|
||||
|
||||
def save_image(self, name = None):
|
||||
disp.get_image(self.points, self.rgbs).save(
|
||||
Image.fromarray(disp.paint_mobject(self)).save(
|
||||
os.path.join(MOVIE_DIR, (name or str(self)) + ".png")
|
||||
)
|
||||
|
||||
@ -286,7 +287,7 @@ class Line(Mobject1D):
|
||||
])
|
||||
|
||||
|
||||
class Cube(Mobject2D):
|
||||
class CubeWithFaces(Mobject2D):
|
||||
def generate_points(self):
|
||||
self.add_points([
|
||||
sgn * np.array(coords)
|
||||
@ -300,7 +301,7 @@ class Cube(Mobject2D):
|
||||
def unit_normal(self, coords):
|
||||
return np.array(map(lambda x : 1 if abs(x) == 1 else 0, coords))
|
||||
|
||||
class CubeShell(Mobject1D):
|
||||
class Cube(Mobject1D):
|
||||
DEFAULT_COLOR = "yellow"
|
||||
def generate_points(self):
|
||||
self.add_points([
|
||||
@ -449,24 +450,15 @@ class NumberLine(Mobject1D):
|
||||
for x in np.arange(-self.radius, self.radius, self.interval_size)
|
||||
for y in np.arange(-self.tick_size, self.tick_size, self.epsilon)
|
||||
])
|
||||
if self.with_numbers: #TODO, make these numbers a separate object
|
||||
if self.with_numbers:
|
||||
#TODO, test
|
||||
vertical_displacement = -0.3
|
||||
max_explicit_num = 3
|
||||
num_to_name = dict(
|
||||
(x, str(x))
|
||||
for x in range(-max_explicit_num, max_explicit_num + 1)
|
||||
)
|
||||
num_to_name[max_explicit_num + 1] = "cdots"
|
||||
num_to_name[-max_explicit_num - 1] = "cdots"
|
||||
nums = CompoundMobject(*[
|
||||
ImageMobject(
|
||||
NAME_TO_IMAGE_FILE[num_to_name[x]]
|
||||
).scale(0.6).center().shift(
|
||||
[x * self.interval_size, vertical_displacement, 0]
|
||||
)
|
||||
for x in range(-max_explicit_num - 1, max_explicit_num + 2)
|
||||
])
|
||||
self.add_points(nums.points, nums.rgbs)
|
||||
nums = range(-self.radius, self.radius)
|
||||
nums = map(lambda x : x / self.interval_size, nums)
|
||||
mobs = tex_mobjects(*[str(num) for num in nums])
|
||||
for num, mob in zip(nums, mobs):
|
||||
mob.center().shift([num, vertical_displacement, 0])
|
||||
self.add(*mobs)
|
||||
|
||||
# class ComplexPlane(Grid):
|
||||
# def __init__(self, *args, **kwargs):
|
||||
@ -529,15 +521,20 @@ class ImageMobject(Mobject2D):
|
||||
for i in indices
|
||||
], dtype = 'float64')
|
||||
height, width = map(float, (height, width))
|
||||
if height / width > 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 tex_mobjects(expression, size = "\HUGE"):
|
||||
images = tex_to_image(expression, size)
|
||||
if isinstance(images, list):
|
||||
#TODO, is checking listiness really the best here?
|
||||
return [ImageMobject(im) for im in images]
|
||||
else:
|
||||
return ImageMobject(images)
|
||||
|
||||
|
||||
|
||||
|
66
scene.py
66
scene.py
@ -1,6 +1,7 @@
|
||||
from PIL import Image
|
||||
from colour import Color
|
||||
import numpy as np
|
||||
import itertools as it
|
||||
import warnings
|
||||
import time
|
||||
import os
|
||||
@ -16,29 +17,47 @@ import displayer as disp
|
||||
|
||||
class Scene(object):
|
||||
def __init__(self,
|
||||
name = None,
|
||||
frame_duration = DEFAULT_FRAME_DURATION,
|
||||
name = None):
|
||||
background = None,
|
||||
height = DEFAULT_HEIGHT,
|
||||
width = DEFAULT_WIDTH,):
|
||||
self.frame_duration = frame_duration
|
||||
self.frames = []
|
||||
self.mobjects = set([])
|
||||
self.mobjects = []
|
||||
if background:
|
||||
self.background = np.array(background)
|
||||
#TODO, Error checking?
|
||||
else:
|
||||
self.background = np.zeros((height, width, 3))
|
||||
self.shape = self.background.shape[:2]
|
||||
self.name = name
|
||||
|
||||
def __str__(self):
|
||||
return self.name or "Babadinook" #TODO
|
||||
|
||||
def add(self, *mobjects):
|
||||
#TODO, perhaps mobjects should be ordered, for foreground/background
|
||||
self.mobjects.update(mobjects)
|
||||
"""
|
||||
Mobjects will be displayed, from background to foreground,
|
||||
in the order with which they are entered.
|
||||
"""
|
||||
for mobject in mobjects:
|
||||
#In case it's already in there, it should
|
||||
#now be closer to the foreground.
|
||||
self.remove(mobject)
|
||||
self.mobjects.append(mobject)
|
||||
|
||||
def remove(self, *mobjects):
|
||||
self.mobjects.difference_update(mobjects)
|
||||
for mobject in mobjects:
|
||||
while mobject in self.mobjects:
|
||||
self.mobjects.remove(mobject)
|
||||
|
||||
def animate(self, animations,
|
||||
dither_time = DEFAULT_DITHER_TIME):
|
||||
if isinstance(animations, Animation):
|
||||
animations = [animations]
|
||||
self.pause(dither_time)
|
||||
run_time = max([anim.run_time for anim in animations])
|
||||
def animate(self, *animations):
|
||||
#Runtime is determined by the first animation
|
||||
run_time = animations[0].run_time
|
||||
moving_mobjects = [a.mobject for a in animations]
|
||||
self.remove(*moving_mobjects)
|
||||
background = self.get_frame()
|
||||
|
||||
print "Generating animations..."
|
||||
progress_bar = progressbar.ProgressBar(maxval=run_time)
|
||||
@ -46,28 +65,31 @@ class Scene(object):
|
||||
|
||||
for t in np.arange(0, run_time, self.frame_duration):
|
||||
progress_bar.update(t)
|
||||
new_frame = background
|
||||
for anim in animations:
|
||||
anim.update(t)
|
||||
self.frames.append(self.get_frame(*animations))
|
||||
anim.update(t / anim.run_time)
|
||||
new_frame = disp.paint_mobject(anim.mobject, new_frame)
|
||||
self.frames.append(new_frame)
|
||||
for anim in animations:
|
||||
anim.clean_up()
|
||||
self.add(*moving_mobjects)
|
||||
progress_bar.finish()
|
||||
|
||||
def pause(self, duration):
|
||||
def get_frame(self):
|
||||
frame = self.background
|
||||
for mob in self.mobjects:
|
||||
frame = disp.paint_mobject(mob, frame)
|
||||
return frame
|
||||
|
||||
def dither(self, duration = DEFAULT_DITHER_TIME):
|
||||
self.frames += [self.get_frame()]*int(duration / self.frame_duration)
|
||||
|
||||
def get_frame(self, *animations):
|
||||
#Include animations so as to display mobjects not in the list
|
||||
#TODO, This is temporary
|
||||
mob = list(self.mobjects)[0]
|
||||
return disp.get_image(mob.points, mob.rgbs)
|
||||
|
||||
def write_to_gif(self, name = None, end_dither_time = DEFAULT_DITHER_TIME):
|
||||
self.pause(end_dither_time)
|
||||
self.dither(end_dither_time)
|
||||
disp.write_to_gif(self, name or str(self))
|
||||
|
||||
def write_to_movie(self, name = None, end_dither_time = DEFAULT_DITHER_TIME):
|
||||
self.pause(end_dither_time)
|
||||
self.dither(end_dither_time)
|
||||
disp.write_to_movie(self, name or str(self))
|
||||
|
||||
|
||||
|
109
tex_utils.py
Normal file
109
tex_utils.py
Normal file
@ -0,0 +1,109 @@
|
||||
import os
|
||||
import itertools as it
|
||||
from PIL import Image
|
||||
from constants import *
|
||||
|
||||
def tex_to_image(expression, size = "\HUGE"):
|
||||
"""
|
||||
Returns list of images for correpsonding with a list of expressions
|
||||
"""
|
||||
return_list = False
|
||||
arg = expression
|
||||
if not isinstance(expression, str):
|
||||
#TODO, verify that expression is iterable of strings
|
||||
expression = "\n".join([
|
||||
"\onslide<%d>{"%count + exp + "}"
|
||||
for count, exp in zip(it.count(1), expression)
|
||||
])
|
||||
return_list = True
|
||||
filestem = os.path.join(
|
||||
TEX_DIR, str(hash(expression + size))
|
||||
)
|
||||
if not os.path.exists(filestem + ".dvi"):
|
||||
if not os.path.exists(filestem + ".tex"):
|
||||
print " ".join([
|
||||
"Writing ",
|
||||
"".join(arg),
|
||||
"at size %s to "%(size),
|
||||
filestem,
|
||||
])
|
||||
with open(TEMPLATE_TEX_FILE, "r") as infile:
|
||||
body = infile.read()
|
||||
body = body.replace(SIZE_TO_REPLACE, size)
|
||||
body = body.replace(TEX_TEXT_TO_REPLACE, expression)
|
||||
with open(filestem + ".tex", "w") as outfile:
|
||||
outfile.write(body)
|
||||
commands = [
|
||||
"latex",
|
||||
"-interaction=batchmode",
|
||||
"-output-directory=" + TEX_DIR,
|
||||
filestem + ".tex"
|
||||
]
|
||||
#TODO, Error check
|
||||
os.system(" ".join(commands))
|
||||
assert os.path.exists(filestem + ".dvi")
|
||||
result = dvi_to_png(filestem + ".dvi")
|
||||
return result if return_list else result[0]
|
||||
|
||||
|
||||
|
||||
def dvi_to_png(filename, regen_if_exists = False):
|
||||
"""
|
||||
Converts a dvi, which potentially has multiple slides, into a
|
||||
directory full of enumerated pngs corresponding with these slides.
|
||||
Returns a list of PIL Image objects for these images sorted as they
|
||||
where in the dvi
|
||||
"""
|
||||
possible_paths = [
|
||||
filename,
|
||||
os.path.join(TEX_DIR, filename),
|
||||
os.path.join(TEX_DIR, filename + ".dvi"),
|
||||
]
|
||||
for path in possible_paths:
|
||||
if os.path.exists(path):
|
||||
directory, filename = os.path.split(path)
|
||||
name = filename.split(".")[0]
|
||||
images_dir = os.path.join(TEX_IMAGE_DIR, name)
|
||||
if not os.path.exists(images_dir):
|
||||
os.mkdir(images_dir)
|
||||
if os.listdir(images_dir) == [] or regen_if_exists:
|
||||
commands = [
|
||||
"convert",
|
||||
"-density",
|
||||
str(PDF_DENSITY),
|
||||
path,
|
||||
"-size",
|
||||
str(DEFAULT_WIDTH) + "x" + str(DEFAULT_HEIGHT),
|
||||
os.path.join(images_dir, name + ".png")
|
||||
]
|
||||
os.system(" ".join(commands))
|
||||
image_paths = [
|
||||
os.path.join(images_dir, name)
|
||||
for name in os.listdir(images_dir)
|
||||
if name.endswith(".png")
|
||||
]
|
||||
image_paths.sort(cmp_enumerated_files)
|
||||
return [Image.open(path).convert('RGB') for path in image_paths]
|
||||
raise IOError("File not Found")
|
||||
|
||||
def cmp_enumerated_files(name1, name2):
|
||||
num1, num2 = [
|
||||
int(name.split(".")[0].split("-")[-1])
|
||||
for name in (name1, name2)
|
||||
]
|
||||
return num1 - num2
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user