Convert a C or numpy array to a Tkinter PhotoImage with a minimum number of copies
I can reduce it to 1 (maybe 2) copies by using PIL and a Label:
import numpy as npimport tkinter as tkfrom PIL import Image, ImageTka = np.random.randint(low=255, size=(100, 100, 3), dtype=np.uint8) # Originalroot = tk.Tk()img = ImageTk.PhotoImage(Image.fromarray(a)) # First and maybe second copy.lbl = tk.Label(root, image=img)lbl.pack()root.mainloop()
However that's still not mutable. If you want that I think you need to reinvent an image by placing a pixel on the canvas yourself. I did that once with this project and found that the fastest update was a matplotlib animation, which works really well for you since you are already using np arrays.
My code for using a tk.Canvas, a PIL Image(using putpixel()), and matplotlib.
- you can eliminate the 1st and 2nd copies
You get a numpy.ndarray
over arbitrary data with numpy.frombuffer
:
shape=(100,100,3)ppm_header = b'P6\n%i %i\n255\n'%(shape[0], shape[1])ppm_bytes = ppm_header + b'\0'*(shape[0]*shape[1]*shape[2])array_image = np.frombuffer(ppm_bytes, dtype=np.uint8, offset=len(ppm_header)).reshape(shape)
the 3rd and 4th copies are inevitable (see below), but the 3rd one is discarded right after the call
the 5th copy is not actually made (also see below)
the drawing stage involves a copy to the screen via the windowing system's drawing API which is also inevitable.
Tcl is a safe, garbage-collected language like Python, and Tcl objects don't support either a "buffer protocol", or using memory for their data that they don't own (though objects can be shared.
img = tk.PhotoImage(data=ppm_bytes) # Third and fourth copies?
When making most Tcl calls, Python variables are first converted into Equivalent Tcl objects (first into C values which doesn't involve copying for a bytestring which are then passed to Tcl constructors which does involve a copy), then these objects are passed to
Tcl_EvalObjv
.On the Tcl side,
photo
(whichPhotoImage()
wraps) also parses input data (-data
string argument) and as such, also cannot reuse its memory block. Even if it's a raw bitmap 'cuz Tcl strings have no "view" functionality.So, for a bytestring, there's one mandatory copy of the bitmap data involved at Pythob-to-Tcl call stage, and one more at image construction stage. (On the bright side, one copy (the argument string for the Tcl call) is discarded after the call.)
canvas.create_image(0, 0, anchor=tk.NW, image=img) # Fifth copy?
A copy is not involved here, the canvas just saves a reference to the image object in its composition data.
Now,
canvas create image
requires a Tcl image identifier asimage
argument; so there's no way aroundPhotoImage()
, either.(drawing stage)
xlib
's commands to draw a bitmap are used, no additional copies are involved.The thing to note here is that a canvas doesn't even have direct access to the resulting screen pixels. Instead,
xlib
uses backend's graphical API for drawing (like WinGDI'sGetDC()
,BitBlt()
etc that you might be familiar with). E.g. in Windows,xlib
'sXCopyArea
usesBitBlt
.