Dev:Adding a Layer

From Synfig Studio :: Documentation
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.

Section 1, "The Code" will present the entire source, uninterrupted, and section 2, "The Description" will break it up into small chunks and discuss it.

The Code

We need to create two new files (one header (.h) file, and one implementation (.cpp) file), and edit two existing files (the module's main.cpp, and the Makefile.am), all in the synfig-core/src/modules/mod_filter/ folder:

<module>/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;
}; // END of class Desaturate

#endif

<module>/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;
}

<module>/Makefile.am

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

<module>/main.cpp

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 four 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-core/src/synfig/layer.h for its definition.

    SYNFIG_LAYER_MODULE_EXT

The Methods

Then we declare the four methods we're going to implement.

get_param

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.

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;
}; // END of class Desaturate

#endif

Implementation

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():

Note the "G L O B A L S" section of the code defines the layer's name (lower case, used in .sif files), its "local" name (this is the string which is translated, and is capitalized), which category the layer belongs in (this determines where it appears in the 'new layer' menu), the layer's version (0.1 for new layers), etc.

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;
}

Adding Parameters

Suppose now we want to add a parameter to the layer. Let's add a Real valued parameter called "scale". It will let the user set a value to scale the saturation by. It will default to zero, meaning to scale the saturation down to zero. When adding parameters, it's a good idea to set the default value to its effective previous value, so that old .sif files will continue to be rendered the same as before.

To add this parameter, we need to do the following:

desaturate.h

In the desaturate.h header file, include real.h (the parameter is Real valued, so we need the typedef for Real):

#include <synfig/real.h>

Add a private member to the class, type Real:

private:
    synfig::Real scale;

Declare a default constructor. This is where we will set the default value of the new parameter:

    Desaturate();

Declare set_param(). This will allow our layer to accept new values for the parameter:

    virtual bool set_param(const synfig::String&, const synfig::ValueBase&);

desaturate.cpp

In the implementation file, we need to:

write a simple default constructor, which initializes the value of 'scale' to zero:

Desaturate::Desaturate():
    scale(0)
{}

Add a line to supply the value of 'scale' in get_param():

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

    EXPORT(scale);

    return ValueBase();
}

Add new method set_param():

bool
Desaturate::set_param(const String &param, const ValueBase &value)
{
     IMPORT(scale);

     return false;
}

Replace the definition of get_param_vocab():

Layer::Vocab
Desaturate::get_param_vocab()const
{
    Layer::Vocab ret;

    ret.push_back(ParamDesc("scale")
       .set_local_name(_("Scale"))
       .set_description(_("Factor to scale the saturation by"))
    );

    return ret;
}

And change the two places where the saturation is set. In get_color():

    return tmp.set_s(scale * tmp.get_s());

And in accelerated_render():

            pen.put_value(tmp.set_s(scale * tmp.get_s()));