[gdk-pixbuf] gif: Render GIF frames on demand



commit 4e7b5345d2fc8f0d1dee93d8ba9ab805bc95d42f
Author: Robert Ancell <robert ancell canonical com>
Date:   Wed Dec 5 17:08:30 2018 +1300

    gif: Render GIF frames on demand
    
    The previous code loaded all the frames into memory, which causes a memory
    and CPU explosion when a large GIF was loaded.
    
    From the existing code:
    /* The below reflects the "use hell of a lot of RAM" philosophy of coding */
    
    The updated code now generates each image on demand by storing the compressed
    data and decompressing and combining with the last image. This uses more CPU
    than the old method, but allows arbitraritly large GIFs to play.
    
    The stop_after_first_frame hack that worked around this is no longer required
    and removed. This hack didn't work for progressive loading.
    
    If a GIF was played with frames out of order (e.g. in reverse) this would be
    quite slow, as repeated rendering would occur. This seems unlikely in the
    GIF use case but if it was a problem storing some key frames that would
    normally be discarded would reduce this while not using too much memory.
    
    A render cache could also be helpful in making a CPU / RAM trade-off for
    small repeating GIFs that have few frames.
    
    Fixes #101

 gdk-pixbuf/io-gif-animation.c | 419 +++++++++++++-----------------------
 gdk-pixbuf/io-gif-animation.h |  58 +++--
 gdk-pixbuf/io-gif.c           | 478 +++++++-----------------------------------
 3 files changed, 239 insertions(+), 716 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
diff --git a/gdk-pixbuf/io-gif-animation.h b/gdk-pixbuf/io-gif-animation.h
index 8f15fab53..840e37cf0 100644
--- a/gdk-pixbuf/io-gif-animation.h
+++ b/gdk-pixbuf/io-gif-animation.h
@@ -59,23 +59,24 @@ typedef struct _GdkPixbufFrame GdkPixbufFrame;
 struct _GdkPixbufGifAnim {
         GdkPixbufAnimation parent_instance;
 
-        /* Number of frames */
-        int n_frames;
-
         /* Total length of animation */
         int total_time;
         
+        /* Color map */
+        guchar color_map[256 * 3];
+
        /* List of GdkPixbufFrame structures */
         GList *frames;
 
        /* bounding box size */
        int width, height;
 
-        guchar bg_red;
-        guchar bg_green;
-        guchar bg_blue;
-        
         int loop;
+
+        /* Last rendered frames */
+       GdkPixbuf *last_frame_data;
+       GdkPixbufFrame *last_frame;
+       GdkPixbuf *last_frame_revert_data;
 };
 
 struct _GdkPixbufGifAnimClass {
@@ -127,12 +128,25 @@ GType gdk_pixbuf_gif_anim_iter_get_type (void) G_GNUC_CONST;
 
 
 struct _GdkPixbufFrame {
-       /* The pixbuf with this frame's image data */
-       GdkPixbuf *pixbuf;
+       /* Compressed frame data */
+       GByteArray *lzw_data;
+       guint8 lzw_code_size;
 
-        /* Offsets for overlaying onto the GIF graphic area */
+        /* Position of frame data in image */
         int x_offset;
        int y_offset;
+       guint16 width;
+       guint16 height;
+
+       /* Layout of pixels */
+       gboolean interlace;
+
+       /* Color map */
+       gboolean color_map_allocated;
+       guchar *color_map;
+
+       /* Transparency */
+       int transparent_index;
 
        /* Frame duration in ms */
        int delay_time;
@@ -142,30 +156,6 @@ struct _GdkPixbufFrame {
         
         /* Overlay mode */
        GdkPixbufFrameAction action;
-
-        /* TRUE if the pixbuf has been modified since
-         * the last frame composite operation
-         */
-        gboolean need_recomposite;
-
-        /* The below reflects the "use hell of a lot of RAM"
-         * philosophy of coding
-         */
-        
-        /* Cached composite image (the image you actually display
-         * for this frame)
-         */
-        GdkPixbuf *composited;
-
-        /* Cached revert image (the contents of the area
-         * covered by the frame prior to compositing;
-         * same size as pixbuf, not as the composite image; only
-         * used for FRAME_REVERT frames)
-         */
-        GdkPixbuf *revert;
 };
 
-void gdk_pixbuf_gif_anim_frame_composite (GdkPixbufGifAnim *gif_anim,
-                                          GdkPixbufFrame   *frame);
-
 #endif
diff --git a/gdk-pixbuf/io-gif.c b/gdk-pixbuf/io-gif.c
index 911499520..3fba50e11 100644
--- a/gdk-pixbuf/io-gif.c
+++ b/gdk-pixbuf/io-gif.c
@@ -58,7 +58,6 @@
 #include <glib/gi18n-lib.h>
 #include "gdk-pixbuf-io.h"
 #include "io-gif-animation.h"
-#include "lzw.h"
 
 
 
@@ -108,12 +107,10 @@ struct _GifContext
 
         gboolean has_global_cmap;
 
-        CMap global_color_map;
         gint global_colormap_size;
         unsigned int global_bit_pixel;
        unsigned int global_color_resolution;
         unsigned int background_index;
-        gboolean stop_after_first_frame;
 
         gboolean frame_cmap_active;
         CMap frame_color_map;
@@ -155,14 +152,6 @@ struct _GifContext
        guchar block_buf[280];
 
        guchar lzw_set_code_size;
-       LZWDecoder *lzw_decoder;
-       guint8 *index_buffer;
-       gsize index_buffer_length;
-
-       /* painting context */
-       gint draw_xpos;
-       gint draw_ypos;
-       gint draw_pass;
 
         /* error pointer */
         GError **error;
@@ -259,15 +248,9 @@ gif_get_colormap (GifContext *context)
                        return -1;
                }
 
-               context->global_color_map[0][context->global_colormap_size] = rgb[0];
-               context->global_color_map[1][context->global_colormap_size] = rgb[1];
-               context->global_color_map[2][context->global_colormap_size] = rgb[2];
-
-                if (context->global_colormap_size == context->background_index) {
-                        context->animation->bg_red = rgb[0];
-                        context->animation->bg_green = rgb[1];
-                        context->animation->bg_blue = rgb[2];
-                }
+               context->animation->color_map[context->global_colormap_size * 3 + 0] = rgb[0];
+               context->animation->color_map[context->global_colormap_size * 3 + 1] = rgb[1];
+               context->animation->color_map[context->global_colormap_size * 3 + 2] = rgb[2];
 
                context->global_colormap_size ++;
        }
@@ -430,173 +413,23 @@ static void
 gif_set_get_lzw (GifContext *context)
 {
        context->state = GIF_GET_LZW;
-       context->draw_xpos = 0;
-       context->draw_ypos = 0;
-       context->draw_pass = 0;
-}
-
-static void
-gif_fill_in_pixels (GifContext *context, guchar *dest, gint offset, guchar v)
-{
-       guchar *pixel = NULL;
-        guchar (*cmap)[MAXCOLORMAPSIZE];
-
-        if (context->frame_cmap_active)
-                cmap = context->frame_color_map;
-        else
-                cmap = context->global_color_map;
-        
-       if (context->gif89.transparent != -1) {
-               pixel = dest + (context->draw_ypos + offset) * gdk_pixbuf_get_rowstride 
(context->frame->pixbuf) + context->draw_xpos * 4;
-               *pixel = cmap [0][(guchar) v];
-               *(pixel+1) = cmap [1][(guchar) v];
-               *(pixel+2) = cmap [2][(guchar) v];
-               *(pixel+3) = (guchar) ((v == context->gif89.transparent) ? 0 : 255);
-       } else {
-               pixel = dest + (context->draw_ypos + offset) * gdk_pixbuf_get_rowstride 
(context->frame->pixbuf) + context->draw_xpos * 3;
-               *pixel = cmap [0][(guchar) v];
-               *(pixel+1) = cmap [1][(guchar) v];
-               *(pixel+2) = cmap [2][(guchar) v];
-       }
-}
-
-
-/* only called if progressive and interlaced */
-static void
-gif_fill_in_lines (GifContext *context, guchar *dest, guchar v)
-{
-       switch (context->draw_pass) {
-       case 0:
-               if (context->draw_ypos > 4) {
-                       gif_fill_in_pixels (context, dest, -4, v);
-                       gif_fill_in_pixels (context, dest, -3, v);
-               }
-               if (context->draw_ypos < (context->frame_height - 4)) {
-                       gif_fill_in_pixels (context, dest, 3, v);
-                       gif_fill_in_pixels (context, dest, 4, v);
-               }
-               /* we don't need a break here.  We draw the outer pixels first, then the
-                * inner ones, then the innermost ones.  case 0 needs to draw all 3 bands.
-                * case 1, just the last two, and case 2 just draws the last one*/
-       case 1:
-               if (context->draw_ypos > 2)
-                       gif_fill_in_pixels (context, dest, -2, v);
-               if (context->draw_ypos < (context->frame_height - 2))
-                       gif_fill_in_pixels (context, dest, 2, v);
-               /* no break as above. */
-       case 2:
-               if (context->draw_ypos > 1)
-                       gif_fill_in_pixels (context, dest, -1, v);
-               if (context->draw_ypos < (context->frame_height - 1))
-                       gif_fill_in_pixels (context, dest, 1, v);
-       case 3:
-       default:
-               break;
-       }
-}
-
-/* Clips a rectancle to the base dimensions. Returns TRUE if the clipped rectangle is non-empty. */
-static gboolean
-clip_frame (GifContext *context, 
-            gint       *x, 
-            gint       *y, 
-            gint       *width, 
-            gint       *height)
-{
-        gint orig_x, orig_y;
-        
-        orig_x = *x;
-        orig_y = *y;
-       *x = MAX (0, *x);
-       *y = MAX (0, *y);
-       *width = MIN (context->width, orig_x + *width) - *x;
-       *height = MIN (context->height, orig_y + *height) - *y;
-
-       if (*width > 0 && *height > 0)
-               return TRUE;
-
-       /* The frame is completely off-bounds */
-
-       *x = 0;
-       *y = 0;
-       *width = 0;
-       *height = 0;
-
-        return FALSE;
-}
-
-/* Call update_func on the given rectangle, unless it is completely off-bounds */
-static void
-maybe_update (GifContext *context,
-              gint        x,
-              gint        y,
-              gint        width,
-              gint        height)
-{
-        if (clip_frame (context, &x, &y, &width, &height))
-                (*context->update_func) (context->frame->pixbuf, 
-                                         x, y, width, height,
-                                         context->user_data);
 }
 
 static int
 gif_get_lzw (GifContext *context)
 {
-       guchar *dest, *temp;
-       gint lower_bound, upper_bound; /* bounds for emitting the area_updated signal */
-       gboolean bound_flag;
-       gint first_pass; /* bounds for emitting the area_updated signal */
-       gint v;
-
        if (context->frame == NULL) {
-                context->frame = g_new (GdkPixbufFrame, 1);
-
-                context->frame->composited = NULL;
-                context->frame->revert = NULL;
-                
-                if (context->frame_len == 0 || context->frame_height == 0) {
-                        /* An empty frame, we just output a single transparent
-                         * pixel at (0, 0).
-                         */
-                        context->x_offset = 0;
-                        context->y_offset = 0;
-                        context->frame_len = 1;
-                        context->frame_height = 1;
-                        context->frame->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
-                        if (context->frame->pixbuf) {
-                                guchar *pixels;
-
-                                pixels = gdk_pixbuf_get_pixels (context->frame->pixbuf);
-                                pixels[0] = 0;
-                                pixels[1] = 0;
-                                pixels[2] = 0;
-                                pixels[3] = 0;
-                        }
-                } else {
-                        int rowstride;
-                        guint64 len;
-
-                        rowstride = gdk_pixbuf_calculate_rowstride (GDK_COLORSPACE_RGB,
-                                                                    TRUE,
-                                                                    8,
-                                                                    context->frame_len,
-                                                                    context->frame_height);
-                        if (rowstride > 0 &&
-                            g_uint64_checked_mul (&len, rowstride, context->frame_height) &&
-                            len <= G_MAXINT) {
-                                context->frame->pixbuf =
-                                        gdk_pixbuf_new (GDK_COLORSPACE_RGB,
-                                                        TRUE,
-                                                        8,
-                                                        context->frame_len,
-                                                        context->frame_height);
-                        } else {
-                                context->frame->pixbuf = NULL;
-                        }
-                }
-
-                if (!context->frame->pixbuf) {
-                        g_free (context->frame);
+                int rowstride;
+                guint64 len;
+
+                rowstride = gdk_pixbuf_calculate_rowstride (GDK_COLORSPACE_RGB,
+                                                            TRUE,
+                                                            8,
+                                                            context->frame_len,
+                                                            context->frame_height);
+                if (rowstride < 0 ||
+                    !g_uint64_checked_mul (&len, rowstride, context->frame_height) ||
+                    len >= G_MAXINT) {
                         g_set_error_literal (context->error,
                                              GDK_PIXBUF_ERROR,
                                              GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
@@ -604,10 +437,34 @@ gif_get_lzw (GifContext *context)
                         return -2;
                 }
 
+                context->frame = g_new0 (GdkPixbufFrame, 1);
+
+                context->frame->lzw_data = g_byte_array_new ();
+                context->frame->lzw_code_size = context->lzw_set_code_size;
+
+                context->frame->width = context->frame_len;
+                context->frame->height = context->frame_height;
                 context->frame->x_offset = context->x_offset;
                 context->frame->y_offset = context->y_offset;
-                context->frame->need_recomposite = TRUE;
-                
+                context->frame->interlace = context->frame_interlace;
+
+                if (context->frame_colormap_size > 0) {
+                        int i;
+
+                        context->frame->color_map = g_malloc (256 * 3);
+                        context->frame->color_map_allocated = TRUE;
+                        for (i = 0; i < 256; i++) {
+                                context->frame->color_map[i * 3 + 0] = context->frame_color_map[0][i];
+                                context->frame->color_map[i * 3 + 1] = context->frame_color_map[1][i];
+                                context->frame->color_map[i * 3 + 2] = context->frame_color_map[2][i];
+                        }
+                }
+                else {
+                        context->frame->color_map = context->animation->color_map;
+                }
+
+                context->frame->transparent_index = context->gif89.transparent;
+
                 /* GIF delay is in hundredths, we want thousandths */
                 context->frame->delay_time = context->gif89.delay_time * 10;
 
@@ -622,7 +479,7 @@ gif_get_lzw (GifContext *context)
                  */
                 if (context->frame->delay_time < 20)
                         context->frame->delay_time = 20; /* 20 = "fast" */
-                
+
                 context->frame->elapsed = context->animation->total_time;
                 context->animation->total_time += context->frame->delay_time;                
                 
@@ -642,228 +499,47 @@ gif_get_lzw (GifContext *context)
                         break;
                 }
 
-                context->animation->n_frames ++;
                 context->animation->frames = g_list_append (context->animation->frames, context->frame);
 
-                /* Only call prepare_func for the first frame */
-               if (context->animation->frames->next == NULL) { 
-                        if (context->animation->width == 0 )
-                                context->animation->width = gdk_pixbuf_get_width(context->frame->pixbuf);
-                        if (context->animation->height == 0)
-                                context->animation->height = gdk_pixbuf_get_height (context->frame->pixbuf);
-
-                        if (context->prepare_func)
-                                (* context->prepare_func) (context->frame->pixbuf,
-                                                           GDK_PIXBUF_ANIMATION (context->animation),
-                                                           context->user_data);
-                } else {
-                        /* Otherwise init frame with last frame */
-                        GList *link;
-                        GdkPixbufFrame *prev_frame;
-                        gint x, y, w, h;
-                        
-                        link = g_list_find (context->animation->frames, context->frame);
-
-                        prev_frame = link->prev->data;
-
-                        gdk_pixbuf_gif_anim_frame_composite (context->animation, prev_frame);
-
-                        /* Composite failed */
-                        if (prev_frame->composited == NULL) {
-                                GdkPixbufFrame *frame = NULL;
-                                link = g_list_first (context->animation->frames);
-                                while (link != NULL) {
-                                        frame = (GdkPixbufFrame *)link->data;
-                                        if (frame != NULL) {
-                                                if (frame->pixbuf != NULL)
-                                                        g_object_unref (frame->pixbuf);
-                                                if (frame->composited != NULL)
-                                                        g_object_unref (frame->composited);
-                                                if (frame->revert != NULL)
-                                                        g_object_unref (frame->revert);
-                                                g_free (frame);
-                                        }
-                                        link = link->next;
-                                }
-                                
-                                g_list_free (context->animation->frames);
-                                context->animation->frames = NULL;
-                                
-                                g_set_error_literal (context->error,
-                                                     GDK_PIXBUF_ERROR,
-                                                     GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
-                                                     _("Not enough memory to composite a frame in GIF 
file"));
-                                return -2;
-                        }
-                    
-                        x = context->frame->x_offset;
-                        y = context->frame->y_offset;
-                        w = gdk_pixbuf_get_width (context->frame->pixbuf);
-                        h = gdk_pixbuf_get_height (context->frame->pixbuf);
-                        if (clip_frame (context, &x, &y, &w, &h))
-                                gdk_pixbuf_copy_area (prev_frame->composited,
-                                                      x, y, w, h,
-                                                      context->frame->pixbuf,
-                                                      0, 0);
-                }
+               /* Notify when have first frame */
+               if (context->animation->frames->next == NULL && context->prepare_func != NULL) {
+                       GdkPixbuf *pixbuf = gdk_pixbuf_animation_get_static_image (GDK_PIXBUF_ANIMATION 
(context->animation));
+                       if (pixbuf != NULL)
+                               (* context->prepare_func) (pixbuf,
+                                                          GDK_PIXBUF_ANIMATION (context->animation),
+                                                          context->user_data);
+               }
         }
 
-       dest = gdk_pixbuf_get_pixels (context->frame->pixbuf);
-
-       bound_flag = FALSE;
-       lower_bound = upper_bound = context->draw_ypos;
-       first_pass = context->draw_pass;
-
+       /* read all blocks */
        while (TRUE) {
-                guchar (*cmap)[MAXCOLORMAPSIZE];
-                guint8 block[255];
-                gint empty_block = FALSE;
-                gsize n_indexes, i;
-
-                if (context->frame_cmap_active)
-                        cmap = context->frame_color_map;
-                else
-                        cmap = context->global_color_map;
-
-               v = get_data_block (context, block, &empty_block);
-               if (v < 0) {
-                       goto finished_data;
-               }
-               if (empty_block) {
-                       goto done;
-               }
-
-               bound_flag = TRUE;
+               gint retval, empty_block = FALSE;
 
-                g_assert (gdk_pixbuf_get_has_alpha (context->frame->pixbuf));
-
-               if (context->lzw_decoder == NULL) {
-                       context->lzw_decoder = lzw_decoder_new (context->lzw_set_code_size + 1);
-                       context->index_buffer_length = context->frame_len * context->frame_height;
-                       context->index_buffer = g_new (guint8, context->index_buffer_length);
-                }
-               n_indexes = lzw_decoder_feed (context->lzw_decoder, block, context->block_count, 
context->index_buffer, context->index_buffer_length);
-                context->block_count = 0;
-
-               for (i = 0; i < n_indexes; i++) {
-                v = context->index_buffer[i];
-
-                temp = dest + context->draw_ypos * gdk_pixbuf_get_rowstride (context->frame->pixbuf) + 
context->draw_xpos * 4;
-                *temp = cmap [0][(guchar) v];
-                *(temp+1) = cmap [1][(guchar) v];
-                *(temp+2) = cmap [2][(guchar) v];
-                *(temp+3) = (guchar) ((v == context->gif89.transparent) ? 0 : 255);
-
-               if (context->prepare_func && context->frame_interlace)
-                       gif_fill_in_lines (context, dest, v);
-
-               context->draw_xpos++;
-                
-               if (context->draw_xpos == context->frame_len) {
-                       context->draw_xpos = 0;
-                       if (context->frame_interlace) {
-                               switch (context->draw_pass) {
-                               case 0:
-                               case 1:
-                                       context->draw_ypos += 8;
-                                       break;
-                               case 2:
-                                       context->draw_ypos += 4;
-                                       break;
-                               case 3:
-                                       context->draw_ypos += 2;
-                                       break;
-                               }
+               retval = get_data_block (context, (unsigned char *) context->block_buf, &empty_block);
 
-                               if (context->draw_ypos >= context->frame_height) {
-                                       context->draw_pass++;
-                                       switch (context->draw_pass) {
-                                       case 1:
-                                               context->draw_ypos = 4;
-                                               break;
-                                       case 2:
-                                               context->draw_ypos = 2;
-                                               break;
-                                       case 3:
-                                               context->draw_ypos = 1;
-                                               break;
-                                       default:
-                                               goto done;
-                                       }
-                               }
-                       } else {
-                               context->draw_ypos++;
-                       }
-                       if (context->draw_pass != first_pass) {
-                               if (context->draw_ypos > lower_bound) {
-                                       lower_bound = 0;
-                                       upper_bound = context->frame_height;
-                               } else {
-                                        
-                               }
-                       } else
-                               upper_bound = context->draw_ypos;
-               }
-               if (context->draw_ypos >= context->frame_height)
-                       break;
+               /* Notify frame update */
+               if ((retval != 0 || empty_block) && context->animation->frames->next == NULL && 
context->update_func != NULL) {
+                       GdkPixbuf *pixbuf = gdk_pixbuf_animation_get_static_image (GDK_PIXBUF_ANIMATION 
(context->animation));
+                       if (pixbuf)
+                               (* context->update_func) (pixbuf,
+                                                         0, 0, context->frame->width, context->frame->height,
+                                                         context->user_data);
                }
-       }
-
- done:
-
-        context->state = GIF_GET_NEXT_STEP;
 
-        v = 0;
+               if (retval != 0)
+                       return retval;
 
- finished_data:
-        
-        if (bound_flag)
-                context->frame->need_recomposite = TRUE;
-        
-       if (bound_flag && context->update_func) {
-               if (lower_bound <= upper_bound && first_pass == context->draw_pass) {
-                        maybe_update (context,
-                                      context->frame->x_offset,
-                                      context->frame->y_offset + lower_bound,
-                                      gdk_pixbuf_get_width (context->frame->pixbuf),
-                                      upper_bound - lower_bound + 1);
-               } else {
-                       if (lower_bound <= upper_bound) {
-                                maybe_update (context,
-                                              context->frame->x_offset,
-                                              context->frame->y_offset,
-                                              gdk_pixbuf_get_width (context->frame->pixbuf),
-                                              gdk_pixbuf_get_height (context->frame->pixbuf));
-                       } else {
-                                maybe_update (context,
-                                              context->frame->x_offset,
-                                              context->frame->y_offset,
-                                              gdk_pixbuf_get_width (context->frame->pixbuf),
-                                              upper_bound);
-                                maybe_update (context,
-                                              context->frame->x_offset,
-                                              context->frame->y_offset + lower_bound,
-                                              gdk_pixbuf_get_width (context->frame->pixbuf),
-                                              gdk_pixbuf_get_height (context->frame->pixbuf) - lower_bound);
-                       }
+               if (empty_block) {
+                       context->frame = NULL;
+                       context->state = GIF_GET_NEXT_STEP;
+                       return 0;
                }
-       }
-
-       if (context->state == GIF_GET_NEXT_STEP) {
-               g_clear_object (&context->lzw_decoder);
-               g_clear_pointer (&context->index_buffer, g_free);
-
-                /* Will be freed with context->animation, we are just
-                 * marking that we're done with it (no current frame)
-                 */
-               context->frame = NULL;
-                context->frame_cmap_active = FALSE;
 
-                if (context->stop_after_first_frame)
-                        context->state =  GIF_DONE;
+               g_byte_array_append (context->frame->lzw_data, context->block_buf, context->block_count);
+               if (context->animation->last_frame == context->frame)
+                       context->animation->last_frame = NULL;
+               context->block_count = 0;
        }
-       
-       return v;
 }
 
 static void
@@ -879,7 +555,7 @@ gif_prepare_lzw (GifContext *context)
                return -1;
        }
         
-        if (context->lzw_set_code_size > LZW_CODE_MAX) {
+        if (context->lzw_set_code_size > 12) {
                 g_set_error_literal (context->error,
                                      GDK_PIXBUF_ERROR,
                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
@@ -887,9 +563,6 @@ gif_prepare_lzw (GifContext *context)
                 return -2;
         }
 
-       context->lzw_decoder = NULL;
-       context->index_buffer = NULL;
-
        gif_set_get_lzw (context);
 
        return 0;
@@ -955,13 +628,6 @@ gif_init (GifContext *context)
        context->background_index = buf[5];
        context->aspect_ratio = buf[6];
 
-        /* Use background of transparent black as default, though if
-         * one isn't set explicitly no one should ever use it.
-         */
-        context->animation->bg_red = 0;
-        context->animation->bg_green = 0;
-        context->animation->bg_blue = 0;
-
         context->animation->width = context->width;
         context->animation->height = context->height;
 
@@ -1010,7 +676,7 @@ gif_get_frame_info (GifContext *context)
        if (!gif_read (context, buf, 9)) {
                return -1;
        }
-        
+
        /* Okay, we got all the info we need.  Lets record it */
        context->frame_len = LM_to_uint (buf[4], buf[5]);
        context->frame_height = LM_to_uint (buf[6], buf[7]);
@@ -1195,7 +861,6 @@ new_context (void)
        context->gif89.disposal = -1;
         context->animation->loop = 1;
         context->in_loop_extension = FALSE;
-        context->stop_after_first_frame = FALSE;
 
        return context;
 }
@@ -1221,7 +886,6 @@ gdk_pixbuf__gif_image_load (FILE *file, GError **error)
         
        context->file = file;
         context->error = error;
-        context->stop_after_first_frame = TRUE;
 
         retval = gif_main_loop (context);
        if (retval == -1 || context->animation->frames == NULL) {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]