diff --git a/doc/api/next_api_changes/development/20840-RJS.rst b/doc/api/next_api_changes/development/20840-RJS.rst new file mode 100644 index 000000000000..443331d7c887 --- /dev/null +++ b/doc/api/next_api_changes/development/20840-RJS.rst @@ -0,0 +1,13 @@ +Increase to minimum supported optional dependencies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For Matplotlib 3.5, the :ref:`minimum supported versions of optional dependencies +` are being bumped: + ++------------+-----------------+---------------+ +| Dependency | min in mpl3.4 | min in mpl3.5 | ++============+=================+===============+ +| Tk | 8.3 | 8.4 | ++------------+-----------------+---------------+ + +This is consistent with our :ref:`min_deps_policy` diff --git a/doc/devel/dependencies.rst b/doc/devel/dependencies.rst index 33a35b3dde1a..0a3c40da5524 100644 --- a/doc/devel/dependencies.rst +++ b/doc/devel/dependencies.rst @@ -40,7 +40,7 @@ Matplotlib figures can be rendered to various user interfaces. See :ref:`what-is-a-backend` for more details on the optional Matplotlib backends and the capabilities they provide. -* Tk_ (>= 8.3, != 8.6.0 or 8.6.1) [#]_: for the Tk-based backends. +* Tk_ (>= 8.4, != 8.6.0 or 8.6.1) [#]_: for the Tk-based backends. * PyQt6_ (>= 6.1), PySide6_, PyQt5_, or PySide2_: for the Qt-based backends. * PyGObject_: for the GTK3-based backends [#]_. * wxPython_ (>= 4) [#]_: for the wx-based backends. diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 28c5ae737c9b..e07c089676b4 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -53,6 +53,9 @@ def _restore_foreground_window_at_end(): # Initialize to a non-empty string that is not a Tcl command _blit_tcl_name = "mpl_blit_" + uuid.uuid4().hex +TK_PHOTO_COMPOSITE_OVERLAY = 0 # apply transparency rules pixel-wise +TK_PHOTO_COMPOSITE_SET = 1 # set image buffer directly + def _blit(argsid): """ @@ -60,15 +63,10 @@ def _blit(argsid): *argsid* is a unique string identifier to fetch the correct arguments from the ``_blit_args`` dict, since arguments cannot be passed directly. - - photoimage blanking must occur in the same event and thread as blitting - to avoid flickering. """ - photoimage, dataptr, offsets, bboxptr, blank = _blit_args.pop(argsid) - if blank: - photoimage.blank() - _tkagg.blit( - photoimage.tk.interpaddr(), str(photoimage), dataptr, offsets, bboxptr) + photoimage, dataptr, offsets, bboxptr, comp_rule = _blit_args.pop(argsid) + _tkagg.blit(photoimage.tk.interpaddr(), str(photoimage), dataptr, + comp_rule, offsets, bboxptr) def blit(photoimage, aggimage, offsets, bbox=None): @@ -81,7 +79,7 @@ def blit(photoimage, aggimage, offsets, bbox=None): for big-endian ARGB32 (i.e. ARGB8888) data. If *bbox* is passed, it defines the region that gets blitted. That region - will NOT be blanked before blitting. + will be composed with the previous data according to the alpha channel. Tcl events must be dispatched to trigger a blit from a non-Tcl thread. """ @@ -95,10 +93,10 @@ def blit(photoimage, aggimage, offsets, bbox=None): y1 = max(math.floor(y1), 0) y2 = min(math.ceil(y2), height) bboxptr = (x1, x2, y1, y2) - blank = False + comp_rule = TK_PHOTO_COMPOSITE_OVERLAY else: bboxptr = (0, width, 0, height) - blank = True + comp_rule = TK_PHOTO_COMPOSITE_SET # NOTE: _tkagg.blit is thread unsafe and will crash the process if called # from a thread (GH#13293). Instead of blanking and blitting here, @@ -107,7 +105,7 @@ def blit(photoimage, aggimage, offsets, bbox=None): # tkapp.call coerces all arguments to strings, so to avoid string parsing # within _blit, pack up the arguments into a global data structure. - args = photoimage, dataptr, offsets, bboxptr, blank + args = photoimage, dataptr, offsets, bboxptr, comp_rule # Need a unique key to avoid thread races. # Again, make the key a string to avoid string parsing in _blit. argsid = str(id(args)) diff --git a/lib/matplotlib/tests/test_backend_tk.py b/lib/matplotlib/tests/test_backend_tk.py index 4c714b303bd1..7c6a051df9ad 100644 --- a/lib/matplotlib/tests/test_backend_tk.py +++ b/lib/matplotlib/tests/test_backend_tk.py @@ -80,7 +80,7 @@ def test_blit(): # pragma: no cover for bad_box in bad_boxes: try: _tkagg.blit( - photoimage.tk.interpaddr(), str(photoimage), dataptr, + photoimage.tk.interpaddr(), str(photoimage), dataptr, 0, (0, 1, 2, 3), bad_box) except ValueError: print("success") diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index fc1fe2d82787..8c5ba4a4c800 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -50,7 +50,7 @@ static int convert_voidptr(PyObject *obj, void *p) // Global vars for Tk functions. We load these symbols from the tkinter // extension module or loaded Tk libraries at run-time. static Tk_FindPhoto_t TK_FIND_PHOTO; -static Tk_PhotoPutBlock_NoComposite_t TK_PHOTO_PUT_BLOCK_NO_COMPOSITE; +static Tk_PhotoPutBlock_t TK_PHOTO_PUT_BLOCK; #ifdef WIN32_DLL // Global vars for Tcl functions. We load these symbols from the tkinter // extension module or loaded Tcl libraries at run-time. @@ -63,13 +63,16 @@ static PyObject *mpl_tk_blit(PyObject *self, PyObject *args) char const *photo_name; int height, width; unsigned char *data_ptr; + int comp_rule; + int put_retval; int o0, o1, o2, o3; int x1, x2, y1, y2; Tk_PhotoHandle photo; Tk_PhotoImageBlock block; - if (!PyArg_ParseTuple(args, "O&s(iiO&)(iiii)(iiii):blit", + if (!PyArg_ParseTuple(args, "O&s(iiO&)i(iiii)(iiii):blit", convert_voidptr, &interp, &photo_name, &height, &width, convert_voidptr, &data_ptr, + &comp_rule, &o0, &o1, &o2, &o3, &x1, &x2, &y1, &y2)) { goto exit; @@ -82,7 +85,12 @@ static PyObject *mpl_tk_blit(PyObject *self, PyObject *args) PyErr_SetString(PyExc_ValueError, "Attempting to draw out of bounds"); goto exit; } + if (comp_rule != TK_PHOTO_COMPOSITE_OVERLAY && comp_rule != TK_PHOTO_COMPOSITE_SET) { + PyErr_SetString(PyExc_ValueError, "Invalid comp_rule argument"); + goto exit; + } + Py_BEGIN_ALLOW_THREADS block.pixelPtr = data_ptr + 4 * ((height - y2) * width + x1); block.width = x2 - x1; block.height = y2 - y1; @@ -92,8 +100,13 @@ static PyObject *mpl_tk_blit(PyObject *self, PyObject *args) block.offset[1] = o1; block.offset[2] = o2; block.offset[3] = o3; - TK_PHOTO_PUT_BLOCK_NO_COMPOSITE( - photo, &block, x1, height - y2, x2 - x1, y2 - y1); + put_retval = TK_PHOTO_PUT_BLOCK( + interp, photo, &block, x1, height - y2, x2 - x1, y2 - y1, comp_rule); + Py_END_ALLOW_THREADS + if (put_retval == TCL_ERROR) { + return PyErr_NoMemory(); + } + exit: if (PyErr_Occurred()) { return NULL; @@ -219,8 +232,8 @@ int load_tk(T lib) return !!(TK_FIND_PHOTO = (Tk_FindPhoto_t)dlsym(lib, "Tk_FindPhoto")) + - !!(TK_PHOTO_PUT_BLOCK_NO_COMPOSITE = - (Tk_PhotoPutBlock_NoComposite_t)dlsym(lib, "Tk_PhotoPutBlock_NoComposite")); + !!(TK_PHOTO_PUT_BLOCK = + (Tk_PhotoPutBlock_t)dlsym(lib, "Tk_PhotoPutBlock")); } #ifdef WIN32_DLL @@ -341,8 +354,8 @@ PyMODINIT_FUNC PyInit__tkagg(void) } else if (!TK_FIND_PHOTO) { PyErr_SetString(PyExc_RuntimeError, "Failed to load Tk_FindPhoto"); return NULL; - } else if (!TK_PHOTO_PUT_BLOCK_NO_COMPOSITE) { - PyErr_SetString(PyExc_RuntimeError, "Failed to load Tk_PhotoPutBlock_NoComposite"); + } else if (!TK_PHOTO_PUT_BLOCK) { + PyErr_SetString(PyExc_RuntimeError, "Failed to load Tk_PhotoPutBlock"); return NULL; } return PyModule_Create(&_tkagg_module); diff --git a/src/_tkmini.h b/src/_tkmini.h index e45184166b67..be7c13fa14d5 100644 --- a/src/_tkmini.h +++ b/src/_tkmini.h @@ -86,14 +86,19 @@ typedef struct Tk_PhotoImageBlock int offset[4]; } Tk_PhotoImageBlock; +#define TK_PHOTO_COMPOSITE_OVERLAY 0 // apply transparency rules pixel-wise +#define TK_PHOTO_COMPOSITE_SET 1 // set image buffer directly +#define TCL_OK 0 +#define TCL_ERROR 1 + /* Typedefs derived from function signatures in Tk header */ /* Tk_FindPhoto typedef */ typedef Tk_PhotoHandle (*Tk_FindPhoto_t) (Tcl_Interp *interp, const char *imageName); -/* Tk_PhotoPutBLock_NoComposite typedef */ -typedef void (*Tk_PhotoPutBlock_NoComposite_t) (Tk_PhotoHandle handle, +/* Tk_PhotoPutBLock typedef */ +typedef int (*Tk_PhotoPutBlock_t) (Tcl_Interp *interp, Tk_PhotoHandle handle, Tk_PhotoImageBlock *blockPtr, int x, int y, - int width, int height); + int width, int height, int compRule); #ifdef WIN32_DLL /* Typedefs derived from function signatures in Tcl header */