Q:

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.

A:

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...

