Skip to content

Commit

Permalink
Fix docstring and lazy load pillow
Browse files Browse the repository at this point in the history
  • Loading branch information
laggykiller committed Feb 26, 2024
1 parent c8ac128 commit 51f8e7c
Showing 1 changed file with 154 additions and 162 deletions.
316 changes: 154 additions & 162 deletions rlottie_python/rlottie_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,10 @@
import sys
import ctypes
import gzip
from typing import Tuple, Optional
from typing import Tuple, Optional, TYPE_CHECKING

try:
if TYPE_CHECKING:
from PIL import Image

PILLOW_LOADED = True
except ModuleNotFoundError:
PILLOW_LOADED = False
from .rlottiecommon import LOTLayerNode, LOTMarkerList

# References: rlottie/inc/rlottie.h
Expand Down Expand Up @@ -626,7 +622,7 @@ def lottie_animation_get_markerlist(self) -> Optional[list]:

try:
return markerlist.contents
except ValueError: # NULL pointer access
except ValueError: # NULL pointer access
return None

def lottie_configure_model_cache_size(self, cache_size: int):
Expand All @@ -649,164 +645,160 @@ def lottie_configure_model_cache_size(self, cache_size: int):
self.rlottie_lib.lottie_configure_model_cache_size.restype = ctypes.c_void_p
self.rlottie_lib.lottie_configure_model_cache_size(ctypes.c_size_t(cache_size))

if PILLOW_LOADED:

def render_pillow_frame(
self,
frame_num: int = 0,
buffer_size: Optional[int] = None,
width: Optional[int] = None,
height: Optional[int] = None,
bytes_per_line: Optional[int] = None,
) -> Image.Image:
"""
Create Pillow Image at frame_num
:param int frame_num: the frame number needs to be rendered.
Defaults to 0.
:param Optional[int] buffer_size: size of surface buffer use for rendering
:param Optional[int] width: width of the surface
:param Optional[int] height: height of the surface
:param Optional[int] bytes_per_line: stride of the surface in bytes.
:return: rendered Pillow Image
:rtype: PIL.Image.Image
"""
if width is None or height is None:
width, height = self.lottie_animation_get_size()

buffer = self.lottie_animation_render(
frame_num=frame_num,
buffer_size=buffer_size,
width=width,
height=height,
bytes_per_line=bytes_per_line,
)
def render_pillow_frame(
self,
frame_num: int = 0,
buffer_size: Optional[int] = None,
width: Optional[int] = None,
height: Optional[int] = None,
bytes_per_line: Optional[int] = None,
) -> Image.Image:
"""
Create Pillow Image at frame_num
:param int frame_num: the frame number needs to be rendered.
Defaults to 0.
:param Optional[int] buffer_size: size of surface buffer use for rendering
:param Optional[int] width: width of the surface
:param Optional[int] height: height of the surface
:param Optional[int] bytes_per_line: stride of the surface in bytes.
:return: rendered Pillow Image
:rtype: PIL.Image.Image
"""
from PIL import Image

im = Image.frombuffer("RGBA", (width, height), buffer, "raw", "BGRA")
if width is None or height is None:
width, height = self.lottie_animation_get_size()

return im
buffer = self.lottie_animation_render(
frame_num=frame_num,
buffer_size=buffer_size,
width=width,
height=height,
bytes_per_line=bytes_per_line,
)

def save_frame(
self,
save_path: str,
frame_num: int = 0,
buffer_size: Optional[int] = None,
width: Optional[int] = None,
height: Optional[int] = None,
bytes_per_line: Optional[int] = None,
*args,
**kwargs,
):
"""
Save Image at frame_num to save_path
:param str save_path: path to save the Pillow Image
:param int frame_num: the frame number needs to be rendered.
Defaults to 0.
:param Optional[int] buffer_size: size of surface buffer use for rendering
:param Optional[int] width: width of the surface
:param Optional[int] height: height of the surface
:param Optional[int] bytes_per_line: stride of the surface in bytes.
:param *args: additional arguments passing to im.save()
:param **kwargs: additional arguments passing to im.save()
:return: rendered Pillow Image
"""
im = self.render_pillow_frame(
frame_num=frame_num,
buffer_size=buffer_size,
width=width,
height=height,
bytes_per_line=bytes_per_line,
im = Image.frombuffer("RGBA", (width, height), buffer, "raw", "BGRA")

return im

def save_frame(
self,
save_path: str,
frame_num: int = 0,
buffer_size: Optional[int] = None,
width: Optional[int] = None,
height: Optional[int] = None,
bytes_per_line: Optional[int] = None,
*args,
**kwargs,
):
"""
Save Image at frame_num to save_path
:param str save_path: path to save the Pillow Image
:param int frame_num: the frame number needs to be rendered.
Defaults to 0.
:param Optional[int] buffer_size: size of surface buffer use for rendering
:param Optional[int] width: width of the surface
:param Optional[int] height: height of the surface
:param Optional[int] bytes_per_line: stride of the surface in bytes.
:param *args: additional arguments passing to im.save()
:param **kwargs: additional arguments passing to im.save()
"""
im = self.render_pillow_frame(
frame_num=frame_num,
buffer_size=buffer_size,
width=width,
height=height,
bytes_per_line=bytes_per_line,
)
im.save(save_path, *args, **kwargs)

def save_animation(
self,
save_path: str,
fps: Optional[int] = None,
frame_num_start: Optional[int] = None,
frame_num_end: Optional[int] = None,
buffer_size: Optional[int] = None,
width: Optional[int] = None,
height: Optional[int] = None,
bytes_per_line: Optional[int] = None,
*args,
**kwargs,
):
"""
Save Image from frame_num_start to frame_num_end and save it to save_path.
It is possible to save animation as apng, gif or webp.
For .gif, maximum framerate is capped at 50.
Users may override this by specifying fps, at risk of breaking their gif.
:param str save_path: Path to save the Pillow Image
:param Optional[int] fps: Set fps of output image.
Will skip frames if lower than original.
:param Optional[int] frame_num_start: the starting frame number
needs to be rendered.
:param Optional[int] frame_num_end: the ending frame number
needs to be rendered.
:param Optional[int] buffer_size: size of surface buffer use for rendering
:param Optional[int] width: width of the surface
:param Optional[int] height: height of the surface
:param Optional[int] bytes_per_line: stride of the surface in bytes.
:param *args: additional arguments passing to im.save()
:param **kwargs: additional arguments passing to im.save()
"""
fps_orig = self.lottie_animation_get_framerate()
duration = self.lottie_animation_get_duration()

export_ext = os.path.splitext(save_path)[-1].lower()

if not fps:
fps = fps_orig

# For .gif, maximum framerate is capped at 50
# Users may override this by specifying fps, at risk of breaking their gif
# Reference: https://wunkolo.github.io/post/2020/02/buttery-smooth-10fps/
if export_ext == ".gif" and fps_orig > 50:
fps = 50

if export_ext == ".gif" and kwargs.get("disposal") is None:
kwargs["disposal"] = 2

if kwargs.get("loop") is None:
kwargs["loop"] = 0

frames = int(duration * fps)
frame_duration = 1000 / fps

if frame_num_start is None:
frame_num_start = 0
if frame_num_end is None:
frame_num_end = frames

im_list = []
for frame in range(frame_num_start, frame_num_end):
pos = frame / frame_num_end
frame_num = self.lottie_animation_get_frame_at_pos(pos)
im_list.append(
self.render_pillow_frame(
frame_num=frame_num,
buffer_size=buffer_size,
width=width,
height=height,
bytes_per_line=bytes_per_line,
).copy()
)
im.save(save_path, *args, **kwargs)

def save_animation(
self,
save_path: str,
fps: Optional[int] = None,
frame_num_start: Optional[int] = None,
frame_num_end: Optional[int] = None,
buffer_size: Optional[int] = None,
width: Optional[int] = None,
height: Optional[int] = None,
bytes_per_line: Optional[int] = None,

im_list[0].save(
save_path,
save_all=True,
append_images=im_list[1:],
duration=int(frame_duration),
*args,
**kwargs,
):
"""
Save Image from frame_num_start to frame_num_end and save it to save_path.
It is possible to save animation as apng, gif or webp.
For .gif, maximum framerate is capped at 50.
Users may override this by specifying fps, at risk of breaking their gif.
:param str save_path: Path to save the Pillow Image
:param Optional[int] fps: Set fps of output image.
Will skip frames if lower than original.
:param Optional[int] frame_num_start: the starting frame number
needs to be rendered.
:param Optional[int] frame_num_end: the ending frame number
needs to be rendered.
:param Optional[int] buffer_size: size of surface buffer use for rendering
:param Optional[int] width: width of the surface
:param Optional[int] height: height of the surface
:param Optional[int] bytes_per_line: stride of the surface in bytes.
:param *args: additional arguments passing to im.save()
:param **kwargs: additional arguments passing to im.save()
:return: rendered Pillow Image
"""
fps_orig = self.lottie_animation_get_framerate()
duration = self.lottie_animation_get_duration()

export_ext = os.path.splitext(save_path)[-1].lower()

if not fps:
fps = fps_orig

# For .gif, maximum framerate is capped at 50
# Users may override this by specifying fps, at risk of breaking their gif
# Reference: https://wunkolo.github.io/post/2020/02/buttery-smooth-10fps/
if export_ext == ".gif" and fps_orig > 50:
fps = 50

if export_ext == ".gif" and kwargs.get("disposal") is None:
kwargs["disposal"] = 2

if kwargs.get("loop") is None:
kwargs["loop"] = 0

frames = int(duration * fps)
frame_duration = 1000 / fps

if frame_num_start is None:
frame_num_start = 0
if frame_num_end is None:
frame_num_end = frames

im_list = []
for frame in range(frame_num_start, frame_num_end):
pos = frame / frame_num_end
frame_num = self.lottie_animation_get_frame_at_pos(pos)
im_list.append(
self.render_pillow_frame(
frame_num=frame_num,
buffer_size=buffer_size,
width=width,
height=height,
bytes_per_line=bytes_per_line,
).copy()
)

im_list[0].save(
save_path,
save_all=True,
append_images=im_list[1:],
duration=int(frame_duration),
*args,
**kwargs,
)
)

0 comments on commit 51f8e7c

Please sign in to comment.