Reputation: 63
Is it possible to draw on a Cairo Surface and then display that as a tkinter.PhotoImage? Could somebody provide an example or at least a description of how one would do this?
Upvotes: 4
Views: 2415
Reputation: 31
@zeronineseven's answer works, but I found I needed to further call the tobytes()
function, otherwise a pointer is returned which Image.frombuffer
cannot understand. Maybe this is because of some update in Cairo or PIL in the meantime?
Also, the base64 gif method could work with PNG instead on Tk8+ (Python 3.4+??), allowing for full colour map.
self._image_ref = ImageTk.PhotoImage(Image.frombuffer("RGBA", (w, h), self.surface.get_data().tobytes(), "raw", "BGRA", 0, 1))
Upvotes: 0
Reputation: 169
There are quite a few ways to achieve what you want and tkinter.PhotoImage is definitely not the best one, unless you really have to use it. The simplest one is to use ImageTK module from Pillow library:
from tkinter import Tk, Label
from PIL import Image, ImageTk
from cairo import ImageSurface, Context, FORMAT_ARGB32
class ExampleGui(Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
w, h = 800, 600
self.geometry("{}x{}".format(w, h))
self.surface = ImageSurface(FORMAT_ARGB32, w, h)
self.context = Context(self.surface)
# Draw something
self.context.scale(w, h)
self.context.rectangle(0, 0, 1, 1)
self.context.set_source_rgba(1, 0, 0, 0.8)
self.context.fill()
self._image_ref = ImageTk.PhotoImage(Image.frombuffer("RGBA", (w, h), self.surface.get_data(), "raw", "BGRA", 0, 1))
self.label = Label(self, image=self._image_ref)
self.label.pack(expand=True, fill="both")
self.mainloop()
if __name__ == "__main__":
ExampleGui()
Otherwise you can do it by converting cairo's surface to base64-encoded GIF image(either with the help of Pillow or manually, which is going to be a little bit time consuming) and passing the result as "data" arg to tkinter.PhotoImage constructor(untested!):
from io import BytesIO
from PIL import Image
from cairo import ImageSurface, Context, FORMAT_ARGB32
w, h = 800, 600
surface = ImageSurface(FORMAT_ARGB32, w, h)
context = Context(surface)
output = io.BytesIO()
image = Image.frombuffer("RGBA", (w, h), self.surface.get_data(), "raw", "BGRA", 0, 1)
image.save(output, format="gif")
b64 = base64.b64encode(output.read())
Note: On big-endian machines "ARGB" mode should be used for the source data AFAIK.
Upvotes: 3