summaryrefslogtreecommitdiff
path: root/gdk-pixbuf/io-gif-animation.c
diff options
context:
space:
mode:
Diffstat (limited to 'gdk-pixbuf/io-gif-animation.c')
-rw-r--r--gdk-pixbuf/io-gif-animation.c419
1 files changed, 144 insertions, 275 deletions
diff --git a/gdk-pixbuf/io-gif-animation.c b/gdk-pixbuf/io-gif-animation.c
index 971801ee8..554ede632 100644
--- a/gdk-pixbuf/io-gif-animation.c
+++ b/gdk-pixbuf/io-gif-animation.c
@@ -24,6 +24,7 @@
#include <errno.h>
#include "gdk-pixbuf-transform.h"
#include "io-gif-animation.h"
+#include "lzw.h"
static void gdk_pixbuf_gif_anim_finalize (GObject *object);
@@ -37,6 +38,7 @@ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
static GdkPixbufAnimationIter* gdk_pixbuf_gif_anim_get_iter (GdkPixbufAnimation *anim,
const GTimeVal *start_time);
G_GNUC_END_IGNORE_DEPRECATIONS
+static GdkPixbuf* gdk_pixbuf_gif_anim_iter_get_pixbuf (GdkPixbufAnimationIter *iter);
@@ -71,16 +73,17 @@ gdk_pixbuf_gif_anim_finalize (GObject *object)
for (l = gif_anim->frames; l; l = l->next) {
frame = l->data;
- g_object_unref (frame->pixbuf);
- if (frame->composited)
- g_object_unref (frame->composited);
- if (frame->revert)
- g_object_unref (frame->revert);
+ g_byte_array_unref (frame->lzw_data);
+ if (frame->color_map_allocated)
+ g_free (frame->color_map);
g_free (frame);
}
g_list_free (gif_anim->frames);
+ g_clear_object (&gif_anim->last_frame_data);
+ g_clear_object (&gif_anim->last_frame_revert_data);
+
G_OBJECT_CLASS (gdk_pixbuf_gif_anim_parent_class)->finalize (object);
}
@@ -99,13 +102,16 @@ static GdkPixbuf*
gdk_pixbuf_gif_anim_get_static_image (GdkPixbufAnimation *animation)
{
GdkPixbufGifAnim *gif_anim;
+ GdkPixbufAnimationIter *iter;
+ GTimeVal start_time = { 0, 0 };
gif_anim = GDK_PIXBUF_GIF_ANIM (animation);
if (gif_anim->frames == NULL)
return NULL;
- else
- return GDK_PIXBUF (((GdkPixbufFrame*)gif_anim->frames->data)->pixbuf);
+
+ iter = gdk_pixbuf_gif_anim_get_iter (animation, &start_time);
+ return gdk_pixbuf_gif_anim_iter_get_pixbuf (iter);
}
static void
@@ -167,7 +173,6 @@ G_GNUC_END_IGNORE_DEPRECATIONS
static void gdk_pixbuf_gif_anim_iter_finalize (GObject *object);
static int gdk_pixbuf_gif_anim_iter_get_delay_time (GdkPixbufAnimationIter *iter);
-static GdkPixbuf* gdk_pixbuf_gif_anim_iter_get_pixbuf (GdkPixbufAnimationIter *iter);
static gboolean gdk_pixbuf_gif_anim_iter_on_currently_loading_frame (GdkPixbufAnimationIter *iter);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
static gboolean gdk_pixbuf_gif_anim_iter_advance (GdkPixbufAnimationIter *iter,
@@ -210,47 +215,6 @@ gdk_pixbuf_gif_anim_iter_finalize (GObject *object)
G_OBJECT_CLASS (gdk_pixbuf_gif_anim_iter_parent_class)->finalize (object);
}
-static void
-gtk_pixpuf_gif_reuse_old_composited_buffer (GdkPixbufFrame *old,
- GdkPixbufFrame *new)
-{
- /* Move old buffer to new frame to reuse memory and
- * minimise footprint */
- new->composited = old->composited;
- old->composited = NULL;
-}
-
-static void
-gdk_pixbuf_gif_anim_iter_clean_previous (GList *initial)
-{
- GdkPixbufFrame *frame;
- GList *tmp;
-
- /* Cleanup previous frames until first cleaned frame */
- frame = initial->data;
-
- /* Check the current frame */
- if (!frame->composited || frame->need_recomposite) {
- /* The composite frame doesn't exist,
- * nothing to cleanup */
- return;
- }
-
- /* Work with previous frames */
- tmp = initial->prev;
- while (tmp != NULL) {
- frame = tmp->data;
- if (!frame->composited || frame->need_recomposite) {
- /* Composite for previous frame doesn't exist */
- break;
- }
-
- /* delete cached pixbuf */
- g_clear_object (&frame->composited);
- tmp = tmp->prev;
- }
-}
-
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
static gboolean
gdk_pixbuf_gif_anim_iter_advance (GdkPixbufAnimationIter *anim_iter,
@@ -312,8 +276,6 @@ gdk_pixbuf_gif_anim_iter_advance (GdkPixbufAnimationIter *anim_iter,
break;
tmp = tmp->next;
- if (tmp)
- gdk_pixbuf_gif_anim_iter_clean_previous(tmp);
}
old = iter->current_frame;
@@ -348,245 +310,152 @@ gdk_pixbuf_gif_anim_iter_get_delay_time (GdkPixbufAnimationIter *anim_iter)
return -1; /* show last frame forever */
}
-void
-gdk_pixbuf_gif_anim_frame_composite (GdkPixbufGifAnim *gif_anim,
- GdkPixbufFrame *frame)
+static void
+composite_frame (GdkPixbufGifAnim *anim, GdkPixbufFrame *frame)
{
- GList *link;
- GList *tmp;
-
- link = g_list_find (gif_anim->frames, frame);
-
- if (frame->need_recomposite || frame->composited == NULL) {
- /* For now, to composite we start with the last
- * composited frame and composite everything up to
- * here.
- */
-
- /* Rewind to last composited frame. */
- tmp = link;
- while (tmp != NULL) {
- GdkPixbufFrame *f = tmp->data;
-
- if (f->need_recomposite) {
- if (f->composited) {
- g_object_unref (f->composited);
- f->composited = NULL;
- }
- }
-
- if (f->composited != NULL)
- break;
-
- tmp = tmp->prev;
- }
-
- /* Go forward, compositing all frames up to the current frame */
- if (tmp == NULL)
- tmp = gif_anim->frames;
-
- while (tmp != NULL) {
- GdkPixbufFrame *f = tmp->data;
- gint clipped_width, clipped_height;
-
- if (f->pixbuf == NULL)
- return;
-
- clipped_width = MIN (gif_anim->width - f->x_offset, gdk_pixbuf_get_width (f->pixbuf));
- clipped_height = MIN (gif_anim->height - f->y_offset, gdk_pixbuf_get_height (f->pixbuf));
-
- if (f->need_recomposite) {
- if (f->composited) {
- g_object_unref (f->composited);
- f->composited = NULL;
- }
- }
-
- if (f->composited != NULL)
- goto next;
-
- if (tmp->prev == NULL) {
- /* First frame may be smaller than the whole image;
- * if so, we make the area outside it full alpha if the
- * image has alpha, and background color otherwise.
- * GIF spec doesn't actually say what to do about this.
- */
- f->composited = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
- TRUE,
- 8, gif_anim->width, gif_anim->height);
-
- if (f->composited == NULL)
- return;
-
- /* alpha gets dumped if f->composited has no alpha */
-
- gdk_pixbuf_fill (f->composited,
- ((unsigned) gif_anim->bg_red << 24) |
- (gif_anim->bg_green << 16) |
- (gif_anim->bg_blue << 8));
-
- if (clipped_width > 0 && clipped_height > 0)
- gdk_pixbuf_composite (f->pixbuf,
- f->composited,
- f->x_offset,
- f->y_offset,
- clipped_width,
- clipped_height,
- f->x_offset, f->y_offset,
- 1.0, 1.0,
- GDK_INTERP_BILINEAR,
- 255);
-
- if (f->action == GDK_PIXBUF_FRAME_REVERT)
- g_warning ("First frame of GIF has bad dispose mode, GIF loader should not have loaded this image");
-
- f->need_recomposite = FALSE;
- } else {
- GdkPixbufFrame *prev_frame;
- gint prev_clipped_width;
- gint prev_clipped_height;
-
- prev_frame = tmp->prev->data;
-
- prev_clipped_width = MIN (gif_anim->width - prev_frame->x_offset, gdk_pixbuf_get_width (prev_frame->pixbuf));
- prev_clipped_height = MIN (gif_anim->height - prev_frame->y_offset, gdk_pixbuf_get_height (prev_frame->pixbuf));
-
- /* Init f->composited with what we should have after the previous
- * frame
- */
-
- if (prev_frame->action == GDK_PIXBUF_FRAME_RETAIN) {
- gtk_pixpuf_gif_reuse_old_composited_buffer (prev_frame, f);
-
- if (f->composited == NULL)
- return;
-
- } else if (prev_frame->action == GDK_PIXBUF_FRAME_DISPOSE) {
- gtk_pixpuf_gif_reuse_old_composited_buffer (prev_frame, f);
-
- if (f->composited == NULL)
- return;
-
- if (prev_clipped_width > 0 && prev_clipped_height > 0) {
- /* Clear area of previous frame to background */
- GdkPixbuf *area;
-
- area = gdk_pixbuf_new_subpixbuf (f->composited,
- prev_frame->x_offset,
- prev_frame->y_offset,
- prev_clipped_width,
- prev_clipped_height);
-
- if (area == NULL)
- return;
-
- gdk_pixbuf_fill (area,
- (gif_anim->bg_red << 24) |
- (gif_anim->bg_green << 16) |
- (gif_anim->bg_blue << 8));
-
- g_object_unref (area);
- }
- } else if (prev_frame->action == GDK_PIXBUF_FRAME_REVERT) {
- gtk_pixpuf_gif_reuse_old_composited_buffer (prev_frame, f);
-
- if (f->composited == NULL)
- return;
-
- if (prev_frame->revert != NULL &&
- prev_clipped_width > 0 && prev_clipped_height > 0) {
- /* Copy in the revert frame */
- gdk_pixbuf_copy_area (prev_frame->revert,
- 0, 0,
- gdk_pixbuf_get_width (prev_frame->revert),
- gdk_pixbuf_get_height (prev_frame->revert),
- f->composited,
- prev_frame->x_offset,
- prev_frame->y_offset);
- }
- } else {
- g_warning ("Unknown revert action for GIF frame");
- }
-
- if (f->revert == NULL &&
- f->action == GDK_PIXBUF_FRAME_REVERT) {
- if (clipped_width > 0 && clipped_height > 0) {
- /* We need to save the contents before compositing */
- GdkPixbuf *area;
-
- area = gdk_pixbuf_new_subpixbuf (f->composited,
- f->x_offset,
- f->y_offset,
- clipped_width,
- clipped_height);
-
- if (area == NULL)
- return;
-
- f->revert = gdk_pixbuf_copy (area);
-
- g_object_unref (area);
-
- if (f->revert == NULL)
- return;
- }
- }
-
- if (clipped_width > 0 && clipped_height > 0 &&
- f->pixbuf != NULL && f->composited != NULL) {
- /* Put current frame onto f->composited */
- gdk_pixbuf_composite (f->pixbuf,
- f->composited,
- f->x_offset,
- f->y_offset,
- clipped_width,
- clipped_height,
- f->x_offset, f->y_offset,
- 1.0, 1.0,
- GDK_INTERP_NEAREST,
- 255);
- }
-
- f->need_recomposite = FALSE;
- }
+ LZWDecoder *lzw_decoder = NULL;
+ guint8 *index_buffer = NULL;
+ gsize n_indexes, i;
+ guint16 *interlace_rows = NULL;
+ guchar *pixels;
+
+ anim->last_frame = frame;
+
+ /* Store overwritten data if required */
+ g_clear_object (&anim->last_frame_revert_data);
+ if (frame->action == GDK_PIXBUF_FRAME_REVERT) {
+ anim->last_frame_revert_data = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, frame->width, frame->height);
+ if (anim->last_frame_revert_data != NULL)
+ gdk_pixbuf_copy_area (anim->last_frame_data,
+ frame->x_offset, frame->y_offset, frame->width, frame->height,
+ anim->last_frame_revert_data,
+ 0, 0);
+ }
- next:
- if (tmp == link)
- break;
+ lzw_decoder = lzw_decoder_new (frame->lzw_code_size + 1);
+ index_buffer = g_new (guint8, frame->width * frame->height);
+ if (index_buffer == NULL)
+ goto out;
+
+ interlace_rows = g_new (guint16, frame->height);
+ if (interlace_rows == NULL)
+ goto out;
+ if (frame->interlace) {
+ int row, n = 0;
+ for (row = 0; row < frame->height; row += 8)
+ interlace_rows[n++] = row;
+ for (row = 4; row < frame->height; row += 8)
+ interlace_rows[n++] = row;
+ for (row = 2; row < frame->height; row += 4)
+ interlace_rows[n++] = row;
+ for (row = 1; row < frame->height; row += 2)
+ interlace_rows[n++] = row;
+ }
+ else {
+ int row;
+ for (row = 0; row < frame->height; row++)
+ interlace_rows[row] = row;
+ }
- tmp = tmp->next;
- if (tmp)
- gdk_pixbuf_gif_anim_iter_clean_previous(tmp);
- }
+ n_indexes = lzw_decoder_feed (lzw_decoder, frame->lzw_data->data, frame->lzw_data->len, index_buffer, frame->width * frame->height);
+ pixels = gdk_pixbuf_get_pixels (anim->last_frame_data);
+ for (i = 0; i < n_indexes; i++) {
+ guint8 index = index_buffer[i];
+ guint x, y;
+ int offset;
+
+ if (index == frame->transparent_index)
+ continue;
+
+ x = i % frame->width + frame->x_offset;
+ y = interlace_rows[i / frame->width] + frame->y_offset;
+ if (x >= anim->width || y >= anim->height)
+ continue;
+
+ offset = y * gdk_pixbuf_get_rowstride (anim->last_frame_data) + x * 4;
+ pixels[offset + 0] = frame->color_map[index * 3 + 0];
+ pixels[offset + 1] = frame->color_map[index * 3 + 1];
+ pixels[offset + 2] = frame->color_map[index * 3 + 2];
+ pixels[offset + 3] = 255;
}
+
+out:
+ g_clear_object (&lzw_decoder);
+ g_free (index_buffer);
+ g_free (interlace_rows);
}
GdkPixbuf*
gdk_pixbuf_gif_anim_iter_get_pixbuf (GdkPixbufAnimationIter *anim_iter)
{
- GdkPixbufGifAnimIter *iter;
- GdkPixbufFrame *frame;
+ GdkPixbufGifAnimIter *iter = GDK_PIXBUF_GIF_ANIM_ITER (anim_iter);
+ GdkPixbufGifAnim *anim = iter->gif_anim;
+ GdkPixbufFrame *requested_frame;
+ GList *link;
- iter = GDK_PIXBUF_GIF_ANIM_ITER (anim_iter);
+ if (iter->current_frame != NULL)
+ requested_frame = iter->current_frame->data;
+ else
+ requested_frame = g_list_last (anim->frames)->data;
+
+ /* If the previously rendered frame is not before this one, then throw it away */
+ if (anim->last_frame != NULL) {
+ link = g_list_find (anim->frames, anim->last_frame);
+ while (link != NULL && link->data != requested_frame)
+ link = link->next;
+ if (link == NULL)
+ anim->last_frame = NULL;
+ }
- frame = iter->current_frame ? iter->current_frame->data : g_list_last (iter->gif_anim->frames)->data;
+ /* If no rendered frame, render the first frame */
+ if (anim->last_frame == NULL) {
+ if (anim->last_frame_data == NULL)
+ anim->last_frame_data = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, anim->width, anim->height);
+ if (anim->last_frame_data == NULL)
+ return NULL;
+ memset (gdk_pixbuf_get_pixels (anim->last_frame_data), 0, gdk_pixbuf_get_rowstride (anim->last_frame_data) * anim->height);
+ composite_frame (anim, g_list_nth_data (anim->frames, 0));
+ }
-#if 0
- if (FALSE && frame)
- g_print ("current frame %d dispose mode %d %d x %d\n",
- g_list_index (iter->gif_anim->frames,
- frame),
- frame->action,
- gdk_pixbuf_get_width (frame->pixbuf),
- gdk_pixbuf_get_height (frame->pixbuf));
-#endif
+ /* If the requested frame is already rendered, then no action required */
+ if (requested_frame == anim->last_frame)
+ return anim->last_frame_data;
- if (frame == NULL)
- return NULL;
+ /* Starting from the last rendered frame, render to the current frame */
+ for (link = g_list_find (anim->frames, anim->last_frame); link->next != NULL && link->data != requested_frame; link = link->next) {
+ GdkPixbufFrame *frame = link->data;
+ guchar *pixels;
+ int y, x_end, y_end;
+
+ /* Remove last frame if required */
+ switch (frame->action) {
+ case GDK_PIXBUF_FRAME_RETAIN:
+ break;
+ case GDK_PIXBUF_FRAME_DISPOSE:
+ /* Replace previous area with background */
+ pixels = gdk_pixbuf_get_pixels (anim->last_frame_data);
+ x_end = MIN (anim->last_frame->x_offset + anim->last_frame->width, anim->width);
+ y_end = MIN (anim->last_frame->y_offset + anim->last_frame->height, anim->height);
+ for (y = anim->last_frame->y_offset; y < y_end; y++) {
+ guchar *line = pixels + y * gdk_pixbuf_get_rowstride (anim->last_frame_data) + anim->last_frame->x_offset * 4;
+ memset (line, 0, (x_end - anim->last_frame->x_offset) * 4);
+ }
+ break;
+ case GDK_PIXBUF_FRAME_REVERT:
+ /* Replace previous area with last retained area */
+ if (anim->last_frame_revert_data != NULL)
+ gdk_pixbuf_copy_area (anim->last_frame_revert_data,
+ 0, 0, anim->last_frame->width, anim->last_frame->height,
+ anim->last_frame_data,
+ anim->last_frame->x_offset, anim->last_frame->y_offset);
+ break;
+ }
- gdk_pixbuf_gif_anim_frame_composite (iter->gif_anim, frame);
+ /* Render next frame */
+ composite_frame (anim, link->next->data);
+ }
- return frame->composited;
+ return anim->last_frame_data;
}
static gboolean