mutter/doc/cookbook/textures.xml
Emmanuele Bassi 4170eacd94 cookbook: Add a recipe for texture reflection
A common request: how to create a clone of a texture that looks like a
reflection.
2010-07-16 17:04:31 +01:00

702 lines
22 KiB
XML

<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<chapter id="textures">
<title>Textures</title>
<epigraph>
<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>
</epigraph>
<section id="textures-introduction">
<title>Introduction</title>
<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>
</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>
<para>Create a <type>ClutterCairoTexture</type>, then draw onto
the Cairo context it wraps using the Cairo API:</para>
<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>
<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>
<para>Other useful operations:</para>
<itemizedlist>
<listitem>
<para><emphasis>To draw on part of the texture:</emphasis>
use <function>clutter_cairo_texture_create_region()</function> to
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>
use <function>clutter_cairo_texture_clear()</function>.</para>
<para>You may need to do this as the texture reuses the same
Cairo context each time you call
<function>clutter_cairo_texture_create()</function> or
<function>clutter_cairo_texture_create_region()</function>.</para>
</listitem>
<listitem>
<para><emphasis>To resize the Cairo context wrapped
by a texture</emphasis>, use
<function>clutter_cairo_texture_set_surface_size()</function>.</para>
</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>
<note><para>If the page is larger than the Cairo context,
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
to make the PDF page sit inside the Cairo context (e.g. scale the PDF
page or put it inside a scrollable actor).</para></note>
</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
the actor's <property>request-mode</property> property to set
the correct geometry management (see the discussion section); then
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>
<para>The <property>request-mode</property> for an actor
determines how geometry requisition is performed; in this case, this
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>
<para>If set to <constant>CLUTTER_REQUEST_HEIGHT_FOR_WIDTH</constant>
(the default), changing the width causes the height
to be scaled by the same factor as the width.</para>
</listitem>
<listitem>
<para>If set to <constant>CLUTTER_REQUEST_WIDTH_FOR_HEIGHT</constant>,
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
request-mode is set to <constant>CLUTTER_REQUEST_HEIGHT_FOR_WIDTH</constant>.
If a standard, photo-sized image in landscape orientation were
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>
<para>With request-mode set to
<constant>CLUTTER_REQUEST_WIDTH_FOR_HEIGHT</constant>,
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
<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>
<informalexample>
<programlisting>
<![CDATA[
gint width;
gint height;
clutter_texture_get_base_size (CLUTTER_TEXTURE (texture), &width, &height);
]]>
</programlisting>
</informalexample>
<note><para>If you explicitly set the size (both width and height)
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
image loaded into the texture is automatically stretched/shrunk to
fit the texture. This is the case regardless of any other settings
(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>
</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>
<para>Create a <type>ClutterTexture</type> directly from an
image file:</para>
<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>
<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>
</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);
clutter_init (&argc, &argv);
/* ... 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>
<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>
</listitem>
<listitem>
<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>
</listitem>
</itemizedlist>
</section>
</section>
</section>
<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)
{
ClutterActor *source;
ClutterActorBox box;
CoglHandle material;
gfloat width, height;
guint8 opacity;
CoglColor color_1, color_2;
CoglTextureVertex vertices[4];
/* if we don't have a source actor, don't paint */
source = clutter_clone_get_source (CLUTTER_CLONE (actor));
if (source == NULL)
goto out;
/* if the source texture does not have any content, don't paint */
material = clutter_texture_get_cogl_material (CLUTTER_TEXTURE (source));
if (material == NULL)
goto out;
/* set the size of the reflection to be the same as the source */
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
*/
cogl_color_set_from_4f (&color_1, 1.0, 1.0, 1.0, opacity / 255.);
cogl_color_premultiply (&color_1);
cogl_color_set_from_4f (&color_2, 1.0, 1.0, 1.0, 0.0);
cogl_color_premultiply (&color_2);
/* now describe the four vertices of the quad; since it has
* 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);
out:
/* prevent the default clone handler from running */
g_signal_stop_emission_by_name (actor, "paint");
}
int
main (int argc, char *argv[])
{
clutter_init (&argc, &argv);
/* ... 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>
<figure id="textures-reflection-image">
<title>Reflection of a texture</title>
<graphic fileref="images/textures-reflection.png" format="PNG"/>
</figure>
</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>
</section>
</chapter>