/*! \page basic_effect Making an Effect Plugin

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

\subsection effect_first First step

First we need to write the few functions which Demopaja loads from the DLL when the DLL is loaded.
See the \ref new_project for how to set up a new project.

\subsection effect_desc Writing a plugin class descriptor

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 \b 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:

\code

// 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
}
\endcode

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. 

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

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.

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

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

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

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

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

Returns a short description of the plugin class.

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

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.

\code
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/";
}

\endcode

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 \ref html_help for more information how to deal with help files.

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

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.

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

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

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.

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

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

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.

\code
ImageDescC		g_rImageDesc;
\endcode

\subsection effect_final The Effect Class

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:

\code
// 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;
};
\endcode

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

\code
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 ) );
}

\endcode

Clone constructor. Initialize only.

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

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.

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

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

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.

\code
ImageEffectC*
ImageEffectC::create_new()
{
	return new ImageEffectC;
}
\endcode

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).

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

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

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

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).

\code
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 );
}
\endcode

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 undo_system.

\code
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;
}
\endcode

Returns number of gizmos inside the effect.

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

Returns the gizmo at specified index.

\code
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;
}
\endcode

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

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

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

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

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.

\code
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 );
}
\endcode

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

\code
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;
}
\endcode

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 initialisation

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

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.

\code
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 );
}
\endcode

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.

\code
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 );
}
\endcode

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

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

Returns the transformation matrix of the effect.

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

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.

\code
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;
}
\endcode

Some constant for serializing the effect.

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

const uint32	IMAGEEFFECT_VERSION = 1;
\endcode

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.

\code
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;
}
\endcode

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.

\code
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;
}
\endcode

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

*/