ikn
ikn

Reputation: 123

Displaying an image with Pygobject and Python 3 from in-memory data

I have some RGBA data in Python 3 and I want to display the image it represents in a GTK3 window without using any extra libraries.

First thing I tried was editing a Pixbuf's data as in an example (http://developer.gnome.org/gdk-pixbuf/stable/gdk-pixbuf-The-GdkPixbuf-Structure.html#put-pixel - sorry, I'm only allowed 2 links) in the (C) documentation, using Pixbuf.get_pixels(). This gives me an immutable bytes object, though, and so obviously won't work. (A gdk-pixbuf bug report (668084, would link to it if SO would let me) covers the same function, but stuff must've changed a bit since.)

Next, I tried creating a Pixbuf from the data using Pixbuf.new_from_data(), but this is buggy too (see gdk-pixbuf bug 674691) (yes, that's me in comment 3).

Then I looked at Cairo: ImageSurface.create_for_data should do this, but for some reason this isn't available in the Python 3 binding yet. (I have tried this with Python 2, turning the surface into a Pixbuf afterwards and then wrapping that in a gtk.Image, and it works. But I'm using Python3 (and even then, it's messy - Cairo is a vector graphics library, after all)).

I found a reference somewhere to using PIL to write the image out to a PNG file then reading that into a Pixbuf, but this is horrible, uses an extra library, and PIL isn't available for Python 3 anyway.

So...any other methods?

Working code

Based on Havok's answer, here's a working example:

from array import array

from gi.repository import Gtk as gtk, GdkPixbuf

pixels = array('H')
for i in range(20):
    for j in range(20):
        px = (i < 10, j >= 10, (i < 10) ^ (j < 10))
        pixels.extend(65535 * c for c in px)

header = b'P6 20 20 65535 '
img_data = header + pixels

w = gtk.Window()
w.connect('delete-event', gtk.main_quit)
l = GdkPixbuf.PixbufLoader.new_with_type('pnm')
l.write(img_data)
w.add(gtk.Image.new_from_pixbuf(l.get_pixbuf()))
l.close()
w.show_all()
gtk.main()

Edit: actually, for correctness, I think this will need a pixels.byteswap() on little-endian systems before adding to the header (though of course it doesn't matter with these colours).

Upvotes: 12

Views: 4056

Answers (3)

Boris Burkov
Boris Burkov

Reputation: 14446

I don't have gtk3 installed, but in gtk2 it worked perfectly fine:

import gtk
import cairo


def main():
    window = gtk.Window(gtk.WINDOW_TOPLEVEL)
    window.set_title("Drawing Area")
    window.connect("destroy", lambda w:gtk.main_quit())

    sw = gtk.ScrolledWindow()
    area = Area()
    area.show()
    area.connect('expose-event', area.expose_handler)
    sw.add_with_viewport(area)
    window.add(sw)
    window.show_all()


class Area(gtk.DrawingArea):
    def __init__(self):
        super(Area, self).__init__()
        self.pixbuf = gtk.gdk.pixbuf_new_from_file("hard_drive_elements.png")

    def expose_handler(self, widget, event):
        cr = self.window.cairo_create()
        cr.set_operator(cairo.OPERATOR_SOURCE)
        cr.set_source_rgb(1,1,1)
        cr.paint()
        cr.set_source_pixbuf(self.pixbuf, 0, 0)
        cr.paint()


if __name__ == "__main__":
    main()
    gtk.main()

Drawing_area_from_pixbuf

Upvotes: 3

Lassi
Lassi

Reputation: 3960

This now works for me in Python 3:

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GdkPixbuf

pixels = open('test.raw', 'rb').read()
depth = 8
width = 460
height = 460
stride = width * 3
win = Gtk.Window()
win.connect('delete-event', Gtk.main_quit)
win.add(Gtk.Image.new_from_pixbuf(
    GdkPixbuf.Pixbuf.new_from_data(pixels, GdkPixbuf.Colorspace.RGB,
                                   False, depth, width, height, stride)))
win.show_all()
Gtk.main()

You can create a .raw image from JPEG or other common image formats using ImageMagick:

convert test.jpeg -depth 8 -size 460x460 RGB:test.raw

I didn't check whether alpha channel (transparency) data can be loaded but new_from_data() takes a boolean flag for that.

Upvotes: 0

Havok
Havok

Reputation: 5882

Wow, this will be a hard one, pretty hard.

Neither PIL or Cairo's ImageSurface.create_from_data() are available for Python 3. Your only option left is using GdkPixbuf directly, but as you said this is not a perfect solution, in particular because you're expecting to use the alpha channel. My only thought is that you try to use this:

http://developer.gnome.org/gdk-pixbuf/stable/GdkPixbufLoader.html#gdk-pixbuf-loader-new-with-type

As I did at the end of here: Converting PIL GdkPixbuf

loader = GdkPixbuf.PixbufLoader.new_with_type('pnm')
loader.write(contents)
pixbuf = loader.get_pixbuf()
loader.close()

And try to see what types are supported, could not find the PyGObject version of what the C documentation says about gdk_pixbuf_format_get_name() and gdk_pixbuf_get_formats(). With the pnm format you will not have the alpha channel.

EDIT

Great you found the PyGObject functions, posting here the output for documentation purposes:

>>> from gi.repository import GdkPixbuf
>>> formats = GdkPixbuf.Pixbuf.get_formats()
>>> [f.get_name() for f in formats]
['GdkPixdata', 'ras', 'tiff', 'wmf', 'icns', 'ico', 'png', 'qtif', 
 'wbmp', 'gif', 'pnm', 'tga', 'ani', 'xbm', 'xpm', 'jpeg2000', 
 'pcx', 'jpeg', 'bmp', 'svg']

Upvotes: 6

Related Questions