[gimp/wip/animation: 65/197] plug-ins: add new animation type "cel animation".
- From: Jehan Pagès <jehanp src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp/wip/animation: 65/197] plug-ins: add new animation type "cel animation".
- Date: Sat, 7 Oct 2017 03:03:52 +0000 (UTC)
commit ad5e37714bc3ec573e2fb4cc84b9acee36adf1e1
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]