From 83a8d0b3bb2ad47db6afd32bd2aa503814486e07 Mon Sep 17 00:00:00 2001 From: Elliot Smith Date: Thu, 12 Aug 2010 17:39:59 +0100 Subject: [PATCH] cookbook: Added example code for texture cross-fading Added simple image viewer which loads image file names from a directory, displays the first one, then displays the next in the list with each key press. Uses the primitive fade front in/fade back out approach. Also adapted Emmanuele's example code which uses Cogl to produce a similar effect, but within a single texture. This code loads two images specified on the command line and cross-fades between them. --- doc/cookbook/examples/Makefile.am | 4 + .../examples/textures-crossfade-cogl.c | 174 ++++++++++++++++++ doc/cookbook/examples/textures-crossfade.c | 174 ++++++++++++++++++ 3 files changed, 352 insertions(+) create mode 100644 doc/cookbook/examples/textures-crossfade-cogl.c create mode 100644 doc/cookbook/examples/textures-crossfade.c diff --git a/doc/cookbook/examples/Makefile.am b/doc/cookbook/examples/Makefile.am index d80a7da2c..cc9c7c4a1 100644 --- a/doc/cookbook/examples/Makefile.am +++ b/doc/cookbook/examples/Makefile.am @@ -11,6 +11,8 @@ noinst_PROGRAMS = \ layouts-stacking \ layouts-stacking-diff-sized-actors \ events-mouse-scroll \ + textures-crossfade \ + textures-crossfade-cogl \ $(NULL) INCLUDES = \ @@ -40,5 +42,7 @@ textures_sub_texture_SOURCES = textures-sub-texture.c layouts_stacking_SOURCES = layouts-stacking.c layouts_stacking_diff_sized_actors_SOURCES = layouts-stacking-diff-sized-actors.c events_mouse_scroll_SOURCES = events-mouse-scroll.c +textures_crossfade_SOURCES = textures-crossfade.c +textures_crossfade_cogl_SOURCES = textures-crossfade-cogl.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..029d91dd8 --- /dev/null +++ b/doc/cookbook/examples/textures-crossfade-cogl.c @@ -0,0 +1,174 @@ +#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); + + /* sizes of textures */ + gfloat source_width, source_height, target_width, target_height; + source_width = cogl_texture_get_width (texture_1); + source_height = cogl_texture_get_height (texture_1); + target_width = cogl_texture_get_width (texture_2); + target_height = cogl_texture_get_height (texture_2); + + /* Create a new Cogl material holding the two textures inside two + * separate layers. Layer 0 is the one which will end + * up being visible. + */ + 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_new (); + clutter_stage_set_title (CLUTTER_STAGE (stage), "cross-fade"); + clutter_actor_set_size (stage, 600, 600); + clutter_actor_show (stage); + g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL); + + ClutterActor *texture = clutter_texture_new (); + clutter_actor_set_size (texture, source_width, source_height); + 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)); + clutter_container_add_actor (CLUTTER_CONTAINER (stage), texture); + 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); + + /* animate */ + clutter_timeline_start (timeline); + + clutter_main (); + + g_object_unref (timeline); + + 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..420f43d8b --- /dev/null +++ b/doc/cookbook/examples/textures-crossfade.c @@ -0,0 +1,174 @@ +#include +#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 *front; + ClutterActor *back; + 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->front)); + + if (cogl_texture != NULL) + { + /* copy the current texture into the background */ + clutter_texture_set_cogl_texture (CLUTTER_TEXTURE (app->back), cogl_texture); + + /* make the back opaque and front transparent */ + clutter_state_warp_to_state (app->transitions, "show-back"); + } + + /* load the next image into the front */ + GError *error = NULL; + clutter_texture_set_from_file (CLUTTER_TEXTURE (app->front), + 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 front texture and fade out the back texture */ + clutter_state_set_state (app->transitions, "show-front"); + + 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->back = clutter_texture_new (); + clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (app->back), TRUE); + + app->front = clutter_texture_new (); + clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (app->front), TRUE); + + clutter_container_add_actor (CLUTTER_CONTAINER (box), app->back); + clutter_container_add_actor (CLUTTER_CONTAINER (box), app->front); + clutter_container_add_actor (CLUTTER_CONTAINER (stage), box); + + /* animations */ + app->transitions = clutter_state_new (); + clutter_state_set (app->transitions, NULL, "show-front", + app->front, "opacity", CLUTTER_EASE_IN_CUBIC, 255, + app->back, "opacity", CLUTTER_EASE_IN_CUBIC, 0, + NULL); + clutter_state_set (app->transitions, NULL, "show-back", + app->front, "opacity", CLUTTER_LINEAR, 0, + app->back, "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; +}