Search code examples
pythongtk3pygobject

How to draw a GdkPixbuf using GTK3 and PyGObject


I have a small application that uses a DrawingArea to draw a simple map using PyGObject and GTK3.

I load a Pixbuf using

from gi.repository import Gtk, GdkPixbuf
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size("logo.png", 25, 25)

and then try to draw it in the DrawingArea's draw event signal

def draw(self, widget, context):
    window = widget.get_window()
    ctx = window.cairo_create()
    ctx.set_source_pixbuf(pixbuf, 0, 0)

but I get the error message

"AttributeError: 'cairo.Context' object has no attribute 'set_source_pixbuf'"

If I'm reading the Gtk2 to Gtk3 migration guide correctly, this should work. What am I doing wrong?


Solution

  • The new draw signal uses a callback that already passes the cairo context as a parameter, you don't need to do stuff like window = widget.get_window() like you did in PyGtk to get the cairo context while attending the expose-event signal. In PYGObject is simpler:

    import cairo
    
    class Foo(object):
        def __init__(self):
    
           (...)
            self.image = cairo.ImageSurface.create_from_png('logo.png')
           (...)
    
        def draw(self, widget, context):
            if self.image is not None:
                context.set_source_surface(self.image, 0.0, 0.0)
                context.paint()
            else:
                print('Invalid image')
            return False
    

    That is if you don't need the PixBuf, but if you need it for something else you have several options:

    1. To have both objects in memory. If both are loaded from a PNG there should not be much problems other than the waste of memory.
    2. Convert GdkPixbuf to PIL Image, then PIL Image to data array and then create a Cairo ImageSurface from that data array using create_for_data(). Yak :S I don't know better, sorry :S
    3. Use Gdk.cairo_set_source_pixbuf() proposed by hock. This seems to be the correct way to draw a Pixbuf in a ImageSurface, but it is totally unpythonic (and that's why I hate this Introspection stuff, all looks like C, like a bad C port).

    If you choose the awful second option here is how:

    import Image
    import array
    from gi.repository import Gtk, GdkPixbuf
    
    width = 25
    height = 25
    pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size('logo.png', width, height)
    pil_image = Image.fromstring('RGBA', (width, height), pixbuf.get_pixels())
    byte_array = array.array('B', pil_image.tostring())
    cairo_surface = cairo.ImageSurface.create_for_data(byte_array, cairo.FORMAT_ARGB32, width, height, width * 4)
    

    Note that create_for_data() is not yet available for Python3, only for Python2.

    Check also my answer on how to use a double buffer in PyGObject if this is what you're trying to achieve: Drawing in PyGobject (python3)

    Kind regards