10000 WIP: Try to make tkagg blitting threadsafe · matplotlib/matplotlib@0dcdd21 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0dcdd21

Browse files
WIP: Try to make tkagg blitting threadsafe
current status: doesn't crash but also doesn't draw when called from a thread
1 parent f98427d commit 0dcdd21

File tree

3 files changed

+96
-6
lines changed

3 files changed

+96
-6
lines changed

lib/matplotlib/backends/_backend_tk.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ def __init__(self, figure, master=None, resize_callback=None):
123123
width=w, height=h, borderwidth=0, highlightthickness=0)
124124
self._tkphoto = tk.PhotoImage(
125125
master=self._tkcanvas, width=w, height=h)
126+
self._findphoto_name = self._tkcanvas.register(self._findphoto)
126127
self._tkcanvas.create_image(w//2, h//2, image=self._tkphoto)
127128
self._resize_callback = resize_callback
128129
self._tkcanvas.bind("<Configure>", self.resize)
@@ -178,6 +179,37 @@ def resize(self, event):
178179
int(width / 2), int(height / 2), image=self._tkphoto)
179180
self.resize_event()
180181

182+
def _findphoto(self):
183+
return _tkagg.findphoto(self._tkphoto.tk.interpaddr(), str(self._tkphoto))
184+
185+
def blit(self, bbox=None):
186+
"""
187+
Blit *aggimage* to *photoimage*.
188+
189+
*offsets* is a tuple describing how to fill the ``offset`` field of the
190+
``Tk_PhotoImageBlock`` struct: it should be (0, 1, 2, 3) for RGBA8888 data,
191+
(2, 1, 0, 3) for little-endian ARBG32 (i.e. GBRA8888) data and (1, 2, 3, 0)
192+
for big-endian ARGB32 (i.e. ARGB8888) data.
193+
194+
If *bbox* is passed, it defines the region that gets blitted.
195+
"""
196+
data = np.asarray(self.renderer._renderer)
197+
height, width = data.shape[:2]
198+
dataptr = (height, width, data.ctypes.data)
199+
if bbox is not None:
200+
(x1, y1), (x2, y2) = bbox.__array__()
201+
x1 = max(math.floor(x1), 0)
202+
x2 = min(math.ceil(x2), width)
203+
y1 = max(math.floor(y1), 0)
204+
y2 = min(math.ceil(y2), height)
205+
bboxptr = (x1, x2, y1, y2)
206+
else:
207+
self._tkphoto.blank()
208+
bboxptr = (0, width, 0, height)
209+
# photoptr = self._findphoto() # Thread unsafe
210+
photoptr = self._master.tk.call(self._findphoto_name) # Thread safe
211+
_tkagg.photoputblock(photoptr, dataptr, (0, 1, 2, 3), bboxptr) # ???
212+
181213
def draw_idle(self):
182214
# docstring inherited
183215
if not self._idle:

lib/matplotlib/backends/backend_tkagg.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from . import _backend_tk
21
from .backend_agg import FigureCanvasAgg
32
from ._backend_tk import (
43
_BackendTk, FigureCanvasTk, FigureManagerTk, NavigationToolbar2Tk)
@@ -7,11 +6,7 @@
76
class FigureCanvasTkAgg(FigureCanvasAgg, FigureCanvasTk):
87
def draw(self):
98
super().draw()
10-
_backend_tk.blit(self._tkphoto, self.renderer._renderer, (0, 1, 2, 3))
11-
12-
def blit(self, bbox=None):
13-
_backend_tk.blit(
14-
self._tkphoto, self.renderer._renderer, (0, 1, 2, 3), bbox=bbox)
9+
self.blit()
1510

1611

1712
@_BackendTk.export

src/_tkagg.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,71 @@ static PyObject *mpl_tk_blit(PyObject *self, PyObject *args)
8989
}
9090
}
9191

92+
static PyObject *mpl_tk_findphoto(PyObject *self, PyObject *args)
93+
{
94+
Tcl_Interp *interp;
95+
char const *photo_name;
96+
Tk_PhotoHandle photo;
97+
if (!PyArg_ParseTuple(args, "O&s:findphoto",convert_voidptr, &interp, &photo_name)) {
98+
goto exit;
99+
}
100+
if (!(photo = TK_FIND_PHOTO(interp, photo_name))) {
101+
PyErr_SetString(PyExc_ValueError, "Failed to extract Tk_PhotoHandle");
102+
goto exit;
103+
}
104+
exit:
105+
if (PyErr_Occurred()) {
106+
return NULL;
107+
} else {
108+
return PyLong_FromVoidPtr((void *)photo);
109+
}
110+
}
111+
112+
static PyObject *mpl_tk_photoputblock(PyObject *self, PyObject *args)
113+
{
114+
int height, width;
115+
unsigned char *data_ptr;
116+
int o0, o1, o2, o3;
117+
int x1, x2, y1, y2;
118+
Tk_PhotoHandle photo;
119+
Tk_PhotoImageBlock block;
120+
if (!PyArg_ParseTuple(args, "O&(iiO&)(iiii)(iiii):photoputblock",
121+
convert_voidptr, &photo,
122+
&height, &width, convert_voidptr, &data_ptr,
123+
&o0, &o1, &o2, &o3,
124+
&x1, &x2, &y1, &y2)) {
125+
goto exit;
126+
}
127+
if (0 > y1 || y1 > y2 || y2 > height || 0 > x1 || x1 > x2 || x2 > width) {
128+
PyErr_SetString(PyExc_ValueError, "Attempting to draw out of bounds");
129+
goto exit;
130+
}
131+
132+
Py_BEGIN_ALLOW_THREADS
133+
block.pixelPtr = data_ptr + 4 * ((height - y2) * width + x1);
134+
block.width = x2 - x1;
135+
block.height = y2 - y1;
136+
block.pitch = 4 * width;
137+
block.pixelSize = 4;
138+
block.offset[0] = o0;
139+
block.offset[1] = o1;
140+
block.offset[2] = o2;
141+
block.offset[3] = o3;
142+
TK_PHOTO_PUT_BLOCK_NO_COMPOSITE(
143+
photo, &block, x1, height - y2, x2 - x1, y2 - y1);
144+
Py_END_ALLOW_THREADS
145+
exit:
146+
if (PyErr_Occurred()) {
147+
return NULL;
148+
} else {
149+
Py_RETURN_NONE;
150+
}
151+
}
152+
92153
static PyMethodDef functions[] = {
93154
{ "blit", (PyCFunction)mpl_tk_blit, METH_VARARGS },
155+
{ "findphoto", (PyCFunction)mpl_tk_findphoto, METH_VARARGS },
156+
{ "photoputblock", (PyCFunction)mpl_tk_photoputblock, METH_VARARGS },
94157
{ NULL, NULL } /* sentinel */
95158
};
96159

0 commit comments

Comments
 (0)
0