Main Page Namespace List Class Hierarchy Alphabetical List Compound List File List Namespace Members Compound Members Related Pages

# Making an Effect Plugin

This tutorial will guide you to write a simple effect plugin for Moppi Demopaja.

First we need to write the few functions which Demopaja loads from the DLL when the DLL is loaded. See the Creating a New Plugin Project for how to set up a new project.

The next step is to write a class descriptor. We will implement a class descriptor for an effect plugin. At this point we also need the unique class ID for our class. This class is generated with the class ID generator which comes with the SDK.

The class descriptor of the image effect looks like this:

    // The class ID
    const ClassIdC  CLASS_SIMPLEIMAGE_EFFECT( 0x08DF0B9D, 0xFF4B4769 );

    //  Simple image effect class descriptor.
    class ImageDescC : public ClassDescC
    {
    public:
        ImageDescC();
        virtual ~ImageDescC();
        virtual void*           create();
        virtual int32           get_classtype() const;
        virtual SuperClassIdC   get_super_class_id() const;
        virtual ClassIdC        get_class_id() const;
        virtual const char*     get_name() const;
        virtual const char*     get_desc() const;
        virtual const char*     get_author_name() const;
        virtual const char*     get_copyright_message() const;
        virtual const char*     get_url() const;
        virtual const char*     get_help_filename() const;
        virtual uint32          get_required_device_driver_count() const;
        virtual const ClassIdC& get_required_device_driver( PajaTypes::uint32 ui32Idx );
        virtual uint32          get_ext_count() const;
        virtual const char*     get_ext( uint32 ui32Index ) const;
    };

    ImageDescC::ImageDescC()
    {
        // empty
    }

    ImageDescC::~ImageDescC()
    {
        // empty
    }

The create method is used to create a new instance of the image effect class. In the image effect class there is a static member method which is used to create a new instance.

    void*
    ImageDescC::create()
    {
        return (void*)ImageEffectC::create_new();
    }

Returns the plugin class type. CLASS_TYPE_EFFECT is returned for all effect plugins. The system determines using this method wheter the plugin class is importer or effect.

    int32
    ImageDescC::get_classtype() const
    {
        return CLASS_TYPE_EFFECT;
    }

Returns the super class ID. All effects returns SUPERCLASS_EFFECT. It indicates that the effect is derived from EffectI class.

    SuperClassIdC
    ImageDescC::get_super_class_id() const
    {
        return SUPERCLASS_EFFECT;
    }

Returns the class ID of the image effect class. This class ID has to be unique for every plugin class that is implemented.

    ClassIdC
    ImageDescC::get_class_id() const
    {
        return CLASS_SIMPLEIMAGE_EFFECT;
    };

Returns a short description of the plugin class.

    const char*
    ImageDescC::get_desc() const
    {
        return "Simple Image effect";
    }

These methods returns some information about the author of the plugin class, such name, copyright notice and an URL where the plugin/author can be found.

    const char*
    ImageDescC::get_name() const
    {
        return "Simple Image";
    }

    const char*
    ImageDescC::get_author_name() const
    {
        return "Mikko \"memon\" Mononen";
    }

    const char*
    ImageDescC::get_copyright_message() const
    {
        return "Copyright (c) 2000 Moppi Productions";
    }

    const char*
    ImageDescC::get_url() const
    {
        return "http://www.moppi.inside.org/demopaja/";
    }

Returns the file name of the help file. Help files are in HTML format. This plugin has the HTML file stored in the resources of the DLL. See Demopaja Help System for more information how to deal with help files.

    const char*
    ImageDescC::get_help_filename() const
    {
        return "res://imagehelp.html";
    }

These tow methods are used to checks if this plugin class can be created and used within the current device context. Every device the plugin class depends on should be returned here.

    uint32
    ImageDescC::get_required_device_driver_count() const
    {
        return 1;
    }

    const ClassIdC&
    ImageDescC::get_required_device_driver( uint32 ui32Idx )
    {
        return PajaSystem::CLASS_OPENGL_DEVICEDRIVER;
    }

These two methods are not used for effect plugins. These are used with importer plugin classes and they return the file extensions which the plugin could load.

    uint32
    ImageDescC::get_ext_count() const
    {
        return 0;
    }

    const char*
    ImageDescC::get_ext( uint32 ui32Index ) const
    {
        return 0;
    }

Now we should declare one global variable (those people who fear global variables propably can find a different way to do this) which type is the class we just defined. It is important that this variable is global (or at least static) since a pointer to it is returned to the Demopaja via the get_classdesc() function.

    ImageDescC      g_rImageDesc;

The last thing to do is to write the effect class. The effect class will contain some gizmos, which in turn contains the parameters. Usually it would be required to implement some GizmoI derived classes where the gizmos would be stored, but since the effect we work on is so simple we use a ::AutoGizmoI class instead. The auto gizmo class enables the gizmo to be filled with parameters with out need to write derived gizmo class. Even the auto gizmo saves the data inside parameters it does not save the information about itself. That is, the gizmo has to be populated with the same kind of parameters before it is loaded. It is safe to use the auto gizmo in the way it is represented in this example.

This effect simple has six parameters put in two gizmos. The first gizmo "Transform" holds position, pivot, scale, and rotation parameters, the second gizmo "Attributes" holds color and image parameters.

Here is the class definition of the image effect plugin class:

    // IDs for the transformation parameters.
    enum TransformGizmoParamsE {
        ID_TRANSFORM_POS = 0,
        ID_TRANSFORM_PIVOT,
        ID_TRANSFORM_ROT,
        ID_TRANSFORM_SCALE,
        TRANSFORM_COUNT,
    };

    // IDs for the attribute parameters.
    enum AttributeGizmoParamsE {
        ID_ATTRIBUTE_COLOR = 0,
        ID_ATTRIBUTE_FILE,
        ATTRIBUTE_COUNT,
    };

    // Gizmo IDs.
    enum ImageEffectGizmosE {
        ID_GIZMO_TRANS = 0,
        ID_GIZMO_ATTRIB,
        GIZMO_COUNT,
    };

    // The Image effect class.
    class ImageEffectC : public Composition::EffectI
    {
    public:
        static ImageEffectC*            create_new();
        virtual Edit::DataBlockI*       create();
        virtual Edit::DataBlockI*       create( Edit::EditableI* pOriginal );
        virtual void                    copy( Edit::DataBlockI* pBlock );
        virtual void                    restore( Edit::EditableI* pEditable );

        virtual PajaTypes::int32        get_gizmo_count();
        virtual Composition::GizmoI*    get_gizmo( PajaTypes::int32 i32Index );

        virtual PluginClass::ClassIdC   get_class_id();
        virtual const char*             get_class_name();

        virtual void                    set_default_file( Import::FileHandleC* pHandle );
        virtual Composition::ParamI*    get_default_param( PajaTypes::int32 i32Param );

        virtual void                    initialize( PajaTypes::uint32 ui32Reason, PajaSystem::DeviceContextC* pContext, PajaSystem::TimeContextC* pTimeContext );

        virtual void                    do_frame( PajaSystem::DeviceContextC* pContext );
        virtual void                    eval_state( PajaTypes::int32 i32Time, PajaSystem::TimeContextC* pTimeContext );
        virtual PajaTypes::BBox2C       get_bbox();

        virtual const PajaTypes::Matrix2C&  get_transform_matrix() const;

        virtual bool                    hit_test( const PajaTypes::Vector2C& rPoint );

        virtual PajaTypes::uint32       save( FileIO::SaveC* pSave );
        virtual PajaTypes::uint32       load( FileIO::LoadC* pLoad );

    protected:
        ImageEffectC();
        ImageEffectC( Edit::EditableI* pOriginal );
        virtual ~ImageEffectC();

    private:

        Composition::AutoGizmoC*    m_pTraGizmo;
        Composition::AutoGizmoC*    m_pAttGizmo;

        PajaTypes::Matrix2C m_rTM;
        PajaTypes::BBox2C   m_rBBox;
        PajaTypes::Vector2C m_rVertices[4];
        PajaTypes::ColorC   m_rFillColor;
    };

Here is the implementation:

Default constructor. Create the gizmos and the parameters and put the into the gizmos. We have to do the parameter initialization here, because the auto gizmos has to be filled before the serialization methods are called (load(), save()).

    ImageEffectC::ImageEffectC()
    {
        // Create Transform gizmo.
        m_pTraGizmo = AutoGizmoC::create_new( this, "Transform", ID_GIZMO_TRANS );

        m_pTraGizmo->add_parameter( ParamVector2C::create_new( m_pTraGizmo, "Position", Vector2C(), ID_TRANSFORM_POS,
                            PARAM_STYLE_EDITBOX | PARAM_STYLE_ABS_POSITION, PARAM_ANIMATABLE ) );

        m_pTraGizmo->add_parameter( ParamVector2C::create_new( m_pTraGizmo, "Pivot", Vector2C(), ID_TRANSFORM_PIVOT,
                            PARAM_STYLE_EDITBOX | PARAM_STYLE_REL_POSITION | PARAM_STYLE_WORLD_SPACE, PARAM_ANIMATABLE ) );

        m_pTraGizmo->add_parameter( ParamFloatC::create_new( m_pTraGizmo, "Rotation", 0, ID_TRANSFORM_ROT,
                            PARAM_STYLE_EDITBOX | PARAM_STYLE_ANGLE, PARAM_ANIMATABLE, 0, 0, 1.0f ) );

        m_pTraGizmo->add_parameter( ParamVector2C::create_new( m_pTraGizmo, "Scale", Vector2C( 1, 1 ), ID_TRANSFORM_SCALE,
                            PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, Vector2C(), Vector2C(), 0.01f ) );

        // Create Attributes gizmo.
        m_pAttGizmo = AutoGizmoC::create_new( this, "Attributes", ID_GIZMO_ATTRIB );

        m_pAttGizmo->add_parameter( ParamColorC::create_new( m_pAttGizmo, "Fill Color", ColorC( 1, 1, 1, 1 ), ID_ATTRIBUTE_COLOR,
                                PARAM_STYLE_COLORPICKER, PARAM_ANIMATABLE ) );
        m_pAttGizmo->add_parameter( ParamFileC::create_new( m_pAttGizmo, "Fill Image", SUPERCLASS_IMAGE, NULL_CLASSID, ID_ATTRIBUTE_FILE ) );
    }

Clone constructor. Initialize only.

    ImageEffectC::ImageEffectC( EditableI* pOriginal ) :
        EffectI( pOriginal ),
        m_pTraGizmo( 0 ),
        m_pAttGizmo( 0 )
    {
        // Empty. The parameters are not created in the clone constructor.
    }

The destructor. Since the data is not duplicated when a clone object is created we don't release the data if the object is a clone. Clone objects can be detected using the get_original() method of the EditableI interface. If it returns a valid pointer (anything else than NULL ) then the object is a clone and data should not be released.

    ImageEffectC::~ImageEffectC()
    {
        // Return if this is a clone.
        if( get_original() )
            return;

        // Release gizmos.
        m_pTraGizmo->release();
        m_pAttGizmo->release();
    }

Static member which is used to create a new instance of a plugin class. We cannot use the new operator since the constructor of the class is protected. The reason the cosntructor and desctructor are protected is to prevent the miss use the of the classes. Since the effect classes are created in a DLL file they also has to be released (deleted) there because the main program and a DLL has different memory management.

    ImageEffectC*
    ImageEffectC::create_new()
    {
        return new ImageEffectC;
    }

This method is analogous to the default constructor ans is used by the system to create new instances of this class (for example, to copy the effect).

    DataBlockI*
    ImageEffectC::create()
    {
        return new ImageEffectC;
    }

This method is analogous to the clone constructor and is used by the system to create clones for undo operations.

    DataBlockI*
    ImageEffectC::create( EditableI* pOriginal )
    {
        return new ImageEffectC( pOriginal );
    }

This method is used to copy data from a specified datablock (the editable is guaranteed to be of the same class as the class which copy() method is called). All the data should be copied (deep copy).

    void
    ImageEffectC::copy( EditableI* pEditable )
    {
        EffectI::copy( pEditable );

        // Make deep copy.
        ImageEffectC*   pEffect = (ImageEffectC*)pEditable;
        m_pTraGizmo->copy( pEffect->m_pTraGizmo );
        m_pAttGizmo->copy( pEffect->m_pAttGizmo );
    }

This method is used to make shallow copy from a specified editable (the editable is guaranteed to be of the same class as the class which copy() method is called). Only the variables should be copied, no the data they point to. This method is used first to store the data to a clone object and then later on to restore the data from the clone, hence the name.

See also:

See also:
  undo_system.

    void
    ImageEffectC::restore( EditableI* pEditable )
    {
        EffectI::restore( pEditable );

        // Make shallow copy.
        ImageEffectC*   pEffect = (ImageEffectC*)pEditable;
        m_pTraGizmo = pEffect->m_pTraGizmo;
        m_pAttGizmo = pEffect->m_pAttGizmo;
    }

Returns number of gizmos inside the effect.

    int32
    ImageEffectC::get_gizmo_count()
    {
        // Return number of gizmos inside this effect.
        return GIZMO_COUNT;
    }

Returns the gizmo at specified index.

    GizmoI*
    ImageEffectC::get_gizmo( PajaTypes::int32 i32Index )
    {
        // Returns specified gizmo.
        // Since the ID's are zero based, we can use them as indices.
        switch( i32Index ) {
        case ID_GIZMO_TRANS:
            return m_pTraGizmo;
        case ID_GIZMO_ATTRIB:
            return m_pAttGizmo;
        }

        return 0;
    }

Returns the class ID. This method should return the same information as the method of same name in the class descriptor.

    ClassIdC
    ImageEffectC::get_class_id()
    {
        // Return the class ID. Should be same as in the class descriptor.
        return CLASS_SIMPLEIMAGE_EFFECT;
    }

Returns the name of the class. This method should return the same information as the method of same name in the class descriptor.

    const char*
    ImageEffectC::get_class_name()
    {
        // Return the class name. Should be same as in the class descriptor.
        return "Simple Image";
    }

Sets the default file. This method is called when an effect is created by dragging an imported file to the Layer list. This method should set the file parameter which is ment to be the default for this effect. The file parameter checks that the file is in format allowed by the class filters.

    void
    ImageEffectC::set_default_file( FileHandleC* pHandle )
    {
        // Sets the default file.

        // Get the file parameter.
        ParamFileC* pParam = (ParamFileC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_FILE );

        // Begin Undo block.
        UndoC*  pOldUndo = pParam->begin_editing( get_undo() );

        // Set the file.
        pParam->set_file( pHandle );

        // End undo block.
        pParam->end_editing( pOldUndo );
    }

Returns default parameters used by the user interface (transformation).

    ParamI*
    ImageEffectC::get_default_param( int32 i32Param )
    {
        // Return specified default parameter.
        if( i32Param == DEFAULT_PARAM_POSITION )
            return m_pTraGizmo->get_parameter( ID_TRANSFORM_POS );
        else if( i32Param == DEFAULT_PARAM_ROTATION )
            return m_pTraGizmo->get_parameter( ID_TRANSFORM_ROT );
        else if( i32Param == DEFAULT_PARAM_SCALE )
            return m_pTraGizmo->get_parameter( ID_TRANSFORM_SCALE );
        else if( i32Param == DEFAULT_PARAM_PIVOT )
            return m_pTraGizmo->get_parameter( ID_TRANSFORM_PIVOT );
        return 0;
    }

Initializes the effect. All the initializing which requires the information about the device or time code, should be done here. Also, if parameters needs to be copied (for example the gizmo layout has changed between two version of the effect) don't copy the parameters in the load() method, but do it in this method, because the file handles in file parameters are not valid when the effect is being loaded. When a new effect is created this method is called after data is passed to the effect (default parameter and name).

Note that if an effect is duplicated (for example using Copy/Paste) this method is not called. So the copy() method should copy effects whole state to the another effect.

See also:

See also:
  initialisation

    void
    ImageEffectC::initialize( uint32 ui32Reason, DeviceContextC* pContext, TimeContextC* pTimeContext )
    {
        // Empty, nothign to initialize.
    }

Updates the effect state at specified time. The main purpose is to generate here the data the user interface will need. The most important things are: transformation matrix, bounding box, and possible data which may be needed in hittesting.

    void
    ImageEffectC::eval_state( int32 i32Time, TimeContextC* pTimeContext )
    {
        Matrix2C    rPosMat, rRotMat, rScaleMat, rPivotMat;

        Vector2C    rScale;
        Vector2C    rPos;
        Vector2C    rPivot;
        float32     f32Rot;

        // Get parameters which affect the transformation.
        ((ParamVector2C*)m_pTraGizmo->get_parameter( ID_TRANSFORM_POS ))->get_val( i32Time, rPos );
        ((ParamVector2C*)m_pTraGizmo->get_parameter( ID_TRANSFORM_PIVOT ))->get_val( i32Time, rPivot );
        ((ParamFloatC*)m_pTraGizmo->get_parameter( ID_TRANSFORM_ROT ))->get_val( i32Time, f32Rot );
        ((ParamVector2C*)m_pTraGizmo->get_parameter( ID_TRANSFORM_SCALE ))->get_val( i32Time, rScale );

        // Calculate transformation matrix.
        rPivotMat.set_trans( rPivot );
        rPosMat.set_trans( rPos );
        rRotMat.set_rot( f32Rot / 180.0f * M_PI );
        rScaleMat.set_scale( rScale ) ;
        m_rTM = rPivotMat * rRotMat * rScaleMat * rPosMat;

        float32     f32Width = 25;
        float32     f32Height = 25;
        Vector2C    rMin, rMax;
        Vector2C    rVec;

        // Get the size from the fiel or use the defautls if no file.
        ImportableImageI*   pImp = 0;
        FileHandleC*        pHandle = ((ParamFileC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_FILE ))->get_file();

        if( pHandle )
            pImp = (ImportableImageI*)pHandle->get_importable();

        if( pImp ) {
            f32Width = (float32)pImp->get_width() * 0.5f;
            f32Height = (float32)pImp->get_height() * 0.5f;
        }

        // Calcualte vertices of the rectangle.
        m_rVertices[0][0] = -f32Width;      // top-left
        m_rVertices[0][1] = -f32Height;

        m_rVertices[1][0] =  f32Width;      // top-right
        m_rVertices[1][1] = -f32Height;

        m_rVertices[2][0] =  f32Width;      // bottom-right
        m_rVertices[2][1] =  f32Height;

        m_rVertices[3][0] = -f32Width;      // bottom-left
        m_rVertices[3][1] =  f32Height;

        // Calculate bounding box
        for( uint32 i = 0; i < 4; i++ ) {
            rVec = m_rTM * m_rVertices[i];
            m_rVertices[i] = rVec;

            if( !i )
                rMin = rMax = rVec;
            else {
                if( rVec[0] < rMin[0] ) rMin[0] = rVec[0];
                if( rVec[1] < rMin[1] ) rMin[1] = rVec[1];
                if( rVec[0] > rMax[0] ) rMax[0] = rVec[0];
                if( rVec[1] > rMax[1] ) rMax[1] = rVec[1];
            }
        }

        // Store bounding box.
        m_rBBox[0] = rMin;
        m_rBBox[1] = rMax;

        // Get fill color.
        ((ParamColorC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_COLOR ))->get_val( i32Time, m_rFillColor );
    }

This method is called to render the crrent frame. The eval_state() method is always called before this method. You should render only the are you accupy with the bounding box. The space needed to render should be evaluated in the eval_state() method. For optimization purposes the bounding box may be acquired from the effect after the eval_state() is called and is the bounding box is not visible the frame rendering method may not be never called.

    void
    ImageEffectC::do_frame( DeviceContextC* pContext )
    {
        // Get the OpenGL device.
        OpenGLDeviceC*  pDevice = (OpenGLDeviceC*)pContext->query_interface( CLASS_OPENGL_DEVICEDRIVER );
        if( !pDevice )
            return;

        // Get the OpenGL viewport.
        OpenGLViewportC*    pViewport = (OpenGLViewportC*)pDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
        if( !pViewport )
            return;

        // Set orthographic projection.
        pViewport->set_ortho( m_rBBox, m_rBBox[0][0], m_rBBox[1][0], m_rBBox[1][1], m_rBBox[0][1] );

        // Get file handle and image.
        FileHandleC*        pHandle = 0;
        ImportableImageI*   pImp = 0;

        pHandle = ((ParamFileC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_FILE ))->get_file();
        if( pHandle )
            pImp = (ImportableImageI*)pHandle->get_importable();

        if( pImp ) {
            // If there is image set it as current texture.
            pImp->bind_texture( pDevice, IMAGE_CLAMP | IMAGE_LINEAR );
            glEnable( GL_TEXTURE_2D );
        }
        else
            glDisable( GL_TEXTURE_2D );

        glDisable( GL_DEPTH_TEST );
        glEnable( GL_BLEND );
        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
        glDepthMask( GL_FALSE );

        // Set color
        glColor4fv( m_rFillColor );

        // Draw rectangle.
        glBegin( GL_QUADS );

        glTexCoord2f( 0, 1 );
        glVertex2f( m_rVertices[0][0], m_rVertices[0][1] );

        glTexCoord2f( 1, 1 );
        glVertex2f( m_rVertices[1][0], m_rVertices[1][1] );

        glTexCoord2f( 1, 0 );
        glVertex2f( m_rVertices[2][0], m_rVertices[2][1] );

        glTexCoord2f( 0, 0 );
        glVertex2f( m_rVertices[3][0], m_rVertices[3][1] );

        glEnd();

        glDepthMask( GL_TRUE );
    }

Returns the bounding box of the effect. The whole effect should fit inside this box. The box is axis-aligned.

    BBox2C
    ImageEffectC::get_bbox()
    {
        // Return the bounding box.
        return m_rBBox;
    }

Returns the transformation matrix of the effect.

    const Matrix2C&
    ImageEffectC::get_transform_matrix() const
    {
        // Return the transformation matrix.
        return m_rTM;
    }

Tests if the specified point is inside the the effects rendering area. Simple point in polygon testing is used here. The hit testing should be as accurate as possible, but if it cannot be defined, hit test the bounding box. The code here is taken from the comp.graphics.algorithms newsgroup FAQ.

    bool
    ImageEffectC::hit_test( const Vector2C& rPoint )
    {
        // Point in polygon test.
        // from c.g.a FAQ
        int     i, j;
        bool    bInside = false;

        for( i = 0, j = 4 - 1; i < 4; j = i++ ) {
            if( ( ((m_rVertices[i][1] <= rPoint[1]) && (rPoint[1] < m_rVertices[j][1])) ||
                ((m_rVertices[j][1] <= rPoint[1]) && (rPoint[1] < m_rVertices[i][1])) ) &&
                (rPoint[0] < (m_rVertices[j][0] - m_rVertices[i][0]) * (rPoint[1] - m_rVertices[i][1]) / (m_rVertices[j][1] - m_rVertices[i][1]) + m_rVertices[i][0]) )

                bInside = !bInside;
        }

        return bInside;
    }

Some constant for serializing the effect.

    enum ImageEffectChunksE {
        CHUNK_IMAGEEFFECT_BASE =        0x1000,
        CHUNK_IMAGEEFFECT_TRANSGIZMO =  0x2000,
        CHUNK_IMAGEEFFECT_ATTRIBGIZMO = 0x3000,
    };

    const uint32    IMAGEEFFECT_VERSION = 1;

Saves the effect to Demopaja output stream. It usually won't get any complicated than this. It is important to call the EffectI's save method too.

    uint32
    ImageEffectC::save( SaveC* pSave )
    {
        uint32  ui32Error = IO_OK;

        // EffectI base class
        pSave->begin_chunk( CHUNK_IMAGEEFFECT_BASE, IMAGEEFFECT_VERSION );
            ui32Error = EffectI::save( pSave );
        pSave->end_chunk();

        // Transform
        pSave->begin_chunk( CHUNK_IMAGEEFFECT_TRANSGIZMO, IMAGEEFFECT_VERSION );
            ui32Error = m_pTraGizmo->save( pSave );
        pSave->end_chunk();

        // Attribute
        pSave->begin_chunk( CHUNK_IMAGEEFFECT_ATTRIBGIZMO, IMAGEEFFECT_VERSION );
            ui32Error = m_pAttGizmo->save( pSave );
        pSave->end_chunk();

        return ui32Error;
    }

Loads the effect from Demopaja input stream. It's important that the auto-gizmos are properly set up before this method is called. If they are initialised in the constructor like in this example they will work properly.

    uint32
    ImageEffectC::load( LoadC* pLoad )
    {
        uint32  ui32Error = IO_OK;

        while( (ui32Error = pLoad->open_chunk()) == IO_OK ) {

            switch( pLoad->get_chunk_id() ) {
            case CHUNK_IMAGEEFFECT_BASE:
                // EffectI base class
                if( pLoad->get_chunk_version() == IMAGEEFFECT_VERSION )
                    ui32Error = EffectI::load( pLoad );
                break;

            case CHUNK_IMAGEEFFECT_TRANSGIZMO:
                // Transform
                if( pLoad->get_chunk_version() == IMAGEEFFECT_VERSION )
                    ui32Error = m_pTraGizmo->load( pLoad );
                break;

            case CHUNK_IMAGEEFFECT_ATTRIBGIZMO:
                // Attribute
                if( pLoad->get_chunk_version() == IMAGEEFFECT_VERSION )
                    ui32Error = m_pAttGizmo->load( pLoad );
                break;

            default:
                assert( 0 );
            }

            pLoad->close_chunk();

            if( ui32Error != IO_OK && ui32Error != IO_END )
                return ui32Error;
        }

        return ui32Error;
    }

Now we have created an effect plugin which shows images in Demopaja. The source for this plugin can be found in the `Examples` directory. That plugin also contains a PCX image importer.