Merge remote branch 'elliot/cookbook-textures-crossfade'
* elliot/cookbook-textures-crossfade: cookbook: Use GdkPixbuf instead of getting data from a texture cookbook: Added a recipe for cross-fading between two images cookbook: Modified COGL example for consistency cookbook: Added video of two texture cross-fade cookbook: Removed unused constant cookbook: Renamed front/back to top/bottom in cross-fade example cookbook: Don't need to set keep-aspect-ratio for simple example cookbook: Modified ordering of statements in cross-fade example cookbook: Added a longer slideshow example cookbook: Made code examples more consistent cookbook: Added example code for texture cross-fading Post-release version bump to 1.3.13 Release Clutter 1.3.12 (developers snapshot) Conflicts: doc/cookbook/examples/Makefile.am
This commit is contained in:
commit
5f15a620a1
@ -54,6 +54,7 @@ VIDEO_FILES = \
|
||||
videos/animations-rotating-container-reverses-direction.ogv \
|
||||
videos/textures-split-go.ogv \
|
||||
videos/events-mouse-scroll.ogv \
|
||||
videos/textures-crossfade-two-textures.ogv \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_DIST = \
|
||||
|
@ -15,6 +15,9 @@ noinst_PROGRAMS = \
|
||||
events-pointer-motion-crossing \
|
||||
events-pointer-motion-stacked \
|
||||
events-pointer-motion-scribbler \
|
||||
textures-crossfade \
|
||||
textures-crossfade-cogl \
|
||||
textures-crossfade-slideshow \
|
||||
$(NULL)
|
||||
|
||||
INCLUDES = \
|
||||
@ -48,5 +51,8 @@ events_pointer_motion_SOURCES = events-pointer-motion.c
|
||||
events_pointer_motion_crossing_SOURCES = events-pointer-motion-crossing.c
|
||||
events_pointer_motion_stacked_SOURCES = events-pointer-motion-stacked.c
|
||||
events_pointer_motion_scribbler_SOURCES = events-pointer-motion-scribbler.c
|
||||
textures_crossfade_SOURCES = textures-crossfade.c
|
||||
textures_crossfade_cogl_SOURCES = textures-crossfade-cogl.c
|
||||
textures_crossfade_slideshow_SOURCES = textures-crossfade-slideshow.c
|
||||
|
||||
-include $(top_srcdir)/build/autotools/Makefile.am.gitignore
|
||||
|
163
doc/cookbook/examples/textures-crossfade-cogl.c
Normal file
163
doc/cookbook/examples/textures-crossfade-cogl.c
Normal file
@ -0,0 +1,163 @@
|
||||
#include <stdlib.h>
|
||||
#include <clutter/clutter.h>
|
||||
|
||||
static gchar *source = NULL;
|
||||
static gchar *target = NULL;
|
||||
static guint duration = 1000;
|
||||
|
||||
static GOptionEntry entries[] = {
|
||||
{
|
||||
"source", 's',
|
||||
0,
|
||||
G_OPTION_ARG_FILENAME, &source,
|
||||
"The source image of the cross-fade", "FILE"
|
||||
},
|
||||
{
|
||||
"target", 't',
|
||||
0,
|
||||
G_OPTION_ARG_FILENAME, &target,
|
||||
"The target image of the cross-fade", "FILE"
|
||||
},
|
||||
{
|
||||
"duration", 'd',
|
||||
0,
|
||||
G_OPTION_ARG_INT, &duration,
|
||||
"The duration of the cross-fade, in milliseconds", "MSECS"
|
||||
},
|
||||
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static void
|
||||
_update_progress_cb (ClutterTimeline *timeline,
|
||||
guint elapsed_msecs,
|
||||
ClutterTexture *texture)
|
||||
{
|
||||
CoglHandle material = clutter_texture_get_cogl_material (texture);
|
||||
if (material == COGL_INVALID_HANDLE)
|
||||
return;
|
||||
|
||||
/* You should assume that a material can only be modified once, after
|
||||
* its creation; if you need to modify it later you should use a copy
|
||||
* instead. Cogl makes copying materials reasonably cheap
|
||||
*/
|
||||
CoglHandle copy = cogl_material_copy (material);
|
||||
|
||||
gdouble progress = clutter_timeline_get_progress (timeline);
|
||||
|
||||
/* Create the constant color to be used when combining the two
|
||||
* material layers; we use a black color with an alpha component
|
||||
* depending on the current progress of the timeline
|
||||
*/
|
||||
CoglColor constant;
|
||||
cogl_color_set_from_4ub (&constant, 0x00, 0x00, 0x00, 0xff * progress);
|
||||
|
||||
/* This sets the value of the constant color we use when combining
|
||||
* the two layers
|
||||
*/
|
||||
cogl_material_set_layer_combine_constant (copy, 1, &constant);
|
||||
|
||||
/* The Texture now owns the material */
|
||||
clutter_texture_set_cogl_material (texture, copy);
|
||||
cogl_handle_unref (copy);
|
||||
|
||||
clutter_actor_queue_redraw (CLUTTER_ACTOR (texture));
|
||||
}
|
||||
|
||||
static CoglHandle
|
||||
load_cogl_texture (const char *type,
|
||||
const char *file)
|
||||
{
|
||||
GError *error = NULL;
|
||||
|
||||
CoglHandle retval = cogl_texture_new_from_file (file,
|
||||
COGL_TEXTURE_NO_SLICING,
|
||||
COGL_PIXEL_FORMAT_ANY,
|
||||
&error);
|
||||
if (error != NULL)
|
||||
{
|
||||
g_print ("Unable to load %s image: %s\n", type, error->message);
|
||||
g_error_free (error);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int
|
||||
print_usage_and_exit (const char *exec_name,
|
||||
int exit_code)
|
||||
{
|
||||
g_print ("Usage: %s -s <source> -t <target> [-d <duration>]\n", exec_name);
|
||||
return exit_code;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
clutter_init_with_args (&argc, &argv,
|
||||
" - Crossfade", entries,
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
if (source == NULL || target == NULL)
|
||||
return print_usage_and_exit (argv[0], EXIT_FAILURE);
|
||||
|
||||
/* Load the source and target images using Cogl, because we need
|
||||
* to combine them into the same ClutterTexture.
|
||||
*/
|
||||
CoglHandle texture_1 = load_cogl_texture ("source", source);
|
||||
CoglHandle texture_2 = load_cogl_texture ("target", target);
|
||||
|
||||
/* Create a new Cogl material holding the two textures inside two
|
||||
* separate layers.
|
||||
*/
|
||||
CoglHandle material = cogl_material_new ();
|
||||
cogl_material_set_layer (material, 1, texture_1);
|
||||
cogl_material_set_layer (material, 0, texture_2);
|
||||
|
||||
/* Set the layer combination description for the second layer; the
|
||||
* default for Cogl is to simply multiply the layer with the
|
||||
* precendent one. In this case we interpolate the color for each
|
||||
* pixel between the pixel value of the previous layer and the
|
||||
* current one, using the alpha component of a constant color as
|
||||
* the interpolation factor.
|
||||
*/
|
||||
cogl_material_set_layer_combine (material, 1,
|
||||
"RGBA = INTERPOLATE (PREVIOUS, "
|
||||
"TEXTURE, "
|
||||
"CONSTANT[A])",
|
||||
NULL);
|
||||
|
||||
/* The material now owns the two textures */
|
||||
cogl_handle_unref (texture_1);
|
||||
cogl_handle_unref (texture_2);
|
||||
|
||||
/* Create a Texture and place it in the middle of the stage; then
|
||||
* assign the material we created earlier to the Texture for painting
|
||||
* it
|
||||
*/
|
||||
ClutterActor *stage = clutter_stage_get_default ();
|
||||
clutter_stage_set_title (CLUTTER_STAGE (stage), "cross-fade");
|
||||
clutter_actor_set_size (stage, 400, 300);
|
||||
clutter_actor_show (stage);
|
||||
g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
|
||||
|
||||
ClutterActor *texture = clutter_texture_new ();
|
||||
clutter_container_add_actor (CLUTTER_CONTAINER (stage), texture);
|
||||
clutter_texture_set_cogl_material (CLUTTER_TEXTURE (texture), material);
|
||||
clutter_actor_add_constraint (texture, clutter_align_constraint_new (stage, CLUTTER_ALIGN_X_AXIS, 0.5));
|
||||
clutter_actor_add_constraint (texture, clutter_align_constraint_new (stage, CLUTTER_ALIGN_Y_AXIS, 0.5));
|
||||
cogl_handle_unref (material);
|
||||
|
||||
/* The timeline will drive the cross-fading */
|
||||
ClutterTimeline *timeline = clutter_timeline_new (duration);
|
||||
g_signal_connect (timeline, "new-frame", G_CALLBACK (_update_progress_cb), texture);
|
||||
clutter_timeline_start (timeline);
|
||||
|
||||
clutter_main ();
|
||||
|
||||
g_object_unref (timeline);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
182
doc/cookbook/examples/textures-crossfade-slideshow.c
Normal file
182
doc/cookbook/examples/textures-crossfade-slideshow.c
Normal file
@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Simple slideshow application, cycling images between
|
||||
* two ClutterTextures
|
||||
*
|
||||
* Run by passing one or more image paths or directory globs
|
||||
* which will pick up image files
|
||||
*
|
||||
* When running, press any key to go to the next image
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <clutter/clutter.h>
|
||||
|
||||
static guint stage_side = 600;
|
||||
static guint animation_duration_ms = 1500;
|
||||
|
||||
static const ClutterColor stage_color = { 0x33, 0x33, 0x55, 0xff };
|
||||
|
||||
typedef struct {
|
||||
ClutterActor *top;
|
||||
ClutterActor *bottom;
|
||||
ClutterState *transitions;
|
||||
GSList *image_paths;
|
||||
guint next_image_index;
|
||||
} State;
|
||||
|
||||
static gboolean
|
||||
load_next_image (State *app)
|
||||
{
|
||||
/* don't do anything if already animating */
|
||||
ClutterTimeline *timeline = clutter_state_get_timeline (app->transitions);
|
||||
|
||||
if (clutter_timeline_is_playing (timeline) == 1)
|
||||
{
|
||||
g_debug ("Animation is running already");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!app->next_image_index)
|
||||
app->next_image_index = 0;
|
||||
|
||||
gpointer next = g_slist_nth_data (app->image_paths, app->next_image_index);
|
||||
|
||||
if (next == NULL)
|
||||
return FALSE;
|
||||
|
||||
gchar *image_path = (gchar *)next;
|
||||
|
||||
g_debug ("Loading %s", image_path);
|
||||
|
||||
CoglHandle *cogl_texture;
|
||||
cogl_texture = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (app->top));
|
||||
|
||||
if (cogl_texture != NULL)
|
||||
{
|
||||
/* copy the current texture into the background */
|
||||
clutter_texture_set_cogl_texture (CLUTTER_TEXTURE (app->bottom), cogl_texture);
|
||||
|
||||
/* make the bottom opaque and top transparent */
|
||||
clutter_state_warp_to_state (app->transitions, "show-bottom");
|
||||
}
|
||||
|
||||
/* load the next image into the top */
|
||||
GError *error = NULL;
|
||||
clutter_texture_set_from_file (CLUTTER_TEXTURE (app->top),
|
||||
image_path,
|
||||
&error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
g_warning ("Error loading %s\n%s", image_path, error->message);
|
||||
g_error_free (error);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* fade in the top texture and fade out the bottom texture */
|
||||
clutter_state_set_state (app->transitions, "show-top");
|
||||
|
||||
app->next_image_index++;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_key_pressed_cb (ClutterActor *actor,
|
||||
ClutterEvent *event,
|
||||
gpointer user_data)
|
||||
{
|
||||
State *app = (State *)user_data;
|
||||
|
||||
load_next_image (app);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
if (argc < 2)
|
||||
{
|
||||
g_print ("Usage: %s <image paths to load>\n", argv[0]);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
State *app = g_new0 (State, 1);
|
||||
app->image_paths = NULL;
|
||||
|
||||
/*
|
||||
* NB if your shell globs arguments to this program so argv
|
||||
* includes non-image files, they will fail to load and throw errors
|
||||
*/
|
||||
guint i;
|
||||
for (i = 1; i < argc; i++)
|
||||
app->image_paths = g_slist_append (app->image_paths, argv[i]);
|
||||
|
||||
GError *error = NULL;
|
||||
|
||||
/* UI */
|
||||
ClutterActor *stage;
|
||||
ClutterLayoutManager *layout;
|
||||
ClutterActor *box;
|
||||
|
||||
clutter_init (&argc, &argv);
|
||||
|
||||
stage = clutter_stage_get_default ();
|
||||
g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
|
||||
clutter_stage_set_title (CLUTTER_STAGE (stage), "cross-fade");
|
||||
clutter_actor_set_size (stage, stage_side, stage_side);
|
||||
clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color);
|
||||
|
||||
layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
|
||||
CLUTTER_BIN_ALIGNMENT_CENTER);
|
||||
|
||||
box = clutter_box_new (layout);
|
||||
clutter_actor_set_size (box, stage_side, stage_side);
|
||||
|
||||
app->bottom = clutter_texture_new ();
|
||||
clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (app->bottom), TRUE);
|
||||
|
||||
app->top = clutter_texture_new ();
|
||||
clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (app->top), TRUE);
|
||||
|
||||
clutter_container_add_actor (CLUTTER_CONTAINER (box), app->bottom);
|
||||
clutter_container_add_actor (CLUTTER_CONTAINER (box), app->top);
|
||||
clutter_container_add_actor (CLUTTER_CONTAINER (stage), box);
|
||||
|
||||
/* animations */
|
||||
app->transitions = clutter_state_new ();
|
||||
clutter_state_set (app->transitions, NULL, "show-top",
|
||||
app->top, "opacity", CLUTTER_EASE_IN_CUBIC, 255,
|
||||
app->bottom, "opacity", CLUTTER_EASE_IN_CUBIC, 0,
|
||||
NULL);
|
||||
clutter_state_set (app->transitions, NULL, "show-bottom",
|
||||
app->top, "opacity", CLUTTER_LINEAR, 0,
|
||||
app->bottom, "opacity", CLUTTER_LINEAR, 255,
|
||||
NULL);
|
||||
clutter_state_set_duration (app->transitions,
|
||||
NULL,
|
||||
NULL,
|
||||
animation_duration_ms);
|
||||
|
||||
/* display the next (first) image */
|
||||
load_next_image (app);
|
||||
|
||||
/* key press displays the next image */
|
||||
g_signal_connect (stage,
|
||||
"key-press-event",
|
||||
G_CALLBACK (_key_pressed_cb),
|
||||
app);
|
||||
|
||||
clutter_actor_show (stage);
|
||||
|
||||
clutter_main ();
|
||||
|
||||
g_slist_free (app->image_paths);
|
||||
g_object_unref (app->transitions);
|
||||
g_free (app);
|
||||
|
||||
if (error != NULL)
|
||||
g_error_free (error);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
142
doc/cookbook/examples/textures-crossfade.c
Normal file
142
doc/cookbook/examples/textures-crossfade.c
Normal file
@ -0,0 +1,142 @@
|
||||
#include <stdlib.h>
|
||||
#include <clutter/clutter.h>
|
||||
|
||||
static gchar *source = NULL;
|
||||
static gchar *target = NULL;
|
||||
static guint duration = 1000;
|
||||
|
||||
static GOptionEntry entries[] = {
|
||||
{
|
||||
"source", 's',
|
||||
0,
|
||||
G_OPTION_ARG_FILENAME, &source,
|
||||
"The source image of the cross-fade", "FILE"
|
||||
},
|
||||
{
|
||||
"target", 't',
|
||||
0,
|
||||
G_OPTION_ARG_FILENAME, &target,
|
||||
"The target image of the cross-fade", "FILE"
|
||||
},
|
||||
{
|
||||
"duration", 'd',
|
||||
0,
|
||||
G_OPTION_ARG_INT, &duration,
|
||||
"The duration of the cross-fade, in milliseconds", "MSECS"
|
||||
},
|
||||
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static gboolean
|
||||
start_animation (ClutterActor *actor,
|
||||
ClutterEvent *event,
|
||||
gpointer user_data)
|
||||
{
|
||||
ClutterState *transitions = CLUTTER_STATE (user_data);
|
||||
clutter_state_set_state (transitions, "show-top");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
load_image (ClutterTexture *texture,
|
||||
gchar *image_path)
|
||||
{
|
||||
GError *error = NULL;
|
||||
|
||||
gboolean success = clutter_texture_set_from_file (CLUTTER_TEXTURE (texture),
|
||||
image_path,
|
||||
&error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
g_warning ("Error loading %s\n%s", image_path, error->message);
|
||||
g_error_free (error);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
clutter_init_with_args (&argc, &argv,
|
||||
" - cross-fade", entries,
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
if (source == NULL || target == NULL)
|
||||
{
|
||||
g_print ("Usage: %s -s <source> -t <target> [-d <duration>]\n", argv[0]);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
GError *error = NULL;
|
||||
|
||||
/* UI */
|
||||
ClutterActor *stage;
|
||||
ClutterLayoutManager *layout;
|
||||
ClutterActor *box;
|
||||
ClutterActor *top, *bottom;
|
||||
ClutterState *transitions;
|
||||
|
||||
clutter_init (&argc, &argv);
|
||||
|
||||
stage = clutter_stage_get_default ();
|
||||
clutter_stage_set_title (CLUTTER_STAGE (stage), "cross-fade");
|
||||
clutter_actor_set_size (stage, 400, 300);
|
||||
clutter_actor_show (stage);
|
||||
g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
|
||||
|
||||
layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
|
||||
CLUTTER_BIN_ALIGNMENT_CENTER);
|
||||
|
||||
box = clutter_box_new (layout);
|
||||
clutter_actor_set_size (box, 400, 300);
|
||||
|
||||
bottom = clutter_texture_new ();
|
||||
top = clutter_texture_new ();
|
||||
|
||||
clutter_container_add_actor (CLUTTER_CONTAINER (box), bottom);
|
||||
clutter_container_add_actor (CLUTTER_CONTAINER (box), top);
|
||||
clutter_container_add_actor (CLUTTER_CONTAINER (stage), box);
|
||||
|
||||
/* load the first image into the bottom */
|
||||
load_image (CLUTTER_TEXTURE (bottom), source);
|
||||
|
||||
/* load the second image into the top */
|
||||
load_image (CLUTTER_TEXTURE (top), target);
|
||||
|
||||
/* animations */
|
||||
transitions = clutter_state_new ();
|
||||
clutter_state_set (transitions, NULL, "show-bottom",
|
||||
top, "opacity", CLUTTER_LINEAR, 0,
|
||||
bottom, "opacity", CLUTTER_LINEAR, 255,
|
||||
NULL);
|
||||
clutter_state_set (transitions, NULL, "show-top",
|
||||
top, "opacity", CLUTTER_EASE_IN_CUBIC, 255,
|
||||
bottom, "opacity", CLUTTER_EASE_IN_CUBIC, 0,
|
||||
NULL);
|
||||
clutter_state_set_duration (transitions, NULL, NULL, duration);
|
||||
|
||||
/* make the bottom opaque and top transparent */
|
||||
clutter_state_warp_to_state (transitions, "show-bottom");
|
||||
|
||||
/* on key press, fade in the top texture and fade out the bottom texture */
|
||||
g_signal_connect (stage,
|
||||
"key-press-event",
|
||||
G_CALLBACK (start_animation),
|
||||
transitions);
|
||||
|
||||
clutter_actor_show (stage);
|
||||
|
||||
clutter_main ();
|
||||
|
||||
g_object_unref (transitions);
|
||||
|
||||
if (error != NULL)
|
||||
g_error_free (error);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
@ -841,4 +841,540 @@ typedef struct _CoglTextureVertex {
|
||||
|
||||
</section>
|
||||
|
||||
<section id="textures-crossfade">
|
||||
<title>Cross-fading between two images</title>
|
||||
|
||||
<section>
|
||||
<title>Problem</title>
|
||||
|
||||
<para>You want to do a cross-fade animation (a.k.a. a dissolve
|
||||
transition) between two images.</para>
|
||||
|
||||
<para>An example use case would be creating a slideshow effect:
|
||||
load an image from a file, display it in the UI, then load a second
|
||||
image and cross-fade to it.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Solutions</title>
|
||||
|
||||
<para>There are two main approaches you could take:</para>
|
||||
|
||||
<orderedlist>
|
||||
<listitem>
|
||||
<para>Use two <type>ClutterTextures</type>, one on top
|
||||
of the other.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Use a single <type>ClutterTexture</type>
|
||||
with the two images in separate layers inside it.</para>
|
||||
</listitem>
|
||||
</orderedlist>
|
||||
|
||||
<section>
|
||||
<title>Solution 1: two textures</title>
|
||||
|
||||
<para>This approach uses two <type>ClutterTextures</type>,
|
||||
<varname>bottom</varname> and <varname>top</varname>. To begin
|
||||
with, the <varname>bottom</varname> texture shows the
|
||||
<emphasis>source</emphasis> image and is opaque; the
|
||||
<varname>top</varname> texture is loaded with
|
||||
the <emphasis>target</emphasis> image, but is not visible as
|
||||
it is fully transparent.</para>
|
||||
|
||||
<para>An animation is then used to fade in the
|
||||
<varname>top</varname> texture and fade out the
|
||||
<varname>bottom</varname> texture, leaving just <varname>top</varname>
|
||||
visible.</para>
|
||||
|
||||
<para>To implement this, first create the two textures inside a
|
||||
<type>ClutterBinLayout</type>:</para>
|
||||
|
||||
<informalexample>
|
||||
<programlisting>
|
||||
<![CDATA[
|
||||
/* ... initialise Clutter, get default stage etc. ... */
|
||||
|
||||
/* Actors added to this layout are centered on both x and y axes */
|
||||
ClutterLayoutManager *layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_CENTER,
|
||||
CLUTTER_BIN_ALIGNMENT_CENTER);
|
||||
|
||||
ClutterActor *box = clutter_box_new (layout);
|
||||
|
||||
/* set the size of the box so it fills the stage (in this case 600x600) */
|
||||
clutter_actor_set_size (box, 400, 400);
|
||||
|
||||
ClutterActor *bottom = clutter_texture_new ();
|
||||
ClutterActor *top = clutter_texture_new ();
|
||||
|
||||
/*
|
||||
* Add the textures to the layout;
|
||||
* NB because top is added last, it will be "on top of" bottom
|
||||
*/
|
||||
clutter_container_add_actor (CLUTTER_CONTAINER (box), bottom);
|
||||
clutter_container_add_actor (CLUTTER_CONTAINER (box), top);
|
||||
|
||||
/* stage is a ClutterStage instance */
|
||||
clutter_container_add_actor (CLUTTER_CONTAINER (stage), box);
|
||||
]]>
|
||||
</programlisting>
|
||||
</informalexample>
|
||||
|
||||
<para>Load the <varname>source</varname> image into the bottom
|
||||
texture and the <varname>target</varname> image into the top one.
|
||||
As this is the same operation each time, it makes sense to write
|
||||
a function for loading an image into a texture and checking
|
||||
for errors, e.g.:</para>
|
||||
|
||||
<informalexample>
|
||||
<programlisting>
|
||||
<![CDATA[
|
||||
static gboolean
|
||||
load_image (ClutterTexture *texture,
|
||||
gchar *image_path)
|
||||
{
|
||||
GError *error = NULL;
|
||||
|
||||
gboolean success = clutter_texture_set_from_file (CLUTTER_TEXTURE (texture),
|
||||
image_path,
|
||||
&error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
g_warning ("Error loading %s\n%s", image_path, error->message);
|
||||
g_error_free (error);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
]]>
|
||||
</programlisting>
|
||||
</informalexample>
|
||||
|
||||
<para>The <function>load_image()</function> function can then
|
||||
be called for each texture:</para>
|
||||
|
||||
<informalexample>
|
||||
<programlisting>
|
||||
<![CDATA[
|
||||
/* file path to the image visible when the UI is first displayed */
|
||||
gchar *source = NULL;
|
||||
|
||||
/* file path to the image we're going to cross-fade to */
|
||||
gchar *target = NULL;
|
||||
|
||||
/* ...set image file paths, e.g. from command line or directory read... */
|
||||
|
||||
/* the bottom texture contains the source image */
|
||||
load_image (CLUTTER_TEXTURE (bottom), source);
|
||||
|
||||
/* the top texture contains the target image */
|
||||
load_image (CLUTTER_TEXTURE (top), target);
|
||||
]]>
|
||||
</programlisting>
|
||||
</informalexample>
|
||||
|
||||
<para>For the animations, we use <type>ClutterState</type> as we
|
||||
want to animate two actors at once (<varname>top</varname>
|
||||
and <varname>bottom</varname>):</para>
|
||||
|
||||
<informalexample>
|
||||
<programlisting>
|
||||
<![CDATA[
|
||||
ClutterState *transitions = clutter_state_new ();
|
||||
|
||||
/* start state, where bottom is opaque and top is transparent */
|
||||
clutter_state_set (transitions, NULL, "show-bottom",
|
||||
top, "opacity", CLUTTER_LINEAR, 0,
|
||||
bottom, "opacity", CLUTTER_LINEAR, 255,
|
||||
NULL);
|
||||
|
||||
/* end state, where top is opaque and bottom is transparent */
|
||||
clutter_state_set (transitions, NULL, "show-top",
|
||||
top, "opacity", CLUTTER_EASE_IN_CUBIC, 255,
|
||||
bottom, "opacity", CLUTTER_EASE_IN_CUBIC, 0,
|
||||
NULL);
|
||||
|
||||
/* set 1000ms duration for all transitions between states */
|
||||
clutter_state_set_duration (transitions, NULL, NULL, 1000);
|
||||
]]>
|
||||
</programlisting>
|
||||
</informalexample>
|
||||
|
||||
<para>Note that rather than set the start opacities manually
|
||||
on the actors (e.g. using
|
||||
<function>clutter_actor_set_opacity()</function>),
|
||||
I've used a <type>ClutterState</type> to define the start
|
||||
state (as well as the end state). This makes it easier to
|
||||
track transitions, as they are all kept in one data structure.</para>
|
||||
|
||||
<note>
|
||||
<para>The easing modes used for the cross-fade animation
|
||||
(<constant>CLUTTER_EASE_IN_CUBIC</constant>)
|
||||
can be set to whatever you like. I personally think that
|
||||
ease-in modes look best for cross-fading.</para>
|
||||
</note>
|
||||
|
||||
<para>"Warp" the two textures into the start state
|
||||
(<varname>bottom</varname> opaque, <varname>top</varname>
|
||||
transparent):</para>
|
||||
|
||||
<informalexample>
|
||||
<programlisting>
|
||||
<![CDATA[
|
||||
clutter_state_warp_to_state (transitions, "show-bottom");
|
||||
]]>
|
||||
</programlisting>
|
||||
</informalexample>
|
||||
|
||||
<para>Using <function>clutter_state_warp_to_state()</function>
|
||||
immediately transitions to a state without animating, which
|
||||
in this case sets up the initial state of the UI.</para>
|
||||
|
||||
<para>Finally, use the <type>ClutterState</type> to animate
|
||||
the two textures, so <varname>top</varname> fades in and
|
||||
<varname>bottom</varname> fades out:</para>
|
||||
|
||||
<informalexample>
|
||||
<programlisting>
|
||||
<![CDATA[
|
||||
clutter_state_set_state (transitions, "show-top");
|
||||
]]>
|
||||
</programlisting>
|
||||
</informalexample>
|
||||
|
||||
<para>Here's what it looks like:</para>
|
||||
|
||||
<inlinemediaobject>
|
||||
<videoobject>
|
||||
<videodata fileref="videos/textures-crossfade-two-textures.ogv"/>
|
||||
</videoobject>
|
||||
<alt>
|
||||
<para>Video showing a cross-fade between two textures</para>
|
||||
</alt>
|
||||
</inlinemediaobject>
|
||||
|
||||
<para>The full code for this example
|
||||
<link linkend="textures-crossfade-example-1">is in the
|
||||
appendix</link>.</para>
|
||||
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Solution 2: one texture with two layers</title>
|
||||
|
||||
<para>The alternative solution is to use a single texture
|
||||
and the low-level COGL API to set up two different layers
|
||||
inside it, one for each image.</para>
|
||||
|
||||
<para>Then, rather than fade between two textures,
|
||||
progressively combine the two layers together using an
|
||||
alpha value which changes over the course of an animation
|
||||
(from 0.0 at the start of the animation to 1.0 at its end).</para>
|
||||
|
||||
<para>At any point in the cross-fade animation, you are
|
||||
actually seeing a combination of the color
|
||||
values in the two images (modified by an alpha component), rather
|
||||
than seeing one image through the other. This can give a smoother
|
||||
cross-fade effect than the two texture approach.</para>
|
||||
|
||||
<para>As this solution is more complex
|
||||
and relies on the lower-level (and more difficult to follow)
|
||||
COGL API, the next section is just a short summary of how it
|
||||
works; see <link linkend="textures-crossfade-example-2">the
|
||||
sample code, which has liberal comments</link> for more details.</para>
|
||||
|
||||
<note>
|
||||
<para>For more about texture combining, refer to the COGL
|
||||
API documentation (particularly the section about material
|
||||
blend strings). You may also find it useful to get hold of
|
||||
a decent OpenGL reference. (So you can look it up, what we're
|
||||
doing in this solution is using a texture combiner with
|
||||
interpolation as the texture combiner function.)</para>
|
||||
</note>
|
||||
|
||||
<section>
|
||||
<title>Cross-fading using a texture combiner with interpolation</title>
|
||||
|
||||
<para>The cross-fade is implemented by combining the two layers,
|
||||
computing a color value for each pixel in the resulting texture.
|
||||
The value for each pixel at a given point in the animation
|
||||
is based on three things:</para>
|
||||
|
||||
<orderedlist>
|
||||
<listitem>
|
||||
<para>The color value of the <emphasis>source</emphasis>
|
||||
pixel</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>The color value of the <emphasis>target</emphasis>
|
||||
pixel</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>The alpha value of a third colour at the given point
|
||||
in the animation's timeline</para>
|
||||
</listitem>
|
||||
</orderedlist>
|
||||
|
||||
<para>The resulting value for each RGBA color component in each pixel
|
||||
is computed using an interpolation function. In pseudo-code, it
|
||||
looks like this:</para>
|
||||
|
||||
<informalexample>
|
||||
<programlisting>
|
||||
color component value = (target pixel value * alpha) + (source pixel value * (1 - alpha))
|
||||
</programlisting>
|
||||
</informalexample>
|
||||
|
||||
<para>The effect is that as the alpha increases towards 1.0,
|
||||
progressively more of the <emphasis>target</emphasis> pixel's
|
||||
color is used, and progressively less of the <emphasis>source</emphasis>
|
||||
pixel's: so the <emphasis>target</emphasis> fades in, while
|
||||
the <emphasis>source</emphasis> fades out.</para>
|
||||
|
||||
<para>The advantage of this approach is that color and
|
||||
brightness transitions only occur where pixels differ between
|
||||
the two images. This means that you transitions are smoother
|
||||
where you are cross-fading between images with similar color ranges
|
||||
and brightness levels.</para>
|
||||
|
||||
<para>A special case is where you're cross-fading
|
||||
from an image to itself: the two texture approach can cause some
|
||||
dimming during this kind of transition; but the single texture
|
||||
approach results in no color or brightness changes (it's not even
|
||||
a transition as such, as all the pixels are identical in
|
||||
the two layers).</para>
|
||||
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="textures-crossfade-discussion">
|
||||
<title>Discussion</title>
|
||||
|
||||
<section>
|
||||
<title>Cross-fades between images of different sizes</title>
|
||||
|
||||
<para>The code examples
|
||||
(<link linkend="textures-crossfade-example-1">two textures</link>,
|
||||
<link linkend="textures-crossfade-example-2">one texture with
|
||||
COGL</link>) don't take account of the size of the images being
|
||||
loaded.</para>
|
||||
|
||||
<para>In the two texture example, this isn't so much of a problem,
|
||||
as you can resize the textures individually to the images:
|
||||
providing you use
|
||||
<function>clutter_texture_set_keep_aspect_ratio()</function>,
|
||||
different image sizes shouldn't be a problem. See
|
||||
<link linkend="textures-crossfade-example-3">the slideshow
|
||||
example</link>, for a demonstration of how to cycle through
|
||||
different sized images.</para>
|
||||
|
||||
<para>In the case of the single texture approach, you will get
|
||||
problems when cross-fading between two images with
|
||||
different sizes. There is no easy way to maintain the aspect
|
||||
ratio (as you have two layers, potentially with different sizes,
|
||||
in the same texture). The last layer added to the
|
||||
<type>CoglMaterial</type> determines the size of the texture;
|
||||
so if the previous layer has different dimensions, it will
|
||||
appear distorted in the UI. In the
|
||||
<link linkend="textures-crossfade-example-2">single texture
|
||||
code example</link>, the <emphasis>source</emphasis> layer
|
||||
is added first; so, if the <emphasis>target</emphasis> layer has
|
||||
different dimensions, the <emphasis>source</emphasis> will
|
||||
appear distorted.</para>
|
||||
|
||||
<para>There are a couple of ways you can remedy this:</para>
|
||||
|
||||
<orderedlist>
|
||||
|
||||
<listitem>
|
||||
<para>As you load each image into its own
|
||||
<type>CoglTexture</type>, get its size with
|
||||
<function>cogl_texture_get_width()</function> and
|
||||
<function>cogl_texture_get_height()</function>. Then set the
|
||||
<type>ClutterTexture's</type> size to the
|
||||
size of the source layer. Next, as
|
||||
you cross-fade, simultaneously animate a
|
||||
size change in the <type>ClutterTexture</type> to
|
||||
the target image's size.</para>
|
||||
<para>This could work with non-realistic images where
|
||||
some distortion of the image is acceptable (the target image
|
||||
may be the wrong size to start with, but transition to the
|
||||
correct size by the time it's fully faded in). But it can
|
||||
look a bit odd for transitions between photos.</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>Use GdkPixbuf (or similar) to load the images into a temporary
|
||||
data structure. (GdkPixbuf works well for this as it can resize
|
||||
the image while retaining its aspect ratio.) Then load the data from
|
||||
the pixbuf into a <emphasis>region</emphasis> of a
|
||||
<type>CoglTexture</type> which has the same dimensions as
|
||||
the <type>ClutterTexture</type>.</para>
|
||||
|
||||
<para>Here's an example of how you can rewrite the
|
||||
<function>load_cogl_texture()</function> function of
|
||||
the <link linkend="textures-crossfade-example-2">single
|
||||
texture example</link> to do this:</para>
|
||||
|
||||
<informalexample>
|
||||
<programlisting>
|
||||
<![CDATA[
|
||||
/* requires this extra include */
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||
|
||||
static CoglHandle
|
||||
load_cogl_texture (const char *type,
|
||||
const char *file,
|
||||
const guint texture_width,
|
||||
const guint texture_height)
|
||||
{
|
||||
GError *error = NULL;
|
||||
|
||||
/*
|
||||
* Load image data from a file into a GdkPixbuf,
|
||||
* but constrained to the size of the target ClutterTexture;
|
||||
* aspect ratio is maintained
|
||||
*
|
||||
* texture_width and texture_height are set elsewhere to
|
||||
* the width and height of the ClutterTexture
|
||||
*/
|
||||
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_size (file,
|
||||
texture_width,
|
||||
texture_height,
|
||||
&error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
g_print ("Unable to load %s image: %s\n", type, error->message);
|
||||
g_error_free (error);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
guint rowstride = gdk_pixbuf_get_rowstride (pixbuf);
|
||||
guint width = gdk_pixbuf_get_width (pixbuf);
|
||||
guint height = gdk_pixbuf_get_height (pixbuf);
|
||||
guchar *data = gdk_pixbuf_get_pixels (pixbuf);
|
||||
|
||||
CoglPixelFormat format = COGL_PIXEL_FORMAT_RGB_888;
|
||||
if (gdk_pixbuf_get_has_alpha (pixbuf) == TRUE)
|
||||
format = COGL_PIXEL_FORMAT_RGBA_8888;
|
||||
|
||||
/* CoglTexture with the same dimensions as the ClutterTexture */
|
||||
CoglHandle *tex = cogl_texture_new_with_size (texture_width,
|
||||
texture_height,
|
||||
COGL_TEXTURE_NO_SLICING,
|
||||
format);
|
||||
|
||||
/*
|
||||
* load the texture data into a region of the full-sized texture;
|
||||
* the size of the region is set from the size of the image data
|
||||
* (as resized by GdkPixbuf)
|
||||
*/
|
||||
cogl_texture_set_region (tex,
|
||||
0, 0, /* from top-left corner of the pixbuf */
|
||||
(texture_width - width) / 2, /* center on the CoglTexture */
|
||||
(texture_height - height) / 2, /* center on the CoglTexture */
|
||||
width, height,
|
||||
width, height,
|
||||
format,
|
||||
rowstride,
|
||||
data);
|
||||
|
||||
return tex;
|
||||
}
|
||||
]]>
|
||||
</programlisting>
|
||||
</informalexample>
|
||||
|
||||
<para>Because you're copying the image data from the
|
||||
file into a region of the <type>CoglTexture</type>
|
||||
that's the same size as the image data in the pixbuf, it isn't
|
||||
distorted.</para>
|
||||
|
||||
</listitem>
|
||||
|
||||
</orderedlist>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="textures-crossfade-discussion-slideshows">
|
||||
<title>Slideshows</title>
|
||||
|
||||
<para>The two texture solution can be easily extended
|
||||
to cycle through multiple images. To begin with, the first
|
||||
image is loaded into the <varname>top</varname> texture. Then,
|
||||
the basic pattern for transitioning to the next image is as follows:</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>Copy the data from the <varname>top</varname> texture
|
||||
to the <varname>bottom</varname> texture.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Make the <varname>top</varname> texture transparent
|
||||
and the <varname>bottom</varname> texture opaque (using
|
||||
<function>clutter_state_warp_to_state()</function>). At this
|
||||
point, it appears as though the textures haven't changed.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Load the next image into <varname>top</varname>.</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>When <varname>top</varname> has finished loading,
|
||||
fade it in while simultaneously fading out
|
||||
<varname>bottom</varname> (using
|
||||
<function>clutter_state_set_state()</function>).</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>The <link linkend="textures-crossfade-example-3">sample
|
||||
code in the appendix</link> implements this as part of
|
||||
a simple slideshow application.</para>
|
||||
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Full examples</title>
|
||||
|
||||
<example id="textures-crossfade-example-1">
|
||||
<title>Cross-fading between two images using two
|
||||
<type>ClutterTextures</type></title>
|
||||
<programlisting>
|
||||
<xi:include href="examples/textures-crossfade.c" parse="text">
|
||||
<xi:fallback>there should be a code sample here, but there isn't...</xi:fallback>
|
||||
</xi:include>
|
||||
</programlisting>
|
||||
</example>
|
||||
|
||||
<example id="textures-crossfade-example-2">
|
||||
<title>Cross-fading between two images using one
|
||||
<type>ClutterTexture</type> and the COGL API</title>
|
||||
<programlisting>
|
||||
<xi:include href="examples/textures-crossfade-cogl.c" parse="text">
|
||||
<xi:fallback>there should be a code sample here, but there isn't...</xi:fallback>
|
||||
</xi:include>
|
||||
</programlisting>
|
||||
</example>
|
||||
|
||||
<example id="textures-crossfade-example-3">
|
||||
<title>A simple slideshow application using two
|
||||
<type>ClutterTextures</type></title>
|
||||
<programlisting>
|
||||
<xi:include href="examples/textures-crossfade-slideshow.c" parse="text">
|
||||
<xi:fallback>there should be a code sample here, but there isn't...</xi:fallback>
|
||||
</xi:include>
|
||||
</programlisting>
|
||||
</example>
|
||||
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
|
BIN
doc/cookbook/videos/textures-crossfade-two-textures.ogv
Normal file
BIN
doc/cookbook/videos/textures-crossfade-two-textures.ogv
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user