Convert symbolic icon to gdktexture/gdkpixbuf

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.

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);
    }

Why do you need a texture?

Here is some symbolic paintable code: Matthias Clasen / gtk-image-support · GitLab

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)

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.