mirror of
https://github.com/3b1b/manim.git
synced 2025-07-28 20:43:56 +08:00
Reimplented svg rendering using cairo, and changed vmobject color model to allow for gradeints and strokes with opacities. Many errors associated with python 2 to python 3 conversion are likely still present at this point.
This commit is contained in:
163
camera/camera.py
163
camera/camera.py
@ -10,6 +10,7 @@ import time
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
from colour import Color
|
from colour import Color
|
||||||
from scipy.spatial.distance import pdist
|
from scipy.spatial.distance import pdist
|
||||||
|
import cairo
|
||||||
|
|
||||||
from constants import *
|
from constants import *
|
||||||
from mobject.types.image_mobject import AbstractImageMobject
|
from mobject.types.image_mobject import AbstractImageMobject
|
||||||
@ -50,6 +51,7 @@ class Camera(object):
|
|||||||
# z_buff_func is only used if the flag above is set to True.
|
# z_buff_func is only used if the flag above is set to True.
|
||||||
# round z coordinate to nearest hundredth when comparring
|
# round z coordinate to nearest hundredth when comparring
|
||||||
"z_buff_func": lambda m: np.round(m.get_center()[2], 2),
|
"z_buff_func": lambda m: np.round(m.get_center()[2], 2),
|
||||||
|
"cairo_line_width_multiple": 0.01,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, background=None, **kwargs):
|
def __init__(self, background=None, **kwargs):
|
||||||
@ -278,14 +280,25 @@ class Camera(object):
|
|||||||
|
|
||||||
# Methods associated with svg rendering
|
# Methods associated with svg rendering
|
||||||
|
|
||||||
def get_aggdraw_canvas(self):
|
def get_cairo_context(self):
|
||||||
if not hasattr(self, "canvas") or not self.canvas:
|
# TODO, make sure this isn't run too much
|
||||||
self.reset_aggdraw_canvas()
|
pw = self.get_pixel_width()
|
||||||
return self.canvas
|
ph = self.get_pixel_height()
|
||||||
|
fw = self.get_frame_width()
|
||||||
def reset_aggdraw_canvas(self):
|
fh = self.get_frame_height()
|
||||||
image = Image.fromarray(self.pixel_array, mode=self.image_mode)
|
surface = cairo.ImageSurface.create_for_data(
|
||||||
self.canvas = aggdraw.Draw(image)
|
self.pixel_array,
|
||||||
|
cairo.FORMAT_ARGB32,
|
||||||
|
pw, ph
|
||||||
|
)
|
||||||
|
ctx = cairo.Context(surface)
|
||||||
|
ctx.scale(pw, ph)
|
||||||
|
ctx.set_matrix(cairo.Matrix(
|
||||||
|
fdiv(pw, fw), 0,
|
||||||
|
0, -fdiv(ph, fh),
|
||||||
|
pw / 2, ph / 2,
|
||||||
|
))
|
||||||
|
return ctx
|
||||||
|
|
||||||
def display_multiple_vectorized_mobjects(self, vmobjects):
|
def display_multiple_vectorized_mobjects(self, vmobjects):
|
||||||
if len(vmobjects) == 0:
|
if len(vmobjects) == 0:
|
||||||
@ -301,90 +314,87 @@ class Camera(object):
|
|||||||
self.display_multiple_non_background_colored_vmobjects(batch)
|
self.display_multiple_non_background_colored_vmobjects(batch)
|
||||||
|
|
||||||
def display_multiple_non_background_colored_vmobjects(self, vmobjects):
|
def display_multiple_non_background_colored_vmobjects(self, vmobjects):
|
||||||
self.reset_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.flush()
|
|
||||||
|
|
||||||
def display_vectorized(self, vmobject, canvas=None):
|
def display_vectorized(self, vmobject):
|
||||||
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
|
||||||
canvas = canvas or self.get_aggdraw_canvas()
|
ctx = self.get_cairo_context()
|
||||||
pen, fill = self.get_pen_and_fill(vmobject)
|
self.set_cairo_context_path(ctx, vmobject)
|
||||||
pathstring = self.get_pathstring(vmobject)
|
self.apply_stroke(ctx, vmobject, background=True)
|
||||||
symbol = aggdraw.Symbol(pathstring)
|
self.apply_fill(ctx, vmobject)
|
||||||
self.draw_background_stroke(canvas, vmobject, symbol)
|
self.apply_stroke(ctx, vmobject)
|
||||||
canvas.symbol((0, 0), symbol, pen, fill)
|
ctx.new_path()
|
||||||
|
return self
|
||||||
|
|
||||||
def draw_background_stroke(self, canvas, vmobject, symbol):
|
def set_cairo_context_path(self, ctx, vmobject):
|
||||||
bs_width = vmobject.get_background_stroke_width()
|
for vmob in it.chain([vmobject], vmobject.get_subpath_mobjects()):
|
||||||
if bs_width == 0:
|
points = vmob.points
|
||||||
return
|
ctx.new_sub_path()
|
||||||
bs_rgb = vmobject.get_background_stroke_rgb()
|
ctx.move_to(*points[0][:2])
|
||||||
bs_hex = rgb_to_hex(bs_rgb)
|
for triplet in zip(points[1::3], points[2::3], points[3::3]):
|
||||||
pen = aggdraw.Pen(bs_hex, bs_width)
|
ctx.curve_to(*it.chain(*[
|
||||||
canvas.symbol((0, 0), symbol, pen, None)
|
point[:2] for point in triplet
|
||||||
|
]))
|
||||||
|
if vmob.is_closed():
|
||||||
|
ctx.close_path()
|
||||||
|
return self
|
||||||
|
|
||||||
def get_pen_and_fill(self, vmobject):
|
def set_cairo_context_color(self, ctx, rgbas, vmobject):
|
||||||
stroke_width = max(vmobject.get_stroke_width(), 0)
|
if len(rgbas) == 0:
|
||||||
if stroke_width == 0:
|
# Use reversed rgb because cairo surface is
|
||||||
pen = None
|
# encodes it in reverse order
|
||||||
|
ctx.set_source_rgba(
|
||||||
|
*rgbas[0][2::-1], rgbas[0][3]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
stroke_rgb = self.get_stroke_rgb(vmobject)
|
points = vmobject.get_gradient_start_and_end_points()
|
||||||
stroke_hex = rgb_to_hex(stroke_rgb)
|
pat = cairo.LinearGradient(*it.chain(*[
|
||||||
pen = aggdraw.Pen(stroke_hex, stroke_width)
|
point[:2] for point in points
|
||||||
|
]))
|
||||||
|
offsets = np.linspace(1, 0, len(rgbas))
|
||||||
|
for rgba, offset in zip(rgbas, offsets):
|
||||||
|
pat.add_color_stop_rgba(
|
||||||
|
offset, *rgba[2::-1], rgba[3]
|
||||||
|
)
|
||||||
|
ctx.set_source(pat)
|
||||||
|
return self
|
||||||
|
|
||||||
fill_opacity = int(self.rgb_max_val * vmobject.get_fill_opacity())
|
def apply_fill(self, ctx, vmobject):
|
||||||
if fill_opacity == 0:
|
self.set_cairo_context_color(
|
||||||
fill = None
|
ctx, self.get_fill_rgbas(vmobject), vmobject
|
||||||
else:
|
)
|
||||||
fill_rgb = self.get_fill_rgb(vmobject)
|
ctx.fill_preserve()
|
||||||
fill_hex = rgb_to_hex(fill_rgb)
|
return self
|
||||||
fill = aggdraw.Brush(fill_hex, fill_opacity)
|
|
||||||
|
|
||||||
return (pen, fill)
|
def apply_stroke(self, ctx, vmobject, background=False):
|
||||||
|
width = vmobject.get_stroke_width(background)
|
||||||
|
self.set_cairo_context_color(
|
||||||
|
ctx,
|
||||||
|
self.get_stroke_rgbas(vmobject, background=background),
|
||||||
|
vmobject
|
||||||
|
)
|
||||||
|
ctx.set_line_width(
|
||||||
|
width * self.cairo_line_width_multiple
|
||||||
|
)
|
||||||
|
ctx.stroke_preserve()
|
||||||
|
return self
|
||||||
|
|
||||||
def color_to_hex_l(self, color):
|
def get_stroke_rgbas(self, vmobject, background=False):
|
||||||
try:
|
return vmobject.get_stroke_rgbas(background)
|
||||||
return color.get_hex_l()
|
|
||||||
except:
|
|
||||||
return Color(BLACK).get_hex_l()
|
|
||||||
|
|
||||||
def get_stroke_rgb(self, vmobject):
|
def get_fill_rgbas(self, vmobject):
|
||||||
return vmobject.get_stroke_rgb()
|
return vmobject.get_fill_rgbas()
|
||||||
|
|
||||||
def get_fill_rgb(self, vmobject):
|
|
||||||
return vmobject.get_fill_rgb()
|
|
||||||
|
|
||||||
def get_pathstring(self, vmobject):
|
|
||||||
result = ""
|
|
||||||
for mob in [vmobject] + vmobject.get_subpath_mobjects():
|
|
||||||
points = mob.points
|
|
||||||
# points = self.adjust_out_of_range_points(points)
|
|
||||||
if len(points) == 0:
|
|
||||||
continue
|
|
||||||
coords = self.points_to_pixel_coords(points)
|
|
||||||
coord_strings = coords.flatten().astype(str)
|
|
||||||
# Start new path string with M
|
|
||||||
coord_strings[0] = "M" + coord_strings[0]
|
|
||||||
# The C at the start of every 6th number communicates
|
|
||||||
# that the following 6 define a cubic Bezier
|
|
||||||
coord_strings[2::6] = ["C" + str(s) for s in coord_strings[2::6]]
|
|
||||||
# Possibly finish with "Z"
|
|
||||||
if vmobject.mark_paths_closed:
|
|
||||||
coord_strings[-1] = coord_strings[-1] + " Z"
|
|
||||||
result += " ".join(coord_strings)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_background_colored_vmobject_displayer(self):
|
def get_background_colored_vmobject_displayer(self):
|
||||||
# Quite wordy to type out a bunch
|
# Quite wordy to type out a bunch
|
||||||
long_name = "background_colored_vmobject_displayer"
|
bcvd = "background_colored_vmobject_displayer"
|
||||||
if not hasattr(self, long_name):
|
if not hasattr(self, bcvd):
|
||||||
setattr(self, long_name, BackgroundColoredVMobjectDisplayer(self))
|
setattr(self, bcvd, BackgroundColoredVMobjectDisplayer(self))
|
||||||
return getattr(self, long_name)
|
return getattr(self, bcvd)
|
||||||
|
|
||||||
def display_multiple_background_colored_vmobject(self, cvmobjects):
|
def display_multiple_background_colored_vmobject(self, cvmobjects):
|
||||||
displayer = self.get_background_colored_vmobject_displayer()
|
displayer = self.get_background_colored_vmobject_displayer()
|
||||||
@ -595,6 +605,7 @@ class Camera(object):
|
|||||||
return centered_space_coords
|
return centered_space_coords
|
||||||
|
|
||||||
|
|
||||||
|
# TODO
|
||||||
class BackgroundColoredVMobjectDisplayer(object):
|
class BackgroundColoredVMobjectDisplayer(object):
|
||||||
def __init__(self, camera):
|
def __init__(self, camera):
|
||||||
self.camera = camera
|
self.camera = camera
|
||||||
|
@ -878,7 +878,7 @@ class Mobject(Container):
|
|||||||
self.add(self.copy())
|
self.add(self.copy())
|
||||||
n -= 1
|
n -= 1
|
||||||
curr += 1
|
curr += 1
|
||||||
indices = curr * np.arange(curr + n) / (curr + n)
|
indices = curr * np.arange(curr + n) // (curr + n)
|
||||||
new_submobjects = []
|
new_submobjects = []
|
||||||
for index in indices:
|
for index in indices:
|
||||||
submob = self.submobjects[index]
|
submob = self.submobjects[index]
|
||||||
|
@ -375,7 +375,7 @@ class VMobjectFromSVGPathstring(VMobject):
|
|||||||
numbers = string_to_numbers(coord_string)
|
numbers = string_to_numbers(coord_string)
|
||||||
if len(numbers) % 2 == 1:
|
if len(numbers) % 2 == 1:
|
||||||
numbers.append(0)
|
numbers.append(0)
|
||||||
num_points = len(numbers) / 2
|
num_points = len(numbers) // 2
|
||||||
result = np.zeros((num_points, self.dim))
|
result = np.zeros((num_points, self.dim))
|
||||||
result[:, :2] = np.array(numbers).reshape((num_points, 2))
|
result[:, :2] = np.array(numbers).reshape((num_points, 2))
|
||||||
return result
|
return result
|
||||||
|
@ -9,8 +9,11 @@ from utils.bezier import get_smooth_handle_points
|
|||||||
from utils.bezier import interpolate
|
from utils.bezier import interpolate
|
||||||
from utils.bezier import is_closed
|
from utils.bezier import is_closed
|
||||||
from utils.bezier import partial_bezier_points
|
from utils.bezier import partial_bezier_points
|
||||||
from utils.color import color_to_rgb
|
from utils.color import color_to_rgba
|
||||||
|
from utils.color import interpolate_color
|
||||||
from utils.iterables import make_even
|
from utils.iterables import make_even
|
||||||
|
from utils.iterables import tuplify
|
||||||
|
from utils.iterables import stretch_array_to_length
|
||||||
|
|
||||||
|
|
||||||
class VMobject(Mobject):
|
class VMobject(Mobject):
|
||||||
@ -18,11 +21,21 @@ class VMobject(Mobject):
|
|||||||
"fill_color": None,
|
"fill_color": None,
|
||||||
"fill_opacity": 0.0,
|
"fill_opacity": 0.0,
|
||||||
"stroke_color": None,
|
"stroke_color": None,
|
||||||
|
"stroke_opacity": 1.0,
|
||||||
"stroke_width": DEFAULT_POINT_THICKNESS,
|
"stroke_width": DEFAULT_POINT_THICKNESS,
|
||||||
# The purpose of background stroke is to have
|
# The purpose of background stroke is to have
|
||||||
# something that won't overlap the fill
|
# something that won't overlap the fill, e.g.
|
||||||
|
# For text against some textured background
|
||||||
"background_stroke_color": BLACK,
|
"background_stroke_color": BLACK,
|
||||||
|
"background_stroke_opacity": 1.0,
|
||||||
"background_stroke_width": 0,
|
"background_stroke_width": 0,
|
||||||
|
# When a color c is set, there will be a second color
|
||||||
|
# computed based on interpolating c to WHITE by with
|
||||||
|
# gradient_to_white_factor, and the display will
|
||||||
|
# gradient to this secondary color in the direction
|
||||||
|
# of color_gradient_direction.
|
||||||
|
"color_gradient_direction": UL,
|
||||||
|
"gradient_to_white_factor": 0.2,
|
||||||
# Indicates that it will not be displayed, but
|
# Indicates that it will not be displayed, but
|
||||||
# that it should count in parent mobject's path
|
# that it should count in parent mobject's path
|
||||||
"is_subpath": False,
|
"is_subpath": False,
|
||||||
@ -39,95 +52,95 @@ class VMobject(Mobject):
|
|||||||
|
|
||||||
# Colors
|
# Colors
|
||||||
def init_colors(self):
|
def init_colors(self):
|
||||||
self.set_style_data(
|
self.set_fill(
|
||||||
fill_color=self.fill_color or self.color,
|
color=self.fill_color or self.color,
|
||||||
fill_opacity=self.fill_opacity,
|
opacity=self.fill_opacity,
|
||||||
stroke_color=self.stroke_color or self.color,
|
|
||||||
stroke_width=self.stroke_width,
|
|
||||||
background_stroke_color=self.background_stroke_color,
|
|
||||||
background_stroke_width=self.background_stroke_width,
|
|
||||||
family=self.propagate_style_to_family
|
family=self.propagate_style_to_family
|
||||||
)
|
)
|
||||||
|
self.set_stroke(
|
||||||
|
color=self.stroke_color or self.color,
|
||||||
|
width=self.stroke_width,
|
||||||
|
opacity=self.stroke_opacity,
|
||||||
|
family=self.propagate_style_to_family
|
||||||
|
)
|
||||||
|
self.set_background_stroke(
|
||||||
|
color=self.background_stroke_color,
|
||||||
|
width=self.background_stroke_width,
|
||||||
|
opacity=self.background_stroke_opacity,
|
||||||
|
family=self.propagate_style_to_family,
|
||||||
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def set_family_attr(self, attr, value):
|
def get_rgbas_array(self, color=None, opacity=None):
|
||||||
for mob in self.submobject_family():
|
"""
|
||||||
setattr(mob, attr, value)
|
First arg can be either a color, or a tuple/list of colors.
|
||||||
|
Likewise, opacity can either be a float, or a tuple of floats.
|
||||||
|
If self.gradient_to_white_factor is not zero, and only
|
||||||
|
one color was passed in, a second slightly light color
|
||||||
|
will automatically be added for the gradient
|
||||||
|
"""
|
||||||
|
if color is None:
|
||||||
|
color = self.color
|
||||||
|
colors = list(tuplify(color))
|
||||||
|
opacities = list(tuplify(opacity))
|
||||||
|
g2w_factor = self.get_gradient_to_white_factor()
|
||||||
|
if g2w_factor != 0 and len(colors) == 1:
|
||||||
|
lighter_color = interpolate_color(
|
||||||
|
colors[0], WHITE, g2w_factor
|
||||||
|
)
|
||||||
|
colors.append(lighter_color)
|
||||||
|
|
||||||
def set_style_data(self,
|
return np.array([
|
||||||
fill_color=None,
|
color_to_rgba(c, o)
|
||||||
fill_opacity=None,
|
for c, o in zip(*make_even(colors, opacities))
|
||||||
stroke_color=None,
|
])
|
||||||
stroke_width=None,
|
|
||||||
background_stroke_color=None,
|
|
||||||
background_stroke_width=None,
|
|
||||||
family=True
|
|
||||||
):
|
|
||||||
kwargs = {
|
|
||||||
"fill_color": fill_color,
|
|
||||||
"fill_opacity": fill_opacity,
|
|
||||||
"stroke_color": stroke_color,
|
|
||||||
"stroke_width": stroke_width,
|
|
||||||
"background_stroke_color": background_stroke_color,
|
|
||||||
"background_stroke_width": background_stroke_width,
|
|
||||||
"family": family,
|
|
||||||
}
|
|
||||||
for key in "fill_color", "stroke_color", "background_stroke_color":
|
|
||||||
# Instead of setting a self.fill_color attr,
|
|
||||||
# set a numerical self.fill_rgb to make
|
|
||||||
# interpolation easier
|
|
||||||
key_with_rgb = key.replace("color", "rgb")
|
|
||||||
color = kwargs[key]
|
|
||||||
if color is not None:
|
|
||||||
setattr(self, key_with_rgb, color_to_rgb(color))
|
|
||||||
for key in "fill_opacity", "stroke_width", "background_stroke_width":
|
|
||||||
if kwargs[key] is not None:
|
|
||||||
setattr(self, key, kwargs[key])
|
|
||||||
if family:
|
|
||||||
for mob in self.submobjects:
|
|
||||||
mob.set_style_data(**kwargs)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def set_fill(self, color=None, opacity=None, family=True):
|
def set_fill(self, color=None, opacity=None, family=True):
|
||||||
return self.set_style_data(
|
if opacity is None:
|
||||||
fill_color=color,
|
opacity = self.get_fill_opacity()
|
||||||
fill_opacity=opacity,
|
self.fill_rgbas = self.get_rgbas_array(color, opacity)
|
||||||
family=family
|
if family:
|
||||||
)
|
for submobject in self.submobjects:
|
||||||
|
submobject.set_fill(color, opacity, family)
|
||||||
|
return self
|
||||||
|
|
||||||
def set_stroke(self, color=None, width=None, family=True):
|
def set_stroke(self, color=None, width=None, opacity=None,
|
||||||
return self.set_style_data(
|
background=False, family=True):
|
||||||
stroke_color=color,
|
if opacity is None:
|
||||||
stroke_width=width,
|
opacity = self.get_stroke_opacity(background)
|
||||||
family=family
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_background_stroke(self, color=None, width=None, family=True):
|
if background:
|
||||||
return self.set_style_data(
|
array_name = "background_stroke_rgbas"
|
||||||
background_stroke_color=color,
|
width_name = "background_stroke_width"
|
||||||
background_stroke_width=width,
|
else:
|
||||||
family=family
|
array_name = "stroke_rgbas"
|
||||||
)
|
width_name = "stroke_width"
|
||||||
|
rgbas = self.get_rgbas_array(color, opacity)
|
||||||
|
setattr(self, array_name, rgbas)
|
||||||
|
if width is not None:
|
||||||
|
setattr(self, width_name, width)
|
||||||
|
if family:
|
||||||
|
for submobject in self.submobjects:
|
||||||
|
submobject.set_stroke(
|
||||||
|
color, width, opacity, background, family
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def set_background_stroke(self, **kwargs):
|
||||||
|
kwargs["background"] = True
|
||||||
|
self.set_stroke(**kwargs)
|
||||||
|
return self
|
||||||
|
|
||||||
def set_color(self, color, family=True):
|
def set_color(self, color, family=True):
|
||||||
self.set_style_data(
|
self.set_fill(color, family=family)
|
||||||
stroke_color=color,
|
self.set_stroke(color, family=family)
|
||||||
fill_color=color,
|
|
||||||
family=family
|
|
||||||
)
|
|
||||||
self.color = color
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def match_style(self, vmobject):
|
def match_style(self, vmobject):
|
||||||
self.set_style_data(
|
for a_name in ["fill_rgbas", "stroke_rgbas", "background_stroke_rgbas"]:
|
||||||
fill_color=vmobject.get_fill_color(),
|
setattr(self, np.array(get_attr(vmobject, a_name)))
|
||||||
fill_opacity=vmobject.get_fill_opacity(),
|
self.stroke_width = vmobject.stroke_width
|
||||||
stroke_color=vmobject.get_stroke_color(),
|
self.background_stroke_width = vmobject.background_stroke_width
|
||||||
stroke_width=vmobject.get_stroke_width(),
|
|
||||||
background_stroke_color=vmobject.get_background_stroke_color(),
|
|
||||||
background_stroke_width=vmobject.get_background_stroke_width(),
|
|
||||||
family=False
|
|
||||||
)
|
|
||||||
|
|
||||||
# Does its best to match up submobject lists, and
|
# Does its best to match up submobject lists, and
|
||||||
# match styles accordingly
|
# match styles accordingly
|
||||||
@ -151,52 +164,99 @@ class VMobject(Mobject):
|
|||||||
)
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_fill_rgb(self):
|
def get_fill_rgbas(self):
|
||||||
return np.clip(self.fill_rgb, 0, 1)
|
return np.clip(self.fill_rgbas, 0, 1)
|
||||||
|
|
||||||
def get_fill_color(self):
|
def get_fill_color(self):
|
||||||
try:
|
"""
|
||||||
self.fill_rgb = np.clip(self.fill_rgb, 0.0, 1.0)
|
If there are multiple colors (for gradient)
|
||||||
return Color(rgb=self.fill_rgb)
|
this returns the first one
|
||||||
except:
|
"""
|
||||||
return Color(WHITE)
|
return self.get_fill_colors()[0]
|
||||||
|
|
||||||
def get_fill_opacity(self):
|
def get_fill_opacity(self):
|
||||||
return np.clip(self.fill_opacity, 0, 1)
|
"""
|
||||||
|
If there are multiple opacities, this returns the
|
||||||
|
first
|
||||||
|
"""
|
||||||
|
return self.get_fill_opacities()[0]
|
||||||
|
|
||||||
def get_stroke_rgb(self):
|
def get_fill_colors(self):
|
||||||
return np.clip(self.stroke_rgb, 0, 1)
|
return [
|
||||||
|
Color(rgb=rgba[:3])
|
||||||
|
for rgba in self.get_fill_rgbas()
|
||||||
|
]
|
||||||
|
|
||||||
def get_stroke_color(self):
|
def get_fill_opacities(self):
|
||||||
try:
|
return self.get_fill_rgbas()[:, 3]
|
||||||
self.stroke_rgb = np.clip(self.stroke_rgb, 0, 1)
|
|
||||||
return Color(rgb=self.stroke_rgb)
|
|
||||||
except:
|
|
||||||
return Color(WHITE)
|
|
||||||
|
|
||||||
def get_stroke_width(self):
|
def get_stroke_rgbas(self, background=False):
|
||||||
return max(0, self.stroke_width)
|
if background:
|
||||||
|
rgbas = self.background_stroke_rgbas
|
||||||
|
else:
|
||||||
|
rgbas = self.stroke_rgbas
|
||||||
|
return np.clip(rgbas, 0, 1)
|
||||||
|
|
||||||
def get_background_stroke_rgb(self):
|
def get_stroke_color(self, background=False):
|
||||||
return np.clip(self.background_stroke_rgb, 0, 1)
|
return self.get_stroke_colors(background)[0]
|
||||||
|
|
||||||
def get_background_stroke_color(self):
|
def get_stroke_width(self, background=False):
|
||||||
try:
|
if background:
|
||||||
self.background_stroke_rgb = np.clip(
|
width = self.background_stroke_width
|
||||||
self.background_stroke_rgb, 0, 1
|
else:
|
||||||
)
|
width = self.stroke_width
|
||||||
return Color(rgb=self.background_stroke_rgb)
|
return max(0, width)
|
||||||
except:
|
|
||||||
return Color(WHITE)
|
|
||||||
|
|
||||||
def get_background_stroke_width(self):
|
def get_stroke_opacity(self, background=False):
|
||||||
return max(0, self.background_stroke_width)
|
return self.get_stroke_opacities(background)[0]
|
||||||
|
|
||||||
|
def get_stroke_colors(self, background=False):
|
||||||
|
return [
|
||||||
|
Color(rgb=rgba[:3])
|
||||||
|
for rgba in self.get_stroke_rgbas(background)
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_stroke_opacities(self, background=False):
|
||||||
|
return self.get_stroke_rgbas(background)[:, 3]
|
||||||
|
|
||||||
def get_color(self):
|
def get_color(self):
|
||||||
if self.fill_opacity == 0:
|
if np.all(self.get_fill_opacities() == 0):
|
||||||
return self.get_stroke_color()
|
return self.get_stroke_color()
|
||||||
return self.get_fill_color()
|
return self.get_fill_color()
|
||||||
|
|
||||||
|
def set_color_gradient_direction(self, direction, family=True):
|
||||||
|
direction = np.array(direction)
|
||||||
|
if family:
|
||||||
|
for submob in self.submobject_family():
|
||||||
|
submob.color_gradient_direction = direction
|
||||||
|
else:
|
||||||
|
self.color_gradient_direction = direction
|
||||||
|
return self
|
||||||
|
|
||||||
|
def set_gradient_to_white_factor(self, factor, family=True):
|
||||||
|
if family:
|
||||||
|
for submob in self.submobject_family():
|
||||||
|
submob.gradient_to_white_factor = factor
|
||||||
|
else:
|
||||||
|
self.gradient_to_white_factor = factor
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_color_gradient_direction(self):
|
||||||
|
return np.array(self.color_gradient_direction)
|
||||||
|
|
||||||
|
def get_gradient_to_white_factor(self):
|
||||||
|
return self.gradient_to_white_factor
|
||||||
|
|
||||||
|
def get_gradient_start_and_end_points(self):
|
||||||
|
direction = self.get_color_gradient_direction()
|
||||||
|
c = self.get_center()
|
||||||
|
bases = np.array([
|
||||||
|
self.get_edge_center(vect) - c
|
||||||
|
for vect in [RIGHT, UP, OUT]
|
||||||
|
]).transpose()
|
||||||
|
offset = np.dot(bases, direction)
|
||||||
|
return (c + offset, c - offset)
|
||||||
|
|
||||||
def color_using_background_image(self, background_image_file):
|
def color_using_background_image(self, background_image_file):
|
||||||
self.background_image_file = background_image_file
|
self.background_image_file = background_image_file
|
||||||
self.set_color(WHITE)
|
self.set_color(WHITE)
|
||||||
@ -357,7 +417,7 @@ class VMobject(Mobject):
|
|||||||
return bezier(self.points[3 * n:3 * n + 4])
|
return bezier(self.points[3 * n:3 * n + 4])
|
||||||
|
|
||||||
def get_num_anchor_points(self):
|
def get_num_anchor_points(self):
|
||||||
return (len(self.points) - 1) / 3 + 1
|
return (len(self.points) - 1) // 3 + 1
|
||||||
|
|
||||||
def point_from_proportion(self, alpha):
|
def point_from_proportion(self, alpha):
|
||||||
num_cubics = self.get_num_anchor_points() - 1
|
num_cubics = self.get_num_anchor_points() - 1
|
||||||
@ -379,12 +439,13 @@ class VMobject(Mobject):
|
|||||||
return self.get_anchors()
|
return self.get_anchors()
|
||||||
|
|
||||||
# Alignment
|
# Alignment
|
||||||
def align_points(self, mobject):
|
def align_points(self, vmobject):
|
||||||
Mobject.align_points(self, mobject)
|
Mobject.align_points(self, vmobject)
|
||||||
is_subpath = self.is_subpath or mobject.is_subpath
|
self.align_rgbas(vmobject)
|
||||||
self.is_subpath = mobject.is_subpath = is_subpath
|
is_subpath = self.is_subpath or vmobject.is_subpath
|
||||||
mark_closed = self.mark_paths_closed and mobject.mark_paths_closed
|
self.is_subpath = vmobject.is_subpath = is_subpath
|
||||||
self.mark_paths_closed = mobject.mark_paths_closed = mark_closed
|
mark_closed = self.mark_paths_closed and vmobject.mark_paths_closed
|
||||||
|
self.mark_paths_closed = vmobject.mark_paths_closed = mark_closed
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def align_points_with_larger(self, larger_mobject):
|
def align_points_with_larger(self, larger_mobject):
|
||||||
@ -411,7 +472,7 @@ class VMobject(Mobject):
|
|||||||
# 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(curr + n - 1) *
|
index_allocation = (np.arange(curr + n - 1) *
|
||||||
num_curves) / (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)
|
||||||
@ -427,6 +488,19 @@ class VMobject(Mobject):
|
|||||||
self.set_points(points)
|
self.set_points(points)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def align_rgbas(self, vmobject):
|
||||||
|
attrs = ["fill_rgbas", "stroke_rgbas", "background_stroke_rgbas"]
|
||||||
|
for attr in attrs:
|
||||||
|
a1 = getattr(self, attr)
|
||||||
|
a2 = getattr(vmobject, attr)
|
||||||
|
if len(a1) > len(a2):
|
||||||
|
new_a2 = stretch_array_to_length(a2, len(a1))
|
||||||
|
setattr(vmobject, attr, new_a2)
|
||||||
|
elif len(a2) > len(a1):
|
||||||
|
new_a1 = stretch_array_to_length(a1, len(a2))
|
||||||
|
setattr(self, attr, new_a1)
|
||||||
|
return self
|
||||||
|
|
||||||
def get_point_mobject(self, center=None):
|
def get_point_mobject(self, center=None):
|
||||||
if center is None:
|
if center is None:
|
||||||
center = self.get_center()
|
center = self.get_center()
|
||||||
@ -439,12 +513,13 @@ class VMobject(Mobject):
|
|||||||
|
|
||||||
def interpolate_color(self, mobject1, mobject2, alpha):
|
def interpolate_color(self, mobject1, mobject2, alpha):
|
||||||
attrs = [
|
attrs = [
|
||||||
"fill_rgb",
|
"fill_rgbas",
|
||||||
"fill_opacity",
|
"stroke_rgbas",
|
||||||
"stroke_rgb",
|
"background_stroke_rgbas",
|
||||||
"stroke_width",
|
"stroke_width",
|
||||||
"background_stroke_rgb",
|
|
||||||
"background_stroke_width",
|
"background_stroke_width",
|
||||||
|
"color_gradient_direction",
|
||||||
|
"gradient_to_white_factor",
|
||||||
]
|
]
|
||||||
for attr in attrs:
|
for attr in attrs:
|
||||||
setattr(self, attr, interpolate(
|
setattr(self, attr, interpolate(
|
||||||
@ -547,6 +622,6 @@ class DashedMobject(VMobject):
|
|||||||
for i in range(self.dashes_num):
|
for i in range(self.dashes_num):
|
||||||
a = ((1 + buff) * i) / self.dashes_num
|
a = ((1 + buff) * i) / self.dashes_num
|
||||||
b = 1 - ((1 + buff) * (self.dashes_num - 1 - i)) / self.dashes_num
|
b = 1 - ((1 + buff) * (self.dashes_num - 1 - i)) / self.dashes_num
|
||||||
dash = VMobject(color=self.color)
|
dash = VMobject(color=self.get_color())
|
||||||
dash.pointwise_become_partial(mobject, a, b)
|
dash.pointwise_become_partial(mobject, a, b)
|
||||||
self.submobjects.append(dash)
|
self.submobjects.append(dash)
|
||||||
|
@ -6,4 +6,4 @@ progressbar==2.5
|
|||||||
scipy==1.1.0
|
scipy==1.1.0
|
||||||
tqdm==4.24.0
|
tqdm==4.24.0
|
||||||
opencv-python==3.4.2.17
|
opencv-python==3.4.2.17
|
||||||
git+https://github.com/scottopell/aggdraw-64bits@c95aac4369038706943fd0effb7d888683860e5a#egg=aggdraw
|
pycairo==1.17.1
|
||||||
|
@ -72,7 +72,7 @@ def tuplify(obj):
|
|||||||
return (obj,)
|
return (obj,)
|
||||||
try:
|
try:
|
||||||
return tuple(obj)
|
return tuple(obj)
|
||||||
except:
|
except TypeError:
|
||||||
return (obj,)
|
return (obj,)
|
||||||
|
|
||||||
|
|
||||||
@ -90,8 +90,8 @@ def make_even(iterable_1, iterable_2):
|
|||||||
list_1, list_2 = list(iterable_1), list(iterable_2)
|
list_1, list_2 = list(iterable_1), list(iterable_2)
|
||||||
length = max(len(list_1), len(list_2))
|
length = max(len(list_1), len(list_2))
|
||||||
return (
|
return (
|
||||||
[list_1[(n * len(list_1)) / length] for n in range(length)],
|
[list_1[(n * len(list_1)) // length] for n in range(length)],
|
||||||
[list_2[(n * len(list_2)) / length] for n in range(length)]
|
[list_2[(n * len(list_2)) // length] for n in range(length)]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
import hashlib
|
||||||
|
|
||||||
from constants import TEX_DIR
|
from constants import TEX_DIR
|
||||||
from constants import TEX_TEXT_TO_REPLACE
|
from constants import TEX_TEXT_TO_REPLACE
|
||||||
|
|
||||||
|
|
||||||
def tex_hash(expression, template_tex_file):
|
def tex_hash(expression, template_tex_file):
|
||||||
return str(hash(expression + template_tex_file))
|
id_str = str(expression + template_tex_file)
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
hasher.update(id_str.encode())
|
||||||
|
# Truncating at 16 bytes for cleanliness
|
||||||
|
return hasher.hexdigest()[:16]
|
||||||
|
|
||||||
|
|
||||||
def tex_to_svg_file(expression, template_tex_file):
|
def tex_to_svg_file(expression, template_tex_file):
|
||||||
|
Reference in New Issue
Block a user