Repository: image-rs/image (language: rust)

Issue:
Animated WebP's cannot actually be loaded
`WebPDecoder` fails to decode WebP animations.

## Expected

It should probably work?

## Actual behaviour

- One file (`piston.webp` from the zip archive below) that works fine in Firefox and Chromium results in `UnexpectedEof` ("failed to fill whole buffer")
- Files from the `image-webp` test suite fail with "Format error decoding WebP: No more frames: No more frames"
  - https://github.com/image-rs/image-webp/tree/768b16b70b5a1d1315dda29ccc3777761a9b5a31/tests/images/animated

## Reproduction steps

`WebPDecoder::new(BufReader::new(File::open(path)?))?.into_frames().collect_frames()?`

[piston.zip](https://github.com/user-attachments/files/15927745/piston.zip)


Discussion / hints:
I believe this to be a regression introduced in `0.25`.

My [code using `0.24`](https://github.com/porkbrain/bevy-webp-anim/blob/main/README.md#example) plays the image fine (also tested using this repo's `tests/images/webp/extended_images/anim.webp`), albeit the [`has_animation`](https://docs.rs/image/0.24.9/image/codecs/webp/struct.WebPDecoder.html#method.has_animation) method returns `false`.

After [update to `0.25`](https://github.com/porkbrain/bevy-webp-anim/pull/1/files) (wrapping the bytes in `Cursor` instead of passing them as `&[u8]`) I cannot play my test webp nor the one I mentioned above. As mentioned in the issue description, `collect_frames` runs into
> Format error decoding WebP: No more frames

Although ironically, after the update, the `has_animation` method now returns correctly `true`.
~[This commit](https://github.com/image-rs/image/commit/fe7f010a81ffbe63b5532e8b990c11fd85c37e4f) breaks the webp animation loader.~

EDIT: That's impossible, probably some caching issue when I was bisecting the culprit commit. Going to try again
This issue is actually quite straightforward. The iterator now returns an error if there are no more frames instead of `None`.

```rust
decoder
            .into_frames()
            .take_while(|frame| frame.is_ok())
            .collect()
```

Collecting the frames manually this way works. I will send an MR with a suggested fix as I believe `NoMoreFrames` should be caught in the iterator and converted to `None`.

Fix patch:
diff --git a/src/codecs/webp/decoder.rs b/src/codecs/webp/decoder.rs
index 584f20ac2f..a9d7b4b098 100644
--- a/src/codecs/webp/decoder.rs
+++ b/src/codecs/webp/decoder.rs
@@ -84,12 +84,14 @@ impl<'a, R: 'a + Read + Seek> AnimationDecoder<'a> for WebPDecoder<R> {
                     let mut img = RgbaImage::new(width, height);
                     match self.decoder.inner.read_frame(&mut img) {
                         Ok(delay) => (img, delay),
+                        Err(image_webp::DecodingError::NoMoreFrames) => return None,
                         Err(e) => return Some(Err(ImageError::from_webp_decode(e))),
                     }
                 } else {
                     let mut img = RgbImage::new(width, height);
                     match self.decoder.inner.read_frame(&mut img) {
                         Ok(delay) => (img.convert(), delay),
+                        Err(image_webp::DecodingError::NoMoreFrames) => return None,
                         Err(e) => return Some(Err(ImageError::from_webp_decode(e))),
                     }
                 };


Test patch:
diff --git a/tests/regression.rs b/tests/regression.rs
index 78ff0321e5..eb1e20f518 100644
--- a/tests/regression.rs
+++ b/tests/regression.rs
@@ -54,7 +54,7 @@ fn check_webp_frames_regressions() {
         .join("*.webp");
     let pattern = &*format!("{}", path.display());
     for path in glob::glob(pattern).unwrap().filter_map(Result::ok) {
-        let bytes = fs::read(path).unwrap();
+        let bytes = fs::read(&path).unwrap();
         let cursor = Cursor::new(&bytes);
         let frame_count = image_webp::WebPDecoder::new(cursor.clone())
             .unwrap()
@@ -62,8 +62,18 @@ fn check_webp_frames_regressions() {
         let decoder = WebPDecoder::new(cursor).unwrap();
         // The `take` guards against a potentially infinitely running iterator.
         // Since we take `frame_count + 1`, we can assume that the last iteration already returns `None`.
-        let actual_frame_count = decoder.into_frames().take(frame_count + 1).count();
-        assert_eq!(actual_frame_count, frame_count);
+        // We then check that each frame has been decoded successfully.
+        let decoded_frames_count = decoder
+            .into_frames()
+            .take(frame_count + 1)
+            .enumerate()
+            .inspect(|(frame_index, frame_res)| {
+                if let Err(e) = frame_res {
+                    panic!("Error decoding {path:?} frame {}: {e:?}", frame_index + 1);
+                }
+            })
+            .count();
+        assert_eq!(frame_count, decoded_frames_count);
     }
 }