Convert symbolic icon to gdktexture/gdkpixbuf (original) (raw)
Firefox just got support for native gtk symbolic icons (quite crazy after so many years :), looks really good, ie window close button etc) but as that is gtk3 I was thinking of how it would be done in gtk4? I thought it would be simple but it turns out to be quite complicated. But after some reading and tinkering I finally managed to make it work without warnings (gtkimage only used for testing).
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk
gi.require_version('Gdk', '4.0')
from gi.repository import Gdk
from gi.repository import Gsk
from gi.repository import GdkPixbuf
class Main:
def __init__(self, app):
self.mainwin = Gtk.ApplicationWindow.new(app)
_display = Gdk.Display.get_default()
icon_theme = Gtk.IconTheme.get_for_display(_display)
icon_paint = icon_theme.lookup_icon("go-next-symbolic", None, 32, 1, 0, 0)
_rgba = Gdk.RGBA()
_rgba.parse("#d52")
_snapshot = Gtk.Snapshot.new()
icon_paint.snapshot_symbolic(_snapshot, 32, 32, [_rgba])
_surface = Gdk.Surface.new_toplevel(_display)
_renderer = Gsk.Renderer.new_for_surface(_surface)
_texture = _renderer.render_texture(_snapshot.to_node(), None)
_bytes = _texture.save_to_tiff_bytes()
_stride = GdkPixbuf.Pixbuf.calculate_rowstride(0, True, 8, 32, 32)
_pixbuf = GdkPixbuf.Pixbuf.new_from_bytes(_bytes, 0, True, 8, 32, 32, _stride)
_image = Gtk.Image.new_from_pixbuf(_pixbuf)
self.mainwin.set_child(_image)
self.mainwin.set_default_size(400, 300)
self.mainwin.set_visible(True)
app = Gtk.Application()
app.connect('activate', Main)
app.run(None)
But this looks very complicated though, especially the need for creating a gdksurface just to produce a gskrenderer. And so many steps (snapshot-rendernode-texture-tiff-pixbuf) involved. Maybe this could be done in a simpler way? Something like gtksnapshot.to_texture would be nice but couldn’t find it. Or a method in gtkiconpaintable that would return a gdktexture directly (in gtk3 gtkiconinfo.load_symbolic returns a gdkpixbuf).
And Firefox does not seem to even use gdkpixbuf as it just directly converts it into a “bytebuf” with the MozGdkPixbufToByteBuf function. Would it not be possible (easily :)) to port that into taking a gdktexture instead?
Anyways, I put this example out there as I could not find anything similar.
jensgeorg (Jens Georg) June 9, 2025, 3:20pm 2
This is what I do currently, it’s marginally better, but not by much:
private Gdk.Pixbuf? get_icon_trinket(string icon_name, int scale) {
var theme = Gtk.IconTheme.get_for_display(AppWindow.get_instance().get_display());
var paintable = theme.lookup_icon(icon_name, null, 1, scale * 2, Gtk.TextDirection.NONE, Gtk.IconLookupFlags.PRELOAD);
var snapshot = new Gtk.Snapshot();
paintable.snapshot_symbolic(snapshot, scale * 2, scale * 2, {{0.8f, 0.8f, 0.8f, 1.0f}});
var node = snapshot.free_to_node();
var surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, scale * 2, scale * 2);
var ctx = new Cairo.Context(surface);
ctx.set_source_rgba(0.0, 0.0, 0.0, 0.35);
ctx.rectangle(0, 0, scale * 2, scale * 2);
ctx.fill();
ctx.move_to(0,0);
node.draw(ctx);
ctx.paint();
return Gdk.pixbuf_get_from_surface(surface, 0, 0, scale * 2, scale * 2);
}
matthiasc (Matthias Clasen) June 9, 2025, 10:49pm 3
Why do you need a texture?
Here is some symbolic paintable code: Matthias Clasen / gtk-image-support · GitLab
xerxes2 (Jens Persson) June 10, 2025, 10:06am 4
Yeah this solution actually solves my biggest complaint with having to use a gskrenderer. It does use cairo and gives a deprecation warning on the last line but you can probably live with that. I include it in the example python code.
import gi, cairo
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk
gi.require_version('Gdk', '4.0')
from gi.repository import Gdk
from gi.repository import Gsk
from gi.repository import GdkPixbuf
class Main:
def __init__(self, app):
self.mainwin = Gtk.ApplicationWindow.new(app)
_display = Gdk.Display.get_default()
icon_theme = Gtk.IconTheme.get_for_display(_display)
_size = 32
icon_paint = icon_theme.lookup_icon("go-next-symbolic", None, _size, 1, 0, 0)
_rgba = Gdk.RGBA()
_rgba.parse("#d52")
_snapshot = Gtk.Snapshot.new()
icon_paint.snapshot_symbolic(_snapshot, _size, _size, [_rgba])
_node = _snapshot.to_node()
#_surface = Gdk.Surface.new_toplevel(_display)
#_renderer = Gsk.Renderer.new_for_surface(_surface)
#_texture = _renderer.render_texture(_node, None)
#_bytes = _texture.save_to_tiff_bytes()
#_stride = GdkPixbuf.Pixbuf.calculate_rowstride(0, True, 8, _size, _size)
#_pixbuf = GdkPixbuf.Pixbuf.new_from_bytes(_bytes, 0, True, 8, _size, _size, _stride)
_surface = cairo.ImageSurface(cairo.Format.ARGB32, _size, _size)
_ctx = cairo.Context(_surface)
_node.draw(_ctx)
_pixbuf = Gdk.pixbuf_get_from_surface(_surface, 0, 0, _size, _size)
_image = Gtk.Image.new_from_pixbuf(_pixbuf)
self.mainwin.set_child(_image)
self.mainwin.set_default_size(400, 300)
self.mainwin.set_visible(True)
app = Gtk.Application()
app.connect('activate', Main)
app.run(None)
xerxes2 (Jens Persson) June 10, 2025, 10:18am 5
Well, I need a gdkpixbuf but it seems to be deprecated in gtk4 so I thought I could settle for a gdktexture instead. In gtk3 this was a one-liner but it seems to be much more complicated in gtk4. A gtksnapshot.to_texture method would be fine but a gtkiconpaintable.load_symbolic method that returns a gdktexture would be even better. Similar to gtkiconinfo.load_symbolic in gtk3.
xerxes2 (Jens Persson) June 13, 2025, 12:32pm 6
It says in the docs that it’s possible to use a gskrenderer without having to create a gdksurface. But the new_for_surface constructor does not take null as argument. Should there not be a plain gskrenderer.new constructor too?
xerxes2 (Jens Persson) June 15, 2025, 1:18pm 7
So I got around to implement this now, topic is back to being a one-liner again. The python example now looks like this.
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk
gi.require_version('Gdk', '4.0')
from gi.repository import Gdk
from gi.repository import Gsk
from gi.repository import GdkPixbuf
class Main:
def __init__(self, app):
self.mainwin = Gtk.ApplicationWindow.new(app)
_display = Gdk.Display.get_default()
icon_theme = Gtk.IconTheme.get_for_display(_display)
_size = 32
icon_paint = icon_theme.lookup_icon("go-next-symbolic", None, _size, 1, 0, 0)
_rgba = Gdk.RGBA()
_rgba.parse("#d52")
_texture = icon_paint.load_symbolic(None, _size, _size, [_rgba])
_bytes = _texture.save_to_tiff_bytes()
_stride = GdkPixbuf.Pixbuf.calculate_rowstride(0, True, 8, _size, _size)
_pixbuf = GdkPixbuf.Pixbuf.new_from_bytes(_bytes, 0, True, 8, _size, _size, _stride)
_viewer = Gtk.Picture.new_for_pixbuf(_pixbuf)
self.mainwin.set_child(_viewer)
self.mainwin.set_default_size(400, 300)
self.mainwin.set_visible(True)
app = Gtk.Application()
app.connect('activate', Main)
app.run(None)
If you think this is a good idea I will supply patches. The big one is gskrenderer.new_for_display as it is a bit hard locked into gdksurface but it seems fine to use a gdkdisplay too. It’s not that many lines so fairly easy to review but still more changes than I initially wanted to make. The other 2 are easier and low risk. And gtksnapshot.to_texture will basically make it a one-liner to save a screenshot too. Adding gtkiconpaintable.load_symbolic is not extremely important but wouldn’t hurt either as it saves a few lines and would take it back to the same api (using gdktexture instead) as in gtk3.
ebassi (Emmanuele Bassi) June 15, 2025, 1:36pm 8
You are going through a lot of hassle for no reason whatsoever.
Gtk.IconTheme.lookup_icon()
already returns a Gtk.IconPaintable
, which implements Gdk.Paintable
, which is how Gtk.Picture
works.
I think you’re fixating over the “give me a texture” side of things just because that’s how GTK2 and GTK3 worked, when what you really want is “give me something that knows how to paint itself”, which is how GTK4 works.
xerxes2 (Jens Persson) June 15, 2025, 1:57pm 9
I need a gdkpixbuf, the gtkpicture is only used for testing.