Fix PillowWriter GIF frame skew: frame_size must match the rendered RGBA buffer - #32189
Fix PillowWriter GIF frame skew: frame_size must match the rendered RGBA buffer#32189fudianchn wants to merge 1 commit into
Conversation
AbstractMovieWriter.frame_size computed frame dimensions with plain int(w * dpi), which truncates, while the RGBA buffer PillowWriter grabs via savefig(format="rgba") is sized by FigureCanvasBase.get_width_height, which rounds up when within 1e-8 px of the integer (since matplotlib#32038). For figure sizes where w * dpi lands just below an integer (e.g. 8.2 in at 100 dpi = 819.9999...), frame_size was 819 while the buffer was 820, so PIL.Image.frombuffer misread the rows and skewed the GIF frames. Use the same 1e-8 rounding as get_width_height so frame_size always matches the rendered buffer. Closes matplotlib#32186
|
Thank you for opening your first PR into Matplotlib! If you have not heard from us in a week or so, please leave a new comment below and that should bring it to our attention. Most of our reviewers are volunteers and sometimes things fall through the cracks. We also ask that you please finish addressing any review comments on this PR and wait for it to be merged (or closed) before opening a new one, as it can be a valuable learning experience to go through the review process. You can also join us on discourse chat for real-time discussion. For details on testing, writing docs, and our review process, please see the developer guide. We strive to be a welcoming and open project. Please follow our Code of Conduct. |
|
This PR shows indications of having been generated by AI. Please check it against our policy and, if it complies, update the PR summary to use our template. |
|
⏰ This pull request might be automatically closed in two weeks from now. Thank you for your contribution to Matplotlib and for the effort you have put into this PR. This pull request does not yet meet the quality and clarity standards needed for an effective review. Project maintainers have limited time for code reviews, and our goal is to prioritize well-prepared contributions to keep Matplotlib maintainable. Matplotlib maintainers cannot provide one-to-one guidance on this PR. However, if you ask focused, well-researched questions, a community member may be willing to help. 💬 To increase the chance of a productive review:
As the author, you are responsible for driving this PR, which entails doing necessary background research as well as presenting its context and your thought process. If you are a new contributor, or do not know how to fulfill these requirements, we recommend that you familiarize yourself with Matplotlib's development conventions or engage with the community via our Discourse or one of our meetings before submitting code. If you substantially improve this PR within two weeks, leave a comment and a team member may remove the |
|
Hi @rcomer, thanks for the pointer. To be transparent: I used AI assistance (Claude) when implementing the fix, and for translating and polishing my comments here into English (I'm not a native English speaker). Reading the policy, I realize my broader pattern of contributing across many projects at the same time falls under "increasing breadth of contributions," which the policy specifically asks against. To be honest about the why: I subscribe to two 20× plans — Claude Code and Codex — because one on its own isn't enough for me, but two together is more than I can always fully use, so rather than let the surplus go to waste I wanted to put it toward community contributions. I work as a full-stack developer, so these technologies do fall within my stack, and I personally reviewed and tested each contribution — but that's still no excuse for spreading myself so thin and adding to your reviewer load. I'm sorry for the noise, and I'll close this PR. For #32186, the fix is a one-liner — Thanks for the clear feedback. |
Even though your suggested fix works, it's not the "best" way to fix it because |
Problem
Since 3.11.1, GIF frames written by
animation.PillowWriterare skewed to the left (e.g. figsize(8.2, 7)at 100 dpi). #32186AbstractMovieWriter.frame_sizecomputes the frame dimensions with plainint(w * dpi)(truncation), but the RGBA bufferPillowWriter.grab_framegrabs viasavefig(format="rgba")is sized byFigureCanvasBase.get_width_height, which—since #32038—rounds up when the value is within 1e-8 px of the integer:For
8.2 in * 100 dpi = 819.9999999999999,frame_sizetruncates to 819 while the buffer is 820.PIL.Image.frombuffer("RGBA", (819, 700), buf, …)then reads the 820-wide rows as 819-wide and the image is skewed.Fix
Apply the same
+ 1e-8roundingget_width_height(physical=True)uses, soframe_sizealways matches the buffer that gets grabbed. This is the “missed point to pass the corrected size through” noted in the bisect comment on the issue.frame_sizelives onAbstractMovieWriter, so every writer (ffmpeg/imagemagick via-s %dx%d, etc.) now also reports the corrected size consistently with the frames it writes.Verification
Verified fail-before / pass-after on matplotlib 3.11.1:
The rounding only changes the result when
w * dpiis within 1e-8 below an integer (the bug case); for typical sizes (6@100,8@72,10.5@100,5@100,4.8@100) the value is identical, so there is no behavior change for non-affected figures.Added a regression test asserting
frame_sizeequals the width of the RGBA buffer produced bysavefig(format="rgba").Closes #32186