Implement a consistent behavior in TkAgg backend for bad blit bbox #20887
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
PR Summary
This PR seeks to make the failure modes more consistent and manageable when blitting on the
TkAgg
backend. Draft for now because I don't think it is release critical and some feedback is needed on design choices.Background
The thread safety changes of #18565 interact a bit awkwardly with the memory safety changes of #14478, namely when
_tkagg.blit
raises, it pops directly out of the tkintermainloop
call and poisons the current instance of tkapp so that pumping the event loop always raises theValueError
forever. This isn't a big deal for bad values ofcomp_rule
orphotoimage
since those are matplotlib's responsibilities, however as a user-facing API this prevents a client application from gracefully handling bad bboxes; they are basically fatal.Why, if this is so bad, did nobody notice? In #14461 the bbox limits are clipped to a mostly-safe range, so only very bad bboxes that fall entirely outside the canvas trigger the issue. (And only my app is crazy enough to generate such bboxes 🤦)
Proposed fix(es)
Extend safe-clipping
The initial commit in this PR makes the most minimal possible change to existing behavior, extending the safe-clipping to boxes outside the canvas; they are simply dropped, and with a fast path optimization to boot. This way if
ValueErrors
are generated, they are 100% our fault. Theif
expression comes from the check in_tkagg.cpp
but stripping the rules that are redundant to the clipping logic. The disadvantage is that the only way a user knows that their bbox is being clipped is by weird visual artifacts.Raise aggressively
An alternative fix is to promote the
_tkagg.cpp
check into the first lines of_backend_tk.blit
:This
raise
is now catchable and the check inside_tkagg.blit
is redundant (though it is super cheap so it should remain in both places). Adding tests for this would finally cover this branch of the if statement. Also ALL bad bbox blits are errors now.I see two disadvantages to this alternative. The integer conversion has to be different (truncated) so that full-canvas blits are not liable to pick up an extra pixel and error out, however this floor rounding behavior is consistent with other backends. Some user code that worked by silently clipping will now raise.
Open questions
Should we consider this alternative option as exposing latent bugs that should have been caught before, or as making
blit
"less robust"? Should we reconsider the current rounding/clipping rules? How strict are other backends to weird bboxes and can we make things consistent across all of them?PR Checklist
pytest
passes).flake8
on changed files to check).flake8-docstrings
and runflake8 --docstring-convention=all
).doc/users/next_whats_new/
(follow instructions in README.rst there).doc/api/next_api_changes/
(follow instructions in README.rst there).