Animate animations

This commit is contained in:
Přemysl Eric Janouch 2021-11-28 20:05:36 +01:00
parent 33f24fa184
commit cfe3dc55c6
Signed by: p
GPG Key ID: A0420B94F92B9493
2 changed files with 135 additions and 16 deletions

View File

@ -27,7 +27,7 @@ char **fastiv_io_all_supported_media_types(void);
// Userdata are typically attached to all Cairo surfaces in an animation. // Userdata are typically attached to all Cairo surfaces in an animation.
/// GBytes with plain Exif data. /// GBytes with plain Exif/TIFF data.
extern cairo_user_data_key_t fastiv_io_key_exif; extern cairo_user_data_key_t fastiv_io_key_exif;
/// FastivIoOrientation, as a uintptr_t. /// FastivIoOrientation, as a uintptr_t.
extern cairo_user_data_key_t fastiv_io_key_orientation; extern cairo_user_data_key_t fastiv_io_key_orientation;

View File

@ -34,10 +34,14 @@ struct _FastivView {
cairo_surface_t *image; ///< The loaded image (sequence) cairo_surface_t *image; ///< The loaded image (sequence)
cairo_surface_t *page; ///< Current page within image, weak cairo_surface_t *page; ///< Current page within image, weak
cairo_surface_t *frame; ///< Current frame within page, weak cairo_surface_t *frame; ///< Current frame within page, weak
FastivIoOrientation orientation; ///< Current orientation FastivIoOrientation orientation; ///< Current page orientation
bool filter; bool filter; ///< Smooth scaling toggle
bool scale_to_fit; bool scale_to_fit; ///< Image no larger than the allocation
double scale; double scale; ///< Scaling factor
int remaining_loops; ///< Greater than zero if limited
gint64 frame_time; ///< Current frame's start, µs precision
gulong frame_update_connection; ///< GdkFrameClock::update
}; };
G_DEFINE_TYPE(FastivView, fastiv_view, GTK_TYPE_WIDGET) G_DEFINE_TYPE(FastivView, fastiv_view, GTK_TYPE_WIDGET)
@ -426,6 +430,123 @@ fastiv_view_scroll_event(GtkWidget *widget, GdkEventScroll *event)
} }
} }
static void
stop_animating(FastivView *self)
{
GdkFrameClock *clock = gtk_widget_get_frame_clock(GTK_WIDGET(self));
if (!clock || !self->frame_update_connection)
return;
g_signal_handler_disconnect(clock, self->frame_update_connection);
gdk_frame_clock_end_updating(clock);
self->frame_time = 0;
self->frame_update_connection = 0;
self->remaining_loops = 0;
}
static gboolean
advance_frame(FastivView *self)
{
cairo_surface_t *next =
cairo_surface_get_user_data(self->frame, &fastiv_io_key_frame_next);
if (next) {
self->frame = next;
} else {
if (self->remaining_loops && !--self->remaining_loops)
return FALSE;
self->frame = self->page;
}
return TRUE;
}
static gboolean
advance_animation(FastivView *self, GdkFrameClock *clock)
{
gint64 now = gdk_frame_clock_get_frame_time(clock);
while (true) {
// TODO(p): See if infinite frames can actually happen, and how.
intptr_t duration = (intptr_t) cairo_surface_get_user_data(
self->frame, &fastiv_io_key_frame_duration);
if (duration < 0)
return FALSE;
// Do not busy loop. GIF timings are given in hundredths of a second.
if (duration == 0)
duration = gdk_frame_timings_get_refresh_interval(
gdk_frame_clock_get_current_timings(clock)) / 1000;
if (duration == 0)
duration = 1;
gint64 then = self->frame_time + duration * 1000;
if (then > now)
return TRUE;
if (!advance_frame(self))
return FALSE;
self->frame_time = then;
gtk_widget_queue_draw(GTK_WIDGET(self));
}
}
static void
on_frame_clock_update(GdkFrameClock *clock, gpointer user_data)
{
FastivView *self = FASTIV_VIEW(user_data);
if (!advance_animation(self, clock))
stop_animating(self);
}
static void
start_animating(FastivView *self)
{
stop_animating(self);
GdkFrameClock *clock = gtk_widget_get_frame_clock(GTK_WIDGET(self));
if (!clock ||
!cairo_surface_get_user_data(self->page, &fastiv_io_key_frame_next))
return;
self->frame_time = gdk_frame_clock_get_frame_time(clock);
self->frame_update_connection = g_signal_connect(
clock, "update", G_CALLBACK(on_frame_clock_update), self);
self->remaining_loops = (uintptr_t) cairo_surface_get_user_data(
self->page, &fastiv_io_key_loops);
gdk_frame_clock_begin_updating(clock);
}
static void
switch_page(FastivView *self, cairo_surface_t *page)
{
GtkWidget *widget = GTK_WIDGET(self);
self->frame = self->page = page;
if ((self->orientation = (uintptr_t) cairo_surface_get_user_data(
self->page, &fastiv_io_key_orientation)) ==
FastivIoOrientationUnknown)
self->orientation = FastivIoOrientation0;
start_animating(self);
gtk_widget_queue_resize(widget);
}
static void
fastiv_view_map(GtkWidget *widget)
{
GTK_WIDGET_CLASS(fastiv_view_parent_class)->map(widget);
// Loading before mapping will fail to obtain a GdkFrameClock.
start_animating(FASTIV_VIEW(widget));
}
void
fastiv_view_unmap(GtkWidget *widget)
{
stop_animating(FASTIV_VIEW(widget));
GTK_WIDGET_CLASS(fastiv_view_parent_class)->unmap(widget);
}
static gboolean static gboolean
fastiv_view_key_press_event(GtkWidget *widget, GdkEventKey *event) fastiv_view_key_press_event(GtkWidget *widget, GdkEventKey *event)
{ {
@ -467,26 +588,26 @@ fastiv_view_key_press_event(GtkWidget *widget, GdkEventKey *event)
cairo_surface_t *page = cairo_surface_get_user_data( cairo_surface_t *page = cairo_surface_get_user_data(
self->page, &fastiv_io_key_page_previous); self->page, &fastiv_io_key_page_previous);
if (page) if (page)
self->frame = self->page = page; switch_page(self, page);
gtk_widget_queue_resize(widget);
return TRUE; return TRUE;
} }
case GDK_KEY_bracketright: { case GDK_KEY_bracketright: {
cairo_surface_t *page = cairo_surface_get_user_data( cairo_surface_t *page = cairo_surface_get_user_data(
self->page, &fastiv_io_key_page_next); self->page, &fastiv_io_key_page_next);
if (page) if (page)
self->frame = self->page = page; switch_page(self, page);
gtk_widget_queue_resize(widget);
return TRUE; return TRUE;
} }
case GDK_KEY_braceleft: case GDK_KEY_braceleft:
stop_animating(self);
if (!(self->frame = cairo_surface_get_user_data( if (!(self->frame = cairo_surface_get_user_data(
self->frame, &fastiv_io_key_frame_previous))) self->frame, &fastiv_io_key_frame_previous)))
self->frame = self->page; self->frame = self->page;
gtk_widget_queue_draw(widget); gtk_widget_queue_draw(widget);
return TRUE; return TRUE;
case GDK_KEY_braceright: case GDK_KEY_braceright:
stop_animating(self);
if (!(self->frame = cairo_surface_get_user_data( if (!(self->frame = cairo_surface_get_user_data(
self->frame, &fastiv_io_key_frame_next))) self->frame, &fastiv_io_key_frame_next)))
self->frame = self->page; self->frame = self->page;
@ -516,6 +637,8 @@ fastiv_view_class_init(FastivViewClass *klass)
widget_class->get_preferred_height = fastiv_view_get_preferred_height; widget_class->get_preferred_height = fastiv_view_get_preferred_height;
widget_class->get_preferred_width = fastiv_view_get_preferred_width; widget_class->get_preferred_width = fastiv_view_get_preferred_width;
widget_class->size_allocate = fastiv_view_size_allocate; widget_class->size_allocate = fastiv_view_size_allocate;
widget_class->map = fastiv_view_map;
widget_class->unmap = fastiv_view_unmap;
widget_class->realize = fastiv_view_realize; widget_class->realize = fastiv_view_realize;
widget_class->draw = fastiv_view_draw; widget_class->draw = fastiv_view_draw;
widget_class->button_press_event = fastiv_view_button_press_event; widget_class->button_press_event = fastiv_view_button_press_event;
@ -548,13 +671,9 @@ fastiv_view_open(FastivView *self, const gchar *path, GError **error)
if (self->image) if (self->image)
cairo_surface_destroy(self->image); cairo_surface_destroy(self->image);
self->frame = self->page = self->image = surface; self->frame = self->page = NULL;
self->image = surface;
switch_page(self, self->image);
set_scale_to_fit(self, true); set_scale_to_fit(self, true);
// TODO(p): This is actually per-page.
if ((self->orientation = (uintptr_t) cairo_surface_get_user_data(
self->image, &fastiv_io_key_orientation)) ==
FastivIoOrientationUnknown)
self->orientation = FastivIoOrientation0;
return TRUE; return TRUE;
} }