简体   繁体   中英

Change the colour of pixels in a GdkPixbuf (GTK3)

I'm using Gtk.StatusIcon , and want to change the colour of some pixels; I have a working piece of code, this loads a 1x1 pixel PNG file with the colour I want to set, and then copies that to thhe icon Pixbuf.
While this works, it has the obvious drawback of having to create a 1x1 pixel for every colour, so I can't use an arbitrary colour, only the predefined colours I created images for.

How do I set a pixel to an arbitrary RGBA colour?

The code I'm using now (simplified for demonstration purposes):

#!/usr/bin/env python

from gi.repository import Gtk, GLib, GdkPixbuf

def set_icon_cb(widget, data=None):
    set_icon(widget)

def set_icon(icon):
    fill_amount = 20
    img = GdkPixbuf.Pixbuf.new_from_file('./data/icons/battery.png')
    fill = GdkPixbuf.Pixbuf.new_from_file('./data/icons/green.png')

    for row_num, row in enumerate(zip(*(iter(img.get_pixels()),) *img.get_rowstride())):
        # Blank row
        if 255 not in row: continue

        for col_num, pixel in enumerate(zip(*(iter(row),) * 4)):
            r, g, b, a = pixel

            if col_num > 2 and col_num <= fill_amount and a == 0:
                fill.copy_area(0, 0, 1, 1, img, col_num, row_num)

    icon.set_from_pixbuf(img)


icon = Gtk.StatusIcon()
icon.connect('activate', set_icon_cb)
set_icon(icon)

Gtk.main()

I tried creating a new Pixbuf object with GdkPixbuf.PixbufLoader , but this seems to expect a PNG image, not a bytes object, so this isn't very helpful:

fill = GdkPixbuf.PixbufLoader()
fill.write(b'\x88\x88\x88\xff')
fill.close()
fill = fill.get_pixbuf()

# Gives error:
# GLib.Error: gdk-pixbuf-error-quark: Unrecognized image file format (3)

My next try was to use GdkPixbuf.Pixbuf.new_from_data , which looked promossing:

fill = GdkPixbuf.Pixbuf.new_from_data(b'\xff\x00\xff', GdkPixbuf.Colorspace.RGB,
    False, 8, 1, 1, 3)

However, this doesn't work either. It not only sets the pixels to the wrong colour, it also sets it to different colours on multiple invocations of set_icon() ; print(fill.get_pixels()) gives me b'\\x00\\x00\\x00' ... Am I using this wrong? I tied various different parameters, but all give the same result...

I also found a C example which modified the result of gdk_pixbuf_get_pixels() , since this returns a pointer to the image data. But this is not something you can do in Python (AFAIK).

A little background of what I'm trying to accomplish:
I have a little tray application to show my laptop's battery status; it fills up the battery icon to give an indication of how much battery power is left. Below a certain percentage the colour changes from green to red (this works with the above example), but I would like to have more flexibility in this; eg. allowing other people to set their own shade of green, or use purple, or whatever.

It turned out to be fairly simple, but not at all obvious from reading the docs:

# red, green, blue, alpha
color = 0xeeff2dff

# Create blank 1x1 image
fill = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 1, 1)

# Fill entire image with color
fill.fill(color)

My original solution, left here for archival purposes; this is more complicated & doesn't work with transparency, but it might be better suited for more advanced operations

After more mucking about I ended up using GdkPixbuf.PixbufLoader.new_with_type , from:

list(map(lambda x: x.get_name(), GdkPixbuf.Pixbuf.get_formats()))

I choose the simplest image format, which appears to be the "portable anymap format" or pnm .

I created a simple 1x1 image with GIMP, which gave me this data:

>>> open('test.pnm', 'rb').read()
b'P6\n# CREATOR: GIMP PNM Filter Version 1.1\n1 1\n255\n\xff\x00\xff'

The last 3 bytes ( \\xff\\x00\\xff ) is the colour I choose.

Full example I ended up using:

color = b'\xee\xff\x2d'

px = GdkPixbuf.PixbufLoader.new_with_type('pnm')
px.write(b'P6\n\n1 1\n255\n' + color)
px.write(color)
px.close()
fill = px.get_pixbuf()

I can then use fill as in the original example with fill.copy_area()

It's a bit of a workaround, but acceptable...

PS.
From the documentation, GdkPixbuf.Pixbuf.new_from_data looks like the best option to do this, but it seems broken... I can't it to work no matter what I do...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM