|
15 | 15 | import cairo
|
16 | 16 | import numpy as np
|
17 | 17 | 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 |
31 | 30 |
|
32 | 31 | LINE_JOIN_MAP = {
|
33 | 32 | LineJointType.AUTO: None, # TODO: this could be improved
|
@@ -973,48 +972,60 @@ def display_image_mobject(
|
973 | 972 | pixel_array
|
974 | 973 | The Pixel array to put the imagemobject in.
|
975 | 974 | """
|
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 |
| - |
982 | 975 | 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]), |
990 | 995 | )
|
991 | 996 |
|
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]] |
1000 | 1004 | )
|
1001 | 1005 |
|
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 | + ) |
1003 | 1016 |
|
1004 | 1017 | # Paste into an image as large as the camera's pixel array
|
1005 | 1018 | full_image = Image.fromarray(
|
1006 | 1019 | np.zeros((self.pixel_height, self.pixel_width)),
|
1007 | 1020 | mode="RGBA",
|
1008 | 1021 | )
|
1009 |
| - new_ul_coords = center_coords - np.array(sub_image.size) / 2 |
1010 |
| - new_ul_coords = new_ul_coords.astype(int) |
1011 | 1022 | full_image.paste(
|
1012 | 1023 | sub_image,
|
1013 | 1024 | 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], |
1018 | 1029 | ),
|
1019 | 1030 | )
|
1020 | 1031 | # Paint on top of existing pixel array
|
|
0 commit comments