mirror of
https://github.com/brl/mutter.git
synced 2024-12-28 22:02:14 +00:00
cookbook: Added a recipe for cross-fading between two images
The recipe covers a two texture approach (using the Clutter API) and a single texture approach (using COGL). It also discusses issues with cross-fading between images of different sizes with the COGL API, and gives a longer example of cycling through multiple images in a slideshow application.
This commit is contained in:
parent
c492faecb2
commit
c207820bef
@ -841,4 +841,539 @@ 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 few 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>If the bounds of the images you're going to be loading
|
||||
are known (i.e. you know the greatest width and height
|
||||
of all the images you're going to load), you can size
|
||||
the <type>ClutterTexture</type> to those bounds.</para>
|
||||
|
||||
<para>Then, as you load each image (e.g. you could do this in the
|
||||
<function>load_cogl_texture()</function> function of
|
||||
the <link linkend="textures-crossfade-example-2">code
|
||||
example</link>):</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>Load the image into a temporary
|
||||
<type>CoglTexture</type> (e.g. called
|
||||
<varname>filetex</varname>). Then use the
|
||||
<type>CoglTexture</type> API to get the data from it.
|
||||
For example:</para>
|
||||
|
||||
<informalexample>
|
||||
<programlisting>
|
||||
<![CDATA[
|
||||
/* load texture from a file */
|
||||
CoglHandle *filetex = cogl_texture_new_from_file (FOX_FILE,
|
||||
COGL_TEXTURE_NO_SLICING,
|
||||
COGL_PIXEL_FORMAT_ANY,
|
||||
NULL);
|
||||
|
||||
CoglPixelFormat format = cogl_texture_get_format (filetex);
|
||||
guint rowstride = cogl_texture_get_rowstride (filetex);
|
||||
guint width = cogl_texture_get_width (filetex);
|
||||
guint height = cogl_texture_get_height (filetex);
|
||||
|
||||
/* allocate memory large enough for the data */
|
||||
gint size = cogl_texture_get_data (filetex, format, 0, NULL);
|
||||
guchar *data = g_new0 (guchar, size);
|
||||
|
||||
/* get the data from the texture */
|
||||
cogl_texture_get_data (filetex,
|
||||
format,
|
||||
rowstride,
|
||||
data);
|
||||
]]>
|
||||
</programlisting>
|
||||
</informalexample>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Create another <type>CoglTexture</type> as
|
||||
large as the <type>ClutterTexture</type> (i.e. big enough
|
||||
to fit any image you're going to load). Then copy the
|
||||
data from <varname>filetex</varname> into a region of
|
||||
it. For example:</para>
|
||||
|
||||
<informalexample>
|
||||
<programlisting>
|
||||
<![CDATA[
|
||||
/* COGL texture which is the same size as the ClutterTexture */
|
||||
CoglHandle *tex = cogl_texture_new_with_size (clutter_texture_width,
|
||||
clutter_texture_height,
|
||||
COGL_TEXTURE_NO_SLICING,
|
||||
format);
|
||||
|
||||
/* copy the texture data (from filetex) into a region of the full-sized texture */
|
||||
cogl_texture_set_region (tex,
|
||||
0, 0, 0, 0,
|
||||
clutter_texture_width,
|
||||
clutter_texture_height,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
rowstride,
|
||||
data);
|
||||
]]>
|
||||
</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, it won't be
|
||||
distorted. However, it's worth stressing that the
|
||||
<type>ClutterTexture</type> needs to be as wide as
|
||||
the widest image and as tall as the tallest, so
|
||||
all the images can be accommodated. Otherwise this
|
||||
works, but images might be clipped.</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
</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>
|
||||
|
Loading…
Reference in New Issue
Block a user