Skip to content

Commit 23ec1f3

Browse files
committed
Fix 3D ImageMobject rotation
1 parent bcab73a commit 23ec1f3

File tree

1 file changed

+52
-41
lines changed

1 file changed

+52
-41
lines changed

manim/camera/camera.py

Lines changed: 52 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,18 @@
1515
import cairo
1616
import numpy as np
1717
from PIL import Image
18-
from scipy.spatial.distance import pdist
19-
20-
from .. import config, logger
21-
from ..constants import *
22-
from ..mobject.mobject import Mobject
23-
from ..mobject.types.image_mobject import AbstractImageMobject
24-
from ..mobject.types.point_cloud_mobject import PMobject
25-
from ..mobject.types.vectorized_mobject import VMobject
26-
from ..utils.color import ManimColor, ParsableManimColor, color_to_int_rgba
27-
from ..utils.family import extract_mobject_family_members
28-
from ..utils.images import get_full_raster_image_path
29-
from ..utils.iterables import list_difference_update
30-
from ..utils.space_ops import angle_of_vector
18+
19+
from manim._config import config, logger
20+
from manim.constants import *
21+
from manim.mobject.mobject import Mobject
22+
from manim.mobject.types.image_mobject import AbstractImageMobject
23+
from manim.mobject.types.point_cloud_mobject import PMobject
24+
from manim.mobject.types.vectorized_mobject import VMobject
25+
from manim.typing import ManimFloat
26+
from manim.utils.color import ManimColor, ParsableManimColor, color_to_int_rgba
27+
from manim.utils.family import extract_mobject_family_members
28+
from manim.utils.images import get_full_raster_image_path
29+
from manim.utils.iterables import list_difference_update
3130

3231
LINE_JOIN_MAP = {
3332
LineJointType.AUTO: None, # TODO: this could be improved
@@ -973,48 +972,60 @@ def display_image_mobject(
973972
pixel_array
974973
The Pixel array to put the imagemobject in.
975974
"""
976-
corner_coords = self.points_to_pixel_coords(image_mobject, image_mobject.points)
977-
ul_coords, ur_coords, dl_coords, _ = corner_coords
978-
right_vect = ur_coords - ul_coords
979-
down_vect = dl_coords - ul_coords
980-
center_coords = ul_coords + (right_vect + down_vect) / 2
981-
982975
sub_image = Image.fromarray(image_mobject.get_pixel_array(), mode="RGBA")
983-
984-
# Reshape
985-
pixel_width = max(int(pdist([ul_coords, ur_coords]).item()), 1)
986-
pixel_height = max(int(pdist([ul_coords, dl_coords]).item()), 1)
987-
sub_image = sub_image.resize(
988-
(pixel_width, pixel_height),
989-
resample=image_mobject.resampling_algorithm,
976+
original_coords = np.array(
977+
[
978+
[0, 0],
979+
[sub_image.width, 0],
980+
[0, sub_image.height],
981+
[sub_image.width, sub_image.height],
982+
]
983+
)
984+
target_coords = self.points_to_pixel_coords(image_mobject, image_mobject.points)
985+
shift_vector = np.array(
986+
[
987+
min(*[x for x, y in target_coords]),
988+
min(*[y for x, y in target_coords]),
989+
]
990+
)
991+
target_coords -= shift_vector
992+
target_size = (
993+
max(*[x for x, y in target_coords]),
994+
max(*[y for x, y in target_coords]),
990995
)
991996

992-
# Rotate
993-
angle = angle_of_vector(right_vect)
994-
adjusted_angle = -int(360 * angle / TAU)
995-
if adjusted_angle != 0:
996-
sub_image = sub_image.rotate(
997-
adjusted_angle,
998-
resample=image_mobject.resampling_algorithm,
999-
expand=1,
997+
homographic_matrix = []
998+
for tc, oc in zip(target_coords, original_coords):
999+
homographic_matrix.append(
1000+
[tc[0], tc[1], 1, 0, 0, 0, -oc[0] * tc[0], -oc[0] * tc[1]]
1001+
)
1002+
homographic_matrix.append(
1003+
[0, 0, 0, tc[0], tc[1], 1, -oc[1] * tc[0], -oc[1] * tc[1]]
10001004
)
10011005

1002-
# TODO, there is no accounting for a shear...
1006+
A = np.array(homographic_matrix, dtype=ManimFloat)
1007+
b = original_coords.reshape(8).astype(ManimFloat)
1008+
transform_coefficients = np.linalg.solve(A, b)
1009+
1010+
sub_image = sub_image.transform(
1011+
size=target_size, # Use the smallest possible size for speed
1012+
method=Image.Transform.PERSPECTIVE,
1013+
data=transform_coefficients,
1014+
resample=image_mobject.resampling_algorithm,
1015+
)
10031016

10041017
# Paste into an image as large as the camera's pixel array
10051018
full_image = Image.fromarray(
10061019
np.zeros((self.pixel_height, self.pixel_width)),
10071020
mode="RGBA",
10081021
)
1009-
new_ul_coords = center_coords - np.array(sub_image.size) / 2
1010-
new_ul_coords = new_ul_coords.astype(int)
10111022
full_image.paste(
10121023
sub_image,
10131024
box=(
1014-
new_ul_coords[0],
1015-
new_ul_coords[1],
1016-
new_ul_coords[0] + sub_image.size[0],
1017-
new_ul_coords[1] + sub_image.size[1],
1025+
shift_vector[0],
1026+
shift_vector[1],
1027+
shift_vector[0] + target_size[0],
1028+
shift_vector[1] + target_size[1],
10181029
),
10191030
)
10201031
# Paint on top of existing pixel array

0 commit comments

Comments
 (0)