1 /* -*- mode: c++; c-basic-offset: 4 -*- */
2 
3 /*
4  * This code is derived from The Python Imaging Library and is covered
5  * by the PIL license.
6  *
7  * See LICENSE/LICENSE.PIL for details.
8  *
9  */
10 
11 #include <Python.h>
12 #include <cstdlib>
13 #include <cstdio>
14 #include <sstream>
15 
16 #include <agg_basics.h> // agg:int8u
17 
18 // Include our own excerpts from the Tcl / Tk headers
19 #include "_tkmini.h"
20 
21 #if defined(_MSC_VER)
22 #  define IMG_FORMAT "%d %d %Iu"
23 #else
24 #  define IMG_FORMAT "%d %d %zu"
25 #endif
26 #define BBOX_FORMAT "%f %f %f %f"
27 
28 typedef struct
29 {
30     PyObject_HEAD
31     Tcl_Interp *interp;
32 } TkappObject;
33 
34 // Global vars for Tcl / Tk functions.  We load these symbols from the tkinter
35 // extension module or loaded Tcl / Tk libraries at run-time.
36 static Tcl_CreateCommand_t TCL_CREATE_COMMAND;
37 static Tcl_AppendResult_t TCL_APPEND_RESULT;
38 static Tk_MainWindow_t TK_MAIN_WINDOW;
39 static Tk_FindPhoto_t TK_FIND_PHOTO;
40 static Tk_PhotoPutBlock_NoComposite_t TK_PHOTO_PUT_BLOCK_NO_COMPOSITE;
41 static Tk_PhotoBlank_t TK_PHOTO_BLANK;
42 
PyAggImagePhoto(ClientData clientdata,Tcl_Interp * interp,int argc,char ** argv)43 static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int
44         argc, char **argv)
45 {
46     Tk_PhotoHandle photo;
47     Tk_PhotoImageBlock block;
48 
49     // vars for blitting
50 
51     size_t pdata;
52     int wdata, hdata, bbox_parse;
53     float x1, x2, y1, y2;
54     bool has_bbox;
55     agg::int8u *destbuffer, *buffer;
56     int destx, desty, destwidth, destheight, deststride;
57 
58     long mode;
59     long nval;
60     if (TK_MAIN_WINDOW(interp) == NULL) {
61         // Will throw a _tkinter.TclError with "this isn't a Tk application"
62         return TCL_ERROR;
63     }
64 
65     if (argc != 5) {
66         TCL_APPEND_RESULT(interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL);
67         return TCL_ERROR;
68     }
69 
70     /* get Tcl PhotoImage handle */
71     photo = TK_FIND_PHOTO(interp, argv[1]);
72     if (photo == NULL) {
73         TCL_APPEND_RESULT(interp, "destination photo must exist", (char *)NULL);
74         return TCL_ERROR;
75     }
76     /* get buffer from str which is "height width ptr" */
77     if (sscanf(argv[2], IMG_FORMAT, &hdata, &wdata, &pdata) != 3) {
78         TCL_APPEND_RESULT(interp,
79                           "error reading data, expected height width ptr",
80                           (char *)NULL);
81         return TCL_ERROR;
82     }
83     buffer = (agg::int8u*)pdata;
84 
85     /* get array mode (0=mono, 1=rgb, 2=rgba) */
86     mode = atol(argv[3]);
87     if ((mode != 0) && (mode != 1) && (mode != 2)) {
88         TCL_APPEND_RESULT(interp, "illegal image mode", (char *)NULL);
89         return TCL_ERROR;
90     }
91 
92     /* check for bbox/blitting */
93     bbox_parse = sscanf(argv[4], BBOX_FORMAT, &x1, &x2, &y1, &y2);
94     if (bbox_parse == 4) {
95         has_bbox = true;
96     }
97     else if ((bbox_parse == 1) && (x1 == 0)){
98         has_bbox = false;
99     } else {
100         TCL_APPEND_RESULT(interp, "illegal bbox", (char *)NULL);
101         return TCL_ERROR;
102     }
103 
104     if (has_bbox) {
105         int srcstride = wdata * 4;
106         destx = (int)x1;
107         desty = (int)(hdata - y2);
108         destwidth = (int)(x2 - x1);
109         destheight = (int)(y2 - y1);
110         deststride = 4 * destwidth;
111 
112         destbuffer = new agg::int8u[deststride * destheight];
113         if (destbuffer == NULL) {
114             TCL_APPEND_RESULT(interp, "could not allocate memory", (char *)NULL);
115             return TCL_ERROR;
116         }
117 
118         for (int i = 0; i < destheight; ++i) {
119             memcpy(destbuffer + (deststride * i),
120                    &buffer[(i + desty) * srcstride + (destx * 4)],
121                    deststride);
122         }
123     } else {
124         destbuffer = NULL;
125         destx = desty = destwidth = destheight = deststride = 0;
126     }
127 
128     /* setup tkblock */
129     block.pixelSize = 1;
130     if (mode == 0) {
131         block.offset[0] = block.offset[1] = block.offset[2] = 0;
132         nval = 1;
133     } else {
134         block.offset[0] = 0;
135         block.offset[1] = 1;
136         block.offset[2] = 2;
137         if (mode == 1) {
138             block.offset[3] = 0;
139             block.pixelSize = 3;
140             nval = 3;
141         } else {
142             block.offset[3] = 3;
143             block.pixelSize = 4;
144             nval = 4;
145         }
146     }
147 
148     if (has_bbox) {
149         block.width = destwidth;
150         block.height = destheight;
151         block.pitch = deststride;
152         block.pixelPtr = destbuffer;
153 
154         TK_PHOTO_PUT_BLOCK_NO_COMPOSITE(photo, &block, destx, desty,
155                 destwidth, destheight);
156         delete[] destbuffer;
157 
158     } else {
159         block.width = wdata;
160         block.height = hdata;
161         block.pitch = (int)block.width * nval;
162         block.pixelPtr = buffer;
163 
164         /* Clear current contents */
165         TK_PHOTO_BLANK(photo);
166         /* Copy opaque block to photo image, and leave the rest to TK */
167         TK_PHOTO_PUT_BLOCK_NO_COMPOSITE(photo, &block, 0, 0, block.width,
168                 block.height);
169     }
170 
171     return TCL_OK;
172 }
173 
_tkinit(PyObject * self,PyObject * args)174 static PyObject *_tkinit(PyObject *self, PyObject *args)
175 {
176     Tcl_Interp *interp;
177     TkappObject *app;
178 
179     PyObject *arg;
180     int is_interp;
181     if (!PyArg_ParseTuple(args, "Oi", &arg, &is_interp)) {
182         return NULL;
183     }
184 
185     if (is_interp) {
186         interp = (Tcl_Interp *)PyLong_AsVoidPtr(arg);
187     } else {
188         /* Do it the hard way.  This will break if the TkappObject
189            layout changes */
190         app = (TkappObject *)arg;
191         interp = app->interp;
192     }
193 
194     /* This will bomb if interp is invalid... */
195 
196     TCL_CREATE_COMMAND(interp,
197                        "PyAggImagePhoto",
198                        (Tcl_CmdProc *)PyAggImagePhoto,
199                        (ClientData)0,
200                        (Tcl_CmdDeleteProc *)NULL);
201 
202     Py_INCREF(Py_None);
203     return Py_None;
204 }
205 
206 static PyMethodDef functions[] = {
207     /* Tkinter interface stuff */
208     { "tkinit", (PyCFunction)_tkinit, 1 },
209     { NULL, NULL } /* sentinel */
210 };
211 
212 // Functions to fill global TCL / Tk function pointers by dynamic loading
213 #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
214 
215 /*
216  * On Windows, we can't load the tkinter module to get the TCL or Tk symbols,
217  * because Windows does not load symbols into the library name-space of
218  * importing modules. So, knowing that tkinter has already been imported by
219  * Python, we scan all modules in the running process for the TCL and Tk
220  * function names.
221  */
222 #include <windows.h>
223 #define PSAPI_VERSION 1
224 #include <psapi.h>
225 // Must be linked with 'psapi' library
226 
_dfunc(HMODULE lib_handle,const char * func_name)227 FARPROC _dfunc(HMODULE lib_handle, const char *func_name)
228 {
229     // Load function `func_name` from `lib_handle`.
230     // Set Python exception if we can't find `func_name` in `lib_handle`.
231     // Returns function pointer or NULL if not present.
232 
233     char message[100];
234 
235     FARPROC func = GetProcAddress(lib_handle, func_name);
236     if (func == NULL) {
237         sprintf(message, "Cannot load function %s", func_name);
238         PyErr_SetString(PyExc_RuntimeError, message);
239     }
240     return func;
241 }
242 
get_tcl(HMODULE hMod)243 int get_tcl(HMODULE hMod)
244 {
245     // Try to fill TCL global vars with function pointers. Return 0 for no
246     // functions found, 1 for all functions found, -1 for some but not all
247     // functions found.
248     TCL_CREATE_COMMAND = (Tcl_CreateCommand_t)
249         GetProcAddress(hMod, "Tcl_CreateCommand");
250     if (TCL_CREATE_COMMAND == NULL) {  // Maybe not TCL module
251         return 0;
252     }
253     TCL_APPEND_RESULT = (Tcl_AppendResult_t) _dfunc(hMod,
254             "Tcl_AppendResult");
255     return (TCL_APPEND_RESULT == NULL) ? -1 : 1;
256 }
257 
get_tk(HMODULE hMod)258 int get_tk(HMODULE hMod)
259 {
260     // Try to fill Tk global vars with function pointers. Return 0 for no
261     // functions found, 1 for all functions found, -1 for some but not all
262     // functions found.
263     TK_MAIN_WINDOW = (Tk_MainWindow_t)
264         GetProcAddress(hMod, "Tk_MainWindow");
265     if (TK_MAIN_WINDOW == NULL) {  // Maybe not Tk module
266         return 0;
267     }
268     return ( // -1 if any remaining symbols are NULL
269         ((TK_FIND_PHOTO = (Tk_FindPhoto_t)
270           _dfunc(hMod, "Tk_FindPhoto")) == NULL) ||
271         ((TK_PHOTO_PUT_BLOCK_NO_COMPOSITE = (Tk_PhotoPutBlock_NoComposite_t)
272           _dfunc(hMod, "Tk_PhotoPutBlock_NoComposite")) == NULL) ||
273         ((TK_PHOTO_BLANK = (Tk_PhotoBlank_t)
274           _dfunc(hMod, "Tk_PhotoBlank")) == NULL))
275         ? -1 : 1;
276 }
277 
load_tkinter_funcs(void)278 int load_tkinter_funcs(void)
279 {
280     // Load TCL and Tk functions by searching all modules in current process.
281     // Return 0 for success, non-zero for failure.
282 
283     HMODULE hMods[1024];
284     HANDLE hProcess;
285     DWORD cbNeeded;
286     unsigned int i;
287     int found_tcl = 0;
288     int found_tk = 0;
289 
290     // Returns pseudo-handle that does not need to be closed
291     hProcess = GetCurrentProcess();
292 
293     // Iterate through modules in this process looking for TCL / Tk names
294     if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
295         for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
296             if (!found_tcl) {
297                 found_tcl = get_tcl(hMods[i]);
298                 if (found_tcl == -1) {
299                     return 1;
300                 }
301             }
302             if (!found_tk) {
303                 found_tk = get_tk(hMods[i]);
304                 if (found_tk == -1) {
305                     return 1;
306                 }
307             }
308             if (found_tcl && found_tk) {
309                 return 0;
310             }
311         }
312     }
313 
314     if (found_tcl == 0) {
315         PyErr_SetString(PyExc_RuntimeError, "Could not find TCL routines");
316     } else {
317         PyErr_SetString(PyExc_RuntimeError, "Could not find Tk routines");
318     }
319     return 1;
320 }
321 
322 #else  // not Windows
323 
324 /*
325  * On Unix, we can get the TCL and Tk synbols from the tkinter module, because
326  * tkinter uses these symbols, and the symbols are therefore visible in the
327  * tkinter dynamic library (module).
328  */
329 #if PY_MAJOR_VERSION >= 3
330 #define TKINTER_PKG "tkinter"
331 #define TKINTER_MOD "_tkinter"
332 // From module __file__ attribute to char *string for dlopen.
fname2char(PyObject * fname)333 char *fname2char(PyObject *fname)
334 {
335     PyObject* bytes;
336     bytes = PyUnicode_EncodeFSDefault(fname);
337     if (bytes == NULL) {
338         return NULL;
339     }
340     return PyBytes_AsString(bytes);
341 }
342 #else
343 #define TKINTER_PKG "Tkinter"
344 #define TKINTER_MOD "tkinter"
345 // From module __file__ attribute to char *string for dlopen
346 #define fname2char(s) (PyString_AsString(s))
347 #endif
348 
349 #include <dlfcn.h>
350 
_dfunc(void * lib_handle,const char * func_name)351 void *_dfunc(void *lib_handle, const char *func_name)
352 {
353     // Load function `func_name` from `lib_handle`.
354     // Set Python exception if we can't find `func_name` in `lib_handle`.
355     // Returns function pointer or NULL if not present.
356 
357     void* func;
358     // Reset errors.
359     dlerror();
360     func = dlsym(lib_handle, func_name);
361     if (func == NULL) {
362         const char *error = dlerror();
363         PyErr_SetString(PyExc_RuntimeError, error);
364     }
365     return func;
366 }
367 
_func_loader(void * lib)368 int _func_loader(void *lib)
369 {
370     // Fill global function pointers from dynamic lib.
371     // Return 1 if any pointer is NULL, 0 otherwise.
372     return (
373          ((TCL_CREATE_COMMAND = (Tcl_CreateCommand_t)
374            _dfunc(lib, "Tcl_CreateCommand")) == NULL) ||
375          ((TCL_APPEND_RESULT = (Tcl_AppendResult_t)
376            _dfunc(lib, "Tcl_AppendResult")) == NULL) ||
377          ((TK_MAIN_WINDOW = (Tk_MainWindow_t)
378            _dfunc(lib, "Tk_MainWindow")) == NULL) ||
379          ((TK_FIND_PHOTO = (Tk_FindPhoto_t)
380            _dfunc(lib, "Tk_FindPhoto")) == NULL) ||
381          ((TK_PHOTO_PUT_BLOCK_NO_COMPOSITE = (Tk_PhotoPutBlock_NoComposite_t)
382            _dfunc(lib, "Tk_PhotoPutBlock_NoComposite")) == NULL) ||
383          ((TK_PHOTO_BLANK = (Tk_PhotoBlank_t)
384            _dfunc(lib, "Tk_PhotoBlank")) == NULL));
385 }
386 
load_tkinter_funcs(void)387 int load_tkinter_funcs(void)
388 {
389     // Load tkinter global funcs from tkinter compiled module.
390     // Return 0 for success, non-zero for failure.
391     int ret = -1;
392     void *main_program, *tkinter_lib;
393     char *tkinter_libname;
394     PyObject *pModule = NULL, *pSubmodule = NULL, *pString = NULL;
395 
396     // Try loading from the main program namespace first
397     main_program = dlopen(NULL, RTLD_LAZY);
398     if (_func_loader(main_program) == 0) {
399         return 0;
400     }
401     // Clear exception triggered when we didn't find symbols above.
402     PyErr_Clear();
403 
404     // Now try finding the tkinter compiled module
405     pModule = PyImport_ImportModule(TKINTER_PKG);
406     if (pModule == NULL) {
407         goto exit;
408     }
409     pSubmodule = PyObject_GetAttrString(pModule, TKINTER_MOD);
410     if (pSubmodule == NULL) {
411         goto exit;
412     }
413     pString = PyObject_GetAttrString(pSubmodule, "__file__");
414     if (pString == NULL) {
415         goto exit;
416     }
417     tkinter_libname = fname2char(pString);
418     if (tkinter_libname == NULL) {
419         goto exit;
420     }
421     tkinter_lib = dlopen(tkinter_libname, RTLD_LAZY);
422     if (tkinter_lib == NULL) {
423         /* Perhaps it is a cffi module, like in PyPy? */
424         pString = PyObject_GetAttrString(pSubmodule, "tklib_cffi");
425         if (pString == NULL) {
426             goto fail;
427         }
428         pString = PyObject_GetAttrString(pString, "__file__");
429         if (pString == NULL) {
430             goto fail;
431         }
432         tkinter_libname = fname2char(pString);
433         if (tkinter_libname == NULL) {
434             goto fail;
435         }
436         tkinter_lib = dlopen(tkinter_libname, RTLD_LAZY);
437     }
438     if (tkinter_lib == NULL) {
439         goto fail;
440     }
441     ret = _func_loader(tkinter_lib);
442     // dlclose probably safe because tkinter has been imported.
443     dlclose(tkinter_lib);
444     goto exit;
445 fail:
446     PyErr_SetString(PyExc_RuntimeError,
447             "Cannot dlopen tkinter module file");
448 exit:
449     Py_XDECREF(pModule);
450     Py_XDECREF(pSubmodule);
451     Py_XDECREF(pString);
452     return ret;
453 }
454 #endif // end not Windows
455 
456 #if PY_MAJOR_VERSION >= 3
457 static PyModuleDef _tkagg_module = { PyModuleDef_HEAD_INIT, "_tkagg", "",   -1,  functions,
458                                      NULL,                  NULL,     NULL, NULL };
459 
PyInit__tkagg(void)460 PyMODINIT_FUNC PyInit__tkagg(void)
461 {
462     PyObject *m;
463 
464     m = PyModule_Create(&_tkagg_module);
465 
466     return (load_tkinter_funcs() == 0) ? m : NULL;
467 }
468 #else
init_tkagg(void)469 PyMODINIT_FUNC init_tkagg(void)
470 {
471     Py_InitModule("_tkagg", functions);
472 
473     load_tkinter_funcs();
474 }
475 #endif
476