diff --git a/doc/users/next_whats_new/2018_01_10_pypy_tkagg_support.rst b/doc/users/next_whats_new/2018_01_10_pypy_tkagg_support.rst new file mode 100644 index 000000000000..cd4fb18f06c1 --- /dev/null +++ b/doc/users/next_whats_new/2018_01_10_pypy_tkagg_support.rst @@ -0,0 +1,7 @@ +Rework TkAgg backend to support PyPy +------------------------------------ + +PyPy_ can plot using the TkAgg backend, supported on PyPy 5.9 +and greater (both PyPy for python 2.7 and PyPy for python 3.5) + +.. _PyPy: https:/www.pypy.org diff --git a/examples/user_interfaces/embedding_in_tk_sgskip.py b/examples/user_interfaces/embedding_in_tk_sgskip.py index bee6bb050281..f79390d30990 100644 --- a/examples/user_interfaces/embedding_in_tk_sgskip.py +++ b/examples/user_interfaces/embedding_in_tk_sgskip.py @@ -12,6 +12,7 @@ # Implement the default Matplotlib key bindings. from matplotlib.backend_bases import key_press_handler from matplotlib.figure import Figure +from six.moves import tkinter as Tk import numpy as np diff --git a/lib/matplotlib/backends/tkagg.py b/lib/matplotlib/backends/tkagg.py index 060f4726c060..e3b6be68ceb2 100644 --- a/lib/matplotlib/backends/tkagg.py +++ b/lib/matplotlib/backends/tkagg.py @@ -13,23 +13,25 @@ def blit(photoimage, aggimage, bbox=None, colormode=1): if bbox is not None: bbox_array = bbox.__array__() + # x1, x2, y1, y2 + bboxptr = (bbox_array[0, 0], bbox_array[1, 0], + bbox_array[0, 1], bbox_array[1, 1]) else: - bbox_array = None + bboxptr = 0 data = np.asarray(aggimage) + dataptr = (data.ctypes.data, data.shape[0], data.shape[1]) try: tk.call( "PyAggImagePhoto", photoimage, - id(data), colormode, id(bbox_array)) + dataptr, colormode, bboxptr) except Tk.TclError: - try: - try: - _tkagg.tkinit(tk.interpaddr(), 1) - except AttributeError: - _tkagg.tkinit(id(tk), 0) - tk.call("PyAggImagePhoto", photoimage, - id(data), colormode, id(bbox_array)) - except (ImportError, AttributeError, Tk.TclError): - raise + if hasattr(tk, 'interpaddr'): + _tkagg.tkinit(tk.interpaddr(), 1) + else: + # very old python? + _tkagg.tkinit(tk, 0) + tk.call("PyAggImagePhoto", photoimage, + dataptr, colormode, bboxptr) def test(aggimage): r = Tk.Tk() diff --git a/setupext.py b/setupext.py index 508f2b825d45..acef4ba2549f 100644 --- a/setupext.py +++ b/setupext.py @@ -1505,13 +1505,11 @@ def runtime_check(self): def get_extension(self): sources = [ - 'src/py_converters.cpp', 'src/_tkagg.cpp' ] ext = make_extension('matplotlib.backends._tkagg', sources) self.add_flags(ext) - Numpy().add_flags(ext) LibAgg().add_flags(ext, add_sources=False) return ext diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index 830831400d50..d92d8e7ffc9d 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -13,16 +13,17 @@ #include #include -#include "py_converters.h" +#include // agg:int8u // Include our own excerpts from the Tcl / Tk headers #include "_tkmini.h" #if defined(_MSC_VER) -# define SIZE_T_FORMAT "%Iu" +# define IMG_FORMAT "%Iu %d %d" #else -# define SIZE_T_FORMAT "%zu" +# define IMG_FORMAT "%zu %d %d" #endif +#define BBOX_FORMAT "%f %f %f %f" typedef struct { @@ -44,16 +45,15 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int { Tk_PhotoHandle photo; Tk_PhotoImageBlock block; - PyObject *bufferobj; // vars for blitting - PyObject *bboxo; - size_t aggl, bboxl; + size_t pdata; + int wdata, hdata, bbox_parse; + float x1, x2, y1, y2; bool has_bbox; - uint8_t *destbuffer; + agg::int8u *destbuffer, *buffer; int destx, desty, destwidth, destheight, deststride; - //unsigned long tmp_ptr; long mode; long nval; @@ -73,24 +73,14 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int TCL_APPEND_RESULT(interp, "destination photo must exist", (char *)NULL); return TCL_ERROR; } - /* get array (or object that can be converted to array) pointer */ - if (sscanf(argv[2], SIZE_T_FORMAT, &aggl) != 1) { - TCL_APPEND_RESULT(interp, "error casting pointer", (char *)NULL); + /* get buffer from str which is "ptr height width" */ + if (sscanf(argv[2], IMG_FORMAT, &pdata, &hdata, &wdata) != 3) { + TCL_APPEND_RESULT(interp, + "error reading data, expected ptr height width", + (char *)NULL); return TCL_ERROR; } - bufferobj = (PyObject *)aggl; - - numpy::array_view buffer; - try { - buffer = numpy::array_view(bufferobj); - } catch (...) { - TCL_APPEND_RESULT(interp, "buffer is of wrong type", (char *)NULL); - PyErr_Clear(); - return TCL_ERROR; - } - int srcheight = buffer.dim(0); - - /* XXX insert aggRenderer type check */ + buffer = (agg::int8u*)pdata; /* get array mode (0=mono, 1=rgb, 2=rgba) */ mode = atol(argv[3]); @@ -100,24 +90,23 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int } /* check for bbox/blitting */ - if (sscanf(argv[4], SIZE_T_FORMAT, &bboxl) != 1) { - TCL_APPEND_RESULT(interp, "error casting pointer", (char *)NULL); + bbox_parse = sscanf(argv[4], BBOX_FORMAT, &x1, &x2, &y1, &y2); + if (bbox_parse == 4) { + has_bbox = true; + } + else if ((bbox_parse == 1) && (x1 == 0)){ + has_bbox = false; + } else { + TCL_APPEND_RESULT(interp, "illegal bbox", (char *)NULL); return TCL_ERROR; } - bboxo = (PyObject *)bboxl; - - if (bboxo != NULL && bboxo != Py_None) { - agg::rect_d rect; - if (!convert_rect(bboxo, &rect)) { - return TCL_ERROR; - } - - has_bbox = true; - destx = (int)rect.x1; - desty = srcheight - (int)rect.y2; - destwidth = (int)(rect.x2 - rect.x1); - destheight = (int)(rect.y2 - rect.y1); + if (has_bbox) { + int srcstride = wdata * 4; + destx = (int)x1; + desty = (int)(hdata - y2); + destwidth = (int)(x2 - x1); + destheight = (int)(y2 - y1); deststride = 4 * destwidth; destbuffer = new agg::int8u[deststride * destheight]; @@ -128,11 +117,10 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int for (int i = 0; i < destheight; ++i) { memcpy(destbuffer + (deststride * i), - &buffer(i + desty, destx, 0), + &buffer[(i + desty) * srcstride + (destx * 4)], deststride); } } else { - has_bbox = false; destbuffer = NULL; destx = desty = destwidth = destheight = deststride = 0; } @@ -168,10 +156,10 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int delete[] destbuffer; } else { - block.width = buffer.dim(1); - block.height = buffer.dim(0); + block.width = wdata; + block.height = hdata; block.pitch = (int)block.width * nval; - block.pixelPtr = buffer.data(); + block.pixelPtr = buffer; /* Clear current contents */ TK_PHOTO_BLANK(photo); @@ -199,7 +187,7 @@ static PyObject *_tkinit(PyObject *self, PyObject *args) } else { /* Do it the hard way. This will break if the TkappObject layout changes */ - app = (TkappObject *)PyLong_AsVoidPtr(arg); + app = (TkappObject *)arg; interp = app->interp; } @@ -338,7 +326,7 @@ int load_tkinter_funcs(void) * tkinter uses these symbols, and the symbols are therefore visible in the * tkinter dynamic library (module). */ -#if PY3K +#if PY_MAJOR_VERSION >= 3 #define TKINTER_PKG "tkinter" #define TKINTER_MOD "_tkinter" // From module __file__ attribute to char *string for dlopen. @@ -432,13 +420,31 @@ int load_tkinter_funcs(void) } tkinter_lib = dlopen(tkinter_libname, RTLD_LAZY); if (tkinter_lib == NULL) { - PyErr_SetString(PyExc_RuntimeError, - "Cannot dlopen tkinter module file"); - goto exit; + /* Perhaps it is a cffi module, like in PyPy? */ + pString = PyObject_GetAttrString(pSubmodule, "tklib_cffi"); + if (pString == NULL) { + goto fail; + } + pString = PyObject_GetAttrString(pString, "__file__"); + if (pString == NULL) { + goto fail; + } + tkinter_libname = fname2char(pString); + if (tkinter_libname == NULL) { + goto fail; + } + tkinter_lib = dlopen(tkinter_libname, RTLD_LAZY); + } + if (tkinter_lib == NULL) { + goto fail; } ret = _func_loader(tkinter_lib); // dlclose probably safe because tkinter has been imported. dlclose(tkinter_lib); + goto exit; +fail: + PyErr_SetString(PyExc_RuntimeError, + "Cannot dlopen tkinter module file"); exit: Py_XDECREF(pModule); Py_XDECREF(pSubmodule); @@ -447,7 +453,7 @@ int load_tkinter_funcs(void) } #endif // end not Windows -#if PY3K +#if PY_MAJOR_VERSION >= 3 static PyModuleDef _tkagg_module = { PyModuleDef_HEAD_INIT, "_tkagg", "", -1, functions, NULL, NULL, NULL, NULL }; @@ -457,15 +463,11 @@ PyMODINIT_FUNC PyInit__tkagg(void) m = PyModule_Create(&_tkagg_module); - import_array(); - return (load_tkinter_funcs() == 0) ? m : NULL; } #else PyMODINIT_FUNC init_tkagg(void) { - import_array(); - Py_InitModule("_tkagg", functions); load_tkinter_funcs(); diff --git a/src/file_compat.h b/src/file_compat.h index a8f04eaeddca..42fdd162288c 100644 --- a/src/file_compat.h +++ b/src/file_compat.h @@ -48,7 +48,7 @@ extern "C" { /* * PyFile_* compatibility */ -#if PY3K +#if defined(PY3K) | defined(PYPY_VERSION) /* * Get a FILE* handle to the file represented by the Python object