3

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.

4

1 回答 1

3

事实证明它相当简单,但从阅读文档来看一点也不明显:

# 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)

我最初的解决方案,留在这里用于存档目的;这更复杂并且不适用于透明度,但它可能更适合更高级的操作

经过更多的讨论后,我最终使用了GdkPixbuf.PixbufLoader.new_with_type, 来自:

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

我选择了最简单的图像格式,它似乎是“便携式 anymap 格式”或pnm.

我用 GIMP 创建了一个简单的 1x1 图像,它给了我以下数据:

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

最后 3 个字节 ( \xff\x00\xff) 是我选择的颜色。

我最终使用的完整示例:

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()

然后我可以fill像在原始示例中一样使用fill.copy_area()

这是一种解决方法,但可以接受...

PS。
从文档来看,GdkPixbuf.Pixbuf.new_from_data看起来是最好的选择,但它似乎坏了......无论我做什么我都无法工作......

于 2014-11-04T23:22:16.483 回答