2010-07-01 10:29:46 +00:00
|
|
|
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
|
|
|
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
|
|
|
|
2010-07-18 10:15:25 +00:00
|
|
|
<chapter id="textures"
|
|
|
|
xmlns:xi="http://www.w3.org/2003/XInclude">
|
2010-07-01 10:29:46 +00:00
|
|
|
<title>Textures</title>
|
|
|
|
|
|
|
|
<epigraph>
|
2010-07-16 11:01:42 +00:00
|
|
|
<attribution>Antoine de Saint-Exupery</attribution>
|
|
|
|
<para>A rock pile ceases to be a rock pile the moment a single man
|
|
|
|
contemplates it, bearing within him the image of a cathedral.</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
</epigraph>
|
|
|
|
|
|
|
|
<section id="textures-introduction">
|
|
|
|
<title>Introduction</title>
|
|
|
|
|
2010-07-16 11:01:42 +00:00
|
|
|
<para>Textures are one of the most important actors in Clutter. Whether
|
|
|
|
they are employed as the background for a user interface control, or
|
|
|
|
to show the picture of a kitten, a big part of any Clutter-based
|
|
|
|
application is going to involve textures.</para>
|
|
|
|
|
|
|
|
<para>A ClutterTexture is an actor that can hold any raw image data and
|
|
|
|
paint it. ClutterTexture can also load image data from a file on disk and
|
|
|
|
convert it.</para>
|
|
|
|
|
|
|
|
<note><para>The actual formats supported by ClutterTexture depend on the
|
|
|
|
platform on which Clutter is being used.</para></note>
|
2010-07-01 10:29:46 +00:00
|
|
|
</section>
|
|
|
|
|
|
|
|
<section id="textures-drawing-with-cairo">
|
|
|
|
<title>Drawing 2D graphics onto a texture</title>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Problem</title>
|
|
|
|
|
|
|
|
<para>You want to draw 2D graphics inside a Clutter application.</para>
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Solution</title>
|
|
|
|
|
2010-07-06 15:00:33 +00:00
|
|
|
<para>Create a <type>ClutterCairoTexture</type>, then draw onto
|
|
|
|
the Cairo context it wraps using the Cairo API:</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
ClutterActor *texture;
|
|
|
|
cairo_t *cr;
|
|
|
|
|
|
|
|
guint width, height;
|
|
|
|
width = 800;
|
|
|
|
height = 600;
|
|
|
|
|
|
|
|
texture = clutter_cairo_texture_new (width, height);
|
|
|
|
|
|
|
|
cr = clutter_cairo_texture_create (CLUTTER_CAIRO_TEXTURE (texture));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* write onto the Cairo context cr using the Cairo API;
|
|
|
|
* see <ulink url="http://cairographics.org/manual/">the Cairo API reference</ulink> for details
|
|
|
|
*/
|
|
|
|
cairo_move_to (cr, 0, 0);
|
|
|
|
cairo_line_to (cr, 800, 600);
|
|
|
|
cairo_stroke (cr);
|
|
|
|
|
|
|
|
/* does the actual drawing onto the texture */
|
|
|
|
cairo_destroy (cr);
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<para>Here's a <ulink url="http://cairographics.org/tutorial/">useful
|
|
|
|
Cairo tutorial</ulink> if you want to learn more about the Cairo API
|
|
|
|
itself.</para>
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Discussion</title>
|
|
|
|
|
2010-07-06 15:00:33 +00:00
|
|
|
<para>A <type>ClutterCairoTexture</type> is a standard
|
|
|
|
<type>ClutterActor</type>, so it can be added to a
|
|
|
|
<type>ClutterContainer</type> (e.g. a <type>ClutterStage</type>
|
|
|
|
or <type>ClutterGroup</type>), animated, resized etc. in the
|
|
|
|
usual ways.</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
<para>Other useful operations:</para>
|
|
|
|
|
|
|
|
<itemizedlist>
|
|
|
|
|
|
|
|
<listitem>
|
|
|
|
<para><emphasis>To draw on part of the texture:</emphasis>
|
2010-07-06 15:00:33 +00:00
|
|
|
use <function>clutter_cairo_texture_create_region()</function> to
|
2010-07-01 10:29:46 +00:00
|
|
|
retrieve a Cairo context for the region you want to draw on.</para>
|
|
|
|
</listitem>
|
|
|
|
|
|
|
|
<listitem>
|
|
|
|
<para><emphasis>To clear existing content from a texture:</emphasis>
|
2010-07-06 15:00:33 +00:00
|
|
|
use <function>clutter_cairo_texture_clear()</function>.</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
<para>You may need to do this as the texture reuses the same
|
|
|
|
Cairo context each time you call
|
2010-07-06 15:00:33 +00:00
|
|
|
<function>clutter_cairo_texture_create()</function> or
|
|
|
|
<function>clutter_cairo_texture_create_region()</function>.</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
</listitem>
|
|
|
|
|
|
|
|
<listitem>
|
|
|
|
<para><emphasis>To resize the Cairo context wrapped
|
|
|
|
by a texture</emphasis>, use
|
2010-07-06 15:00:33 +00:00
|
|
|
<function>clutter_cairo_texture_set_surface_size()</function>.</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
</listitem>
|
|
|
|
|
|
|
|
</itemizedlist>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Drawing pages from a PDF onto a ClutterCairoContext</title>
|
|
|
|
|
|
|
|
<para>Other libraries may provide an API for writing onto a
|
|
|
|
Cairo context; you can make use of these APIs on the exposed
|
|
|
|
Cairo context of a ClutterCairoTexture. For example, you
|
|
|
|
can use the poppler-glib API to display pages
|
|
|
|
from a PopplerDocument inside a Clutter application:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
<![CDATA[
|
|
|
|
#include <poppler/glib/poppler.h>
|
|
|
|
|
|
|
|
/* snipped setup code (as above) */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* cast to CLUTTER_CAIRO_TEXTURE, as the functions
|
|
|
|
* used below require that type
|
|
|
|
*/
|
|
|
|
ClutterCairoTexture *cc_texture = CLUTTER_CAIRO_TEXTURE (texture);
|
|
|
|
|
|
|
|
clutter_cairo_texture_clear (cc_texture);
|
|
|
|
|
|
|
|
gchar *file_uri = "file:///path/to/file.pdf";
|
|
|
|
guint page_num = 0;
|
|
|
|
double page_width, page_height;
|
|
|
|
|
|
|
|
PopplerDocument *doc;
|
|
|
|
PopplerPage *page;
|
|
|
|
GError *error = NULL;
|
|
|
|
|
|
|
|
doc = poppler_document_new_from_file (file_uri, NULL, &error);
|
|
|
|
|
|
|
|
page = poppler_document_get_page (doc, page_num);
|
|
|
|
|
|
|
|
poppler_page_get_size (page, &page_width, &page_height);
|
|
|
|
|
|
|
|
cr = clutter_cairo_texture_create (cc_texture);
|
|
|
|
|
|
|
|
/* render the page to the context */
|
|
|
|
poppler_page_render (page, cr);
|
|
|
|
|
|
|
|
cairo_destroy (cr);
|
|
|
|
]]>
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
2010-07-06 15:09:58 +00:00
|
|
|
<note><para>If the page is larger than the Cairo context,
|
2010-07-06 15:00:33 +00:00
|
|
|
some of it might not be visible. Similarly, if the
|
|
|
|
<type>ClutterCairoTexture</type> is larger than the stage,
|
|
|
|
some of that might not be visible. So you
|
|
|
|
may need to do some work to make the <type>ClutterCairoTexture</type>
|
|
|
|
fit inside the stage properly (e.g. resize the stage), and/or some work
|
2010-07-01 10:29:46 +00:00
|
|
|
to make the PDF page sit inside the Cairo context (e.g. scale the PDF
|
2010-07-06 15:09:58 +00:00
|
|
|
page or put it inside a scrollable actor).</para></note>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section id="textures-aspect-ratio">
|
|
|
|
<title>Maintaining the aspect ratio when loading an
|
|
|
|
image into a texture</title>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Problem</title>
|
|
|
|
|
|
|
|
<para>You want want to load an image into a texture
|
|
|
|
and scale it, while retaining the underlying image's aspect ratio.</para>
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Solution</title>
|
|
|
|
|
|
|
|
<para>Set the texture to keep the aspect ratio of the
|
|
|
|
underlying image (so it doesn't distort when it's scaled); use
|
2010-07-06 15:00:33 +00:00
|
|
|
the actor's <property>request-mode</property> property to set
|
|
|
|
the correct geometry management (see the discussion section); then
|
2010-07-01 10:29:46 +00:00
|
|
|
resize the texture along one dimension (height or width).
|
|
|
|
Now, when an image is loaded into the texture, the image is
|
|
|
|
scaled to fit the set height or width; the other dimension
|
|
|
|
is automatically scaled by the same factor so the image fits
|
|
|
|
the texture:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
<![CDATA[
|
|
|
|
ClutterActor *texture;
|
|
|
|
texture = clutter_texture_new ();
|
|
|
|
|
|
|
|
clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (texture), TRUE);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* this setting means the height of the scaled image is based on its width;
|
|
|
|
* it's not strictly necessary to set this, as this is the default
|
|
|
|
*/
|
|
|
|
clutter_actor_set_request_mode (texture, CLUTTER_REQUEST_HEIGHT_FOR_WIDTH);
|
|
|
|
|
|
|
|
/* set the width, which causes height to be scaled by the same factor */
|
|
|
|
clutter_actor_set_width (texture, 300);
|
|
|
|
|
|
|
|
clutter_texture_set_from_file (CLUTTER_TEXTURE (texture),
|
|
|
|
"/path/to/image.jpg",
|
|
|
|
NULL);
|
|
|
|
]]>
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Discussion</title>
|
|
|
|
|
2010-07-06 15:00:33 +00:00
|
|
|
<para>The <property>request-mode</property> for an actor
|
|
|
|
determines how geometry requisition is performed; in this case, this
|
2010-07-01 10:29:46 +00:00
|
|
|
includes how scaling is applied if you change the actor's
|
|
|
|
width or height. There are two possible values for
|
|
|
|
request-mode:</para>
|
|
|
|
|
|
|
|
<orderedlist>
|
|
|
|
<listitem>
|
2010-07-06 15:00:33 +00:00
|
|
|
<para>If set to <constant>CLUTTER_REQUEST_HEIGHT_FOR_WIDTH</constant>
|
2010-07-01 10:29:46 +00:00
|
|
|
(the default), changing the width causes the height
|
|
|
|
to be scaled by the same factor as the width.</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
2010-07-06 15:00:33 +00:00
|
|
|
<para>If set to <constant>CLUTTER_REQUEST_WIDTH_FOR_HEIGHT</constant>,
|
2010-07-01 10:29:46 +00:00
|
|
|
changing the height causes the width to be scaled by the
|
|
|
|
same factor as the height.</para>
|
|
|
|
</listitem>
|
|
|
|
</orderedlist>
|
|
|
|
|
|
|
|
<para>In the example above, the texture is set to keep its
|
|
|
|
aspect ratio then fixed to a width of 300 pixels; the
|
2010-07-06 15:00:33 +00:00
|
|
|
request-mode is set to <constant>CLUTTER_REQUEST_HEIGHT_FOR_WIDTH</constant>.
|
|
|
|
If a standard, photo-sized image in landscape orientation were
|
2010-07-01 10:29:46 +00:00
|
|
|
loaded into it (2848 pixels wide x 2136 high), it would be scaled
|
|
|
|
down to 300 pixels wide; then, its height would be scaled by the
|
|
|
|
same factor as the width (i.e. scaled down to 225 pixels).</para>
|
|
|
|
|
2010-07-06 15:00:33 +00:00
|
|
|
<para>With request-mode set to
|
|
|
|
<constant>CLUTTER_REQUEST_WIDTH_FOR_HEIGHT</constant>,
|
2010-07-01 10:29:46 +00:00
|
|
|
you would get the same effect by setting the height first;
|
|
|
|
then, computation of the width for the scaled image would be
|
|
|
|
based on the scaling factor applied to its height instead.</para>
|
|
|
|
|
|
|
|
<para>You can work out which side of the source image is longest using
|
2010-07-06 15:00:33 +00:00
|
|
|
<function>clutter_texture_base_size()</function> to get its
|
|
|
|
width and height. This can be useful when trying to scale images
|
|
|
|
with different orientations to fit into uniform rows or columns:</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
<![CDATA[
|
|
|
|
gint width;
|
|
|
|
gint height;
|
|
|
|
|
|
|
|
clutter_texture_get_base_size (CLUTTER_TEXTURE (texture), &width, &height);
|
|
|
|
]]>
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
2010-07-01 15:05:41 +00:00
|
|
|
<note><para>If you explicitly set the size (both width and height)
|
2010-07-06 15:00:33 +00:00
|
|
|
of a texture with <function>clutter_actor_set_size()</function> (or
|
|
|
|
with <function>clutter_actor_set_width()</function> and
|
|
|
|
<function>clutter_actor_set_height()</function>), any
|
2010-07-01 10:29:46 +00:00
|
|
|
image loaded into the texture is automatically stretched/shrunk to
|
|
|
|
fit the texture. This is the case regardless of any other settings
|
2010-07-01 15:05:41 +00:00
|
|
|
(like whether to keep aspect ratio).</para></note>
|
|
|
|
|
|
|
|
<note><para>Since a texture can scale down its contents, its minimum
|
|
|
|
preferred size is 0.</para></note>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section id="textures-image-loading">
|
|
|
|
<title>Loading image data into a texture</title>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Problem</title>
|
|
|
|
|
|
|
|
<para>You want to display an image inside a Clutter
|
|
|
|
application.</para>
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Solution</title>
|
|
|
|
|
2010-07-06 15:00:33 +00:00
|
|
|
<para>Create a <type>ClutterTexture</type> directly from an
|
|
|
|
image file:</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
<![CDATA[
|
|
|
|
ClutterActor *texture;
|
|
|
|
GError *error = NULL;
|
|
|
|
gchar *image_path = "/path/to/image";
|
|
|
|
|
|
|
|
texture = clutter_texture_new_from_file (image_path, &error);
|
|
|
|
|
|
|
|
if (error != NULL)
|
|
|
|
{
|
|
|
|
// handle error
|
|
|
|
}
|
|
|
|
]]>
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<para>Or create a texture and set its source to an image
|
|
|
|
file:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
<![CDATA[
|
|
|
|
ClutterActor *texture;
|
|
|
|
GError *error = NULL;
|
|
|
|
gchar *image_path = "/path/to/image";
|
|
|
|
gboolean loaded;
|
|
|
|
|
|
|
|
texture = clutter_texture_new ();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* returns FALSE if file could not be loaded or texture
|
|
|
|
* could not be set from image data in the file
|
|
|
|
*/
|
|
|
|
loaded = clutter_texture_set_from_file (CLUTTER_TEXTURE (texture),
|
|
|
|
image_path,
|
|
|
|
&error);
|
|
|
|
|
|
|
|
if (error != NULL)
|
|
|
|
{
|
|
|
|
// handle error
|
|
|
|
}
|
|
|
|
]]>
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Discussion</title>
|
|
|
|
|
|
|
|
<para>Bear the following in mind when loading images into a
|
|
|
|
texture:</para>
|
|
|
|
|
|
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
|
|
<para>An image load may fail if:
|
|
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
|
|
<para>The file does not exist.</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>The image format is unsupported: most of the
|
|
|
|
common bitmap formats (PNG, JPEG, BMP, GIF, TIFF, XPM)
|
|
|
|
are supported, but more exotic ones may not be.</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>Whether you're creating a texture from an image file,
|
|
|
|
or loading an image from a file into an existing texture,
|
|
|
|
you should specify the filesystem path to the file, rather
|
|
|
|
than a URI.</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Synchronous vs. asynchronous image loading</title>
|
|
|
|
|
|
|
|
<para>The code examples above show the simplest approach:
|
|
|
|
loading an image into a texture synchronously. This means that
|
|
|
|
the application waits for each image to be loaded before continuing;
|
|
|
|
which is acceptable in this case, but may not be when
|
|
|
|
loading images into multiple textures.</para>
|
|
|
|
|
|
|
|
<para>Another approach is to load data into textures
|
|
|
|
asynchronously. This requires some extra set up in your code:</para>
|
|
|
|
|
|
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
2010-07-06 15:00:33 +00:00
|
|
|
<para>Call <function>g_thread_init()</function> (from the
|
|
|
|
GLib library) prior to calling <function>clutter_init()</function>,
|
|
|
|
so that a local thread is used to load the file, rather
|
|
|
|
than the main loop. (Note that this is not necessary if
|
|
|
|
you're using GLib version >= 2.24, since GObject
|
|
|
|
initializes threading with the type system.)</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>Set the texture to load data asynchronously.</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>Connect a callback to the texture's load-finished
|
|
|
|
signal to handle any errors which occur during loading,
|
|
|
|
and/or to do extra work if data loads successfully.</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
|
|
|
|
<para>The code below shows how to put these together:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
<![CDATA[
|
|
|
|
/* callback to invoke when a texture finishes loading image data */
|
|
|
|
static void
|
|
|
|
_load_finished_cb (ClutterTexture *texture,
|
|
|
|
gpointer error,
|
|
|
|
gpointer user_data)
|
|
|
|
{
|
|
|
|
GError *err = error;
|
|
|
|
const gchar *image_path = user_data;
|
|
|
|
|
|
|
|
if (err != NULL)
|
|
|
|
g_warning ("Could not load image from file %s; message: %s",
|
|
|
|
image_path,
|
|
|
|
err->message);
|
|
|
|
else
|
|
|
|
g_debug ("Image loaded from %s", image_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
main (int argc, char *argv[])
|
|
|
|
{
|
|
|
|
/* initialize GLib's default threading implementation */
|
|
|
|
g_thread_init (NULL);
|
|
|
|
|
2011-02-22 00:44:55 +00:00
|
|
|
if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
|
|
|
|
return 1;
|
2010-07-01 10:29:46 +00:00
|
|
|
|
|
|
|
/* ... get stage etc. */
|
|
|
|
|
|
|
|
ClutterActor *texture;
|
|
|
|
GError *error = NULL;
|
|
|
|
|
|
|
|
texture = clutter_texture_new ();
|
|
|
|
|
|
|
|
/* load data asynchronously */
|
|
|
|
clutter_texture_set_load_async (CLUTTER_TEXTURE (texture), TRUE);
|
|
|
|
|
|
|
|
/* connect a callback to the "load-finished" signal */
|
|
|
|
g_signal_connect (texture,
|
|
|
|
"load-finished",
|
|
|
|
G_CALLBACK (_load_finished_cb),
|
|
|
|
image_path);
|
|
|
|
|
|
|
|
/* load the image from a file */
|
|
|
|
clutter_texture_set_from_file (CLUTTER_TEXTURE (texture),
|
|
|
|
image_path,
|
|
|
|
&error);
|
|
|
|
|
|
|
|
/* ... clutter_main () etc. */
|
|
|
|
}
|
|
|
|
]]>
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Other ways to load image data into a texture</title>
|
|
|
|
|
|
|
|
<para>While it's useful to load image data into a texture directly
|
|
|
|
from a file, there are occasions where you may have image data
|
|
|
|
in some other (non-file) format:</para>
|
|
|
|
|
|
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
2010-07-06 15:00:33 +00:00
|
|
|
<para>Various GNOME libraries provide image data in
|
|
|
|
<type>GdkPixbuf</type> structures; clutter-gtk has
|
|
|
|
functions for creating or setting a texture from a
|
|
|
|
<type>GdkPixbuf</type>:
|
|
|
|
<function>gtk_clutter_texture_new_from_pixbuf()</function>
|
|
|
|
and <function>gtk_clutter_texture_set_from_pixbuf()</function>
|
|
|
|
respectively.</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
</listitem>
|
|
|
|
<listitem>
|
2010-07-06 15:00:33 +00:00
|
|
|
<para>If you have raw RGB pixel data, <type>ClutterTexture</type>
|
|
|
|
also has a <function>clutter_texture_set_from_rgb_data()</function>
|
|
|
|
function for loading it.</para>
|
2010-07-01 10:29:46 +00:00
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
|
2010-08-05 09:58:42 +00:00
|
|
|
<section id="textures-sub-textures">
|
|
|
|
<title>Creating sub-textures from an existing texture</title>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Problem</title>
|
|
|
|
|
|
|
|
<para>You want to create a new <type>ClutterTexture</type> that only
|
|
|
|
displays a rectangular sub-region of an existing texture.</para>
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Solution</title>
|
|
|
|
|
|
|
|
<para>A possible way of achieving this is to retrieve the
|
|
|
|
<type>CoglHandle</type> of the underlying Cogl texture of the existing
|
|
|
|
<type>ClutterTexture</type>, create a new handle representing the
|
|
|
|
sub-region with <function>cogl_texture_new_from_sub_texture()</function>
|
|
|
|
and finally populate a new <type>ClutterTexture</type> with that handle.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
<![CDATA[
|
|
|
|
/* Create a new ClutterTexture that shows smiley.png */
|
|
|
|
image = clutter_texture_new_from_file ("smiley.png", NULL);
|
|
|
|
clutter_actor_get_size (image, &image_width, &image_height);
|
|
|
|
|
|
|
|
/* Grab the CoglHandle of the underlying Cogl texture */
|
|
|
|
texture = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (image));
|
|
|
|
|
|
|
|
/* Create a new Cogl texture from the handle above. That new texture is a
|
2011-12-29 09:15:05 +00:00
|
|
|
* rectangular region from image, more precisely the northwest corner
|
2010-08-05 09:58:42 +00:00
|
|
|
* of the image */
|
|
|
|
sub_texture = cogl_texture_new_from_sub_texture (texture,
|
|
|
|
0, 0,
|
|
|
|
image_width / 2,
|
|
|
|
image_height / 2);
|
|
|
|
|
|
|
|
/* Finally, use the newly created Cogl texture to feed a new ClutterTexture
|
|
|
|
* and thus create a new actor that displays sub_texture */
|
|
|
|
sub_image = clutter_texture_new ();
|
|
|
|
clutter_texture_set_cogl_texture (CLUTTER_TEXTURE (sub_image), sub_texture);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* You could have used the more straightforward g_object_new() function that
|
|
|
|
* can create an object and set some properties on it at the same time:
|
|
|
|
* sub_image = g_object_new (CLUTTER_TYPE_TEXTURE,
|
|
|
|
* "cogl-texture", sub_texture,
|
|
|
|
* NULL);
|
|
|
|
*/
|
|
|
|
]]> </programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<screenshot>
|
|
|
|
<mediaobject>
|
|
|
|
<imageobject>
|
|
|
|
<imagedata format="PNG"
|
|
|
|
fileref="images/textures-sub-texture.png" />
|
|
|
|
</imageobject>
|
|
|
|
<alt>
|
|
|
|
<para>A texture and its sub-texture next to it</para>
|
|
|
|
</alt>
|
|
|
|
</mediaobject>
|
|
|
|
</screenshot>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Discussion</title>
|
|
|
|
|
|
|
|
<para>The key of this recipe is the Cogl handle that represents the
|
|
|
|
underlying texture, the actual array of pixels the GPU will use
|
|
|
|
when it's told to texture geometry.</para>
|
|
|
|
|
|
|
|
<para>From this handle, it's possible to create a new texture handle
|
|
|
|
that represents a rectangular region of the former texture. To do this
|
|
|
|
one must call <function>cogl_texture_new_from_sub_texture()</function>
|
|
|
|
with the position and size of the said region. The interesting bit
|
|
|
|
about this function is that, when drawing either with the original
|
|
|
|
texture or with the new one, it's still the same GPU resource (pixels)
|
|
|
|
being used, meaning that creating a sub-texture doesn't use extra GPU
|
|
|
|
memory.</para>
|
|
|
|
|
|
|
|
<para>Once the sub-texture handle is created, the next step is
|
|
|
|
to create a new actor that will be able to draw it, namely a new
|
|
|
|
<type>ClutterTexture</type>. You then need to tell the texture to
|
|
|
|
draw from the sub-texture.</para>
|
|
|
|
|
|
|
|
<note><para>The handle you can get from
|
|
|
|
<function>clutter_texture_get_cogl_texture()</function> is effectively
|
|
|
|
the same texture than the first layer of the material retrieved by
|
|
|
|
<function>clutter_texture_get_cogl_material()</function></para></note>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Full example</title>
|
|
|
|
|
|
|
|
<example id="textures-sub-texture">
|
|
|
|
<title>Creating a sub-texture from an existing texture</title>
|
|
|
|
<programlisting
|
|
|
|
><xi:include href="examples/textures-sub-texture.c" parse="text">
|
|
|
|
<xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback>
|
|
|
|
</xi:include></programlisting>
|
|
|
|
</example>
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Going further</title>
|
|
|
|
|
|
|
|
<para>Now that we know how to create sub-textures, it's time to make
|
|
|
|
something a bit more shiny with them. Let's animate them! In case you
|
|
|
|
have not heard about implicit animations in Clutter yet, it's a good
|
|
|
|
time to have a look at the animation section of this cookbook.
|
|
|
|
</para>
|
|
|
|
|
|
|
|
<inlinemediaobject>
|
|
|
|
<videoobject>
|
|
|
|
<videodata fileref="videos/textures-split-go.ogv"/>
|
|
|
|
</videoobject>
|
|
|
|
<alt>
|
|
|
|
<para>Video showing 4 sub-textures being animated</para>
|
|
|
|
</alt>
|
|
|
|
</inlinemediaobject>
|
|
|
|
|
|
|
|
<example id="textures-split-go">
|
|
|
|
<title>Creating a sub-texture from an existing texture</title>
|
|
|
|
<programlisting
|
|
|
|
><xi:include href="examples/textures-split-go.c" parse="text">
|
|
|
|
<xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback>
|
|
|
|
</xi:include></programlisting>
|
|
|
|
</example>
|
|
|
|
</section>
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
2010-07-16 16:04:31 +00:00
|
|
|
<section id="textures-reflection">
|
|
|
|
<title>Creating a reflection of a texture</title>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Problem</title>
|
|
|
|
|
|
|
|
<para>You want to create the reflection of a texture.</para>
|
|
|
|
|
|
|
|
<para>The reflection is going to be positioned below the original
|
|
|
|
texture, and is going to fade out as if the original was placed on
|
|
|
|
a glassy surface.</para>
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Solution</title>
|
|
|
|
|
|
|
|
<para>You can use a ClutterClone actor and override its paint
|
|
|
|
implementation with a custom one:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
|
|
|
<![CDATA[
|
|
|
|
static void
|
|
|
|
_clone_paint_cb (ClutterActor *actor)
|
|
|
|
{
|
2010-07-18 10:15:25 +00:00
|
|
|
/* ... */
|
|
|
|
|
|
|
|
/* get the Cogl material of the source texture */
|
2010-07-16 16:04:31 +00:00
|
|
|
material = clutter_texture_get_cogl_material (CLUTTER_TEXTURE (source));
|
|
|
|
|
2010-07-18 10:15:25 +00:00
|
|
|
/* get the size of the actor, which will be used to size the reflection */
|
2010-07-16 16:04:31 +00:00
|
|
|
clutter_actor_get_allocation_box (actor, &box);
|
|
|
|
clutter_actor_box_get_size (&box, &width, &height);
|
|
|
|
|
|
|
|
/* get the composite opacity of the actor */
|
|
|
|
opacity = clutter_actor_get_paint_opacity (actor);
|
|
|
|
|
|
|
|
/* figure out the two colors for the reflection: the first is
|
|
|
|
* full color and the second is the same, but at 0 opacity
|
|
|
|
*/
|
2010-09-03 15:55:12 +00:00
|
|
|
cogl_color_init_from_4f (&color_1, 1.0, 1.0, 1.0, opacity / 255.);
|
2010-07-16 16:04:31 +00:00
|
|
|
cogl_color_premultiply (&color_1);
|
2010-09-03 15:55:12 +00:00
|
|
|
cogl_color_init_from_4f (&color_2, 1.0, 1.0, 1.0, 0.0);
|
2010-07-16 16:04:31 +00:00
|
|
|
cogl_color_premultiply (&color_2);
|
|
|
|
|
2010-07-18 10:15:25 +00:00
|
|
|
/* describe the four vertices of the quad; since it has
|
2010-07-16 16:04:31 +00:00
|
|
|
* to be a reflection, we need to invert it as well
|
|
|
|
*/
|
|
|
|
vertices[0].x = 0; vertices[0].y = 0; vertices[0].z = 0;
|
|
|
|
vertices[0].tx = 0.0; vertices[0].ty = 1.0;
|
|
|
|
vertices[0].color = color_1;
|
|
|
|
|
|
|
|
vertices[1].x = width; vertices[1].y = 0; vertices[1].z = 0;
|
|
|
|
vertices[1].tx = 1.0; vertices[1].ty = 1.0;
|
|
|
|
vertices[1].color = color_1;
|
|
|
|
|
|
|
|
vertices[2].x = width; vertices[2].y = height; vertices[2].z = 0;
|
|
|
|
vertices[2].tx = 1.0; vertices[2].ty = 0.0;
|
|
|
|
vertices[2].color = color_2;
|
|
|
|
|
|
|
|
vertices[3].x = 0; vertices[3].y = height; vertices[3].z = 0;
|
|
|
|
vertices[3].tx = 0.0; vertices[3].ty = 0.0;
|
|
|
|
vertices[3].color = color_2;
|
|
|
|
|
|
|
|
/* paint the same texture but with a different geometry */
|
|
|
|
cogl_set_source (material);
|
|
|
|
cogl_polygon (vertices, 4, TRUE);
|
|
|
|
|
2010-07-18 10:15:25 +00:00
|
|
|
/* ... */
|
2010-07-16 16:04:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
main (int argc, char *argv[])
|
|
|
|
{
|
2011-02-22 00:44:55 +00:00
|
|
|
if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
|
|
|
|
return 1;
|
2010-07-16 16:04:31 +00:00
|
|
|
|
|
|
|
/* ... get stage etc. */
|
|
|
|
|
|
|
|
ClutterActor *texture;
|
|
|
|
GError *error = NULL;
|
|
|
|
|
|
|
|
texture = clutter_texture_new ();
|
|
|
|
|
|
|
|
/* load the image from a file */
|
|
|
|
clutter_texture_set_from_file (CLUTTER_TEXTURE (texture),
|
|
|
|
image_path,
|
|
|
|
&error);
|
|
|
|
|
|
|
|
ClutterActor *clone;
|
|
|
|
|
|
|
|
clone = clutter_clone_new (texture);
|
|
|
|
|
|
|
|
g_signal_connect (clone,
|
|
|
|
"paint",
|
|
|
|
G_CALLBACK (_clone_paint_cb),
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
/* ... clutter_main () etc. */
|
|
|
|
}
|
|
|
|
]]>
|
|
|
|
</programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
2010-07-16 16:31:27 +00:00
|
|
|
<screenshot>
|
|
|
|
<mediaobject>
|
|
|
|
<imageobject>
|
|
|
|
<imagedata format="PNG"
|
|
|
|
fileref="images/textures-reflection.png" />
|
|
|
|
</imageobject>
|
|
|
|
<alt>
|
|
|
|
<para>A texture and its reflection below</para>
|
|
|
|
</alt>
|
|
|
|
</mediaobject>
|
|
|
|
</screenshot>
|
2010-07-16 16:04:31 +00:00
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Discussion</title>
|
|
|
|
|
|
|
|
<para>The essence of painting a reflection of a texture lies in reusing
|
|
|
|
the same material used by the original. This not only allows painting
|
|
|
|
always an up to date version of the original, but it also saves
|
|
|
|
resources.</para>
|
|
|
|
|
|
|
|
<para>In the code example above we take the <type>CoglMaterial</type>
|
|
|
|
out of the source <type>ClutterTexture</type> and we ask the Cogl
|
|
|
|
pipeline to paint it by using <function>cogl_set_source()</function>. The
|
|
|
|
main difference between this code and the equivalent code inside the
|
|
|
|
<type>ClutterTexture</type> <function>paint()</function> implementation
|
|
|
|
is that we also specify the texture vertices and their color by using the
|
|
|
|
<type>CoglTextureVertex</type> structure and the
|
|
|
|
<function>cogl_polygon()</function> function.</para>
|
|
|
|
|
|
|
|
<para>The <type>CoglTextureVertex</type> structure contains three fields
|
|
|
|
for the position of the vertex in 3D space:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting><![CDATA[
|
|
|
|
typedef struct _CoglTextureVertex {
|
|
|
|
float x;
|
|
|
|
float y;
|
|
|
|
float z;
|
|
|
|
...
|
|
|
|
]]></programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<para>It also contains the normalized texture coordinate (also known as
|
|
|
|
texture element, or <emphasis>texel</emphasis>):</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting><![CDATA[
|
|
|
|
...
|
|
|
|
float tx;
|
|
|
|
float ty;
|
|
|
|
...
|
|
|
|
]]></programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<para>And, finally, the color of the vertex, expressed as a
|
|
|
|
<type>CoglColor</type>:</para>
|
|
|
|
|
|
|
|
<informalexample>
|
|
|
|
<programlisting><![CDATA[
|
|
|
|
...
|
|
|
|
CoglColor color;
|
|
|
|
} CoglTextureVertex;
|
|
|
|
]]></programlisting>
|
|
|
|
</informalexample>
|
|
|
|
|
|
|
|
<para>The example code sets the position of the vertices in clockwise
|
|
|
|
order starting from the top left corner, and sets the coordinate of the
|
|
|
|
texels in counter-clockwise order, starting with the bottom left corner.
|
|
|
|
This makes sure that the copy of the original texture appears as being
|
|
|
|
flipped vertically.</para>
|
|
|
|
|
|
|
|
<para>The gradual fading out to the background color is done by setting
|
|
|
|
the color of the top vertices to be fully opaque, and the color of the
|
|
|
|
bottom ones to be fully transparent; GL will then automatically create a
|
|
|
|
gradient that will be applied when painting the material.</para>
|
|
|
|
|
|
|
|
<note><para>The color values must be pre-multiplied with their alpha
|
|
|
|
component, otherwise the bleding will not be correct. You can either
|
|
|
|
multiply the values by yourself when creating the color or, better yet,
|
|
|
|
use the <function>cogl_color_premultiply()</function> that Cogl provides
|
|
|
|
for this operation.</para></note>
|
|
|
|
|
|
|
|
</section>
|
2010-07-18 10:15:25 +00:00
|
|
|
|
|
|
|
<section>
|
|
|
|
<title>Full example</title>
|
|
|
|
|
|
|
|
<example id="textures-reflection-example">
|
|
|
|
<title>Creating a glassy reflection of a texture</title>
|
|
|
|
<programlisting>
|
|
|
|
<xi:include href="examples/textures-reflection.c" parse="text">
|
|
|
|
<xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback>
|
|
|
|
</xi:include>
|
|
|
|
</programlisting>
|
|
|
|
</example>
|
|
|
|
</section>
|
|
|
|
|
2010-07-16 16:04:31 +00:00
|
|
|
</section>
|
|
|
|
|
2010-08-18 10:41:10 +00:00
|
|
|
<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>
|
|
|
|
|
2010-08-18 15:02:51 +00:00
|
|
|
<para>There are a couple of ways you can remedy this:</para>
|
2010-08-18 10:41:10 +00:00
|
|
|
|
|
|
|
<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>
|
2010-08-18 15:02:51 +00:00
|
|
|
<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
|
2010-08-18 10:41:10 +00:00
|
|
|
<function>load_cogl_texture()</function> function of
|
2010-08-18 15:02:51 +00:00
|
|
|
the <link linkend="textures-crossfade-example-2">single
|
|
|
|
texture example</link> to do this:</para>
|
2010-08-18 10:41:10 +00:00
|
|
|
|
2010-08-18 15:02:51 +00:00
|
|
|
<informalexample>
|
|
|
|
<programlisting>
|
2010-08-18 10:41:10 +00:00
|
|
|
<![CDATA[
|
2010-08-18 15:02:51 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
2010-08-18 10:41:10 +00:00
|
|
|
]]>
|
2010-08-18 15:02:51 +00:00
|
|
|
</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>
|
2010-08-18 10:41:10 +00:00
|
|
|
|
|
|
|
</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>
|
|
|
|
|
2010-07-01 10:29:46 +00:00
|
|
|
</chapter>
|