Coverage for lib/lottie/exporters/gif.py: 5%
68 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-20 16:17 +0100
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-20 16:17 +0100
1import io
2from PIL import Image
3from PIL import features
5from .cairo import PngRenderer
6from .base import exporter, io_progress
7from ..parsers.baseporter import ExtraOption
10def _png_gif_prepare(image):
11 if image.mode not in ["RGBA", "RGBa"]:
12 image = image.convert("RGBA")
13 alpha = image.getchannel("A")
14 image = image.convert("RGB").convert('P', palette=Image.ADAPTIVE, colors=255)
15 mask = Image.eval(alpha, lambda a: 255 if a <= 128 else 0)
16 image.paste(255, mask=mask)
17 return image
20def _log_frame(fmt, frame_no=None, end=None):
21 if frame_no is None:
22 io_progress().report_message("%s frame rendering completed" % (fmt))
23 else:
24 io_progress().report_progress("%s rendering frame" % fmt, frame_no, end)
27@exporter("GIF", ["gif"], [
28 ExtraOption("skip_frames", type=int, default=1, help="Only renderer 1 out of these many frames"),
29])
30def export_gif(animation, fp, dpi=96, skip_frames=1):
31 """
32 Gif export
34 Note that it's a bit slow.
35 """
36 start = int(animation.in_point)
37 end = int(animation.out_point)
38 frames = []
39 with PngRenderer(animation, dpi) as renderer:
40 for i in range(start, end+1, skip_frames):
41 _log_frame("GIF", i, end)
42 file = io.BytesIO()
43 renderer.serialize(i, file)
44 file.seek(0)
45 frames.append(_png_gif_prepare(Image.open(file)))
46 _log_frame("GIF")
48 io_progress().report_message("GIF Writing to file...")
49 duration = int(round(1000 / animation.frame_rate * skip_frames / 10)) * 10
50 frames[0].save(
51 fp,
52 format='GIF',
53 append_images=frames[1:],
54 save_all=True,
55 duration=duration,
56 loop=0,
57 transparency=255,
58 disposal=2,
59 )
62@exporter("WebP", ["webp"], [
63 ExtraOption("lossless", action="store_true", help="If present, use lossless compression"),
64 ExtraOption("quality", type=int, default=80,
65 help="Compression effort between 0 and 100\n" +
66 "for lossy 0 gives the smallest size\n" +
67 "for lossless 0 gives the largest file"),
68 ExtraOption("method", type=int, default=0, help="Quality/speed trade-off (0=fast, 6=slower-better)"),
69 ExtraOption("skip_frames", type=int, default=1, help="Only renderer 1 out of these many frames"),
70])
71def export_webp(animation, fp, dpi=96, lossless=False, quality=80, method=0, skip_frames=1):
72 """
73 Export WebP
75 See https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#webp
76 """
77 if not features.check("webp_anim"):
78 raise Exception("WebP animations not supported in this system")
80 start = int(animation.in_point)
81 end = int(animation.out_point)
82 frames = []
83 with PngRenderer(animation, dpi) as renderer:
84 for i in range(start, end+1, skip_frames):
85 _log_frame("WebP", i, end)
86 file = io.BytesIO()
87 renderer.serialize(i, file)
88 file.seek(0)
89 frames.append(Image.open(file))
91 _log_frame("WebP")
93 io_progress().report_message("WebP Writing to file...")
94 duration = int(round(1000 / animation.frame_rate * skip_frames))
95 frames[0].save(
96 fp,
97 format='WebP',
98 append_images=frames[1:],
99 save_all=True,
100 duration=duration,
101 loop=0,
102 background=(0, 0, 0, 0),
103 lossless=lossless,
104 quality=quality,
105 method=method
106 )
109@exporter("TIFF", ["tiff"])
110def export_tiff(animation, fp, dpi=96):
111 """
112 Export TIFF
113 """
114 start = int(animation.in_point)
115 end = int(animation.out_point)
116 frames = []
117 with PngRenderer(animation, dpi) as renderer:
118 for i in range(start, end+1):
119 _log_frame("TIFF", i, end)
120 file = io.BytesIO()
121 renderer.serialize(i, file)
122 file.seek(0)
123 frames.append(Image.open(file))
124 _log_frame("TIFF")
126 io_progress().report_message("TIFF Writing to file...")
127 duration = int(round(1000 / animation.frame_rate))
128 frames[0].save(
129 fp,
130 format='TIFF',
131 append_images=frames[1:],
132 save_all=True,
133 duration=duration,
134 loop=0,
135 dpi=(dpi, dpi),
136 )