I know a recipe for displaying an MxNx3 numpy array as an RGB image via Tkinter, but my recipe makes several copies of the array in the process:
a = np.random.randint(low=255, size=(100, 100, 3), dtype=np.uint8) # Original
ppm_header = b'P6\n%i %i\n255\n'%(a.shape[0], a.shape[1])
a_bytes = a.tobytes() # First copy
ppm_bytes = ppm_header + a_bytes # Second copy https://en.wikipedia.org/wiki/Netpbm_format
root = tk.Tk()
img = tk.PhotoImage(data=ppm_bytes) # Third and fourth copies?
canvas = tk.Canvas(root, width=a.shape[0], height=a.shape[1])
canvas.pack()
canvas.create_image(0, 0, anchor=tk.NW, image=img) # Fifth copy?
root.mainloop()
How can I achieve an equivalent result with a minimum number of copies?
Ideally, I would create a numpy array which was a view of the same bytes that the Tkinter PhotoImage
object was using, effectively giving me a PhotoImage
with mutable pixel values, and making it cheap and fast to update the Tkinter display. I don't know how to extract this pointer from Tkinter.
Perhaps there's a way via ctypes, as hinted at here?
The PhotoImage.put()
method seems very slow, but maybe I'm wrong, and that's a path forward?
I tried making a bytearray()
containing the ppm header and the image pixel values, and then using numpy.frombuffer()
to view the image pixel values as a numpy array, but I think the PhotoImage
constructor wants a bytes()
object, not a bytearray()
object, and also I think Tkinter copies the bytes of its data
input into its internal format (32-bit RGBA?). I guess this saves me one copy compared to the recipe above?
Tk_PhotoPutBlock()
(here). Assembling the arguments for it… well, that's a bit trickier. The handle can be got fromTk_FindPhoto()
, but the interp is buried inside Tkinter (the Python-to-Tcl/Tk wrapping library) and I don't know that well enough to help. Also, you MUST use that code from only one thread or you get major trouble (a common Python/Tkinter issue). – Salamone