How to scale SVG icons in GTK?

What is the proper way to scale SVG icons inside a GTK app in 2025? I have been struggling with this for some time now.

I have a GTK app that ships with some SVG icons (through GResource). The icons are placed inside button-like widgets, which are resized as the window is resized. The icons need to resize/scale accordingly while maintaining their quality.

So far, the best solution I have found is:

  • Define a custom widget that inherits directly from Gtk.Widget
  • Load the desired icon as a Gtk.Texture using GdkPixbuf.Pixbuf.new_from_resource_at_scale and Gdk.Texture.new_for_pixbuf
  • Override do_snapshot to draw the texture on the widget, reloading the icon if the widget size has changed

The following snippet using Python bindings demonstrates this:

MYWIDGET_SNAPSHOT_SCALE = 0.6


class MyWidget(Gtk.Widget):
    def __init__(self):
        super().__init__()
        self.texture = None
        self.resource_path = None

    def set_texture(self, resource_path):
        w, h = self.get_width(), self.get_height()
        texture_size = min(w, h)

        try:
            # Load the texture at the widget size
            pixbuf = GdkPixbuf.Pixbuf.new_from_resource_at_scale(
                resource_path, texture_size, texture_size, False
            )
            assert pixbuf is not None
            self.texture = Gdk.Texture.new_for_pixbuf(pixbuf)
        except GLib.Error as e:
            print("Failed to load image from resource:", e.message, file=sys.stderr)
            self.texture = None
            self.resource_path = None
            return

        self.resource_path = resource_path
        self.queue_draw()

    def do_snapshot(self, snapshot):
        if self.texture is None or self.resource_path is None:
            return

        w, h = self.get_width(), self.get_height()
        texture_size = min(w, h)

        # Reload and scale the texture if needed
        if (
            self.texture.get_width() != texture_size
            or self.texture.get_height() != texture_size
        ):
            self.set_texture(self.resource_path)
            if self.texture is None:
                return

        # Scale the snapshot to occupy the desired space on the widget, e.g. 60%
        snapshot_size = texture_size * MYWIDGET_SNAPSHOT_SCALE

        # Compute top-left origin point for centering the snapshot
        offset_x = max(0, (w - snapshot_size) / 2)
        offset_y = max(0, (h - snapshot_size) / 2)

        # Snapshot texture at the widget center
        snapshot.save()
        snapshot.translate(Graphene.Point().init(offset_x, offset_y))
        self.texture.snapshot(snapshot, snapshot_size, snapshot_size)
        snapshot.restore()

The icons now scale and look OK with some noticable aliasing however.

Is there something wrong with this approach? Is there better and more efficient way?

Any advice, pointers, or examples would be greatly appreciated!

Thanks in advance.

Please forgive me if I’m talking nonsense, but is there any reason you shouldn’t use a Gtk.Image? I have an app that uses sgv icons on some buttons, I simply add the Gtk.Image as a child of the button and control the display size with the Gtk.Image.pixel-size property.

My images don’t have dynamic size, but it would be very easy to change the pixel-size according to the size of the window.

My first attempt was with Gtk.Image. In fact, my custom widget initially subclassed Gtk.Button. However, at that time, I couldn’t get the button’s image to scale properly when the button was resized. After some experimentation, I decided to subclass Gtk.Widget directly. From what I’ve read, using Gdk.Texture and implementing the snapshot virtual function is considered the right way for drawing custom widgets.

I’m still unsure whether creating a Pixbuf first through new_from_resource_at_scale is necessary in my case. I did try creating a Texture directly from the resource path once, and scaling it inside self.texture.snapshot, but the result was really blurry.

I wasn’t aware of the Gtk.Image.pixel-size property until now. I just tried replacing the Texture with an Image, overriding size_allocate to resize the image and set its pixel-size accordingly.

To my eyes, the result looks about the same as with Texture, which is interesting. Plus, it’s a lot less code: no need to override do_snapshot, as setting the MyWidget instance as the parent of the Image with set_parent handles the drawing automatically.

Thanks for pointing that out. The reason for this post is exactly to explore the available/best options as I’m still quite new to GTK/GNOME.