mirror of
https://github.com/3b1b/manim.git
synced 2025-07-29 21:12:35 +08:00
Rudimenary ImageMobject
This commit is contained in:
50
camera.py
50
camera.py
@ -7,7 +7,7 @@ from colour import Color
|
|||||||
import aggdraw
|
import aggdraw
|
||||||
|
|
||||||
from helpers import *
|
from helpers import *
|
||||||
from mobject import PMobject, VMobject
|
from mobject import PMobject, VMobject, ImageMobject
|
||||||
|
|
||||||
class Camera(object):
|
class Camera(object):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
@ -91,6 +91,10 @@ class Camera(object):
|
|||||||
mobject.points, mobject.rgbs,
|
mobject.points, mobject.rgbs,
|
||||||
self.adjusted_thickness(mobject.stroke_width)
|
self.adjusted_thickness(mobject.stroke_width)
|
||||||
)
|
)
|
||||||
|
elif isinstance(mobject, ImageMobject):
|
||||||
|
self.display_image_mobject(mobject)
|
||||||
|
else:
|
||||||
|
raise Exception("Unknown mobject type: " + type(mobject))
|
||||||
#TODO, more? Call out if it's unknown?
|
#TODO, more? Call out if it's unknown?
|
||||||
self.display_multiple_vectorized_mobjects(vmobjects)
|
self.display_multiple_vectorized_mobjects(vmobjects)
|
||||||
|
|
||||||
@ -184,6 +188,50 @@ class Camera(object):
|
|||||||
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 display_image_mobject(self, image_mobject):
|
||||||
|
corner_coords = self.points_to_pixel_coords(image_mobject.points)
|
||||||
|
ul_coords, ur_coords, dl_coords = corner_coords
|
||||||
|
right_vect = ur_coords - ul_coords
|
||||||
|
down_vect = dl_coords - ul_coords
|
||||||
|
|
||||||
|
impa = image_mobject.pixel_array
|
||||||
|
|
||||||
|
oh, ow = self.pixel_array.shape[:2] #Outer width and height
|
||||||
|
ih, iw = impa.shape[:2] #inner with and height
|
||||||
|
rgb_len = self.pixel_array.shape[2]
|
||||||
|
|
||||||
|
# List of all coordinates of pixels, given as (x, y),
|
||||||
|
# which matches the return type of points_to_pixel_coords,
|
||||||
|
# even though np.array indexing naturally happens as (y, x)
|
||||||
|
all_pixel_coords = np.zeros((oh*ow, 2), dtype = 'int')
|
||||||
|
a = np.arange(oh*ow, dtype = 'int')
|
||||||
|
all_pixel_coords[:,0] = a%ow
|
||||||
|
all_pixel_coords[:,1] = a/ow
|
||||||
|
|
||||||
|
recentered_coords = all_pixel_coords - ul_coords
|
||||||
|
coord_norms = np.linalg.norm(recentered_coords, axis = 1)
|
||||||
|
|
||||||
|
with np.errstate(divide='ignore'):
|
||||||
|
ix_coords, iy_coords = [
|
||||||
|
np.divide(
|
||||||
|
dim*np.dot(recentered_coords, vect),
|
||||||
|
np.dot(vect, vect),
|
||||||
|
)
|
||||||
|
for vect, dim in (right_vect, iw), (down_vect, ih)
|
||||||
|
]
|
||||||
|
to_change = reduce(op.and_, [
|
||||||
|
ix_coords >= 0, ix_coords < iw,
|
||||||
|
iy_coords >= 0, iy_coords < ih,
|
||||||
|
])
|
||||||
|
n_to_change = np.sum(to_change)
|
||||||
|
inner_flat_coords = iw*iy_coords[to_change] + ix_coords[to_change]
|
||||||
|
flat_impa = impa.reshape((iw*ih, rgb_len))
|
||||||
|
target_rgbs = flat_impa[inner_flat_coords, :]
|
||||||
|
|
||||||
|
flat_pa = self.pixel_array.reshape((ow*oh, rgb_len))
|
||||||
|
flat_pa[to_change] = target_rgbs
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -59,11 +59,14 @@ BOTTOM = SPACE_HEIGHT*DOWN
|
|||||||
LEFT_SIDE = SPACE_WIDTH*LEFT
|
LEFT_SIDE = SPACE_WIDTH*LEFT
|
||||||
RIGHT_SIDE = SPACE_WIDTH*RIGHT
|
RIGHT_SIDE = SPACE_WIDTH*RIGHT
|
||||||
|
|
||||||
|
# Change this to point to where you want
|
||||||
|
# animation files to output
|
||||||
|
MOVIE_DIR = os.path.join(os.path.expanduser('~'), "Dropbox/3b1b_videos/animations/")
|
||||||
|
###
|
||||||
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
|
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||||
FILE_DIR = os.path.join(THIS_DIR, "files")
|
FILE_DIR = os.path.join(THIS_DIR, "files")
|
||||||
IMAGE_DIR = os.path.join(FILE_DIR, "images")
|
IMAGE_DIR = os.path.join(FILE_DIR, "images")
|
||||||
GIF_DIR = os.path.join(FILE_DIR, "gifs")
|
GIF_DIR = os.path.join(FILE_DIR, "gifs")
|
||||||
MOVIE_DIR = os.path.join(FILE_DIR, "movies")
|
|
||||||
STAGED_SCENES_DIR = os.path.join(FILE_DIR, "staged_scenes")
|
STAGED_SCENES_DIR = os.path.join(FILE_DIR, "staged_scenes")
|
||||||
TEX_DIR = os.path.join(FILE_DIR, "Tex")
|
TEX_DIR = os.path.join(FILE_DIR, "Tex")
|
||||||
TEX_IMAGE_DIR = os.path.join(IMAGE_DIR, "Tex")
|
TEX_IMAGE_DIR = os.path.join(IMAGE_DIR, "Tex")
|
||||||
|
@ -6,4 +6,5 @@ __all__ = [
|
|||||||
|
|
||||||
from mobject import Mobject, Group
|
from mobject import Mobject, Group
|
||||||
from point_cloud_mobject import Point, Mobject1D, Mobject2D, PMobject
|
from point_cloud_mobject import Point, Mobject1D, Mobject2D, PMobject
|
||||||
from vectorized_mobject import VMobject, VGroup
|
from vectorized_mobject import VMobject, VGroup
|
||||||
|
from image_mobject import ImageMobject
|
@ -8,95 +8,41 @@ from helpers import *
|
|||||||
from mobject import Mobject
|
from mobject import Mobject
|
||||||
from point_cloud_mobject import PMobject
|
from point_cloud_mobject import PMobject
|
||||||
|
|
||||||
class ImageMobject(PMobject):
|
class ImageMobject(Mobject):
|
||||||
"""
|
"""
|
||||||
Automatically filters out black pixels
|
Automatically filters out black pixels
|
||||||
"""
|
"""
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"filter_color" : "black",
|
"filter_color" : "black",
|
||||||
"invert" : False,
|
"invert" : False,
|
||||||
"use_cache" : True,
|
# "use_cache" : True,
|
||||||
"stroke_width" : 1,
|
"height": 2.0,
|
||||||
"scale_factorue": 1.0,
|
|
||||||
"should_center" : True,
|
|
||||||
}
|
}
|
||||||
def __init__(self, image_file, **kwargs):
|
def __init__(self, filename_or_array, **kwargs):
|
||||||
digest_locals(self)
|
if isinstance(filename_or_array, str):
|
||||||
Mobject.__init__(self, **kwargs)
|
path = get_full_image_path(filename_or_array)
|
||||||
self.name = to_camel_case(
|
image = Image.open(path).convert("RGB")
|
||||||
os.path.split(image_file)[-1].split(".")[0]
|
self.pixel_array = np.array(image)
|
||||||
)
|
|
||||||
path = get_full_image_path(image_file)
|
|
||||||
self.generate_points_from_file(path)
|
|
||||||
self.scale(self.scale_factorue)
|
|
||||||
if self.should_center:
|
|
||||||
self.center()
|
|
||||||
|
|
||||||
def generate_points_from_file(self, path):
|
|
||||||
if self.use_cache and self.read_in_cached_attrs(path):
|
|
||||||
return
|
|
||||||
image = Image.open(path).convert('RGB')
|
|
||||||
if self.invert:
|
|
||||||
image = invert_image(image)
|
|
||||||
self.generate_points_from_image_array(np.array(image))
|
|
||||||
self.cache_attrs(path)
|
|
||||||
|
|
||||||
def get_cached_attr_files(self, path, attrs):
|
|
||||||
#Hash should be unique to (path, invert) pair
|
|
||||||
unique_hash = str(hash(path+str(self.invert)))
|
|
||||||
return [
|
|
||||||
os.path.join(IMAGE_MOBJECT_DIR, unique_hash)+"."+attr
|
|
||||||
for attr in attrs
|
|
||||||
]
|
|
||||||
|
|
||||||
def read_in_cached_attrs(self, path,
|
|
||||||
attrs = ("points", "rgbs"),
|
|
||||||
dtype = "float64"):
|
|
||||||
cached_attr_files = self.get_cached_attr_files(path, attrs)
|
|
||||||
if all(map(os.path.exists, cached_attr_files)):
|
|
||||||
for attr, cache_file in zip(attrs, cached_attr_files):
|
|
||||||
arr = np.fromfile(cache_file, dtype = dtype)
|
|
||||||
arr = arr.reshape(arr.size/self.dim, self.dim)
|
|
||||||
setattr(self, attr, arr)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def cache_attrs(self, path,
|
|
||||||
attrs = ("points", "rgbs"),
|
|
||||||
dtype = "float64"):
|
|
||||||
cached_attr_files = self.get_cached_attr_files(path, attrs)
|
|
||||||
for attr, cache_file in zip(attrs, cached_attr_files):
|
|
||||||
getattr(self, attr).astype(dtype).tofile(cache_file)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_points_from_image_array(self, image_array):
|
|
||||||
height, width = image_array.shape[:2]
|
|
||||||
#Flatten array, and find indices where rgb is not filter_rgb
|
|
||||||
array = image_array.reshape((height * width, 3))
|
|
||||||
filter_rgb = np.array(Color(self.filter_color).get_rgb())
|
|
||||||
filter_rgb = 255*filter_rgb.astype('uint8')
|
|
||||||
bools = array == filter_rgb
|
|
||||||
bools = bools[:,0]*bools[:,1]*bools[:,2]
|
|
||||||
indices = np.arange(height * width, dtype = 'int')[~bools]
|
|
||||||
rgbs = array[indices, :].astype('float') / 255.0
|
|
||||||
|
|
||||||
points = np.zeros((indices.size, 3), dtype = 'float64')
|
|
||||||
points[:,0] = indices%width - width/2
|
|
||||||
points[:,1] = -indices/width + height/2
|
|
||||||
|
|
||||||
height, width = map(float, (height, width))
|
|
||||||
if height / width > float(DEFAULT_HEIGHT) / DEFAULT_WIDTH:
|
|
||||||
points *= 2 * SPACE_HEIGHT / height
|
|
||||||
else:
|
else:
|
||||||
points *= 2 * SPACE_WIDTH / width
|
self.pixel_array = np.array(filename_or_array)
|
||||||
self.add_points(points, rgbs = rgbs)
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
class MobjectFromPixelArray(ImageMobject):
|
|
||||||
def __init__(self, image_array, **kwargs):
|
|
||||||
Mobject.__init__(self, **kwargs)
|
Mobject.__init__(self, **kwargs)
|
||||||
self.generate_points_from_image_array(image_array)
|
|
||||||
|
|
||||||
|
def init_points(self):
|
||||||
|
#Corresponding corners of image are fixed to these
|
||||||
|
#Three points
|
||||||
|
self.points = np.array([
|
||||||
|
UP+LEFT,
|
||||||
|
UP+RIGHT,
|
||||||
|
DOWN+LEFT,
|
||||||
|
])
|
||||||
|
self.center()
|
||||||
|
self.scale_to_fit_height(self.height)
|
||||||
|
h, w = self.pixel_array.shape[:2]
|
||||||
|
self.stretch_to_fit_width(self.height*w/h)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user