1
+ import uuid
1
2
from contextlib import contextmanager
2
3
import logging
3
4
import math
@@ -44,6 +45,28 @@ def _restore_foreground_window_at_end():
44
45
_c_internal_utils .Win32_SetForegroundWindow (foreground )
45
46
46
47
48
+ _blit_args = {}
49
+ # Initialize to a non-empty string that is not a Tcl command
50
+ _blit_tcl_name = "mpl_blit_" + uuid .uuid4 ().hex
51
+
52
+
53
+ def _blit (argsid ):
54
+ """
55
+ Thin wrapper to blit called via tkapp.call.
56
+
57
+ *argsid* is a unique string identifier to fetch the correct arguments from
58
+ the ``_blit_args`` dict, since arguments cannot be passed directly.
59
+
60
+ photoimage blanking must occur in the same event and thread as blitting
61
+ to avoid flickering.
62
+ """
63
+ photoimage , dataptr , offsets , bboxptr , blank = _blit_args .pop (argsid )
64
+ if blank :
65
+ photoimage .blank ()
66
+ _tkagg .blit (
67
+ photoimage .tk .interpaddr (), str (photoimage ), dataptr , offsets , bboxptr )
68
+
69
+
47
70
def blit (photoimage , aggimage , offsets , bbox = None ):
48
71
"""
49
72
Blit *aggimage* to *photoimage*.
@@ -53,7 +76,10 @@ def blit(photoimage, aggimage, offsets, bbox=None):
53
76
(2, 1, 0, 3) for little-endian ARBG32 (i.e. GBRA8888) data and (1, 2, 3, 0)
54
77
for big-endian ARGB32 (i.e. ARGB8888) data.
55
78
56
- If *bbox* is passed, it defines the region that gets blitted.
79
+ If *bbox* is passed, it defines the region that gets blitted. That region
80
+ will NOT be blanked before blitting.
81
+
82
+ Tcl events must be dispatched to trigger a blit from a non-Tcl thread.
57
83
"""
58
84
data = np .asarray (aggimage )
59
85
height , width = data .shape [:2 ]
@@ -65,11 +91,31 @@ def blit(photoimage, aggimage, offsets, bbox=None):
65
91
y1 = max (math .floor (y1 ), 0 )
66
92
y2 = min (math .ceil (y2 ), height )
67
93
bboxptr = (x1 , x2 , y1 , y2 )
94
+ blank = False
68
95
else :
69
- photoimage .blank ()
70
96
bboxptr = (0 , width , 0 , height )
71
- _tkagg .blit (
72
- photoimage .tk .interpaddr (), str (photoimage ), dataptr , offsets , bboxptr )
97
+ blank = True
98
+
99
+ # NOTE: _tkagg.blit is thread unsafe and will crash the process if called
100
+ # from a thread (GH#13293). Instead of blanking and blitting here,
101
+ # use tkapp.call to post a cross-thread event if this function is called
102
+ # from a non-Tcl thread.
103
+
104
+ # tkapp.call coerces all arguments to strings, so to avoid string parsing
105
+ # within _blit, pack up the arguments into a global data structure.
106
+ args = photoimage , dataptr , offsets , bboxptr , blank
107
+ # Need a unique key to avoid thread races.
108
+ # Again, make the key a string to avoid string parsing in _blit.
109
+ argsid = str (id (args ))
110
+ _blit_args [argsid ] = args
111
+
112
+ try :
113
+ photoimage .tk .call (_blit_tcl_name , argsid )
114
+ except tk .TclError as e :
115
+ if "invalid command name" not in str (e ):
116
+ raise
117
+ photoimage .tk .createcommand (_blit_tcl_name , _blit )
118
+ photoimage .tk .call (_blit_tcl_name , argsid )
73
119
74
120
75
121
class TimerTk (TimerBase ):
@@ -402,10 +448,18 @@ def destroy(self, *args):
402
448
if self .canvas ._idle_callback :
403
449
self .canvas ._tkcanvas .after_cancel (self .canvas ._idle_callback )
404
450
405
- self .window .destroy ()
451
+ # NOTE: events need to be flushed before issuing destroy (GH #9956),
452
+ # however, self.window.update() can break user code. This is the
453
+ # safest way to achieve a complete draining of the event queue,
454
+ # but it may require users to update() on their own to execute the
455
+ # completion in obscure corner cases.
456
+ def delayed_destroy ():
457
+ self .window .destroy ()
458
+
459
+ if self ._owns_mainloop and not Gcf .get_num_fig_managers ():
460
+ self .window .quit ()
406
461
407
- if self ._owns_mainloop and not Gcf .get_num_fig_managers ():
408
- self .window .quit ()
462
+ self .window .after_idle (delayed_destroy )
409
463
410
464
def get_window_title (self ):
411
465
return self .window .wm_title ()
0 commit comments