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
usingGdkPixbuf.Pixbuf.new_from_resource_at_scale
andGdk.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.