Skip to content

Instantly share code, notes, and snippets.

@mozbugbox
Last active October 15, 2023 14:05
Show Gist options
  • Save mozbugbox/10cd35b2872628246140 to your computer and use it in GitHub Desktop.
Save mozbugbox/10cd35b2872628246140 to your computer and use it in GitHub Desktop.
Python Pillow image to/from GdkPixbuf with gobject introspection
#!/usr/bin/python3
# vim:fileencoding=utf-8:sw=4:et
# Convert between pygobject Pixbuf and PIL/Pillow image format
# Also a function to do fast gamma correction with Pillow image
from __future__ import print_function, unicode_literals, absolute_import
import sys
from gi.repository import GLib, GdkPixbuf
from PIL import Image
def pixbuf2image(pix):
"""Convert gdkpixbuf to PIL image"""
data = pix.get_pixels()
w = pix.props.width
h = pix.props.height
stride = pix.props.rowstride
mode = "RGB"
if pix.props.has_alpha == True:
mode = "RGBA"
im = Image.frombytes(mode, (w, h), data, "raw", mode, stride)
return im
def image2pixbuf(im):
"""Convert Pillow image to GdkPixbuf"""
data = im.tobytes()
w, h = im.size
data = GLib.Bytes.new(data)
pix = GdkPixbuf.Pixbuf.new_from_bytes(data, GdkPixbuf.Colorspace.RGB,
False, 8, w, h, w * 3)
return pix
def do_gamma(im, gamma):
"""Fast gamma correction with PIL's image.point() method"""
invert_gamma = 1.0/gamma
lut = [pow(x/255., invert_gamma) * 255 for x in range(256)]
lut = lut*3 # need one set of data for each band for RGB
im = im.point(lut)
return im
def test_i2p(infile, outfile):
im = Image.open(infile)
pix = image2pixbuf(im)
pix.savev(outfile, "png", (), ())
def test_p2i(infile, outfile):
pix = GdkPixbuf.Pixbuf.new_from_file(infile)
im = pixbuf2image(pix)
# FIXME: We also test gamma correction here
im = do_gamma(im, 1.8)
im.save(outfile)
def main():
infile = sys.argv[1]
outfile = "pixpil-test.png"
test_p2i(infile, outfile)
#test_i2p(infile, outfile)
print("result saved to {}".format(outfile))
if __name__ == '__main__':
main()
@seanrostami
Copy link

Hi mozbugbox. There seems to be an issue in GDK such that your image2pixbuf will indirectly result in a memory leak. Christopher Reiter from gitlab.gnome.org/GNOME/pygobject helped me understand the details and suggested this easy workaround, which I thought you probably want to know: replace pixbuf by pixbuf.copy() either inside or outside.

Here is a short script to highlight the problem:

from PIL import Image, ImageColor

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

from pixbuf2pillow import image2pixbuf

def cb( gtkimage, pilimage ):
	if cb.counter > 60: # run for 1 minute
		return False # discontinue timeout
	cb.counter += 1
	gtkimage.set_from_pixbuf( image2pixbuf( pilimage ) ) # yes leak
	##gtkimage.set_from_pixbuf( image2pixbuf( pilimage ).copy() ) # no leak (can also use .copy inside image2pixbuf)
	return True # continue timeout

topgtkwindow = Gtk.Window( title = "TEST" )
topgtkwindow.connect( "destroy", Gtk.main_quit )

gtkimage = Gtk.Image()
topgtkwindow.add( gtkimage )
topgtkwindow.show_all()

pilimage = Image.new( "RGB", (1200,900), ImageColor.getrgb("Indigo") )

cb.counter = 0
GLib.timeout_add( 1000, cb, gtkimage, pilimage ) # run every 1 second

Gtk.main() # check RAM usage

Best wishes.

[details: https://gitlab.gnome.org/GNOME/pygobject/issues/225]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment