/////////////////////////////////////////////////////////////
// CINEMA SDK : BITMAPFILTER                               //
/////////////////////////////////////////////////////////////
// VERSION    : CINEMA 4D																	 //
/////////////////////////////////////////////////////////////
// (c) 1989-2002 MAXON Computer GmbH, all rights reserved	 //
/////////////////////////////////////////////////////////////

// example threshold filter

#include "c4d.h"
#include "c4d_symbols.h"
#include "filterthread.h"
#include "filterpreview.h"
#include "filter_prefs.h"
#include "matrixutil.h"
#include "matrixwrapper.h"

//----------------------------------------------------------------------------------------
// filter interface functions
//----------------------------------------------------------------------------------------

// filter settings
static FILTER_PARAM					filter_new_settings(FI_REF fi,  BaseContainer *t_bc );
static LONG									filter_delete_settings( FI_REF fi, FILTER_PARAM settings );
static FLAT_FILTER_SETTINGS	*filter_flatten_settings( FI_REF fi, FILTER_PARAM settings, void *(*flat_malloc)( LONG size ));
static FILTER_PARAM					filter_unflatten_settings( FI_REF fi, FLAT_FILTER_SETTINGS *flat_settings );

// filter actions
static LONG									filter_start( FI_REF fi, BM_REF bm, FILTER_PARAM settings );
static LONG									filter_end( FI_REF fi, BM_REF bm, FILTER_PARAM settings );
static LONG									filter_run( FI_REF fi, BM_REF bm, FILTER_PARAM settings, Bool use_gui );

static BITMAP_FILTER	threshold_filter =
{
	'bmpf',

	// filter settings
	filter_new_settings,
	filter_delete_settings,
	filter_flatten_settings,
	filter_unflatten_settings,

	// filter actions
	filter_start,
	filter_end,
	filter_run

};

//----------------------------------------------------------------------------------------
// private functions
//----------------------------------------------------------------------------------------
#define BM_FILTER_THRESHOLD_LEVELS 1

#define	MIN_LEVELS			2
#define	MAX_LEVELS			256
#define	STEP_LEVELS			1
#define	DFLT_LEVELS			16

#define	PRIVATE_SETTINGS	THRESHOLD_SETTINGS
#define	PRIVATE_SETTINGS_MAGIC	CHAR_CONST32('thrs')
struct PRIVATE_SETTINGS
{
	ULONG		magic;
	LONG		no_levels;

	LONG		max_value;
	ULONG		*scale_table;
};

static LONG	apply_effect( BM_REF bm, const RECT32 *src_rect, BM_TILE *dt, PRIVATE_SETTINGS *settings, Bool update_view, FilterThread *thread);
static LONG	do_effect( BM_REF bm, const RECT32 *src_rect, BM_TILE *dst, PRIVATE_SETTINGS *settings );
static Bool	change_scale_table( PRIVATE_SETTINGS *settings, LONG no_levels, Bool always );


//----------------------------------------------------------------------------------------
// filter dialog class (ModalDialog)
//----------------------------------------------------------------------------------------
class ThresholdDialog : public GeModalDialog
{
		PRIVATE_SETTINGS	*settings;
		PreviewGroup			preview;
		BM_REF						bm;
		Bool							real_time;
		Bool							document_preview;

	public:

		ThresholdDialog( void );
		~ThresholdDialog();

		virtual Bool	Command( LONG id, const BaseContainer &msg );
		virtual Bool	CreateLayout( void );
		virtual Bool	InitValues( void );

		Bool	Init( BM_REF bm, PRIVATE_SETTINGS *settings );
};


//----------------------------------------------------------------------------------------
// Register filter
// Function result:		TRUE/FALSE
//----------------------------------------------------------------------------------------
Bool RegisterThreshold(void)
{
	String name=GeLoadString(IDS_THRESHOLD); if (!name.Content()) return TRUE;

	// be sure to use a unique ID obtained from www.plugincafe.com
	return GeRegisterPlugin(C4DPL_BITMAPFILTER,1000691,name,&threshold_filter,sizeof(threshold_filter));
}

//----------------------------------------------------------------------------------------
// Create default filter settings
// Function result:		filter settings oder 0 (not enough memory)
//----------------------------------------------------------------------------------------
static FILTER_PARAM	filter_new_settings( FI_REF fi, BaseContainer *t_bc )
{
	PRIVATE_SETTINGS	*settings;

	settings = (PRIVATE_SETTINGS *) GeAlloc( sizeof( PRIVATE_SETTINGS ));
	if ( settings )
	{
		settings->magic = PRIVATE_SETTINGS_MAGIC;
		settings->no_levels = DFLT_LEVELS;
		settings->scale_table = 0;

		if ( t_bc )																							// read settings from container?
		{
			settings->no_levels = t_bc->GetReal( BM_FILTER_THRESHOLD_LEVELS );

			if ( settings->no_levels < MIN_LEVELS )
				settings->no_levels = MIN_LEVELS;
			if ( settings->no_levels > MAX_LEVELS )
				settings->no_levels = MAX_LEVELS;
		}
	}
	
	return (FILTER_PARAM)settings;
}

//----------------------------------------------------------------------------------------
// Dispose of filter settings
// Function result:		FILTER_OK or an error code
// settings:					private filter settings
//----------------------------------------------------------------------------------------
static LONG	filter_delete_settings( FI_REF fi, FILTER_PARAM settings )
{
	if ( settings )
	{
		GeFree( settings );
		settings = 0;
	}
	return FILTER_OK;
}

//----------------------------------------------------------------------------------------
// Convert private settings in format that can be saved (no pointers, ...)
// Function result:		pointer to the "flat" filter settings or 0
// settings:					private filter settings
// flat_malloc:				must be used to allocate the memory for flat_settings
//----------------------------------------------------------------------------------------
static FLAT_FILTER_SETTINGS	*filter_flatten_settings( FI_REF fi, FILTER_PARAM settings, void *(*flat_malloc)( LONG size ))
{
	FLAT_FILTER_SETTINGS	*flat_settings;
	LONG	len;
	
	len = sizeof_FLAT_FILTER_SETTINGS + sizeof( PRIVATE_SETTINGS );
	flat_settings = (FLAT_FILTER_SETTINGS *) flat_malloc( len );
	
	if ( flat_settings )
	{
		flat_settings->magic = FLAT_SETTINGS_MAGIC;
		flat_settings->length = len;
		flat_settings->format = 0;
		flat_settings->reserved = 0;

		CopyMem( settings, flat_settings->data, sizeof( PRIVATE_SETTINGS ));	// ignore max_value and scale_table
	}
	return flat_settings;
}

//----------------------------------------------------------------------------------------
// Validate flat settings and convert into private format
// Function result:		pointer to the private filter settings or 0
// flat_settings:			saved settings
//----------------------------------------------------------------------------------------
static FILTER_PARAM	filter_unflatten_settings( FI_REF fi, FLAT_FILTER_SETTINGS *flat_settings )
{
	PRIVATE_SETTINGS	*settings;

	settings = 0;

	if ( flat_settings->magic == FLAT_SETTINGS_MAGIC )
	{
		PRIVATE_SETTINGS	*p;

		p = (PRIVATE_SETTINGS *) flat_settings->data;
		if ( p->magic == PRIVATE_SETTINGS_MAGIC )								// do they belong to this filter?
		{
			settings = (PRIVATE_SETTINGS *) filter_new_settings( fi,0 );
			if ( settings )
			{
				if (( p->no_levels > MIN_LEVELS ) && ( p->no_levels < MAX_LEVELS ))	// valid range? 
					settings->no_levels = p->no_levels;								// set value
			}
		}
	}
	return settings;
}

//----------------------------------------------------------------------------------------
// Start of a filter action
// Function result:		FILTER_OK or an error code (e.g. FILTER_MEM_ERR)
// bm:								bitmap reference
// settings:					private filter settings
//----------------------------------------------------------------------------------------
static LONG	filter_start( FI_REF fi, BM_REF bm, FILTER_PARAM _settings )
{
	PRIVATE_SETTINGS	*settings;

	settings = (PRIVATE_SETTINGS *) _settings;
	if ( settings && ( settings->magic == PRIVATE_SETTINGS_MAGIC ))
	{
		LONG	component_size;
		LONG	max_value;
		ULONG	*scale_table;

		component_size = get_PX_USED( bm->image_px_format ) / get_PX_CMPNTS( bm->image_px_format );	// get the number of bits per component
		max_value = ( 1L << component_size ) - 1;								// maximum pixel component value

		scale_table = (ULONG *) GeAlloc(( max_value + 1 ) * sizeof( ULONG ));
		if ( scale_table )
		{
			settings->max_value = max_value;
			settings->scale_table = scale_table;
			change_scale_table( settings, settings->no_levels, TRUE );

			return FILTER_OK;																			// everything is fine
		}
		return FILTER_MEM_ERR;																	// missing memory
	}
	return FILTER_PARAM_ERR;																	// invalid parameters
}

//----------------------------------------------------------------------------------------
// End of a filter action
// Function result:		FILTER_OK or an error code
// bm:								bitmap reference
// settings:					private filter settings
//----------------------------------------------------------------------------------------
static LONG	filter_end( FI_REF fi, BM_REF bm, FILTER_PARAM _settings )
{
	PRIVATE_SETTINGS	*settings;

	settings = (PRIVATE_SETTINGS *) _settings;

	if ( settings && ( settings->magic == PRIVATE_SETTINGS_MAGIC ))
	{
		if ( settings->scale_table )
		{
			GeFree( settings->scale_table );											// free scale table
			settings->scale_table = 0;
		}	
		return FILTER_OK;
	}
	return FILTER_PARAM_ERR;
}

//----------------------------------------------------------------------------------------
// Run the filter action
// Function result:		FILTER_OK, FILTER_CANCEL, FILTER_ABORT or an error code
// bm:								bitmap reference
// settings:					private filter settings
// use_gui:						TRUE: show dialog FALSE: use current settings (silent mode)
//----------------------------------------------------------------------------------------
static LONG	filter_run( FI_REF fi, BM_REF bm, FILTER_PARAM _settings, Bool use_gui )
{
	PRIVATE_SETTINGS	*settings;

	settings = (PRIVATE_SETTINGS *) _settings;
	if ( use_gui )
	{
		ThresholdDialog dlg;

		if ( dlg.Init( bm, settings ))
		{
			if ( dlg.Open())																			// do the dialog handling
				return FILTER_OK;
		}

		return FILTER_CANCEL;
	}
	else																											// might be better to use threading here, too (depending on the filter speed)
		return apply_effect(bm,0,0,settings,FALSE,NULL);
}

//----------------------------------------------------------------------------------------
// Fill 1D scaling table for pixel components (adjusts gamma, contrast, brightness)
// Function result:		TRUE: new table content has been generated FALSE: no change
// settings:					private filter settings
// no_levels:					new no_levels value
// always:						TRUE: force new calculation FALSE: only if input values are different 
//----------------------------------------------------------------------------------------
static Bool	change_scale_table( PRIVATE_SETTINGS *settings, LONG no_levels, Bool always )
{
	if ( always || ( no_levels != settings->no_levels ))
	{
		LONG	i;
		LONG	max_value;
		ULONG	*scale_table;

		settings->no_levels = no_levels;
		scale_table = settings->scale_table;
		max_value = settings->max_value;												// max. pixel component value

		for ( i = 0; i <= max_value; i++ )											// calculate scaling table
		{
			LONG	value;

			value = i * no_levels;
			if ( value )
				value--;
			value /= max_value;
			value *= max_value;
			value /= ( no_levels - 1 );

			*scale_table++ = (ULONG) value;
		}
		return TRUE;																						// settings have been altered
	}
	return FALSE;																							// settings remain unchanged
}

//----------------------------------------------------------------------------------------
// Apply the effect to the whole bitmap and update the document view (if indicated)
// Function result:		FILTER_OK or error code
// bm:								bitmap reference
// src_rect:					requested source rectangle
// dt:								pointer of the preview destination tile or 0 (apply effect to the mask_rect area)
// settings:					private filter settings
// update_view:				TRUE: call BfUpdateView() for processed tiles
// thread:						pointer to thread class (0: no thread)
//----------------------------------------------------------------------------------------
static LONG	apply_effect( BM_REF bm, const RECT32 *src_rect, BM_TILE *dt, PRIVATE_SETTINGS *settings, Bool update_view, FilterThread *thread )
{
	LONG tile_width	 = bm->preferred_tile_width;
  LONG tile_height = bm->preferred_tile_height;
	LONG err = FILTER_CANCEL;

	PROGRESS_ID	pid;
	LONG 	progress_cnt;
	LONG 	progress_max;

	RECT32	rect;
	LONG		x;
	LONG		y;
	String	apply_filter_str;
	
	apply_filter_str = GeLoadString( IDS_APPLY_FILTER );

	if ( src_rect == 0 )
		src_rect = &bm->mask_rect;

	if ( dt )
		err = do_effect( bm, src_rect, dt, (PRIVATE_SETTINGS *) settings );
	else
	{
		rect = bm->mask_rect;
	
		progress_cnt = 0;
		progress_max = ((( rect.y2 + tile_height - 1 ) / tile_height ) - ( rect.y1 / tile_height )) *
		 							 ((( rect.x2 + tile_width - 1 ) / tile_width ) - ( rect.x1 / tile_width ));
		pid = BfProgressNew();
	
		for ( y = rect.y1 - ( rect.y1 % tile_height ); y < rect.y2; y += tile_height )
		{
			for ( x = rect.x1 - ( rect.x1 % tile_width ); x < rect.x2; x += tile_width )
			{
				RECT32	tile_rect;
				BM_TILE	*dst;
	
				if ( thread && thread->TestBreak())										// force exit?
				{
					err = FILTER_CANCEL;
					update_view = FALSE;
					goto exit_filter;
				}
	
				BfProgressUpdate( pid, progress_cnt++, progress_max, apply_filter_str );
	
				tile_rect.x1 = x;
				tile_rect.y1 = y;
				tile_rect.x2 = x + tile_width;
				tile_rect.y2 = y + tile_height;
				dst = BfBitmapTileGet( bm, &tile_rect, 0, 0, TILE_BM_WRITE, 0 );	// allocate destination tile
				
				if ( dst )
				{
					err = do_effect( bm, (RECT32 *) &dst->xmin, dst, settings );
					BfBitmapTileDetach( bm, dst, err == FILTER_OK );		// return destination tile and apply it (FILTER_OK)
					if ( err != FILTER_OK )
						goto exit_filter;
	
					if ( update_view )
						BfUpdateView( bm );																// update view
				}
			}
		}
	
	exit_filter:
	
		BfProgressDelete( pid );
	}	

	return err;
}

static LONG do_effect( BM_REF bm, const RECT32 *src_rect, BM_TILE *dst, PRIVATE_SETTINGS *settings )
{
	BM_TILE	*src;

	if (( bm == 0 ) || ( dst == 0 ) || ( settings == 0 ))
		return FILTER_MEM_ERR;

	src = BfBitmapTileGet( bm, src_rect, dst->xmax - dst->xmin, dst->ymax - dst->ymin, TILE_BM_READ_ONLY, 0 );
	if ( src )
	{
		ULONG		*scale_table;
		UCHAR	*srcptr;
		UCHAR	*dstptr;
		LONG x,y,w,inc;

		srcptr = (UCHAR *) src->addr;
		dstptr = (UCHAR *) dst->addr;
		w = src->xmax - src->xmin;
		inc = get_PX_CMPNTS( src->px_format );
		scale_table = settings->scale_table;

		for ( y = src->ymin; y < src->ymax; y++ )
		{
			for ( x = src->xmin; x < src->xmax; x++ )
			{
				LONG i=0;

				if ( bm->image_color_space & CSPACE_ALPHA_FLAG )
				{
					*dstptr++ = *srcptr++;
					i++;
				}

				for ( ; i < inc; i++ )
					*dstptr++ = scale_table[*srcptr++];
			}
		}
		BfBitmapTileDetach( bm, src, FALSE );
		return FILTER_OK;
	}
	return FILTER_MEM_ERR;
}

ThresholdDialog::ThresholdDialog(void)
{
	bm = NULL;
	real_time = BfpGetRealTimeFlag();
	document_preview = BfpGetDocumentPreviewFlag();
}

ThresholdDialog::~ThresholdDialog()
{
	if ( GetResult())																					// exit dialog with "OK"?
	{
		preview.FinishDocumentUpdate();													// wait until the effect has been applied
		BfpSetDocumentPreviewFlag( document_preview );
	}
}

Bool ThresholdDialog::Init( BM_REF _bm, PRIVATE_SETTINGS *_settings )
{
	if ( _bm )
	{
		bm = _bm;
		settings = _settings;
		return TRUE;
	}
	return FALSE;
}

Bool ThresholdDialog::CreateLayout(void)
{
	LoadDialogResource( DLG_THRESHOLD, NULL, 0 );
	preview.Create( this, GADGET_THRESHOLD_PREVIEWGROUP );
	preview.SetSource( bm );
	preview.SetDestination( bm, (FT_APPLY_EFFECT *) apply_effect, settings, TRUE, document_preview );

	return TRUE;
}

Bool ThresholdDialog::InitValues(void)
{
	SetLong( GADGET_THRESHOLD_DOCUMENT_PREVIEW, document_preview );
	SetReal( GADGET_THRESHOLD_SLIDER_LEVELS, settings->no_levels, MIN_LEVELS, MAX_LEVELS, STEP_LEVELS, FORMAT_REAL, 0.0, 0.0, TRUE );

	return TRUE;
}

Bool	ThresholdDialog::Command( LONG id, const BaseContainer &msg )
{
	Bool	update_settings = FALSE;
	LONG	no_levels = settings->no_levels;

	if ( preview.Command( id, msg ))
		return TRUE;

	switch ( id )
	{
		case	GADGET_THRESHOLD_SLIDER_LEVELS:
		{
			if ( real_time || ( msg.GetLong( BFM_ACTION_INDRAG ) == FALSE ))
			{
				no_levels = msg.GetLong( BFM_ACTION_VALUE );
				update_settings = TRUE;															// indicates changed effect settings
			}
			break;
		}
		case	GADGET_THRESHOLD_DOCUMENT_PREVIEW:
		{
			document_preview = msg.GetLong( BFM_ACTION_VALUE );
			preview.SetDocumentPreview( document_preview );
			break;
		}
	}

	if ( update_settings )
	{
		if ( change_scale_table( settings, no_levels, FALSE ))
		{
			preview.ChangedSettings();
			preview.Update();
		}
	}

	return TRUE;
}

