|
4 | 4 | from io import BytesIO |
5 | 5 | from typing import IO |
6 | 6 |
|
7 | | -from . import ExifTags, Image, ImageFile |
| 7 | +from . import ExifTags, Image, ImageFile, ImageSequence |
8 | 8 |
|
9 | 9 | try: |
10 | 10 | from . import _avif |
@@ -153,17 +153,20 @@ def _save( |
153 | 153 | else: |
154 | 154 | append_images = [] |
155 | 155 |
|
156 | | - total = 0 |
157 | | - for ims in [im] + append_images: |
158 | | - total += getattr(ims, "n_frames", 1) |
| 156 | + grayscale_modes = {"1", "L", "I", "I;16", "I;16L", "I;16B", "I;16N", "F"} |
| 157 | + grayscale = all( |
| 158 | + frame.mode in grayscale_modes |
| 159 | + for ims in [im] + append_images |
| 160 | + for frame in ImageSequence.Iterator(ims) |
| 161 | + ) |
159 | 162 |
|
160 | 163 | quality = info.get("quality", 75) |
161 | 164 | if not isinstance(quality, int) or quality < 0 or quality > 100: |
162 | 165 | msg = "Invalid quality setting" |
163 | 166 | raise ValueError(msg) |
164 | 167 |
|
165 | 168 | duration = info.get("duration", 0) |
166 | | - subsampling = info.get("subsampling", "4:2:0") |
| 169 | + subsampling = info.get("subsampling", "4:0:0" if grayscale else "4:2:0") |
167 | 170 | speed = info.get("speed", 6) |
168 | 171 | max_threads = info.get("max_threads", _get_default_max_threads()) |
169 | 172 | codec = info.get("codec", "auto") |
@@ -236,21 +239,20 @@ def _save( |
236 | 239 | frame_idx = 0 |
237 | 240 | frame_duration = 0 |
238 | 241 | cur_idx = im.tell() |
239 | | - is_single_frame = total == 1 |
| 242 | + is_single_frame = not append_images and not getattr(im, "is_animated", False) |
240 | 243 | try: |
241 | 244 | for ims in [im] + append_images: |
242 | | - # Get number of frames in this image |
243 | | - nfr = getattr(ims, "n_frames", 1) |
244 | | - |
245 | | - for idx in range(nfr): |
246 | | - ims.seek(idx) |
247 | | - |
| 245 | + for frame in ImageSequence.Iterator(ims): |
248 | 246 | # Make sure image mode is supported |
249 | | - frame = ims |
250 | | - rawmode = ims.mode |
251 | | - if ims.mode not in {"RGB", "RGBA"}: |
252 | | - rawmode = "RGBA" if ims.has_transparency_data else "RGB" |
253 | | - frame = ims.convert(rawmode) |
| 247 | + rawmode = frame.mode |
| 248 | + if ims.mode not in {"L", "RGB", "RGBA"}: |
| 249 | + if ims.has_transparency_data: |
| 250 | + rawmode = "RGBA" |
| 251 | + elif ims.mode in grayscale_modes: |
| 252 | + rawmode = "L" |
| 253 | + else: |
| 254 | + rawmode = "RGB" |
| 255 | + frame = frame.convert(rawmode) |
254 | 256 |
|
255 | 257 | # Update frame duration |
256 | 258 | if isinstance(duration, (list, tuple)): |
|
0 commit comments