Skip to content

Commit 3af4c86

Browse files
adamryczkowskikolibril13pre-commit-ci[bot]
authored
Support for specifying the interpolation algorithms for individual ImageMobjects (#1122)
* Adds support for specifying the interpolation algorithms for individual ImageMobjects. * Remove setup.py * Added a proper unit test * Code formatting * Adds Doc string * Test rewritten by kolibril13 * Added control data to the test * Replaces string resampling constants with Pillow integers; renames the constants into o 'resampling algorithms' * Final 'nitpicks' * ValueError update * update example * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: kolibril13 <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 08e06e1 commit 3af4c86

File tree

5 files changed

+105
-4
lines changed

5 files changed

+105
-4
lines changed

manim/camera/camera.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,7 @@ def display_multiple_image_mobjects(self, image_mobjects, pixel_array):
890890
for image_mobject in image_mobjects:
891891
self.display_image_mobject(image_mobject, pixel_array)
892892

893-
def display_image_mobject(self, image_mobject, pixel_array):
893+
def display_image_mobject(self, image_mobject: AbstractImageMobject, pixel_array):
894894
"""Displays an ImageMobject by changing the pixel_array suitably.
895895
896896
Parameters
@@ -912,15 +912,15 @@ def display_image_mobject(self, image_mobject, pixel_array):
912912
pixel_width = max(int(pdist([ul_coords, ur_coords])), 1)
913913
pixel_height = max(int(pdist([ul_coords, dl_coords])), 1)
914914
sub_image = sub_image.resize(
915-
(pixel_width, pixel_height), resample=Image.BICUBIC
915+
(pixel_width, pixel_height), resample=image_mobject.resampling_algorithm
916916
)
917917

918918
# Rotate
919919
angle = angle_of_vector(right_vect)
920920
adjusted_angle = -int(360 * angle / TAU)
921921
if adjusted_angle != 0:
922922
sub_image = sub_image.rotate(
923-
adjusted_angle, resample=Image.BICUBIC, expand=1
923+
adjusted_angle, resample=image_mobject.resampling_algorithm, expand=1
924924
)
925925

926926
# TODO, there is no accounting for a shear...

manim/constants.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import typing
66

77
import numpy as np
8+
from PIL import Image
89

910
# Messages
1011
NOT_SETTING_FONT_MSG: str = """
@@ -42,6 +43,18 @@
4243
HEAVY: str = "HEAVY"
4344
ULTRAHEAVY: str = "ULTRAHEAVY"
4445

46+
RESAMPLING_ALGORITHMS = {
47+
"nearest": Image.NEAREST,
48+
"none": Image.NEAREST,
49+
"lanczos": Image.LANCZOS,
50+
"antialias": Image.LANCZOS,
51+
"bilinear": Image.BILINEAR,
52+
"linear": Image.BILINEAR,
53+
"bicubic": Image.BICUBIC,
54+
"cubic": Image.BICUBIC,
55+
"box": Image.BOX,
56+
"hamming": Image.HAMMING,
57+
}
4558

4659
# Geometry: directions
4760
ORIGIN: np.ndarray = np.array((0.0, 0.0, 0.0))

manim/mobject/types/image_mobject.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,16 @@ class AbstractImageMobject(Mobject):
3030
This is a custom parameter of ImageMobject so that rendering a scene with e.g. the ``--quality low`` or ``--quality medium`` flag for faster rendering won't effect the position of the image on the screen.
3131
"""
3232

33-
def __init__(self, scale_to_resolution, pixel_array_dtype="uint8", **kwargs):
33+
def __init__(
34+
self,
35+
scale_to_resolution,
36+
pixel_array_dtype="uint8",
37+
resampling_algorithm=Image.BICUBIC,
38+
**kwargs,
39+
):
3440
self.pixel_array_dtype = pixel_array_dtype
3541
self.scale_to_resolution = scale_to_resolution
42+
self.set_resampling_algorithm(resampling_algorithm)
3643
Mobject.__init__(self, **kwargs)
3744

3845
def get_pixel_array(self):
@@ -42,6 +49,28 @@ def set_color(self):
4249
# Likely to be implemented in subclasses, but no obligation
4350
pass
4451

52+
def set_resampling_algorithm(self, resampling_algorithm):
53+
"""
54+
Sets the interpolation method for upscaling the image. By default the image is interpolated using bicubic algorithm. This method lets you change it.
55+
Interpolation is done internally using Pillow, and the function besides the string constants describing the algorithm accepts the Pillow integer constants.
56+
57+
Parameters
58+
----------
59+
resampling_algorithm : :class:`int`, an integer constant described in the Pillow library, or one from the RESAMPLING_ALGORITHMS global dictionary, under the following keys:
60+
* 'bicubic' or 'cubic'
61+
* 'nearest' or 'none'
62+
* 'box'
63+
* 'bilinear' or 'linear'
64+
* 'hamming'
65+
* 'lanczos' or 'antialias'
66+
"""
67+
if isinstance(resampling_algorithm, int):
68+
self.resampling_algorithm = resampling_algorithm
69+
else:
70+
raise ValueError(
71+
"resampling_algorithm has to be an int, one of the values defined in RESAMPLING_ALGORITHMS or a Pillow resampling filter constant. Available algorithms: 'bicubic', 'nearest', 'box', 'bilinear', 'hamming', 'lanczos'."
72+
)
73+
4574
def reset_points(self):
4675
# Corresponding corners of image are fixed to these 3 points
4776
self.points = np.array(
@@ -83,6 +112,41 @@ def construct(self):
83112
image.height = 7
84113
self.add(image)
85114
115+
116+
Changing interpolation style:
117+
118+
.. manim:: ImageInterpolationEx
119+
:save_last_frame:
120+
121+
class ImageInterpolationEx(Scene):
122+
def construct(self):
123+
img = ImageMobject(np.uint8([[63, 0, 0, 0],
124+
[0, 127, 0, 0],
125+
[0, 0, 191, 0],
126+
[0, 0, 0, 255]
127+
]))
128+
129+
img.height = 2
130+
img1 = img.copy()
131+
img2 = img.copy()
132+
img3 = img.copy()
133+
img4 = img.copy()
134+
img5 = img.copy()
135+
136+
img1.set_resampling_algorithm(RESAMPLING_ALGORITHMS["nearest"])
137+
img2.set_resampling_algorithm(RESAMPLING_ALGORITHMS["lanczos"])
138+
img3.set_resampling_algorithm(RESAMPLING_ALGORITHMS["linear"])
139+
img4.set_resampling_algorithm(RESAMPLING_ALGORITHMS["cubic"])
140+
img5.set_resampling_algorithm(RESAMPLING_ALGORITHMS["box"])
141+
img1.add(Text("nearest").scale(0.5).next_to(img1,UP))
142+
img2.add(Text("lanczos").scale(0.5).next_to(img2,UP))
143+
img3.add(Text("linear").scale(0.5).next_to(img3,UP))
144+
img4.add(Text("cubic").scale(0.5).next_to(img4,UP))
145+
img5.add(Text("box").scale(0.5).next_to(img5,UP))
146+
147+
x= Group(img1,img2,img3,img4,img5)
148+
x.arrange()
149+
self.add(x)
86150
"""
87151

88152
def __init__(
Binary file not shown.

tests/test_graphical_units/test_img_and_svg.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,30 @@ def construct(self):
232232
self.wait(1)
233233

234234

235+
class ImageInterpolationTest(Scene):
236+
def construct(self):
237+
img = ImageMobject(
238+
np.uint8([[63, 0, 0, 0], [0, 127, 0, 0], [0, 0, 191, 0], [0, 0, 0, 255]])
239+
)
240+
241+
img.height = 2
242+
img1 = img.copy()
243+
img2 = img.copy()
244+
img3 = img.copy()
245+
img4 = img.copy()
246+
img5 = img.copy()
247+
248+
img1.set_resampling_algorithm(RESAMPLING_ALGORITHMS["nearest"])
249+
img2.set_resampling_algorithm(RESAMPLING_ALGORITHMS["lanczos"])
250+
img3.set_resampling_algorithm(RESAMPLING_ALGORITHMS["linear"])
251+
img4.set_resampling_algorithm(RESAMPLING_ALGORITHMS["cubic"])
252+
img5.set_resampling_algorithm(RESAMPLING_ALGORITHMS["box"])
253+
254+
self.add(img1, img2, img3, img4, img5)
255+
[s.shift(4 * LEFT + pos * 2 * RIGHT) for pos, s in enumerate(self.mobjects)]
256+
self.wait()
257+
258+
235259
MODULE_NAME = "img_and_svg"
236260

237261

0 commit comments

Comments
 (0)