[gimp/wip/animation: 241/373] plug-ins: add new animation type "cel animation".



commit 2586f083bd9db37bbaa372b6fbbef03aba7caf61
Author: Jehan <jehan girinstud io>
Date:   Fri Jul 29 03:18:51 2016 +0200

    plug-ins: add new animation type "cel animation".
    
    This is for more featureful animation with several layers/tracks of
    images composed into single frames.
    Right now, there is no UI. Only the core code is implemented.
    There is no XML serialization for this type of animation yet.

 plug-ins/animation-play/Makefile.am                |    2 +
 plug-ins/animation-play/animation-utils.c          |  137 ++++++
 plug-ins/animation-play/animation-utils.h          |   17 +-
 .../animation-play/core/animation-celanimation.c   |  493 ++++++++++++++++++++
 .../animation-play/core/animation-celanimation.h   |   58 +++
 plug-ins/animation-play/core/animation.c           |   84 ++++-
 plug-ins/animation-play/core/animation.h           |    3 +-
 plug-ins/animation-play/core/animationanimatic.c   |   71 +---
 plug-ins/animation-play/widgets/animation-dialog.c |  193 +++++----
 9 files changed, 914 insertions(+), 144 deletions(-)
---
diff --git a/plug-ins/animation-play/Makefile.am b/plug-ins/animation-play/Makefile.am
index 944432e..0533e6b 100644
--- a/plug-ins/animation-play/Makefile.am
+++ b/plug-ins/animation-play/Makefile.am
@@ -47,6 +47,8 @@ animation_play_SOURCES = \
        core/animation.c                                \
        core/animationanimatic.h                \
        core/animationanimatic.c                \
+       core/animation-celanimation.h   \
+       core/animation-celanimation.c   \
        widgets/animation-dialog.h              \
        widgets/animation-dialog.c              \
        widgets/animation-storyboard.h  \
diff --git a/plug-ins/animation-play/animation-utils.c b/plug-ins/animation-play/animation-utils.c
index f5f51d0..67b6a91 100755
--- a/plug-ins/animation-play/animation-utils.c
+++ b/plug-ins/animation-play/animation-utils.c
@@ -96,3 +96,140 @@ total_alpha_preview (guchar *drawing_data,
         }
     }
 }
+
+/**
+ * normal_blend:
+ * @width: width of the returned #GeglBuffer.
+ * @height: height of the returned #GeglBuffer.
+ * @backdrop_buffer: optional backdrop image (may be %NULL).
+ * @backdrop_scale_ratio: scale ratio (`]0.0, 1.0]`) for @backdrop_buffer.
+ * @backdrop_offset_x: original X offset of the backdrop (not processed
+ * with @backdrop_scale_ratio yet).
+ * @backdrop_offset_y: original Y offset of the backdrop (not processed
+ * with @backdrop_scale_ratio yet).
+ * @source_buffer: source image (cannot be %NULL).
+ * @source_scale_ratio: scale ratio (`]0.0, 1.0]`) for @source_buffer.
+ * @source_offset_x: original X offset of the source (not processed with
+ * @source_scale_ratio yet).
+ * @source_offset_y: original Y offset of the source (not processed with
+ * @source_scale_ratio yet).
+ *
+ * Creates a new #GeglBuffer of size @widthx@height with @source_buffer
+ * scaled with @source_scale_ratio r, and translated by offsets:
+ * (@source_offset_x * @source_scale_ratio,
+ *  @source_offset_y * @source_scale_ratio).
+ *
+ * If @backdrop_buffer is not %NULL, it is resized with
+ * @backdrop_scale_ratio, and offsetted by:
+ * (@backdrop_offset_x * @backdrop_scale_ratio,
+ *  @backdrop_offset_y * @backdrop_scale_ratio)
+ *
+ * Finally @source_buffer is composited over @backdrop_buffer in normal
+ * blend mode.
+ *
+ * Returns: the newly allocated #GeglBuffer containing the result of
+ * said scaling, translation and blending.
+ */
+GeglBuffer *
+normal_blend (gint        width,
+              gint        height,
+              GeglBuffer *backdrop_buffer,
+              gdouble     backdrop_scale_ratio,
+              gint        backdrop_offset_x,
+              gint        backdrop_offset_y,
+              GeglBuffer *source_buffer,
+              gdouble     source_scale_ratio,
+              gint        source_offset_x,
+              gint        source_offset_y)
+{
+  GeglBuffer *buffer;
+  GeglNode   *graph;
+  GeglNode   *source, *src_scale, *src_translate;
+  GeglNode   *backdrop, *bd_scale, *bd_translate;
+  GeglNode   *blend, *target;
+  gdouble     offx;
+  gdouble     offy;
+
+  g_return_val_if_fail (source_scale_ratio >  0.0 &&
+                        source_scale_ratio <= 1.0 &&
+                        source_buffer             &&
+                        (! backdrop_buffer ||
+                         (backdrop_scale_ratio >= 0.1 &&
+                          backdrop_scale_ratio <= 1.0)),
+                        NULL);
+
+  /* Panel image. */
+  buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
+                            gegl_buffer_get_format (source_buffer));
+  graph  = gegl_node_new ();
+
+  /* Source */
+  source = gegl_node_new_child (graph,
+                                "operation", "gegl:buffer-source",
+                                "buffer", source_buffer,
+                                NULL);
+  src_scale  = gegl_node_new_child (graph,
+                                    "operation", "gegl:scale-ratio",
+                                    "sampler", GEGL_SAMPLER_NEAREST,
+                                    "x", source_scale_ratio,
+                                    "y", source_scale_ratio,
+                                    NULL);
+
+  offx = source_offset_x * source_scale_ratio;
+  offy = source_offset_y * source_scale_ratio;
+  src_translate =  gegl_node_new_child (graph,
+                                        "operation", "gegl:translate",
+                                        "x", offx,
+                                        "y", offy,
+                                        NULL);
+
+  /* Target */
+  target = gegl_node_new_child (graph,
+                                "operation", "gegl:write-buffer",
+                                "buffer", buffer,
+                                NULL);
+
+  if (backdrop_buffer)
+    {
+      /* Backdrop */
+      backdrop = gegl_node_new_child (graph,
+                                      "operation", "gegl:buffer-source",
+                                      "buffer", backdrop_buffer,
+                                      NULL);
+      bd_scale  = gegl_node_new_child (graph,
+                                       "operation", "gegl:scale-ratio",
+                                       "sampler", GEGL_SAMPLER_NEAREST,
+                                       "x", backdrop_scale_ratio,
+                                       "y", backdrop_scale_ratio,
+                                       NULL);
+
+      offx = backdrop_offset_x * backdrop_scale_ratio;
+      offy = backdrop_offset_y * backdrop_scale_ratio;
+      bd_translate =  gegl_node_new_child (graph,
+                                           "operation", "gegl:translate",
+                                           "x", offx,
+                                           "y", offy,
+                                           NULL);
+
+      gegl_node_link_many (source, src_scale, src_translate, NULL);
+      gegl_node_link_many (backdrop, bd_scale, bd_translate, NULL);
+
+      /* Blending */
+      blend =  gegl_node_new_child (graph,
+                                    "operation", "gegl:over",
+                                    NULL);
+
+      gegl_node_link_many (bd_translate, blend, target, NULL);
+      gegl_node_connect_to (src_translate, "output",
+                            blend, "aux");
+    }
+  else
+    {
+      gegl_node_link_many (source, src_scale, src_translate, target, NULL);
+    }
+
+  gegl_node_process (target);
+  g_object_unref (graph);
+
+  return buffer;
+}
diff --git a/plug-ins/animation-play/animation-utils.h b/plug-ins/animation-play/animation-utils.h
index de3dcb5..bb5a34f 100755
--- a/plug-ins/animation-play/animation-utils.h
+++ b/plug-ins/animation-play/animation-utils.h
@@ -28,9 +28,20 @@
 #define MAX_FRAMERATE      300.0
 #define DEFAULT_FRAMERATE  24.0
 
-void total_alpha_preview (guchar *drawing_data,
-                          guint   drawing_width,
-                          guint   drawing_height);
+void         total_alpha_preview (guchar     *drawing_data,
+                                  guint       drawing_width,
+                                  guint       drawing_height);
+
+GeglBuffer * normal_blend        (gint        width,
+                                  gint        height,
+                                  GeglBuffer *backdrop,
+                                  gdouble     backdrop_scale_ratio,
+                                  gint        backdrop_offset_x,
+                                  gint        backdrop_offset_y,
+                                  GeglBuffer *source,
+                                  gdouble     source_scale_ratio,
+                                  gint        source_offset_x,
+                                  gint        source_offset_y);
 
 #endif  /*  __ANIMATION_UTILS_H__  */
 
diff --git a/plug-ins/animation-play/core/animation-celanimation.c 
b/plug-ins/animation-play/core/animation-celanimation.c
new file mode 100644
index 0000000..7b3dd58
--- /dev/null
+++ b/plug-ins/animation-play/core/animation-celanimation.c
@@ -0,0 +1,493 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * animation.c
+ * Copyright (C) 2016 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libgimp/gimp.h>
+#include <libgimp/stdplugins-intl.h>
+
+#include "animation-utils.h"
+#include "animation-celanimation.h"
+
+typedef struct _AnimationCelAnimationPrivate AnimationCelAnimationPrivate;
+
+typedef struct
+{
+  gchar *title;
+  /* The list of layers (identified by their tattoos). */
+  GList *frames;
+}
+Track;
+
+typedef struct
+{
+  GeglBuffer *buffer;
+
+  /* The list of layers (identified by their tattoos) composited into
+   * this buffer allows to easily compare caches so that to not
+   * duplicate them.*/
+  gint *composition;
+  gint  n_sources;
+
+  gint refs;
+}
+Cache;
+
+struct _AnimationCelAnimationPrivate
+{
+  /* The number of frames. */
+  gint   duration;
+
+  /* Frames are cached as GEGL buffers. */
+  GList *cache;
+
+  /* Panel comments. */
+  GList *comments;
+
+  /* List of tracks/levels.
+   * The background is a special-named track, always present
+   * and first.
+   * There is always at least 1 additional track. */
+  GList *tracks;
+};
+
+#define GET_PRIVATE(animation) \
+        G_TYPE_INSTANCE_GET_PRIVATE (animation, \
+                                     ANIMATION_TYPE_CEL_ANIMATION, \
+                                     AnimationCelAnimationPrivate)
+
+static void         animation_cel_animation_finalize   (GObject           *object);
+
+/* Virtual methods */
+
+static gint         animation_cel_animation_get_length  (Animation         *animation);
+
+static void         animation_cel_animation_load        (Animation         *animation);
+static GeglBuffer * animation_cel_animation_get_frame   (Animation         *animation,
+                                                         gint               pos);
+static gboolean     animation_cel_animation_same        (Animation         *animation,
+                                                         gint               previous_pos,
+                                                         gint               next_pos);
+
+/* Utils */
+
+static void         animation_cel_animation_cleanup   (AnimationCelAnimation  *animation);
+static void         animation_cel_animation_cache      (AnimationCelAnimation *animation,
+                                                        gint                   position);
+static gboolean     animation_cel_animation_cache_cmp (Cache *cache1,
+                                                       Cache *cache2);
+
+G_DEFINE_TYPE (AnimationCelAnimation, animation_cel_animation, ANIMATION_TYPE_ANIMATION)
+
+#define parent_class animation_cel_animation_parent_class
+
+static void
+animation_cel_animation_class_init (AnimationCelAnimationClass *klass)
+{
+  GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+  AnimationClass *anim_class   = ANIMATION_CLASS (klass);
+
+  object_class->finalize = animation_cel_animation_finalize;
+
+  anim_class->get_length = animation_cel_animation_get_length;
+  anim_class->load       = animation_cel_animation_load;
+  anim_class->get_frame  = animation_cel_animation_get_frame;
+  anim_class->same       = animation_cel_animation_same;
+
+  g_type_class_add_private (klass, sizeof (AnimationCelAnimationPrivate));
+}
+
+static void
+animation_cel_animation_init (AnimationCelAnimation *animation)
+{
+  animation->priv = G_TYPE_INSTANCE_GET_PRIVATE (animation,
+                                                 ANIMATION_TYPE_CEL_ANIMATION,
+                                                 AnimationCelAnimationPrivate);
+}
+
+static void
+animation_cel_animation_finalize (GObject *object)
+{
+  animation_cel_animation_cleanup (ANIMATION_CEL_ANIMATION (object));
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+/**** Public Functions ****/
+
+void
+animation_cel_animation_set_comment (AnimationCelAnimation *animation,
+                                     gint                   position,
+                                     const gchar           *comment)
+{
+  GList *item;
+
+  g_return_if_fail (position > 0 &&
+                    position <= animation->priv->duration);
+
+  item = g_list_nth (animation->priv->comments, position - 1);
+  if (item->data)
+    {
+      g_free (item->data);
+    }
+
+  item->data = g_strdup (comment);
+}
+
+const gchar *
+animation_cel_animation_get_comment (AnimationCelAnimation *animation,
+                                     gint                   position)
+{
+  g_return_val_if_fail (position > 0 &&
+                        position <= animation->priv->duration,
+                        0);
+
+  return g_list_nth_data (animation->priv->comments, position - 1);
+}
+
+/**** Virtual methods ****/
+
+static gint
+animation_cel_animation_get_length (Animation *animation)
+{
+  return ANIMATION_CEL_ANIMATION (animation)->priv->duration;
+}
+
+static void
+animation_cel_animation_load (Animation *animation)
+{
+  AnimationCelAnimationPrivate *priv;
+  Track                        *track;
+  gint32                        image_id;
+  gint32                        layer;
+  gint                          i;
+
+  priv = ANIMATION_CEL_ANIMATION (animation)->priv;
+
+  /* Cleaning. */
+  animation_cel_animation_cleanup (ANIMATION_CEL_ANIMATION (animation));
+
+  /* Purely arbitrary value. User will anyway change it to suit one's needs. */
+  priv->duration = 240;
+
+  /* There are at least 2 tracks.
+   * Second one is freely-named. */
+  track = g_new0 (Track, 1);
+  track->title = g_strdup (_("Name me"));
+  priv->tracks = g_list_prepend (priv->tracks, track);
+  /* The first track is called "Background". */
+  track = g_new0 (Track, 1);
+  track->title = g_strdup (_("Background"));
+  priv->tracks = g_list_prepend (priv->tracks, track);
+
+  /* If there is a layer named "Background", set it to all frames
+   * on background track. */
+  image_id = animation_get_image_id (animation);
+  layer    = gimp_image_get_layer_by_name (image_id, _("Background"));
+  if (layer > 0)
+    {
+      gint tattoo;
+
+      tattoo = gimp_item_get_tattoo (layer);
+      for (i = 0; i < priv->duration; i++)
+        {
+          track->frames = g_list_prepend (track->frames,
+                                          GINT_TO_POINTER (tattoo));
+        }
+    }
+
+  /* Finally cache. */
+  for (i = 0; i < priv->duration; i++)
+    {
+      g_signal_emit_by_name (animation, "loading",
+                             (gdouble) i / ((gdouble) priv->duration - 0.999));
+
+      /* Panel image. */
+      animation_cel_animation_cache (ANIMATION_CEL_ANIMATION (animation), i);
+    }
+}
+
+static GeglBuffer *
+animation_cel_animation_get_frame (Animation *animation,
+                                   gint       pos)
+{
+  AnimationCelAnimation *cel_animation;
+  Cache                 *cache;
+  GeglBuffer            *frame = NULL;
+
+  cel_animation = ANIMATION_CEL_ANIMATION (animation);
+
+  cache = g_list_nth_data (cel_animation->priv->cache,
+                           pos - 1);
+  if (cache)
+    {
+      frame = g_object_ref (cache->buffer);
+    }
+  return frame;
+}
+
+static gboolean
+animation_cel_animation_same (Animation *animation,
+                              gint       pos1,
+                              gint       pos2)
+{
+  AnimationCelAnimation *cel_animation;
+  Cache                 *cache1;
+  Cache                 *cache2;
+
+  cel_animation = ANIMATION_CEL_ANIMATION (animation);
+
+  g_return_val_if_fail (pos1 > 0                              &&
+                        pos1 <= cel_animation->priv->duration &&
+                        pos2 > 0                              &&
+                        pos2 <= cel_animation->priv->duration,
+                        FALSE);
+
+  cache1 = g_list_nth_data (cel_animation->priv->cache,
+                            pos1);
+  cache2 = g_list_nth_data (cel_animation->priv->cache,
+                            pos2);
+
+  return animation_cel_animation_cache_cmp (cache1, cache2);
+}
+
+/**** Utils ****/
+
+static void
+animation_cel_animation_cleanup (AnimationCelAnimation *animation)
+{
+  AnimationCelAnimationPrivate *priv;
+  GList                        *iter;
+
+  priv = ANIMATION_CEL_ANIMATION (animation)->priv;
+
+  if (priv->cache)
+    {
+      for (iter = priv->cache; iter; iter = iter->next)
+        {
+          if (iter->data)
+            {
+              Cache *cache = iter->data;
+
+              if (--(cache->refs) == 0)
+                {
+                  g_object_unref (cache->buffer);
+                  g_free (cache->composition);
+                }
+            }
+        }
+      g_list_free (priv->cache);
+    }
+  if (priv->comments)
+    {
+      for (iter = priv->comments; iter; iter = iter->next)
+        {
+          if (iter->data)
+            {
+              g_free (iter->data);
+            }
+        }
+      g_list_free (priv->comments);
+    }
+  if (priv->tracks)
+    {
+      for (iter = priv->tracks; iter; iter = iter->next)
+        {
+          Track *track = iter->data;
+
+          /* The item's data must always exist. */
+          g_free (track->title);
+          g_list_free (track->frames);
+        }
+      g_list_free (priv->tracks);
+    }
+  priv->cache    = NULL;
+  priv->comments = NULL;
+  priv->tracks   = NULL;
+}
+
+static void
+animation_cel_animation_cache (AnimationCelAnimation *animation,
+                               gint                   pos)
+{
+  GeglBuffer *backdrop = NULL;
+  GList      *iter;
+  Cache      *cache;
+  gint       *composition;
+  gint        n_sources = 0;
+  gint32      image_id;
+  gdouble     proxy_ratio;
+  gint        preview_width;
+  gint        preview_height;
+  gint        i;
+
+  /* Clean out current cache. */
+  iter = g_list_nth (animation->priv->cache, pos);
+  if (iter && iter->data)
+    {
+      Cache *cache = iter->data;
+
+      if (--(cache->refs) == 0)
+        {
+          g_free (cache->composition);
+          g_object_unref (cache->buffer);
+          g_free (cache);
+        }
+      iter->data = NULL;
+    }
+
+  /* Check if new configuration needs caching. */
+  for (iter = animation->priv->tracks; iter; iter = iter->next)
+    {
+      Track *track = iter->data;
+
+      if (GPOINTER_TO_INT (g_list_nth_data (track->frames, pos)))
+        {
+          n_sources++;
+        }
+    }
+  if (n_sources == 0)
+    {
+      return;
+    }
+
+  /* Make sure the cache list is long enough. */
+  if (pos >= g_list_length (animation->priv->cache))
+    {
+      animation->priv->cache = g_list_reverse (animation->priv->cache);
+      for (i = g_list_length (animation->priv->cache); i <= pos; i++)
+        {
+          animation->priv->cache = g_list_prepend (animation->priv->cache,
+                                                   NULL);
+        }
+      animation->priv->cache = g_list_reverse (animation->priv->cache);
+    }
+
+  /* Create the new buffer composition. */
+  composition = g_new0 (int, n_sources);
+  i = 0;
+  for (iter = animation->priv->tracks; iter; iter = iter->next)
+    {
+      Track *track = iter->data;
+      gint   tattoo;
+
+      tattoo = GPOINTER_TO_INT (g_list_nth_data (track->frames, pos));
+      if (tattoo)
+        {
+          composition[i++] = tattoo;
+        }
+    }
+
+  /* Check if new configuration was not already cached. */
+  for (iter = animation->priv->cache; iter; iter = iter->next)
+    {
+      if (iter->data)
+        {
+          Cache    *cache = iter->data;
+          gboolean  same = FALSE;
+
+          if (n_sources == cache->n_sources)
+            {
+              same = TRUE;
+              for (i = 0; i < n_sources; i++)
+                {
+                  if (cache->composition[i] != composition[i])
+                    {
+                      same = FALSE;
+                      break;
+                    }
+                }
+              if (same)
+                {
+                  /* A buffer with the same contents already exists. */
+                  g_free (composition);
+                  (cache->refs)++;
+                  g_list_nth (animation->priv->cache, pos)->data = cache;
+                  return;
+                }
+            }
+        }
+    }
+
+  /* New configuration. Finally compute the cache. */
+  cache = g_new0 (Cache, 1);
+  cache->refs        = 1;
+  cache->n_sources   = n_sources;
+  cache->composition = composition;
+
+  image_id    = animation_get_image_id (ANIMATION (animation));
+  proxy_ratio = animation_get_proxy (ANIMATION (animation));
+  animation_get_size (ANIMATION (animation),
+                      &preview_width, &preview_height);
+
+  for (i = 0; i < n_sources; i++)
+    {
+      GeglBuffer *source;
+      GeglBuffer *intermediate;
+      gint32      layer;
+      gint        layer_offx;
+      gint        layer_offy;
+
+      layer = gimp_image_get_layer_by_tattoo (image_id,
+                                              cache->composition[i]);
+      source = gimp_drawable_get_buffer (layer);
+      gimp_drawable_offsets (layer,
+                             &layer_offx, &layer_offy);
+      intermediate = normal_blend (preview_width, preview_height,
+                                   backdrop, 1.0, 0, 0,
+                                   source, proxy_ratio,
+                                   layer_offx, layer_offy);
+      g_object_unref (source);
+      if (backdrop)
+        {
+          g_object_unref (backdrop);
+        }
+      backdrop = intermediate;
+    }
+  cache->buffer = backdrop;
+
+  /* This item exists and has a NULL data. */
+  g_list_nth (animation->priv->cache, pos)->data = cache;
+}
+
+
+static gboolean
+animation_cel_animation_cache_cmp (Cache *cache1,
+                                   Cache *cache2)
+{
+  gboolean identical = FALSE;
+
+  if (cache1 && cache2 &&
+      cache1->n_sources == cache2->n_sources)
+    {
+      gint i;
+
+      identical = TRUE;
+      for (i = 0; i < cache1->n_sources; i++)
+        {
+          if (cache1->composition[i] != cache2->composition[i])
+            {
+              identical = FALSE;
+              break;
+            }
+        }
+    }
+  return identical;
+}
diff --git a/plug-ins/animation-play/core/animation-celanimation.h 
b/plug-ins/animation-play/core/animation-celanimation.h
new file mode 100644
index 0000000..a33e403
--- /dev/null
+++ b/plug-ins/animation-play/core/animation-celanimation.h
@@ -0,0 +1,58 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * animation.h
+ * Copyright (C) 2016 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ANIMATION_CEL_ANIMATION_H__
+#define __ANIMATION_CEL_ANIMATION_H__
+
+#include "animation.h"
+
+#define ANIMATION_TYPE_CEL_ANIMATION            (animation_cel_animation_get_type ())
+#define ANIMATION_CEL_ANIMATION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
ANIMATION_TYPE_CEL_ANIMATION, AnimationCelAnimation))
+#define ANIMATION_CEL_ANIMATION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), 
ANIMATION_TYPE_CEL_ANIMATION, AnimationCelAnimationClass))
+#define ANIMATION_IS_CEL_ANIMATION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
ANIMATION_TYPE_CEL_ANIMATION))
+#define ANIMATION_IS_CEL_ANIMATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
ANIMATION_TYPE_CEL_ANIMATION))
+#define ANIMATION_CEL_ANIMATION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), 
ANIMATION_TYPE_CEL_ANIMATION, AnimationCelAnimationClass))
+
+typedef struct _AnimationCelAnimation        AnimationCelAnimation;
+typedef struct _AnimationCelAnimationClass   AnimationCelAnimationClass;
+typedef struct _AnimationCelAnimationPrivate AnimationCelAnimationPrivate;
+
+struct _AnimationCelAnimation
+{
+  Animation                     parent_instance;
+
+  AnimationCelAnimationPrivate *priv;
+};
+
+struct _AnimationCelAnimationClass
+{
+  AnimationClass  parent_class;
+};
+
+GType            animation_cel_animation_get_type (void);
+
+
+void             animation_cel_animation_set_comment  (AnimationCelAnimation *animation,
+                                                       gint                   position,
+                                                       const gchar           *comment);
+const gchar    * animation_cel_animation_get_comment  (AnimationCelAnimation *animation,
+                                                       gint                   position);
+
+#endif  /*  __ANIMATION_CEL_ANIMATION_H__  */
diff --git a/plug-ins/animation-play/core/animation.c b/plug-ins/animation-play/core/animation.c
index 1e4d75d..5ca9569 100644
--- a/plug-ins/animation-play/core/animation.c
+++ b/plug-ins/animation-play/core/animation.c
@@ -29,6 +29,16 @@
 
 #include "animation.h"
 #include "animationanimatic.h"
+#include "animation-celanimation.h"
+
+/* Settings we cache assuming they may be the user's
+ * favorite, like a framerate.
+ * These will be used only for image without stored animation. */
+typedef struct
+{
+  gdouble framerate;
+}
+CachedSettings;
 
 enum
 {
@@ -100,6 +110,7 @@ static gint       animation_real_get_start_position (Animation *animation);
 static gboolean   animation_real_same               (Animation *animation,
                                                      gint       prev_pos,
                                                      gint       next_pos);
+gchar           * animation_real_serialize          (Animation *animation);
 
 /* Timer callback for playback. */
 static gboolean   animation_advance_frame_callback  (Animation *animation);
@@ -316,6 +327,7 @@ animation_class_init (AnimationClass *klass)
 
   klass->get_start_position  = animation_real_get_start_position;
   klass->same                = animation_real_same;
+  klass->serialize           = animation_real_serialize;
 
   /**
    * Animation:image:
@@ -335,9 +347,16 @@ static void
 animation_init (Animation *animation)
 {
   AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+  CachedSettings    settings;
+
+  /* Acceptable default settings. */
+  settings.framerate = 24.0;
+
+  /* If we saved any settings globally, use the one from the last run. */
+  gimp_get_data (PLUG_IN_PROC, &settings);
 
   /* Acceptable settings for the default. */
-  priv->framerate   = 24.0; /* fps */
+  priv->framerate   = settings.framerate; /* fps */
   priv->proxy_ratio = 1.0;
 }
 
@@ -345,12 +364,14 @@ animation_init (Animation *animation)
 
 Animation *
 animation_new (gint32       image_id,
+               gboolean     animatic,
                const gchar *xml)
 {
   Animation        *animation;
   AnimationPrivate *priv;
 
-  animation = g_object_new (ANIMATION_TYPE_ANIMATIC,
+  animation = g_object_new (animatic? ANIMATION_TYPE_ANIMATIC :
+                                      ANIMATION_TYPE_CEL_ANIMATION,
                             "image", image_id,
                             NULL);
   priv = ANIMATION_GET_PRIVATE (animation);
@@ -415,10 +436,57 @@ animation_load (Animation *animation)
     g_object_unref (buffer);
 }
 
-gchar *
-animation_serialize (Animation   *animation)
+void
+animation_save_to_parasite (Animation *animation)
 {
-  return ANIMATION_GET_CLASS (animation)->serialize (animation);
+  AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+  GimpParasite     *old_parasite;
+  const gchar      *parasite_name;
+  gchar            *xml;
+  gboolean          undo_step_started = FALSE;
+  CachedSettings    settings;
+
+  /* First saving in cache as default in the same session. */
+  settings.framerate = animation_get_framerate (animation);
+  gimp_set_data (PLUG_IN_PROC, &settings, sizeof (&settings));
+
+  /* Then as a parasite for the specific image. */
+  xml = ANIMATION_GET_CLASS (animation)->serialize (animation);
+
+  if (ANIMATION_IS_ANIMATIC (animation))
+    {
+      parasite_name = PLUG_IN_PROC "/animatic";
+    }
+  else /* ANIMATION_IS_CEL_ANIMATION */
+    {
+      parasite_name = PLUG_IN_PROC "/cel-animation";
+    }
+  /* If there was already parasites and they were all the same as the
+   * current state, do not resave them.
+   * This prevents setting the image in a dirty state while it stayed
+   * the same. */
+  old_parasite = gimp_image_get_parasite (priv->image_id, parasite_name);
+  if (xml && (! old_parasite ||
+              g_strcmp0 ((gchar *) gimp_parasite_data (old_parasite), xml)))
+    {
+      GimpParasite *parasite;
+      if (! undo_step_started)
+        {
+          gimp_image_undo_group_start (priv->image_id);
+          undo_step_started = TRUE;
+        }
+      parasite = gimp_parasite_new (parasite_name,
+                                    GIMP_PARASITE_PERSISTENT | GIMP_PARASITE_UNDOABLE,
+                                    strlen (xml) + 1, xml);
+      gimp_image_attach_parasite (priv->image_id, parasite);
+      gimp_parasite_free (parasite);
+    }
+  gimp_parasite_free (old_parasite);
+
+  if (undo_step_started)
+    {
+      gimp_image_undo_group_end (priv->image_id);
+    }
 }
 
 gint
@@ -801,6 +869,12 @@ animation_real_same (Animation *animation,
   return (previous_pos == next_pos);
 }
 
+gchar *
+animation_real_serialize (Animation   *animation)
+{
+  return NULL;
+}
+
 static gboolean
 animation_advance_frame_callback (Animation *animation)
 {
diff --git a/plug-ins/animation-play/core/animation.h b/plug-ins/animation-play/core/animation.h
index f8e3c2f..6aa2638 100644
--- a/plug-ins/animation-play/core/animation.h
+++ b/plug-ins/animation-play/core/animation.h
@@ -64,13 +64,14 @@ struct _AnimationClass
 GType         animation_get_type (void);
 
 Animation   * animation_new                (gint32       image_id,
+                                            gboolean     animatic,
                                             const gchar *xml);
 
 gint32        animation_get_image_id       (Animation   *animation);
 
 void          animation_load               (Animation   *animation);
 
-gchar       * animation_serialize          (Animation   *animation);
+void          animation_save_to_parasite   (Animation   *animation);
 
 gint          animation_get_start_position (Animation   *animation);
 gint          animation_get_position       (Animation   *animation);
diff --git a/plug-ins/animation-play/core/animationanimatic.c 
b/plug-ins/animation-play/core/animationanimatic.c
index 93762fe..356036f 100644
--- a/plug-ins/animation-play/core/animationanimatic.c
+++ b/plug-ins/animation-play/core/animationanimatic.c
@@ -169,12 +169,12 @@ animation_animatic_class_init (AnimationAnimaticClass *klass)
 
   object_class->finalize = animation_animatic_finalize;
 
-  anim_class->get_length  = animation_animatic_get_length;
-  anim_class->load        = animation_animatic_load;
-  anim_class->load_xml    = animation_animatic_load_xml;
-  anim_class->get_frame   = animation_animatic_get_frame;
-  anim_class->serialize   = animation_animatic_serialize;
-  anim_class->same        = animation_animatic_same;
+  anim_class->get_length = animation_animatic_get_length;
+  anim_class->load       = animation_animatic_load;
+  anim_class->load_xml   = animation_animatic_load_xml;
+  anim_class->get_frame  = animation_animatic_get_frame;
+  anim_class->serialize  = animation_animatic_serialize;
+  anim_class->same       = animation_animatic_same;
 
   g_type_class_add_private (klass, sizeof (AnimationAnimaticPrivate));
 }
@@ -190,6 +190,9 @@ animation_animatic_finalize (GObject *object)
   AnimationAnimaticPrivate *priv = GET_PRIVATE (object);
   gint                      i;
 
+  /* Save first, before cleaning anything. */
+  animation_save_to_parasite (ANIMATION (object));
+
   if (priv->tattoos)
     g_free (priv->tattoos);
   if (priv->durations)
@@ -862,12 +865,9 @@ animation_animatic_cache (AnimationAnimatic *animatic,
   AnimationAnimaticPrivate *priv      = GET_PRIVATE (animatic);
   Animation                *animation = ANIMATION (animatic);
   GeglBuffer               *buffer;
-  GeglNode                 *graph, *source, *scale, *translate, *target;
-  GeglNode                 *backdrop, *blend;
+  GeglBuffer               *backdrop_buffer = NULL;
   gint                      layer_offx;
   gint                      layer_offy;
-  gdouble                   panel_offx;
-  gdouble                   panel_offy;
   gint                      position;
   gint                      preview_width;
   gint                      preview_height;
@@ -890,62 +890,21 @@ animation_animatic_cache (AnimationAnimatic *animatic,
       g_object_unref (priv->cache[panel - 1]);
     }
 
-  /* Panel image. */
   proxy_ratio = animation_get_proxy (animation);
   buffer = gimp_drawable_get_buffer (layer);
   animation_get_size (animation, &preview_width, &preview_height);
-  priv->cache[panel - 1] = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
-                                                            preview_width,
-                                                            preview_height),
-                                            gegl_buffer_get_format (buffer));
-  graph  = gegl_node_new ();
-  source = gegl_node_new_child (graph,
-                                "operation", "gegl:buffer-source",
-                                "buffer", buffer,
-                                NULL);
-  scale  = gegl_node_new_child (graph,
-                                "operation", "gegl:scale-ratio",
-                                "sampler", GEGL_SAMPLER_NEAREST,
-                                "x", proxy_ratio,
-                                "y", proxy_ratio,
-                                NULL);
-
   gimp_drawable_offsets (layer,
                          &layer_offx, &layer_offy);
-  panel_offx = layer_offx * proxy_ratio;
-  panel_offy = layer_offy * proxy_ratio;
-  translate =  gegl_node_new_child (graph,
-                                    "operation", "gegl:translate",
-                                    "x", panel_offx,
-                                    "y", panel_offy,
-                                    NULL);
-
-  target = gegl_node_new_child (graph,
-                                "operation", "gegl:write-buffer",
-                                "buffer", priv->cache[panel - 1],
-                                NULL);
 
   if (panel > 1 && priv->combine[panel - 1])
     {
-      backdrop = gegl_node_new_child (graph,
-                                      "operation", "gegl:buffer-source",
-                                      "buffer", priv->cache[panel - 2],
-                                      NULL);
-      blend =  gegl_node_new_child (graph,
-                                    "operation", "gegl:over",
-                                    NULL);
-      gegl_node_link_many (source, scale, translate, NULL);
-      gegl_node_link_many (backdrop, blend, target, NULL);
-      gegl_node_connect_to (translate, "output",
-                            blend, "aux");
-    }
-  else
-    {
-      gegl_node_link_many (source, scale, translate, target, NULL);
+      backdrop_buffer = priv->cache[panel - 2];
     }
-  gegl_node_process (target);
 
-  g_object_unref (graph);
+  priv->cache[panel - 1] = normal_blend (preview_width, preview_height,
+                                         backdrop_buffer, 1.0, 0, 0,
+                                         buffer, proxy_ratio,
+                                         layer_offx, layer_offy);
   g_object_unref (buffer);
 
   /* If next panel is in "combine" mode, it must also be re-cached.
diff --git a/plug-ins/animation-play/widgets/animation-dialog.c 
b/plug-ins/animation-play/widgets/animation-dialog.c
index e57c398..744c8c7 100755
--- a/plug-ins/animation-play/widgets/animation-dialog.c
+++ b/plug-ins/animation-play/widgets/animation-dialog.c
@@ -38,15 +38,6 @@
 
 #define DITHERTYPE     GDK_RGB_DITHER_NORMAL
 
-/* Settings we cache assuming they may be the user's
- * favorite, like a framerate.
- * These will be used only for image without stored animation. */
-typedef struct
-{
-  gdouble framerate;
-}
-CachedSettings;
-
 /* for shaping */
 typedef struct
 {
@@ -73,6 +64,7 @@ struct _AnimationDialogPrivate
   GtkWidget      *progress_bar;
   GtkWidget      *settings_bar;
 
+  GtkWidget      *animation_type_combo;
   GtkWidget      *fpscombo;
   GtkWidget      *zoomcombo;
   GtkWidget      *proxycombo;
@@ -135,8 +127,6 @@ static void        connect_accelerators      (GtkUIManager     *ui_manager,
 static void        animation_dialog_set_animation (AnimationDialog *dialog,
                                                    Animation       *animation);
 /* Finalization. */
-static void        animation_dialog_save_settings (AnimationDialog *dialog);
-
 static void        animation_dialog_refresh  (AnimationDialog  *dialog);
 
 static void        update_ui_sensitivity     (AnimationDialog  *dialog);
@@ -147,6 +137,9 @@ static void        close_callback            (GtkAction        *action,
 static void        help_callback             (GtkAction        *action,
                                               AnimationDialog  *dialog);
 
+static void        animation_type_changed    (GtkWidget        *combo,
+                                              AnimationDialog  *dialog);
+
 static void        fpscombo_activated        (GtkEntry         *combo,
                                               AnimationDialog  *dialog);
 static void        fpscombo_changed          (GtkWidget        *combo,
@@ -329,24 +322,40 @@ animation_dialog_new (gint32 image_id)
   Animation      *animation;
   GimpParasite   *parasite;
   const gchar    *xml = NULL;
-  CachedSettings  settings;
-
-  /* Acceptable default settings. */
-  settings.framerate = 24.0;
-
-  /* If we saved any settings globally, use the one from the last run. */
-  gimp_get_data (PLUG_IN_PROC, &settings);
+  gboolean        animatic_selected = TRUE;
 
   /* If this animation has specific settings already, override the global ones. */
   parasite = gimp_image_get_parasite (image_id,
-                                      PLUG_IN_PROC "/animation-0");
+                                      PLUG_IN_PROC "/selected");
+  if (parasite)
+    {
+      const gchar *selected;
+
+      selected = gimp_parasite_data (parasite);
+      if (g_strcmp0 (selected, "cel-animation") == 0)
+        {
+          animatic_selected = FALSE;
+        }
+      gimp_parasite_free (parasite);
+    }
+
+  if (animatic_selected)
+    {
+      parasite = gimp_image_get_parasite (image_id,
+                                          PLUG_IN_PROC "/animatic");
+    }
+  else
+    {
+      parasite = gimp_image_get_parasite (image_id,
+                                          PLUG_IN_PROC "/cel-animation");
+    }
   if (parasite)
     {
       xml = g_strdup (gimp_parasite_data (parasite));
       gimp_parasite_free (parasite);
     }
 
-  animation = animation_new (image_id, xml);
+  animation = animation_new (image_id, animatic_selected, xml);
   g_free ((gchar *) xml);
 
   dialog = g_object_new (ANIMATION_TYPE_DIALOG,
@@ -499,6 +508,27 @@ animation_dialog_constructed (GObject *object)
   gtk_box_pack_end (GTK_BOX (priv->settings_bar), priv->fpscombo, FALSE, FALSE, 0);
   gtk_widget_show (priv->fpscombo);
 
+  /* Settings: animation type */
+  priv->animation_type_combo = gtk_combo_box_text_new ();
+
+  text = _("Animatic");
+  gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (priv->animation_type_combo),
+                                  text);
+  text = _("Cel Animation");
+  gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (priv->animation_type_combo),
+                                  text);
+  gtk_combo_box_set_active (GTK_COMBO_BOX (priv->animation_type_combo), 0);
+
+  g_signal_connect (priv->animation_type_combo, "changed",
+                    G_CALLBACK (animation_type_changed),
+                    dialog);
+
+  gimp_help_set_help_data (priv->animation_type_combo, _("Animation Type"), NULL);
+
+  gtk_box_pack_end (GTK_BOX (priv->settings_bar),
+                    priv->animation_type_combo, FALSE, FALSE, 0);
+  gtk_widget_show (priv->animation_type_combo);
+
   /*************/
   /* View box. */
   /*************/
@@ -822,8 +852,6 @@ animation_dialog_finalize (GObject *object)
   AnimationDialog        *dialog = ANIMATION_DIALOG (object);
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
 
-  animation_dialog_save_settings (ANIMATION_DIALOG (dialog));
-
   if (priv->shape_window)
     gtk_widget_destroy (GTK_WIDGET (priv->shape_window));
 
@@ -1051,6 +1079,7 @@ animation_dialog_set_animation (AnimationDialog *dialog,
                                 Animation       *animation)
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  GtkWidget              *right_pane;
   gchar                  *text;
   gdouble                 fps;
   gint                    index;
@@ -1086,6 +1115,10 @@ animation_dialog_set_animation (AnimationDialog *dialog,
     }
 
   /* Block all handlers on UI widgets. */
+  g_signal_handlers_block_by_func (priv->animation_type_combo,
+                                   G_CALLBACK (animation_type_changed),
+                                   dialog);
+
   g_signal_handlers_block_by_func (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->proxycombo))),
                                    G_CALLBACK (proxycombo_activated),
                                    dialog);
@@ -1167,6 +1200,10 @@ animation_dialog_set_animation (AnimationDialog *dialog,
                                      G_CALLBACK (zoomcombo_changed),
                                      dialog);
 
+  /* Animation type. */
+  g_signal_handlers_unblock_by_func (priv->animation_type_combo,
+                                     G_CALLBACK (animation_type_changed),
+                                     dialog);
 
   /* Progress bar. */
   g_signal_handlers_unblock_by_func (priv->progress,
@@ -1182,17 +1219,16 @@ animation_dialog_set_animation (AnimationDialog *dialog,
                                      G_CALLBACK (progress_button),
                                      dialog);
 
-  /* The layer list. */
+  right_pane = gtk_paned_get_child2 (GTK_PANED (priv->hpaned));
+  if (right_pane)
+    gtk_widget_destroy (right_pane);
+
+  /* The Storyboard view. */
   if (ANIMATION_IS_ANIMATIC (animation))
     {
       GtkWidget *scrolled_win;
-      GtkWidget *layer_list;
       GtkWidget *frame;
 
-      layer_list = gtk_paned_get_child2 (GTK_PANED (priv->hpaned));
-      if (layer_list)
-        gtk_widget_destroy (layer_list);
-
       frame = gtk_frame_new (_("Storyboard"));
       gtk_paned_pack2 (GTK_PANED (priv->hpaned), frame,
                        TRUE, TRUE);
@@ -1202,11 +1238,11 @@ animation_dialog_set_animation (AnimationDialog *dialog,
       gtk_container_add (GTK_CONTAINER (frame), scrolled_win);
       gtk_widget_show (scrolled_win);
 
-      layer_list = animation_storyboard_new (ANIMATION_ANIMATIC (animation));
+      right_pane = animation_storyboard_new (ANIMATION_ANIMATIC (animation));
       gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_win),
-                                             layer_list);
+                                             right_pane);
 
-      gtk_widget_show (layer_list);
+      gtk_widget_show (right_pane);
     }
 
   /* Animation. */
@@ -1240,50 +1276,6 @@ animation_dialog_set_animation (AnimationDialog *dialog,
 }
 
 static void
-animation_dialog_save_settings (AnimationDialog *dialog)
-{
-  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
-  GimpParasite           *old_parasite;
-  gchar                  *xml;
-  gboolean                undo_step_started = FALSE;
-  CachedSettings          cached_settings;
-
-  /* First saving in cache for any image. */
-  cached_settings.framerate = animation_get_framerate (priv->animation);
-
-  gimp_set_data (PLUG_IN_PROC, &cached_settings, sizeof (&cached_settings));
-
-  xml = animation_serialize (priv->animation);
-  /* Then as a parasite for the specific image.
-   * If there was already parasites and they were all the same as the
-   * current state, do not resave them.
-   * This prevents setting the image in a dirty state while it stayed
-   * the same. */
-  old_parasite = gimp_image_get_parasite (priv->image_id, PLUG_IN_PROC "/animation-0");
-  if (! old_parasite ||
-      (gchar *) gimp_parasite_data (old_parasite) != xml)
-    {
-      GimpParasite *parasite;
-      if (! undo_step_started)
-        {
-          gimp_image_undo_group_start (priv->image_id);
-          undo_step_started = TRUE;
-        }
-      parasite = gimp_parasite_new (PLUG_IN_PROC "/animation-0",
-                                    GIMP_PARASITE_PERSISTENT | GIMP_PARASITE_UNDOABLE,
-                                    strlen (xml) + 1, xml);
-      gimp_image_attach_parasite (priv->image_id, parasite);
-      gimp_parasite_free (parasite);
-    }
-  gimp_parasite_free (old_parasite);
-
-  if (undo_step_started)
-    {
-      gimp_image_undo_group_end (priv->image_id);
-    }
-}
-
-static void
 animation_dialog_refresh (AnimationDialog *dialog)
 {
   AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
@@ -1366,6 +1358,52 @@ help_callback (GtkAction           *action,
   gimp_standard_help_func (PLUG_IN_PROC, dialog);
 }
 
+static void
+animation_type_changed (GtkWidget       *combo,
+                        AnimationDialog *dialog)
+{
+  AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+  Animation              *animation;
+  GimpParasite           *parasite;
+  const gchar            *xml = NULL;
+  gint                    index;
+
+  index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
+
+  if (! priv->animation ||
+      ! animation_loaded (priv->animation))
+    return;
+
+  if (index == 0)
+    {
+      parasite = gimp_image_get_parasite (priv->image_id,
+                                          PLUG_IN_PROC "/animatic");
+      if (parasite)
+        {
+          xml = g_strdup (gimp_parasite_data (parasite));
+          gimp_parasite_free (parasite);
+        }
+    }
+  else
+    {
+      parasite = gimp_image_get_parasite (priv->image_id,
+                                          PLUG_IN_PROC "/cel-animation");
+    }
+  if (parasite)
+    {
+      xml = g_strdup (gimp_parasite_data (parasite));
+      gimp_parasite_free (parasite);
+    }
+  animation = animation_new (priv->image_id,
+                             (index == 0),
+                             xml);
+  g_free ((gchar *) xml);
+  animation_dialog_set_animation (ANIMATION_DIALOG (dialog),
+                                  animation);
+  animation_load (animation);
+}
+
+
 /*
  * Callback emitted when the user hits the Enter key of the fps combo.
  */
@@ -2373,11 +2411,8 @@ render_frame (AnimationDialog *dialog,
       total_alpha_preview (preview_data, drawing_width, drawing_height);
     }
 
-  if (buffer == NULL)
-    return;
-
   /* When there is no frame to show, we simply display the alpha background and return. */
-  if (animation_get_length (priv->animation) > 0)
+  if (buffer && animation_get_length (priv->animation) > 0)
     {
       /* Update the rawframe. */
       if (rawframe_size < drawing_width * drawing_height * 4)


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