Summary
I’m using GTK 4 and would like to display an animated GIF using a Gtk.Picture widget so that the GIF shrinks or expands depending on the container size.
If I understand correctly, one needs to create a custom class that implements the Gdk.Paintable interface, but I don’t understand how to implement the snapshot method.
Example GIF paintable in C
Looking at GTK’s demo programs, which are written in C, I’ve found an example of an animated GIF and the custom paintable used for it (pixbufpaintable.c
):
#include <gtk/gtk.h>
#include "pixbufpaintable.h"
struct _PixbufPaintable {
GObject parent_instance;
char *resource_path;
GdkPixbufAnimation *anim;
GdkPixbufAnimationIter *iter;
guint timeout;
};
enum {
PROP_RESOURCE_PATH = 1,
NUM_PROPERTIES
};
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
static void
pixbuf_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
PixbufPaintable *self = PIXBUF_PAINTABLE (paintable);
GTimeVal val;
GdkPixbuf *pixbuf;
GdkTexture *texture;
g_get_current_time (&val);
gdk_pixbuf_animation_iter_advance (self->iter, &val);
pixbuf = gdk_pixbuf_animation_iter_get_pixbuf (self->iter);
texture = gdk_texture_new_for_pixbuf (pixbuf);
gdk_paintable_snapshot (GDK_PAINTABLE (texture), snapshot, width, height);
g_object_unref (texture);
}
G_GNUC_END_IGNORE_DEPRECATIONS;
static int
pixbuf_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
PixbufPaintable *self = PIXBUF_PAINTABLE (paintable);
return gdk_pixbuf_animation_get_width (self->anim);
}
static int
pixbuf_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
PixbufPaintable *self = PIXBUF_PAINTABLE (paintable);
return gdk_pixbuf_animation_get_height (self->anim);
}
static void
pixbuf_paintable_init_interface (GdkPaintableInterface *iface)
{
iface->snapshot = pixbuf_paintable_snapshot;
iface->get_intrinsic_width = pixbuf_paintable_get_intrinsic_width;
iface->get_intrinsic_height = pixbuf_paintable_get_intrinsic_height;
}
G_DEFINE_TYPE_WITH_CODE(PixbufPaintable, pixbuf_paintable, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
pixbuf_paintable_init_interface))
static void
pixbuf_paintable_init (PixbufPaintable *paintable)
{
}
static gboolean
delay_cb (gpointer data)
{
PixbufPaintable *self = data;
int delay;
delay = gdk_pixbuf_animation_iter_get_delay_time (self->iter);
self->timeout = g_timeout_add (delay, delay_cb, self);
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
return G_SOURCE_REMOVE;
}
static void
pixbuf_paintable_set_resource_path (PixbufPaintable *self,
const char *resource_path)
{
int delay;
g_free (self->resource_path);
self->resource_path = g_strdup (resource_path);
g_clear_object (&self->anim);
self->anim = gdk_pixbuf_animation_new_from_resource (resource_path, NULL);
g_clear_object (&self->iter);
self->iter = gdk_pixbuf_animation_get_iter (self->anim, NULL);
delay = gdk_pixbuf_animation_iter_get_delay_time (self->iter);
self->timeout = g_timeout_add (delay, delay_cb, self);
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
g_object_notify (G_OBJECT (self), "resource-path");
}
static void
pixbuf_paintable_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
PixbufPaintable *self = PIXBUF_PAINTABLE (object);
switch (prop_id)
{
case PROP_RESOURCE_PATH:
pixbuf_paintable_set_resource_path (self, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
pixbuf_paintable_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
PixbufPaintable *self = PIXBUF_PAINTABLE (object);
switch (prop_id)
{
case PROP_RESOURCE_PATH:
g_value_set_string (value, self->resource_path);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
pixbuf_paintable_dispose (GObject *object)
{
PixbufPaintable *self = PIXBUF_PAINTABLE (object);
g_clear_pointer (&self->resource_path, g_free);
g_clear_object (&self->anim);
g_clear_object (&self->iter);
if (self->timeout)
{
g_source_remove (self->timeout);
self->timeout = 0;
}
G_OBJECT_CLASS (pixbuf_paintable_parent_class)->dispose (object);
}
static void
pixbuf_paintable_class_init (PixbufPaintableClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->dispose = pixbuf_paintable_dispose;
object_class->get_property = pixbuf_paintable_get_property;
object_class->set_property = pixbuf_paintable_set_property;
g_object_class_install_property (object_class, PROP_RESOURCE_PATH,
g_param_spec_string ("resource-path", "Resource path", "Resource path",
NULL, G_PARAM_READWRITE));
}
GdkPaintable *
pixbuf_paintable_new_from_resource (const char *path)
{
return g_object_new (PIXBUF_TYPE_PAINTABLE,
"resource-path", path,
NULL);
}
#+end_src
The paintable is then used like this:
#+begin_src c
paintable = pixbuf_paintable_new_from_resource ("/images/floppybuddy.gif");
picture = gtk_picture_new_for_paintable (paintable);
g_object_unref (paintable);
So I’m trying to translate the code above into Python (as follows).
Example GIF paintable in Python (incomplete)
I think I’ve translated most of the C code, but I don’t know how to finish the Paintable’s snapshot
method.
So far, I have a mini app structured like this:
gtk4-animated-gif
├── app.py
├── app-window.ui
└── Muybridge_Buffalo_galloping.gif
Python code (save as app.py
):
"""Displaying animated GIFs with Gtk.Picture."""
import sys
import gi
gi.require_version("Gdk", "4.0")
gi.require_version("GdkPixbuf", "2.0")
gi.require_version("Gtk", "4.0")
from gi.repository import Gdk, GdkPixbuf, Gio, GObject, Gtk
# VIEWS
class App(Gtk.Application):
def __init__(self):
Gtk.Application.__init__(
self,
application_id="org.example.app",
flags=Gio.ApplicationFlags.FLAGS_NONE
)
def do_startup(self):
Gtk.Application.do_startup(self)
def do_activate(self):
# Show default application window.
window = AppWindow(self)
self.add_window(window)
window.present()
def on_quit(self, action, param):
self.quit()
@Gtk.Template(filename="app-window.ui")
class AppWindow(Gtk.ApplicationWindow):
__gtype_name__ = "AppWindow"
header_bar = Gtk.Template.Child("header-bar")
image = Gtk.Template.Child("image")
picture = Gtk.Template.Child("picture")
def __init__(self, app):
super().__init__()
# Display animated GIF.
gif = "Muybridge_Buffalo_galloping.gif"
# Set Image.
self.image.set_from_file(gif)
# Set Picture.
paintable = GifPaintable(gif)
self.picture.set_paintable(paintable)
class GifPaintable(GObject.Object, Gdk.Paintable):
def __init__(self, path):
super().__init__()
self.animation = GdkPixbuf.PixbufAnimation.new_from_file(path)
self.iterator = self.animation.get_iter()
self.delay = self.iterator.get_delay_time()
self.timeout = GObject.timeout_add(self.delay, self.on_delay)
self.invalidate_contents()
def on_delay(self):
delay = self.iterator.get_delay_time()
self.timeout = GObject.timeout_add(delay, self.on_delay)
self.invalidate_contents()
# return G_SOURCE_REMOVE (?)
def get_intrinsic_height(self):
return self.animation.get_height()
def get_intrinsic_width(self):
return self.animation.get_width()
def invalidate_contents(self):
self.emit("invalidate-contents")
def snapshot(self, snapshot, width, height):
self.iterator.advance(GObject.get_current_time())
pixbuf = self.iterator.get_pixbuf()
texture = Gdk.Texture.new_for_pixbuf(pixbuf)
# TODO: And then what do I do with the texture and snapshot?
# The GTK images example in C (gtk4-demo) says
# gdk_paintable_snapshot (GDK_PAINTABLE (texture), snapshot, width, height);
# But how do I translate that into Python?
# RUN APP
if __name__ == "__main__":
app = App()
exit_status = app.run(sys.argv)
sys.exit(exit_status)
XML UI (save as app-window.ui
):
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.10.3 -->
<interface>
<!-- interface-name app-window.ui -->
<requires lib="gtk" version="4.6"/>
<template class="AppWindow" parent="GtkApplicationWindow">
<property name="default-height">400</property>
<property name="default-width">300</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="header-bar">
<child type="title">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="valign">center</property>
<child>
<object class="GtkLabel">
<property name="justify">center</property>
<property name="label"><b>Animated GIF</b></property>
<property name="use-markup">True</property>
</object>
</child>
<child>
<object class="GtkLabel" id="title-label">
<property name="css-classes">subtitle</property>
<property name="ellipsize">middle</property>
<property name="justify">center</property>
<property name="label">GdkPaintable & GtkPicture</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="vexpand">True</property>
<child>
<object class="GtkImage" id="image">
<property name="vexpand">True</property>
</object>
</child>
<child>
<object class="GtkPicture" id="picture">
<property name="vexpand">True</property>
</object>
</child>
</object>
</child>
</template>
</interface>
And
The app requires the following software in the environment:
gobject-introspection
gtk4
python3
pygobject
Running the application will currently display the window alright, but the animated GIF is not displayed, and I get critical errors that read:
#+begin_example
(app.py:450): Gdk-CRITICAL **: 16:04:18.902: Paintable of type ‘main+GifPaintable’ does not implement GdkPaintable::snapshot
#+end_example
Questions
-
In
GifPaintable.snapshot
, what am I supposed to do with thetexture
object and thesnapshot
argument?In the C code, they use those objects like this:
gdk_paintable_snapshot (GDK_PAINTABLE (texture), snapshot, width, height);
But I don’t get what
gdk_paintable_snapshot
is.