[gimp/wip/animation: 230/373] plug-ins: the legacy animation becomes "animatic" animation.
- From: Jehan Pagès <jehanp src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp/wip/animation: 230/373] plug-ins: the legacy animation becomes "animatic" animation.
- Date: Sat, 7 Oct 2017 02:14:03 +0000 (UTC)
commit 2f1e669bebb33cda6c4d814917212fab3b3b6a50
Author: Jehan <jehan girinstud io>
Date: Sat Apr 9 13:45:16 2016 +0200
plug-ins: the legacy animation becomes "animatic" animation.
We show all layers as panels of a storyboard view, where one can choose
each panel's duration, and whether it combines with the previous one.
Layer name tagging will still be processed, for backward compatibility,
but everything will be doable through a much nicer UI now.
Changes of logics:
- There aren't 2 modes now. By default, all panels just replace the
previous one. Note that the button to combine exists but does not work
yet.
- Panels can now be commented. Current version has the UI, but neither
comments nor any other settings are saved at this point.
- Time is not given in ms as for tagging. Each panel duration is in
frames, with a global framerate. Keeping irregular frame duration is
not how animation works.
plug-ins/animation-play/Makefile.am | 6 +-
plug-ins/animation-play/core/animation.c | 134 +++--
plug-ins/animation-play/core/animation.h | 30 +-
plug-ins/animation-play/core/animationanimatic.c | 709 ++++++++++++++++++++
plug-ins/animation-play/core/animationanimatic.h | 66 ++
plug-ins/animation-play/core/animationlegacy.c | 473 -------------
plug-ins/animation-play/core/animationlegacy.h | 48 --
plug-ins/animation-play/widgets/animation-dialog.c | 241 +++----
.../animation-play/widgets/animation-storyboard.c | 334 +++++++++
.../animation-play/widgets/animation-storyboard.h | 52 ++
10 files changed, 1365 insertions(+), 728 deletions(-)
---
diff --git a/plug-ins/animation-play/Makefile.am b/plug-ins/animation-play/Makefile.am
index 763be80..944432e 100644
--- a/plug-ins/animation-play/Makefile.am
+++ b/plug-ins/animation-play/Makefile.am
@@ -45,10 +45,12 @@ LDADD = \
animation_play_SOURCES = \
core/animation.h \
core/animation.c \
- core/animationlegacy.h \
- core/animationlegacy.c \
+ core/animationanimatic.h \
+ core/animationanimatic.c \
widgets/animation-dialog.h \
widgets/animation-dialog.c \
+ widgets/animation-storyboard.h \
+ widgets/animation-storyboard.c \
animation-utils.h \
animation-utils.c \
animation-play.c
diff --git a/plug-ins/animation-play/core/animation.c b/plug-ins/animation-play/core/animation.c
index 6fe1758..3faefcd 100644
--- a/plug-ins/animation-play/core/animation.c
+++ b/plug-ins/animation-play/core/animation.c
@@ -28,7 +28,7 @@
#include "animation-utils.h"
#include "animation.h"
-#include "animationlegacy.h"
+#include "animationanimatic.h"
enum
{
@@ -63,7 +63,6 @@ struct _AnimationPrivate
/* State of the currently loaded animation. */
gint position;
- gint32 length;
/* We want to allow animator to set any number as first frame,
* for frame export capability. */
guint start_pos;
@@ -74,8 +73,10 @@ struct _AnimationPrivate
/* Proxy settings generates a reload. */
gdouble proxy_ratio;
- /* Frames are associated to an unused image (used as backend for GEGL buffers). */
gboolean loaded;
+
+ gint64 playback_start_time;
+ gint64 frames_played;
};
@@ -95,7 +96,7 @@ static void animation_get_property (GObject *object,
/* Base implementation of virtual methods. */
static gint animation_real_get_start_position (Animation *animation);
-static gboolean animation_real_identical_frames (Animation *animation,
+static gboolean animation_real_same (Animation *animation,
gint prev_pos,
gint next_pos);
@@ -253,6 +254,8 @@ animation_class_init (AnimationClass *klass)
* @animation: the animation.
* @playback_start: the playback start frame.
* @playback_stop: the playback last frame.
+ * @first_frame: the first frame.
+ * @length: the full length.
*
* The ::playback-range signal is emitted when the playback range is
* updated.
@@ -265,7 +268,9 @@ animation_class_init (AnimationClass *klass)
NULL, NULL,
NULL,
G_TYPE_NONE,
- 2,
+ 4,
+ G_TYPE_INT,
+ G_TYPE_INT,
G_TYPE_INT,
G_TYPE_INT);
/**
@@ -310,7 +315,7 @@ animation_class_init (AnimationClass *klass)
object_class->get_property = animation_get_property;
klass->get_start_position = animation_real_get_start_position;
- klass->identical_frames = animation_real_identical_frames;
+ klass->same = animation_real_same;
/**
* Animation:image:
@@ -339,15 +344,13 @@ animation_init (Animation *animation)
/************ Public Functions ****************/
Animation *
-animation_new (gint32 image_id,
- DisposeType disposal)
+animation_new (gint32 image_id,
+ gboolean xsheet)
{
Animation *animation;
- /* Right now, only legacy animation type exists. */
- animation = g_object_new (ANIMATION_TYPE_ANIMATION_LEGACY,
+ animation = g_object_new (ANIMATION_TYPE_ANIMATIC,
"image", image_id,
- "disposal", disposal,
NULL);
return animation;
}
@@ -367,6 +370,7 @@ animation_load (Animation *animation,
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
GeglBuffer *buffer;
gint width, height;
+ gint length;
priv->loaded = FALSE;
g_signal_emit (animation, animation_signals[LOADING_START], 0);
@@ -378,17 +382,17 @@ animation_load (Animation *animation,
priv->start_pos = ANIMATION_GET_CLASS (animation)->get_start_position (animation);
priv->position = priv->start_pos;
- priv->length = ANIMATION_GET_CLASS (animation)->get_length (animation);
+ length = ANIMATION_GET_CLASS (animation)->get_length (animation);
/* Default playback is the full range of frames. */
priv->playback_start = priv->position;
- priv->playback_stop = priv->position + priv->length - 1;
+ priv->playback_stop = priv->position + length - 1;
priv->loaded = TRUE;
animation_get_size (animation, &width, &height);
g_signal_emit (animation, animation_signals[LOADED], 0,
- 1, priv->length,
+ 1, length,
priv->playback_start,
priv->playback_stop,
width,
@@ -403,13 +407,14 @@ animation_load (Animation *animation,
g_object_unref (buffer);
}
-DisposeType
-animation_get_disposal (Animation *animation)
+gchar *
+animation_serialize (Animation *animation)
{
- return ANIMATION_GET_CLASS (animation)->get_disposal (animation);
+ return ANIMATION_GET_CLASS (animation)->serialize (animation);
}
-gint animation_get_start_position (Animation *animation)
+gint
+animation_get_start_position (Animation *animation)
{
return ANIMATION_GET_CLASS (animation)->get_start_position (animation);
}
@@ -465,7 +470,7 @@ animation_play (Animation *animation)
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
gint duration;
- duration = ANIMATION_GET_CLASS (animation)->time_to_next (animation);
+ duration = (gint) (1000.0 / animation_get_framerate (animation));
if (priv->playback_timer)
{
@@ -476,11 +481,8 @@ animation_play (Animation *animation)
g_source_remove (priv->playback_timer);
}
- if (duration <= 0)
- {
- gdouble fps = animation_get_framerate (animation);
- duration = (gint) (1000.0 / fps);
- }
+ priv->playback_start_time = g_get_monotonic_time ();
+ priv->frames_played = 1;
priv->playback_timer = g_timeout_add ((guint) duration,
(GSourceFunc) animation_advance_frame_callback,
@@ -508,19 +510,21 @@ animation_next (Animation *animation)
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
GeglBuffer *buffer = NULL;
gint previous_pos = priv->position;
+ gboolean identical;
priv->position = animation_get_playback_start (animation) +
((priv->position - animation_get_playback_start (animation) + 1) %
(animation_get_playback_stop (animation) - animation_get_playback_start (animation) +
1));
- if (! ANIMATION_GET_CLASS (animation)->identical_frames (animation,
- previous_pos,
- priv->position))
+ identical = ANIMATION_GET_CLASS (animation)->same (animation,
+ previous_pos,
+ priv->position);
+ if (! identical)
{
buffer = animation_get_frame (animation, priv->position);
}
g_signal_emit (animation, animation_signals[RENDER], 0,
- priv->position, buffer, FALSE);
+ priv->position, buffer, ! identical);
if (buffer != NULL)
{
g_object_unref (buffer);
@@ -533,6 +537,7 @@ animation_prev (Animation *animation)
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
GeglBuffer *buffer = NULL;
gint prev_pos = priv->position;
+ gboolean identical;
if (priv->position == animation_get_playback_start (animation))
{
@@ -543,14 +548,15 @@ animation_prev (Animation *animation)
--priv->position;
}
- if (! ANIMATION_GET_CLASS (animation)->identical_frames (animation,
- prev_pos,
- priv->position))
+ identical = ANIMATION_GET_CLASS (animation)->same (animation,
+ prev_pos,
+ priv->position);
+ if (! identical)
{
buffer = animation_get_frame (animation, priv->position);
}
g_signal_emit (animation, animation_signals[RENDER], 0,
- priv->position, buffer, FALSE);
+ priv->position, buffer, ! identical);
if (buffer)
g_object_unref (buffer);
}
@@ -562,6 +568,7 @@ animation_jump (Animation *animation,
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
GeglBuffer *buffer = NULL;
gint prev_pos = priv->position;
+ gboolean identical;
if (index < priv->playback_start ||
index > priv->playback_stop)
@@ -569,14 +576,15 @@ animation_jump (Animation *animation,
else
priv->position = index;
- if (! ANIMATION_GET_CLASS (animation)->identical_frames (animation,
- prev_pos,
- priv->position))
+ identical = ANIMATION_GET_CLASS (animation)->same (animation,
+ prev_pos,
+ priv->position);
+ if (! identical)
{
buffer = animation_get_frame (animation, priv->position);
}
g_signal_emit (animation, animation_signals[RENDER], 0,
- priv->position, buffer, FALSE);
+ priv->position, buffer, ! identical);
if (buffer)
g_object_unref (buffer);
}
@@ -586,9 +594,12 @@ animation_set_playback_start (Animation *animation,
gint frame_number)
{
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+ gint length;
+
+ length = animation_get_length (animation);
if (frame_number < priv->start_pos ||
- frame_number >= priv->start_pos + priv->length)
+ frame_number >= priv->start_pos + length)
{
priv->playback_start = priv->start_pos;
}
@@ -598,11 +609,12 @@ animation_set_playback_start (Animation *animation,
}
if (priv->playback_stop < priv->playback_start)
{
- priv->playback_stop = priv->start_pos + priv->length - 1;
+ priv->playback_stop = priv->start_pos + length - 1;
}
g_signal_emit (animation, animation_signals[PLAYBACK_RANGE], 0,
- priv->playback_start, priv->playback_stop);
+ priv->playback_start, priv->playback_stop,
+ priv->start_pos, length);
if (priv->position < priv->playback_start ||
priv->position > priv->playback_stop)
@@ -624,11 +636,14 @@ animation_set_playback_stop (Animation *animation,
gint frame_number)
{
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
+ gint length;
+
+ length = animation_get_length (animation);
if (frame_number < priv->start_pos ||
- frame_number >= priv->start_pos + priv->length)
+ frame_number >= priv->start_pos + length)
{
- priv->playback_stop = priv->start_pos + priv->length - 1;
+ priv->playback_stop = priv->start_pos + length - 1;
}
else
{
@@ -639,7 +654,8 @@ animation_set_playback_stop (Animation *animation,
priv->playback_start = priv->start_pos;
}
g_signal_emit (animation, animation_signals[PLAYBACK_RANGE], 0,
- priv->playback_start, priv->playback_stop);
+ priv->playback_start, priv->playback_stop,
+ priv->start_pos, length);
if (priv->position < priv->playback_start ||
priv->position > priv->playback_stop)
@@ -738,9 +754,9 @@ gint animation_real_get_start_position (Animation *animation)
}
static gboolean
-animation_real_identical_frames (Animation *animation,
- gint previous_pos,
- gint next_pos)
+animation_real_same (Animation *animation,
+ gint previous_pos,
+ gint next_pos)
{
/* By default all frames are supposed different. */
return (previous_pos == next_pos);
@@ -750,16 +766,34 @@ static gboolean
animation_advance_frame_callback (Animation *animation)
{
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
- gint duration;
+ gint64 duration;
+ gint64 duration_since_start;
+ static gboolean prev_low_framerate = FALSE;
animation_next (animation);
- duration = ANIMATION_GET_CLASS (animation)->time_to_next (animation);
+ duration = (gint) (1000.0 / animation_get_framerate (animation));
+
+ duration_since_start = (g_get_monotonic_time () - priv->playback_start_time) / 1000;
+ duration = duration - (duration_since_start - priv->frames_played * duration);
- if (duration <= 0)
+ if (duration < 1)
+ {
+ if (prev_low_framerate)
+ {
+ /* Let's only warn the user for several subsequent slow frames. */
+ gdouble real_framerate = (gdouble) priv->frames_played * 1000.0 / duration_since_start;
+ if (real_framerate < priv->framerate)
+ g_signal_emit (animation, animation_signals[LOW_FRAMERATE], 0,
+ real_framerate);
+ }
+ duration = 1;
+ prev_low_framerate = TRUE;
+ }
+ else
{
- gdouble fps = animation_get_framerate (animation);
- duration = (gint) (1000.0 / fps);
+ prev_low_framerate = FALSE;
}
+ priv->frames_played++;
priv->playback_timer = g_timeout_add ((guint) duration,
(GSourceFunc) animation_advance_frame_callback,
diff --git a/plug-ins/animation-play/core/animation.h b/plug-ins/animation-play/core/animation.h
index 9b701f8..21d9418 100644
--- a/plug-ins/animation-play/core/animation.h
+++ b/plug-ins/animation-play/core/animation.h
@@ -31,18 +31,6 @@
typedef struct _Animation Animation;
typedef struct _AnimationClass AnimationClass;
-typedef enum
-{
- /* Each layer is combined over the previous frame. */
- DISPOSE_COMBINE = 0x00,
- /* Each layer is a frame. */
- DISPOSE_REPLACE = 0x01,
- /* Custom disposal through timeline. */
- DISPOSE_XSHEET = 0x02,
- /* Keep the current disposal */
- DISPOSE_KEEP = 0x03,
-} DisposeType;
-
struct _Animation
{
GObject parent_instance;
@@ -56,38 +44,36 @@ struct _AnimationClass
gint (*get_start_position) (Animation *animation);
/* Defaults to returning FALSE for any different position. */
- gboolean (*identical_frames) (Animation *animation,
- gint prev_pos,
- gint next_pos);
+ gboolean (*same) (Animation *animation,
+ gint prev_pos,
+ gint next_pos);
/* These virtual methods must be implemented by any subclass. */
void (*load) (Animation *animation,
gdouble proxy_ratio);
- DisposeType (*get_disposal) (Animation *animation);
gint (*get_length) (Animation *animation);
- gint (*next) (Animation *animation);
- gint (*prev) (Animation *animation);
void (*get_size) (Animation *animation,
gint *width,
gint *height);
- gint (*time_to_next) (Animation *animation);
-
GeglBuffer * (*get_frame) (Animation *animation,
gint pos);
+
+ gchar * (*serialize) (Animation *animation);
};
GType animation_get_type (void);
Animation * animation_new (gint32 image_id,
- DisposeType disposal);
+ gboolean xsheet);
gint32 animation_get_image_id (Animation *animation);
void animation_load (Animation *animation,
gdouble proxy_ratio);
-DisposeType animation_get_disposal (Animation *animation);
+
+gchar * animation_serialize (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
new file mode 100644
index 0000000..060aecf
--- /dev/null
+++ b/plug-ins/animation-play/core/animationanimatic.c
@@ -0,0 +1,709 @@
+/* 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 "animationanimatic.h"
+
+enum
+{
+ IMAGE_DURATION,
+ LAST_SIGNAL
+};
+
+typedef struct _AnimationAnimaticPrivate AnimationAnimaticPrivate;
+
+struct _AnimationAnimaticPrivate
+{
+ gint preview_width;
+ gint preview_height;
+
+ /* Panel images are associated to an unused image (used as backend
+ * for GEGL buffers). */
+ gint32 panels;
+ /* The number of panels. */
+ gint n_panels;
+ /* Layers associated to each panel. For serialization. */
+ gint *tattoos;
+ /* The duration of each panel in frames. */
+ gint *durations;
+ /* Whether a panel should get blended together with previous panel. */
+ gboolean *combine;
+ /* Panel comments. */
+ gchar **comments;
+};
+
+#define GET_PRIVATE(animation) \
+ G_TYPE_INSTANCE_GET_PRIVATE (animation, \
+ ANIMATION_TYPE_ANIMATIC, \
+ AnimationAnimaticPrivate)
+
+static void animation_animatic_finalize (GObject *object);
+
+/* Virtual methods */
+
+static gint animation_animatic_get_length (Animation *animation);
+static void animation_animatic_get_size (Animation *animation,
+ gint *width,
+ gint *height);
+
+static void animation_animatic_load (Animation *animation,
+ gdouble proxy_ratio);
+static GeglBuffer * animation_animatic_get_frame (Animation *animation,
+ gint pos);
+static gchar * animation_animatic_serialize (Animation *animation);
+static gboolean animation_animatic_same (Animation *animation,
+ gint previous_pos,
+ gint next_pos);
+
+/* Utils */
+
+static gint animation_animatic_get_layer (AnimationAnimatic *animation,
+ gint pos);
+
+/* Tag handling (from layer names) */
+
+static gint parse_ms_tag (Animation *animation,
+ const gchar *str);
+static gboolean parse_combine_tag (const gchar *str);
+
+static gboolean is_ms_tag (const gchar *str,
+ gint *duration,
+ gint *taglength);
+
+G_DEFINE_TYPE (AnimationAnimatic, animation_animatic, ANIMATION_TYPE_ANIMATION)
+
+#define parent_class animation_animatic_parent_class
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+animation_animatic_class_init (AnimationAnimaticClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ AnimationClass *anim_class = ANIMATION_CLASS (klass);
+
+ /**
+ * AnimationAnimatic::image-duration:
+ * @animatic: the #AnimationAnimatic.
+ * @layer_id: the #GimpLayer id.
+ * @duration: the new duration for @layer_id (in number of panels).
+ *
+ * The ::image-duration will be emitted when the duration of a layer
+ * changes. It can be %0 meaning that this layer should not be shown
+ * in the reel.
+ */
+ signals[IMAGE_DURATION] =
+ g_signal_new ("image-duration",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_INT,
+ G_TYPE_INT);
+
+ object_class->finalize = animation_animatic_finalize;
+
+ anim_class->get_length = animation_animatic_get_length;
+ anim_class->get_size = animation_animatic_get_size;
+ anim_class->load = animation_animatic_load;
+ 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));
+}
+
+static void
+animation_animatic_init (AnimationAnimatic *animation)
+{
+}
+
+static void
+animation_animatic_finalize (GObject *object)
+{
+ AnimationAnimaticPrivate *priv = GET_PRIVATE (object);
+
+ gimp_image_delete (priv->panels);
+ if (priv->tattoos)
+ g_free (priv->tattoos);
+ if (priv->durations)
+ g_free (priv->durations);
+ if (priv->combine)
+ g_free (priv->combine);
+ if (priv->comments)
+ {
+ gint i;
+
+ for (i = 0; i < priv->n_panels; i++)
+ {
+ g_free (priv->comments[i]);
+ }
+ g_free (priv->comments);
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+/**** Public Functions ****/
+
+void
+animation_animatic_set_duration (AnimationAnimatic *animatic,
+ gint panel_num,
+ gint duration)
+{
+ AnimationAnimaticPrivate *priv = GET_PRIVATE (animatic);
+ Animation *animation = ANIMATION (animatic);
+ gint prev_length = animation_get_length (animation);
+ gint playback_start = animation_get_playback_start (animation);
+ gint playback_stop = animation_get_playback_stop (animation);
+ gint position = animation_get_position (animation);
+ gint layer_id;
+ gint length;
+
+ g_return_if_fail (duration >= 0 &&
+ panel_num > 0 &&
+ panel_num <= priv->n_panels);
+
+ layer_id = animation_animatic_get_layer (animatic, position);
+
+ priv->durations[panel_num - 1] = duration;
+ length = animation_get_length (animation);
+
+ if (playback_start > length)
+ {
+ playback_start = animation_get_start_position (animation);
+ }
+ if (playback_stop > length ||
+ playback_stop == prev_length)
+ {
+ playback_stop = length;
+ }
+ g_signal_emit (animatic, signals[IMAGE_DURATION], 0,
+ panel_num, duration);
+ g_signal_emit_by_name (animatic, "playback-range",
+ playback_start, playback_stop,
+ animation_get_start_position (animation),
+ animation_get_length (animation));
+ if (position > length)
+ {
+ animation_jump (animation, length);
+ }
+ else if (layer_id != animation_animatic_get_layer (animatic, position))
+ {
+ GeglBuffer *buffer;
+
+ buffer = animation_get_frame (animation, position);
+ g_signal_emit_by_name (animation, "render",
+ position, buffer, TRUE);
+ if (buffer)
+ g_object_unref (buffer);
+ }
+}
+
+gint
+animation_animatic_get_duration (AnimationAnimatic *animatic,
+ gint panel_num)
+{
+ AnimationAnimaticPrivate *priv = GET_PRIVATE (animatic);
+
+ g_return_val_if_fail (panel_num > 0 &&
+ panel_num <= priv->n_panels,
+ 0);
+
+ return priv->durations[panel_num - 1];
+}
+
+void
+animation_animatic_set_comment (AnimationAnimatic *animatic,
+ gint panel_num,
+ const gchar *comment)
+{
+ AnimationAnimaticPrivate *priv = GET_PRIVATE (animatic);
+
+ g_return_if_fail (panel_num > 0 &&
+ panel_num <= priv->n_panels);
+
+ if (priv->comments[panel_num - 1])
+ g_free (priv->comments[panel_num - 1]);
+
+ priv->comments[panel_num - 1] = g_strdup (comment);
+}
+
+const gchar *
+animation_animatic_get_comment (AnimationAnimatic *animatic,
+ gint panel_num)
+{
+ AnimationAnimaticPrivate *priv = GET_PRIVATE (animatic);
+
+ g_return_val_if_fail (panel_num > 0 &&
+ panel_num <= priv->n_panels,
+ 0);
+ return priv->comments[panel_num - 1];
+}
+
+void
+animation_animatic_set_combine (AnimationAnimatic *animatic,
+ gint panel_num,
+ gboolean combine)
+{
+ AnimationAnimaticPrivate *priv = GET_PRIVATE (animatic);
+
+ g_return_if_fail (panel_num > 0 &&
+ panel_num <= priv->n_panels);
+
+ priv->combine[panel_num] = combine;
+}
+
+const gboolean
+animation_animatic_get_combine (AnimationAnimatic *animatic,
+ gint panel_num)
+{
+ AnimationAnimaticPrivate *priv = GET_PRIVATE (animatic);
+
+ g_return_val_if_fail (panel_num > 0 &&
+ panel_num <= priv->n_panels,
+ 0);
+ return priv->combine[panel_num - 1];
+}
+
+/**** Virtual methods ****/
+
+static gint
+animation_animatic_get_length (Animation *animation)
+{
+ AnimationAnimaticPrivate *priv = GET_PRIVATE (animation);
+ gint count = 0;
+ gint i ;
+
+ for (i = 0; i < priv->n_panels; i++)
+ {
+ count += priv->durations[i];
+ }
+
+ return count;
+}
+
+static void
+animation_animatic_get_size (Animation *animation,
+ gint *width,
+ gint *height)
+{
+ AnimationAnimaticPrivate *priv = GET_PRIVATE (animation);
+
+ *width = priv->preview_width;
+ *height = priv->preview_height;
+}
+
+static void
+animation_animatic_load (Animation *animation,
+ gdouble proxy_ratio)
+{
+ AnimationAnimaticPrivate *priv = GET_PRIVATE (animation);
+ gint32 *layers;
+ gint32 image_id;
+ gint32 new_frame;
+ gint32 previous_frame = 0;
+ gint image_width;
+ gint image_height;
+ gint i;
+
+ g_return_if_fail (proxy_ratio > 0.0 && proxy_ratio <= 1.0);
+
+ /* Cleaning. */
+ if (gimp_image_is_valid (priv->panels))
+ {
+ gimp_image_delete (priv->panels);
+ g_free (priv->tattoos);
+ g_free (priv->durations);
+ g_free (priv->combine);
+
+ for (i = 0; i < priv->n_panels; i++)
+ {
+ g_free (priv->comments[i]);
+ }
+ g_free (priv->comments);
+ }
+
+ image_id = animation_get_image_id (animation);
+ layers = gimp_image_get_layers (image_id, &priv->n_panels);
+
+ priv->tattoos = g_try_malloc0_n (priv->n_panels, sizeof (gint));
+ priv->durations = g_try_malloc0_n (priv->n_panels, sizeof (gint));
+ priv->combine = g_try_malloc0_n (priv->n_panels, sizeof (gboolean));
+ priv->comments = g_try_malloc0_n (priv->n_panels, sizeof (gchar*));
+ if (! priv->tattoos || ! priv->durations ||
+ ! priv->combine || ! priv->comments)
+ {
+ gimp_message (_("Memory could not be allocated to the animatic."));
+ gimp_quit ();
+ return;
+ }
+
+ /* We default at full preview size. */
+ image_width = gimp_image_width (image_id);
+ image_height = gimp_image_height (image_id);
+
+ priv->preview_width = image_width;
+ priv->preview_height = image_height;
+
+ /* Apply proxy ratio. */
+ priv->preview_width *= proxy_ratio;
+ priv->preview_height *= proxy_ratio;
+
+ priv->panels = gimp_image_new (priv->preview_width,
+ priv->preview_width,
+ GIMP_RGB);
+
+ gimp_image_undo_disable (priv->panels);
+
+ for (i = 0; i < priv->n_panels; i++)
+ {
+ gchar *layer_name;
+ gint duration;
+ gboolean combine;
+ gint layer_offx;
+ gint layer_offy;
+
+ g_signal_emit_by_name (animation, "loading",
+ (gdouble) i / ((gdouble) priv->n_panels - 0.999));
+
+ layer_name = gimp_item_get_name (layers[priv->n_panels - (i + 1)]);
+
+ duration = parse_ms_tag (animation, layer_name);
+ combine = parse_combine_tag (layer_name);
+
+ /* Frame duration. */
+ priv->tattoos[i] = gimp_item_get_tattoo (layers[priv->n_panels - (i + 1)]);
+ priv->durations[i] = duration;
+ priv->combine[i] = combine;
+ /* Layer names are used as default comments. */
+ priv->comments[i] = layer_name;
+
+ /* Frame disposal. */
+ if (i > 0 && combine)
+ {
+ previous_frame = gimp_layer_copy (previous_frame);
+ gimp_image_insert_layer (priv->panels, previous_frame, 0, 0);
+ gimp_item_set_visible (previous_frame, TRUE);
+ }
+
+ new_frame = gimp_layer_new_from_drawable (layers[priv->n_panels - (i + 1)],
+ priv->panels);
+ gimp_image_insert_layer (priv->panels, new_frame, 0, 0);
+ gimp_layer_scale (new_frame,
+ (gimp_drawable_width (layers[priv->n_panels - (i + 1)]) * (gint)
priv->preview_width) / image_width,
+ (gimp_drawable_height (layers[priv->n_panels - (i + 1)]) * (gint)
priv->preview_height) / image_height,
+ FALSE);
+ gimp_drawable_offsets (layers[priv->n_panels - (i + 1)], &layer_offx, &layer_offy);
+ gimp_layer_set_offsets (new_frame,
+ (layer_offx * (gint) priv->preview_width) / image_width,
+ (layer_offy * (gint) priv->preview_height) / image_height);
+ gimp_layer_resize_to_image_size (new_frame);
+ gimp_item_set_visible (new_frame, TRUE);
+ new_frame = gimp_image_merge_visible_layers (priv->panels, GIMP_CLIP_TO_IMAGE);
+ gimp_item_set_visible (new_frame, FALSE);
+
+ previous_frame = new_frame;
+ }
+ g_free (layers);
+}
+
+static GeglBuffer *
+animation_animatic_get_frame (Animation *animation,
+ gint pos)
+{
+ AnimationAnimaticPrivate *priv = GET_PRIVATE (animation);
+ GeglBuffer *buffer = NULL;
+
+ if (priv->panels)
+ {
+ gint32 *layers;
+ gint32 num_layers;
+ gint count = 0;
+
+ layers = gimp_image_get_layers (priv->panels, &num_layers);
+
+ if (num_layers > 0 &&
+ pos >= 1 &&
+ pos <= animation_animatic_get_length (animation))
+ {
+ gint i ;
+
+ for (i = 0; i < num_layers; i++)
+ {
+ count += priv->durations[i];
+ if (count >= pos)
+ break;
+ }
+
+ buffer = gimp_drawable_get_buffer (layers[num_layers - i - 1]);
+ }
+
+ g_free (layers);
+ }
+
+ return buffer;
+}
+
+static gchar *
+animation_animatic_serialize (Animation *animation)
+{
+ AnimationAnimaticPrivate *priv = GET_PRIVATE (animation);
+ gchar *text;
+ gchar *tmp;
+ gint i;
+
+ text = g_strdup_printf ("<animation framerate=\"%f\" type=\"animatic\"><sequence>",
+ animation_get_framerate (animation));
+ for (i = 0; i < priv->n_panels; i++)
+ {
+ gchar *panel;
+ gchar *layer_name;
+ gint32 *panels;
+ gint n_panels;
+
+ panels = gimp_image_get_layers (priv->panels, &n_panels);
+ layer_name = gimp_item_get_name (panels[n_panels - (i + 1)]);
+
+ panel = g_markup_printf_escaped ("<panel title=\"%s\" duration=\"%d\" layer=\"%d\">",
+ layer_name, priv->durations[i],
+ priv->tattoos[i]);
+ g_free (layer_name);
+
+ tmp = text;
+ text = g_strconcat (text, panel, NULL);
+ g_free (tmp);
+ g_free (panel);
+
+ if (priv->comments[i])
+ {
+ gchar *comment;
+
+ comment = g_markup_printf_escaped ("<notes title=\"Notes\">%s</notes>",
+ priv->comments[i]);
+ tmp = text;
+ text = g_strconcat (text, comment, NULL);
+ g_free (tmp);
+ g_free (comment);
+ }
+ tmp = text;
+ text = g_strconcat (text, "</panel>", NULL);
+ g_free (tmp);
+ }
+ tmp = text;
+ text = g_strconcat (text, "</sequence></animation>", NULL);
+ g_free (tmp);
+
+ return text;
+}
+
+static gboolean
+animation_animatic_same (Animation *animation,
+ gint previous_pos,
+ gint next_pos)
+{
+ AnimationAnimaticPrivate *priv = GET_PRIVATE (animation);
+ gint count = 0;
+ gboolean identical = FALSE;
+ gint i ;
+
+ for (i = 0; i < priv->n_panels; i++)
+ {
+ count += priv->durations[i];
+ if (count >= previous_pos && count >= next_pos)
+ {
+ identical = TRUE;
+ break;
+ }
+ else if (count >= previous_pos || count >= next_pos)
+ {
+ identical = FALSE;
+ break;
+ }
+ }
+
+ return identical;
+}
+
+/**** Utils ****/
+
+static gint
+animation_animatic_get_layer (AnimationAnimatic *animation,
+ gint pos)
+{
+ AnimationAnimaticPrivate *priv = GET_PRIVATE (animation);
+ gint i = -1;
+
+ if (priv->panels)
+ {
+ gint32 *layers;
+ gint32 num_layers;
+ gint count = 0;
+
+ layers = gimp_image_get_layers (priv->panels, &num_layers);
+
+ if (num_layers > 0 &&
+ pos >= 1 &&
+ pos <= animation_animatic_get_length (ANIMATION (animation)))
+ {
+ for (i = num_layers - 1; i >= 0; i--)
+ {
+ count += priv->durations[i];
+ if (count >= pos)
+ break;
+ }
+ }
+
+ g_free (layers);
+ }
+
+ return i;
+}
+
+/**** TAG UTILS ****/
+
+static gint
+parse_ms_tag (Animation *animation,
+ const gchar *str)
+{
+ if (str != NULL)
+ {
+ gint length = strlen (str);
+ gint i;
+
+ for (i = 0; i < length; i++)
+ {
+ gint duration;
+ gint dummy;
+
+ if (is_ms_tag (&str[i], &duration, &dummy))
+ {
+ gdouble fps = animation_get_framerate (animation);
+
+ /* Get frame duration in frame numbers, not millisecond. */
+ duration = (gint) ((fps * (gdouble) duration) / 1000.0);
+
+ return duration;
+ }
+ }
+ }
+
+ /* Default to 6 frames per panel.
+ * Storyboard-type animations are rarely detailed. */
+ return 6;
+}
+
+static gboolean
+parse_combine_tag (const gchar *str)
+{
+ gboolean combine = FALSE;
+
+ if (str != NULL)
+ {
+ gint length = strlen (str);
+ gint i;
+
+ for (i = 0; i < length; i++)
+ {
+ if (strlen (str) != 9)
+ continue;
+
+ if (strncmp (str, "(combine)", 9) == 0)
+ {
+ combine = TRUE;
+ break;
+ }
+ else if (strncmp (str, "(replace)", 9) == 0)
+ {
+ combine = FALSE;
+ break;
+ }
+ }
+ }
+
+ return combine;
+}
+
+static gboolean
+is_ms_tag (const gchar *str,
+ gint *duration,
+ gint *taglength)
+{
+ gint sum = 0;
+ gint offset;
+ gint length;
+
+ length = strlen(str);
+
+ if (str[0] != '(')
+ return FALSE;
+
+ offset = 1;
+
+ /* eat any spaces between open-parenthesis and number */
+ while ((offset < length) && (str[offset] == ' '))
+ offset++;
+
+ if ((offset>=length) || (!g_ascii_isdigit (str[offset])))
+ return FALSE;
+
+ do
+ {
+ sum *= 10;
+ sum += str[offset] - '0';
+ offset++;
+ }
+ while ((offset<length) && (g_ascii_isdigit (str[offset])));
+
+ if (length - offset <= 2)
+ return FALSE;
+
+ /* eat any spaces between number and 'ms' */
+ while ((offset < length) && (str[offset] == ' '))
+ offset++;
+
+ if (length - offset <= 2 ||
+ g_ascii_toupper (str[offset]) != 'M' ||
+ g_ascii_toupper (str[offset + 1]) != 'S')
+ return FALSE;
+
+ offset += 2;
+
+ /* eat any spaces between 'ms' and close-parenthesis */
+ while ((offset < length) && (str[offset] == ' '))
+ offset++;
+
+ if ((length - offset < 1) || (str[offset] != ')'))
+ return FALSE;
+
+ offset++;
+
+ *duration = sum;
+ *taglength = offset;
+
+ return TRUE;
+}
diff --git a/plug-ins/animation-play/core/animationanimatic.h
b/plug-ins/animation-play/core/animationanimatic.h
new file mode 100644
index 0000000..f2e084c
--- /dev/null
+++ b/plug-ins/animation-play/core/animationanimatic.h
@@ -0,0 +1,66 @@
+/* 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_ANIMATIC_H__
+#define __ANIMATION_ANIMATIC_H__
+
+#include "animation.h"
+
+#define ANIMATION_TYPE_ANIMATIC (animation_animatic_get_type ())
+#define ANIMATION_ANIMATIC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_ANIMATIC,
AnimationAnimatic))
+#define ANIMATION_ANIMATIC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_ANIMATIC,
AnimationAnimaticClass))
+#define ANIMATION_IS_ANIMATIC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_ANIMATIC))
+#define ANIMATION_IS_ANIMATIC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_ANIMATIC))
+#define ANIMATION_ANIMATIC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_ANIMATIC,
AnimationAnimaticClass))
+
+typedef struct _AnimationAnimatic AnimationAnimatic;
+typedef struct _AnimationAnimaticClass AnimationAnimaticClass;
+
+struct _AnimationAnimatic
+{
+ Animation parent_instance;
+};
+
+struct _AnimationAnimaticClass
+{
+ AnimationClass parent_class;
+};
+
+GType animation_animatic_get_type (void);
+
+void animation_animatic_set_duration (AnimationAnimatic *animatic,
+ gint panel_num,
+ gint duration);
+gint animation_animatic_get_duration (AnimationAnimatic *animatic,
+ gint panel_num);
+
+void animation_animatic_set_comment (AnimationAnimatic *animatic,
+ gint panel_num,
+ const gchar *comment);
+const gchar * animation_animatic_get_comment (AnimationAnimatic *animatic,
+ gint panel_num);
+
+void animation_animatic_set_combine (AnimationAnimatic *animatic,
+ gint panel_num,
+ gboolean combine);
+const gboolean animation_animatic_get_combine (AnimationAnimatic *animatic,
+ gint panel_num);
+
+#endif /* __ANIMATION_ANIMATIC_H__ */
diff --git a/plug-ins/animation-play/widgets/animation-dialog.c
b/plug-ins/animation-play/widgets/animation-dialog.c
index ea1ba62..416ee73 100755
--- a/plug-ins/animation-play/widgets/animation-dialog.c
+++ b/plug-ins/animation-play/widgets/animation-dialog.c
@@ -31,8 +31,12 @@
#include <libgimp/gimpui.h>
#include "animation-utils.h"
+
#include "core/animation.h"
+#include "core/animationanimatic.h"
+
#include "animation-dialog.h"
+#include "animation-storyboard.h"
#include "libgimp/stdplugins-intl.h"
@@ -40,11 +44,10 @@
#define DITHERTYPE GDK_RGB_DITHER_NORMAL
/* Settings we cache assuming they may be the user's
- * favorite, like a framerate, or a type of frame disposal.
+ * favorite, like a framerate.
* These will be used only for image without stored animation. */
typedef struct
{
- DisposeType disposal;
gdouble framerate;
}
CachedSettings;
@@ -75,7 +78,6 @@ struct _AnimationDialogPrivate
GtkWidget *progress_bar;
GtkWidget *settings_bar;
- GtkWidget *disposalcombo;
GtkWidget *fpscombo;
GtkWidget *zoomcombo;
GtkWidget *proxycheckbox;
@@ -99,6 +101,9 @@ struct _AnimationDialogPrivate
guint shape_drawing_area_width;
guint shape_drawing_area_height;
+ /* The hpaned (left is preview, right is layer list. */
+ GtkWidget *hpaned;
+
/* The vpaned (bottom is timeline, above is preview). */
GtkWidget *vpaned;
@@ -148,8 +153,6 @@ static void close_callback (GtkAction *action,
static void help_callback (GtkAction *action,
AnimationDialog *dialog);
-static void disposalcombo_changed (GtkWidget *combo,
- AnimationDialog *dialog);
static void fpscombo_activated (GtkEntry *combo,
AnimationDialog *dialog);
static void fpscombo_changed (GtkWidget *combo,
@@ -206,6 +209,12 @@ static gboolean popup_menu (GtkWidget *widget,
static void show_loading_progress (Animation *animation,
gdouble load_rate,
AnimationDialog *dialog);
+static void animation_update_progress (Animation *animation,
+ gint first_frame,
+ gint num_frames,
+ gint playback_start,
+ gint playback_stop,
+ AnimationDialog *dialog);
static void block_ui (Animation *animation,
AnimationDialog *dialog);
static void unblock_ui (Animation *animation,
@@ -219,6 +228,8 @@ static void unblock_ui (Animation *animation,
static void playback_range_changed (Animation *animation,
gint playback_start,
gint playback_stop,
+ gint start_pos,
+ gint length,
AnimationDialog *dialog);
static void proxy_changed (Animation *animation,
gdouble fps,
@@ -324,7 +335,7 @@ animation_dialog_init (AnimationDialog *dialog)
priv->shape_drawing_area_height = -1;
}
-/************ Public Functions ****************/
+/**** Public Functions ****/
GtkWidget *
animation_dialog_new (gint32 image_id)
@@ -335,21 +346,12 @@ animation_dialog_new (gint32 image_id)
CachedSettings settings;
/* Acceptable default settings. */
- settings.disposal = DISPOSE_COMBINE;
settings.framerate = 24.0;
/* If we saved any settings globally, use the one from the last run. */
gimp_get_data (PLUG_IN_PROC, &settings);
/* If this animation has specific settings already, override the global ones. */
- parasite = gimp_image_get_parasite (image_id, PLUG_IN_PROC "/frame-disposal");
- if (parasite)
- {
- const DisposeType *mode = gimp_parasite_data (parasite);
-
- settings.disposal = *mode;
- gimp_parasite_free (parasite);
- }
parasite = gimp_image_get_parasite (image_id,
PLUG_IN_PROC "/framerate");
if (parasite)
@@ -360,7 +362,7 @@ animation_dialog_new (gint32 image_id)
gimp_parasite_free (parasite);
}
- animation = animation_new (image_id, settings.disposal);
+ animation = animation_new (image_id, FALSE);
dialog = g_object_new (ANIMATION_TYPE_DIALOG,
"type", GTK_WINDOW_TOPLEVEL,
@@ -375,14 +377,13 @@ animation_dialog_new (gint32 image_id)
return dialog;
}
-/************ Private Functions ****************/
+/**** Private Functions ****/
static void
animation_dialog_constructed (GObject *object)
{
AnimationDialog *dialog = ANIMATION_DIALOG (object);
AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
- GtkWidget *hpaned;
GtkAdjustment *adjust;
GtkWidget *main_vbox;
GtkWidget *upper_bar;
@@ -422,13 +423,13 @@ animation_dialog_constructed (GObject *object)
gtk_container_add (GTK_CONTAINER (dialog), priv->vpaned);
gtk_widget_show (priv->vpaned);
- hpaned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
- gtk_paned_pack1 (GTK_PANED (priv->vpaned), hpaned, TRUE, TRUE);
- gtk_widget_show (hpaned);
+ priv->hpaned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_paned_pack1 (GTK_PANED (priv->vpaned), priv->hpaned, TRUE, TRUE);
+ gtk_widget_show (priv->hpaned);
/* Playback vertical box. */
main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
- gtk_paned_pack1 (GTK_PANED (hpaned), main_vbox, TRUE, TRUE);
+ gtk_paned_pack1 (GTK_PANED (priv->hpaned), main_vbox, TRUE, TRUE);
gtk_widget_show (main_vbox);
/* Upper Bar */
@@ -444,6 +445,7 @@ animation_dialog_constructed (GObject *object)
/*****************/
/* Settings box. */
/*****************/
+
priv->settings_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_paned_add1 (GTK_PANED (hbox), priv->settings_bar);
gtk_widget_show (priv->settings_bar);
@@ -519,27 +521,6 @@ 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: frame mode. */
- priv->disposalcombo = gtk_combo_box_text_new ();
-
- text = g_strdup (_("Cumulative layers (combine)"));
- gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (priv->disposalcombo),
- DISPOSE_COMBINE, text);
- g_free (text);
-
- text = g_strdup (_("One frame per layer (replace)"));
- gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (priv->disposalcombo),
- DISPOSE_REPLACE, text);
- g_free (text);
-
- g_signal_connect (priv->disposalcombo, "changed",
- G_CALLBACK (disposalcombo_changed),
- dialog);
-
- gtk_box_pack_end (GTK_BOX (priv->settings_bar),
- priv->disposalcombo, FALSE, FALSE, 0);
- gtk_widget_show (priv->disposalcombo);
-
/*************/
/* View box. */
/*************/
@@ -762,7 +743,6 @@ animation_dialog_constructed (GObject *object)
preview_width = gimp_image_width (priv->image_id);
preview_height = gimp_image_height (priv->image_id);
- /*animation_get_size (priv->animation, &preview_width, &preview_height);*/
gtk_window_set_default_size (GTK_WINDOW (dialog),
MAX (preview_width + 20,
screen_width - 20),
@@ -1145,10 +1125,6 @@ animation_dialog_set_animation (AnimationDialog *dialog,
G_CALLBACK (fpscombo_activated),
dialog);
- g_signal_handlers_block_by_func (priv->disposalcombo,
- G_CALLBACK (disposalcombo_changed),
- dialog);
-
g_signal_handlers_block_by_func (gtk_bin_get_child (GTK_BIN (priv->zoomcombo)),
G_CALLBACK (zoomcombo_activated),
dialog);
@@ -1211,14 +1187,6 @@ animation_dialog_set_animation (AnimationDialog *dialog,
G_CALLBACK (fpscombo_activated),
dialog);
- /* Settings: frame mode. */
- gtk_combo_box_set_active (GTK_COMBO_BOX (priv->disposalcombo),
- animation_get_disposal (priv->animation));
-
- g_signal_handlers_unblock_by_func (priv->disposalcombo,
- G_CALLBACK (disposalcombo_changed),
- dialog);
-
/* View: zoom. */
g_signal_handlers_unblock_by_func (gtk_bin_get_child (GTK_BIN (priv->zoomcombo)),
G_CALLBACK (zoomcombo_activated),
@@ -1242,6 +1210,33 @@ animation_dialog_set_animation (AnimationDialog *dialog,
G_CALLBACK (progress_button),
dialog);
+ /* The layer list. */
+ 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);
+ gtk_widget_show (frame);
+
+ scrolled_win = gtk_scrolled_window_new (NULL, NULL);
+ gtk_container_add (GTK_CONTAINER (frame), scrolled_win);
+ gtk_widget_show (scrolled_win);
+
+ layer_list = animation_storyboard_new (ANIMATION_ANIMATIC (animation));
+ gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_win),
+ layer_list);
+
+ gtk_widget_show (layer_list);
+ }
+
/* Animation. */
g_signal_connect (priv->animation, "proxy",
G_CALLBACK (proxy_changed),
@@ -1281,7 +1276,6 @@ animation_dialog_save_settings (AnimationDialog *dialog)
CachedSettings cached_settings;
/* First saving in cache for any image. */
- cached_settings.disposal = animation_get_disposal (priv->animation);
cached_settings.framerate = animation_get_framerate (priv->animation);
gimp_set_data (PLUG_IN_PROC, &cached_settings, sizeof (&cached_settings));
@@ -1291,25 +1285,6 @@ animation_dialog_save_settings (AnimationDialog *dialog)
* 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 "/frame-disposal");
- if (! old_parasite ||
- *(DisposeType *) gimp_parasite_data (old_parasite) != cached_settings.disposal)
- {
- GimpParasite *parasite;
-
- gimp_image_undo_group_start (priv->image_id);
- undo_step_started = TRUE;
-
- parasite = gimp_parasite_new (PLUG_IN_PROC "/frame-disposal",
- GIMP_PARASITE_PERSISTENT | GIMP_PARASITE_UNDOABLE,
- sizeof (cached_settings.disposal),
- &cached_settings.disposal);
- gimp_image_attach_parasite (priv->image_id, parasite);
- gimp_parasite_free (parasite);
- }
- gimp_parasite_free (old_parasite);
-
old_parasite = gimp_image_get_parasite (priv->image_id, PLUG_IN_PROC "/framerate");
if (! old_parasite ||
*(gdouble *) gimp_parasite_data (old_parasite) != cached_settings.framerate)
@@ -1419,38 +1394,6 @@ help_callback (GtkAction *action,
}
/*
- * Callback emitted when the user changes the disposal in UI.
- */
-static void
-disposalcombo_changed (GtkWidget *combo,
- AnimationDialog *dialog)
-{
- AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
- Animation *animation;
- DisposeType disposal;
-
- disposal = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
-
- if (! priv->animation ||
- animation_get_disposal (priv->animation) != disposal)
- {
- gdouble proxy;
-
- if (priv->animation &&
- animation_is_playing (priv->animation))
- animation_stop (priv->animation);
-
- /* Keep same proxy ratio as previously. */
- proxy = animation_get_proxy (priv->animation);
-
- animation = animation_new (priv->image_id, disposal);
- animation_dialog_set_animation (dialog,
- animation);
- animation_load (animation, proxy);
- }
-}
-
-/*
* Callback emitted when the user hits the Enter key of the fps combo.
*/
static void
@@ -1971,6 +1914,36 @@ show_loading_progress (Animation *animation,
}
static void
+animation_update_progress (Animation *animation,
+ gint first_frame,
+ gint num_frames,
+ gint playback_start,
+ gint playback_stop,
+ AnimationDialog *dialog)
+{
+ AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
+ gint frame_spin_size;
+ gint last_frame = num_frames + first_frame - 1;
+
+ frame_spin_size = (gint) (log10 (last_frame - (last_frame % 10))) + 1;
+ gtk_entry_set_width_chars (GTK_ENTRY (priv->startframe_spin), frame_spin_size);
+ gtk_entry_set_width_chars (GTK_ENTRY (priv->endframe_spin), frame_spin_size);
+
+ gtk_adjustment_configure (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->startframe_spin)),
+ playback_start,
+ first_frame,
+ last_frame,
+ 1.0, 5.0, 0.0);
+ gtk_adjustment_configure (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->endframe_spin)),
+ playback_stop,
+ playback_start,
+ last_frame,
+ 1.0, 5.0, 0.0);
+
+ update_ui_sensitivity (dialog);
+}
+
+static void
block_ui (Animation *animation,
AnimationDialog *dialog)
{
@@ -1998,27 +1971,12 @@ unblock_ui (Animation *animation,
gint preview_height,
AnimationDialog *dialog)
{
- AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
- gint frame_spin_size;
- gint last_frame = num_frames + first_frame - 1;
-
- frame_spin_size = (gint) (log10 (last_frame - (last_frame % 10))) + 1;
- gtk_entry_set_width_chars (GTK_ENTRY (priv->startframe_spin), frame_spin_size);
- gtk_entry_set_width_chars (GTK_ENTRY (priv->endframe_spin), frame_spin_size);
-
- gtk_adjustment_configure (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->startframe_spin)),
- playback_start,
- first_frame,
- last_frame,
- 1.0, 5.0, 0.0);
- gtk_adjustment_configure (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->endframe_spin)),
- playback_stop,
- playback_start,
- last_frame,
- 1.0, 5.0, 0.0);
-
- update_ui_sensitivity (dialog);
-
+ animation_update_progress (animation,
+ first_frame,
+ num_frames,
+ playback_start,
+ playback_stop,
+ dialog);
animation_dialog_refresh (dialog);
}
@@ -2026,12 +1984,21 @@ static void
playback_range_changed (Animation *animation,
gint playback_start,
gint playback_stop,
+ gint start_pos,
+ gint length,
AnimationDialog *dialog)
{
AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
GtkAdjustment *startframe_adjust;
GtkAdjustment *stopframe_adjust;
+ animation_update_progress (animation,
+ start_pos,
+ length,
+ playback_start,
+ playback_stop,
+ dialog);
+
startframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->startframe_spin));
stopframe_adjust = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (priv->endframe_spin));
@@ -2049,6 +2016,8 @@ playback_range_changed (Animation *animation,
g_signal_handlers_unblock_by_func (stopframe_adjust,
G_CALLBACK (endframe_changed),
dialog);
+
+ show_playing_progress (dialog);
}
static void
@@ -2261,7 +2230,7 @@ da_size_callback (GtkWidget *drawing_area,
allocation->height);
repaint_da (priv->drawing_area, NULL, dialog);
}
- else
+ else
{
/* Update the zoom information. */
AnimationDialogPrivate *priv = GET_PRIVATE (dialog);
@@ -2590,7 +2559,9 @@ progress_button (GtkWidget *widget,
gtk_widget_get_allocation (widget, &allocation);
goto_frame = animation_get_start_position (priv->animation) +
- (gint) (event->x / (allocation.width / animation_get_length (priv->animation)));
+ (gint) (event->x /
+ ((gdouble) allocation.width /
+ (gdouble) animation_get_length (priv->animation)));
animation_jump (priv->animation, goto_frame);
}
@@ -2610,7 +2581,9 @@ progress_entered (GtkWidget *widget,
gtk_widget_get_allocation (widget, &allocation);
goto_frame = animation_get_start_position (priv->animation) +
- (gint) (event->x / (allocation.width / animation_get_length (priv->animation)));
+ (gint) (event->x /
+ ((gdouble) allocation.width /
+ (gdouble) animation_get_length (priv->animation)));
show_goto_progress (dialog, goto_frame);
@@ -2629,7 +2602,9 @@ progress_motion (GtkWidget *widget,
gtk_widget_get_allocation (widget, &allocation);
goto_frame = animation_get_start_position (priv->animation) +
- (gint) (event->x / (allocation.width / animation_get_length (priv->animation)));
+ (gint) (event->x /
+ ((gdouble) allocation.width /
+ (gdouble) animation_get_length (priv->animation)));
show_goto_progress (dialog, goto_frame);
diff --git a/plug-ins/animation-play/widgets/animation-storyboard.c
b/plug-ins/animation-play/widgets/animation-storyboard.c
new file mode 100644
index 0000000..ee5c528
--- /dev/null
+++ b/plug-ins/animation-play/widgets/animation-storyboard.c
@@ -0,0 +1,334 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * animation-layer_view.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/gimpui.h>
+
+#include "libgimp/stdplugins-intl.h"
+
+#include "core/animationanimatic.h"
+
+#include "animation-storyboard.h"
+
+/* Properties. */
+enum
+{
+ PROP_0,
+ PROP_ANIMATION
+};
+
+struct _AnimationStoryboardPrivate
+{
+ AnimationAnimatic *animation;
+};
+
+/* GObject handlers */
+static void animation_storyboard_constructed (GObject *object);
+static void animation_storyboard_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void animation_storyboard_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void animation_storyboard_finalize (GObject *object);
+
+/* Callbacks on animation */
+static void animation_storyboard_load (Animation *animation,
+ G_GNUC_UNUSED gint first_frame,
+ G_GNUC_UNUSED gint num_frames,
+ G_GNUC_UNUSED gint playback_start,
+ G_GNUC_UNUSED gint playback_stop,
+ G_GNUC_UNUSED gint preview_width,
+ G_GNUC_UNUSED gint preview_height,
+ AnimationStoryboard *view);
+
+static void animation_storyboard_duration_spin_changed (GtkSpinButton *spinbutton,
+ AnimationAnimatic *animation);
+
+G_DEFINE_TYPE (AnimationStoryboard, animation_storyboard, GTK_TYPE_TABLE)
+
+#define parent_class animation_storyboard_parent_class
+
+static void
+animation_storyboard_class_init (AnimationStoryboardClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = animation_storyboard_constructed;
+ object_class->finalize = animation_storyboard_finalize;
+ object_class->get_property = animation_storyboard_get_property;
+ object_class->set_property = animation_storyboard_set_property;
+
+ /**
+ * AnimationStoryboard:animation:
+ *
+ * The associated #AnimationAnimatic.
+ */
+ g_object_class_install_property (object_class, PROP_ANIMATION,
+ g_param_spec_object ("animation",
+ NULL, NULL,
+ ANIMATION_TYPE_ANIMATIC,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_type_class_add_private (klass, sizeof (AnimationStoryboardPrivate));
+}
+
+static void
+animation_storyboard_init (AnimationStoryboard *view)
+{
+ view->priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
+ ANIMATION_TYPE_STORYBOARD,
+ AnimationStoryboardPrivate);
+
+ gtk_table_set_homogeneous (GTK_TABLE (view), TRUE);
+}
+
+/**** Public Functions ****/
+
+/**
+ * animation_storyboard_new:
+ * @animation: the #AnimationAnimatic for this storyboard.
+ *
+ * Creates a new layer view tied to @animation, ready to be displayed.
+ */
+GtkWidget *
+animation_storyboard_new (AnimationAnimatic *animation)
+{
+ GtkWidget *layer_view;
+
+ layer_view = g_object_new (ANIMATION_TYPE_STORYBOARD,
+ "animation", animation,
+ NULL);
+ return layer_view;
+}
+
+/**** Private Functions ****/
+
+static void
+animation_storyboard_constructed (GObject *object)
+{
+ AnimationStoryboard *view = ANIMATION_STORYBOARD (object);
+
+ g_signal_connect (view->priv->animation, "loaded",
+ (GCallback) animation_storyboard_load,
+ view);
+}
+
+static void
+animation_storyboard_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AnimationStoryboard *view = ANIMATION_STORYBOARD (object);
+
+ switch (property_id)
+ {
+ case PROP_ANIMATION:
+ view->priv->animation = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+animation_storyboard_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ AnimationStoryboard *view = ANIMATION_STORYBOARD (object);
+
+ switch (property_id)
+ {
+ case PROP_ANIMATION:
+ g_value_set_object (value, view->priv->animation);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+animation_storyboard_finalize (GObject *object)
+{
+ AnimationStoryboard *view = ANIMATION_STORYBOARD (object);
+
+ g_object_unref (view->priv->animation);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+/* animation_storyboard_load:
+ * @view: the #AnimationStoryboard.
+ *
+ * Recursively fills @store with the #GimpLayers data of the #GimpImage
+ * tied to @view.
+ */
+static void
+animation_storyboard_load (Animation *animation,
+ gint first_frame,
+ gint num_frames,
+ gint playback_start,
+ gint playback_stop,
+ gint preview_width,
+ gint preview_height,
+ AnimationStoryboard *view)
+{
+ gint *layers;
+ gint32 image_id;
+ gint n_images;
+ gint i;
+
+ image_id = animation_get_image_id (animation);
+
+ /* Cleaning previous loads. */
+ gtk_container_foreach (GTK_CONTAINER (view),
+ (GtkCallback) gtk_widget_destroy,
+ NULL);
+
+ /* Setting new values. */
+ layers = gimp_image_get_layers (image_id,
+ &n_images);
+
+ gtk_table_resize (GTK_TABLE (view),
+ 5 * n_images,
+ 10);
+ for (i = 0; i < n_images; i++)
+ {
+ GdkPixbuf *thumbnail;
+ GtkWidget *image;
+ GtkWidget *comment;
+ GtkWidget *duration;
+ GtkWidget *disposal;
+ gchar *image_name;
+ gint panel_num = n_images - i;
+
+ thumbnail = gimp_drawable_get_thumbnail (layers[i], 250, 250,
+ GIMP_PIXBUF_SMALL_CHECKS);
+ image = gtk_image_new_from_pixbuf (thumbnail);
+ g_object_unref (thumbnail);
+
+ /* Let's align top-right, in case the storyboard gets resized
+ * and the image grows (the thumbnail right now stays as fixed size). */
+ gtk_misc_set_alignment (GTK_MISC (image), 1.0, 0.0);
+
+ gtk_table_attach (GTK_TABLE (view),
+ image, 0, 4,
+ 5 * n_images - 5 * i - 5,
+ 5 * n_images - 5 * i,
+ GTK_EXPAND | GTK_FILL,
+ GTK_EXPAND | GTK_FILL,
+ 1, 1);
+ gtk_widget_show (image);
+
+ comment = gtk_text_view_new ();
+ gtk_table_attach (GTK_TABLE (view),
+ comment, 5, 10,
+ 5 * n_images - 5 * i - 5,
+ 5 * n_images - 5 * i,
+ GTK_EXPAND | GTK_FILL,
+ GTK_EXPAND | GTK_FILL,
+ 0, 1);
+ gtk_widget_show (comment);
+
+ image_name = gimp_item_get_name (layers[i]);
+ if (image_name)
+ {
+ GtkTextBuffer *buffer;
+ const gchar *comment_contents;
+
+ /* Layer name is shown as a tooltip. */
+ gtk_widget_set_tooltip_text (image, image_name);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (comment));
+
+ comment_contents = animation_animatic_get_comment (view->priv->animation,
+ panel_num);
+ if (comment_contents != NULL)
+ gtk_text_buffer_insert_at_cursor (buffer, comment_contents, -1);
+
+ g_free (image_name);
+ }
+
+ duration = gtk_spin_button_new_with_range (0.0, G_MAXINT, 1.0);
+ gtk_spin_button_set_digits (GTK_SPIN_BUTTON (duration), 0);
+ gtk_spin_button_set_increments (GTK_SPIN_BUTTON (duration), 1.0, 10.0);
+ gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (duration), TRUE);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (duration),
+ animation_animatic_get_duration (ANIMATION_ANIMATIC (animation),
+ panel_num));
+ gtk_entry_set_width_chars (GTK_ENTRY (duration), 2);
+ /* Allowing non-numeric text to type "ms" or "s". */
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (duration), FALSE);
+
+ gtk_table_attach (GTK_TABLE (view),
+ duration, 4, 5,
+ 5 * n_images - 5 * i - 5,
+ 5 * n_images - 5 * i - 4,
+ 0, /* Do not expand nor fill, nor shrink. */
+ 0, /* Do not expand nor fill, nor shrink. */
+ 0, 1);
+ g_object_set_data (G_OBJECT (duration), "layer-position",
+ GINT_TO_POINTER (panel_num));
+ g_signal_connect (duration, "value-changed",
+ (GCallback) animation_storyboard_duration_spin_changed,
+ animation);
+ gtk_widget_show (duration);
+
+ disposal = gtk_toggle_button_new ();
+ image = gtk_image_new_from_icon_name (GIMP_STOCK_TRANSPARENCY,
+ GTK_ICON_SIZE_MENU);
+ gtk_container_add (GTK_CONTAINER (disposal), image);
+ gtk_widget_show (image);
+ gtk_table_attach (GTK_TABLE (view),
+ disposal, 4, 5,
+ 5 * n_images - 5 * i - 3,
+ 5 * n_images - 5 * i - 2,
+ GTK_EXPAND, GTK_EXPAND,
+ 0, 1);
+ gtk_widget_show (disposal);
+ }
+}
+
+static void
+animation_storyboard_duration_spin_changed (GtkSpinButton *spinbutton,
+ AnimationAnimatic *animation)
+{
+ gpointer layer_pos;
+ gint duration;
+
+ layer_pos = g_object_get_data (G_OBJECT (spinbutton), "layer-position");
+ duration = gtk_spin_button_get_value_as_int (spinbutton);
+
+ animation_animatic_set_duration (animation,
+ GPOINTER_TO_INT (layer_pos),
+ duration);
+}
diff --git a/plug-ins/animation-play/widgets/animation-storyboard.h
b/plug-ins/animation-play/widgets/animation-storyboard.h
new file mode 100644
index 0000000..f92a23e
--- /dev/null
+++ b/plug-ins/animation-play/widgets/animation-storyboard.h
@@ -0,0 +1,52 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * animation-layer_view.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_STORYBOARD_H__
+#define __ANIMATION_STORYBOARD_H__
+
+#define ANIMATION_TYPE_STORYBOARD (animation_storyboard_get_type ())
+#define ANIMATION_STORYBOARD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_STORYBOARD,
AnimationStoryboard))
+#define ANIMATION_STORYBOARD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_STORYBOARD,
AnimationStoryboardClass))
+#define ANIMATION_IS_STORYBOARD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_STORYBOARD))
+#define ANIMATION_IS_STORYBOARD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_STORYBOARD))
+#define ANIMATION_STORYBOARD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_STORYBOARD,
AnimationStoryboardClass))
+
+typedef struct _AnimationStoryboard AnimationStoryboard;
+typedef struct _AnimationStoryboardClass AnimationStoryboardClass;
+typedef struct _AnimationStoryboardPrivate AnimationStoryboardPrivate;
+
+struct _AnimationStoryboard
+{
+ GtkTable parent_instance;
+
+ AnimationStoryboardPrivate *priv;
+};
+
+struct _AnimationStoryboardClass
+{
+ GtkTableClass parent_class;
+};
+
+GType animation_storyboard_get_type (void) G_GNUC_CONST;
+
+GtkWidget * animation_storyboard_new (AnimationAnimatic *animation);
+
+#endif /* __ANIMATION_STORYBOARD_H__ */
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]