mirror of
https://gitlab.gnome.org/GNOME/gimp.git
synced 2025-10-06 05:22:40 +02:00
Compare commits
162 Commits
alxsa-tota
...
wip/animat
Author | SHA1 | Date | |
---|---|---|---|
|
14fed539c4 | ||
|
778625133d | ||
|
82db9d3baa | ||
|
b01f53b54c | ||
|
b0f34c93d6 | ||
|
ee4be4703a | ||
|
50ab0e1bde | ||
|
dcafccfd0e | ||
|
26d2a4dd7e | ||
|
82a41c648a | ||
|
ff757b0e76 | ||
|
225c64bedb | ||
|
3f1af1a294 | ||
|
36d14167e0 | ||
|
dbbd861913 | ||
|
e1a0eaea9c | ||
|
9af5152811 | ||
|
b0cbfdf56f | ||
|
1e54537c8c | ||
|
23c6d5ea21 | ||
|
8b05db33e2 | ||
|
d490db1141 | ||
|
e5bda45efa | ||
|
483ffdb063 | ||
|
21a32a7b99 | ||
|
03d6b2499e | ||
|
50fc5ac8aa | ||
|
e650bf80d5 | ||
|
8d02bc5fcb | ||
|
6381b376c1 | ||
|
646ca41bcd | ||
|
61c9cddf53 | ||
|
1087dccf96 | ||
|
e6f44694dc | ||
|
6f9078d6b4 | ||
|
589569f948 | ||
|
a795f35ff2 | ||
|
eb7804d9bc | ||
|
600110b2bc | ||
|
6219a3fe17 | ||
|
31b715f37c | ||
|
89af4d59ab | ||
|
24b75f4a9e | ||
|
f2bc2f5e6f | ||
|
c536d16959 | ||
|
2bed4e942c | ||
|
a3daecc313 | ||
|
1a4262d1a5 | ||
|
a67d9019d1 | ||
|
c375ab2a13 | ||
|
3b91ef052f | ||
|
2f4fbfa380 | ||
|
3d4a41ed29 | ||
|
2ea27098dc | ||
|
6fa670f77c | ||
|
23d80e6567 | ||
|
b941092e53 | ||
|
b70fb642e4 | ||
|
b614e8e8e6 | ||
|
2559054563 | ||
|
d27b28a185 | ||
|
09d1d07b9d | ||
|
7ad5037c14 | ||
|
d19bc7016a | ||
|
993aefb50e | ||
|
84b473106f | ||
|
726f5a7729 | ||
|
8310eb0d4d | ||
|
816f19513f | ||
|
a65bb3884b | ||
|
8c04ada9a0 | ||
|
c295a9ca48 | ||
|
1be912fa54 | ||
|
35e3e961d5 | ||
|
67dd03c7e9 | ||
|
926292b66d | ||
|
d237ed96a6 | ||
|
230ffc8456 | ||
|
96e03bdb1f | ||
|
5f48f8ed60 | ||
|
1dbcbc9bc3 | ||
|
ebbc934f3f | ||
|
b1b6ccfc2b | ||
|
673fed12fe | ||
|
9df60bc5ae | ||
|
4638d812df | ||
|
0d3b17d678 | ||
|
05a3da5d10 | ||
|
a28927c85c | ||
|
ecfe2e49e2 | ||
|
dffa184d0f | ||
|
b25ca9b9b5 | ||
|
a5590895fc | ||
|
cabc8a58cf | ||
|
1e2aa92e47 | ||
|
114d704e44 | ||
|
81c421ea87 | ||
|
d3edfacffb | ||
|
ab4e577aac | ||
|
d598791f19 | ||
|
6396c312e4 | ||
|
e7c0aac59d | ||
|
db1b0a76c3 | ||
|
e13eb5a432 | ||
|
ceb582c6ed | ||
|
39fe5b3081 | ||
|
3eb896007f | ||
|
07462546e3 | ||
|
c1467113f3 | ||
|
5fbd2fea07 | ||
|
1664e97a88 | ||
|
9dd4179f25 | ||
|
5d3e3cae84 | ||
|
52b764db69 | ||
|
ba9d2d88ba | ||
|
e04da2b7ad | ||
|
b39b898cd6 | ||
|
7833558f58 | ||
|
131a5fe1b4 | ||
|
b17da21b56 | ||
|
fc9ea67e26 | ||
|
a003111553 | ||
|
9bd475308c | ||
|
2ba6950e6d | ||
|
95a140a3a6 | ||
|
b969fde412 | ||
|
d415344a5a | ||
|
5417753d8c | ||
|
b37df6ebe3 | ||
|
d2120a74dd | ||
|
0bd00d9b8d | ||
|
ac3a7d1910 | ||
|
ad5e37714b | ||
|
7a92c3a888 | ||
|
3bd674ee39 | ||
|
bf881644df | ||
|
050cf79264 | ||
|
0458f5ad69 | ||
|
4563a6c229 | ||
|
3d3673d220 | ||
|
1644b1c3a0 | ||
|
11568f0582 | ||
|
03b3d46a03 | ||
|
39a6e2e179 | ||
|
cff95a477f | ||
|
cceeedabb9 | ||
|
95703ef951 | ||
|
c008eaf4fc | ||
|
c15f828aa6 | ||
|
a33b0cc0fe | ||
|
1dc3171d58 | ||
|
974ba1c4f4 | ||
|
0beb322993 | ||
|
245538dc57 | ||
|
af392e2ecb | ||
|
a3c58e5f0d | ||
|
a23605e53b | ||
|
0a069fe46d | ||
|
c68f85b2b2 | ||
|
afbc399e50 | ||
|
ee28201289 | ||
|
5d33347c83 |
@@ -2536,6 +2536,7 @@ build/windows/Makefile
|
||||
build/windows/gimp.rc
|
||||
build/windows/gimp-plug-ins.rc
|
||||
plug-ins/Makefile
|
||||
plug-ins/animation-play/Makefile
|
||||
plug-ins/file-bmp/Makefile
|
||||
plug-ins/file-exr/Makefile
|
||||
plug-ins/file-faxg3/Makefile
|
||||
|
@@ -31,6 +31,7 @@ endif
|
||||
SUBDIRS = \
|
||||
$(script_fu) \
|
||||
$(pygimp) \
|
||||
animation-play \
|
||||
file-bmp \
|
||||
$(file_darktable) \
|
||||
$(file_exr) \
|
||||
|
2
plug-ins/animation-play/.gitignore
vendored
Normal file
2
plug-ins/animation-play/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/Makefile.in
|
||||
/Makefile
|
82
plug-ins/animation-play/Makefile.am
Normal file
82
plug-ins/animation-play/Makefile.am
Normal file
@@ -0,0 +1,82 @@
|
||||
AUTOMAKE_OPTIONS = subdir-objects
|
||||
|
||||
if OS_WIN32
|
||||
mwindows = -mwindows
|
||||
else
|
||||
libm = -lm
|
||||
endif
|
||||
|
||||
libgimp = $(top_builddir)/libgimp/libgimp-$(GIMP_API_VERSION).la
|
||||
libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la
|
||||
libgimpcolor = $(top_builddir)/libgimpcolor/libgimpcolor-$(GIMP_API_VERSION).la
|
||||
libgimpconfig = $(top_builddir)/libgimpconfig/libgimpconfig-$(GIMP_API_VERSION).la
|
||||
libgimpmath = $(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la $(libm)
|
||||
libgimpmodule = $(top_builddir)/libgimpmodule/libgimpmodule-$(GIMP_API_VERSION).la
|
||||
libgimpui = $(top_builddir)/libgimp/libgimpui-$(GIMP_API_VERSION).la
|
||||
libgimpwidgets = $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la
|
||||
|
||||
AM_LDFLAGS = $(mwindows)
|
||||
|
||||
libexecdir = $(gimpplugindir)/plug-ins
|
||||
|
||||
libexec_PROGRAMS = animation-play
|
||||
|
||||
AM_CPPFLAGS = \
|
||||
-I$(top_srcdir) \
|
||||
$(GTK_CFLAGS) \
|
||||
$(GEGL_CFLAGS) \
|
||||
-I$(includedir)
|
||||
|
||||
LDADD = \
|
||||
$(libgimpui) \
|
||||
$(libgimpwidgets) \
|
||||
$(libgimpmodule) \
|
||||
$(libgimp) \
|
||||
$(libgimpmath) \
|
||||
$(libgimpconfig) \
|
||||
$(libgimpcolor) \
|
||||
$(libgimpbase) \
|
||||
$(GTK_LIBS) \
|
||||
$(GEGL_LIBS) \
|
||||
$(RT_LIBS) \
|
||||
$(INTLLIBS) \
|
||||
$(animation_play_RC)
|
||||
|
||||
animation_play_SOURCES = \
|
||||
core/animation.h \
|
||||
core/animation.c \
|
||||
core/animation-animatic.h \
|
||||
core/animation-animatic.c \
|
||||
core/animation-camera.h \
|
||||
core/animation-camera.c \
|
||||
core/animation-celanimation.h \
|
||||
core/animation-celanimation.c \
|
||||
core/animation-playback.h \
|
||||
core/animation-playback.c \
|
||||
core/animation-renderer.h \
|
||||
core/animation-renderer.c \
|
||||
widgets/animation-dialog.h \
|
||||
widgets/animation-dialog.c \
|
||||
widgets/animation-dialog-export.h \
|
||||
widgets/animation-dialog-export.c \
|
||||
widgets/animation-editable-label.h \
|
||||
widgets/animation-editable-label.c \
|
||||
widgets/animation-editable-label-string.h \
|
||||
widgets/animation-editable-label-string.c \
|
||||
widgets/animation-keyframe-view.h \
|
||||
widgets/animation-keyframe-view.c \
|
||||
widgets/animation-layer-view.h \
|
||||
widgets/animation-layer-view.c \
|
||||
widgets/animation-menus.h \
|
||||
widgets/animation-menus.c \
|
||||
widgets/animation-storyboard.h \
|
||||
widgets/animation-storyboard.c \
|
||||
widgets/animation-xsheet.h \
|
||||
widgets/animation-xsheet.c \
|
||||
animation-utils.h \
|
||||
animation-utils.c \
|
||||
animation-play.c
|
||||
|
||||
iconsdir = $(gimpdatadir)/plug-ins/animation-play/icons/
|
||||
|
||||
icons_DATA = icons/gimp-motion.png
|
136
plug-ins/animation-play/animation-play.c
Normal file
136
plug-ins/animation-play/animation-play.c
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Animation Playback plug-in version 0.99.1
|
||||
*
|
||||
* (c) Adam D. Moss : 1997-2000 : adam@gimp.org : adam@foxbox.org
|
||||
* (c) Mircea Purdea : 2009 : someone_else@exhalus.net
|
||||
* (c) Jehan : 2012-2016 : jehan at girinstud.io
|
||||
*
|
||||
* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* 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>
|
||||
#undef GDK_DISABLE_DEPRECATED
|
||||
#include <libgimp/gimpui.h>
|
||||
#include "libgimp/stdplugins-intl.h"
|
||||
|
||||
#include "widgets/animation-dialog.h"
|
||||
|
||||
#include "animation-utils.h"
|
||||
|
||||
static void query (void);
|
||||
static void run (const gchar *name,
|
||||
gint nparams,
|
||||
const GimpParam *param,
|
||||
gint *nreturn_vals,
|
||||
GimpParam **return_vals);
|
||||
|
||||
const GimpPlugInInfo PLUG_IN_INFO =
|
||||
{
|
||||
NULL, /* init_proc */
|
||||
NULL, /* quit_proc */
|
||||
query, /* query_proc */
|
||||
run, /* run_proc */
|
||||
};
|
||||
|
||||
MAIN ()
|
||||
|
||||
static void
|
||||
query (void)
|
||||
{
|
||||
static const GimpParamDef args[] =
|
||||
{
|
||||
{ GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
|
||||
{ GIMP_PDB_IMAGE, "image", "Input image" },
|
||||
{ GIMP_PDB_DRAWABLE, "drawable", "Input drawable (unused)" }
|
||||
};
|
||||
|
||||
gimp_install_procedure (PLUG_IN_PROC,
|
||||
N_("Preview an animation"),
|
||||
"",
|
||||
"Adam D. Moss <adam@gimp.org>",
|
||||
"Adam D. Moss <adam@gimp.org>",
|
||||
"1997, 1998...",
|
||||
N_("Animation _Playback..."),
|
||||
"RGB*, INDEXED*, GRAY*",
|
||||
GIMP_PLUGIN,
|
||||
G_N_ELEMENTS (args), 0,
|
||||
args, NULL);
|
||||
|
||||
gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/Filters/Animation");
|
||||
gimp_plugin_icon_register (PLUG_IN_PROC, GIMP_ICON_TYPE_ICON_NAME,
|
||||
(const guint8 *) "media-playback-start");
|
||||
}
|
||||
|
||||
static void
|
||||
run (const gchar *name,
|
||||
gint n_params,
|
||||
const GimpParam *param,
|
||||
gint *nreturn_vals,
|
||||
GimpParam **return_vals)
|
||||
{
|
||||
static GimpParam values[1];
|
||||
GimpPDBStatusType status;
|
||||
GimpRunMode run_mode;
|
||||
GeglConfig *config;
|
||||
|
||||
INIT_I18N ();
|
||||
|
||||
gegl_init (NULL, NULL);
|
||||
config = gegl_config ();
|
||||
/* For preview, we want fast (0.0) over high quality (1.0). */
|
||||
g_object_set (config, "quality", 0.0, NULL);
|
||||
|
||||
run_mode = param[0].data.d_int32;
|
||||
|
||||
if (run_mode == GIMP_RUN_NONINTERACTIVE ||
|
||||
n_params != 3)
|
||||
{
|
||||
/* This plugin is meaningless right now other than interactive. */
|
||||
status = GIMP_PDB_CALLING_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
GtkWidget *dialog;
|
||||
gint32 image_id;
|
||||
|
||||
gimp_ui_init (PLUG_IN_BINARY, TRUE);
|
||||
|
||||
image_id = param[1].data.d_image;
|
||||
dialog = animation_dialog_new (image_id);
|
||||
|
||||
gimp_help_connect (GTK_WIDGET (dialog),
|
||||
gimp_standard_help_func,
|
||||
PLUG_IN_PROC, NULL);
|
||||
gtk_widget_show_now (GTK_WIDGET (dialog));
|
||||
|
||||
gtk_main ();
|
||||
gimp_displays_flush ();
|
||||
|
||||
status = GIMP_PDB_SUCCESS;
|
||||
}
|
||||
|
||||
values[0].type = GIMP_PDB_STATUS;
|
||||
values[0].data.d_status = status;
|
||||
|
||||
*nreturn_vals = 1;
|
||||
*return_vals = values;
|
||||
|
||||
gegl_exit ();
|
||||
gimp_quit ();
|
||||
}
|
344
plug-ins/animation-play/animation-utils.c
Executable file
344
plug-ins/animation-play/animation-utils.c
Executable file
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* Animation Playback plug-in version 0.99.1
|
||||
*
|
||||
* (c) Jehan : 2012-2015 : jehan at girinstud.io
|
||||
*
|
||||
* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* 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 <string.h>
|
||||
|
||||
#include <libgimp/gimp.h>
|
||||
#undef GDK_DISABLE_DEPRECATED
|
||||
#include <libgimp/gimpui.h>
|
||||
|
||||
#include "libgimp/stdplugins-intl.h"
|
||||
|
||||
#include "animation-utils.h"
|
||||
|
||||
/* total_alpha_preview:
|
||||
* Fill the @drawing_data with an alpha (grey chess) pattern.
|
||||
* This uses a static array, copied over each line (with some shift to
|
||||
* reproduce the pattern), using `memcpy()`.
|
||||
* The reason why we keep the pattern in the statically allocated memory,
|
||||
* instead of simply looping through @drawing_data and recreating the
|
||||
* pattern is simply because `memcpy()` implementations are supposed to
|
||||
* be more efficient than loops over an array. */
|
||||
void
|
||||
total_alpha_preview (guchar *drawing_data,
|
||||
guint drawing_width,
|
||||
guint drawing_height)
|
||||
{
|
||||
static guint alpha_line_width = 0;
|
||||
static guchar *alpha_line = NULL;
|
||||
gint i;
|
||||
|
||||
g_assert (drawing_width > 0);
|
||||
|
||||
/* If width change, we update the "alpha" line. */
|
||||
if (alpha_line_width < drawing_width + 8)
|
||||
{
|
||||
alpha_line_width = drawing_width + 8;
|
||||
|
||||
g_free (alpha_line);
|
||||
|
||||
/* A full line + 8 pixels (1 square). */
|
||||
alpha_line = g_malloc (alpha_line_width * 3);
|
||||
|
||||
for (i = 0; i < alpha_line_width; i++)
|
||||
{
|
||||
/* 8 pixels dark grey, 8 pixels light grey, and so on. */
|
||||
if (i & 8)
|
||||
{
|
||||
alpha_line[i * 3 + 0] =
|
||||
alpha_line[i * 3 + 1] =
|
||||
alpha_line[i * 3 + 2] = 102;
|
||||
}
|
||||
else
|
||||
{
|
||||
alpha_line[i * 3 + 0] =
|
||||
alpha_line[i * 3 + 1] =
|
||||
alpha_line[i * 3 + 2] = 154;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < drawing_height; i++)
|
||||
{
|
||||
if (i & 8)
|
||||
{
|
||||
memcpy (&drawing_data[i * 3 * drawing_width],
|
||||
alpha_line,
|
||||
3 * drawing_width);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Every 8 vertical pixels, we shift the horizontal line by 8 pixels. */
|
||||
memcpy (&drawing_data[i * 3 * drawing_width],
|
||||
alpha_line + 24,
|
||||
3 * drawing_width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_buffer &&
|
||||
(! backdrop_buffer ||
|
||||
backdrop_scale_ratio >= 0.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;
|
||||
}
|
||||
|
||||
void
|
||||
show_scrolled_child (GtkScrolledWindow *window,
|
||||
GtkWidget *child)
|
||||
{
|
||||
GtkWidget *contents = gtk_bin_get_child (GTK_BIN (window));
|
||||
GtkAdjustment *hadj;
|
||||
GtkAdjustment *vadj;
|
||||
GtkAllocation window_allocation;
|
||||
GtkAllocation child_allocation;
|
||||
gint x;
|
||||
gint y;
|
||||
gint x_xsheet;
|
||||
gint y_xsheet;
|
||||
|
||||
hadj = gtk_scrolled_window_get_vadjustment (window);
|
||||
vadj = gtk_scrolled_window_get_vadjustment (window);
|
||||
|
||||
/* Handling both contents with native scroll abilities, and
|
||||
contents added with a viewport. */
|
||||
if (GTK_IS_VIEWPORT (contents))
|
||||
contents = gtk_bin_get_child (GTK_BIN (contents));
|
||||
|
||||
gtk_widget_translate_coordinates (child, GTK_WIDGET (window),
|
||||
0, 0, &x_xsheet, &y_xsheet);
|
||||
gtk_widget_translate_coordinates (child, contents,
|
||||
0, 0, &x, &y);
|
||||
|
||||
gtk_widget_get_allocation (child, &child_allocation);
|
||||
gtk_widget_get_allocation (GTK_WIDGET (window),
|
||||
&window_allocation);
|
||||
|
||||
/* Scroll only if the widget is not already visible. */
|
||||
if (x_xsheet < 0 || x_xsheet + child_allocation.width > window_allocation.width)
|
||||
{
|
||||
gtk_adjustment_set_value (hadj, x);
|
||||
}
|
||||
if (y_xsheet < 0 || y_xsheet + child_allocation.height > window_allocation.height)
|
||||
{
|
||||
gtk_adjustment_set_value (vadj, y);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
hide_item (gint item,
|
||||
gboolean recursive,
|
||||
gboolean reset_tag)
|
||||
{
|
||||
gimp_item_set_visible (item, FALSE);
|
||||
if (reset_tag)
|
||||
gimp_item_set_color_tag (item, GIMP_COLOR_TAG_NONE);
|
||||
|
||||
if (recursive && gimp_item_is_group (item))
|
||||
{
|
||||
gint32 *children;
|
||||
gint32 n_children;
|
||||
gint i;
|
||||
|
||||
children = gimp_item_get_children (item, &n_children);
|
||||
|
||||
for (i = 0; i < n_children; i++)
|
||||
{
|
||||
hide_item (children[i], TRUE, reset_tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
show_layer (gint item,
|
||||
gint32 color_tag,
|
||||
gdouble opacity)
|
||||
{
|
||||
gint32 parent;
|
||||
|
||||
gimp_item_set_visible (item, TRUE);
|
||||
if (color_tag >= 0)
|
||||
gimp_item_set_color_tag (item, color_tag);
|
||||
gimp_layer_set_opacity (item, opacity * 100.0);
|
||||
|
||||
/* Show the parent as well, but do not update its color tag. */
|
||||
parent = gimp_item_get_parent (item);
|
||||
if (parent > 0)
|
||||
{
|
||||
show_layer (parent, -1, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
gint
|
||||
compare_int_from (gconstpointer f1,
|
||||
gconstpointer f2,
|
||||
gpointer data)
|
||||
{
|
||||
gint first_frame = GPOINTER_TO_INT (data);
|
||||
gint frame1 = GPOINTER_TO_INT (f1);
|
||||
gint frame2 = GPOINTER_TO_INT (f2);
|
||||
gint invert;
|
||||
|
||||
if (frame1 == frame2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
invert = ((frame1 >= first_frame && frame2 >= first_frame) ||
|
||||
(frame1 < first_frame && frame2 < first_frame)) ? 1 : -1;
|
||||
if (frame1 < frame2)
|
||||
return invert * -1;
|
||||
else
|
||||
return invert;
|
||||
}
|
||||
}
|
72
plug-ins/animation-play/animation-utils.h
Executable file
72
plug-ins/animation-play/animation-utils.h
Executable file
@@ -0,0 +1,72 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation-utils.c
|
||||
* Copyright (C) 2015 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_UTILS_H__
|
||||
#define __ANIMATION_UTILS_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#define PLUG_IN_PROC "plug-in-animationplay"
|
||||
#define PLUG_IN_BINARY "animation-play"
|
||||
#define PLUG_IN_ROLE "gimp-animation-playback"
|
||||
|
||||
#define MAX_FRAMERATE 300.0
|
||||
#define DEFAULT_FRAMERATE 24.0
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ANIMATION_DND_TYPE_NONE = 0,
|
||||
ANIMATION_DND_TYPE_PANEL = 1,
|
||||
|
||||
GIMP_DND_TYPE_LAST = ANIMATION_DND_TYPE_PANEL
|
||||
} AnimationDndType;
|
||||
|
||||
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);
|
||||
|
||||
void show_scrolled_child (GtkScrolledWindow *window,
|
||||
GtkWidget *child);
|
||||
|
||||
void hide_item (gint item,
|
||||
gboolean recursive,
|
||||
gboolean reset_color_tag);
|
||||
void show_layer (gint item,
|
||||
gint32 color_tag,
|
||||
gdouble opacity);
|
||||
|
||||
gint compare_int_from (gconstpointer f1,
|
||||
gconstpointer f2,
|
||||
gpointer data);
|
||||
|
||||
#endif /* __ANIMATION_UTILS_H__ */
|
||||
|
||||
|
1196
plug-ins/animation-play/core/animation-animatic.c
Normal file
1196
plug-ins/animation-play/core/animation-animatic.c
Normal file
File diff suppressed because it is too large
Load Diff
74
plug-ins/animation-play/core/animation-animatic.h
Normal file
74
plug-ins/animation-play/core/animation-animatic.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/* 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_panel_duration (AnimationAnimatic *animatic,
|
||||
gint panel,
|
||||
gint duration);
|
||||
gint animation_animatic_get_panel_duration (AnimationAnimatic *animatic,
|
||||
gint panel);
|
||||
|
||||
void animation_animatic_set_comment (AnimationAnimatic *animatic,
|
||||
gint panel,
|
||||
const gchar *comment);
|
||||
const gchar * animation_animatic_get_comment (AnimationAnimatic *animatic,
|
||||
gint panel);
|
||||
|
||||
void animation_animatic_set_combine (AnimationAnimatic *animatic,
|
||||
gint panel,
|
||||
gboolean combine);
|
||||
const gboolean animation_animatic_get_combine (AnimationAnimatic *animatic,
|
||||
gint panel);
|
||||
|
||||
gint animation_animatic_get_panel (AnimationAnimatic *animation,
|
||||
gint position);
|
||||
gint animation_animatic_get_position (AnimationAnimatic *animation,
|
||||
gint panel);
|
||||
|
||||
void animation_animatic_move_panel (AnimationAnimatic *animatic,
|
||||
gint panel,
|
||||
gint new_panel);
|
||||
#endif /* __ANIMATION_ANIMATIC_H__ */
|
735
plug-ins/animation-play/core/animation-camera.c
Normal file
735
plug-ins/animation-play/core/animation-camera.c
Normal file
@@ -0,0 +1,735 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation-camera.c
|
||||
* Copyright (C) 2016-2017 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.h"
|
||||
|
||||
#include "animation-camera.h"
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_ANIMATION,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
CAMERA_CHANGED,
|
||||
KEYFRAME_SET,
|
||||
KEYFRAME_DELETED,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gint x;
|
||||
gint y;
|
||||
}
|
||||
Offset;
|
||||
|
||||
struct _AnimationCameraPrivate
|
||||
{
|
||||
Animation *animation;
|
||||
|
||||
/* Panning and tilting. */
|
||||
GList *offsets;
|
||||
GList *zoom;
|
||||
|
||||
/* Preview */
|
||||
gint preview_position;
|
||||
Offset *preview_offset;
|
||||
gdouble preview_scale;
|
||||
|
||||
gboolean block_signals;
|
||||
};
|
||||
|
||||
static void animation_camera_finalize (GObject *object);
|
||||
static void animation_camera_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec);
|
||||
static void animation_camera_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec);
|
||||
|
||||
static void animation_camera_emit_camera_changed (AnimationCamera *camera,
|
||||
gint position);
|
||||
static void animation_camera_get_real (AnimationCamera *camera,
|
||||
gint position,
|
||||
gint *x_offset,
|
||||
gint *y_offset,
|
||||
gdouble *scale);
|
||||
|
||||
G_DEFINE_TYPE (AnimationCamera, animation_camera, G_TYPE_OBJECT)
|
||||
|
||||
#define parent_class animation_camera_parent_class
|
||||
|
||||
static guint signals[LAST_SIGNAL] = { 0 };
|
||||
|
||||
static void
|
||||
animation_camera_class_init (AnimationCameraClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = animation_camera_finalize;
|
||||
object_class->get_property = animation_camera_get_property;
|
||||
object_class->set_property = animation_camera_set_property;
|
||||
|
||||
/**
|
||||
* AnimationCamera::camera-changed:
|
||||
* @camera: the #AnimationCamera.
|
||||
* @position:
|
||||
* @duration:
|
||||
*
|
||||
* The ::camera-changed signal will be emitted when camera offsets,
|
||||
* zoom, or other characteristics were updated between
|
||||
* [@position; @position + @duration[.
|
||||
*/
|
||||
signals[CAMERA_CHANGED] =
|
||||
g_signal_new ("camera-changed",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationCameraClass, camera_changed),
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
2,
|
||||
G_TYPE_INT,
|
||||
G_TYPE_INT);
|
||||
|
||||
/**
|
||||
* AnimationCamera::keyframe-set:
|
||||
* @camera: the #AnimationCamera.
|
||||
* @position:
|
||||
*
|
||||
* The ::keyframe-set signal will be emitted when a keyframe is
|
||||
* created or modified at @position.
|
||||
*/
|
||||
signals[KEYFRAME_SET] =
|
||||
g_signal_new ("keyframe-set",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationCameraClass, keyframe_set),
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_INT);
|
||||
|
||||
/**
|
||||
* AnimationCamera::keyframe-deleted:
|
||||
* @camera: the #AnimationCamera.
|
||||
* @position:
|
||||
*
|
||||
* The ::keyframe-set signal will be emitted when a keyframe is
|
||||
* deleted at @position.
|
||||
*/
|
||||
signals[KEYFRAME_DELETED] =
|
||||
g_signal_new ("keyframe-deleted",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationCameraClass, keyframe_deleted),
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_INT);
|
||||
|
||||
g_object_class_install_property (object_class, PROP_ANIMATION,
|
||||
g_param_spec_object ("animation",
|
||||
NULL, NULL,
|
||||
ANIMATION_TYPE_ANIMATION,
|
||||
G_PARAM_READWRITE |
|
||||
G_PARAM_CONSTRUCT_ONLY));
|
||||
|
||||
g_type_class_add_private (klass, sizeof (AnimationCameraPrivate));
|
||||
}
|
||||
|
||||
static void
|
||||
animation_camera_init (AnimationCamera *view)
|
||||
{
|
||||
view->priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
|
||||
ANIMATION_TYPE_CAMERA,
|
||||
AnimationCameraPrivate);
|
||||
view->priv->preview_position = -1;
|
||||
}
|
||||
|
||||
/************ Public Functions ****************/
|
||||
|
||||
/**
|
||||
* animation_camera_new:
|
||||
*
|
||||
* Creates a new camera.
|
||||
*/
|
||||
AnimationCamera *
|
||||
animation_camera_new (Animation *animation)
|
||||
{
|
||||
AnimationCamera *camera;
|
||||
|
||||
camera = g_object_new (ANIMATION_TYPE_CAMERA,
|
||||
"animation", animation,
|
||||
NULL);
|
||||
|
||||
return camera;
|
||||
}
|
||||
|
||||
gboolean
|
||||
animation_camera_has_offset_keyframe (AnimationCamera *camera,
|
||||
gint position)
|
||||
{
|
||||
g_return_val_if_fail (position >= 0 &&
|
||||
position < animation_get_duration (camera->priv->animation),
|
||||
FALSE);
|
||||
|
||||
return g_list_nth_data (camera->priv->offsets, position) != NULL;
|
||||
}
|
||||
|
||||
gboolean
|
||||
animation_camera_has_zoom_keyframe (AnimationCamera *camera,
|
||||
gint position)
|
||||
{
|
||||
g_return_val_if_fail (position >= 0 &&
|
||||
position < animation_get_duration (camera->priv->animation),
|
||||
FALSE);
|
||||
|
||||
return g_list_nth_data (camera->priv->zoom, position) != NULL;
|
||||
}
|
||||
|
||||
void
|
||||
animation_camera_set_offsets (AnimationCamera *camera,
|
||||
gint position,
|
||||
gint x,
|
||||
gint y)
|
||||
{
|
||||
GList *iter;
|
||||
Offset *offset;
|
||||
|
||||
g_return_if_fail (position >= 0 &&
|
||||
position < animation_get_duration (camera->priv->animation));
|
||||
|
||||
iter = g_list_nth (camera->priv->offsets, position);
|
||||
|
||||
if (! iter)
|
||||
{
|
||||
gint length = g_list_length (camera->priv->offsets);
|
||||
gint i;
|
||||
|
||||
for (i = length; i < position; i++)
|
||||
{
|
||||
camera->priv->offsets = g_list_append (camera->priv->offsets, NULL);
|
||||
}
|
||||
offset = g_new (Offset, 1);
|
||||
camera->priv->offsets = g_list_append (camera->priv->offsets, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (! iter->data)
|
||||
{
|
||||
iter->data = g_new (Offset, 1);
|
||||
}
|
||||
offset = iter->data;
|
||||
}
|
||||
|
||||
offset->x = x;
|
||||
offset->y = y;
|
||||
|
||||
if (! camera->priv->block_signals)
|
||||
{
|
||||
g_signal_emit (camera, signals[KEYFRAME_SET], 0, position);
|
||||
animation_camera_emit_camera_changed (camera, position);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
animation_camera_zoom (AnimationCamera *camera,
|
||||
gint position,
|
||||
gdouble scale)
|
||||
{
|
||||
GList *iter;
|
||||
gdouble *zoom;
|
||||
|
||||
g_return_if_fail (position >= 0 &&
|
||||
position < animation_get_duration (camera->priv->animation));
|
||||
|
||||
iter = g_list_nth (camera->priv->zoom, position);
|
||||
|
||||
if (! iter)
|
||||
{
|
||||
gint length = g_list_length (camera->priv->zoom);
|
||||
gint i;
|
||||
|
||||
for (i = length; i < position; i++)
|
||||
{
|
||||
camera->priv->zoom = g_list_append (camera->priv->zoom, NULL);
|
||||
}
|
||||
zoom = g_new (gdouble, 1);
|
||||
camera->priv->zoom = g_list_append (camera->priv->zoom, zoom);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (! iter->data)
|
||||
{
|
||||
iter->data = g_new (gdouble, 1);
|
||||
}
|
||||
zoom = iter->data;
|
||||
}
|
||||
*zoom = scale;
|
||||
|
||||
if (! camera->priv->block_signals)
|
||||
{
|
||||
g_signal_emit (camera, signals[KEYFRAME_SET], 0, position);
|
||||
animation_camera_emit_camera_changed (camera, position);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
animation_camera_delete_offset_keyframe (AnimationCamera *camera,
|
||||
gint position)
|
||||
{
|
||||
GList *iter;
|
||||
|
||||
g_return_if_fail (position >= 0 &&
|
||||
position < animation_get_duration (camera->priv->animation));
|
||||
|
||||
iter = g_list_nth (camera->priv->offsets, position);
|
||||
if (iter && iter->data)
|
||||
{
|
||||
g_free (iter->data);
|
||||
iter->data = NULL;
|
||||
|
||||
g_signal_emit (camera, signals[KEYFRAME_DELETED], 0, position);
|
||||
animation_camera_emit_camera_changed (camera, position);
|
||||
}
|
||||
if (camera->priv->preview_position == position)
|
||||
{
|
||||
animation_camera_reset_preview (camera);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
animation_camera_delete_zoom_keyframe (AnimationCamera *camera,
|
||||
gint position)
|
||||
{
|
||||
GList *iter;
|
||||
|
||||
g_return_if_fail (position >= 0 &&
|
||||
position < animation_get_duration (camera->priv->animation));
|
||||
|
||||
iter = g_list_nth (camera->priv->zoom, position);
|
||||
if (iter && iter->data)
|
||||
{
|
||||
g_free (iter->data);
|
||||
iter->data = NULL;
|
||||
|
||||
g_signal_emit (camera, signals[KEYFRAME_DELETED], 0, position);
|
||||
animation_camera_emit_camera_changed (camera, position);
|
||||
}
|
||||
if (camera->priv->preview_position == position)
|
||||
{
|
||||
animation_camera_reset_preview (camera);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
animation_camera_preview_keyframe (AnimationCamera *camera,
|
||||
gint position,
|
||||
gint x,
|
||||
gint y,
|
||||
gdouble scale)
|
||||
{
|
||||
g_return_if_fail (position >= 0 &&
|
||||
position < animation_get_duration (camera->priv->animation));
|
||||
|
||||
if (! camera->priv->preview_offset)
|
||||
camera->priv->preview_offset = g_new (Offset, 1);
|
||||
|
||||
camera->priv->preview_offset->x = x;
|
||||
camera->priv->preview_offset->y = y;
|
||||
camera->priv->preview_scale = scale;
|
||||
camera->priv->preview_position = position;
|
||||
|
||||
g_signal_emit (camera, signals[KEYFRAME_SET], 0, position);
|
||||
g_signal_emit (camera, signals[CAMERA_CHANGED], 0,
|
||||
position, 1);
|
||||
}
|
||||
|
||||
void
|
||||
animation_camera_apply_preview (AnimationCamera *camera)
|
||||
{
|
||||
if (camera->priv->preview_position != -1)
|
||||
{
|
||||
gint preview_offset_x;
|
||||
gint preview_offset_y;
|
||||
gdouble preview_scale;
|
||||
gint real_offset_x;
|
||||
gint real_offset_y;
|
||||
gdouble real_scale;
|
||||
gint position;
|
||||
|
||||
animation_camera_get (camera, camera->priv->preview_position,
|
||||
&preview_offset_x, &preview_offset_y,
|
||||
&preview_scale);
|
||||
animation_camera_get_real (camera, camera->priv->preview_position,
|
||||
&real_offset_x, &real_offset_y,
|
||||
&real_scale);
|
||||
|
||||
if (camera->priv->preview_offset)
|
||||
g_free (camera->priv->preview_offset);
|
||||
camera->priv->preview_offset = NULL;
|
||||
position = camera->priv->preview_position;
|
||||
camera->priv->preview_position = -1;
|
||||
|
||||
/* Do not run the changed signal twice and recompute twice the
|
||||
* same frame. Just a little internal trick. */
|
||||
camera->priv->block_signals = TRUE;
|
||||
if (preview_offset_x != real_offset_x ||
|
||||
preview_offset_y != real_offset_y)
|
||||
{
|
||||
animation_camera_set_offsets (camera, position,
|
||||
preview_offset_x,
|
||||
preview_offset_y);
|
||||
}
|
||||
if (preview_scale != real_scale)
|
||||
{
|
||||
animation_camera_zoom (camera, position, preview_scale);
|
||||
}
|
||||
camera->priv->block_signals = FALSE;
|
||||
g_signal_emit (camera, signals[KEYFRAME_SET], 0, position);
|
||||
animation_camera_emit_camera_changed (camera, position);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
animation_camera_reset_preview (AnimationCamera *camera)
|
||||
{
|
||||
gboolean changed = FALSE;
|
||||
gint position_changed = -1;
|
||||
|
||||
if (camera->priv->preview_position != -1)
|
||||
{
|
||||
gint preview_offset_x;
|
||||
gint preview_offset_y;
|
||||
gdouble preview_scale;
|
||||
gint real_offset_x;
|
||||
gint real_offset_y;
|
||||
gdouble real_scale;
|
||||
|
||||
animation_camera_get (camera, camera->priv->preview_position,
|
||||
&preview_offset_x, &preview_offset_y,
|
||||
&preview_scale);
|
||||
animation_camera_get_real (camera, camera->priv->preview_position,
|
||||
&real_offset_x, &real_offset_y,
|
||||
&real_scale);
|
||||
changed = (preview_offset_x != real_offset_x ||
|
||||
preview_offset_y != real_offset_y ||
|
||||
preview_scale != real_scale);
|
||||
position_changed = camera->priv->preview_position;
|
||||
|
||||
if (camera->priv->preview_offset)
|
||||
g_free (camera->priv->preview_offset);
|
||||
camera->priv->preview_offset = NULL;
|
||||
}
|
||||
|
||||
camera->priv->preview_position = -1;
|
||||
if (changed)
|
||||
{
|
||||
g_signal_emit (camera, signals[KEYFRAME_DELETED], 0,
|
||||
position_changed);
|
||||
g_signal_emit (camera, signals[CAMERA_CHANGED], 0,
|
||||
position_changed, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
animation_camera_get (AnimationCamera *camera,
|
||||
gint position,
|
||||
gint *x_offset,
|
||||
gint *y_offset,
|
||||
gdouble *scale)
|
||||
{
|
||||
if (camera->priv->preview_position == position)
|
||||
{
|
||||
*x_offset = camera->priv->preview_offset->x;
|
||||
*y_offset = camera->priv->preview_offset->y;
|
||||
*scale = camera->priv->preview_scale;
|
||||
}
|
||||
else
|
||||
{
|
||||
animation_camera_get_real (camera, position,
|
||||
x_offset, y_offset,
|
||||
scale);
|
||||
}
|
||||
}
|
||||
|
||||
/************ Private Functions ****************/
|
||||
|
||||
static void
|
||||
animation_camera_finalize (GObject *object)
|
||||
{
|
||||
g_list_free_full (ANIMATION_CAMERA (object)->priv->offsets, g_free);
|
||||
g_list_free_full (ANIMATION_CAMERA (object)->priv->zoom, g_free);
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_camera_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
AnimationCamera *camera = ANIMATION_CAMERA (object);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_ANIMATION:
|
||||
camera->priv->animation = g_value_get_object (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
animation_camera_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
AnimationCamera *camera = ANIMATION_CAMERA (object);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_ANIMATION:
|
||||
g_value_set_object (value, camera->priv->animation);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
animation_camera_emit_camera_changed (AnimationCamera *camera,
|
||||
gint position)
|
||||
{
|
||||
GList *iter;
|
||||
gint prev_keyframe;
|
||||
gint next_keyframe;
|
||||
gint i;
|
||||
|
||||
if (position > 0)
|
||||
{
|
||||
i = position - 1;
|
||||
iter = g_list_nth (camera->priv->offsets, i);
|
||||
for (; iter && ! iter->data; iter = iter->prev, i--)
|
||||
;
|
||||
iter = g_list_nth (camera->priv->zoom, i);
|
||||
prev_keyframe = i + 1;
|
||||
for (; iter && ! iter->data; iter = iter->prev, i--)
|
||||
;
|
||||
prev_keyframe = MIN (i + 1, prev_keyframe);
|
||||
}
|
||||
else
|
||||
{
|
||||
prev_keyframe = 0;
|
||||
}
|
||||
if (position < animation_get_duration (camera->priv->animation) - 1)
|
||||
{
|
||||
i = position + 1;
|
||||
iter = g_list_nth (camera->priv->offsets, i);
|
||||
for (; iter && ! iter->data; iter = iter->next, i++)
|
||||
;
|
||||
if (iter && iter->data)
|
||||
{
|
||||
next_keyframe = i - 1;
|
||||
iter = g_list_nth (camera->priv->zoom, i);
|
||||
for (; iter && ! iter->data; iter = iter->next, i++)
|
||||
;
|
||||
next_keyframe = MAX (i - 1, next_keyframe);
|
||||
}
|
||||
else
|
||||
{
|
||||
next_keyframe = animation_get_duration (camera->priv->animation) - 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
next_keyframe = animation_get_duration (camera->priv->animation) - 1;
|
||||
}
|
||||
g_signal_emit (camera, signals[CAMERA_CHANGED], 0,
|
||||
prev_keyframe, next_keyframe - prev_keyframe + 1);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_camera_get_real (AnimationCamera *camera,
|
||||
gint position,
|
||||
gint *x_offset,
|
||||
gint *y_offset,
|
||||
gdouble *scale)
|
||||
{
|
||||
Offset *position_keyframe;
|
||||
gdouble *zoom_keyframe;
|
||||
|
||||
g_return_if_fail (position >= 0 &&
|
||||
position < animation_get_duration (camera->priv->animation));
|
||||
|
||||
position_keyframe = g_list_nth_data (camera->priv->offsets, position);
|
||||
if (position_keyframe)
|
||||
{
|
||||
/* There is a keyframe to this exact position. Use its values. */
|
||||
*x_offset = position_keyframe->x;
|
||||
*y_offset = position_keyframe->y;
|
||||
}
|
||||
else
|
||||
{
|
||||
GList *iter;
|
||||
Offset *prev_keyframe = NULL;
|
||||
Offset *next_keyframe = NULL;
|
||||
gint prev_keyframe_pos;
|
||||
gint next_keyframe_pos;
|
||||
gint i;
|
||||
|
||||
/* This position is not a keyframe. */
|
||||
if (position > 0)
|
||||
{
|
||||
i = MIN (position - 1, g_list_length (camera->priv->offsets) - 1);
|
||||
iter = g_list_nth (camera->priv->offsets, i);
|
||||
for (; iter && ! iter->data; iter = iter->prev, i--)
|
||||
;
|
||||
if (iter && iter->data)
|
||||
{
|
||||
prev_keyframe_pos = i;
|
||||
prev_keyframe = iter->data;
|
||||
}
|
||||
}
|
||||
if (position < animation_get_duration (camera->priv->animation) - 1)
|
||||
{
|
||||
i = position + 1;
|
||||
iter = g_list_nth (camera->priv->offsets, i);
|
||||
for (; iter && ! iter->data; iter = iter->next, i++)
|
||||
;
|
||||
if (iter && iter->data)
|
||||
{
|
||||
next_keyframe_pos = i;
|
||||
next_keyframe = iter->data;
|
||||
}
|
||||
}
|
||||
|
||||
if (prev_keyframe == NULL && next_keyframe == NULL)
|
||||
{
|
||||
*x_offset = *y_offset = 0;
|
||||
}
|
||||
else if (prev_keyframe == NULL)
|
||||
{
|
||||
*x_offset = next_keyframe->x;
|
||||
*y_offset = next_keyframe->y;
|
||||
}
|
||||
else if (next_keyframe == NULL)
|
||||
{
|
||||
*x_offset = prev_keyframe->x;
|
||||
*y_offset = prev_keyframe->y;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* XXX No curve editing or anything like this yet.
|
||||
* All keyframing is linear in this first version.
|
||||
*/
|
||||
*x_offset = prev_keyframe->x + (position - prev_keyframe_pos) *
|
||||
(next_keyframe->x - prev_keyframe->x) /
|
||||
(next_keyframe_pos - prev_keyframe_pos);
|
||||
*y_offset = prev_keyframe->y + (position - prev_keyframe_pos) *
|
||||
(next_keyframe->y - prev_keyframe->y) /
|
||||
(next_keyframe_pos - prev_keyframe_pos);
|
||||
}
|
||||
}
|
||||
|
||||
zoom_keyframe = g_list_nth_data (camera->priv->zoom, position);
|
||||
if (zoom_keyframe)
|
||||
{
|
||||
/* There is a keyframe to this exact position. Use its values. */
|
||||
*scale = *zoom_keyframe;
|
||||
}
|
||||
else
|
||||
{
|
||||
GList *iter;
|
||||
gdouble *prev_keyframe = NULL;
|
||||
gdouble *next_keyframe = NULL;
|
||||
gint prev_keyframe_pos;
|
||||
gint next_keyframe_pos;
|
||||
gint i;
|
||||
|
||||
/* This position is not a keyframe. */
|
||||
if (position > 0)
|
||||
{
|
||||
i = MIN (position - 1, g_list_length (camera->priv->zoom) - 1);
|
||||
iter = g_list_nth (camera->priv->zoom, i);
|
||||
for (; iter && ! iter->data; iter = iter->prev, i--)
|
||||
;
|
||||
if (iter && iter->data)
|
||||
{
|
||||
prev_keyframe_pos = i;
|
||||
prev_keyframe = iter->data;
|
||||
}
|
||||
}
|
||||
if (position < animation_get_duration (camera->priv->animation) - 1)
|
||||
{
|
||||
i = position + 1;
|
||||
iter = g_list_nth (camera->priv->zoom, i);
|
||||
for (; iter && ! iter->data; iter = iter->next, i++)
|
||||
;
|
||||
if (iter && iter->data)
|
||||
{
|
||||
next_keyframe_pos = i;
|
||||
next_keyframe = iter->data;
|
||||
}
|
||||
}
|
||||
|
||||
if (prev_keyframe == NULL && next_keyframe == NULL)
|
||||
{
|
||||
*scale = 1.0;
|
||||
}
|
||||
else if (prev_keyframe == NULL)
|
||||
{
|
||||
*scale = *next_keyframe;
|
||||
}
|
||||
else if (next_keyframe == NULL)
|
||||
{
|
||||
*scale = *prev_keyframe;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* XXX No curve editing or anything like this yet.
|
||||
* All keyframing is linear in this first version.
|
||||
*/
|
||||
*scale = *prev_keyframe + (position - prev_keyframe_pos) *
|
||||
(*next_keyframe - *prev_keyframe) /
|
||||
(next_keyframe_pos - prev_keyframe_pos);
|
||||
}
|
||||
}
|
||||
}
|
90
plug-ins/animation-play/core/animation-camera.h
Normal file
90
plug-ins/animation-play/core/animation-camera.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation-camera.h
|
||||
* Copyright (C) 2016-2017 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_CAMERA_H__
|
||||
#define __ANIMATION_CAMERA_H__
|
||||
|
||||
#define ANIMATION_TYPE_CAMERA (animation_camera_get_type ())
|
||||
#define ANIMATION_CAMERA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_CAMERA, AnimationCamera))
|
||||
#define ANIMATION_CAMERA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_CAMERA, AnimationCameraClass))
|
||||
#define ANIMATION_IS_CAMERA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_CAMERA))
|
||||
#define ANIMATION_IS_CAMERA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_CAMERA))
|
||||
#define ANIMATION_CAMERA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_CAMERA, AnimationCameraClass))
|
||||
|
||||
typedef struct _AnimationCamera AnimationCamera;
|
||||
typedef struct _AnimationCameraClass AnimationCameraClass;
|
||||
typedef struct _AnimationCameraPrivate AnimationCameraPrivate;
|
||||
|
||||
struct _AnimationCamera
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
AnimationCameraPrivate *priv;
|
||||
};
|
||||
|
||||
struct _AnimationCameraClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
|
||||
/* Signals */
|
||||
void (*camera_changed) (AnimationCamera *camera,
|
||||
gint position,
|
||||
gint duration);
|
||||
void (*keyframe_set) (AnimationCamera *camera,
|
||||
gint position);
|
||||
void (*keyframe_deleted) (AnimationCamera *camera,
|
||||
gint position);
|
||||
};
|
||||
|
||||
GType animation_camera_get_type (void) G_GNUC_CONST;
|
||||
|
||||
AnimationCamera * animation_camera_new (Animation *animation);
|
||||
|
||||
gboolean animation_camera_has_offset_keyframe (AnimationCamera *camera,
|
||||
gint position);
|
||||
gboolean animation_camera_has_zoom_keyframe (AnimationCamera *camera,
|
||||
gint position);
|
||||
|
||||
void animation_camera_set_offsets (AnimationCamera *camera,
|
||||
gint position,
|
||||
gint x,
|
||||
gint y);
|
||||
void animation_camera_zoom (AnimationCamera *camera,
|
||||
gint position,
|
||||
gdouble scale);
|
||||
void animation_camera_delete_offset_keyframe (AnimationCamera *camera,
|
||||
gint position);
|
||||
void animation_camera_delete_zoom_keyframe (AnimationCamera *camera,
|
||||
gint position);
|
||||
void animation_camera_preview_keyframe (AnimationCamera *camera,
|
||||
gint position,
|
||||
gint x,
|
||||
gint y,
|
||||
gdouble scale);
|
||||
void animation_camera_apply_preview (AnimationCamera *camera);
|
||||
void animation_camera_reset_preview (AnimationCamera *camera);
|
||||
|
||||
void animation_camera_get (AnimationCamera *camera,
|
||||
gint position,
|
||||
gint *x_offset,
|
||||
gint *y_offset,
|
||||
gdouble *scale);
|
||||
|
||||
#endif /* __ANIMATION_CAMERA_H__ */
|
1560
plug-ins/animation-play/core/animation-celanimation.c
Normal file
1560
plug-ins/animation-play/core/animation-celanimation.c
Normal file
File diff suppressed because it is too large
Load Diff
99
plug-ins/animation-play/core/animation-celanimation.h
Normal file
99
plug-ins/animation-play/core/animation-celanimation.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/* 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_layers (AnimationCelAnimation *animation,
|
||||
gint level,
|
||||
gint position,
|
||||
const GList *layers);
|
||||
const GList * animation_cel_animation_get_layers (AnimationCelAnimation *animation,
|
||||
gint level,
|
||||
gint position);
|
||||
|
||||
void animation_cel_animation_set_comment (AnimationCelAnimation *animation,
|
||||
gint position,
|
||||
const gchar *comment);
|
||||
const gchar * animation_cel_animation_get_comment (AnimationCelAnimation *animation,
|
||||
gint position);
|
||||
|
||||
void animation_cel_animation_set_duration (AnimationCelAnimation *animation,
|
||||
gint duration);
|
||||
|
||||
void animation_cel_animation_set_onion_skins (AnimationCelAnimation *animation,
|
||||
gint skins);
|
||||
gint animation_cel_animation_get_onion_skins (AnimationCelAnimation *animation);
|
||||
|
||||
GObject * animation_cel_animation_get_main_camera (AnimationCelAnimation *animation);
|
||||
|
||||
gint animation_cel_animation_get_levels (AnimationCelAnimation *animation);
|
||||
gint animation_cel_animation_level_up (AnimationCelAnimation *animation,
|
||||
gint level);
|
||||
gint animation_cel_animation_level_down (AnimationCelAnimation *animation,
|
||||
gint level);
|
||||
gboolean animation_cel_animation_level_delete (AnimationCelAnimation *animation,
|
||||
gint level);
|
||||
gboolean animation_cel_animation_level_add (AnimationCelAnimation *animation,
|
||||
gint level);
|
||||
|
||||
void animation_cel_animation_set_track_title (AnimationCelAnimation *animation,
|
||||
gint level,
|
||||
const gchar *title);
|
||||
const gchar * animation_cel_animation_get_track_title (AnimationCelAnimation *animation,
|
||||
gint level);
|
||||
|
||||
gboolean animation_cel_animation_cel_delete (AnimationCelAnimation *animation,
|
||||
gint level,
|
||||
gint position);
|
||||
gboolean animation_cel_animation_cel_add (AnimationCelAnimation *animation,
|
||||
gint level,
|
||||
gint position,
|
||||
gboolean dup_previous);
|
||||
|
||||
#endif /* __ANIMATION_CEL_ANIMATION_H__ */
|
933
plug-ins/animation-play/core/animation-playback.c
Normal file
933
plug-ins/animation-play/core/animation-playback.c
Normal file
@@ -0,0 +1,933 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation-playback.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 <string.h>
|
||||
|
||||
#include <libgimp/gimp.h>
|
||||
#include "libgimp/stdplugins-intl.h"
|
||||
|
||||
#include "animation.h"
|
||||
#include "animation-playback.h"
|
||||
#include "animation-renderer.h"
|
||||
|
||||
enum
|
||||
{
|
||||
START,
|
||||
STOP,
|
||||
RANGE,
|
||||
POSITION,
|
||||
LOW_FRAMERATE,
|
||||
PROXY_CHANGED,
|
||||
RENDERING,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_ANIMATION
|
||||
};
|
||||
|
||||
struct _AnimationPlaybackPrivate
|
||||
{
|
||||
Animation *animation;
|
||||
GObject *renderer;
|
||||
|
||||
/* State of the currently loaded playback. */
|
||||
gint position;
|
||||
/* Playback can be a subset of frames. */
|
||||
gint start;
|
||||
gint stop;
|
||||
gboolean stop_at_end;
|
||||
|
||||
gdouble proxy_ratio;
|
||||
|
||||
guint timer;
|
||||
gint64 start_time;
|
||||
gint64 frames_played;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
AnimationPlayback *playback;
|
||||
|
||||
gint level;
|
||||
} ParseStatus;
|
||||
|
||||
static void animation_playback_finalize (GObject *object);
|
||||
static void animation_playback_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec);
|
||||
static void animation_playback_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec);
|
||||
|
||||
static void on_duration_changed (Animation *animation,
|
||||
gint duration,
|
||||
AnimationPlayback *playback);
|
||||
static void on_cache_updated (AnimationRenderer *renderer,
|
||||
gint position,
|
||||
AnimationPlayback *playback);
|
||||
static void on_rendering (AnimationRenderer *renderer,
|
||||
gboolean rendering,
|
||||
AnimationPlayback *playback);
|
||||
|
||||
/* Timer callback for playback. */
|
||||
static gboolean animation_playback_advance_frame_callback (AnimationPlayback *playback);
|
||||
|
||||
/* XML parsing */
|
||||
static void animation_playback_start_element (GMarkupParseContext *context,
|
||||
const gchar *element_name,
|
||||
const gchar **attribute_names,
|
||||
const gchar **attribute_values,
|
||||
gpointer user_data,
|
||||
GError **error);
|
||||
static void animation_playback_end_element (GMarkupParseContext *context,
|
||||
const gchar *element_name,
|
||||
gpointer user_data,
|
||||
GError **error);
|
||||
|
||||
|
||||
G_DEFINE_TYPE (AnimationPlayback, animation_playback, G_TYPE_OBJECT)
|
||||
|
||||
#define parent_class animation_playback_parent_class
|
||||
|
||||
static guint animation_playback_signals[LAST_SIGNAL] = { 0 };
|
||||
|
||||
static void
|
||||
animation_playback_class_init (AnimationPlaybackClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
/**
|
||||
* AnimationPlayback::start:
|
||||
* @playback: the #AnimationPlayback.
|
||||
*
|
||||
* The @playback starts to play.
|
||||
*/
|
||||
animation_playback_signals[START] =
|
||||
g_signal_new ("start",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationPlaybackClass, start),
|
||||
NULL, NULL,
|
||||
g_cclosure_marshal_VOID__VOID,
|
||||
G_TYPE_NONE,
|
||||
0);
|
||||
/**
|
||||
* AnimationPlayback::stop:
|
||||
* @playback: the #AnimationPlayback.
|
||||
*
|
||||
* The @playback stops playing.
|
||||
*/
|
||||
animation_playback_signals[STOP] =
|
||||
g_signal_new ("stop",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationPlaybackClass, stop),
|
||||
NULL, NULL,
|
||||
g_cclosure_marshal_VOID__VOID,
|
||||
G_TYPE_NONE,
|
||||
0);
|
||||
/**
|
||||
* AnimationPlayback::range:
|
||||
* @playback: the #AnimationPlayback.
|
||||
* @start: the playback start frame.
|
||||
* @stop: the playback last frame.
|
||||
*
|
||||
* The ::range signal is emitted when the playback range is
|
||||
* updated.
|
||||
*/
|
||||
animation_playback_signals[RANGE] =
|
||||
g_signal_new ("range",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationPlaybackClass, range),
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
2,
|
||||
G_TYPE_INT,
|
||||
G_TYPE_INT);
|
||||
/**
|
||||
* AnimationPlayback::position:
|
||||
* @playback: the #AnimationPlayback.
|
||||
* @position: current position to be displayed.
|
||||
* @buffer: the #GeglBuffer for the frame at @position.
|
||||
* @must_draw_null: meaning of a %NULL @buffer.
|
||||
* %TRUE means we have to draw an empty frame.
|
||||
* %FALSE means the new frame is same as the current frame.
|
||||
*
|
||||
* This signal indicates that playback position has changed so that
|
||||
* the GUI can display it and process other updates.
|
||||
*/
|
||||
animation_playback_signals[POSITION] =
|
||||
g_signal_new ("position",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationPlaybackClass, position),
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
3,
|
||||
G_TYPE_INT,
|
||||
GEGL_TYPE_BUFFER,
|
||||
G_TYPE_BOOLEAN);
|
||||
/**
|
||||
* AnimationPlayback::low-framerate:
|
||||
* @playback: the #AnimationPlayback.
|
||||
* @actual_fps: the current playback framerate in fps.
|
||||
*
|
||||
* The ::low-framerate signal is emitted when the playback framerate
|
||||
* is lower than expected. It is also emitted once when the framerate
|
||||
* comes back to acceptable rate.
|
||||
*/
|
||||
animation_playback_signals[LOW_FRAMERATE] =
|
||||
g_signal_new ("low-framerate",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationPlaybackClass, low_framerate),
|
||||
NULL, NULL,
|
||||
g_cclosure_marshal_VOID__DOUBLE,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_DOUBLE);
|
||||
|
||||
/**
|
||||
* AnimationPlayback::proxy:
|
||||
* @playback: the #AnimationPlayback.
|
||||
* @ratio: the current proxy ratio [0-1.0].
|
||||
*
|
||||
* The ::proxy signal is emitted to announce a change of proxy size.
|
||||
*/
|
||||
animation_playback_signals[PROXY_CHANGED] =
|
||||
g_signal_new ("proxy-changed",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationPlaybackClass, proxy_changed),
|
||||
NULL, NULL,
|
||||
g_cclosure_marshal_VOID__DOUBLE,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_DOUBLE);
|
||||
|
||||
/**
|
||||
* AnimationPlayback::rendering:
|
||||
* @playback: the #AnimationPlayback.
|
||||
* @has_queue: whether there is more to render.
|
||||
*
|
||||
* The ::rendering signal will be emitted when the renderer has queued
|
||||
* frames, and a last time with @has_queue as #TRUE when all is
|
||||
* rendered. It mostly passes along AnimationRenderer::rendering
|
||||
* signal, since only @playback has access to the renderer object.
|
||||
*/
|
||||
animation_playback_signals[RENDERING] =
|
||||
g_signal_new ("rendering",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationRendererClass, rendering),
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_BOOLEAN);
|
||||
|
||||
object_class->finalize = animation_playback_finalize;
|
||||
object_class->set_property = animation_playback_set_property;
|
||||
object_class->get_property = animation_playback_get_property;
|
||||
|
||||
/**
|
||||
* AnimationPlayback:animation:
|
||||
*
|
||||
* The associated #Animation.
|
||||
*/
|
||||
g_object_class_install_property (object_class, PROP_ANIMATION,
|
||||
g_param_spec_object ("animation",
|
||||
NULL, NULL,
|
||||
ANIMATION_TYPE_ANIMATION,
|
||||
G_PARAM_READWRITE));
|
||||
|
||||
g_type_class_add_private (klass, sizeof (AnimationPlaybackPrivate));
|
||||
}
|
||||
|
||||
static void
|
||||
animation_playback_init (AnimationPlayback *playback)
|
||||
{
|
||||
playback->priv = G_TYPE_INSTANCE_GET_PRIVATE (playback,
|
||||
ANIMATION_TYPE_PLAYBACK,
|
||||
AnimationPlaybackPrivate);
|
||||
playback->priv->proxy_ratio = 1.0;
|
||||
}
|
||||
|
||||
/************ Public Functions ****************/
|
||||
|
||||
AnimationPlayback *
|
||||
animation_playback_new (void)
|
||||
{
|
||||
AnimationPlayback *playback;
|
||||
|
||||
playback = g_object_new (ANIMATION_TYPE_PLAYBACK,
|
||||
NULL);
|
||||
|
||||
return playback;
|
||||
}
|
||||
|
||||
gchar *
|
||||
animation_playback_serialize (AnimationPlayback *playback)
|
||||
{
|
||||
gchar *xml;
|
||||
gchar proxy[6];
|
||||
|
||||
/* Make sure to have a locale-independent string. Also no need to have
|
||||
* useless precision. This should give 3 digits after the decimal point.
|
||||
* More than enough.
|
||||
*/
|
||||
g_ascii_dtostr ((gchar*) &proxy, 6, playback->priv->proxy_ratio);
|
||||
xml = g_strdup_printf ("<playback position=\"%d\" "
|
||||
"start=\"%d\" stop=\"%d\" proxy=\"%s\"/>",
|
||||
playback->priv->position,
|
||||
playback->priv->start,
|
||||
playback->priv->stop,
|
||||
proxy);
|
||||
return xml;
|
||||
}
|
||||
|
||||
void
|
||||
animation_playback_set_animation (AnimationPlayback *playback,
|
||||
Animation *animation,
|
||||
const gchar *xml)
|
||||
{
|
||||
g_object_set (playback,
|
||||
"animation", animation,
|
||||
NULL);
|
||||
|
||||
if (xml)
|
||||
{
|
||||
/* Reset to last known playback status. */
|
||||
const GMarkupParser markup_parser =
|
||||
{
|
||||
animation_playback_start_element,
|
||||
animation_playback_end_element,
|
||||
NULL, /* text */
|
||||
NULL, /* passthrough */
|
||||
NULL /* error */
|
||||
};
|
||||
GMarkupParseContext *context;
|
||||
ParseStatus status = { 0, };
|
||||
GError *error = NULL;
|
||||
|
||||
status.playback = playback;
|
||||
status.level = 0;
|
||||
context = g_markup_parse_context_new (&markup_parser,
|
||||
0, &status, NULL);
|
||||
g_markup_parse_context_parse (context, xml, strlen (xml), &error);
|
||||
if (error == NULL)
|
||||
g_markup_parse_context_end_parse (context, &error);
|
||||
g_markup_parse_context_free (context);
|
||||
if (error)
|
||||
g_warning ("Error parsing XML: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
}
|
||||
|
||||
Animation *
|
||||
animation_playback_get_animation (AnimationPlayback *playback)
|
||||
{
|
||||
return playback->priv->animation;
|
||||
}
|
||||
|
||||
gint
|
||||
animation_playback_get_position (AnimationPlayback *playback)
|
||||
{
|
||||
return playback->priv->position;
|
||||
}
|
||||
|
||||
GeglBuffer *
|
||||
animation_playback_get_buffer (AnimationPlayback *playback,
|
||||
gint position)
|
||||
{
|
||||
AnimationRenderer *renderer;
|
||||
|
||||
renderer = ANIMATION_RENDERER (playback->priv->renderer);
|
||||
return animation_renderer_get_buffer (renderer, position);
|
||||
}
|
||||
|
||||
gboolean
|
||||
animation_playback_is_playing (AnimationPlayback *playback)
|
||||
{
|
||||
return (playback->priv->timer != 0);
|
||||
}
|
||||
|
||||
void
|
||||
animation_playback_play (AnimationPlayback *playback)
|
||||
{
|
||||
gint duration;
|
||||
|
||||
duration = (gint) (1000.0 /
|
||||
animation_get_framerate (playback->priv->animation));
|
||||
|
||||
if (playback->priv->timer)
|
||||
{
|
||||
/* It means we are already playing, so we should not need to play
|
||||
* again.
|
||||
* Still be liberal and simply remove the timer before creating a
|
||||
* new one. */
|
||||
g_source_remove (playback->priv->timer);
|
||||
g_signal_emit (playback, animation_playback_signals[STOP], 0);
|
||||
}
|
||||
|
||||
playback->priv->start_time = g_get_monotonic_time ();
|
||||
playback->priv->frames_played = 1;
|
||||
|
||||
playback->priv->timer = g_timeout_add ((guint) duration,
|
||||
(GSourceFunc) animation_playback_advance_frame_callback,
|
||||
playback);
|
||||
g_signal_emit (playback, animation_playback_signals[START], 0);
|
||||
}
|
||||
|
||||
void
|
||||
animation_playback_stop (AnimationPlayback *playback)
|
||||
{
|
||||
if (playback->priv->timer)
|
||||
{
|
||||
/* Stop playing by removing any playback timer. */
|
||||
g_source_remove (playback->priv->timer);
|
||||
playback->priv->timer = 0;
|
||||
g_signal_emit (playback, animation_playback_signals[STOP], 0);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
animation_playback_next (AnimationPlayback *playback)
|
||||
{
|
||||
AnimationRenderer *renderer;
|
||||
GeglBuffer *buffer = NULL;
|
||||
gint previous_pos = playback->priv->position;
|
||||
gboolean identical;
|
||||
|
||||
if (! playback->priv->animation)
|
||||
return;
|
||||
|
||||
playback->priv->position = playback->priv->start +
|
||||
((playback->priv->position - playback->priv->start + 1) %
|
||||
(playback->priv->stop - playback->priv->start + 1));
|
||||
|
||||
renderer = ANIMATION_RENDERER (playback->priv->renderer);
|
||||
identical = animation_renderer_identical (renderer,
|
||||
previous_pos,
|
||||
playback->priv->position);
|
||||
if (! identical)
|
||||
{
|
||||
buffer = animation_renderer_get_buffer (renderer,
|
||||
playback->priv->position);
|
||||
}
|
||||
g_signal_emit (playback, animation_playback_signals[POSITION], 0,
|
||||
playback->priv->position, buffer, ! identical);
|
||||
if (buffer != NULL)
|
||||
{
|
||||
g_object_unref (buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
animation_playback_prev (AnimationPlayback *playback)
|
||||
{
|
||||
AnimationRenderer *renderer;
|
||||
GeglBuffer *buffer = NULL;
|
||||
gint prev_pos = playback->priv->position;
|
||||
gboolean identical;
|
||||
|
||||
if (! playback->priv->animation)
|
||||
return;
|
||||
|
||||
if (playback->priv->position == playback->priv->start)
|
||||
{
|
||||
playback->priv->position = animation_playback_get_stop (playback);
|
||||
}
|
||||
else
|
||||
{
|
||||
--playback->priv->position;
|
||||
}
|
||||
|
||||
renderer = ANIMATION_RENDERER (playback->priv->renderer);
|
||||
identical = animation_renderer_identical (renderer,
|
||||
prev_pos,
|
||||
playback->priv->position);
|
||||
if (! identical)
|
||||
{
|
||||
buffer = animation_renderer_get_buffer (renderer, playback->priv->position);
|
||||
}
|
||||
g_signal_emit (playback, animation_playback_signals[POSITION], 0,
|
||||
playback->priv->position, buffer, ! identical);
|
||||
if (buffer)
|
||||
g_object_unref (buffer);
|
||||
}
|
||||
|
||||
void
|
||||
animation_playback_jump (AnimationPlayback *playback,
|
||||
gint index)
|
||||
{
|
||||
AnimationRenderer *renderer;
|
||||
GeglBuffer *buffer = NULL;
|
||||
gint prev_pos = playback->priv->position;
|
||||
gboolean identical;
|
||||
|
||||
if (! playback->priv->animation)
|
||||
return;
|
||||
|
||||
if (index < playback->priv->start ||
|
||||
index > playback->priv->stop)
|
||||
return;
|
||||
else
|
||||
playback->priv->position = index;
|
||||
|
||||
renderer = ANIMATION_RENDERER (playback->priv->renderer);
|
||||
identical = animation_renderer_identical (renderer,
|
||||
prev_pos,
|
||||
playback->priv->position);
|
||||
if (! identical)
|
||||
{
|
||||
buffer = animation_renderer_get_buffer (renderer, playback->priv->position);
|
||||
}
|
||||
g_signal_emit (playback, animation_playback_signals[POSITION], 0,
|
||||
playback->priv->position, buffer, ! identical);
|
||||
if (buffer)
|
||||
g_object_unref (buffer);
|
||||
}
|
||||
|
||||
void
|
||||
animation_playback_set_start (AnimationPlayback *playback,
|
||||
gint index)
|
||||
{
|
||||
gint duration;
|
||||
|
||||
if (! playback->priv->animation)
|
||||
return;
|
||||
|
||||
duration = animation_get_duration (playback->priv->animation);
|
||||
|
||||
if (index < 0 ||
|
||||
index >= duration)
|
||||
{
|
||||
playback->priv->start = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
playback->priv->start = index;
|
||||
}
|
||||
if (playback->priv->stop < playback->priv->start)
|
||||
{
|
||||
playback->priv->stop = duration - 1;
|
||||
playback->priv->stop_at_end = TRUE;
|
||||
}
|
||||
|
||||
g_signal_emit (playback, animation_playback_signals[RANGE], 0,
|
||||
playback->priv->start, playback->priv->stop);
|
||||
|
||||
if (playback->priv->position < playback->priv->start ||
|
||||
playback->priv->position > playback->priv->stop)
|
||||
{
|
||||
animation_playback_jump (playback, playback->priv->start);
|
||||
}
|
||||
}
|
||||
|
||||
gint
|
||||
animation_playback_get_start (AnimationPlayback *playback)
|
||||
{
|
||||
return playback->priv->start;
|
||||
}
|
||||
|
||||
void
|
||||
animation_playback_set_stop (AnimationPlayback *playback,
|
||||
gint index)
|
||||
{
|
||||
gint duration;
|
||||
|
||||
if (! playback->priv->animation)
|
||||
return;
|
||||
|
||||
duration = animation_get_duration (playback->priv->animation);
|
||||
|
||||
if (index < 0 ||
|
||||
index >= duration)
|
||||
{
|
||||
playback->priv->stop = duration - 1;
|
||||
playback->priv->stop_at_end = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
playback->priv->stop = index;
|
||||
|
||||
if (index == duration - 1)
|
||||
playback->priv->stop_at_end = TRUE;
|
||||
}
|
||||
if (playback->priv->stop < playback->priv->start)
|
||||
{
|
||||
playback->priv->start = 0;
|
||||
}
|
||||
|
||||
g_signal_emit (playback, animation_playback_signals[RANGE], 0,
|
||||
playback->priv->start, playback->priv->stop);
|
||||
|
||||
if (playback->priv->position < playback->priv->start ||
|
||||
playback->priv->position > playback->priv->stop)
|
||||
{
|
||||
animation_playback_jump (playback, playback->priv->start);
|
||||
}
|
||||
}
|
||||
|
||||
gint
|
||||
animation_playback_get_stop (AnimationPlayback *playback)
|
||||
{
|
||||
return playback->priv->stop;
|
||||
}
|
||||
|
||||
void
|
||||
animation_playback_get_size (AnimationPlayback *playback,
|
||||
gint *width,
|
||||
gint *height)
|
||||
{
|
||||
animation_get_size (playback->priv->animation,
|
||||
width, height);
|
||||
|
||||
/* Apply proxy ratio. */
|
||||
*width *= playback->priv->proxy_ratio;
|
||||
*height *= playback->priv->proxy_ratio;
|
||||
}
|
||||
|
||||
void
|
||||
animation_playback_set_proxy (AnimationPlayback *playback,
|
||||
gdouble ratio)
|
||||
{
|
||||
g_return_if_fail (ratio > 0.0 && ratio <= 1.0);
|
||||
|
||||
if (playback->priv->proxy_ratio != ratio)
|
||||
{
|
||||
playback->priv->proxy_ratio = ratio;
|
||||
g_signal_emit (playback,
|
||||
animation_playback_signals[PROXY_CHANGED],
|
||||
0, ratio);
|
||||
}
|
||||
}
|
||||
|
||||
gdouble
|
||||
animation_playback_get_proxy (AnimationPlayback *playback)
|
||||
{
|
||||
return playback->priv->proxy_ratio;
|
||||
}
|
||||
|
||||
/************ Private Functions ****************/
|
||||
|
||||
static void
|
||||
animation_playback_finalize (GObject *object)
|
||||
{
|
||||
AnimationPlayback *playback = ANIMATION_PLAYBACK (object);
|
||||
|
||||
g_object_unref (playback->priv->renderer);
|
||||
if (playback->priv->animation)
|
||||
g_object_unref (playback->priv->animation);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_playback_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
AnimationPlayback *playback = ANIMATION_PLAYBACK (object);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_ANIMATION:
|
||||
{
|
||||
Animation *animation;
|
||||
|
||||
if (playback->priv->renderer)
|
||||
g_object_unref (playback->priv->renderer);
|
||||
if (playback->priv->animation)
|
||||
g_object_unref (playback->priv->animation);
|
||||
|
||||
animation = g_value_dup_object (value);
|
||||
playback->priv->animation = animation;
|
||||
playback->priv->renderer = NULL;
|
||||
|
||||
if (! animation)
|
||||
break;
|
||||
|
||||
/* Default playback is the full range of frames. */
|
||||
playback->priv->position = 0;
|
||||
playback->priv->start = 0;
|
||||
playback->priv->stop = animation_get_duration (animation) - 1;
|
||||
playback->priv->stop_at_end = TRUE;
|
||||
|
||||
g_signal_connect (animation, "duration-changed",
|
||||
G_CALLBACK (on_duration_changed), playback);
|
||||
|
||||
playback->priv->renderer = animation_renderer_new (object);
|
||||
g_signal_connect (playback->priv->renderer, "cache-updated",
|
||||
G_CALLBACK (on_cache_updated), playback);
|
||||
g_signal_connect (playback->priv->renderer, "rendering",
|
||||
G_CALLBACK (on_rendering), playback);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
animation_playback_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
AnimationPlayback *playback = ANIMATION_PLAYBACK (object);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_ANIMATION:
|
||||
g_value_set_object (value, playback->priv->animation);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_duration_changed (Animation *animation,
|
||||
gint duration,
|
||||
AnimationPlayback *playback)
|
||||
{
|
||||
if (! playback->priv->animation)
|
||||
return;
|
||||
|
||||
if (playback->priv->stop >= duration ||
|
||||
playback->priv->stop_at_end)
|
||||
{
|
||||
playback->priv->stop = duration - 1;
|
||||
playback->priv->stop_at_end = TRUE;
|
||||
}
|
||||
if (playback->priv->start >= duration ||
|
||||
playback->priv->start > playback->priv->stop)
|
||||
{
|
||||
playback->priv->start = 0;
|
||||
}
|
||||
|
||||
if (playback->priv->position < playback->priv->start ||
|
||||
playback->priv->position > playback->priv->stop)
|
||||
{
|
||||
playback->priv->position = playback->priv->start;
|
||||
}
|
||||
g_signal_emit (playback, animation_playback_signals[RANGE], 0,
|
||||
playback->priv->start, playback->priv->stop);
|
||||
}
|
||||
|
||||
static void
|
||||
on_cache_updated (AnimationRenderer *renderer,
|
||||
gint position,
|
||||
AnimationPlayback *playback)
|
||||
{
|
||||
if (animation_playback_get_position (playback) == position)
|
||||
{
|
||||
AnimationRenderer *renderer;
|
||||
GeglBuffer *buffer;
|
||||
|
||||
renderer = ANIMATION_RENDERER (playback->priv->renderer);
|
||||
buffer = animation_renderer_get_buffer (renderer, position);
|
||||
g_signal_emit (playback, animation_playback_signals[POSITION], 0,
|
||||
position, buffer, TRUE);
|
||||
if (buffer)
|
||||
{
|
||||
g_object_unref (buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_rendering (AnimationRenderer *renderer,
|
||||
gboolean rendering,
|
||||
AnimationPlayback *playback)
|
||||
{
|
||||
/* Just transform the renderer's "rendering" signal into a playback's
|
||||
* one to pass the information along to the GUI.
|
||||
*/
|
||||
g_signal_emit (playback, animation_playback_signals[RENDERING], 0,
|
||||
rendering);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
animation_playback_advance_frame_callback (AnimationPlayback *playback)
|
||||
{
|
||||
gdouble framerate;
|
||||
gint64 duration;
|
||||
gint64 duration_since_start;
|
||||
static gboolean prev_low_framerate = FALSE;
|
||||
|
||||
framerate = animation_get_framerate (playback->priv->animation);
|
||||
|
||||
animation_playback_next (playback);
|
||||
duration = (gint) (1000.0 / framerate);
|
||||
|
||||
duration_since_start = (g_get_monotonic_time () - playback->priv->start_time) / 1000;
|
||||
duration = duration - (duration_since_start - playback->priv->frames_played * duration);
|
||||
|
||||
if (duration < 1)
|
||||
{
|
||||
if (prev_low_framerate)
|
||||
{
|
||||
/* Let's only warn the user for several subsequent slow frames. */
|
||||
gdouble real_framerate;
|
||||
|
||||
real_framerate = (gdouble) playback->priv->frames_played * 1000.0 / duration_since_start;
|
||||
|
||||
if (real_framerate < framerate)
|
||||
g_signal_emit (playback, animation_playback_signals[LOW_FRAMERATE], 0,
|
||||
real_framerate);
|
||||
}
|
||||
duration = 1;
|
||||
prev_low_framerate = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (prev_low_framerate)
|
||||
{
|
||||
/* Let's reset framerate warning. */
|
||||
g_signal_emit (playback, animation_playback_signals[LOW_FRAMERATE], 0,
|
||||
framerate);
|
||||
}
|
||||
prev_low_framerate = FALSE;
|
||||
}
|
||||
playback->priv->frames_played++;
|
||||
|
||||
playback->priv->timer = g_timeout_add ((guint) duration,
|
||||
(GSourceFunc) animation_playback_advance_frame_callback,
|
||||
(AnimationPlayback *) playback);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
animation_playback_start_element (GMarkupParseContext *context,
|
||||
const gchar *element_name,
|
||||
const gchar **attribute_names,
|
||||
const gchar **attribute_values,
|
||||
gpointer user_data,
|
||||
GError **error)
|
||||
{
|
||||
const gchar **names = attribute_names;
|
||||
const gchar **values = attribute_values;
|
||||
ParseStatus *status = (ParseStatus *) user_data;
|
||||
AnimationPlayback *playback = status->playback;
|
||||
|
||||
if (status->level == 1 && g_strcmp0 (element_name, "playback") == 0)
|
||||
{
|
||||
gint duration;
|
||||
|
||||
duration = animation_get_duration (playback->priv->animation);
|
||||
while (*names && *values)
|
||||
{
|
||||
if (strcmp (*names, "position") == 0 && **values)
|
||||
{
|
||||
gint position = g_ascii_strtoll (*values, NULL, 10);
|
||||
|
||||
if (position >= duration)
|
||||
{
|
||||
g_set_error (error, 0, 0,
|
||||
_("Playback position %d out of range [0, %d]."),
|
||||
position, duration - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
playback->priv->position = position;
|
||||
}
|
||||
}
|
||||
else if (strcmp (*names, "start") == 0 && **values)
|
||||
{
|
||||
gint start = g_ascii_strtoll (*values, NULL, 10);
|
||||
|
||||
if (start >= duration)
|
||||
{
|
||||
g_set_error (error, 0, 0,
|
||||
_("Playback start %d out of range [0, %d]."),
|
||||
start, duration - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
playback->priv->start = start;
|
||||
}
|
||||
}
|
||||
else if (strcmp (*names, "stop") == 0 && **values)
|
||||
{
|
||||
gint stop = g_ascii_strtoll (*values, NULL, 10);
|
||||
|
||||
if (stop >= duration)
|
||||
{
|
||||
g_set_error (error, 0, 0,
|
||||
_("Playback stop %d out of range [0, %d]."),
|
||||
stop, duration - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
playback->priv->stop = stop;
|
||||
playback->priv->stop_at_end = (stop == duration - 1);
|
||||
}
|
||||
}
|
||||
else if (strcmp (*names, "proxy") == 0 && **values)
|
||||
{
|
||||
gdouble ratio = g_ascii_strtod (*values, NULL);
|
||||
animation_playback_set_proxy (playback, ratio);
|
||||
}
|
||||
|
||||
names++;
|
||||
values++;
|
||||
}
|
||||
if (playback->priv->stop < playback->priv->start)
|
||||
{
|
||||
playback->priv->stop = duration - 1;
|
||||
playback->priv->stop_at_end = TRUE;
|
||||
}
|
||||
|
||||
if (playback->priv->position < playback->priv->start ||
|
||||
playback->priv->position > playback->priv->stop)
|
||||
{
|
||||
playback->priv->position = playback->priv->start;
|
||||
}
|
||||
}
|
||||
status->level++;
|
||||
}
|
||||
|
||||
static void
|
||||
animation_playback_end_element (GMarkupParseContext *context,
|
||||
const gchar *element_name,
|
||||
gpointer user_data,
|
||||
GError **error)
|
||||
{
|
||||
((ParseStatus *) user_data)->level--;
|
||||
}
|
101
plug-ins/animation-play/core/animation-playback.h
Normal file
101
plug-ins/animation-play/core/animation-playback.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation_playback.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_PLAYBACK_H__
|
||||
#define __ANIMATION_PLAYBACK_H__
|
||||
|
||||
#define ANIMATION_TYPE_PLAYBACK (animation_playback_get_type ())
|
||||
#define ANIMATION_PLAYBACK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_PLAYBACK, AnimationPlayback))
|
||||
#define ANIMATION_PLAYBACK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_PLAYBACK, AnimationPlaybackClass))
|
||||
#define ANIMATION_IS_PLAYBACK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_PLAYBACK))
|
||||
#define ANIMATION_IS_PLAYBACK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_PLAYBACK))
|
||||
#define ANIMATION_PLAYBACK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_PLAYBACK, AnimationPlaybackClass))
|
||||
|
||||
typedef struct _AnimationPlayback AnimationPlayback;
|
||||
typedef struct _AnimationPlaybackClass AnimationPlaybackClass;
|
||||
typedef struct _AnimationPlaybackPrivate AnimationPlaybackPrivate;
|
||||
|
||||
struct _AnimationPlayback
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
AnimationPlaybackPrivate *priv;
|
||||
};
|
||||
|
||||
struct _AnimationPlaybackClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
|
||||
/* Signals */
|
||||
void (*start) (AnimationPlayback *playback);
|
||||
void (*stop) (AnimationPlayback *playback);
|
||||
void (*range) (AnimationPlayback *playback,
|
||||
gint start,
|
||||
gint stop);
|
||||
void (*position) (AnimationPlayback *playback,
|
||||
gint position,
|
||||
GeglBuffer *buffer,
|
||||
gboolean must_draw_null);
|
||||
void (*low_framerate) (AnimationPlayback *playback,
|
||||
gdouble actual_fps);
|
||||
void (*proxy_changed) (AnimationPlayback *animation,
|
||||
gdouble ratio);
|
||||
void (*rendering) (AnimationPlayback *renderer,
|
||||
gboolean has_render_queue);
|
||||
};
|
||||
|
||||
GType animation_playback_get_type (void);
|
||||
|
||||
AnimationPlayback * animation_playback_new (void);
|
||||
gchar * animation_playback_serialize (AnimationPlayback *playback);
|
||||
|
||||
void animation_playback_set_animation (AnimationPlayback *playback,
|
||||
Animation *animation,
|
||||
const gchar *xml);
|
||||
Animation * animation_playback_get_animation (AnimationPlayback *playback);
|
||||
|
||||
gint animation_playback_get_position (AnimationPlayback *playback);
|
||||
GeglBuffer * animation_playback_get_buffer (AnimationPlayback *playback,
|
||||
gint position);
|
||||
|
||||
gboolean animation_playback_is_playing (AnimationPlayback *playback);
|
||||
void animation_playback_play (AnimationPlayback *playback);
|
||||
void animation_playback_stop (AnimationPlayback *playback);
|
||||
void animation_playback_next (AnimationPlayback *playback);
|
||||
void animation_playback_prev (AnimationPlayback *playback);
|
||||
void animation_playback_jump (AnimationPlayback *playback,
|
||||
gint index);
|
||||
|
||||
void animation_playback_set_start (AnimationPlayback *playback,
|
||||
gint index);
|
||||
gint animation_playback_get_start (AnimationPlayback *playback);
|
||||
|
||||
void animation_playback_set_stop (AnimationPlayback *playback,
|
||||
gint index);
|
||||
gint animation_playback_get_stop (AnimationPlayback *playback);
|
||||
|
||||
void animation_playback_get_size (AnimationPlayback *playback,
|
||||
gint *width,
|
||||
gint *height);
|
||||
void animation_playback_set_proxy (AnimationPlayback *playback,
|
||||
gdouble ratio);
|
||||
gdouble animation_playback_get_proxy (AnimationPlayback *playback);
|
||||
|
||||
#endif /* __ANIMATION_PLAYBACK_H__ */
|
717
plug-ins/animation-play/core/animation-renderer.c
Normal file
717
plug-ins/animation-play/core/animation-renderer.c
Normal file
@@ -0,0 +1,717 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation-renderer.c
|
||||
* Copyright (C) 2017 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.h"
|
||||
#include "animation-playback.h"
|
||||
#include "animation-renderer.h"
|
||||
|
||||
enum
|
||||
{
|
||||
CACHE_UPDATED,
|
||||
RENDERING,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_PLAYBACK
|
||||
};
|
||||
|
||||
struct _AnimationRendererPrivate
|
||||
{
|
||||
AnimationPlayback *playback;
|
||||
|
||||
GAsyncQueue *queue;
|
||||
|
||||
GAsyncQueue *ack_queue;
|
||||
guint idle_id;
|
||||
|
||||
/* Frames are cached as GEGL buffers. */
|
||||
GMutex lock;
|
||||
GeglBuffer **cache;
|
||||
gchar **hashes;
|
||||
GHashTable *cache_table;
|
||||
gint cache_size;
|
||||
|
||||
GThread *queue_thread;
|
||||
};
|
||||
|
||||
static void animation_renderer_finalize (GObject *object);
|
||||
static void animation_renderer_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec);
|
||||
static void animation_renderer_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec);
|
||||
|
||||
static gpointer animation_renderer_process_queue (AnimationRenderer *renderer);
|
||||
static gboolean animation_renderer_idle_update (AnimationRenderer *renderer);
|
||||
|
||||
static void on_proxy_changed (AnimationPlayback *animation,
|
||||
gdouble ratio,
|
||||
AnimationRenderer *renderer);
|
||||
static void on_playback_position (AnimationPlayback *animation,
|
||||
gint frame_number,
|
||||
GeglBuffer *buffer,
|
||||
gboolean must_draw_null,
|
||||
AnimationRenderer *renderer);
|
||||
|
||||
static void on_size_changed (Animation *animation,
|
||||
gint width,
|
||||
gint height,
|
||||
AnimationRenderer *renderer);
|
||||
static void on_frames_changed (Animation *animation,
|
||||
gint position,
|
||||
gint length,
|
||||
AnimationRenderer *renderer);
|
||||
static void on_duration_changed (Animation *animation,
|
||||
gint duration,
|
||||
AnimationRenderer *renderer);
|
||||
static void on_animation_loaded (Animation *animation,
|
||||
AnimationRenderer *renderer);
|
||||
|
||||
G_DEFINE_TYPE (AnimationRenderer, animation_renderer, G_TYPE_OBJECT)
|
||||
|
||||
#define parent_class animation_renderer_parent_class
|
||||
|
||||
static guint signals[LAST_SIGNAL] = { 0 };
|
||||
|
||||
static void
|
||||
animation_renderer_class_init (AnimationRendererClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
/**
|
||||
* AnimationRenderer::cache-updated:
|
||||
* @renderer: the #AnimationRenderer.
|
||||
* @position: the frame position whose cache was updated.
|
||||
*
|
||||
* The ::cache-updated signal will be emitted when the contents
|
||||
* of frame at @position changes.
|
||||
*/
|
||||
signals[CACHE_UPDATED] =
|
||||
g_signal_new ("cache-updated",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationRendererClass, cache_updated),
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_INT);
|
||||
/**
|
||||
* AnimationRenderer::rendering:
|
||||
* @renderer: the #AnimationRenderer.
|
||||
* @has_queue: whether there is more to render.
|
||||
*
|
||||
* The ::rendering signal will be emitted when the renderer has queued
|
||||
* frames, and a last time with @has_queue as #TRUE when all is
|
||||
* rendered.
|
||||
*/
|
||||
signals[RENDERING] =
|
||||
g_signal_new ("rendering",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationRendererClass, rendering),
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_BOOLEAN);
|
||||
|
||||
object_class->finalize = animation_renderer_finalize;
|
||||
object_class->set_property = animation_renderer_set_property;
|
||||
object_class->get_property = animation_renderer_get_property;
|
||||
|
||||
/**
|
||||
* AnimationRenderer:animation:
|
||||
*
|
||||
* The associated #Animation.
|
||||
*/
|
||||
g_object_class_install_property (object_class, PROP_PLAYBACK,
|
||||
g_param_spec_object ("playback",
|
||||
NULL, NULL,
|
||||
ANIMATION_TYPE_PLAYBACK,
|
||||
G_PARAM_READWRITE |
|
||||
G_PARAM_CONSTRUCT_ONLY));
|
||||
|
||||
g_type_class_add_private (klass, sizeof (AnimationRendererPrivate));
|
||||
}
|
||||
|
||||
static void
|
||||
animation_renderer_init (AnimationRenderer *renderer)
|
||||
{
|
||||
renderer->priv = G_TYPE_INSTANCE_GET_PRIVATE (renderer,
|
||||
ANIMATION_TYPE_RENDERER,
|
||||
AnimationRendererPrivate);
|
||||
g_mutex_init (&(renderer->priv->lock));
|
||||
renderer->priv->cache_table = g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
(GDestroyNotify) g_free,
|
||||
(GDestroyNotify) g_weak_ref_clear);
|
||||
renderer->priv->queue = g_async_queue_new ();
|
||||
renderer->priv->ack_queue = g_async_queue_new ();
|
||||
}
|
||||
|
||||
static void
|
||||
animation_renderer_finalize (GObject *object)
|
||||
{
|
||||
AnimationRenderer *renderer = ANIMATION_RENDERER (object);
|
||||
Animation *animation;
|
||||
gint i;
|
||||
|
||||
animation = animation_playback_get_animation (renderer->priv->playback);
|
||||
|
||||
/* Stop the thread. */
|
||||
g_async_queue_push_front (renderer->priv->queue,
|
||||
GINT_TO_POINTER (- 1));
|
||||
g_thread_join (renderer->priv->queue_thread);
|
||||
g_thread_unref (renderer->priv->queue_thread);
|
||||
|
||||
/* Clean remaining data. */
|
||||
if (renderer->priv->idle_id)
|
||||
g_source_remove (renderer->priv->idle_id);
|
||||
renderer->priv->idle_id = 0;
|
||||
g_mutex_lock (&renderer->priv->lock);
|
||||
for (i = 0; i < animation_get_duration (animation); i++)
|
||||
{
|
||||
if (renderer->priv->cache[i])
|
||||
g_object_unref (renderer->priv->cache[i]);
|
||||
if (renderer->priv->hashes[i])
|
||||
g_free (renderer->priv->hashes[i]);
|
||||
}
|
||||
g_free (renderer->priv->cache);
|
||||
g_free (renderer->priv->hashes);
|
||||
|
||||
g_async_queue_unref (renderer->priv->queue);
|
||||
renderer->priv->queue = NULL;
|
||||
g_async_queue_unref (renderer->priv->ack_queue);
|
||||
renderer->priv->ack_queue = NULL;
|
||||
g_hash_table_destroy (renderer->priv->cache_table);
|
||||
g_mutex_unlock (&renderer->priv->lock);
|
||||
g_mutex_clear (&renderer->priv->lock);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_renderer_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
AnimationRenderer *renderer = ANIMATION_RENDERER (object);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_PLAYBACK:
|
||||
renderer->priv->playback = g_value_get_object (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
animation_renderer_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
AnimationRenderer *renderer = ANIMATION_RENDERER (object);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_PLAYBACK:
|
||||
g_value_set_object (value, renderer->priv->playback);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gpointer
|
||||
animation_renderer_process_queue (AnimationRenderer *renderer)
|
||||
{
|
||||
while (TRUE)
|
||||
{
|
||||
Animation *animation;
|
||||
GeglBuffer *buffer = NULL;
|
||||
GWeakRef *ref = NULL;
|
||||
gchar *hash = NULL;
|
||||
gchar *hash_cp = NULL;
|
||||
gint frame;
|
||||
|
||||
frame = GPOINTER_TO_INT (g_async_queue_pop (renderer->priv->queue)) - 1;
|
||||
|
||||
/* End flag. */
|
||||
if (frame < 0)
|
||||
g_thread_exit (NULL);
|
||||
|
||||
/* It is possible to have position bigger than the animation duration if the
|
||||
* request was sent before a duration change. When this happens, just ignore
|
||||
* the request silently and go to the next one. */
|
||||
g_mutex_lock (&renderer->priv->lock);
|
||||
if (frame >= renderer->priv->cache_size)
|
||||
{
|
||||
g_mutex_unlock (&renderer->priv->lock);
|
||||
continue;
|
||||
}
|
||||
g_mutex_unlock (&renderer->priv->lock);
|
||||
|
||||
animation = animation_playback_get_animation (renderer->priv->playback);
|
||||
hash = ANIMATION_GET_CLASS (animation)->get_frame_hash (animation,
|
||||
frame);
|
||||
if (hash)
|
||||
{
|
||||
ref = g_hash_table_lookup (renderer->priv->cache_table, hash);
|
||||
hash_cp = g_strdup (hash);
|
||||
if (ref)
|
||||
{
|
||||
/* Acquire and add a new reference to the buffer. */
|
||||
buffer = g_weak_ref_get (ref);
|
||||
}
|
||||
else
|
||||
{
|
||||
ref = g_new (GWeakRef, 1);
|
||||
g_weak_ref_init (ref, NULL);
|
||||
g_mutex_lock (&renderer->priv->lock);
|
||||
g_hash_table_insert (renderer->priv->cache_table,
|
||||
hash, ref);
|
||||
g_mutex_unlock (&renderer->priv->lock);
|
||||
}
|
||||
|
||||
if (! buffer)
|
||||
{
|
||||
gdouble proxy_ratio;
|
||||
|
||||
proxy_ratio = animation_playback_get_proxy (renderer->priv->playback);
|
||||
buffer = ANIMATION_GET_CLASS (animation)->create_frame (animation,
|
||||
G_OBJECT (renderer),
|
||||
frame,
|
||||
proxy_ratio);
|
||||
g_weak_ref_set (ref, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
g_mutex_lock (&renderer->priv->lock);
|
||||
if (renderer->priv->cache[frame])
|
||||
g_object_unref (renderer->priv->cache[frame]);
|
||||
if (renderer->priv->hashes[frame])
|
||||
g_free (renderer->priv->hashes[frame]);
|
||||
renderer->priv->cache[frame] = buffer;
|
||||
renderer->priv->hashes[frame] = hash_cp;
|
||||
g_mutex_unlock (&renderer->priv->lock);
|
||||
|
||||
/* Tell the main thread which buffers were updated, so that the
|
||||
* GUI reflects the change. */
|
||||
g_async_queue_remove (renderer->priv->ack_queue,
|
||||
GINT_TO_POINTER (frame + 1));
|
||||
g_async_queue_push (renderer->priv->ack_queue,
|
||||
GINT_TO_POINTER (frame + 1));
|
||||
|
||||
/* Relinquish CPU regularly. */
|
||||
g_thread_yield ();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
animation_renderer_idle_update (AnimationRenderer *renderer)
|
||||
{
|
||||
gpointer p;
|
||||
gboolean retval = G_SOURCE_CONTINUE;;
|
||||
|
||||
while ((p = g_async_queue_try_pop (renderer->priv->ack_queue)))
|
||||
{
|
||||
gint frame = GPOINTER_TO_INT (p) - 1;
|
||||
g_signal_emit (renderer, signals[CACHE_UPDATED], 0, frame);
|
||||
}
|
||||
/* Make sure the UI gets updated regularly. */
|
||||
while (g_main_context_pending (NULL))
|
||||
g_main_context_iteration (NULL, FALSE);
|
||||
|
||||
/* If nothing is being rendered (negative queue length, meaning the
|
||||
* renderer is waiting), nor is there anything in the ACK queue, just
|
||||
* stop the idle source. */
|
||||
if (renderer->priv && renderer->priv->queue &&
|
||||
g_async_queue_length (renderer->priv->queue) < 0 &&
|
||||
g_async_queue_length (renderer->priv->ack_queue) == 0)
|
||||
{
|
||||
retval = G_SOURCE_REMOVE;
|
||||
renderer->priv->idle_id = 0;
|
||||
g_signal_emit (renderer, signals[RENDERING], 0, FALSE);
|
||||
}
|
||||
else if (renderer->priv && renderer->priv->queue)
|
||||
{
|
||||
g_signal_emit (renderer, signals[RENDERING], 0, TRUE);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void
|
||||
on_proxy_changed (AnimationPlayback *playback,
|
||||
gdouble ratio,
|
||||
AnimationRenderer *renderer)
|
||||
{
|
||||
Animation *animation = animation_playback_get_animation (playback);
|
||||
gint current_pos;
|
||||
gint i;
|
||||
|
||||
if (renderer->priv->idle_id)
|
||||
{
|
||||
g_source_remove (renderer->priv->idle_id);
|
||||
renderer->priv->idle_id = 0;
|
||||
}
|
||||
/* Stop any rendering. */
|
||||
for (i = 0; i < animation_get_duration (animation); i++)
|
||||
{
|
||||
g_async_queue_remove (renderer->priv->queue, GINT_TO_POINTER (i + 1));
|
||||
}
|
||||
/* Delete the cache. */
|
||||
g_mutex_lock (&renderer->priv->lock);
|
||||
for (i = 0; i < animation_get_duration (animation); i++)
|
||||
{
|
||||
if (renderer->priv->cache[i])
|
||||
{
|
||||
g_object_unref (renderer->priv->cache[i]);
|
||||
renderer->priv->cache[i] = NULL;
|
||||
}
|
||||
if (renderer->priv->hashes[i])
|
||||
{
|
||||
g_free (renderer->priv->hashes[i]);
|
||||
renderer->priv->hashes[i] = NULL;
|
||||
}
|
||||
}
|
||||
g_mutex_unlock (&renderer->priv->lock);
|
||||
|
||||
/* Queue the whole animation to be updated. */
|
||||
current_pos = animation_playback_get_position (renderer->priv->playback);
|
||||
g_async_queue_sort (renderer->priv->queue,
|
||||
(GCompareDataFunc) compare_int_from,
|
||||
GINT_TO_POINTER (current_pos + 1));
|
||||
for (i = 0; i < animation_get_duration (animation); i++)
|
||||
{
|
||||
g_async_queue_push_sorted (renderer->priv->queue,
|
||||
GINT_TO_POINTER (i + 1),
|
||||
(GCompareDataFunc) compare_int_from,
|
||||
GINT_TO_POINTER (current_pos + 1));
|
||||
}
|
||||
g_signal_emit (renderer, signals[RENDERING], 0, TRUE);
|
||||
renderer->priv->idle_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
|
||||
(GSourceFunc) animation_renderer_idle_update,
|
||||
renderer, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
on_playback_position (AnimationPlayback *animation,
|
||||
gint frame_number,
|
||||
GeglBuffer *buffer,
|
||||
gboolean must_draw_null,
|
||||
AnimationRenderer *renderer)
|
||||
{
|
||||
gint current_pos;
|
||||
|
||||
current_pos = animation_playback_get_position (renderer->priv->playback);
|
||||
g_async_queue_sort (renderer->priv->queue,
|
||||
(GCompareDataFunc) compare_int_from,
|
||||
GINT_TO_POINTER (current_pos + 1));
|
||||
}
|
||||
|
||||
static void
|
||||
on_size_changed (Animation *animation,
|
||||
gint width,
|
||||
gint height,
|
||||
AnimationRenderer *renderer)
|
||||
{
|
||||
on_frames_changed (animation, 0, animation_get_duration (animation),
|
||||
renderer);
|
||||
}
|
||||
|
||||
static void
|
||||
on_frames_changed (Animation *animation,
|
||||
gint position,
|
||||
gint length,
|
||||
AnimationRenderer *renderer)
|
||||
{
|
||||
gint i;
|
||||
gint current_pos;
|
||||
|
||||
current_pos = animation_playback_get_position (renderer->priv->playback);
|
||||
g_async_queue_sort (renderer->priv->queue,
|
||||
(GCompareDataFunc) compare_int_from,
|
||||
GINT_TO_POINTER (current_pos + 1));
|
||||
for (i = position; i < position + length; i++)
|
||||
{
|
||||
/* Remove if already present: don't process twice the same frames.
|
||||
* XXX GAsyncQueue does not allow NULL data for no good reason (it relies
|
||||
* on GQueue which does allow NULL data. As a trick, I just add 1 so that
|
||||
* we can process the frame 0. */
|
||||
g_async_queue_remove (renderer->priv->queue, GINT_TO_POINTER (i + 1));
|
||||
g_async_queue_push_sorted (renderer->priv->queue,
|
||||
GINT_TO_POINTER (i + 1),
|
||||
(GCompareDataFunc) compare_int_from,
|
||||
GINT_TO_POINTER (current_pos + 1));
|
||||
if (renderer->priv->idle_id == 0)
|
||||
renderer->priv->idle_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
|
||||
(GSourceFunc) animation_renderer_idle_update,
|
||||
renderer, NULL);
|
||||
}
|
||||
g_signal_emit (renderer, signals[RENDERING], 0, TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
on_invalidate_cache (Animation *animation,
|
||||
gint position,
|
||||
gint length,
|
||||
AnimationRenderer *renderer)
|
||||
{
|
||||
GList *update = NULL;
|
||||
GList *iter;
|
||||
gint current_pos;
|
||||
gint i;
|
||||
|
||||
if (renderer->priv->idle_id)
|
||||
{
|
||||
g_source_remove (renderer->priv->idle_id);
|
||||
renderer->priv->idle_id = 0;
|
||||
}
|
||||
/* Stop any rendering temporarily. */
|
||||
for (i = 0; i < animation_get_duration (animation); i++)
|
||||
{
|
||||
if (g_async_queue_remove (renderer->priv->queue, GINT_TO_POINTER (i + 1)))
|
||||
update = g_list_insert_sorted_with_data (update, GINT_TO_POINTER (i + 1),
|
||||
(GCompareDataFunc) compare_int_from,
|
||||
/* TODO: right now I am sorting the render
|
||||
* queue in common order. I will have to test
|
||||
* sorting it from the current position.
|
||||
*/
|
||||
0);
|
||||
}
|
||||
/* Delete the cache. */
|
||||
g_mutex_lock (&renderer->priv->lock);
|
||||
for (i = position; i < position + length; i++)
|
||||
{
|
||||
gint j;
|
||||
|
||||
if (renderer->priv->cache[i])
|
||||
{
|
||||
/* Delete this frame and all others which share the same cache. */
|
||||
GeglBuffer *tmp = g_object_ref (renderer->priv->cache[i]);
|
||||
for (j = 0; j < animation_get_duration (animation); j++)
|
||||
{
|
||||
if (renderer->priv->cache[j] == tmp)
|
||||
{
|
||||
g_object_unref (renderer->priv->cache[j]);
|
||||
renderer->priv->cache[j] = NULL;
|
||||
g_free (renderer->priv->hashes[j]);
|
||||
renderer->priv->hashes[j] = NULL;
|
||||
if (g_list_index (update, GINT_TO_POINTER (j + 1)) == -1)
|
||||
update = g_list_insert_sorted_with_data (update, GINT_TO_POINTER (j + 1),
|
||||
(GCompareDataFunc) compare_int_from,
|
||||
/* TODO: right now I am sorting the render
|
||||
* queue in common order. I will have to test
|
||||
* sorting it from the current position.
|
||||
*/
|
||||
0);
|
||||
}
|
||||
}
|
||||
g_object_unref (tmp);
|
||||
}
|
||||
if (g_list_index (update, GINT_TO_POINTER (i + 1)) == -1)
|
||||
update = g_list_insert_sorted_with_data (update, GINT_TO_POINTER (i + 1),
|
||||
(GCompareDataFunc) compare_int_from,
|
||||
/* TODO: right now I am sorting the render
|
||||
* queue in common order. I will have to test
|
||||
* sorting it from the current position.
|
||||
*/
|
||||
0);
|
||||
}
|
||||
g_mutex_unlock (&renderer->priv->lock);
|
||||
|
||||
/* Queue the invalidated part of the animation. */
|
||||
current_pos = animation_playback_get_position (renderer->priv->playback);
|
||||
g_async_queue_sort (renderer->priv->queue,
|
||||
(GCompareDataFunc) compare_int_from,
|
||||
GINT_TO_POINTER (current_pos + 1));
|
||||
for (iter = update; iter; iter = iter->next)
|
||||
{
|
||||
g_async_queue_push_sorted (renderer->priv->queue,
|
||||
iter->data,
|
||||
(GCompareDataFunc) compare_int_from,
|
||||
GINT_TO_POINTER (current_pos + 1));
|
||||
}
|
||||
g_list_free (update);
|
||||
g_signal_emit (renderer, signals[RENDERING], 0, TRUE);
|
||||
renderer->priv->idle_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
|
||||
(GSourceFunc) animation_renderer_idle_update,
|
||||
renderer, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
on_duration_changed (Animation *animation,
|
||||
gint duration,
|
||||
AnimationRenderer *renderer)
|
||||
{
|
||||
gint i;
|
||||
|
||||
g_mutex_lock (&renderer->priv->lock);
|
||||
if (duration < renderer->priv->cache_size)
|
||||
{
|
||||
for (i = duration; i < renderer->priv->cache_size; i++)
|
||||
{
|
||||
if (renderer->priv->cache[i])
|
||||
g_object_unref (renderer->priv->cache[i]);
|
||||
if (renderer->priv->hashes[i])
|
||||
g_free (renderer->priv->hashes[i]);
|
||||
}
|
||||
renderer->priv->cache = g_renew (GeglBuffer*,
|
||||
renderer->priv->cache,
|
||||
duration);
|
||||
renderer->priv->hashes = g_renew (gchar*,
|
||||
renderer->priv->hashes,
|
||||
duration);
|
||||
}
|
||||
else if (duration > renderer->priv->cache_size)
|
||||
{
|
||||
renderer->priv->cache = g_renew (GeglBuffer*,
|
||||
renderer->priv->cache,
|
||||
duration);
|
||||
renderer->priv->hashes = g_renew (gchar*,
|
||||
renderer->priv->hashes,
|
||||
duration);
|
||||
for (i = renderer->priv->cache_size; i < duration; i++)
|
||||
{
|
||||
renderer->priv->cache[i] = NULL;
|
||||
renderer->priv->hashes[i] = NULL;
|
||||
}
|
||||
}
|
||||
renderer->priv->cache_size = duration;
|
||||
g_mutex_unlock (&renderer->priv->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
on_animation_loaded (Animation *animation,
|
||||
AnimationRenderer *renderer)
|
||||
{
|
||||
g_signal_handlers_disconnect_by_func (animation,
|
||||
G_CALLBACK (on_animation_loaded),
|
||||
renderer);
|
||||
renderer->priv->queue_thread = g_thread_new ("gimp-animation-process-queue",
|
||||
(GThreadFunc) animation_renderer_process_queue,
|
||||
renderer);
|
||||
renderer->priv->idle_id = 0;
|
||||
}
|
||||
|
||||
/**** Public Functions ****/
|
||||
|
||||
/**
|
||||
* animation_renderer_new:
|
||||
* @playback: the #AnimationPlayback.
|
||||
*
|
||||
* Returns: a new #AnimationRenderer. This renderer as well as all its methods
|
||||
* should only be visible by the attached @playback.
|
||||
**/
|
||||
GObject *
|
||||
animation_renderer_new (GObject *playback)
|
||||
{
|
||||
GObject *object;
|
||||
AnimationRenderer *renderer;
|
||||
Animation *animation;
|
||||
|
||||
object = g_object_new (ANIMATION_TYPE_RENDERER,
|
||||
"playback", playback,
|
||||
NULL);
|
||||
renderer = ANIMATION_RENDERER (object);
|
||||
|
||||
animation = animation_playback_get_animation (renderer->priv->playback);
|
||||
renderer->priv->cache_size = animation_get_duration (animation);
|
||||
renderer->priv->cache = g_new0 (GeglBuffer*,
|
||||
renderer->priv->cache_size);
|
||||
renderer->priv->hashes = g_new0 (gchar*,
|
||||
renderer->priv->cache_size);
|
||||
g_signal_connect (animation, "size-changed",
|
||||
G_CALLBACK (on_size_changed), renderer);
|
||||
g_signal_connect (animation, "frames-changed",
|
||||
G_CALLBACK (on_frames_changed), renderer);
|
||||
g_signal_connect (animation, "invalidate-cache",
|
||||
G_CALLBACK (on_invalidate_cache), renderer);
|
||||
g_signal_connect (animation, "duration-changed",
|
||||
G_CALLBACK (on_duration_changed), renderer);
|
||||
g_signal_connect (animation, "loaded",
|
||||
G_CALLBACK (on_animation_loaded), renderer);
|
||||
g_signal_connect (renderer->priv->playback, "proxy-changed",
|
||||
G_CALLBACK (on_proxy_changed), renderer);
|
||||
g_signal_connect (renderer->priv->playback, "position",
|
||||
G_CALLBACK (on_playback_position), renderer);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* animation_renderer_get_buffer:
|
||||
* @renderer: the #AnimationRenderer.
|
||||
* @position:
|
||||
*
|
||||
* Returns: the #GeglBuffer cached for the frame at @position, with an
|
||||
* additional reference, so that it will stay valid even if the frame is
|
||||
* updated in-between. Therefore call g_object_unref() after usage.
|
||||
* As all other renderer function, it should only be visible by the playback,
|
||||
* or by frame-rendering code in #Animation subclasses themselves, since the
|
||||
* renderer passes itself as argument when it requests a new frame buffer.
|
||||
* Other pieces of code, in particular the GUI, should only call
|
||||
* animation_playback_get_buffer().
|
||||
**/
|
||||
GeglBuffer *
|
||||
animation_renderer_get_buffer (AnimationRenderer *renderer,
|
||||
gint position)
|
||||
{
|
||||
GeglBuffer *frame;
|
||||
|
||||
g_mutex_lock (&renderer->priv->lock);
|
||||
frame = renderer->priv->cache[position];
|
||||
if (frame)
|
||||
frame = g_object_ref (frame);
|
||||
g_mutex_unlock (&renderer->priv->lock);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
gboolean
|
||||
animation_renderer_identical (AnimationRenderer *renderer,
|
||||
gint position1,
|
||||
gint position2)
|
||||
{
|
||||
return (g_strcmp0 (renderer->priv->hashes[position1],
|
||||
renderer->priv->hashes[position2]) == 0);
|
||||
}
|
63
plug-ins/animation-play/core/animation-renderer.h
Normal file
63
plug-ins/animation-play/core/animation-renderer.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation-renderer.h
|
||||
* Copyright (C) 2017 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_RENDERER_H__
|
||||
#define __ANIMATION_RENDERER_H__
|
||||
|
||||
#define ANIMATION_TYPE_RENDERER (animation_renderer_get_type ())
|
||||
#define ANIMATION_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_RENDERER, AnimationRenderer))
|
||||
#define ANIMATION_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_RENDERER, AnimationRendererClass))
|
||||
#define ANIMATION_IS_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_RENDERER))
|
||||
#define ANIMATION_IS_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_RENDERER))
|
||||
#define ANIMATION_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_RENDERER, AnimationRendererClass))
|
||||
|
||||
typedef struct _AnimationRenderer AnimationRenderer;
|
||||
typedef struct _AnimationRendererClass AnimationRendererClass;
|
||||
typedef struct _AnimationRendererPrivate AnimationRendererPrivate;
|
||||
|
||||
struct _AnimationRenderer
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
AnimationRendererPrivate *priv;
|
||||
};
|
||||
|
||||
struct _AnimationRendererClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
|
||||
void (*cache_updated) (AnimationRenderer *renderer,
|
||||
gint position);
|
||||
void (*rendering) (AnimationRenderer *renderer,
|
||||
gboolean has_render_queue);
|
||||
};
|
||||
|
||||
GType animation_renderer_get_type (void);
|
||||
|
||||
|
||||
GObject * animation_renderer_new (GObject *playback);
|
||||
|
||||
GeglBuffer * animation_renderer_get_buffer (AnimationRenderer *renderer,
|
||||
gint position);
|
||||
gboolean animation_renderer_identical (AnimationRenderer *renderer,
|
||||
gint position1,
|
||||
gint position2);
|
||||
|
||||
#endif /* __ANIMATION_RENDERER_H__ */
|
585
plug-ins/animation-play/core/animation.c
Normal file
585
plug-ins/animation-play/core/animation.c
Normal file
@@ -0,0 +1,585 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation.c
|
||||
* Copyright (C) 2015-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 <string.h>
|
||||
|
||||
#include <libgimp/gimp.h>
|
||||
#include <libgimp/stdplugins-intl.h>
|
||||
|
||||
#include "animation-utils.h"
|
||||
|
||||
#include "animation.h"
|
||||
#include "animation-animatic.h"
|
||||
#include "animation-celanimation.h"
|
||||
#include "animation-playback.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
|
||||
{
|
||||
LOADING,
|
||||
LOADED,
|
||||
SIZE_CHANGED,
|
||||
DURATION_CHANGED,
|
||||
FRAMERATE_CHANGED,
|
||||
FRAMES_CHANGED,
|
||||
INVALIDATE_CACHE,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_IMAGE,
|
||||
PROP_XML
|
||||
};
|
||||
|
||||
typedef struct _AnimationPrivate AnimationPrivate;
|
||||
|
||||
struct _AnimationPrivate
|
||||
{
|
||||
gint32 image_id;
|
||||
|
||||
/* Animation size may be different from image size. */
|
||||
gint width;
|
||||
gint height;
|
||||
|
||||
gdouble framerate;
|
||||
|
||||
gboolean loaded;
|
||||
};
|
||||
|
||||
|
||||
#define ANIMATION_GET_PRIVATE(animation) \
|
||||
G_TYPE_INSTANCE_GET_PRIVATE (animation, \
|
||||
ANIMATION_TYPE_ANIMATION, \
|
||||
AnimationPrivate)
|
||||
|
||||
static void animation_finalize (GObject *object);
|
||||
static void animation_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec);
|
||||
static void animation_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec);
|
||||
|
||||
G_DEFINE_TYPE (Animation, animation, G_TYPE_OBJECT)
|
||||
|
||||
#define parent_class animation_parent_class
|
||||
|
||||
static guint animation_signals[LAST_SIGNAL] = { 0 };
|
||||
|
||||
static void
|
||||
animation_class_init (AnimationClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
/**
|
||||
* Animation::loading:
|
||||
* @animation: the animation loading.
|
||||
* @ratio: fraction loaded [0-1].
|
||||
* @label: the text to show as progression message.
|
||||
*
|
||||
* The ::loading signal must be emitted when a long process is taking
|
||||
* place and you want the GUI to display a progress bar.
|
||||
* GUI widgets depending on a consistent state of @animation should
|
||||
* become unresponsive.
|
||||
*
|
||||
* Returns: TRUE is the loading should be stopped, FALSE otherwise.
|
||||
* This allows long loading to be stopped.
|
||||
*/
|
||||
animation_signals[LOADING] =
|
||||
g_signal_new ("loading",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_ACTION,
|
||||
G_STRUCT_OFFSET (AnimationClass, loading),
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_BOOLEAN,
|
||||
2,
|
||||
G_TYPE_DOUBLE,
|
||||
G_TYPE_STRING);
|
||||
/**
|
||||
* Animation::loaded:
|
||||
* @animation: the animation loading.
|
||||
* @duration: number of frames.
|
||||
* @width: display width in pixels.
|
||||
* @height: display height in pixels.
|
||||
*
|
||||
* The ::loaded signal is emitted when @animation is fully loaded.
|
||||
* GUI widgets depending on a consistent state of @animation can
|
||||
* now become responsive to user interaction.
|
||||
*/
|
||||
animation_signals[LOADED] =
|
||||
g_signal_new ("loaded",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationClass, loaded),
|
||||
NULL, NULL,
|
||||
g_cclosure_marshal_VOID__VOID,
|
||||
G_TYPE_NONE,
|
||||
0);
|
||||
/**
|
||||
* Animation::size-changed:
|
||||
* @animation: the animation.
|
||||
* @width: @animation width.
|
||||
* @height: @animation height.
|
||||
*
|
||||
* The ::size-changed signal will be emitted when @animation display
|
||||
* size changes.
|
||||
*/
|
||||
animation_signals[SIZE_CHANGED] =
|
||||
g_signal_new ("size-changed",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationClass, size_changed),
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
2,
|
||||
G_TYPE_INT,
|
||||
G_TYPE_INT);
|
||||
/**
|
||||
* Animation::duration:
|
||||
* @animation: the animation.
|
||||
* @duration: the new duration of @animation in number of frames.
|
||||
*
|
||||
* The ::duration signal must be emitted when the duration of
|
||||
* @animation changes.
|
||||
*/
|
||||
animation_signals[DURATION_CHANGED] =
|
||||
g_signal_new ("duration-changed",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationClass, duration_changed),
|
||||
NULL, NULL,
|
||||
g_cclosure_marshal_VOID__INT,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_INT);
|
||||
/**
|
||||
* Animation::framerate-changed:
|
||||
* @animation: the animation.
|
||||
* @framerate: the new framerate of @animation in frames per second.
|
||||
*
|
||||
* The ::framerate-changed signal is emitted when the framerate of
|
||||
* @animation changes.
|
||||
*/
|
||||
animation_signals[FRAMERATE_CHANGED] =
|
||||
g_signal_new ("framerate-changed",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationClass, framerate_changed),
|
||||
NULL, NULL,
|
||||
g_cclosure_marshal_VOID__DOUBLE,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_DOUBLE);
|
||||
/**
|
||||
* Animation::frames-changed:
|
||||
* @animation: the animation.
|
||||
* @position: the first frame position whose contents changed.
|
||||
* @length: the number of changed frames from @position.
|
||||
*
|
||||
* The ::frames-changed signal must be emitted when the contents
|
||||
* of one or more successive frames change.
|
||||
*/
|
||||
animation_signals[FRAMES_CHANGED] =
|
||||
g_signal_new ("frames-changed",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationClass, frames_changed),
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
2,
|
||||
G_TYPE_INT,
|
||||
G_TYPE_INT);
|
||||
/**
|
||||
* Animation::invalidate-cache:
|
||||
* @animation: the animation.
|
||||
* @position: the first frame position whose contents changed.
|
||||
* @length: the number of changed frames from @position.
|
||||
*
|
||||
* The ::invalidate-cache signal is emitted when one or more
|
||||
* successive frames have to be updated. This is similar to
|
||||
* ::frames-changed except that it forces the cache update even if the
|
||||
* contents apparently did not change. It will also invalidate cache
|
||||
* for similar frames, even when not in the given range.
|
||||
*/
|
||||
animation_signals[INVALIDATE_CACHE] =
|
||||
g_signal_new ("invalidate-cache",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (AnimationClass, invalidate_cache),
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
2,
|
||||
G_TYPE_INT,
|
||||
G_TYPE_INT);
|
||||
|
||||
object_class->finalize = animation_finalize;
|
||||
object_class->set_property = animation_set_property;
|
||||
object_class->get_property = animation_get_property;
|
||||
|
||||
/**
|
||||
* Animation:image:
|
||||
*
|
||||
* The associated image id.
|
||||
*/
|
||||
g_object_class_install_property (object_class, PROP_IMAGE,
|
||||
g_param_spec_int ("image", NULL, NULL,
|
||||
0, G_MAXINT, 0,
|
||||
G_PARAM_READWRITE |
|
||||
G_PARAM_CONSTRUCT_ONLY));
|
||||
/**
|
||||
* Animation:xml:
|
||||
*
|
||||
* The animation serialized as a XML string.
|
||||
*/
|
||||
g_object_class_install_property (object_class, PROP_XML,
|
||||
g_param_spec_string ("xml", NULL,
|
||||
NULL, NULL,
|
||||
G_PARAM_READWRITE |
|
||||
G_PARAM_CONSTRUCT_ONLY));
|
||||
|
||||
g_type_class_add_private (klass, sizeof (AnimationPrivate));
|
||||
}
|
||||
|
||||
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 = settings.framerate;
|
||||
}
|
||||
|
||||
/************ Public Functions ****************/
|
||||
|
||||
Animation *
|
||||
animation_new (gint32 image_id,
|
||||
gboolean animatic,
|
||||
const gchar *xml)
|
||||
{
|
||||
Animation *animation;
|
||||
|
||||
animation = g_object_new (animatic? ANIMATION_TYPE_ANIMATIC :
|
||||
ANIMATION_TYPE_CEL_ANIMATION,
|
||||
"image", image_id,
|
||||
"xml", xml,
|
||||
NULL);
|
||||
g_signal_emit (animation, animation_signals[FRAMES_CHANGED], 0,
|
||||
0, animation_get_duration (animation));
|
||||
|
||||
return animation;
|
||||
}
|
||||
|
||||
gint32
|
||||
animation_get_image_id (Animation *animation)
|
||||
{
|
||||
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
|
||||
|
||||
return priv->image_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* animation_load:
|
||||
* @animation: the #Animation.
|
||||
*
|
||||
* Cache the whole animation. This is to be used at the start, or each
|
||||
* time you want to reload the image contents.
|
||||
**/
|
||||
void
|
||||
animation_load (Animation *animation)
|
||||
{
|
||||
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
|
||||
|
||||
priv->loaded = FALSE;
|
||||
if (ANIMATION_GET_CLASS (animation)->load)
|
||||
ANIMATION_GET_CLASS (animation)->load (animation);
|
||||
g_signal_emit (animation, animation_signals[INVALIDATE_CACHE], 0,
|
||||
0, animation_get_duration (animation));
|
||||
priv->loaded = TRUE;
|
||||
g_signal_emit (animation, animation_signals[LOADED], 0);
|
||||
}
|
||||
|
||||
void
|
||||
animation_save_to_parasite (Animation *animation,
|
||||
const gchar *playback_xml)
|
||||
{
|
||||
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
|
||||
GimpParasite *old_parasite;
|
||||
const gchar *parasite_name;
|
||||
const gchar *selected;
|
||||
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,
|
||||
playback_xml);
|
||||
|
||||
if (ANIMATION_IS_ANIMATIC (animation))
|
||||
{
|
||||
selected = "animatic";
|
||||
parasite_name = PLUG_IN_PROC "/animatic";
|
||||
}
|
||||
else /* ANIMATION_IS_CEL_ANIMATION */
|
||||
{
|
||||
selected = "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 (xml)
|
||||
g_free (xml);
|
||||
|
||||
old_parasite = gimp_image_get_parasite (priv->image_id,
|
||||
PLUG_IN_PROC "/selected");
|
||||
if (! old_parasite ||
|
||||
g_strcmp0 ((gchar *) gimp_parasite_data (old_parasite), selected))
|
||||
{
|
||||
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 "/selected",
|
||||
GIMP_PARASITE_PERSISTENT | GIMP_PARASITE_UNDOABLE,
|
||||
strlen (selected) + 1, selected);
|
||||
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
|
||||
animation_get_duration (Animation *animation)
|
||||
{
|
||||
return ANIMATION_GET_CLASS (animation)->get_duration (animation);
|
||||
}
|
||||
|
||||
void
|
||||
animation_set_size (Animation *animation,
|
||||
gint width,
|
||||
gint height)
|
||||
{
|
||||
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
|
||||
|
||||
if (width != priv->width || height != priv->height)
|
||||
{
|
||||
priv->width = width;
|
||||
priv->height = height;
|
||||
g_signal_emit (animation, animation_signals[SIZE_CHANGED], 0,
|
||||
width, height);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
animation_get_size (Animation *animation,
|
||||
gint *width,
|
||||
gint *height)
|
||||
{
|
||||
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
|
||||
|
||||
if (width)
|
||||
*width = priv->width;
|
||||
if (height)
|
||||
*height = priv->height;
|
||||
}
|
||||
|
||||
void
|
||||
animation_set_framerate (Animation *animation,
|
||||
gdouble fps)
|
||||
{
|
||||
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
|
||||
|
||||
g_return_if_fail (fps > 0.0);
|
||||
|
||||
priv->framerate = fps;
|
||||
|
||||
g_signal_emit (animation, animation_signals[FRAMERATE_CHANGED], 0,
|
||||
fps);
|
||||
}
|
||||
|
||||
gdouble
|
||||
animation_get_framerate (Animation *animation)
|
||||
{
|
||||
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
|
||||
|
||||
return priv->framerate;
|
||||
}
|
||||
|
||||
gboolean
|
||||
animation_loaded (Animation *animation)
|
||||
{
|
||||
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
|
||||
|
||||
return priv->loaded;
|
||||
}
|
||||
|
||||
void
|
||||
animation_update_paint_view (Animation *animation,
|
||||
gint position)
|
||||
{
|
||||
ANIMATION_GET_CLASS (animation)->update_paint_view (animation, position);
|
||||
gimp_displays_flush ();
|
||||
}
|
||||
|
||||
/************ Private Functions ****************/
|
||||
|
||||
static void
|
||||
animation_finalize (GObject *object)
|
||||
{
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
Animation *animation = ANIMATION (object);
|
||||
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_IMAGE:
|
||||
priv->image_id = g_value_get_int (value);
|
||||
break;
|
||||
case PROP_XML:
|
||||
{
|
||||
const gchar *xml = g_value_get_string (value);
|
||||
GError *error = NULL;
|
||||
gint width;
|
||||
gint height;
|
||||
|
||||
if (! xml ||
|
||||
! ANIMATION_GET_CLASS (animation)->deserialize (animation,
|
||||
xml,
|
||||
&error))
|
||||
{
|
||||
if (error)
|
||||
g_warning ("Error parsing XML: %s", error->message);
|
||||
|
||||
/* First time or XML parsing failed: reset to defaults. */
|
||||
ANIMATION_GET_CLASS (animation)->reset_defaults (animation);
|
||||
}
|
||||
g_clear_error (&error);
|
||||
|
||||
animation_get_size (animation, &width, &height);
|
||||
if (width <= 0 || height <= 0)
|
||||
{
|
||||
/* Default display size is the size of the image. */
|
||||
animation_set_size (animation,
|
||||
gimp_image_width (priv->image_id),
|
||||
gimp_image_height (priv->image_id));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
animation_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
Animation *animation = ANIMATION (object);
|
||||
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_IMAGE:
|
||||
g_value_set_int (value, priv->image_id);
|
||||
break;
|
||||
case PROP_XML:
|
||||
{
|
||||
gchar *xml;
|
||||
|
||||
xml = ANIMATION_GET_CLASS (animation)->serialize (animation,
|
||||
NULL);
|
||||
g_value_take_string (value, xml);
|
||||
g_free (xml);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
120
plug-ins/animation-play/core/animation.h
Normal file
120
plug-ins/animation-play/core/animation.h
Normal file
@@ -0,0 +1,120 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation.h
|
||||
* Copyright (C) 2015-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_H__
|
||||
#define __ANIMATION_H__
|
||||
|
||||
#define ANIMATION_TYPE_ANIMATION (animation_get_type ())
|
||||
#define ANIMATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_ANIMATION, Animation))
|
||||
#define ANIMATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_ANIMATION, AnimationClass))
|
||||
#define ANIMATION_IS_ANIMATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_ANIMATION))
|
||||
#define ANIMATION_IS_ANIMATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_ANIMATION))
|
||||
#define ANIMATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_ANIMATION, AnimationClass))
|
||||
|
||||
typedef struct _Animation Animation;
|
||||
typedef struct _AnimationClass AnimationClass;
|
||||
|
||||
struct _Animation
|
||||
{
|
||||
GObject parent_instance;
|
||||
};
|
||||
|
||||
struct _AnimationClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
|
||||
/* Signals */
|
||||
void (*loading) (Animation *animation,
|
||||
gdouble ratio,
|
||||
const gchar *label);
|
||||
void (*loaded) (Animation *animation);
|
||||
|
||||
void (*size_changed) (Animation *animation,
|
||||
gint width,
|
||||
gint height);
|
||||
void (*duration_changed) (Animation *animation,
|
||||
gint duration);
|
||||
void (*framerate_changed) (Animation *animation,
|
||||
gdouble fps);
|
||||
void (*frames_changed) (Animation *animation,
|
||||
gint position,
|
||||
gint length);
|
||||
void (*invalidate_cache) (Animation *animation,
|
||||
gint position,
|
||||
gint length);
|
||||
|
||||
/* These virtual methods must be implemented by any subclass. */
|
||||
gint (*get_duration) (Animation *animation);
|
||||
|
||||
void (*reset_defaults) (Animation *animation);
|
||||
gchar * (*serialize) (Animation *animation,
|
||||
const gchar *playback_xml);
|
||||
gboolean (*deserialize) (Animation *animation,
|
||||
const gchar *xml,
|
||||
GError **error);
|
||||
|
||||
void (*update_paint_view) (Animation *animation,
|
||||
gint position);
|
||||
|
||||
/* Optional: to be implemented if there is anything to do else than
|
||||
* refreshing the cache upon loading. */
|
||||
void (*load) (Animation *animation);
|
||||
|
||||
/* Used by the renderer only. Must be implemented too. */
|
||||
gchar * (*get_frame_hash) (Animation *animation,
|
||||
gint position);
|
||||
GeglBuffer * (*create_frame) (Animation *animation,
|
||||
GObject *renderer,
|
||||
gint position,
|
||||
gdouble proxy_ratio);
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
void animation_save_to_parasite (Animation *animation,
|
||||
const gchar *playback_xml);
|
||||
|
||||
gint animation_get_duration (Animation *animation);
|
||||
|
||||
void animation_set_size (Animation *animation,
|
||||
gint width,
|
||||
gint height);
|
||||
void animation_get_size (Animation *animation,
|
||||
gint *width,
|
||||
gint *height);
|
||||
|
||||
void animation_set_framerate (Animation *animation,
|
||||
gdouble fps);
|
||||
gdouble animation_get_framerate (Animation *animation);
|
||||
|
||||
gboolean animation_loaded (Animation *animation);
|
||||
|
||||
void animation_update_paint_view (Animation *animation,
|
||||
gint position);
|
||||
|
||||
#endif /* __ANIMATION_H__ */
|
BIN
plug-ins/animation-play/icons/gimp-motion.png
Normal file
BIN
plug-ins/animation-play/icons/gimp-motion.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
237
plug-ins/animation-play/widgets/animation-dialog-export.c
Normal file
237
plug-ins/animation-play/widgets/animation-dialog-export.c
Normal file
@@ -0,0 +1,237 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation-dialog-export.c
|
||||
* Copyright (C) 2017 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 <string.h>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include <libgimp/gimp.h>
|
||||
#undef GDK_DISABLE_DEPRECATED
|
||||
#include <libgimp/gimpui.h>
|
||||
#include "libgimp/stdplugins-intl.h"
|
||||
|
||||
#include "core/animation.h"
|
||||
#include "core/animation-playback.h"
|
||||
|
||||
#include "animation-dialog.h"
|
||||
#include "animation-dialog-export.h"
|
||||
|
||||
static void animation_dialog_export_video (AnimationPlayback *playback,
|
||||
gchar *filename);
|
||||
static void animation_dialog_export_images (AnimationPlayback *playback,
|
||||
gchar *filename);
|
||||
|
||||
void
|
||||
animation_dialog_export (GtkWindow *main_dialog,
|
||||
AnimationPlayback *playback)
|
||||
{
|
||||
GtkWidget *dialog;
|
||||
GtkFileFilter *all;
|
||||
GtkFileFilter *videos;
|
||||
GtkFileFilter *images;
|
||||
gchar *filename = NULL;
|
||||
|
||||
dialog = gtk_file_chooser_dialog_new (_("Export animation"),
|
||||
main_dialog,
|
||||
GTK_FILE_CHOOSER_ACTION_SAVE,
|
||||
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
||||
_("_Export"), GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
|
||||
gtk_file_chooser_set_create_folders (GTK_FILE_CHOOSER (dialog), TRUE);
|
||||
/*gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog),
|
||||
default_folder_for_saving);*/
|
||||
/*gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog),
|
||||
"Untitled document");*/
|
||||
|
||||
/* Add filters. */
|
||||
all = gtk_file_filter_new ();
|
||||
gtk_file_filter_set_name (all, _("All files"));
|
||||
gtk_file_filter_add_pattern (all, "*");
|
||||
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), all);
|
||||
|
||||
videos = gtk_file_filter_new ();
|
||||
gtk_file_filter_set_name (videos, _("Video Files"));
|
||||
gtk_file_filter_add_pattern (videos, "*.[oO][gG][vV]");
|
||||
gtk_file_filter_add_mime_type (videos, "video/x-theora+ogg");
|
||||
gtk_file_filter_add_mime_type (videos, "video/ogg");
|
||||
gtk_file_filter_add_pattern (videos, "*.[aA][vV][iI]");
|
||||
gtk_file_filter_add_pattern (videos, "*.[mM][oO][vV]");
|
||||
gtk_file_filter_add_pattern (videos, "*.[mM][pP][gG]");
|
||||
gtk_file_filter_add_pattern (videos, "*.[mM][pP]4");
|
||||
gtk_file_filter_add_mime_type (videos, "video/x-msvideo");
|
||||
gtk_file_filter_add_mime_type (videos, "video/quicktime");
|
||||
gtk_file_filter_add_mime_type (videos, "video/mpeg");
|
||||
gtk_file_filter_add_mime_type (videos, "video/mp4");
|
||||
gtk_file_filter_add_mime_type (videos, "video/x-matroska");
|
||||
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), videos);
|
||||
|
||||
images = gtk_file_filter_new ();
|
||||
gtk_file_filter_set_name (images, _("Image Sequences"));
|
||||
gtk_file_filter_add_pattern (images, "*.[pP][nN][gG]");
|
||||
gtk_file_filter_add_pattern (images, "*.[tT][iI][fF][fF]");
|
||||
gtk_file_filter_add_pattern (images, "*.[tT][iI][fF]");
|
||||
gtk_file_filter_add_pattern (images, "*.[jJ][pP][gG]");
|
||||
gtk_file_filter_add_pattern (images, "*.[jJ][pP][eE][gG]");
|
||||
gtk_file_filter_add_mime_type (images, "image/tiff");
|
||||
gtk_file_filter_add_mime_type (images, "image/png");
|
||||
gtk_file_filter_add_mime_type (images, "image/jpeg");
|
||||
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), images);
|
||||
|
||||
gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), videos);
|
||||
|
||||
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
|
||||
{
|
||||
filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
|
||||
}
|
||||
gtk_widget_destroy (dialog);
|
||||
|
||||
if (filename)
|
||||
{
|
||||
gchar *lower = g_ascii_strdown (filename, -1);
|
||||
|
||||
/* check the type of files. */
|
||||
if (g_str_has_suffix (lower, ".png") ||
|
||||
g_str_has_suffix (lower, ".jpg") ||
|
||||
g_str_has_suffix (lower, ".jpeg") ||
|
||||
g_str_has_suffix (lower, ".tiff") ||
|
||||
g_str_has_suffix (lower, ".tif"))
|
||||
{
|
||||
animation_dialog_export_images (playback, filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We assume unrecognized type are videos. I doubt that's
|
||||
* a good assumption, but enough for a first hack. */
|
||||
animation_dialog_export_video (playback, filename);
|
||||
}
|
||||
g_free (lower);
|
||||
g_free (filename);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
animation_dialog_export_video (AnimationPlayback *playback,
|
||||
gchar *filename)
|
||||
{
|
||||
Animation *animation;
|
||||
GeglNode *graph;
|
||||
GeglNode *export;
|
||||
GeglNode *input;
|
||||
gchar *label = g_strdup_printf(_("Exporting \"%s\""),
|
||||
filename);
|
||||
gint duration;
|
||||
gint i;
|
||||
|
||||
animation = animation_playback_get_animation (playback);
|
||||
duration = animation_get_duration (animation);
|
||||
graph = gegl_node_new ();
|
||||
export = gegl_node_new_child (graph,
|
||||
"operation", "gegl:ff-save",
|
||||
"path", filename,
|
||||
NULL);
|
||||
input = gegl_node_new_child (graph,
|
||||
"operation", "gegl:buffer-source",
|
||||
NULL);
|
||||
gegl_node_set (export, "frame-rate", 24.0, NULL);
|
||||
gegl_node_set (export, "video-bufsize", 0, NULL);
|
||||
gegl_node_set (export, "video-bit-rate", 0, NULL);
|
||||
gegl_node_link_many (input, export, NULL);
|
||||
|
||||
for (i = 0; i < duration; i++)
|
||||
{
|
||||
GeglBuffer *buffer;
|
||||
gboolean stops_loading;
|
||||
|
||||
g_signal_emit_by_name (animation, "loading",
|
||||
(gdouble) i / ((gdouble) duration - 0.999),
|
||||
label, &stops_loading);
|
||||
if (stops_loading)
|
||||
break;
|
||||
buffer = animation_playback_get_buffer (playback, i);
|
||||
gegl_node_set (input, "buffer", buffer, NULL);
|
||||
gegl_node_process (export);
|
||||
g_object_unref (buffer);
|
||||
}
|
||||
g_free (label);
|
||||
g_object_unref (graph);
|
||||
g_signal_emit_by_name (animation, "loaded");
|
||||
}
|
||||
|
||||
static void
|
||||
animation_dialog_export_images (AnimationPlayback *playback,
|
||||
gchar *filename)
|
||||
{
|
||||
Animation *animation;
|
||||
GeglNode *graph;
|
||||
GeglNode *export;
|
||||
GeglNode *input;
|
||||
gint duration;
|
||||
gchar *ext;
|
||||
gint i;
|
||||
|
||||
ext = g_strrstr (filename, ".");
|
||||
g_return_if_fail (ext);
|
||||
*ext = '\0';
|
||||
ext++;
|
||||
|
||||
animation = animation_playback_get_animation (playback);
|
||||
duration = animation_get_duration (animation);
|
||||
graph = gegl_node_new ();
|
||||
export = gegl_node_new_child (graph,
|
||||
"operation", "gegl:save",
|
||||
NULL);
|
||||
input = gegl_node_new_child (graph,
|
||||
"operation", "gegl:buffer-source",
|
||||
NULL);
|
||||
gegl_node_link_many (input, export, NULL);
|
||||
|
||||
for (i = 0; i < duration; i++)
|
||||
{
|
||||
GeglBuffer *buffer;
|
||||
gchar *path;
|
||||
gboolean stops_loading;
|
||||
|
||||
/* Make sure GUI interactions are processed, so that one can
|
||||
* cancel the export anytime. */
|
||||
while (g_main_context_pending (NULL))
|
||||
g_main_context_iteration (NULL, FALSE);
|
||||
|
||||
g_signal_emit_by_name (animation, "loading",
|
||||
(gdouble) i / ((gdouble) duration - 0.999),
|
||||
_("Exporting frames"), &stops_loading);
|
||||
if (stops_loading)
|
||||
break;
|
||||
path = g_strdup_printf ("%s-%.*d.%s",
|
||||
filename,
|
||||
(gint) floor (log10(duration)) + 1,
|
||||
i + 1, ext);
|
||||
buffer = animation_playback_get_buffer (playback, i);
|
||||
gegl_node_set (input, "buffer", buffer, NULL);
|
||||
gegl_node_set (export, "path", path, NULL);
|
||||
gegl_node_process (export);
|
||||
g_object_unref (buffer);
|
||||
g_free (path);
|
||||
}
|
||||
g_object_unref (graph);
|
||||
g_signal_emit_by_name (animation, "loaded");
|
||||
}
|
29
plug-ins/animation-play/widgets/animation-dialog-export.h
Normal file
29
plug-ins/animation-play/widgets/animation-dialog-export.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation-dialog-export.h
|
||||
* Copyright (C) 2017 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_DIALOG_EXPORT_H__
|
||||
#define __ANIMATION_DIALOG_EXPORT_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
void animation_dialog_export (GtkWindow *main_dialog,
|
||||
AnimationPlayback *playback);
|
||||
|
||||
#endif /* __ANIMATION_DIALOG_EXPORT_H__ */
|
3286
plug-ins/animation-play/widgets/animation-dialog.c
Executable file
3286
plug-ins/animation-play/widgets/animation-dialog.c
Executable file
File diff suppressed because it is too large
Load Diff
48
plug-ins/animation-play/widgets/animation-dialog.h
Executable file
48
plug-ins/animation-play/widgets/animation-dialog.h
Executable file
@@ -0,0 +1,48 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation-dialog.c
|
||||
* Copyright (C) 2015-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_DIALOG_H__
|
||||
#define __ANIMATION_DIALOG_H__
|
||||
|
||||
#define ANIMATION_TYPE_DIALOG (animation_dialog_get_type ())
|
||||
#define ANIMATION_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_DIALOG, AnimationDialog))
|
||||
#define ANIMATION_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_DIALOG, AnimationDialogClass))
|
||||
#define ANIMATION_IS_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_DIALOG))
|
||||
#define ANIMATION_IS_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_DIALOG))
|
||||
#define ANIMATION_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_DIALOG, AnimationDialogClass))
|
||||
|
||||
typedef struct _AnimationDialog AnimationDialog;
|
||||
typedef struct _AnimationDialogClass AnimationDialogClass;
|
||||
|
||||
struct _AnimationDialog
|
||||
{
|
||||
GtkWindow parent_instance;
|
||||
};
|
||||
|
||||
struct _AnimationDialogClass
|
||||
{
|
||||
GtkWindowClass parent_class;
|
||||
};
|
||||
|
||||
GType animation_dialog_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GtkWidget * animation_dialog_new (gint32 image_id);
|
||||
|
||||
#endif /* __ANIMATION_DIALOG_H__ */
|
@@ -0,0 +1,205 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* Editable label with entry
|
||||
* Copyright (C) 2015-2017 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 <gdk/gdkkeysyms.h>
|
||||
|
||||
#include "animation-editable-label.h"
|
||||
#include "animation-editable-label-string.h"
|
||||
|
||||
struct _AnimationEditableLabelStringPrivate
|
||||
{
|
||||
GtkWidget *editing_widget;
|
||||
};
|
||||
|
||||
static void animation_editable_label_constructed (GObject *object);
|
||||
static void animation_editable_label_icon_clicked (GtkWidget *widget,
|
||||
GtkEntryIconPosition icon_pos,
|
||||
GdkEvent *event,
|
||||
gpointer user_data);
|
||||
static void animation_editable_label_activate (GtkWidget *widget,
|
||||
gpointer user_data);
|
||||
static gboolean animation_editable_label_focus_out (GtkWidget *widget,
|
||||
GdkEvent *event,
|
||||
gpointer user_data);
|
||||
static gboolean animation_editable_label_key_press (GtkWidget *widget,
|
||||
GdkEvent *event,
|
||||
gpointer user_data);
|
||||
static void animation_editable_label_prepare_editing (GtkWidget *widget,
|
||||
gpointer user_data);
|
||||
static void animation_editable_label_editing (GtkWidget *widget,
|
||||
gpointer user_data);
|
||||
|
||||
G_DEFINE_TYPE (AnimationEditableLabelString, animation_editable_label_string, ANIMATION_TYPE_EDITABLE_LABEL)
|
||||
|
||||
#define parent_class animation_editable_label_string_parent_class
|
||||
|
||||
static void
|
||||
animation_editable_label_string_class_init (AnimationEditableLabelStringClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->constructed = animation_editable_label_constructed;
|
||||
|
||||
g_type_class_add_private (klass, sizeof (AnimationEditableLabelStringPrivate));
|
||||
}
|
||||
|
||||
static void
|
||||
animation_editable_label_string_init (AnimationEditableLabelString *label)
|
||||
{
|
||||
label->priv = G_TYPE_INSTANCE_GET_PRIVATE (label,
|
||||
ANIMATION_TYPE_EDITABLE_LABEL_STRING,
|
||||
AnimationEditableLabelStringPrivate);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_editable_label_constructed (GObject *object)
|
||||
{
|
||||
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (object);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->constructed (object);
|
||||
|
||||
/* The editing widget. */
|
||||
gtk_widget_set_can_focus (label->editing_widget, TRUE);
|
||||
gtk_entry_set_icon_from_icon_name (GTK_ENTRY (label->editing_widget),
|
||||
GTK_ENTRY_ICON_SECONDARY,
|
||||
"gtk-apply");
|
||||
if (animation_editable_label_get_text (label))
|
||||
gtk_entry_set_text (GTK_ENTRY (label->editing_widget),
|
||||
animation_editable_label_get_text (label));
|
||||
g_signal_connect (label->editing_widget, "icon-release",
|
||||
G_CALLBACK (animation_editable_label_icon_clicked),
|
||||
label);
|
||||
g_signal_connect (label->editing_widget, "activate",
|
||||
G_CALLBACK (animation_editable_label_activate),
|
||||
label);
|
||||
g_signal_connect (label->editing_widget, "focus-out-event",
|
||||
G_CALLBACK (animation_editable_label_focus_out),
|
||||
label);
|
||||
g_signal_connect (label->editing_widget, "key-press-event",
|
||||
G_CALLBACK (animation_editable_label_key_press),
|
||||
label);
|
||||
gtk_widget_show (label->editing_widget);
|
||||
|
||||
g_signal_connect (label, "prepare-editing",
|
||||
G_CALLBACK (animation_editable_label_prepare_editing),
|
||||
NULL);
|
||||
g_signal_connect (label, "editing",
|
||||
G_CALLBACK (animation_editable_label_editing),
|
||||
NULL);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
animation_editable_label_string_new (const gchar *default_text)
|
||||
{
|
||||
GtkWidget *widget;
|
||||
GtkWidget *entry;
|
||||
|
||||
entry = gtk_entry_new ();
|
||||
widget = g_object_new (ANIMATION_TYPE_EDITABLE_LABEL_STRING,
|
||||
"text", default_text,
|
||||
"editing-widget", entry,
|
||||
NULL);
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
static void
|
||||
animation_editable_label_icon_clicked (GtkWidget *widget,
|
||||
GtkEntryIconPosition icon_pos,
|
||||
GdkEvent *event,
|
||||
gpointer user_data)
|
||||
{
|
||||
animation_editable_label_activate (widget, user_data);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_editable_label_activate (GtkWidget *widget,
|
||||
gpointer user_data)
|
||||
{
|
||||
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (user_data);
|
||||
GtkEntry *entry = GTK_ENTRY (widget);
|
||||
|
||||
g_object_set (label,
|
||||
"text", gtk_entry_get_text (entry),
|
||||
NULL);
|
||||
g_signal_handlers_block_by_func (label->editing_widget,
|
||||
G_CALLBACK (animation_editable_label_focus_out),
|
||||
label);
|
||||
animation_editable_label_set_editing (label, FALSE);
|
||||
g_signal_handlers_unblock_by_func (label->editing_widget,
|
||||
G_CALLBACK (animation_editable_label_focus_out),
|
||||
label);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
animation_editable_label_focus_out (GtkWidget *widget,
|
||||
GdkEvent *event,
|
||||
gpointer user_data)
|
||||
{
|
||||
animation_editable_label_activate (widget, user_data);
|
||||
|
||||
/* Return FALSE because GtkEntry is expecting for this signal. */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
animation_editable_label_key_press (GtkWidget *widget,
|
||||
GdkEvent *event,
|
||||
gpointer user_data)
|
||||
{
|
||||
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (user_data);
|
||||
GdkEventKey *event_key = (GdkEventKey*) event;
|
||||
|
||||
if (event_key->keyval == GDK_KEY_Escape)
|
||||
{
|
||||
/* Discard entry contents and revert to read-only label. */
|
||||
g_signal_handlers_block_by_func (label->editing_widget,
|
||||
G_CALLBACK (animation_editable_label_focus_out),
|
||||
label);
|
||||
animation_editable_label_set_editing (label, FALSE);
|
||||
g_signal_handlers_unblock_by_func (label->editing_widget,
|
||||
G_CALLBACK (animation_editable_label_focus_out),
|
||||
label);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
animation_editable_label_prepare_editing (GtkWidget *widget,
|
||||
gpointer user_data)
|
||||
{
|
||||
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (widget);
|
||||
|
||||
gtk_entry_set_text (GTK_ENTRY (label->editing_widget),
|
||||
animation_editable_label_get_text (label));
|
||||
}
|
||||
|
||||
static void
|
||||
animation_editable_label_editing (GtkWidget *widget,
|
||||
gpointer user_data)
|
||||
{
|
||||
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (widget);
|
||||
|
||||
gtk_widget_grab_focus (GTK_WIDGET (label->editing_widget));
|
||||
gtk_editable_set_position (GTK_EDITABLE (label->editing_widget), -1);
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* Editable label with entry
|
||||
* Copyright (C) 2015-2017 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_EDITABLE_LABEL_STRING_H__
|
||||
#define __ANIMATION_EDITABLE_LABEL_STRING_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#define ANIMATION_TYPE_EDITABLE_LABEL_STRING (animation_editable_label_string_get_type ())
|
||||
#define ANIMATION_EDITABLE_LABEL_STRING(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_EDITABLE_LABEL_STRING, AnimationEditableLabelString))
|
||||
#define ANIMATION_IS_EDITABLE_LABEL_STRING(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_EDITABLE_LABEL_STRING))
|
||||
#define ANIMATION_EDITABLE_LABEL_STRING_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_EDITABLE_LABEL_STRING, AnimationEditableLabelStringClass))
|
||||
#define ANIMATION_IS_EDITABLE_LABEL_STRING_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_EDITABLE_LABEL_STRING))
|
||||
#define ANIMATION_EDITABLE_LABEL_STRING_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_EDITABLE_LABEL_STRING, AnimationEditableLabelStringClass))
|
||||
|
||||
typedef struct _AnimationEditableLabelString AnimationEditableLabelString;
|
||||
typedef struct _AnimationEditableLabelStringClass AnimationEditableLabelStringClass;
|
||||
typedef struct _AnimationEditableLabelStringPrivate AnimationEditableLabelStringPrivate;
|
||||
|
||||
struct _AnimationEditableLabelString
|
||||
{
|
||||
AnimationEditableLabel parent_instance;
|
||||
|
||||
AnimationEditableLabelStringPrivate *priv;
|
||||
};
|
||||
|
||||
struct _AnimationEditableLabelStringClass
|
||||
{
|
||||
AnimationEditableLabelClass parent_class;
|
||||
};
|
||||
|
||||
GType animation_editable_label_string_get_type (void);
|
||||
|
||||
GtkWidget * animation_editable_label_string_new (const gchar *default_text);
|
||||
|
||||
#endif /* __ANIMATION_EDITABLE_LABEL_STRING_H__ */
|
347
plug-ins/animation-play/widgets/animation-editable-label.c
Normal file
347
plug-ins/animation-play/widgets/animation-editable-label.c
Normal file
@@ -0,0 +1,347 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* Editable label base class
|
||||
* Copyright (C) 2015 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 "animation-editable-label.h"
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_TEXT,
|
||||
PROP_EDITING_WIDGET
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
ALTERNATE_ACTION,
|
||||
PREPARE_EDITING,
|
||||
EDITING,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
struct _AnimationEditableLabelPrivate
|
||||
{
|
||||
gchar *text;
|
||||
|
||||
GtkWidget *viewing_widget;
|
||||
GtkWidget *label;
|
||||
|
||||
gboolean is_editing;
|
||||
|
||||
GtkWidget *image;
|
||||
gboolean show_icon;
|
||||
};
|
||||
|
||||
static void animation_editable_label_constructed (GObject *object);
|
||||
static void animation_editable_label_finalize (GObject *object);
|
||||
static void animation_editable_label_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec);
|
||||
static void animation_editable_label_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec);
|
||||
|
||||
static gboolean animation_editable_label_alternate_click (gpointer user_data);
|
||||
static gboolean animation_editable_label_clicked (GtkWidget *widget,
|
||||
GdkEvent *event,
|
||||
gpointer user_data);
|
||||
|
||||
G_DEFINE_TYPE (AnimationEditableLabel, animation_editable_label, GTK_TYPE_FRAME)
|
||||
|
||||
#define parent_class animation_editable_label_parent_class
|
||||
|
||||
static guint animation_editable_label_signals[LAST_SIGNAL] = { 0 };
|
||||
|
||||
static void
|
||||
animation_editable_label_class_init (AnimationEditableLabelClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
animation_editable_label_signals[ALTERNATE_ACTION] =
|
||||
g_signal_new ("alternate-action",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
0,
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
0);
|
||||
animation_editable_label_signals[PREPARE_EDITING] =
|
||||
g_signal_new ("prepare-editing",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
0,
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
0);
|
||||
animation_editable_label_signals[EDITING] =
|
||||
g_signal_new ("editing",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
0,
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
0);
|
||||
|
||||
object_class->constructed = animation_editable_label_constructed;
|
||||
object_class->finalize = animation_editable_label_finalize;
|
||||
object_class->set_property = animation_editable_label_set_property;
|
||||
object_class->get_property = animation_editable_label_get_property;
|
||||
|
||||
g_object_class_install_property (object_class, PROP_TEXT,
|
||||
g_param_spec_string ("text",
|
||||
NULL, NULL,
|
||||
"",
|
||||
G_PARAM_READWRITE |
|
||||
G_PARAM_CONSTRUCT));
|
||||
g_object_class_install_property (object_class, PROP_EDITING_WIDGET,
|
||||
g_param_spec_object ("editing-widget",
|
||||
NULL, NULL,
|
||||
GTK_TYPE_WIDGET,
|
||||
G_PARAM_READWRITE |
|
||||
G_PARAM_CONSTRUCT_ONLY));
|
||||
g_type_class_add_private (klass, sizeof (AnimationEditableLabelPrivate));
|
||||
}
|
||||
|
||||
static void
|
||||
animation_editable_label_init (AnimationEditableLabel *label)
|
||||
{
|
||||
label->priv = G_TYPE_INSTANCE_GET_PRIVATE (label,
|
||||
ANIMATION_TYPE_EDITABLE_LABEL,
|
||||
AnimationEditableLabelPrivate);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_editable_label_constructed (GObject *object)
|
||||
{
|
||||
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (object);
|
||||
GtkWidget *box;
|
||||
GtkWidget *event_box;
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->constructed (object);
|
||||
|
||||
/* The viewing widget. */
|
||||
event_box = gtk_event_box_new ();
|
||||
gtk_widget_add_events (GTK_WIDGET (event_box),
|
||||
GDK_BUTTON_PRESS_MASK);
|
||||
|
||||
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
|
||||
|
||||
label->priv->label = gtk_label_new (label->priv->text? label->priv->text : "");
|
||||
gtk_label_set_justify (GTK_LABEL (label->priv->label), GTK_JUSTIFY_LEFT);
|
||||
gtk_box_pack_start (GTK_BOX (box),
|
||||
label->priv->label,
|
||||
TRUE, TRUE,
|
||||
2.0);
|
||||
gtk_widget_show (label->priv->label);
|
||||
|
||||
label->priv->image = gtk_image_new_from_icon_name ("gtk-edit", GTK_ICON_SIZE_MENU);
|
||||
gtk_box_pack_start (GTK_BOX (box),
|
||||
label->priv->image,
|
||||
FALSE,
|
||||
FALSE,
|
||||
2.0);
|
||||
gtk_widget_show (box);
|
||||
|
||||
gtk_container_add (GTK_CONTAINER (event_box), box);
|
||||
gtk_container_add (GTK_CONTAINER (label),
|
||||
event_box);
|
||||
gtk_widget_show (event_box);
|
||||
label->priv->viewing_widget = event_box;
|
||||
label->priv->is_editing = FALSE;
|
||||
label->priv->show_icon = FALSE;
|
||||
|
||||
g_signal_connect (event_box, "button-press-event",
|
||||
G_CALLBACK (animation_editable_label_clicked),
|
||||
label);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_editable_label_finalize (GObject *object)
|
||||
{
|
||||
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (object);
|
||||
|
||||
if (label->priv->text)
|
||||
g_free (label->priv->text);
|
||||
if (label->priv->is_editing)
|
||||
gtk_widget_destroy (label->priv->viewing_widget);
|
||||
else
|
||||
gtk_widget_destroy (label->editing_widget);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_editable_label_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (object);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_TEXT:
|
||||
if (label->priv->text)
|
||||
g_free (label->priv->text);
|
||||
label->priv->text = g_value_dup_string (value);
|
||||
if (label->priv->label)
|
||||
gtk_label_set_text (GTK_LABEL (label->priv->label),
|
||||
label->priv->text);
|
||||
break;
|
||||
case PROP_EDITING_WIDGET:
|
||||
label->editing_widget = g_value_get_object (value);
|
||||
gtk_widget_show (label->editing_widget);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
animation_editable_label_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (object);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_TEXT:
|
||||
g_value_set_string (value, label->priv->text);
|
||||
break;
|
||||
case PROP_EDITING_WIDGET:
|
||||
g_value_set_object (value, label->editing_widget);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
animation_editable_label_alternate_click (gpointer user_data)
|
||||
{
|
||||
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (user_data);
|
||||
|
||||
if (! label->priv->is_editing)
|
||||
g_signal_emit (label, animation_editable_label_signals[ALTERNATE_ACTION],
|
||||
0, NULL);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
animation_editable_label_clicked (GtkWidget *widget,
|
||||
GdkEvent *event,
|
||||
gpointer user_data)
|
||||
{
|
||||
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (user_data);
|
||||
|
||||
if (event->type == GDK_BUTTON_PRESS)
|
||||
{
|
||||
gint time;
|
||||
|
||||
g_object_get (gtk_settings_get_for_screen (gdk_screen_get_default ()),
|
||||
"gtk-double-click-time", &time,
|
||||
NULL);
|
||||
/* Do not run the alternate action immediately because we want
|
||||
* it to be ignored on a double click. */
|
||||
g_timeout_add (time, animation_editable_label_alternate_click,
|
||||
label);
|
||||
}
|
||||
else if (event->type == GDK_2BUTTON_PRESS)
|
||||
{
|
||||
g_signal_emit (label, animation_editable_label_signals[PREPARE_EDITING],
|
||||
0, NULL);
|
||||
animation_editable_label_set_editing (label, TRUE);
|
||||
g_signal_emit (label, animation_editable_label_signals[EDITING],
|
||||
0, NULL);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
animation_editable_label_set_editing (AnimationEditableLabel *label,
|
||||
gboolean editing)
|
||||
{
|
||||
if (label->priv->is_editing != editing)
|
||||
{
|
||||
GtkWidget *current_widget;
|
||||
GtkWidget *new_widget;
|
||||
|
||||
if (editing)
|
||||
{
|
||||
current_widget = label->priv->viewing_widget;
|
||||
new_widget = label->editing_widget;
|
||||
}
|
||||
else
|
||||
{
|
||||
current_widget = label->editing_widget;
|
||||
new_widget = label->priv->viewing_widget;
|
||||
}
|
||||
g_object_ref (current_widget);
|
||||
gtk_container_remove (GTK_CONTAINER (label),
|
||||
current_widget);
|
||||
gtk_container_add (GTK_CONTAINER (label),
|
||||
new_widget);
|
||||
|
||||
label->priv->is_editing = editing;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
animation_editable_label_show_icon (AnimationEditableLabel *label,
|
||||
gboolean show_icon)
|
||||
{
|
||||
if (label->priv->show_icon != show_icon)
|
||||
{
|
||||
if (show_icon)
|
||||
gtk_widget_show (label->priv->image);
|
||||
else
|
||||
gtk_widget_hide (label->priv->image);
|
||||
label->priv->show_icon = show_icon;
|
||||
}
|
||||
}
|
||||
|
||||
const gchar *
|
||||
animation_editable_label_get_text (AnimationEditableLabel *label)
|
||||
{
|
||||
return label->priv->text;
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
animation_editable_label_get_label (AnimationEditableLabel *label)
|
||||
{
|
||||
return label->priv->label;
|
||||
}
|
64
plug-ins/animation-play/widgets/animation-editable-label.h
Normal file
64
plug-ins/animation-play/widgets/animation-editable-label.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* Editable label base class
|
||||
* Copyright (C) 2015-2017 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_EDITABLE_LABEL_H__
|
||||
#define __ANIMATION_EDITABLE_LABEL_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#define ANIMATION_TYPE_EDITABLE_LABEL (animation_editable_label_get_type ())
|
||||
#define ANIMATION_EDITABLE_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_EDITABLE_LABEL, AnimationEditableLabel))
|
||||
#define ANIMATION_IS_EDITABLE_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_EDITABLE_LABEL))
|
||||
#define ANIMATION_EDITABLE_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_EDITABLE_LABEL, AnimationEditableLabelClass))
|
||||
#define ANIMATION_IS_EDITABLE_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_EDITABLE_LABEL))
|
||||
#define ANIMATION_EDITABLE_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_EDITABLE_LABEL, AnimationEditableLabelClass))
|
||||
|
||||
typedef struct _AnimationEditableLabel AnimationEditableLabel;
|
||||
typedef struct _AnimationEditableLabelClass AnimationEditableLabelClass;
|
||||
typedef struct _AnimationEditableLabelPrivate AnimationEditableLabelPrivate;
|
||||
|
||||
struct _AnimationEditableLabel
|
||||
{
|
||||
GtkFrame parent_instance;
|
||||
|
||||
AnimationEditableLabelPrivate *priv;
|
||||
|
||||
/* Protected variable to be used by the child classes. */
|
||||
GtkWidget *editing_widget;
|
||||
};
|
||||
|
||||
struct _AnimationEditableLabelClass
|
||||
{
|
||||
GtkFrameClass parent_class;
|
||||
};
|
||||
|
||||
GType animation_editable_label_get_type (void);
|
||||
|
||||
void animation_editable_label_set_editing (AnimationEditableLabel *label,
|
||||
gboolean editing);
|
||||
void animation_editable_label_show_icon (AnimationEditableLabel *label,
|
||||
gboolean show_icon);
|
||||
|
||||
const gchar * animation_editable_label_get_text (AnimationEditableLabel *label);
|
||||
|
||||
GtkWidget * animation_editable_label_get_label (AnimationEditableLabel *label);
|
||||
|
||||
#endif /* __ANIMATION_EDITABLE_LABEL_H__ */
|
436
plug-ins/animation-play/widgets/animation-keyframe-view.c
Normal file
436
plug-ins/animation-play/widgets/animation-keyframe-view.c
Normal file
@@ -0,0 +1,436 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation-keyframe_view.c
|
||||
* Copyright (C) 2016-2017 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/animation.h"
|
||||
#include "core/animation-camera.h"
|
||||
#include "core/animation-celanimation.h"
|
||||
|
||||
#include "animation-keyframe-view.h"
|
||||
|
||||
struct _AnimationKeyFrameViewPrivate
|
||||
{
|
||||
AnimationCamera *camera;
|
||||
gint position;
|
||||
|
||||
gboolean local_offset_keyframe;
|
||||
GtkWidget *offset_entry;
|
||||
GtkWidget *delete_offset_button;
|
||||
|
||||
gboolean local_scale_keyframe;
|
||||
GtkWidget *scale_entry;
|
||||
GtkWidget *delete_scale_button;
|
||||
|
||||
guint update_source;
|
||||
gint update_x_offset;
|
||||
gint update_y_offset;
|
||||
gdouble update_scale;
|
||||
gint update_position;
|
||||
};
|
||||
|
||||
/* GObject handlers */
|
||||
static void animation_keyframe_view_constructed (GObject *object);
|
||||
static void animation_keyframe_view_dispose (GObject *object);
|
||||
|
||||
|
||||
static gboolean animation_keyframe_update_source (gpointer user_data);
|
||||
|
||||
static void on_entry_changed (GimpSizeEntry *entry,
|
||||
AnimationKeyFrameView *view);
|
||||
static void on_scale_entry_changed (GtkSpinButton *button,
|
||||
AnimationKeyFrameView *view);
|
||||
static void on_offset_entry_changed (GimpSizeEntry *entry,
|
||||
AnimationKeyFrameView *view);
|
||||
static void on_camera_changed (AnimationCamera *camera,
|
||||
gint position,
|
||||
gint duration,
|
||||
AnimationKeyFrameView *view);
|
||||
static void on_delete_offset_clicked (GtkButton *button,
|
||||
AnimationKeyFrameView *view);
|
||||
static void on_delete_scale_clicked (GtkButton *button,
|
||||
AnimationKeyFrameView *view);
|
||||
|
||||
G_DEFINE_TYPE (AnimationKeyFrameView, animation_keyframe_view, GTK_TYPE_NOTEBOOK)
|
||||
|
||||
#define parent_class animation_keyframe_view_parent_class
|
||||
|
||||
static void
|
||||
animation_keyframe_view_class_init (AnimationKeyFrameViewClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->constructed = animation_keyframe_view_constructed;
|
||||
object_class->dispose = animation_keyframe_view_dispose;
|
||||
|
||||
g_type_class_add_private (klass, sizeof (AnimationKeyFrameViewPrivate));
|
||||
}
|
||||
|
||||
static void
|
||||
animation_keyframe_view_init (AnimationKeyFrameView *view)
|
||||
{
|
||||
view->priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
|
||||
ANIMATION_TYPE_KEYFRAME_VIEW,
|
||||
AnimationKeyFrameViewPrivate);
|
||||
view->priv->position = -1;
|
||||
view->priv->update_position = -1;
|
||||
}
|
||||
|
||||
/************ Public Functions ****************/
|
||||
|
||||
/**
|
||||
* animation_keyframe_view_new:
|
||||
*
|
||||
* Creates a new layer view. You should not show it with
|
||||
* gtk_widget_show() but with animation_keyframe_view_show() instead.
|
||||
*/
|
||||
GtkWidget *
|
||||
animation_keyframe_view_new ()
|
||||
{
|
||||
GtkWidget *view;
|
||||
|
||||
view = g_object_new (ANIMATION_TYPE_KEYFRAME_VIEW,
|
||||
NULL);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* animation_keyframe_view_show:
|
||||
* @view: the #AnimationKeyFrameView.
|
||||
* @animation: the #Animation.
|
||||
* @position:
|
||||
*
|
||||
* Show the @view widget, displaying the keyframes set on
|
||||
* @animation at @position.
|
||||
*/
|
||||
void
|
||||
animation_keyframe_view_show (AnimationKeyFrameView *view,
|
||||
AnimationCelAnimation *animation,
|
||||
gint position)
|
||||
{
|
||||
AnimationCamera *camera;
|
||||
gint32 image_id;
|
||||
gint width;
|
||||
gint height;
|
||||
gdouble xres;
|
||||
gdouble yres;
|
||||
gint x_offset;
|
||||
gint y_offset;
|
||||
gdouble scale;
|
||||
|
||||
camera = ANIMATION_CAMERA (animation_cel_animation_get_main_camera (animation));
|
||||
|
||||
if (view->priv->position != position ||
|
||||
view->priv->camera != camera)
|
||||
{
|
||||
if (view->priv->camera == camera &&
|
||||
view->priv->update_position != -1)
|
||||
{
|
||||
/* We jumped to another position. Apply the ongoing preview. */
|
||||
if (view->priv->local_offset_keyframe)
|
||||
animation_camera_set_offsets (view->priv->camera,
|
||||
view->priv->update_position,
|
||||
view->priv->update_x_offset,
|
||||
view->priv->update_y_offset);
|
||||
if (view->priv->local_scale_keyframe)
|
||||
animation_camera_zoom (view->priv->camera,
|
||||
view->priv->update_position,
|
||||
view->priv->update_scale);
|
||||
}
|
||||
view->priv->camera = camera;
|
||||
view->priv->position = position;
|
||||
|
||||
image_id = animation_get_image_id (ANIMATION (animation));
|
||||
gimp_image_get_resolution (image_id, &xres, &yres);
|
||||
|
||||
animation_get_size (ANIMATION (animation), &width, &height);
|
||||
gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (view->priv->offset_entry),
|
||||
0, (gdouble) -GIMP_MAX_IMAGE_SIZE,
|
||||
(gdouble) GIMP_MAX_IMAGE_SIZE);
|
||||
gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (view->priv->offset_entry),
|
||||
1, (gdouble) -GIMP_MAX_IMAGE_SIZE,
|
||||
(gdouble) GIMP_MAX_IMAGE_SIZE);
|
||||
gimp_size_entry_set_size (GIMP_SIZE_ENTRY (view->priv->offset_entry),
|
||||
0, 0.0, (gdouble) width);
|
||||
gimp_size_entry_set_size (GIMP_SIZE_ENTRY (view->priv->offset_entry),
|
||||
1, 0.0, (gdouble) height);
|
||||
gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (view->priv->offset_entry),
|
||||
0, xres, TRUE);
|
||||
gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (view->priv->offset_entry),
|
||||
1, yres, TRUE);
|
||||
|
||||
g_signal_handlers_disconnect_by_func (view->priv->scale_entry,
|
||||
G_CALLBACK (on_scale_entry_changed),
|
||||
view);
|
||||
g_signal_handlers_disconnect_by_func (view->priv->offset_entry,
|
||||
G_CALLBACK (on_offset_entry_changed),
|
||||
view);
|
||||
g_signal_handlers_disconnect_by_func (view->priv->camera,
|
||||
G_CALLBACK (on_camera_changed),
|
||||
view);
|
||||
animation_camera_reset_preview (camera);
|
||||
animation_camera_get (camera, position, &x_offset, &y_offset, &scale);
|
||||
gimp_size_entry_set_value (GIMP_SIZE_ENTRY (view->priv->offset_entry),
|
||||
0, (gdouble) x_offset);
|
||||
gimp_size_entry_set_value (GIMP_SIZE_ENTRY (view->priv->offset_entry),
|
||||
1, (gdouble) y_offset);
|
||||
gtk_spin_button_set_value (GTK_SPIN_BUTTON (view->priv->scale_entry),
|
||||
scale * 100.0);
|
||||
g_signal_connect (view->priv->scale_entry, "value-changed",
|
||||
G_CALLBACK (on_scale_entry_changed),
|
||||
view);
|
||||
g_signal_connect (view->priv->offset_entry, "value-changed",
|
||||
G_CALLBACK (on_offset_entry_changed),
|
||||
view);
|
||||
g_signal_connect (camera, "camera-changed",
|
||||
G_CALLBACK (on_camera_changed),
|
||||
view);
|
||||
gtk_widget_show (GTK_WIDGET (view));
|
||||
|
||||
if (animation_camera_has_offset_keyframe (camera, view->priv->position))
|
||||
gtk_widget_show (view->priv->delete_offset_button);
|
||||
else
|
||||
gtk_widget_hide (view->priv->delete_offset_button);
|
||||
if (animation_camera_has_zoom_keyframe (camera, view->priv->position))
|
||||
gtk_widget_show (view->priv->delete_scale_button);
|
||||
else
|
||||
gtk_widget_hide (view->priv->delete_scale_button);
|
||||
}
|
||||
}
|
||||
|
||||
/************ Private Functions ****************/
|
||||
|
||||
static void animation_keyframe_view_dispose (GObject *object)
|
||||
{
|
||||
AnimationKeyFrameView *view = ANIMATION_KEYFRAME_VIEW (object);
|
||||
|
||||
if (view->priv->camera)
|
||||
{
|
||||
g_signal_handlers_disconnect_by_func (view->priv->camera,
|
||||
G_CALLBACK (on_camera_changed),
|
||||
view);
|
||||
view->priv->camera = NULL;
|
||||
}
|
||||
G_OBJECT_CLASS (parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_keyframe_view_constructed (GObject *object)
|
||||
{
|
||||
AnimationKeyFrameView *view = ANIMATION_KEYFRAME_VIEW (object);
|
||||
GtkWidget *page;
|
||||
GtkWidget *label;
|
||||
GtkWidget *widget;
|
||||
|
||||
page = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
|
||||
label = gtk_image_new_from_icon_name ("gimp-tool-move",
|
||||
GTK_ICON_SIZE_SMALL_TOOLBAR);
|
||||
gtk_notebook_append_page (GTK_NOTEBOOK (view), page,
|
||||
label);
|
||||
|
||||
view->priv->offset_entry = gimp_size_entry_new (2, GIMP_UNIT_PIXEL, NULL,
|
||||
TRUE, TRUE, FALSE, 5,
|
||||
GIMP_SIZE_ENTRY_UPDATE_SIZE);
|
||||
gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (view->priv->offset_entry),
|
||||
_("Horizontal offset:"), 0, 1, 0.0);
|
||||
gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (view->priv->offset_entry),
|
||||
_("Vertical offset:"), 0, 2, 0.0);
|
||||
gimp_size_entry_set_pixel_digits (GIMP_SIZE_ENTRY (view->priv->offset_entry), 0);
|
||||
gimp_size_entry_show_unit_menu (GIMP_SIZE_ENTRY (view->priv->offset_entry), FALSE);
|
||||
gtk_box_pack_start (GTK_BOX (page), view->priv->offset_entry, FALSE, FALSE, 0);
|
||||
gtk_widget_show (view->priv->offset_entry);
|
||||
|
||||
view->priv->delete_offset_button = gtk_button_new_with_label (_("Reset Offsets"));
|
||||
g_signal_connect (view->priv->delete_offset_button, "clicked",
|
||||
G_CALLBACK (on_delete_offset_clicked),
|
||||
view);
|
||||
gtk_box_pack_end (GTK_BOX (page), view->priv->delete_offset_button, FALSE, FALSE, 0);
|
||||
|
||||
gtk_widget_show (page);
|
||||
|
||||
page = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
|
||||
label = gtk_image_new_from_icon_name ("gimp-scale",
|
||||
GTK_ICON_SIZE_SMALL_TOOLBAR);
|
||||
gtk_notebook_append_page (GTK_NOTEBOOK (view), page,
|
||||
label);
|
||||
|
||||
widget = gtk_label_new (_("Zoom: "));
|
||||
gtk_box_pack_start (GTK_BOX (page), widget, FALSE, FALSE, 0);
|
||||
gtk_widget_show (widget);
|
||||
|
||||
view->priv->scale_entry = gtk_spin_button_new_with_range (0.0, 1000.0, 1.0);
|
||||
gtk_box_pack_start (GTK_BOX (page), view->priv->scale_entry, FALSE, FALSE, 0);
|
||||
gtk_widget_show (view->priv->scale_entry);
|
||||
|
||||
view->priv->delete_scale_button = gtk_button_new_with_label (_("Reset Zoom"));
|
||||
g_signal_connect (view->priv->delete_scale_button, "clicked",
|
||||
G_CALLBACK (on_delete_scale_clicked),
|
||||
view);
|
||||
gtk_box_pack_end (GTK_BOX (page), view->priv->delete_scale_button, FALSE, FALSE, 0);
|
||||
|
||||
gtk_widget_show (page);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
animation_keyframe_update_source (gpointer user_data)
|
||||
{
|
||||
AnimationKeyFrameView *view = user_data;
|
||||
|
||||
view->priv->update_source = 0;
|
||||
/* Only update the preview if we are currently showing this frame. */
|
||||
if (view->priv->position == view->priv->update_position)
|
||||
{
|
||||
animation_camera_preview_keyframe (view->priv->camera,
|
||||
view->priv->update_position,
|
||||
view->priv->update_x_offset,
|
||||
view->priv->update_y_offset,
|
||||
view->priv->update_scale);
|
||||
}
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_entry_changed (GimpSizeEntry *entry G_GNUC_UNUSED,
|
||||
AnimationKeyFrameView *view)
|
||||
{
|
||||
gdouble x_offset;
|
||||
gdouble y_offset;
|
||||
gdouble scale;
|
||||
|
||||
/* If a timeout is pending, remove before recreating in order to
|
||||
* postpone the camera update. */
|
||||
if (view->priv->update_source)
|
||||
{
|
||||
g_source_remove (view->priv->update_source);
|
||||
}
|
||||
|
||||
scale = gtk_spin_button_get_value (GTK_SPIN_BUTTON (view->priv->scale_entry)) / 100.0;
|
||||
x_offset = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (view->priv->offset_entry), 0);
|
||||
y_offset = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (view->priv->offset_entry), 1);
|
||||
|
||||
view->priv->update_x_offset = x_offset;
|
||||
view->priv->update_y_offset = y_offset;
|
||||
view->priv->update_scale = scale;
|
||||
view->priv->update_position = view->priv->position;
|
||||
view->priv->update_source = g_timeout_add (10, animation_keyframe_update_source, view);
|
||||
}
|
||||
|
||||
static void
|
||||
on_scale_entry_changed (GtkSpinButton *button G_GNUC_UNUSED,
|
||||
AnimationKeyFrameView *view)
|
||||
{
|
||||
view->priv->local_scale_keyframe = TRUE;
|
||||
gtk_widget_show (view->priv->delete_scale_button);
|
||||
on_entry_changed (NULL, view);
|
||||
}
|
||||
|
||||
static void
|
||||
on_offset_entry_changed (GimpSizeEntry *entry G_GNUC_UNUSED,
|
||||
AnimationKeyFrameView *view)
|
||||
{
|
||||
view->priv->local_offset_keyframe = TRUE;
|
||||
gtk_widget_show (view->priv->delete_offset_button);
|
||||
on_entry_changed (NULL, view);
|
||||
}
|
||||
|
||||
static void
|
||||
on_camera_changed (AnimationCamera *camera,
|
||||
gint position,
|
||||
gint duration,
|
||||
AnimationKeyFrameView *view)
|
||||
{
|
||||
if (view->priv->position >= position &&
|
||||
view->priv->position < position + duration)
|
||||
{
|
||||
gint x_offset;
|
||||
gint y_offset;
|
||||
gdouble scale;
|
||||
|
||||
g_signal_handlers_block_by_func (view->priv->offset_entry,
|
||||
G_CALLBACK (on_offset_entry_changed),
|
||||
view);
|
||||
g_signal_handlers_block_by_func (view->priv->offset_entry,
|
||||
G_CALLBACK (on_scale_entry_changed),
|
||||
view);
|
||||
animation_camera_get (camera, view->priv->position,
|
||||
&x_offset, &y_offset, &scale);
|
||||
gimp_size_entry_set_value (GIMP_SIZE_ENTRY (view->priv->offset_entry),
|
||||
0, (gdouble) x_offset);
|
||||
gimp_size_entry_set_value (GIMP_SIZE_ENTRY (view->priv->offset_entry),
|
||||
1, (gdouble) y_offset);
|
||||
gtk_spin_button_set_value (GTK_SPIN_BUTTON (view->priv->scale_entry),
|
||||
scale * 100.0);
|
||||
g_signal_handlers_unblock_by_func (view->priv->offset_entry,
|
||||
G_CALLBACK (on_offset_entry_changed),
|
||||
view);
|
||||
g_signal_handlers_unblock_by_func (view->priv->offset_entry,
|
||||
G_CALLBACK (on_scale_entry_changed),
|
||||
view);
|
||||
|
||||
if (animation_camera_has_offset_keyframe (camera, view->priv->position) ||
|
||||
view->priv->local_offset_keyframe)
|
||||
gtk_widget_show (view->priv->delete_offset_button);
|
||||
else
|
||||
gtk_widget_hide (view->priv->delete_offset_button);
|
||||
if (animation_camera_has_zoom_keyframe (camera, view->priv->position) ||
|
||||
view->priv->local_scale_keyframe)
|
||||
gtk_widget_show (view->priv->delete_scale_button);
|
||||
else
|
||||
gtk_widget_hide (view->priv->delete_scale_button);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_delete_offset_clicked (GtkButton *button,
|
||||
AnimationKeyFrameView *view)
|
||||
{
|
||||
g_signal_handlers_block_by_func (view->priv->scale_entry,
|
||||
G_CALLBACK (on_offset_entry_changed),
|
||||
view);
|
||||
view->priv->local_offset_keyframe = FALSE;
|
||||
animation_camera_delete_offset_keyframe (view->priv->camera,
|
||||
view->priv->position);
|
||||
gtk_widget_hide (view->priv->delete_offset_button);
|
||||
g_signal_handlers_unblock_by_func (view->priv->scale_entry,
|
||||
G_CALLBACK (on_offset_entry_changed),
|
||||
view);
|
||||
}
|
||||
|
||||
static void
|
||||
on_delete_scale_clicked (GtkButton *button,
|
||||
AnimationKeyFrameView *view)
|
||||
{
|
||||
g_signal_handlers_block_by_func (view->priv->scale_entry,
|
||||
G_CALLBACK (on_scale_entry_changed),
|
||||
view);
|
||||
view->priv->local_scale_keyframe = FALSE;
|
||||
animation_camera_delete_zoom_keyframe (view->priv->camera,
|
||||
view->priv->position);
|
||||
gtk_widget_hide (view->priv->delete_scale_button);
|
||||
g_signal_handlers_unblock_by_func (view->priv->scale_entry,
|
||||
G_CALLBACK (on_scale_entry_changed),
|
||||
view);
|
||||
}
|
55
plug-ins/animation-play/widgets/animation-keyframe-view.h
Normal file
55
plug-ins/animation-play/widgets/animation-keyframe-view.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation-keyframe_view.h
|
||||
* Copyright (C) 2016-2017 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_KEYFRAME_VIEW_H__
|
||||
#define __ANIMATION_KEYFRAME_VIEW_H__
|
||||
|
||||
#define ANIMATION_TYPE_KEYFRAME_VIEW (animation_keyframe_view_get_type ())
|
||||
#define ANIMATION_KEYFRAME_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_KEYFRAME_VIEW, AnimationKeyFrameView))
|
||||
#define ANIMATION_KEYFRAME_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_KEYFRAME_VIEW, AnimationKeyFrameViewClass))
|
||||
#define ANIMATION_IS_KEYFRAME_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_KEYFRAME_VIEW))
|
||||
#define ANIMATION_IS_KEYFRAME_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_KEYFRAME_VIEW))
|
||||
#define ANIMATION_KEYFRAME_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_KEYFRAME_VIEW, AnimationKeyFrameViewClass))
|
||||
|
||||
typedef struct _AnimationKeyFrameView AnimationKeyFrameView;
|
||||
typedef struct _AnimationKeyFrameViewClass AnimationKeyFrameViewClass;
|
||||
typedef struct _AnimationKeyFrameViewPrivate AnimationKeyFrameViewPrivate;
|
||||
|
||||
struct _AnimationKeyFrameView
|
||||
{
|
||||
GtkNotebook parent_instance;
|
||||
|
||||
AnimationKeyFrameViewPrivate *priv;
|
||||
};
|
||||
|
||||
struct _AnimationKeyFrameViewClass
|
||||
{
|
||||
GtkNotebookClass parent_class;
|
||||
};
|
||||
|
||||
GType animation_keyframe_view_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GtkWidget * animation_keyframe_view_new (void);
|
||||
|
||||
void animation_keyframe_view_show (AnimationKeyFrameView *view,
|
||||
AnimationCelAnimation *animation,
|
||||
gint position);
|
||||
|
||||
#endif /* __ANIMATION_KEYFRAME_VIEW_H__ */
|
659
plug-ins/animation-play/widgets/animation-layer-view.c
Normal file
659
plug-ins/animation-play/widgets/animation-layer-view.c
Normal file
@@ -0,0 +1,659 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation-layer_view.c
|
||||
* Copyright (C) 2015 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 "animation-layer-view.h"
|
||||
|
||||
/* Properties. */
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_IMAGE
|
||||
};
|
||||
|
||||
/* Tree model rows. */
|
||||
enum
|
||||
{
|
||||
COLUMN_LAYER_TATTOO,
|
||||
COLUMN_LAYER_NAME,
|
||||
COLUMN_SIZE
|
||||
};
|
||||
|
||||
/* Signals. */
|
||||
enum
|
||||
{
|
||||
LAYER_SELECTION,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
struct _AnimationLayerViewPrivate
|
||||
{
|
||||
gint32 image_id;
|
||||
|
||||
GtkWidget *tree_view;
|
||||
GtkWidget *filter_button;
|
||||
|
||||
gboolean filter_active;
|
||||
gchar *filter;
|
||||
};
|
||||
|
||||
/* GObject handlers */
|
||||
static void animation_layer_view_constructed (GObject *object);
|
||||
static void animation_layer_view_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec);
|
||||
static void animation_layer_view_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec);
|
||||
|
||||
/* GtkWidget handlers */
|
||||
static gboolean animation_layer_view_button_press (GtkWidget *widget,
|
||||
GdkEventButton *event);
|
||||
|
||||
/* Utils */
|
||||
static gboolean animation_layer_view_keep_group (AnimationLayerView *view,
|
||||
gint parent_layer);
|
||||
static void animation_layer_view_fill (AnimationLayerView *view,
|
||||
GtkTreeStore *store,
|
||||
gboolean ignore_filter,
|
||||
gint parent_layer,
|
||||
GtkTreeIter *parent);
|
||||
static GtkTreePath * animation_layer_view_get_row (AnimationLayerView *view,
|
||||
gint tattoo,
|
||||
GtkTreeIter *parent);
|
||||
|
||||
/* Signal handlers */
|
||||
static void on_selection_changed (GtkTreeSelection *selection,
|
||||
AnimationLayerView *view);
|
||||
static void on_filter_toggled (GtkToggleButton *button,
|
||||
AnimationLayerView *view);
|
||||
|
||||
G_DEFINE_TYPE (AnimationLayerView, animation_layer_view, GTK_TYPE_VBOX)
|
||||
|
||||
#define parent_class animation_layer_view_parent_class
|
||||
|
||||
static guint animation_layer_view_signals[LAST_SIGNAL] = { 0 };
|
||||
|
||||
static void
|
||||
animation_layer_view_class_init (AnimationLayerViewClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
|
||||
/**
|
||||
* AnimationLayerView::layer-selection:
|
||||
* @layer_view: the widget which received the signal.
|
||||
* @layers: the #GList of layer tattoos which are currently selected.
|
||||
*
|
||||
* The ::layer-selection signal is emitted each time the selection changes
|
||||
* in @layer_view.
|
||||
*/
|
||||
animation_layer_view_signals[LAYER_SELECTION] =
|
||||
g_signal_new ("layer-selection",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
0,
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_POINTER);
|
||||
|
||||
object_class->constructed = animation_layer_view_constructed;
|
||||
object_class->get_property = animation_layer_view_get_property;
|
||||
object_class->set_property = animation_layer_view_set_property;
|
||||
|
||||
widget_class->button_press_event = animation_layer_view_button_press;
|
||||
|
||||
/**
|
||||
* AnimationLayerView:animation:
|
||||
*
|
||||
* The associated #GimpImage id.
|
||||
*/
|
||||
g_object_class_install_property (object_class, PROP_IMAGE,
|
||||
g_param_spec_int ("image", NULL, NULL,
|
||||
0, G_MAXINT, 0,
|
||||
G_PARAM_READWRITE |
|
||||
G_PARAM_CONSTRUCT_ONLY));
|
||||
|
||||
g_type_class_add_private (klass, sizeof (AnimationLayerViewPrivate));
|
||||
}
|
||||
|
||||
static void
|
||||
animation_layer_view_init (AnimationLayerView *view)
|
||||
{
|
||||
GtkTreeStore *store;
|
||||
|
||||
view->priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
|
||||
ANIMATION_TYPE_LAYER_VIEW,
|
||||
AnimationLayerViewPrivate);
|
||||
|
||||
store = gtk_tree_store_new (COLUMN_SIZE, G_TYPE_INT, G_TYPE_STRING);
|
||||
view->priv->tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
|
||||
g_object_unref (store);
|
||||
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view->priv->tree_view),
|
||||
0, _("Layer"),
|
||||
gtk_cell_renderer_text_new (),
|
||||
"text", COLUMN_LAYER_NAME,
|
||||
NULL);
|
||||
gtk_tree_view_set_rubber_banding (GTK_TREE_VIEW (view->priv->tree_view),
|
||||
TRUE);
|
||||
gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view->priv->tree_view), FALSE);
|
||||
gtk_box_pack_start (GTK_BOX (view), view->priv->tree_view, TRUE, TRUE, 0);
|
||||
gtk_widget_show (view->priv->tree_view);
|
||||
}
|
||||
|
||||
/************ Public Functions ****************/
|
||||
|
||||
/**
|
||||
* animation_layer_view_new:
|
||||
* @image_id: the #GimpImage id.
|
||||
*
|
||||
* Creates a new layer view tied to @image_id, ready to be displayed.
|
||||
*/
|
||||
GtkWidget *
|
||||
animation_layer_view_new (gint32 image_id)
|
||||
{
|
||||
GtkWidget *layer_view;
|
||||
|
||||
layer_view = g_object_new (ANIMATION_TYPE_LAYER_VIEW,
|
||||
"image", image_id,
|
||||
NULL);
|
||||
return layer_view;
|
||||
}
|
||||
|
||||
/**
|
||||
* animation_layer_view_refresh:
|
||||
* @view: the #AnimationLayerView.
|
||||
*
|
||||
* Refresh the list of layers by reloading the #GimpImage layers.
|
||||
*/
|
||||
void
|
||||
animation_layer_view_refresh (AnimationLayerView *view)
|
||||
{
|
||||
GtkTreeModel *model;
|
||||
GtkTreeSelection *selection;
|
||||
GList *rows;
|
||||
GList *iter;
|
||||
GList *tattoos = NULL;
|
||||
|
||||
model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->priv->tree_view));
|
||||
|
||||
/* Save current selection. */
|
||||
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->priv->tree_view));
|
||||
rows = gtk_tree_selection_get_selected_rows (selection, &model);
|
||||
for (iter = rows; iter; iter = iter->next)
|
||||
{
|
||||
GtkTreeIter it;
|
||||
gint tattoo;
|
||||
|
||||
if (gtk_tree_model_get_iter (model, &it, iter->data))
|
||||
{
|
||||
gtk_tree_model_get (model, &it,
|
||||
COLUMN_LAYER_TATTOO, &tattoo, -1);
|
||||
tattoos = g_list_prepend (tattoos, GINT_TO_POINTER (tattoo));
|
||||
}
|
||||
}
|
||||
g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
|
||||
g_list_free (rows);
|
||||
|
||||
/* Actual refresh. */
|
||||
gtk_tree_store_clear (GTK_TREE_STORE (model));
|
||||
animation_layer_view_fill (view, GTK_TREE_STORE (model), FALSE, 0, NULL);
|
||||
|
||||
/* Restore the selected rows. */
|
||||
for (iter = tattoos; iter; iter = iter->next)
|
||||
{
|
||||
gint tattoo = GPOINTER_TO_INT (iter->data);
|
||||
GtkTreePath *path;
|
||||
|
||||
path = animation_layer_view_get_row (view, tattoo, NULL);
|
||||
if (path)
|
||||
{
|
||||
gtk_tree_selection_select_path (selection, path);
|
||||
gtk_tree_path_free (path);
|
||||
}
|
||||
}
|
||||
g_list_free (tattoos);
|
||||
}
|
||||
|
||||
void
|
||||
animation_layer_view_filter (AnimationLayerView *view,
|
||||
const gchar *filter)
|
||||
{
|
||||
if (g_strcmp0 (view->priv->filter, filter) != 0)
|
||||
{
|
||||
if (view->priv->filter)
|
||||
g_free (view->priv->filter);
|
||||
view->priv->filter = g_strdup (filter);
|
||||
|
||||
if (view->priv->filter_active)
|
||||
animation_layer_view_refresh (view);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* animation_layer_view_select:
|
||||
* @view: the #AnimationLayerView.
|
||||
* @layers: a #GList of #GimpLayer ids.
|
||||
* @filter: the viewing filter.
|
||||
*
|
||||
* Selects the rows for all @layers in @view.
|
||||
*/
|
||||
void
|
||||
animation_layer_view_select (AnimationLayerView *view,
|
||||
const GList *layers,
|
||||
const gchar *filter)
|
||||
{
|
||||
GtkTreeSelection *selection;
|
||||
const GList *layer;
|
||||
GtkToggleButton *filter_button;
|
||||
gboolean filter_was_active;
|
||||
|
||||
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->priv->tree_view));
|
||||
g_signal_handlers_block_by_func (selection,
|
||||
G_CALLBACK (on_selection_changed),
|
||||
view);
|
||||
|
||||
filter_button = GTK_TOGGLE_BUTTON (view->priv->filter_button);
|
||||
filter_was_active = view->priv->filter_active;
|
||||
/* Deactivate the filtering. */
|
||||
if (filter_was_active)
|
||||
gtk_toggle_button_set_active (filter_button, FALSE);
|
||||
|
||||
/* Change the filter but do *not* refresh the GUI. */
|
||||
if (g_strcmp0 (view->priv->filter, filter) != 0)
|
||||
{
|
||||
if (view->priv->filter)
|
||||
g_free (view->priv->filter);
|
||||
view->priv->filter = g_strdup (filter);
|
||||
}
|
||||
|
||||
gtk_tree_selection_unselect_all (selection);
|
||||
for (layer = layers; layer; layer = layer->next)
|
||||
{
|
||||
GtkTreePath *path;
|
||||
gint tattoo = GPOINTER_TO_INT (layer->data);
|
||||
|
||||
path = animation_layer_view_get_row (view, tattoo, NULL);
|
||||
g_warn_if_fail (path != NULL);
|
||||
if (path)
|
||||
{
|
||||
gtk_tree_selection_select_path (selection, path);
|
||||
gtk_tree_path_free (path);
|
||||
}
|
||||
}
|
||||
/* Reactivate the filtering. */
|
||||
if (filter_was_active)
|
||||
gtk_toggle_button_set_active (filter_button, TRUE);
|
||||
|
||||
g_signal_handlers_unblock_by_func (selection,
|
||||
G_CALLBACK (on_selection_changed),
|
||||
view);
|
||||
}
|
||||
|
||||
/************ Private Functions ****************/
|
||||
|
||||
static void
|
||||
animation_layer_view_constructed (GObject *object)
|
||||
{
|
||||
AnimationLayerView *view = ANIMATION_LAYER_VIEW (object);
|
||||
GtkTreeView *tree_view = GTK_TREE_VIEW (view->priv->tree_view);
|
||||
GtkTreeSelection *selection;
|
||||
|
||||
selection = gtk_tree_view_get_selection (tree_view);
|
||||
gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
|
||||
|
||||
g_signal_connect (selection, "changed",
|
||||
G_CALLBACK (on_selection_changed),
|
||||
view);
|
||||
|
||||
view->priv->filter_button = gtk_check_button_new_with_label (_("Filter by level title"));
|
||||
gtk_box_pack_start (GTK_BOX (view), view->priv->filter_button, FALSE, FALSE, 0);
|
||||
g_signal_connect (view->priv->filter_button, "toggled",
|
||||
G_CALLBACK (on_filter_toggled),
|
||||
view);
|
||||
gtk_widget_show (view->priv->filter_button);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_layer_view_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
AnimationLayerView *view = ANIMATION_LAYER_VIEW (object);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_IMAGE:
|
||||
view->priv->image_id = g_value_get_int (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
animation_layer_view_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
AnimationLayerView *view = ANIMATION_LAYER_VIEW (object);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_IMAGE:
|
||||
g_value_set_int (value, view->priv->image_id);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
animation_layer_view_button_press (GtkWidget *widget,
|
||||
GdkEventButton *event)
|
||||
{
|
||||
AnimationLayerView *view = ANIMATION_LAYER_VIEW (widget);
|
||||
GtkTreeView *tree_view = GTK_TREE_VIEW (view->priv->tree_view);
|
||||
GtkTreeModel *model;
|
||||
GtkTreeSelection *selection;
|
||||
GtkTreeRowReference *reference = NULL;
|
||||
GList *rows;
|
||||
|
||||
model = gtk_tree_view_get_model (tree_view);
|
||||
selection = gtk_tree_view_get_selection (tree_view);
|
||||
|
||||
rows = gtk_tree_selection_get_selected_rows (selection, &model);
|
||||
if (g_list_length (rows) == 1)
|
||||
{
|
||||
reference = gtk_tree_row_reference_new (model, rows->data);
|
||||
}
|
||||
g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
|
||||
g_list_free (rows);
|
||||
|
||||
g_signal_handlers_block_by_func (selection,
|
||||
G_CALLBACK (on_selection_changed),
|
||||
tree_view);
|
||||
GTK_WIDGET_CLASS (animation_layer_view_parent_class)->button_press_event (widget, event);
|
||||
g_signal_handlers_unblock_by_func (selection,
|
||||
G_CALLBACK (on_selection_changed),
|
||||
tree_view);
|
||||
|
||||
rows = gtk_tree_selection_get_selected_rows (selection, &model);
|
||||
if (g_list_length (rows) == 1 && reference != NULL)
|
||||
{
|
||||
GtkTreePath *prev_path = gtk_tree_row_reference_get_path (reference);
|
||||
GtkTreePath *new_path = rows->data;
|
||||
|
||||
/* We keep globally the same behavior as default tree view, except that
|
||||
* when there is only 1 item selected and you click it, you unselect it.
|
||||
* You also unselect it by clicking outside any item.
|
||||
*/
|
||||
if (gtk_tree_path_compare (new_path, prev_path) == 0)
|
||||
{
|
||||
gtk_tree_selection_unselect_path (selection, new_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Since we blocked the signal callback, call it ourselves. */
|
||||
on_selection_changed (selection, ANIMATION_LAYER_VIEW (widget));
|
||||
}
|
||||
gtk_tree_path_free (prev_path);
|
||||
gtk_tree_row_reference_free (reference);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Since we blocked the signal callback, call it ourselves. */
|
||||
on_selection_changed (selection, ANIMATION_LAYER_VIEW (widget));
|
||||
}
|
||||
g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
|
||||
g_list_free (rows);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
animation_layer_view_keep_group (AnimationLayerView *view,
|
||||
gint parent_layer)
|
||||
{
|
||||
gint *layers;
|
||||
gint num_layers;
|
||||
gboolean keep = FALSE;
|
||||
gint i;
|
||||
|
||||
g_return_val_if_fail (gimp_item_is_group (parent_layer), FALSE);
|
||||
|
||||
layers = gimp_item_get_children (parent_layer, &num_layers);
|
||||
for (i = 0; i < num_layers; i++)
|
||||
{
|
||||
const gchar *name = gimp_item_get_name (layers[i]);
|
||||
|
||||
if (view->priv->filter_active && view->priv->filter &&
|
||||
g_str_has_prefix (name, view->priv->filter))
|
||||
{
|
||||
keep = TRUE;
|
||||
break;
|
||||
}
|
||||
if (gimp_item_is_group (layers[i]) &&
|
||||
animation_layer_view_keep_group (view, layers[i]))
|
||||
{
|
||||
keep = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_free (layers);
|
||||
return keep;
|
||||
}
|
||||
|
||||
/* animation_layer_view_fill:
|
||||
* @view: the #AnimationLayerView.
|
||||
* @store: the #GtkTreeStore to fill.
|
||||
* @ignore_filter: insert all layers under @parent_layer.
|
||||
* @parent_layer: the parent #GimpLayer id. Set 0 for first call.
|
||||
* @parent: %NULL to search from the first call (used for recursivity).
|
||||
*
|
||||
* Recursively fills @store with the #GimpLayers data of the #GimpImage
|
||||
* tied to @view.
|
||||
*/
|
||||
static void
|
||||
animation_layer_view_fill (AnimationLayerView *view,
|
||||
GtkTreeStore *store,
|
||||
gboolean ignore_filter,
|
||||
gint parent_layer,
|
||||
GtkTreeIter *parent)
|
||||
{
|
||||
gint *layers;
|
||||
gint num_layers;
|
||||
GtkTreeIter iter;
|
||||
gint i;
|
||||
|
||||
if (parent_layer == 0)
|
||||
{
|
||||
layers = gimp_image_get_layers (view->priv->image_id,
|
||||
&num_layers);
|
||||
}
|
||||
else
|
||||
{
|
||||
layers = gimp_item_get_children (parent_layer, &num_layers);
|
||||
}
|
||||
|
||||
for (i = 0; i < num_layers; i++)
|
||||
{
|
||||
const gchar *name = gimp_item_get_name (layers[i]);
|
||||
gboolean keep_group;
|
||||
|
||||
if (! ignore_filter &&
|
||||
view->priv->filter_active &&
|
||||
view->priv->filter &&
|
||||
! gimp_item_is_group (layers[i]) &&
|
||||
! g_str_has_prefix (name, view->priv->filter))
|
||||
continue;
|
||||
|
||||
keep_group = gimp_item_is_group (layers[i]) &&
|
||||
(ignore_filter ||
|
||||
! view->priv->filter_active ||
|
||||
! view->priv->filter ||
|
||||
g_str_has_prefix (name, view->priv->filter) ||
|
||||
animation_layer_view_keep_group (view, layers[i]));
|
||||
if (! gimp_item_is_group (layers[i]) || keep_group)
|
||||
{
|
||||
gtk_tree_store_insert (store, &iter, parent, i);
|
||||
gtk_tree_store_set (store, &iter,
|
||||
COLUMN_LAYER_TATTOO, gimp_item_get_tattoo (layers[i]),
|
||||
COLUMN_LAYER_NAME, name,
|
||||
-1);
|
||||
}
|
||||
if (gimp_item_is_group (layers[i]) && keep_group)
|
||||
{
|
||||
/* We ignore the filter for children if this group name passes
|
||||
* the filter. */
|
||||
animation_layer_view_fill (view, store,
|
||||
ignore_filter ||
|
||||
(view->priv->filter_active &&
|
||||
view->priv->filter &&
|
||||
g_str_has_prefix (name, view->priv->filter)),
|
||||
layers[i], &iter);
|
||||
}
|
||||
}
|
||||
g_free (layers);
|
||||
}
|
||||
|
||||
/* animation_layer_view_get_row:
|
||||
* @view: the #AnimationLayerView.
|
||||
* @tattoo: the #GimpLayer tattoo.
|
||||
* @parent: %NULL to search from the first call (used for recursivity).
|
||||
*
|
||||
* Returns: the #GtkTreePath for the row of @tattoo, NULL if not found
|
||||
* in @view.
|
||||
* The returned path should be freed with gtk_tree_path_free()
|
||||
*/
|
||||
static GtkTreePath *
|
||||
animation_layer_view_get_row (AnimationLayerView *view,
|
||||
gint tattoo,
|
||||
GtkTreeIter *parent)
|
||||
{
|
||||
GtkTreeModel *model;
|
||||
GtkTreeIter iter;
|
||||
|
||||
model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->priv->tree_view));
|
||||
if (! gtk_tree_model_iter_children (model, &iter, parent))
|
||||
return NULL;
|
||||
|
||||
do
|
||||
{
|
||||
GtkTreePath *path = NULL;
|
||||
GValue value = { 0, };
|
||||
|
||||
gtk_tree_model_get_value (model, &iter,
|
||||
COLUMN_LAYER_TATTOO,
|
||||
&value);
|
||||
if (g_value_get_int (&value) == tattoo)
|
||||
path = gtk_tree_model_get_path (model, &iter);
|
||||
|
||||
g_value_unset (&value);
|
||||
|
||||
if (path)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
else if (gtk_tree_model_iter_has_child (model, &iter))
|
||||
{
|
||||
GtkTreePath *found_path;
|
||||
|
||||
found_path = animation_layer_view_get_row (view, tattoo, &iter);
|
||||
|
||||
if (found_path)
|
||||
return found_path;
|
||||
}
|
||||
}
|
||||
while (gtk_tree_model_iter_next (model, &iter));
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
on_selection_changed (GtkTreeSelection *selection,
|
||||
AnimationLayerView *view)
|
||||
{
|
||||
GtkTreeView *tree_view = GTK_TREE_VIEW (view->priv->tree_view);
|
||||
GList *layers = NULL;
|
||||
GtkTreeModel *model;
|
||||
GList *rows;
|
||||
GList *row;
|
||||
|
||||
model = gtk_tree_view_get_model (tree_view);
|
||||
rows = gtk_tree_selection_get_selected_rows (selection, &model);
|
||||
|
||||
for (row = rows; row; row = row->next)
|
||||
{
|
||||
GtkTreePath *path = row->data;
|
||||
GtkTreeIter iter;
|
||||
|
||||
if (gtk_tree_model_get_iter (model, &iter, path))
|
||||
{
|
||||
GValue value = { 0, };
|
||||
|
||||
gtk_tree_model_get_value (model, &iter,
|
||||
COLUMN_LAYER_TATTOO,
|
||||
&value);
|
||||
layers = g_list_prepend (layers,
|
||||
GINT_TO_POINTER (g_value_get_int (&value)));
|
||||
g_value_unset (&value);
|
||||
|
||||
gtk_tree_model_get_value (model, &iter,
|
||||
COLUMN_LAYER_NAME,
|
||||
&value);
|
||||
g_value_unset (&value);
|
||||
}
|
||||
}
|
||||
|
||||
g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
|
||||
g_list_free (rows);
|
||||
|
||||
g_signal_emit (view, animation_layer_view_signals[LAYER_SELECTION], 0,
|
||||
layers);
|
||||
g_list_free (layers);
|
||||
}
|
||||
|
||||
static void
|
||||
on_filter_toggled (GtkToggleButton *button,
|
||||
AnimationLayerView *view)
|
||||
{
|
||||
if (gtk_toggle_button_get_active (button) != view->priv->filter_active)
|
||||
{
|
||||
view->priv->filter_active = gtk_toggle_button_get_active (button);
|
||||
animation_layer_view_refresh (view);
|
||||
}
|
||||
}
|
59
plug-ins/animation-play/widgets/animation-layer-view.h
Normal file
59
plug-ins/animation-play/widgets/animation-layer-view.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation-layer_view.h
|
||||
* Copyright (C) 2015 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_LAYER_VIEW_H__
|
||||
#define __ANIMATION_LAYER_VIEW_H__
|
||||
|
||||
#define ANIMATION_TYPE_LAYER_VIEW (animation_layer_view_get_type ())
|
||||
#define ANIMATION_LAYER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_LAYER_VIEW, AnimationLayerView))
|
||||
#define ANIMATION_LAYER_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_LAYER_VIEW, AnimationLayerViewClass))
|
||||
#define ANIMATION_IS_LAYER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_LAYER_VIEW))
|
||||
#define ANIMATION_IS_LAYER_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_LAYER_VIEW))
|
||||
#define ANIMATION_LAYER_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_LAYER_VIEW, AnimationLayerViewClass))
|
||||
|
||||
typedef struct _AnimationLayerView AnimationLayerView;
|
||||
typedef struct _AnimationLayerViewClass AnimationLayerViewClass;
|
||||
typedef struct _AnimationLayerViewPrivate AnimationLayerViewPrivate;
|
||||
|
||||
struct _AnimationLayerView
|
||||
{
|
||||
GtkVBox parent_instance;
|
||||
|
||||
AnimationLayerViewPrivate *priv;
|
||||
};
|
||||
|
||||
struct _AnimationLayerViewClass
|
||||
{
|
||||
GtkVBoxClass parent_class;
|
||||
};
|
||||
|
||||
GType animation_layer_view_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GtkWidget * animation_layer_view_new (gint32 image_id);
|
||||
|
||||
void animation_layer_view_refresh (AnimationLayerView *view);
|
||||
|
||||
void animation_layer_view_filter (AnimationLayerView *view,
|
||||
const gchar *filter);
|
||||
void animation_layer_view_select (AnimationLayerView *view,
|
||||
const GList *layers,
|
||||
const gchar *filter);
|
||||
#endif /* __ANIMATION_LAYER_VIEW_H__ */
|
||||
|
140
plug-ins/animation-play/widgets/animation-menus.c
Normal file
140
plug-ins/animation-play/widgets/animation-menus.c
Normal file
@@ -0,0 +1,140 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation-menus.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 <string.h>
|
||||
|
||||
#include <libgimp/gimp.h>
|
||||
#include <libgimp/gimpui.h>
|
||||
|
||||
#include "libgimp/stdplugins-intl.h"
|
||||
|
||||
#include "core/animation-celanimation.h"
|
||||
#include "core/animation-playback.h"
|
||||
|
||||
#include "animation-xsheet.h"
|
||||
|
||||
#include "animation-menus.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
AnimationCelAnimation *animation;
|
||||
gint level;
|
||||
gint position;
|
||||
gboolean dup_previous;
|
||||
}
|
||||
CellData;
|
||||
|
||||
static void on_add_cell (GtkMenuItem *menuitem,
|
||||
gpointer user_data);
|
||||
static void on_delete_cell (GtkMenuItem *menuitem,
|
||||
gpointer user_data);
|
||||
|
||||
|
||||
static void
|
||||
on_add_cell (GtkMenuItem *menuitem,
|
||||
gpointer user_data)
|
||||
{
|
||||
CellData *data = user_data;
|
||||
|
||||
animation_cel_animation_cel_add (data->animation,
|
||||
data->level,
|
||||
data->position,
|
||||
data->dup_previous);
|
||||
}
|
||||
|
||||
static void
|
||||
on_delete_cell (GtkMenuItem *menuitem,
|
||||
gpointer user_data)
|
||||
{
|
||||
CellData *data = user_data;
|
||||
|
||||
animation_cel_animation_cel_delete (data->animation,
|
||||
data->level,
|
||||
data->position);
|
||||
}
|
||||
|
||||
void
|
||||
animation_menu_cell (AnimationCelAnimation *animation,
|
||||
GdkEventButton *event,
|
||||
gint frame,
|
||||
gint track)
|
||||
{
|
||||
GtkWidget *menu;
|
||||
GtkWidget *item;
|
||||
CellData *data;
|
||||
|
||||
menu = gtk_menu_new ();
|
||||
|
||||
/* Duplicate cell. */
|
||||
item = gtk_menu_item_new_with_label (_("Duplicate cel"));
|
||||
data = g_new0 (CellData, 1);
|
||||
data->animation = animation;
|
||||
data->position = frame + 1;
|
||||
data->level = track;
|
||||
data->dup_previous = TRUE;
|
||||
g_signal_connect_data (item, "activate",
|
||||
G_CALLBACK (on_add_cell), data,
|
||||
(GClosureNotify) g_free, 0);
|
||||
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
|
||||
gtk_widget_show (item);
|
||||
|
||||
/* Add empty cell. */
|
||||
item = gtk_menu_item_new_with_label (_("Push cels down"));
|
||||
data = g_new0 (CellData, 1);
|
||||
data->animation = animation;
|
||||
data->position = frame;
|
||||
data->level = track;
|
||||
data->dup_previous = FALSE;
|
||||
g_signal_connect_data (item, "activate",
|
||||
G_CALLBACK (on_add_cell), data,
|
||||
(GClosureNotify) g_free, 0);
|
||||
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
|
||||
gtk_widget_show (item);
|
||||
|
||||
/* Add empty cell. */
|
||||
item = gtk_menu_item_new_with_label (_("Add empty cel after"));
|
||||
data = g_new0 (CellData, 1);
|
||||
data->animation = animation;
|
||||
data->position = frame + 1;
|
||||
data->level = track;
|
||||
data->dup_previous = FALSE;
|
||||
g_signal_connect_data (item, "activate",
|
||||
G_CALLBACK (on_add_cell), data,
|
||||
(GClosureNotify) g_free, 0);
|
||||
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
|
||||
gtk_widget_show (item);
|
||||
|
||||
/* Delete cell. */
|
||||
item = gtk_menu_item_new_with_label (_("Delete cell"));
|
||||
data = g_new0 (CellData, 1);
|
||||
data->animation = animation;
|
||||
data->position = frame;
|
||||
data->level = track;
|
||||
g_signal_connect_data (item, "activate",
|
||||
G_CALLBACK (on_delete_cell), data,
|
||||
(GClosureNotify) g_free, 0);
|
||||
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
|
||||
gtk_widget_show (item);
|
||||
|
||||
gtk_widget_show (menu);
|
||||
gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
|
||||
event->button, event->time);
|
||||
}
|
32
plug-ins/animation-play/widgets/animation-menus.h
Normal file
32
plug-ins/animation-play/widgets/animation-menus.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation-menus.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/>.
|
||||
*/
|
||||
|
||||
#ifndef __ANIMATION_MENUS_H__
|
||||
#define __ANIMATION_MENUS_H__
|
||||
|
||||
void animation_menu_cell (AnimationCelAnimation *animation,
|
||||
GdkEventButton *event,
|
||||
gint frame,
|
||||
gint track);
|
||||
|
||||
#endif /* __ANIMATION_MENUS_H__ */
|
||||
|
||||
|
||||
|
905
plug-ins/animation-play/widgets/animation-storyboard.c
Normal file
905
plug-ins/animation-play/widgets/animation-storyboard.c
Normal file
@@ -0,0 +1,905 @@
|
||||
/* 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 "gdk/gdkkeysyms.h"
|
||||
|
||||
#include <libgimp/gimp.h>
|
||||
#include <libgimp/gimpui.h>
|
||||
|
||||
#include "libgimp/stdplugins-intl.h"
|
||||
|
||||
#include "animation-utils.h"
|
||||
|
||||
#include "core/animation-animatic.h"
|
||||
#include "core/animation-playback.h"
|
||||
|
||||
#include "animation-storyboard.h"
|
||||
|
||||
/* Properties. */
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_ANIMATION
|
||||
};
|
||||
|
||||
struct _AnimationStoryboardPrivate
|
||||
{
|
||||
AnimationAnimatic *animation;
|
||||
AnimationPlayback *playback;
|
||||
|
||||
gint current_panel;
|
||||
|
||||
GList *panel_buttons;
|
||||
GList *panel_separators;
|
||||
GtkWidget *displayed_separator;
|
||||
GList *disposal_buttons;
|
||||
GList *comments;
|
||||
|
||||
gint dragged_panel;
|
||||
};
|
||||
|
||||
/* 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,
|
||||
AnimationStoryboard *view);
|
||||
static void animation_storyboard_stopped (AnimationPlayback *playback,
|
||||
AnimationStoryboard *view);
|
||||
static void animation_storyboard_rendered (AnimationPlayback *playback,
|
||||
gint frame_number,
|
||||
GeglBuffer *buffer,
|
||||
gboolean must_draw_null,
|
||||
AnimationStoryboard *view);
|
||||
|
||||
static void animation_storyboard_duration_spin_changed (GtkSpinButton *spinbutton,
|
||||
AnimationStoryboard *storyboard);
|
||||
|
||||
static gboolean animation_storyboard_comment_keypress (GtkWidget *entry,
|
||||
GdkEventKey *event,
|
||||
AnimationStoryboard *view);
|
||||
static void animation_storyboard_comment_changed (GtkTextBuffer *text_buffer,
|
||||
AnimationAnimatic *animatic);
|
||||
static void animation_storyboard_disposal_toggled (GtkToggleButton *button,
|
||||
AnimationAnimatic *animatic);
|
||||
static void animation_storyboard_button_clicked (GtkWidget *widget,
|
||||
AnimationStoryboard *storyboard);
|
||||
|
||||
/* Drag and drop */
|
||||
static void animation_storyboard_panel_drag_begin (GtkWidget *widget,
|
||||
GdkDragContext *drag_context,
|
||||
AnimationStoryboard *storyboard);
|
||||
static void animation_storyboard_panel_drag_end (GtkWidget *widget,
|
||||
GdkDragContext *drag_context,
|
||||
AnimationStoryboard *storyboard);
|
||||
static gboolean animation_storyboard_panel_drag_drop (GtkWidget *widget,
|
||||
GdkDragContext *drag_context,
|
||||
gint x,
|
||||
gint y,
|
||||
guint time,
|
||||
AnimationStoryboard *storyboard);
|
||||
static gboolean animation_storyboard_panel_drag_motion (GtkWidget *widget,
|
||||
GdkDragContext *drag_context,
|
||||
gint x,
|
||||
gint y,
|
||||
guint time,
|
||||
AnimationStoryboard *storyboard);
|
||||
static void animation_storyboard_panel_drag_leave (GtkWidget *widget,
|
||||
GdkDragContext *context,
|
||||
guint time,
|
||||
AnimationStoryboard *storyboard);
|
||||
|
||||
/* Utils */
|
||||
static void animation_storyboard_jump (AnimationStoryboard *view,
|
||||
gint panel);
|
||||
static void animation_storyboard_move (AnimationStoryboard *storyboard,
|
||||
gint from_panel,
|
||||
gint to_panel);
|
||||
|
||||
G_DEFINE_TYPE (AnimationStoryboard, animation_storyboard, GTK_TYPE_SCROLLED_WINDOW)
|
||||
|
||||
#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);
|
||||
view->priv->dragged_panel = -1;
|
||||
}
|
||||
|
||||
/**** 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,
|
||||
AnimationPlayback *playback)
|
||||
{
|
||||
GtkWidget *layer_view;
|
||||
AnimationStoryboard *storyboard;
|
||||
|
||||
layer_view = g_object_new (ANIMATION_TYPE_STORYBOARD,
|
||||
"animation", animation,
|
||||
NULL);
|
||||
storyboard = ANIMATION_STORYBOARD (layer_view);
|
||||
storyboard->priv->playback = playback;
|
||||
|
||||
return layer_view;
|
||||
}
|
||||
|
||||
/**** Private Functions ****/
|
||||
|
||||
static void
|
||||
animation_storyboard_constructed (GObject *object)
|
||||
{
|
||||
AnimationStoryboard *view = ANIMATION_STORYBOARD (object);
|
||||
GtkWidget *layout;
|
||||
|
||||
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (view),
|
||||
GTK_POLICY_AUTOMATIC,
|
||||
GTK_POLICY_AUTOMATIC);
|
||||
|
||||
layout = gtk_table_new (1, 1, FALSE);
|
||||
gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (view),
|
||||
layout);
|
||||
g_signal_connect (view->priv->animation, "loaded",
|
||||
(GCallback) animation_storyboard_load,
|
||||
view);
|
||||
gtk_widget_show (layout);
|
||||
}
|
||||
|
||||
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_signal_handlers_disconnect_by_func (view->priv->playback,
|
||||
(GCallback) animation_storyboard_rendered,
|
||||
view);
|
||||
g_signal_handlers_disconnect_by_func (view->priv->playback,
|
||||
(GCallback) animation_storyboard_stopped,
|
||||
view);
|
||||
g_object_unref (view->priv->animation);
|
||||
if (view->priv->panel_buttons)
|
||||
{
|
||||
g_list_free (view->priv->panel_buttons);
|
||||
}
|
||||
if (view->priv->panel_separators)
|
||||
{
|
||||
g_list_free (view->priv->panel_separators);
|
||||
}
|
||||
if (view->priv->disposal_buttons)
|
||||
{
|
||||
g_list_free (view->priv->disposal_buttons);
|
||||
}
|
||||
if (view->priv->comments)
|
||||
{
|
||||
g_list_free (view->priv->comments);
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static const GtkTargetEntry target_table[] = {
|
||||
{ "application/x-gimp-animation-panel",
|
||||
GTK_TARGET_SAME_APP,
|
||||
ANIMATION_DND_TYPE_PANEL }
|
||||
};
|
||||
|
||||
/* 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,
|
||||
AnimationStoryboard *view)
|
||||
{
|
||||
AnimationAnimatic *animatic = ANIMATION_ANIMATIC (animation);
|
||||
GtkWidget *layout;
|
||||
GtkWidget *separator;
|
||||
gint *orig_layers;
|
||||
gint *layers;
|
||||
gint32 orig_image_id;
|
||||
gint32 image_id;
|
||||
gint n_images;
|
||||
gint i;
|
||||
|
||||
orig_image_id = animation_get_image_id (animation);
|
||||
image_id = gimp_image_duplicate (orig_image_id);
|
||||
gimp_image_undo_disable (image_id);
|
||||
|
||||
/* The actual layout is the grand-child. */
|
||||
layout = gtk_bin_get_child (GTK_BIN (view));
|
||||
layout = gtk_bin_get_child (GTK_BIN (layout));
|
||||
|
||||
/* Cleaning previous loads. */
|
||||
gtk_container_foreach (GTK_CONTAINER (layout),
|
||||
(GtkCallback) gtk_widget_set_sensitive,
|
||||
FALSE);
|
||||
gtk_container_foreach (GTK_CONTAINER (layout),
|
||||
(GtkCallback) gtk_widget_destroy,
|
||||
NULL);
|
||||
if (view->priv->panel_buttons)
|
||||
{
|
||||
g_list_free (view->priv->panel_buttons);
|
||||
view->priv->panel_buttons = NULL;
|
||||
}
|
||||
if (view->priv->panel_separators)
|
||||
{
|
||||
g_list_free (view->priv->panel_separators);
|
||||
view->priv->panel_separators = NULL;
|
||||
}
|
||||
view->priv->displayed_separator = NULL;
|
||||
if (view->priv->disposal_buttons)
|
||||
{
|
||||
g_list_free (view->priv->disposal_buttons);
|
||||
view->priv->disposal_buttons = NULL;
|
||||
}
|
||||
if (view->priv->comments)
|
||||
{
|
||||
g_list_free (view->priv->comments);
|
||||
view->priv->comments = NULL;
|
||||
}
|
||||
|
||||
/* Setting new values. */
|
||||
orig_layers = gimp_image_get_layers (orig_image_id,
|
||||
&n_images);
|
||||
layers = gimp_image_get_layers (image_id,
|
||||
&n_images);
|
||||
|
||||
gtk_table_resize (GTK_TABLE (layout),
|
||||
6 * n_images + 1,
|
||||
9);
|
||||
|
||||
separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
|
||||
gtk_table_attach (GTK_TABLE (layout),
|
||||
separator, 0, 9,
|
||||
6 * n_images,
|
||||
6 * n_images + 1,
|
||||
GTK_EXPAND | GTK_FILL,
|
||||
GTK_FILL,
|
||||
1, 1);
|
||||
view->priv->panel_separators = g_list_prepend (view->priv->panel_separators,
|
||||
separator);
|
||||
|
||||
for (i = 0; i < n_images; i++)
|
||||
{
|
||||
GdkPixbuf *thumbnail;
|
||||
GtkWidget *panel_button;
|
||||
GtkWidget *image;
|
||||
GtkWidget *comment;
|
||||
GtkWidget *duration;
|
||||
GtkWidget *disposal;
|
||||
gchar *image_name;
|
||||
gint panel_num = n_images - i - 1;
|
||||
|
||||
separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
|
||||
gtk_table_attach (GTK_TABLE (layout),
|
||||
separator, 0, 9,
|
||||
6 * panel_num,
|
||||
6 * panel_num + 1,
|
||||
GTK_EXPAND | GTK_FILL,
|
||||
GTK_FILL,
|
||||
1, 1);
|
||||
view->priv->panel_separators = g_list_prepend (view->priv->panel_separators,
|
||||
separator);
|
||||
|
||||
panel_button = gtk_button_new ();
|
||||
gtk_button_set_alignment (GTK_BUTTON (panel_button),
|
||||
0.5, 0.5);
|
||||
gtk_button_set_relief (GTK_BUTTON (panel_button),
|
||||
GTK_RELIEF_NONE);
|
||||
gtk_table_attach (GTK_TABLE (layout),
|
||||
panel_button, 0, 4,
|
||||
6 * panel_num + 1,
|
||||
6 * (panel_num + 1),
|
||||
GTK_EXPAND | GTK_FILL,
|
||||
GTK_FILL,
|
||||
1, 1);
|
||||
g_object_set_data (G_OBJECT (panel_button), "layer-tattoo",
|
||||
GINT_TO_POINTER (gimp_item_get_tattoo (orig_layers[i])));
|
||||
g_object_set_data (G_OBJECT (panel_button), "panel-num",
|
||||
GINT_TO_POINTER (panel_num));
|
||||
g_signal_connect (panel_button, "clicked",
|
||||
G_CALLBACK (animation_storyboard_button_clicked),
|
||||
view);
|
||||
|
||||
view->priv->panel_buttons = g_list_prepend (view->priv->panel_buttons,
|
||||
panel_button);
|
||||
gtk_widget_show (panel_button);
|
||||
|
||||
gimp_layer_resize_to_image_size (layers[i]);
|
||||
thumbnail = gimp_drawable_get_thumbnail (layers[i], 250, 250,
|
||||
GIMP_PIXBUF_SMALL_CHECKS);
|
||||
image = gtk_image_new_from_pixbuf (thumbnail);
|
||||
|
||||
/* Make this button a drag source. */
|
||||
gtk_drag_source_set (panel_button, GDK_BUTTON1_MASK,
|
||||
target_table, G_N_ELEMENTS (target_table),
|
||||
GDK_ACTION_MOVE);
|
||||
gtk_drag_source_set_icon_pixbuf (panel_button, thumbnail);
|
||||
|
||||
g_object_unref (thumbnail);
|
||||
|
||||
gtk_drag_dest_set (panel_button, GTK_DEST_DEFAULT_ALL,
|
||||
target_table, G_N_ELEMENTS (target_table),
|
||||
GDK_ACTION_MOVE);
|
||||
|
||||
g_signal_connect (panel_button, "drag-begin",
|
||||
G_CALLBACK (animation_storyboard_panel_drag_begin),
|
||||
view);
|
||||
g_signal_connect (panel_button, "drag-end",
|
||||
G_CALLBACK (animation_storyboard_panel_drag_end),
|
||||
view);
|
||||
g_signal_connect (panel_button, "drag-drop",
|
||||
G_CALLBACK (animation_storyboard_panel_drag_drop),
|
||||
view);
|
||||
g_signal_connect (panel_button, "drag-motion",
|
||||
G_CALLBACK (animation_storyboard_panel_drag_motion),
|
||||
view);
|
||||
g_signal_connect (panel_button, "drag-leave",
|
||||
G_CALLBACK (animation_storyboard_panel_drag_leave),
|
||||
view);
|
||||
|
||||
/* 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_container_add (GTK_CONTAINER (panel_button), image);
|
||||
gtk_widget_show (image);
|
||||
|
||||
comment = gtk_text_view_new ();
|
||||
g_object_set_data (G_OBJECT (comment), "panel-num",
|
||||
GINT_TO_POINTER (panel_num));
|
||||
gtk_table_attach (GTK_TABLE (layout),
|
||||
comment, 5, 9,
|
||||
6 * panel_num + 1,
|
||||
6 * (panel_num + 1),
|
||||
GTK_EXPAND | GTK_FILL,
|
||||
GTK_FILL,
|
||||
0, 1);
|
||||
view->priv->comments = g_list_prepend (view->priv->comments,
|
||||
comment);
|
||||
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_object_set_data (G_OBJECT (buffer), "panel-num",
|
||||
GINT_TO_POINTER (panel_num));
|
||||
g_signal_connect (comment, "key-press-event",
|
||||
G_CALLBACK (animation_storyboard_comment_keypress),
|
||||
view);
|
||||
g_signal_connect (buffer, "changed",
|
||||
(GCallback) animation_storyboard_comment_changed,
|
||||
animation);
|
||||
|
||||
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_panel_duration (animatic,
|
||||
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 (layout),
|
||||
duration, 4, 5,
|
||||
6 * panel_num + 1,
|
||||
6 * panel_num + 2,
|
||||
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), "panel-num",
|
||||
GINT_TO_POINTER (panel_num));
|
||||
g_signal_connect (duration, "value-changed",
|
||||
(GCallback) animation_storyboard_duration_spin_changed,
|
||||
view);
|
||||
gtk_widget_show (duration);
|
||||
|
||||
disposal = gtk_toggle_button_new ();
|
||||
gtk_button_set_relief (GTK_BUTTON (disposal), GTK_RELIEF_NONE);
|
||||
g_object_set_data (G_OBJECT (disposal), "panel-num",
|
||||
GINT_TO_POINTER (panel_num));
|
||||
image = gtk_image_new_from_icon_name (GIMP_ICON_TRANSPARENCY,
|
||||
GTK_ICON_SIZE_MENU);
|
||||
gtk_container_add (GTK_CONTAINER (disposal), image);
|
||||
gtk_widget_show (image);
|
||||
gtk_table_attach (GTK_TABLE (layout),
|
||||
disposal, 4, 5,
|
||||
6 * panel_num + 2,
|
||||
6 * panel_num + 3,
|
||||
GTK_EXPAND, GTK_EXPAND,
|
||||
0, 1);
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (disposal),
|
||||
animation_animatic_get_combine (animatic,
|
||||
panel_num));
|
||||
g_signal_connect (disposal, "toggled",
|
||||
G_CALLBACK (animation_storyboard_disposal_toggled),
|
||||
animation);
|
||||
view->priv->disposal_buttons = g_list_prepend (view->priv->disposal_buttons,
|
||||
disposal);
|
||||
gtk_widget_show (disposal);
|
||||
}
|
||||
g_signal_connect (view->priv->playback, "render",
|
||||
(GCallback) animation_storyboard_rendered,
|
||||
view);
|
||||
g_signal_connect (view->priv->playback, "stop",
|
||||
(GCallback) animation_storyboard_stopped,
|
||||
view);
|
||||
g_free (layers);
|
||||
g_free (orig_layers);
|
||||
gimp_image_delete (image_id);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_storyboard_stopped (AnimationPlayback *playback,
|
||||
AnimationStoryboard *view)
|
||||
{
|
||||
gint position;
|
||||
gint panel;
|
||||
|
||||
position = animation_playback_get_position (playback);
|
||||
panel = animation_animatic_get_panel (view->priv->animation,
|
||||
position);
|
||||
animation_storyboard_jump (view, panel);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_storyboard_rendered (AnimationPlayback *playback,
|
||||
gint frame_number,
|
||||
GeglBuffer *buffer,
|
||||
gboolean must_draw_null,
|
||||
AnimationStoryboard *view)
|
||||
{
|
||||
gint panel;
|
||||
|
||||
panel = animation_animatic_get_panel (view->priv->animation,
|
||||
frame_number);
|
||||
animation_storyboard_jump (view, panel);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_storyboard_duration_spin_changed (GtkSpinButton *spinbutton,
|
||||
AnimationStoryboard *storyboard)
|
||||
{
|
||||
gpointer panel_num;
|
||||
gint duration;
|
||||
gint panel_position;
|
||||
gint position;
|
||||
|
||||
panel_num = g_object_get_data (G_OBJECT (spinbutton), "panel-num");
|
||||
duration = gtk_spin_button_get_value_as_int (spinbutton);
|
||||
|
||||
position = animation_playback_get_position (storyboard->priv->playback);
|
||||
panel_position = animation_animatic_get_position (storyboard->priv->animation,
|
||||
GPOINTER_TO_INT (panel_num));
|
||||
if (position >= panel_position)
|
||||
{
|
||||
gint cur_duration;
|
||||
|
||||
cur_duration = animation_animatic_get_panel_duration (storyboard->priv->animation,
|
||||
GPOINTER_TO_INT (panel_num));
|
||||
position += duration - cur_duration;
|
||||
}
|
||||
|
||||
animation_animatic_set_panel_duration (storyboard->priv->animation,
|
||||
GPOINTER_TO_INT (panel_num),
|
||||
duration);
|
||||
animation_playback_jump (storyboard->priv->playback, position);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
animation_storyboard_comment_keypress (GtkWidget *entry,
|
||||
GdkEventKey *event,
|
||||
AnimationStoryboard *view)
|
||||
{
|
||||
gpointer panel_num;
|
||||
|
||||
panel_num = g_object_get_data (G_OBJECT (entry), "panel-num");
|
||||
|
||||
if (event->keyval == GDK_KEY_Tab ||
|
||||
event->keyval == GDK_KEY_KP_Tab ||
|
||||
event->keyval == GDK_KEY_ISO_Left_Tab)
|
||||
{
|
||||
GtkWidget *comment;
|
||||
|
||||
comment = g_list_nth_data (view->priv->comments,
|
||||
GPOINTER_TO_INT (panel_num) + 1);
|
||||
if (comment)
|
||||
{
|
||||
/* Grab the next comment widget. */
|
||||
gtk_widget_grab_focus (comment);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Loop to the first comment after the last. */
|
||||
gtk_widget_grab_focus (view->priv->comments->data);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
animation_storyboard_comment_changed (GtkTextBuffer *text_buffer,
|
||||
AnimationAnimatic *animatic)
|
||||
{
|
||||
gchar *text;
|
||||
GtkTextIter start;
|
||||
GtkTextIter end;
|
||||
gpointer panel_num;
|
||||
|
||||
panel_num = g_object_get_data (G_OBJECT (text_buffer), "panel-num");
|
||||
|
||||
gtk_text_buffer_get_bounds (text_buffer, &start, &end);
|
||||
text = gtk_text_buffer_get_text (text_buffer, &start, &end, FALSE);
|
||||
animation_animatic_set_comment (animatic,
|
||||
GPOINTER_TO_INT (panel_num),
|
||||
text);
|
||||
g_free (text);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_storyboard_disposal_toggled (GtkToggleButton *button,
|
||||
AnimationAnimatic *animatic)
|
||||
{
|
||||
gpointer panel_num;
|
||||
|
||||
panel_num = g_object_get_data (G_OBJECT (button), "panel-num");
|
||||
|
||||
animation_animatic_set_combine (animatic,
|
||||
GPOINTER_TO_INT (panel_num),
|
||||
gtk_toggle_button_get_active (button));
|
||||
}
|
||||
|
||||
static void
|
||||
animation_storyboard_button_clicked (GtkWidget *widget,
|
||||
AnimationStoryboard *storyboard)
|
||||
{
|
||||
gpointer panel_num;
|
||||
gint position;
|
||||
|
||||
panel_num = g_object_get_data (G_OBJECT (widget), "panel-num");
|
||||
position = animation_animatic_get_position (storyboard->priv->animation,
|
||||
GPOINTER_TO_INT (panel_num));
|
||||
animation_playback_jump (storyboard->priv->playback, position);
|
||||
}
|
||||
|
||||
/**** Drag and drop ****/
|
||||
|
||||
static void
|
||||
animation_storyboard_panel_drag_begin (GtkWidget *widget,
|
||||
GdkDragContext *drag_context,
|
||||
AnimationStoryboard *storyboard)
|
||||
{
|
||||
gpointer panel_num;
|
||||
|
||||
panel_num = g_object_get_data (G_OBJECT (widget), "panel-num");
|
||||
storyboard->priv->dragged_panel = GPOINTER_TO_INT (panel_num);
|
||||
}
|
||||
|
||||
static void
|
||||
animation_storyboard_panel_drag_end (GtkWidget *widget,
|
||||
GdkDragContext *drag_context,
|
||||
AnimationStoryboard *storyboard)
|
||||
{
|
||||
storyboard->priv->dragged_panel = -1;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
animation_storyboard_panel_drag_drop (GtkWidget *widget,
|
||||
GdkDragContext *context,
|
||||
gint x,
|
||||
gint y,
|
||||
guint time,
|
||||
AnimationStoryboard *storyboard)
|
||||
{
|
||||
gpointer panel_num;
|
||||
GtkAllocation allocation;
|
||||
gint panel_dest;
|
||||
|
||||
g_return_val_if_fail (storyboard->priv->dragged_panel >= 0, FALSE);
|
||||
|
||||
panel_num = g_object_get_data (G_OBJECT (widget), "panel-num");
|
||||
gtk_widget_get_allocation (widget, &allocation);
|
||||
if (y > allocation.height / 2)
|
||||
{
|
||||
panel_dest = GPOINTER_TO_INT (panel_num) + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
panel_dest = GPOINTER_TO_INT (panel_num);
|
||||
}
|
||||
if (storyboard->priv->dragged_panel < panel_dest)
|
||||
{
|
||||
panel_dest--;
|
||||
}
|
||||
animation_storyboard_move (storyboard,
|
||||
storyboard->priv->dragged_panel,
|
||||
panel_dest);
|
||||
|
||||
gtk_drag_finish (context, TRUE,
|
||||
gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE,
|
||||
time);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
animation_storyboard_panel_drag_motion (GtkWidget *widget,
|
||||
GdkDragContext *context,
|
||||
gint x,
|
||||
gint y,
|
||||
guint time,
|
||||
AnimationStoryboard *storyboard)
|
||||
{
|
||||
GtkWidget *separator;
|
||||
gpointer panel_num;
|
||||
GtkAllocation allocation;
|
||||
gint panel_dest;
|
||||
|
||||
g_return_val_if_fail (storyboard->priv->dragged_panel >= 0, FALSE);
|
||||
|
||||
panel_num = g_object_get_data (G_OBJECT (widget), "panel-num");
|
||||
gtk_widget_get_allocation (widget, &allocation);
|
||||
if (y > allocation.height / 2)
|
||||
{
|
||||
panel_dest = GPOINTER_TO_INT (panel_num) + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
panel_dest = GPOINTER_TO_INT (panel_num);
|
||||
}
|
||||
separator = g_list_nth_data (storyboard->priv->panel_separators,
|
||||
panel_dest);
|
||||
if (storyboard->priv->displayed_separator)
|
||||
gtk_widget_hide (storyboard->priv->displayed_separator);
|
||||
storyboard->priv->displayed_separator = separator;
|
||||
gtk_widget_show (separator);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
animation_storyboard_panel_drag_leave (GtkWidget *widget,
|
||||
GdkDragContext *context,
|
||||
guint time,
|
||||
AnimationStoryboard *storyboard)
|
||||
{
|
||||
if (storyboard->priv->displayed_separator)
|
||||
gtk_widget_hide (storyboard->priv->displayed_separator);
|
||||
}
|
||||
|
||||
/**** Utils ****/
|
||||
|
||||
static void
|
||||
animation_storyboard_jump (AnimationStoryboard *view,
|
||||
gint panel)
|
||||
{
|
||||
/* Don't jump while playing. This is too disturbing. */
|
||||
if (! animation_playback_is_playing (view->priv->playback))
|
||||
{
|
||||
GtkWidget *button;
|
||||
|
||||
if (view->priv->current_panel >= 0)
|
||||
{
|
||||
button = g_list_nth_data (view->priv->panel_buttons,
|
||||
view->priv->current_panel);
|
||||
gtk_button_set_relief (GTK_BUTTON (button),
|
||||
GTK_RELIEF_NONE);
|
||||
}
|
||||
|
||||
view->priv->current_panel = panel;
|
||||
button = g_list_nth_data (view->priv->panel_buttons,
|
||||
view->priv->current_panel);
|
||||
gtk_button_set_relief (GTK_BUTTON (button),
|
||||
GTK_RELIEF_NORMAL);
|
||||
show_scrolled_child (GTK_SCROLLED_WINDOW (view), button);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
animation_storyboard_move (AnimationStoryboard *storyboard,
|
||||
gint from_panel,
|
||||
gint to_panel)
|
||||
{
|
||||
Animation *animation;
|
||||
gint32 image_id;
|
||||
|
||||
animation = ANIMATION (storyboard->priv->animation);
|
||||
image_id = animation_get_image_id (animation);
|
||||
if (from_panel != to_panel)
|
||||
{
|
||||
GtkWidget *layout;
|
||||
GList *iter;
|
||||
gpointer tattoo;
|
||||
gint32 layer;
|
||||
gint new_position;
|
||||
gint i;
|
||||
|
||||
layout = gtk_bin_get_child (GTK_BIN (storyboard));
|
||||
layout = gtk_bin_get_child (GTK_BIN (layout));
|
||||
|
||||
iter = g_list_nth (storyboard->priv->panel_buttons,
|
||||
from_panel);
|
||||
tattoo = g_object_get_data (G_OBJECT (iter->data), "layer-tattoo");
|
||||
layer = gimp_image_get_layer_by_tattoo (image_id,
|
||||
GPOINTER_TO_INT (tattoo));
|
||||
|
||||
/* Layers are ordered from top to bottom in GIMP. */
|
||||
new_position = g_list_length (storyboard->priv->panel_buttons) - to_panel - 1;
|
||||
gimp_image_reorder_item (image_id, layer, 0, new_position);
|
||||
|
||||
/* Reorder the internal lists. */
|
||||
storyboard->priv->panel_buttons = g_list_remove_link (storyboard->priv->panel_buttons,
|
||||
iter);
|
||||
storyboard->priv->panel_buttons = g_list_insert (storyboard->priv->panel_buttons,
|
||||
iter->data, to_panel);
|
||||
g_list_free (iter);
|
||||
|
||||
iter = g_list_nth (storyboard->priv->comments,
|
||||
from_panel);
|
||||
storyboard->priv->comments = g_list_remove_link (storyboard->priv->comments,
|
||||
iter);
|
||||
storyboard->priv->comments = g_list_insert (storyboard->priv->comments,
|
||||
iter->data, to_panel);
|
||||
g_list_free (iter);
|
||||
|
||||
/* Refresh the GUI. */
|
||||
i = MIN (from_panel, to_panel);
|
||||
iter = g_list_nth (storyboard->priv->panel_buttons, i);
|
||||
for (; iter; iter = iter->next, i++)
|
||||
{
|
||||
g_object_ref (iter->data);
|
||||
gtk_container_remove (GTK_CONTAINER (layout), iter->data);
|
||||
gtk_table_attach (GTK_TABLE (layout),
|
||||
iter->data, 0, 4,
|
||||
6 * i + 1,
|
||||
6 * (i + 1),
|
||||
GTK_EXPAND | GTK_FILL,
|
||||
GTK_FILL,
|
||||
1, 1);
|
||||
g_object_unref (iter->data);
|
||||
g_object_set_data (G_OBJECT (iter->data), "panel-num",
|
||||
GINT_TO_POINTER (i));
|
||||
}
|
||||
i = MIN (from_panel, to_panel);
|
||||
iter = g_list_nth (storyboard->priv->comments, i);
|
||||
for (; iter; iter = iter->next, i++)
|
||||
{
|
||||
g_object_ref (iter->data);
|
||||
gtk_container_remove (GTK_CONTAINER (layout), iter->data);
|
||||
gtk_table_attach (GTK_TABLE (layout),
|
||||
iter->data, 5, 9,
|
||||
6 * i + 1,
|
||||
6 * (i + 1),
|
||||
GTK_EXPAND | GTK_FILL,
|
||||
GTK_FILL,
|
||||
0, 1);
|
||||
g_object_unref (iter->data);
|
||||
g_object_set_data (G_OBJECT (iter->data), "panel-num",
|
||||
GINT_TO_POINTER (i));
|
||||
}
|
||||
animation_animatic_move_panel (storyboard->priv->animation,
|
||||
from_panel, to_panel);
|
||||
}
|
||||
}
|
53
plug-ins/animation-play/widgets/animation-storyboard.h
Normal file
53
plug-ins/animation-play/widgets/animation-storyboard.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/* 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
|
||||
{
|
||||
GtkScrolledWindow parent_instance;
|
||||
|
||||
AnimationStoryboardPrivate *priv;
|
||||
};
|
||||
|
||||
struct _AnimationStoryboardClass
|
||||
{
|
||||
GtkScrolledWindowClass parent_class;
|
||||
};
|
||||
|
||||
GType animation_storyboard_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GtkWidget * animation_storyboard_new (AnimationAnimatic *animation,
|
||||
AnimationPlayback *playback);
|
||||
|
||||
#endif /* __ANIMATION_STORYBOARD_H__ */
|
||||
|
2501
plug-ins/animation-play/widgets/animation-xsheet.c
Executable file
2501
plug-ins/animation-play/widgets/animation-xsheet.c
Executable file
File diff suppressed because it is too large
Load Diff
54
plug-ins/animation-play/widgets/animation-xsheet.h
Executable file
54
plug-ins/animation-play/widgets/animation-xsheet.h
Executable file
@@ -0,0 +1,54 @@
|
||||
/* GIMP - The GNU Image Manipulation Program
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* animation-xsheet.h
|
||||
* Copyright (C) 2015-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_XSHEET_H__
|
||||
#define __ANIMATION_XSHEET_H__
|
||||
|
||||
#define ANIMATION_TYPE_XSHEET (animation_xsheet_get_type ())
|
||||
#define ANIMATION_XSHEET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_XSHEET, AnimationXSheet))
|
||||
#define ANIMATION_XSHEET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_XSHEET, AnimationXSheetClass))
|
||||
#define ANIMATION_IS_XSHEET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_XSHEET))
|
||||
#define ANIMATION_IS_XSHEET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_XSHEET))
|
||||
#define ANIMATION_XSHEET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_XSHEET, AnimationXSheetClass))
|
||||
|
||||
typedef struct _AnimationXSheet AnimationXSheet;
|
||||
typedef struct _AnimationXSheetClass AnimationXSheetClass;
|
||||
typedef struct _AnimationXSheetPrivate AnimationXSheetPrivate;
|
||||
|
||||
struct _AnimationXSheet
|
||||
{
|
||||
GtkScrolledWindow parent_instance;
|
||||
|
||||
AnimationXSheetPrivate *priv;
|
||||
};
|
||||
|
||||
struct _AnimationXSheetClass
|
||||
{
|
||||
GtkScrolledWindowClass parent_class;
|
||||
};
|
||||
|
||||
GType animation_xsheet_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GtkWidget * animation_xsheet_new (AnimationCelAnimation *animation,
|
||||
AnimationPlayback *playback,
|
||||
GtkWidget *layer_view,
|
||||
GtkWidget *keyframe_view);
|
||||
|
||||
#endif /* __ANIMATION_XSHEET_H__ */
|
@@ -51,7 +51,6 @@ AM_CPPFLAGS = \
|
||||
libexec_PROGRAMS = \
|
||||
align-layers \
|
||||
animation-optimize \
|
||||
animation-play \
|
||||
blinds \
|
||||
blur \
|
||||
border-average \
|
||||
@@ -200,24 +199,6 @@ animation_optimize_LDADD = \
|
||||
$(INTLLIBS) \
|
||||
$(animation_optimize_RC)
|
||||
|
||||
animation_play_SOURCES = \
|
||||
animation-play.c
|
||||
|
||||
animation_play_LDADD = \
|
||||
$(libgimpui) \
|
||||
$(libgimpwidgets) \
|
||||
$(libgimpmodule) \
|
||||
$(libgimp) \
|
||||
$(libgimpmath) \
|
||||
$(libgimpconfig) \
|
||||
$(libgimpcolor) \
|
||||
$(libgimpbase) \
|
||||
$(GTK_LIBS) \
|
||||
$(GEGL_LIBS) \
|
||||
$(RT_LIBS) \
|
||||
$(INTLLIBS) \
|
||||
$(animation_play_RC)
|
||||
|
||||
blinds_SOURCES = \
|
||||
blinds.c
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
||||
%plugins = (
|
||||
'align-layers' => { ui => 1 },
|
||||
'animation-optimize' => {},
|
||||
'animation-play' => { ui => 1, gegl => 1 },
|
||||
'blinds' => { ui => 1 },
|
||||
'blur' => {},
|
||||
'border-average' => { ui => 1, gegl => 1 },
|
||||
|
Reference in New Issue
Block a user