Incorrect VP8X Canvas Dimensions in Animated WebP Files
Summary
Piexif incorrectly sets the VP8X canvas dimensions for certain animated WebP files, leading to a "Frame exceeds canvas" error when frames exceed the dimensions of the last frame.
Steps to Reproduce:
- Process an animated WebP file using Piexif where the frames vary in size.
- Ensure that the last frame is smaller than some of the previous frames.
- Run a tool like
webpinfoon the output file to observe the error.
This can be reproduced with the pil_animated2.webp file in my fork: https://github.com/hMatoba/Piexif/commit/5a12974343580d5f6fbbed74966bc56f2859ffe9
Input Test File
→ webpinfo -diag tests/images/pil_animated2.webp
File: tests/images/pil_animated2.webp
RIFF HEADER:
File size: 37342
Chunk VP8X at offset 12, length 18
ICCP: 0
Alpha: 1
EXIF: 0
XMP: 0
Animation: 1
Canvas size 400 x 400
Chunk ANIM at offset 30, length 14
Background color:(ARGB) ff 00 00 00
Loop count : 0
Chunk ANMF at offset 44, length 5190
Offset_X: 0
Offset_Y: 0
Width: 400
Height: 400
Duration: 70
Dispose: 0
Blend: 1
Chunk VP8L at offset 68, length 5166
Width: 400
Height: 400
Alpha: 0
Animation: 0
Format: Lossless (2)
...
Chunk ANMF at offset 34824, length 2518
Offset_X: 0
Offset_Y: 6
Width: 320
Height: 382
Duration: 70
Dispose: 0
Blend: 0
Chunk VP8L at offset 34848, length 2494
Width: 320
Height: 382
Alpha: 1
Animation: 0
Format: Lossless (2)
No error detected.
Output Test File
→ webpinfo -diag tests/images/out/i_pil_animated2.webp
File: tests/images/out/i_pil_animated2.webp
RIFF HEADER:
File size: 37400
Chunk VP8X at offset 12, length 18
ICCP: 0
Alpha: 0
EXIF: 1
XMP: 0
Animation: 1
Canvas size 320 x 382
Chunk EXIF at offset 30, length 58
Chunk ANIM at offset 88, length 14
Background color:(ARGB) ff 00 00 00
Loop count : 0
Chunk ANMF at offset 102, length 5190
Offset_X: 0
Offset_Y: 0
Width: 400
Height: 400
Duration: 70
Dispose: 0
Blend: 1
Error: Frame exceeds canvas in ANMF chunk.
Errors detected.
Expected Behavior:
The VP8X chunk should reflect the maximum dimensions across all frames to correctly represent the entire canvas, preventing any "Frame exceeds canvas" errors.
Actual Behavior:
The VP8X chunk dimensions are set based on the last frame processed. If this frame is smaller than earlier frames, the overall canvas size is incorrect, leading to errors.
This issue affects users processing animated WebP files with frames of varying sizes. Ensuring the correct canvas size in the VP8X chunk is critical for maintaining compatibility and avoiding errors in tools that validate WebP file structure.
Technical Details
The issue arises because the set_vp8x function in the piexif library sets the width and height based on the last frame it processes. This approach fails to account for previous frames that may be larger, resulting in an incorrect canvas size.
WebP Image (OK)
For non-animated WebP files, the VP8X chunk correctly represents the image dimensions:
+--------------------------+
| WebP Container |
| |
| +--------------------+ |
| | VP8X Chunk | | <-- Flags and size info
| +--------------------+ |
| |
| +--------------------+ |
| | VP8L Chunk | | <-- Image data (Lossless)
| +--------------------+ |
| |
+--------------------------+
Animated WebP (Broken)
For animated WebP files, the VP8X chunk incorrectly sets the canvas dimensions based on the last frame processed, even if earlier frames are larger:
+--------------------------+
| WebP Container |
| |
| +--------------------+ |
| | VP8X Chunk | | <-- Flags and canvas size info (incorrectly set)
| +--------------------+ |
| |
| +--------------------+ |
| | ANIM Chunk | | <-- Animation control info
| +--------------------+ |
| |
| +--------------------+ |
| | ANMF Chunk | | <-- Frame 1 (larger than last frame)
| | +--------------+ | |
| | | VP8L Chunk | | | <-- Image data
| +--------------------+ |
| |
| +--------------------+ |
| | ANMF Chunk | | <-- Frame 2 (smaller, last frame processed)
| | +--------------+ | |
| | | VP8L Chunk | | | <-- Image data
| +--------------------+ |
| |
+--------------------------+
Proposal:
Modify the set_vp8x function to track the maximum width and height across all frames. The VP8X chunk should be updated to reflect these maximum dimensions, ensuring that the canvas size accommodates all frames.
I have a commit in my fork that appears to resolve this: https://github.com/hMatoba/Piexif/commit/7c6522a312b0c95553ec19254ea88ed33efe12f3
Modify the set_vp8x function to track the maximum width and height across all frames.
That isn't necessarily enough. Some webp files have a canvas that's larger than all frames.
Here's a file where the canvas is 16x16 but the two animation frames are 12x10 and 8x2 respectively:
% ~/Downloads/libwebp-1.4.0-rc1-mac-arm64/bin/webpinfo ~/Downloads/test.webp
File: /Users/thakis/Downloads/test.webp
RIFF HEADER:
File size: 154
Chunk VP8X at offset 12, length 18
ICCP: 0
Alpha: 1
EXIF: 0
XMP: 0
Animation: 1
Canvas size 16 x 16
Chunk ANIM at offset 30, length 14
Background color:(ARGB) ff ff ff ff
Loop count : 0
Chunk ANMF at offset 44, length 64
Offset_X: 2
Offset_Y: 4
Width: 12
Height: 10
Duration: 100
Dispose: 0
Blend: 1
Chunk VP8L at offset 68, length 40
Width: 12
Height: 10
Alpha: 1
Animation: 0
Format: Lossless (2)
Chunk ANMF at offset 108, length 46
Offset_X: 4
Offset_Y: 4
Width: 8
Height: 2
Duration: 100
Dispose: 0
Blend: 1
Chunk VP8L at offset 132, length 22
Width: 8
Height: 2
Alpha: 1
Animation: 0
Format: Lossless (2)
No error detected.
(This images was drawn by myself and saved using aseprite. Feel free to use it in tests, I hereby put it in the public domain.)
That isn't necessarily enough. Some webp files have a canvas that's larger than all frames.
Here's a file where the canvas is 16x16 but the two animation frames are 12x10 and 8x2 respectively:
@nico Thanks for the follow-up and that interesting test case! It's right to include examples of WebPs with canvases bigger than their frames - definitely adds another layer to think about.
I've taken a good look at your test fix, and I think it's actually handling these cases pretty well for spec-compliant WebPs. Here's the gist:
- The code checks all chunks (VP8X, VP8, VP8L, ANMF) and finds the biggest dimensions.
- If there's already a VP8X chunk (like in your test.webp), it considers those dimensions too.
- It then creates a new VP8X chunk using the largest size it found, so all frames should fit.
For your 16x16 canvas example with smaller frames:
- If there's a VP8X chunk saying it's 16x16, we'll use that size since it's bigger than the frames.
- If there's no VP8X chunk, we'll use the size of the largest frame, which works too.
I've thrown a bunch of different WebPs at it, including some funky animated ones, and it seems to be handling them fine. That said, if you've got any other tricky WebPs or a nuance to this case that I've missed, please let me know.
Thanks again for diving into this!