Dev:Adding a Layer

From Synfig Studio :: Documentation
Revision as of 12:14, 16 April 2008 by Dooglus (Talk | contribs) (The Code)

Jump to: navigation, search

Here's an example of how to add a simple layer to an existing module. For this example I picked something very easy: a layer which removes all colour from the layers beneath it. I will call this creation "Desaturate", and add it to the mod_filter module.

The Code

So first I need to create desaturate.h and desaturate.cpp in the synfig/src/modules/mod_filter/ folder:

desaturate.h

/* === S Y N F I G ========================================================= */
/*! \file desaturate.h
**  \brief Header file for implementation of the "Desaturate" layer
**
**  \legal
**  Copyright (c) 2008 Chris Moore
**
**  This package is free software; you can redistribute it and/or
**  modify it under the terms of the GNU General Public License as
**  published by the Free Software Foundation; either version 2 of
**  the License, or (at your option) any later version.
**
**  This package is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**  General Public License for more details.
**  \endlegal
** ========================================================================= */

/* === S T A R T =========================================================== */

#ifndef __SYNFIG_DESATURATE_H
#define __SYNFIG_DESATURATE_H

/* === H E A D E R S ======================================================= */

#include <synfig/layer.h>

/* === C L A S S E S & S T R U C T S ======================================= */

class Desaturate : public synfig::Layer
{
    SYNFIG_LAYER_MODULE_EXT
public:
    virtual synfig::ValueBase get_param(const synfig::String&)const;
    virtual Vocab get_param_vocab()const;
    virtual synfig::Color get_color(synfig::Context, const synfig::Point&)const;
    virtual bool accelerated_render(synfig::Context,synfig::Surface*,int,
                                    const synfig::RendDesc &, synfig::ProgressCallback *)const;
    virtual bool reads_context()const { return true; }
}; // END of class Desaturate

#endif

desaturate.cpp

/* === S Y N F I G ========================================================= */
/*! \file desaturate.cpp
**  \brief Implementation of the "Desaturate" layer
**
**  \legal
**  Copyright (c) 2008 Chris Moore
**
**  This package is free software; you can redistribute it and/or
**  modify it under the terms of the GNU General Public License as
**  published by the Free Software Foundation; either version 2 of
**  the License, or (at your option) any later version.
**
**  This package is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**  General Public License for more details.
**  \endlegal
** ========================================================================= */

/* === H E A D E R S ======================================================= */

#ifdef USING_PCH
#   include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#   include <config.h>
#endif

#include "desaturate.h"

#include <synfig/context.h>
#include <synfig/paramdesc.h>
#include <synfig/renddesc.h>
#include <synfig/surface.h>
#include <synfig/value.h>

#endif

using namespace synfig;

/* === G L O B A L S ======================================================= */

SYNFIG_LAYER_INIT(Desaturate);
SYNFIG_LAYER_SET_NAME(Desaturate,"desaturate");
SYNFIG_LAYER_SET_LOCAL_NAME(Desaturate,N_("Desaturate"));
SYNFIG_LAYER_SET_CATEGORY(Desaturate,N_("Filters"));
SYNFIG_LAYER_SET_VERSION(Desaturate,"0.1");
SYNFIG_LAYER_SET_CVS_ID(Desaturate,"$Id$");

/* === M E T H O D S ======================================================= */

ValueBase
Desaturate::get_param(const String &param)const
{
    EXPORT_NAME();
    EXPORT_VERSION();

    return ValueBase();
}

Layer::Vocab
Desaturate::get_param_vocab()const
{
    return Layer::Vocab();
}

Color
Desaturate::get_color(Context context, const Point &getpos)const
{
    Color tmp(context.get_color(getpos));
    return tmp.set_s(0);
}

bool
Desaturate::accelerated_render(Context              context,
                               Surface*             surface,
                               int                  quality,
                               const RendDesc&      renddesc,
                               ProgressCallback*    cb
                              )const
{
    SuperCallback supercb(cb,0,9500,10000);

    // render the context onto the given surface
    if(!context.accelerated_render(surface,quality,renddesc,&supercb))
        return false;

    // set the saturation of each pixel to zero
    int x,y;
    Surface::pen pen(surface->begin());
    for(y=0;y<renddesc.get_h();y++,pen.inc_y(),pen.dec_x(x))
        for(x=0;x<renddesc.get_w();x++,pen.inc_x())
        {
            Color tmp(pen.get_value());
            pen.put_value(tmp.set_s(0));
        }

    // mark our progress as finished
    if(cb && !cb->amount_complete(10000,10000)) return false;
    return true;
}

Then I need to edit modules/mod_filter/Makefile.am and add these two files to the list of source files (maintain alphabetical order please):

libmod_filter_la_SOURCES = blur.cpp blur.h colorcorrect.cpp colorcorrect.h desaturate.cpp desaturate.h halftone2.cpp halftone2.h lumakey.cpp lumakey.h radialblur.cpp radialblur.h main.cpp halftone.cpp halftone.h halftone3.cpp halftone3.h

And finally we edit modules/mod_filter/main.cpp. Add this with the other #include lines:

#include "desaturate.h"

and add this with the other LAYER(...) lines:

LAYER(Desaturate)

then rebuild (including the autoreconf, ./configure, etc., to get the new files added to the Makefile) and we're done.

The Description

I'll break the header up into sections, describing each part:

The initial comment

This contains basic documentation and legal stuff. Just copy, paste, and edit from another file.

/* === S Y N F I G ========================================================= */
/*! \file desaturate.h
**  \brief Header file for implementation of the "Desaturate" layer
**
**  \legal
**  Copyright (c) 2008 Chris Moore
**
**  This package is free software; you can redistribute it and/or
**  modify it under the terms of the GNU General Public License as
**  published by the Free Software Foundation; either version 2 of
**  the License, or (at your option) any later version.
**
**  This package is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**  General Public License for more details.
**  \endlegal
** ========================================================================= */

/* === S T A R T =========================================================== */

Protection against Multiple Inclusion

This #ifndef is a standard way to make sure the header is only compiled once, even if it happens to be included multiple times. Use a unique symbol, based on the file name.

#ifndef __SYNFIG_DESATURATE_H
#define __SYNFIG_DESATURATE_H

Headers

Include files which are needed. We're not doing anything much, so all we need to include is layer.h, which defines the Layer class that all layers inherit from.

/* === H E A D E R S ======================================================= */

#include <synfig/layer.h>

The Class

Define the class which implements our layer. We inherit from the Layer class. For this simple example we only define five methods. The rest will be inherited from the Layer class.

/* === C L A S S E S & S T R U C T S ======================================= */

class Desaturate : public synfig::Layer
{

SYNFIG_LAYER_MODULE_EXT

This is standard. Just use it for every layer. It declares members to hold the layer's name, version, category, etc., and also declares a method to create() the layer. See synfig/src/synfig/layer.h for its definition.

    SYNFIG_LAYER_MODULE_EXT

The Methods

Then we declare the five methods we're going to implement. First "get_param()". Since our layer isn't going to have any parameters, all it needs to do is make the layer's name and version available.

get_param

public:
    virtual synfig::ValueBase get_param(const synfig::String&)const;

get_param_vocab

This one returns a list of the layer's parameters. We have no parameters, so we return an empty list.

    virtual Vocab get_param_vocab()const;

get_color

This one is used to find the color of a single given pixel. It's used by the "Info" panel to display the R,G,B values as you mouse over the canvas. It's also used if our layer has any transformation layers over the top of it. The absence of a working implementation of this method in the "Text" layer is why distorting text currently causes render artifacts.

    virtual synfig::Color get_color(synfig::Context, const synfig::Point&)const;

accelerated_render

This one is used to render a large rectangular piece of our layer in one go. It typically calls the accelerated_render() method for the layers under our layer (the 'context') and then modifies the results of that call in some way:

    virtual bool accelerated_render(synfig::Context,synfig::Surface*,int,
                                    const synfig::RendDesc &, synfig::ProgressCallback *)const;

reads_context

This one returns true always, meaning that this layer's output depends on the layers under it (the 'context'):

    virtual bool reads_context()const { return true; }
}; // END of class Desaturate

#endif

I won't reproduce the whole of the .cpp file here and go through it. The only two methods which are really interesting here are get_color() and accelerated_render():

Implementation

get_color()

Given the context, and a point, get_color() should return the color of the pixel at the given point. The context has a get_color() method, so we use that to look up the color of the pixel before our layer has had a chance to affect it, and store that in 'tmp'. Then we set its saturation to zero using color.set_s() and return it. That's all:

Color
Desaturate::get_color(Context context, const Point &getpos)const
{
    Color tmp(context.get_color(getpos));
    return tmp.set_s(0);
}

accelerated_render()

We accept five arguments:

The context (ie. the layers under us):

bool
Desaturate::accelerated_render(Context              context,

The surface we are supposed to write our results onto:

                               Surface*             surface,

The quality at which we are to render:

                               int                  quality,

A description of the area to render - width, height, top left corner, resolution, etc, etc:

                               const RendDesc&      renddesc,

An object which is used to monitor our progress. It's used for calculating the expected remaining time while rendering:

                               ProgressCallback*    cb
                              )const
{

I didn't get around to looking at the way the 'cb' ProgressCallback is used. But this code is in pretty much all layers:

    SuperCallback supercb(cb,0,9500,10000);

We call accelerated_render() on the context, to render the layers under us. We just pass on the arguments we received for surface, quality and renddesc, along with the new callback we just made. If that render fails, then we fail too:

    // render the context onto the given surface
    if(!context.accelerated_render(surface,quality,renddesc,&supercb))
        return false;

Now we do the real work of this layer. We define integers to do the looping, and a 'pen' which we can walk over the rectangle we're working on:

    // set the saturation of each pixel to zero
    int x,y;
    Surface::pen pen(surface->begin());

We loop through every pixel on every row of the rectangle we're working on. The 'renddesc' gives us the width and height of the rectangle. The pen has methods inc_x(), dec_x(), inc_y(), dec_y() which move it a given amount:

    for(y=0;y<renddesc.get_h();y++,pen.inc_y(),pen.dec_x(x))
        for(x=0;x<renddesc.get_w();x++,pen.inc_x())

At each pixel, we get the color as rendered from the context, set its saturation to zero, and write it back to the same surface:

        {
            Color tmp(pen.get_value());
            pen.put_value(tmp.set_s(0));
        }

And that's about it.

    // mark our progress as finished
    if(cb && !cb->amount_complete(10000,10000)) return false;
    return true;
}