Effects Roger Zelazny, from Prince of Chaos Don't wake me for the end of the world unless it has very good special effects
Introduction Effects modify an actor's appearance, such as how it is positioned, colored and textured. The Clutter API for effects contains several abstract classes you can subclass to create your own effects. It also contains several built-in effects you can use to modify the visual appearance of actors in a variety of ways. The recipes in this section of the cookbook cover how to create your own effects as well as how to apply Clutter's effects.
Creating effects using the abstract effect classes One of the original design goals of Clutter was to abstract the complexity of GL. However, the effects API partially circumvents these abstractions, to give you finer-grained access to the graphics pipeline. Therefore, if you want to write your own effects, some understanding of Cogl, OpenGL, and general graphics programming is essential. Each abstract effect class is tailored to modifying different aspects of an actor, as explained below: <type>ClutterEffect</type> If you're just using the Clutter and Cogl APIs to decorate an actor, this is simplest type of effect to implement. Subclassing ClutterEffect enables you to "wrap" how an actor is painted, by injecting some code before and/or after the actor's own paint() implementation. This is the preferred way to modify how an actor is painted, short of creating your own actor subclass. Subclasses of ClutterEffect: <type>ClutterOffscreenEffect</type> Use this class as a basis if you need GL textures for your effect. GL textures are required for effects which need an offscreen framebuffer. The offscreen framebuffer is used to store a modified rendering of an actor (e.g. with its colors altered or with deformed geometry). This buffer is then redirected to a texture in the stage window. An example is ClutterBlurEffect, which uses a GLSL fragment shader to blur an actor's appearance in an offscreen framebuffer. Subclasses of ClutterOffscreenEffect: <type>ClutterDeformEffect</type> Use this base class if you want to modify an actor's geometry, at the level of individual vertices. ClutterDeformEffect removes the complexity of dealing with vertex-based deformations at the OpenGL level, instead enabling you to easily plug a deformation callback into the graphics pipeline. If you are writing your own deform effects, a good example to work from is ClutterPageTurnEffect. There is also a recipe which explains how to implement a simple custom deform effect (a page fold). <type>ClutterShaderEffect</type> Use this if you want to apply custom GLSL vertex or fragment shaders to your actors. Writing ClutterShaderEffects gives you very fine-grained control over the GL pipeline. However, this makes them the most complex effects to implement. If you want to write your own GLSL shaders, the GLSL specification is a good starting point.
Using the built-in effects Clutter comes with a number of built-in effects which can easily be applied to your actors. This section explains how to do this. First, create an actor. For this example, we use a texture loaded with an image: /* filename could be set from command line or constant */ gchar *filename; /* create a texture */ ClutterActor *texture = clutter_texture_new (); /* ...set texture size, keep aspect ratio etc... */ /* NB ignoring missing file errors here for brevity */ clutter_texture_set_from_file (CLUTTER_TEXTURE (texture), filename, NULL); /* ...add texture to the stage... */ Next, create an instance of an effect; here, we're creating a ClutterColorizeEffect with a pink tint: ClutterColor *pink = clutter_color_new (230, 187, 210, 255); ClutterEffect *effect = clutter_colorize_effect_new (pink); Finally, apply the effect to the actor: clutter_actor_add_effect (texture, effect); The result in this case is an image colorized with a pink tint, like this: Applying a ClutterColorizeEffect to a texture loaded with an image (drawing by Madeleine Smith) The same set of steps applies for any of the built-in Clutter effects. Your own custom effects classes should also behave in a similar way: constructors should return ClutterEffect instances so your effect can be added to an actor through the standard API. One further thing worth mentioning is that because an effect is a GObject, any properties you expose for your effect can be animated via implicit animations, ClutterAnimator or ClutterState. For example, the ClutterPageTurnEffect can be animated by manipulating its period property. An example of how to do this for your own effect is given in the custom deform effect recipe. The full code for the ClutterColorizeEffect example is below. Applying a <type>ClutterColorizeEffect</type> to a texture loaded with an image a code sample should be here... but isn't
Changing an actor's paint sequence using <type>ClutterEffect</type>
Problem You want to paint on top of or under an actor in a generic way, without editing the actor's paint() implementation. Example use cases are: Adding a border on top of an actor. Drawing a background for an actor. A quick way to achieve the same thing (though not readily portable between actors) is to connect a callback before or after an actor's paint signal. See this recipe for more details. However, using a ClutterEffect implementation, as explained in this recipe, is the preferred approach.
Solution Create a subclass of the ClutterEffect abstract class; then implement the pre_paint() and/or post_paint() virtual functions. When the effect is applied to an actor, these functions will paint before and after the actor's own paint() implementation. For this solution, we implement a simple CbBackgroundEffect which draws a gray rectangle under an actor. The full source is in this section. To keep it simple, the effect has no properties and isn't configurable (the background is always gray); see the border effect for a more detailed implementation with GObject trimmings. First, create a ClutterEffect subclass. This requires the trappings of a GObject class; in particular, it needs a private struct to hold the effect's state. This should include any CoglMaterials, CoglColors or other private member variables you intend to use to draw the effect. In the case of the background effect, we have a background CoglMaterial and a CoglColor for that material: struct _CbBackgroundEffectPrivate { CoglMaterial *background; CoglColor *color; }; In the init() function for objects of your class, create any Cogl resources which you need to draw the effect. In the case of the background effect, we need to create the CoglMaterial and CoglColor for the private struct: priv = CB_BACKGROUND_EFFECT_GET_PRIVATE (self); /* create the background material */ priv->background = cogl_material_new (); /* gray color for filling the background material */ priv->color = cogl_color_new (); cogl_color_init_from_4ub (priv->color, 122, 122, 122, 255); /* set the color on the material; NB this isn't configurable * for this effect, and is always gray */ cogl_material_set_color (priv->background, priv->color); } ]]> Optionally, you can create GObject properties for the class, if you want a configurable effect: see this section for details. The dispose() function for your effect should clean up any Cogl resources: priv; if (priv->background != COGL_INVALID_HANDLE) { cogl_handle_unref (priv->background); priv->background = COGL_INVALID_HANDLE; } if (priv->color != NULL) { cogl_color_free (priv->color); priv->color = NULL; } G_OBJECT_CLASS (cb_background_effect_parent_class)->dispose (gobject); } ]]> Now, the important part: implement pre_paint() and/or post_paint(), using Cogl to draw on the material(s) set up for the effect. For the background effect, we implement pre_paint(), to draw a gray rectangle under the actor: priv; /* get the associated actor's dimensions */ actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (self)); clutter_actor_get_size (actor, &width, &height); /* draw a Cogl rectangle in the background using the default color */ cogl_set_source (priv->background); /* the rectangle has the same dimensions as the actor */ cogl_rectangle (0, 0, width, height); return TRUE; } ]]> Now, in the init() function for the effect class, assign your implementations to the virtual methods of the ClutterEffect abstract class: pre_paint = cb_background_effect_pre_paint; gobject_class->dispose = cb_background_effect_dispose; g_type_class_add_private (klass, sizeof (CbBackgroundEffectPrivate)); } ]]> If you intend to make your effect reusable, provide a public constructor (as is done for the example effects in this recipe): ClutterEffect * cb_background_effect_new () { return g_object_new (CB_TYPE_BACKGROUND_EFFECT, NULL); } The effect is now ready to be used. The application code for applying your effect to an actor is the same as for any other effect: ClutterActor *texture; ClutterEffect *background_effect; /* ...initialize texture, load image file etc... */ /* create a gray background effect */ background_effect = cb_background_effect_new (); /* apply the effect to the actor */ clutter_actor_add_effect (texture, background_effect); Below is an example of applying this effect to a texture loaded with an image; the image has a transparent background, so the background is visible through it. The screenshot is from the example application: Applying CbBackgroundEffect to a texture loaded with an image that has a transparent background
Discussion A basic ClutterEffect is particularly useful for amending the appearance of an actor on the fly: for example, to highlight an actor in response to a button presses. This could be done by creating a custom widget whose appearance could be toggled. But what if you wanted to make an arbitrary actor's appearance "togglable"? A generic effect in the style of the border effect in this recipe can be applied to any actor, and easily toggled by enabling/disabling the effect. ClutterEffect works best where you want to overlay or underlay the actor with Cogl paths or primitives, without changing the actor's geometry. If you want to do complicated geometry transformations, or other subtle manipulations of an actor's appearance, it is better to use a ClutterEffect subclass like ClutterOffscreenEffect, ClutterDeformEffect, or ClutterShaderEffect. In a similar vein, when a ClutterEffect is applied to an actor, the effect shouldn't paint outside the actor's allocation. However, if the effect provides a get_paint_volume() implementation which returns a volume larger than the actor's allocation, the effect can paint anywhere within that volume. Though in most cases, creating a custom paint volume is only going to be useful for offscreen effects, where you are changing the actor's geometry.
Effect properties If your effect has GObject properties, you should ensure that an actor associated with the effect is queued for a redraw when those properties change. (You only need to do this for properties which change the effect's appearance; but this is likely to include most of an effect's properties.) In most cases, you're likely define standard GObject properties for the class; for example, CbBorderEffect defines a width property like this: It also defines a standard GObject set_property() function for width: Note that this calls cb_border_effect_set_width(), which is also exposed in the public API. This is where the width member variable is actually set in the private struct; and also where the redraw for the actor associated with the effect should be queued: width = width; /* the property has been updated, so queue a redraw of the actor (if set) */ cb_border_effect_update (self); } ]]> Any other property setters which affect the associated actor's appearance (i.e. color in the case of CbBorderEffect) should also call the update function after setting the property. If your effect exposes GObject properties in this way, it can also be animated with the Clutter animation API as usual. For example, you could animate the border effect in this recipe so that the border gradually becomes thinner or thicker.
Full example The example application applies two effects to a group of ClutterTextures: A CbBackgroundEffect which draws a gray background under each actor. The effect is implemented in a header file and a C code file. A CbBorderEffect which draws a red border on top of an actor; this is toggled by clicking on the actor. The effect is implemented in a header file and a C code file. The application creates textures from the file paths specified on the command line then applies both of these effects to each texture. In the case of the CbBorderEffect, a 5 pixel red border is applied; this is also disabled by default, and enabled when a texture is clicked. Here is an example of the output when the application is loaded with four images: Applying CbBackgroundEffect and a togglable CbBorderEffect to a several textures
<type>CbBackgroundEffect</type> <filename>cb-background-effect.h</filename> (header file) a code sample should be here... but isn't <filename>cb-background-effect.c</filename> (code file) a code sample should be here... but isn't
<type>CbBorderEffect</type> This is a more sophisticated effect with configurable border color and width. <filename>cb-border-effect.h</filename> (header file) a code sample should be here... but isn't <filename>cb-border-effect.c</filename> (code file) a code sample should be here... but isn't
Application Application which applies <type>CbBorderEffect</type> and <type>CbBackgroundEffect</type> to a group of <type>ClutterTextures</type>. a code sample should be here... but isn't
Creating and animating a custom <type>ClutterDeformEffect</type>
Problem You want to deform an actor's geometry: for example, to make it appear stretched, twisted or folded. This recipe demonstrates how to do this with a simple page fold effect, which folds one half of the actor over its other half.
Solution Subclass ClutterDeformEffect and implement a deform_vertex() function to modify the actor's vertices. The signature for deform_vertex() is: void deform_vertex (ClutterDeformEffect *effect, gfloat width, gfloat height, CoglTextureVertex *vertex); The width and height are the width and height of the target material, stored in the offscreen buffer. Usually the target material's size will match the actor's transformed size; however, if the effect implements create_texture(), the target material's size may differ from the actor's transformed size. The vertex contains the position and color of a vertex, to be deformed by your effect. Your deform_vertex() function should modify the member variables of this CoglTextureVertex in place. Usually, this will mean modifying the x, y and y member variables of the vertex, which describe its position in 3D space. The example function below, taken from the full example, applies a transformation to vertices falling in the "right-hand" half of the actor (i.e. vertices with an x value greater than or equal to half the width of the actor). static void cb_page_fold_effect_deform_vertex (ClutterDeformEffect *effect, gfloat width, gfloat height, CoglTextureVertex *vertex) { CbPageFoldEffectPrivate *priv = CB_PAGE_FOLD_EFFECT (effect)->priv; /* the rotation angle is modified by the percentage progress of the fold, * as represented by the period variable */ gfloat radians = (priv->angle * priv->period) / (180.0f / G_PI); /* rotate from the center of the actor on the y axis */ gfloat adjusted_x = vertex->x - (width / 2); /* only rotate vertices to the right of the middle of the actor */ if (adjusted_x >= 0.0) { vertex->x = (vertex->z * sin (radians)) + (adjusted_x * cos (radians)) + width / 2; /* NB add 1 to z to prevent "z fighting"; otherwise, when fully-folded * the image has "stripes" where vertices from the folded part * of the actor interfere with vertices from the unfolded part */ vertex->z = (vertex->z * cos (radians)) + (adjusted_x * sin (radians)) + 1; } /* adjust depth of all vertices so they fit inside the actor while folding; * this has the effect of making the image smaller within the texture, * but does produce a cleaner fold animation */ vertex->z -= width / 2; } Note that this effect has two properties set in its constructor or through setters: angle, representing the angle of the full fold; for the actor to fully fold in half, this would be set to 180.0 period, representing the percentage of the fold to apply As well as rotating the vertex, the deform_vertex() function also shifts the z coordinate "up" by 1 (towards the viewpoint) for vertices on the right-hand side of the actor. This is so that the "folded over" vertices are above vertices on the left-hand side. Without this small shift, the vertices interfere with each other, which can cause striping artefacts. All vertices are also shifted "down", so that the the folding part of the actor remains within the texture. Otherwise the part which is folding may be clipped to the allocation of the actor. This effect can now be applied to an actor, using the approach outlined in the introduction. The result looks like this when period is set to 0.25 and angle to 180.0 (i.e. the page is folded by 45 degrees): Applying a custom ClutterDeformEffect to a texture loaded with an image Because the effect is a GObject which exposes its properties, it can easily be animated, as described in the discussion section.
Discussion A deform effect processes an actor as follows: The actor is divided into a series of triangular tiles. The number of horizontal and vertical tiles is configurable; more tiles implies more vertices. See this section for more details about tiles. The position of each vertex of each tile is then modified (or not) by the deform_vertex() function. In this function, you can change the vertex's position (x, y, z coordinates). You can also modify the color at the vertex if desired. The resulting deformed vertices are stored in an offscreen buffer. Once the deformation has been applied to all vertices, the content of the offscreen buffer is painted at the onscreen position of the actor. You may find it useful to visualise this process by imagining your actor's surface as a net, composed of triangles. (Something like a fishing net, not a mathematical one.) At each corner of each triangle is a marble; and between each pair of corners is an infinitely flexible length of elastic. Moving a marble doesn't change the position of its neighbours; it just stretches or relaxes the elastic. In this analogy, the marbles are the vertices; and the surfaces between the marbles, bordered by triangles of elastic, are the tiles. More triangles (tiles) means more marbles (vertices). When you create a ClutterDeformEffect, think of it as specifying movements of marbles in the net. Changing the position of a vertex corresponds to moving a marble up/down (-/+ y position), left/right (-/+ x position) or away/towards you (-/+ z position) (ignoring color for the moment). Now imagine that you are asked to fold the whole net of marbles; but you can't just grab the edge of the net and pull it over: you can only move one marble at a time. However, once moved, each marble magically stays where you put it in 3D space. To do this, you could project where each marble would be if you could fold the whole sheet in one go; then move the marbles one by one to their projected positions. Even though you'd be moving the marbles one at a time, it would eventually look as though you'd folded the whole net with a single movement. When you write a ClutterDeformEffect, you have to accomplish a similar feat: change the shape of an actor by individually modifying the positions of points on its surface. In most cases, your deform_vertex() implementation can take advantage of an existing geometric transformation method to achieve this. (For example, the page fold in this recipe is based on equations from p.412 of Computer Graphics (C Version), 2nd Edition by Hearn and Baker, 1996.)
Customising the back material When you set up a deform effect, you can optionally specify a material to use for the "back" of any actor it is applied to. If you think of an actor as a sheet of paper with a picture on it, specifying a back is similar to turning the sheet of paper over (rotating it around the y axis) and drawing another picture on the other side. If you then folded or twisted the paper, you would be able to see parts of the pictures on both the front and back of the paper. Similarly, during deformation of an actor, if any vertices of the actor are deformed such that the actor's surface is folded or twisted over itself, parts of its back become visible. If you set a back material, you will see parts of that where the surface is folded over. If you don't set a back material, you will instead see mirror images of parts of the actor's front: as if the actor was flexible stained glass, rather than paper. You can see this if you watch the animation in this section. The back material should be an instance of CoglMaterial. You can either create this via the Cogl API directly; or indirectly through the Clutter API (for example, by getting the material from a ClutterTexture). The code below gives an example of how to do the latter: See the ClutterDeformEffect API reference for more details about back materials. Here's a screenshot of the example with the addition of a back material, folded at an angle of 60 degrees: Applying a custom ClutterDeformEffect to a texture loaded with an image
Animating a custom deform effect Clutter's animation API can animate any GObject which exposes its properties. In the case of the page fold effect, we can expose the period property using standard GObject property installation: /* GObject class init */ static void cb_page_fold_effect_class_init (CbPageFoldEffectClass *klass) { GParamSpec *pspec; GObjectClass *gobject_class = G_OBJECT_CLASS (klass); /* ...other class setup code... */ /* expose the period as a GObject property */ pspec = g_param_spec_double ("period", "Period", "The period of the page fold", 0.0, 1.0, 0.0, G_PARAM_READWRITE); obj_props[PROP_PERIOD] = pspec; g_object_class_install_property (gobject_class, PROP_PERIOD, pspec); /* ...install other properties... */ } We also add a get_property() implementation, as well as a setter (see the full GObject implementation for details). Then set up an animation for the property; in this case, using a ClutterState: ClutterEffect *effect = cb_page_fold_effect_new (180.0, 0.0); ClutterState *transitions = clutter_state_new (); clutter_state_set_duration (transitions, NULL, NULL, 500); clutter_state_set (transitions, NULL, "unfolded", effect, "period", CLUTTER_LINEAR, 0.0, NULL); clutter_state_set (transitions, NULL, "folded", effect, "period", CLUTTER_LINEAR, 1.0, NULL); To start the animation, warp the ClutterState into its "unfolded" state, then set it to "folded": /* this changes state instantaneously */ clutter_state_warp_to_state (transitions, "unfolded"); /* this starts an animation to the state */ clutter_state_set_state (transitions, "folded"); Note that the full code sample is slightly more complex, as it triggers state changes when a mouse button is pressed on the texture. There is also a third "partially folded" state (used to create the screenshot for the previous section). Here's what the resulting animation looks like: Video showing animation of a custom deform effect on a texture
Tiles A ClutterDeformEffect divides the actor being deformed into a number of tiles: the larger the number of tiles, the larger the number of vertices to be manipulated by the effect. Increasing the number of tiles increases the number of vertex computations required, which can slow down animations; at the same time, finer-grained tiles can make an effect appear smoother, particularly when animated. Most of the time, the default number of tiles in the x and y axes should suffice. You can get the current number of tiles associated with an effect with: However, if an effect produces jerky or fragmented output, you want to tweak the number of tiles. Use the clutter_deform_effect_set_n_tiles() function to do this: /* 64 tiles in both axes */ guint x_tiles = 64; guint y_tiles = 64; clutter_deform_effect_set_n_tiles (CLUTTER_DEFORM_EFFECT (effect), x_tiles, y_tiles);
Full example This example consists of three files: A header file for the CbPageFoldEffect GObject. The code file implementing CbPageFoldEffect. A short sample application which applies a CbPageFoldEffect instance to an actor and animates the fold when the actor is clicked. As Clutter effect subclasses are written using GObject, you might find this recipe (which goes into GObject in more detail) a useful introduction. <filename>cb-page-fold-effect.h</filename> (header file) a code sample should be here... but isn't <filename>cb-page-fold-effect.c</filename> (code file) a code sample should be here... but isn't Application which uses <type>CbPageFoldEffect</type> to do animated folding of a <type>ClutterTexture</type> a code sample should be here... but isn't