diff --git a/tests/test_webp.py b/tests/test_webp.py index f623955..b1253eb 100644 --- a/tests/test_webp.py +++ b/tests/test_webp.py @@ -105,6 +105,32 @@ def test_anim_simple(self): expected = np.asarray(img, dtype=np.uint8) assert_array_equal(actual, expected) + def test_anim_loop_count(self): + imgs = [] + width = 256 + height = 64 + for i in range(4): + img = Image.new('RGBA', (width, height)) + draw = ImageDraw.Draw(img) + draw.rectangle((0, 0, width-1, height-1), fill=(0, 0, 255)) + x = i * (width/4) + draw.rectangle((x, 0, x + (width/4-1), height-1), fill=(255, 0, 0)) + imgs.append(img) + + with TemporaryDirectory() as tmpdir: + file_name = os.path.join(tmpdir, 'anim.webp') + + webp.save_images(imgs, file_name, fps=4, loop_count=2, lossless=True) + + with open(file_name, 'rb') as f: + webp_data = webp.WebPData.from_buffer(f.read()) + dec_opts = webp.WebPAnimDecoderOptions.new( + use_threads=True, color_mode=webp.WebPColorMode.RGBA) + dec = webp.WebPAnimDecoder.new(webp_data, dec_opts) + assert dec.anim_info.loop_count == 2 + assert dec.anim_info.width == width + assert dec.anim_info.height == height + # WebP combines adjacent duplicate frames and adjusts timestamps # accordingly, resulting in unevenly spaced frames. By specifying the fps # while loading we can return evenly spaced frames. diff --git a/webp/__init__.py b/webp/__init__.py index f7ed52a..1f4c850 100644 --- a/webp/__init__.py +++ b/webp/__init__.py @@ -360,6 +360,14 @@ class WebPAnimEncoderOptions: def __init__(self, ptr: Any) -> None: self.ptr = ptr + @property + def loop_count(self) -> int: + return self.ptr.anim_params.loop_count + + @loop_count.setter + def loop_count(self, loop_count: int) -> None: + self.ptr.anim_params.loop_count = loop_count + @property def minimize_size(self) -> bool: return self.ptr.minimize_size != 0 @@ -475,6 +483,10 @@ def width(self) -> int: def height(self) -> int: return self.ptr.canvas_height + @property + def loop_count(self) -> int: + return self.ptr.loop_count + @staticmethod def new() -> "WebPAnimInfo": ptr = ffi.new('WebPAnimInfo*') @@ -582,9 +594,12 @@ def _mimwrite_pics( pics: List[WebPPicture], *args: Any, fps: float = 30.0, + loop_count: Optional[int] = None, **kwargs: Any ) -> None: enc_opts = WebPAnimEncoderOptions.new() + if loop_count is not None: + enc_opts.loop_count = loop_count enc = WebPAnimEncoder.new(pics[0].ptr.width, pics[0].ptr.height, enc_opts) config = WebPConfig.new(**kwargs) for i, pic in enumerate(pics): @@ -602,6 +617,7 @@ def mimwrite( arrs: "List[np.ndarray[Any, np.dtype[np.uint8]]]", *args: Any, fps: float = 30.0, + loop_count: Optional[int] = None, pilmode: Optional[str] = None, **kwargs: Any) -> None: """Encode a sequence of PIL Images with WebP and save to file. @@ -610,10 +626,14 @@ def mimwrite( file_path (str): File to save to. imgs (list of np.ndarray): Image data to save. fps (float): Animation speed in frames per second. + loop_count (int, optional): Number of times to repeat the animation. + 0 = infinite. + pilmode (str, optional): Image color mode (RGBA or RGB). Will be + inferred from the images if not specified. kwargs: Keyword arguments for encoder settings (see `WebPConfig.new`). """ pics = [WebPPicture.from_numpy(arr, pilmode=pilmode) for arr in arrs] - _mimwrite_pics(file_path, pics, fps=fps, **kwargs) + _mimwrite_pics(file_path, pics, fps=fps, loop_count=loop_count, **kwargs) def mimread( diff --git a/webp_build/cdef.h b/webp_build/cdef.h index 2038e11..bd31f0f 100644 --- a/webp_build/cdef.h +++ b/webp_build/cdef.h @@ -142,7 +142,14 @@ struct WebPMemoryWriter { }; typedef struct WebPMemoryWriter WebPMemoryWriter; +struct WebPMuxAnimParams { + uint32_t bgcolor; + int loop_count; +}; +typedef struct WebPMuxAnimParams WebPMuxAnimParams; + struct WebPAnimEncoderOptions { + WebPMuxAnimParams anim_params; int minimize_size; int kmin; int kmax;