diff --git a/doc/cookbook/Makefile.am b/doc/cookbook/Makefile.am index 3ca02255e..713af4803 100644 --- a/doc/cookbook/Makefile.am +++ b/doc/cookbook/Makefile.am @@ -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 = \ diff --git a/doc/cookbook/examples/Makefile.am b/doc/cookbook/examples/Makefile.am index 4f28f1027..530f4a92d 100644 --- a/doc/cookbook/examples/Makefile.am +++ b/doc/cookbook/examples/Makefile.am @@ -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 diff --git a/doc/cookbook/examples/textures-crossfade-cogl.c b/doc/cookbook/examples/textures-crossfade-cogl.c new file mode 100644 index 000000000..671d240cf --- /dev/null +++ b/doc/cookbook/examples/textures-crossfade-cogl.c @@ -0,0 +1,163 @@ +#include +#include + +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 -t [-d ]\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; +} diff --git a/doc/cookbook/examples/textures-crossfade-slideshow.c b/doc/cookbook/examples/textures-crossfade-slideshow.c new file mode 100644 index 000000000..bc7973a13 --- /dev/null +++ b/doc/cookbook/examples/textures-crossfade-slideshow.c @@ -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 +#include + +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 \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; +} diff --git a/doc/cookbook/examples/textures-crossfade.c b/doc/cookbook/examples/textures-crossfade.c new file mode 100644 index 000000000..0ba9ccbec --- /dev/null +++ b/doc/cookbook/examples/textures-crossfade.c @@ -0,0 +1,142 @@ +#include +#include + +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 -t [-d ]\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; +} diff --git a/doc/cookbook/textures.xml b/doc/cookbook/textures.xml index 56b661d7d..9a2cd0142 100644 --- a/doc/cookbook/textures.xml +++ b/doc/cookbook/textures.xml @@ -841,4 +841,540 @@ typedef struct _CoglTextureVertex { +
+ Cross-fading between two images + +
+ Problem + + You want to do a cross-fade animation (a.k.a. a dissolve + transition) between two images. + + 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. +
+ +
+ Solutions + + There are two main approaches you could take: + + + + Use two ClutterTextures, one on top + of the other. + + + Use a single ClutterTexture + with the two images in separate layers inside it. + + + +
+ Solution 1: two textures + + This approach uses two ClutterTextures, + bottom and top. To begin + with, the bottom texture shows the + source image and is opaque; the + top texture is loaded with + the target image, but is not visible as + it is fully transparent. + + An animation is then used to fade in the + top texture and fade out the + bottom texture, leaving just top + visible. + + To implement this, first create the two textures inside a + ClutterBinLayout: + + + + + + + + Load the source image into the bottom + texture and the target 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.: + + + +message); + g_error_free (error); + exit (EXIT_FAILURE); + } + + return success; +} +]]> + + + + The load_image() function can then + be called for each texture: + + + + + + + + For the animations, we use ClutterState as we + want to animate two actors at once (top + and bottom): + + + + + + + + Note that rather than set the start opacities manually + on the actors (e.g. using + clutter_actor_set_opacity()), + I've used a ClutterState 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. + + + The easing modes used for the cross-fade animation + (CLUTTER_EASE_IN_CUBIC) + can be set to whatever you like. I personally think that + ease-in modes look best for cross-fading. + + + "Warp" the two textures into the start state + (bottom opaque, top + transparent): + + + + + + + + Using clutter_state_warp_to_state() + immediately transitions to a state without animating, which + in this case sets up the initial state of the UI. + + Finally, use the ClutterState to animate + the two textures, so top fades in and + bottom fades out: + + + + + + + + Here's what it looks like: + + + + + + + Video showing a cross-fade between two textures + + + + The full code for this example + is in the + appendix. + +
+ +
+ Solution 2: one texture with two layers + + 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. + + 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). + + 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. + + 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 the + sample code, which has liberal comments for more details. + + + 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.) + + +
+ Cross-fading using a texture combiner with interpolation + + 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: + + + + The color value of the source + pixel + + + The color value of the target + pixel + + + The alpha value of a third colour at the given point + in the animation's timeline + + + + The resulting value for each RGBA color component in each pixel + is computed using an interpolation function. In pseudo-code, it + looks like this: + + + + color component value = (target pixel value * alpha) + (source pixel value * (1 - alpha)) + + + + The effect is that as the alpha increases towards 1.0, + progressively more of the target pixel's + color is used, and progressively less of the source + pixel's: so the target fades in, while + the source fades out. + + 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. + + 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). + +
+ +
+ +
+ +
+ Discussion + +
+ Cross-fades between images of different sizes + + The code examples + (two textures, + one texture with + COGL) don't take account of the size of the images being + loaded. + + 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 + clutter_texture_set_keep_aspect_ratio(), + different image sizes shouldn't be a problem. See + the slideshow + example, for a demonstration of how to cycle through + different sized images. + + 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 + CoglMaterial determines the size of the texture; + so if the previous layer has different dimensions, it will + appear distorted in the UI. In the + single texture + code example, the source layer + is added first; so, if the target layer has + different dimensions, the source will + appear distorted. + + There are a couple of ways you can remedy this: + + + + + As you load each image into its own + CoglTexture, get its size with + cogl_texture_get_width() and + cogl_texture_get_height(). Then set the + ClutterTexture's size to the + size of the source layer. Next, as + you cross-fade, simultaneously animate a + size change in the ClutterTexture to + the target image's size. + 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. + + + + 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 region of a + CoglTexture which has the same dimensions as + the ClutterTexture. + + Here's an example of how you can rewrite the + load_cogl_texture() function of + the single + texture example to do this: + + + + + +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; +} +]]> + + + + Because you're copying the image data from the + file into a region of the CoglTexture + that's the same size as the image data in the pixbuf, it isn't + distorted. + + + + + +
+ +
+ Slideshows + + The two texture solution can be easily extended + to cycle through multiple images. To begin with, the first + image is loaded into the top texture. Then, + the basic pattern for transitioning to the next image is as follows: + + + + Copy the data from the top texture + to the bottom texture. + + + Make the top texture transparent + and the bottom texture opaque (using + clutter_state_warp_to_state()). At this + point, it appears as though the textures haven't changed. + + + Load the next image into top. + + + When top has finished loading, + fade it in while simultaneously fading out + bottom (using + clutter_state_set_state()). + + + + The sample + code in the appendix implements this as part of + a simple slideshow application. + +
+ +
+ +
+ Full examples + + + Cross-fading between two images using two + <type>ClutterTextures</type> + + + there should be a code sample here, but there isn't... + + + + + + Cross-fading between two images using one + <type>ClutterTexture</type> and the COGL API + + + there should be a code sample here, but there isn't... + + + + + + A simple slideshow application using two + <type>ClutterTextures</type> + + + there should be a code sample here, but there isn't... + + + + +
+ +
+ diff --git a/doc/cookbook/videos/textures-crossfade-two-textures.ogv b/doc/cookbook/videos/textures-crossfade-two-textures.ogv new file mode 100644 index 000000000..b9c3c1b84 Binary files /dev/null and b/doc/cookbook/videos/textures-crossfade-two-textures.ogv differ