Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7b9e1ce

Browse files
Speed up Tkagg blit with Tk_PhotoPutBlock (#20840)
ENH: improve tk blitting performance and release GIL
1 parent c423658 commit 7b9e1ce

File tree

6 files changed

+54
-25
lines changed

6 files changed

+54
-25
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Increase to minimum supported optional dependencies
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
For Matplotlib 3.5, the :ref:`minimum supported versions of optional dependencies
5+
<optional_dependencies>` are being bumped:
6+
7+
+------------+-----------------+---------------+
8+
| Dependency | min in mpl3.4 | min in mpl3.5 |
9+
+============+=================+===============+
10+
| Tk | 8.3 | 8.4 |
11+
+------------+-----------------+---------------+
12+
13+
This is consistent with our :ref:`min_deps_policy`

doc/devel/dependencies.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Matplotlib figures can be rendered to various user interfaces. See
4040
:ref:`what-is-a-backend` for more details on the optional Matplotlib backends
4141
and the capabilities they provide.
4242

43-
* Tk_ (>= 8.3, != 8.6.0 or 8.6.1) [#]_: for the Tk-based backends.
43+
* Tk_ (>= 8.4, != 8.6.0 or 8.6.1) [#]_: for the Tk-based backends.
4444
* PyQt6_ (>= 6.1), PySide6_, PyQt5_, or PySide2_: for the Qt-based backends.
4545
* PyGObject_: for the GTK3-based backends [#]_.
4646
* wxPython_ (>= 4) [#]_: for the wx-based backends.

lib/matplotlib/backends/_backend_tk.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,22 +53,20 @@ def _restore_foreground_window_at_end():
5353
# Initialize to a non-empty string that is not a Tcl command
5454
_blit_tcl_name = "mpl_blit_" + uuid.uuid4().hex
5555

56+
TK_PHOTO_COMPOSITE_OVERLAY = 0 # apply transparency rules pixel-wise
57+
TK_PHOTO_COMPOSITE_SET = 1 # set image buffer directly
58+
5659

5760
def _blit(argsid):
5861
"""
5962
Thin wrapper to blit called via tkapp.call.
6063
6164
*argsid* is a unique string identifier to fetch the correct arguments from
6265
the ``_blit_args`` dict, since arguments cannot be passed directly.
63-
64-
photoimage blanking must occur in the same event and thread as blitting
65-
to avoid flickering.
6666
"""
67-
photoimage, dataptr, offsets, bboxptr, blank = _blit_args.pop(argsid)
68-
if blank:
69-
photoimage.blank()
70-
_tkagg.blit(
71-
photoimage.tk.interpaddr(), str(photoimage), dataptr, offsets, bboxptr)
67+
photoimage, dataptr, offsets, bboxptr, comp_rule = _blit_args.pop(argsid)
68+
_tkagg.blit(photoimage.tk.interpaddr(), str(photoimage), dataptr,
69+
comp_rule, offsets, bboxptr)
7270

7371

7472
def blit(photoimage, aggimage, offsets, bbox=None):
@@ -81,7 +79,7 @@ def blit(photoimage, aggimage, offsets, bbox=None):
8179
for big-endian ARGB32 (i.e. ARGB8888) data.
8280
8381
If *bbox* is passed, it defines the region that gets blitted. That region
84-
will NOT be blanked before blitting.
82+
will be composed with the previous data according to the alpha channel.
8583
8684
Tcl events must be dispatched to trigger a blit from a non-Tcl thread.
8785
"""
@@ -95,10 +93,10 @@ def blit(photoimage, aggimage, offsets, bbox=None):
9593
y1 = max(math.floor(y1), 0)
9694
y2 = min(math.ceil(y2), height)
9795
bboxptr = (x1, x2, y1, y2)
98-
blank = False
96+
comp_rule = TK_PHOTO_COMPOSITE_OVERLAY
9997
else:
10098
bboxptr = (0, width, 0, height)
101-
blank = True
99+
comp_rule = TK_PHOTO_COMPOSITE_SET
102100

103101
# NOTE: _tkagg.blit is thread unsafe and will crash the process if called
104102
# from a thread (GH#13293). Instead of blanking and blitting here,
@@ -107,7 +105,7 @@ def blit(photoimage, aggimage, offsets, bbox=None):
107105

108106
# tkapp.call coerces all arguments to strings, so to avoid string parsing
109107
# within _blit, pack up the arguments into a global data structure.
110-
args = photoimage, dataptr, offsets, bboxptr, blank
108+
args = photoimage, dataptr, offsets, bboxptr, comp_rule
111109
# Need a unique key to avoid thread races.
112110
# Again, make the key a string to avoid string parsing in _blit.
113111
argsid = str(id(args))

lib/matplotlib/tests/test_backend_tk.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def test_blit(): # pragma: no cover
8181
for bad_box in bad_boxes:
8282
try:
8383
_tkagg.blit(
84-
photoimage.tk.interpaddr(), str(photoimage), dataptr,
84+
photoimage.tk.interpaddr(), str(photoimage), dataptr, 0,
8585
(0, 1, 2, 3), bad_box)
8686
except ValueError:
8787
print("success")

src/_tkagg.cpp

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ static int convert_voidptr(PyObject *obj, void *p)
5050
// Global vars for Tk functions. We load these symbols from the tkinter
5151
// extension module or loaded Tk libraries at run-time.
5252
static Tk_FindPhoto_t TK_FIND_PHOTO;
53-
static Tk_PhotoPutBlock_NoComposite_t TK_PHOTO_PUT_BLOCK_NO_COMPOSITE;
53+
static Tk_PhotoPutBlock_t TK_PHOTO_PUT_BLOCK;
5454
#ifdef WIN32_DLL
5555
// Global vars for Tcl functions. We load these symbols from the tkinter
5656
// extension module or loaded Tcl libraries at run-time.
@@ -63,13 +63,16 @@ static PyObject *mpl_tk_blit(PyObject *self, PyObject *args)
6363
char const *photo_name;
6464
int height, width;
6565
unsigned char *data_ptr;
66+
int comp_rule;
67+
int put_retval;
6668
int o0, o1, o2, o3;
6769
int x1, x2, y1, y2;
6870
Tk_PhotoHandle photo;
6971
Tk_PhotoImageBlock block;
70-
if (!PyArg_ParseTuple(args, "O&s(iiO&)(iiii)(iiii):blit",
72+
if (!PyArg_ParseTuple(args, "O&s(iiO&)i(iiii)(iiii):blit",
7173
convert_voidptr, &interp, &photo_name,
7274
&height, &width, convert_voidptr, &data_ptr,
75+
&comp_rule,
7376
&o0, &o1, &o2, &o3,
7477
&x1, &x2, &y1, &y2)) {
7578
goto exit;
@@ -82,7 +85,12 @@ static PyObject *mpl_tk_blit(PyObject *self, PyObject *args)
8285
PyErr_SetString(PyExc_ValueError, "Attempting to draw out of bounds");
8386
goto exit;
8487
}
88+
if (comp_rule != TK_PHOTO_COMPOSITE_OVERLAY && comp_rule != TK_PHOTO_COMPOSITE_SET) {
89+
PyErr_SetString(PyExc_ValueError, "Invalid comp_rule argument");
90+
goto exit;
91+
}
8592

93+
Py_BEGIN_ALLOW_THREADS
8694
block.pixelPtr = data_ptr + 4 * ((height - y2) * width + x1);
8795
block.width = x2 - x1;
8896
block.height = y2 - y1;
@@ -92,8 +100,13 @@ static PyObject *mpl_tk_blit(PyObject *self, PyObject *args)
92100
block.offset[1] = o1;
93101
block.offset[2] = o2;
94102
block.offset[3] = o3;
95-
TK_PHOTO_PUT_BLOCK_NO_COMPOSITE(
96-
photo, &block, x1, height - y2, x2 - x1, y2 - y1);
103+
put_retval = TK_PHOTO_PUT_BLOCK(
104+
interp, photo, &block, x1, height - y2, x2 - x1, y2 - y1, comp_rule);
105+
Py_END_ALLOW_THREADS
106+
if (put_retval == TCL_ERROR) {
107+
return PyErr_NoMemory();
108+
}
109+
97110
exit:
98111
if (PyErr_Occurred()) {
99112
return NULL;
@@ -219,8 +232,8 @@ int load_tk(T lib)
219232
return
220233
!!(TK_FIND_PHOTO =
221234
(Tk_FindPhoto_t)dlsym(lib, "Tk_FindPhoto")) +
222-
!!(TK_PHOTO_PUT_BLOCK_NO_COMPOSITE =
223-
(Tk_PhotoPutBlock_NoComposite_t)dlsym(lib, "Tk_PhotoPutBlock_NoComposite"));
235+
!!(TK_PHOTO_PUT_BLOCK =
236+
(Tk_PhotoPutBlock_t)dlsym(lib, "Tk_PhotoPutBlock"));
224237
}
225238

226239
#ifdef WIN32_DLL
@@ -341,8 +354,8 @@ PyMODINIT_FUNC PyInit__tkagg(void)
341354
} else if (!TK_FIND_PHOTO) {
342355
PyErr_SetString(PyExc_RuntimeError, "Failed to load Tk_FindPhoto");
343356
return NULL;
344-
} else if (!TK_PHOTO_PUT_BLOCK_NO_COMPOSITE) {
345-
PyErr_SetString(PyExc_RuntimeError, "Failed to load Tk_PhotoPutBlock_NoComposite");
357+
} else if (!TK_PHOTO_PUT_BLOCK) {
358+
PyErr_SetString(PyExc_RuntimeError, "Failed to load Tk_PhotoPutBlock");
346359
return NULL;
347360
}
348361
return PyModule_Create(&_tkagg_module);

src/_tkmini.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,19 @@ typedef struct Tk_PhotoImageBlock
8686
int offset[4];
8787
} Tk_PhotoImageBlock;
8888

89+
#define TK_PHOTO_COMPOSITE_OVERLAY 0 // apply transparency rules pixel-wise
90+
#define TK_PHOTO_COMPOSITE_SET 1 // set image buffer directly
91+
#define TCL_OK 0
92+
#define TCL_ERROR 1
93+
8994
/* Typedefs derived from function signatures in Tk header */
9095
/* Tk_FindPhoto typedef */
9196
typedef Tk_PhotoHandle (*Tk_FindPhoto_t) (Tcl_Interp *interp, const char
9297
*imageName);
93-
/* Tk_PhotoPutBLock_NoComposite typedef */
94-
typedef void (*Tk_PhotoPutBlock_NoComposite_t) (Tk_PhotoHandle handle,
98+
/* Tk_PhotoPutBLock typedef */
99+
typedef int (*Tk_PhotoPutBlock_t) (Tcl_Interp *interp, Tk_PhotoHandle handle,
95100
Tk_PhotoImageBlock *blockPtr, int x, int y,
96-
int width, int height);
101+
int width, int height, int compRule);
97102

98103
#ifdef WIN32_DLL
99104
/* Typedefs derived from function signatures in Tcl header */

0 commit comments

Comments
 (0)