Understanding transitions

It can be a little hard to understand how transitions are being applied to your images only by experimenting (although not impossible), so I decided to explain things with a simple example.

To get the better feeling about what is going on, I would advice you to be active when reading this. Replicate the steps that are described with your copy of Imagination and test it, play with it and experiment.

For starters, launch Imagination, create new project (default values will work fine here, but feel free to customize them), import three images and save project. When you're done, you should end up with something like this:

Three images imported

You can see three slides at the bottom of Imagination, but your slideshow actually contains five of them. Imagination automatically adds two slides filled with selected background color (one at the beginning and one at the end of the slideshow) that enable our slideshow to have initial and final transition (I'll call the "hello" and "bye-bye" transitions respectively).

Schematically, our current slideshow looks like this:

Slideshow schematics

Now we'll add some transition effects to our slideshow. Select second slide and apply transition to it. This transition will be added in between first and second slide. Schematically, after adding transition, our slideshow looks like this:

First transition applied

If you now click "Preview" button, you'll notice that Imagination starts preview with transition form slide one to slide two. This is again caused by the fact that transitions are being added in front of selected slide.

To practise a bit more, select slide three and apply transition to it. Can you tell where it has been applied and how would the schematics look like? If the answer is no, than this schematics may help.

Second transition applied

You may think that my brains aren't working properly, since I started adding transitions with second slide (and I really cannot blame you for that;), but there is a perfectly good reason for this: first slide is special. When we apply transition to first slide, it'll be applied both at the beginning and at the end of the slideshow. If you look at the last schematics, the transition is being added before the fist slide and after the last slide.

Hello and bye-bye transitions being applied

So the first slide controls hello and bye-bye transitions.

I hope that this quick explanation makes understanding Imagination's transitions a bit easier. Have fun creating your slideshows.

Writing new transition plugin

Prerequisites

If you would like to write a new transition, you should only know how to use Cairo graphics library. There are quite a few cairo tutorials available on the web, so ask Google about them if you're not familiar with cairo. You'll also need a compiler and development packages for glib and Cairo. We would also recommend GNU make, since this will make your life much easier when compiling plugins.

Last thing you should get is a "First Aid Plugin Kit". This archive contains a Makefile for easier compiling, a template for writing plugins and one sample plugin named sample_trans.c.

How plugins comunicate with main application

Before we start, there is one fundamental thing you should know about plugin system. Each plugin represents a group of transitions in Imagination, not just one one. Information about available transitions is obtained at application start.

For demonstration purposes, we'll be showing you how a real plugin has been written - Misc Shape Wipe. This plugin will sport four transitions: "Heart In", "Heart Out", "Keyhole In" and "Keyhole Out".

We'll start by copying template plugin to new file named misc_shape_wipe.c

We've learned before that Imagination loads and queries plugins at runtime. This information exchange is implemented with img_get_plugin_info function. Each plugin should implement this function in order for Imagination to be able to load it. This function takes two arguments: a pointer to a string which should be filled with a plugin group name and a pointer to a NULL-terminated array of strings, which should be allocated and filled with triplets of:

  1. transition name
  2. transition function name
  3. unique ID of transition

This information is used by Imagination to create transition combo box, group name being listed at the root level and transition names representing leaves.

For our plugin, img_get_plugin_info will look something like this:

void
img_get_plugin_info( gchar  **group,
                     gchar ***trans )
{
    gint i = 0;
    *group = "Misc Shape Wipe";

    *trans = g_new( gchar *, 13 );

    (*trans)[i++] = "Heart In";
    (*trans)[i++] = "img_heart_in";
    (*trans)[i++] = GINT_TO_POINTER( 56 );

    (*trans)[i++] = "Heart Out";
    (*trans)[i++] = "img_heart_out";
    (*trans)[i++] = GINT_TO_POINTER( 57 );

    (*trans)[i++] = "Keyhole In";
    (*trans)[i++] = "img_key_in";
    (*trans)[i++] = GINT_TO_POINTER( 58 );

    (*trans)[i++] = "Keyhole Out";
    (*trans)[i++] = "img_key_out";
    (*trans)[i++] = GINT_TO_POINTER( 59 );

    (*trans)[i++] = NULL;
}

How did we know what numbers to assign as IDs? There is only one rule: your plugins should have ID numbers bigger than 5000. Numbers less that 5000 are reserved for Imagination's official plugins. (This way we can guarantee to our users that if they created slideshow with officially released plugins, this slideshow will look the same on all machines with Imagination installed.)

For each triplet specified in img_get_plugin_info function, you should also create a renderer function with a name specified in second field of triplet. This function will be called when a slide using this transition should be rendered.

Render function takes four arguments that are really easy to understand. First parameter is cairo drawing context, which should be used to do all of the drawing. Second and third parameters are cairo image surfaces, properly sized already, that represent images that should be composed together. Last parameter is a double value that indicates the transition progress. When last parameter is 0, only first surface should be shown. When last parameter is 0.5, transition should be half way done. And when last parameter is 1, only second surface should be shown.

For our example, renderer functions look like this:

void
img_heart_in( cairo_t         *cr,
              cairo_surface_t *image_from,
              cairo_surface_t *image_to,
              gdouble          progress )
{
    transition_render( cr, image_from, image_to, progress, 1, TRUE );
}

void
img_heart_out( cairo_t         *cr,
               cairo_surface_t *image_from,
               cairo_surface_t *image_to,
               gdouble          progress )
{
    transition_render( cr, image_from, image_to, progress, 1, FALSE );
}

void
img_key_in( cairo_t         *cr,
            cairo_surface_t *image_from,
            cairo_surface_t *image_to,
            gdouble          progress )
{
    transition_render( cr, image_from, image_to, progress, 2, TRUE );
}

void
img_key_out( cairo_t         *cr,
             cairo_surface_t *image_from,
             cairo_surface_t *image_to,
             gdouble          progress )
{
    transition_render( cr, image_from, image_to, progress, 2, FALSE );
}

You probably noted that those function only serve as proxies that call main drawing function with proper arguments. This is usefull, since most of the time transitions inside single plugin are drawn with approximately the same instructions, and using proxies enables you to reuse big part of your drawing code.

Main drawing function

Main drawing function is where all the action happens. And this is how this function looks in our example:

static void
transition_render(  cairo_t         *cr,
                    cairo_surface_t *image_from,
                    cairo_surface_t *image_to,
                    gdouble          progress,
                    gint         type,
                    gboolean     direction )
{
    gint width, height, w2, h2, offset;
    cairo_surface_t *layer1, *layer2;

    width  = cairo_image_surface_get_width( image_from );
    height = cairo_image_surface_get_height( image_from );

    /* Drawing code goes here */
    if( direction )
    {
        layer2 = image_from;
        layer1 = image_to;
        progress = 1 - progress;
    }
    else
    {
        layer2 = image_to;
        layer1 = image_from;
    }
    w2 = width / 2;
    h2 = height / 2;

    cairo_set_source_surface( cr, layer1, 0, 0 );
    cairo_paint( cr );

    if( type == 1 )
        offset = 100;
    else
        offset = 40;

    cairo_set_source_surface( cr, layer2, 0, 0 );
    cairo_translate( cr, w2, h2 - ( 1 - progress ) * offset );
    cairo_scale( cr, progress, progress );
    switch( type )
    {
        case 1: /* Heart */
            cairo_move_to( cr, 0, - h2 );

            /* Left hand side */
            cairo_curve_to( cr, - 275, - 355 - h2,
                                - 930, - 5 - h2,
                                - 450, 495 - h2 );
            cairo_curve_to( cr, - 155, 840 - h2,
                                -65, 940 - h2,
                                0, 1020 - h2 );
            
            /* Right hand side */
            cairo_curve_to( cr, 65, 940 - h2,
                                155, 840 - h2,
                                450, 495 - h2 );
            cairo_curve_to( cr, 930, -5 - h2,
                                275, - 355 - h2,
                                0, - h2 );
            break;

        case 2: /* Keyhole */
            cairo_move_to( cr, 0, - 725 );

            cairo_curve_to( cr, - 275, -725,
                                - 500, -500,
                                - 500, -230 );
            cairo_curve_to( cr, - 500, -100,
                                - 450, 25,
                                - 360, 110 );
            cairo_line_to( cr, - 480, 725 );
            cairo_line_to( cr, 480, 725 );
            cairo_line_to( cr, 360, 110 );
            cairo_curve_to( cr, 450, 25,
                                500, - 100,
                                500, - 230 );
            cairo_curve_to( cr, 500, - 500,
                                275, - 725,
                                0, - 725 );
            break;
    }
    cairo_fill( cr );
}

When you finished your plugin, all you have to is to run "make". This will create misc_shape_wipe.so plugin, which should be copied into $HOME/.imagination/plugins. Now start Imagination again and you should notice your plugins listed in combo box.

To add images to your transitions, create one 18x18 px image per each transition and save them into $HOME/.imagination/pixmaps. Images are associated with specific transitions by names. Image for transition with ID 56 should be named imagination-56.png.

And this is it. We wish you a lot of fun with writting new plugins.