#include "c4d_general.h"
#include "c4d_shader.h"
#include "c4d_memory.h"
#include "c4d_resource.h"
#include "c4d_commandplugin.h"
#include "c4d_plugin.h"
#include "c4d_basebitmap.h"
#include "c4d_basecontainer.h"
#include "c4d_gui.h"
#include "customgui_bitmapbutton.h"

#include "x4d_filter.h"
#include "c4d_bitmapfilter.h"
#include "lib_iconcollection.h"

#include	"filterpreview.h"
#include	"filter_private_id.h"
#include	"filter_prefs.h"

#define	SCALE_ONE	4
#define	SCALE_MAX	12

static SReal	scale_factor[] =
{
	100.0 / 800.0,		// 800
	100.0 / 400.0,		// 400
	100.0 / 200.0,		// 200
	100.0 / 150.0,		// 150

	100.0 / 100.0,		// 100
	100.0 / 75.0,			// 75
	100.0 / 66.6,			// 66
	100.0 / 50.0,			// 50
	100.0 / 33.3,			// 33
	100.0 / 25.0,			// 25
	100.0 / 20.0,			// 20
	100.0 / 12.5,			// 12.5
};

//----------------------------------------------------------------------------------------
// private functions
//----------------------------------------------------------------------------------------
static Bool	is_bigger_than_life( BM_REF bm, const RECT32 *src_rect );
static void	get_clipped_size( BM_REF bm, const RECT32 *src_rect, const LONG w, const LONG h,  RECT32 *clipped_rect, LONG *clipped_w, LONG *clipped_h );
static BM_TILE	*get_preview_source( BM_REF bm, const RECT32 *src_rect, LONG w, LONG h, LONG tile_mode, LONG flags );
static LONG	paste_tile_into_bmp( BM_REF bm, BM_TILE *dt, BaseBitmap *bmp );
static void	darken_bmp( BM_REF bm, BaseBitmap *bmp );
static void	blend_line( UCHAR *dst, LONG y, LONG w, LONG l );

//----------------------------------------------------------------------------------------
// Apply checkboard pattern to bitmap tile
// Function result:		-
// tile:							bitmap tile
//----------------------------------------------------------------------------------------
inline UCHAR BLEND_WHITE( UCHAR v )	{ return ((( v << 2 ) - v + 0xffL ) >> 2 ); }
inline UCHAR BLEND_BLACK( UCHAR v ) { return ((( v << 2 ) - v + 0x80L ) >> 2 ); }

inline UWORD BLEND_WHITE( UWORD v )	{ return ((( v << 2 ) - v + (0xffL<<8) ) >> 2 ); }
inline UWORD BLEND_BLACK( UWORD v ) { return ((( v << 2 ) - v + (0x80L<<8) ) >> 2 ); }

inline SReal BLEND_WHITE( SReal v )	{ return ((( v * 4 ) - v + 0xffL/255.0 ) / 4 ); }
inline SReal BLEND_BLACK( SReal v ) { return ((( v * 4 ) - v + 0x80L/255.0 ) / 4 ); }

template <class T> void blend_pattern( BM_TILE *tile , T dummy )
{
#define	PAT_WIDTH		8
#define	PAT_HEIGHT	8

	T *dst,v;
	LONG	dst_offset,w,i,j,x,y,l,bytes;
	
	dst = (T *) tile->addr;
	w = ( tile->xmax - tile->xmin ) * get_PX_CMPNTS( tile->px_format );
	l = get_PX_CMPNTS( tile->px_format ) * PAT_WIDTH;
	bytes = get_PX_BITS(tile->px_format)/get_PX_CMPNTS( tile->px_format )/8;
	dst_offset = tile->width/bytes - w;
	
	for ( y = tile->ymax - tile->ymin; y > 0; )
	{
		for ( j = ( y > PAT_HEIGHT ? PAT_HEIGHT : y ); j > 0; j-- )
		{
			for ( x = w; x > 0; )
			{
				for ( i = ( x > l ? l : x ); i > 0; i-- )
				{
					v = *dst;
					*dst++ = BLEND_WHITE( v );
				}			
				x -= l;
	
				for ( i = ( x > l ? l : x ); i > 0; i-- )
				{
					v = *dst;
					*dst++ = BLEND_BLACK( v );
				}			
				x -= l;
			}
			dst += dst_offset;
		}
		y -= PAT_HEIGHT;

		for ( j = ( y > PAT_HEIGHT ? PAT_HEIGHT : y ); j > 0; j-- )
		{
			for ( x = w; x > 0; )
			{
				for ( i = ( x > l ? l : x ); i > 0; i-- )
				{
					v = *dst;
					*dst++ = BLEND_BLACK( v );
				}			
				x -= l;

				for ( i = ( x > l ? l : x ); i > 0; i-- )
				{
					v = *dst;
					*dst++ = BLEND_WHITE( v );
				}			
				x -= l;
			}
			dst += dst_offset;
		}
		y -= PAT_HEIGHT;
	}
#undef	PAT_HEIGHT
#undef	PAT_WIDTH
}

//----------------------------------------------------------------------------------------


PreviewBmp::PreviewBmp(void)
{
	BaseContainer	prefs;

	prefs = GetGlobalFilterPrefs( BF_PREFS_GLOBAL );

	bm = 0;																										// bitmap reference
	bmp_valid = FALSE;																				// contains no data
	offset_x = 0;																							// 0 means centered with respect to the selection area
	offset_y = 0;
	real_time = FALSE;
	down_scale = prefs.GetLong( BF_PREFS_PREVIEW_PERCENT, SCALE_ONE );	// ###NOTE: hier ist noch Gefrickel zwischen PreviewBmp und PreviewGroup. Bei Gelegenheit aufrŠumen 

	document_preview = FALSE;
	changed_settings = TRUE;
}

PreviewBmp::~PreviewBmp(void)
{
	preview_thread.End();																			// stop the preview preview_thread
}

void PreviewBmp::SetDragEffect( Bool showeffect )
{
	real_time = showeffect;
}

//----------------------------------------------------------------------------------------
// Limit offset_x/y (if necessary) and return the source rect for the current scale. The 
// source rect can be bigger than the source bitmap. In this case clipping has to be done
// before or when scaling the bitmap.
// Function result:		-
// r:									pointer to source rect (or zero if you only want to clip the offsets)
//----------------------------------------------------------------------------------------
void	PreviewBmp::ClipOffsets( RECT32 *r )
{
	LONG	image_width;
	LONG	image_height;
	LONG	w;
	LONG	h;
	LONG	w_scaled;
	LONG	h_scaled;
	LONG	tmp;
	RECT32	tmp_rect;

	if ( r == 0 )
		r = &tmp_rect;

	w = GetWidth();
	h = GetHeight();

	w_scaled = LONG(w * scale_factor[down_scale]);									// resize the source area according to the magnification
	h_scaled = LONG(h * scale_factor[down_scale]);

	image_width = bm->image_rect.x2 - bm->image_rect.x1;
	image_height = bm->image_rect.y2 - bm->image_rect.y1;

	r->x1 = ( bm->mask_rect.x2 + bm->mask_rect.x1 ) >> 1;			// center of the selection
	r->y1 = ( bm->mask_rect.y2 + bm->mask_rect.y1 ) >> 1;
	r->x1 += offset_x - ( w_scaled >> 1 );
	r->y1 += offset_y - ( h_scaled >> 1 );
	r->x2 = r->x1 + w_scaled;
	r->y2 = r->y1 + h_scaled;

	if ( image_width >= w_scaled )
	{
		if ( r->x1 < bm->image_rect.x1 )
		{
			tmp = bm->image_rect.x1 - r->x1;
			offset_x += tmp;
			r->x1 += tmp;
			r->x2 += tmp;
		}
		if ( r->x2 > bm->image_rect.x2 )
		{
			tmp = bm->image_rect.x2 - r->x2;
			offset_x += tmp;
			r->x1 += tmp;
			r->x2 += tmp;
		}
	}
	else																											// center horizontally
	{
		r->x1 -= offset_x;
		r->x2 -= offset_x;
		offset_x = 0;
	}

	if ( image_height >= h_scaled )
	{
		if ( r->y1 < bm->image_rect.y1 )
		{
			tmp = bm->image_rect.y1 - r->y1;
			offset_y += tmp;
			r->y1 += tmp;
			r->y2 += tmp;
		}
		if ( r->y2 > bm->image_rect.y2 )
		{
			tmp = bm->image_rect.y2 - r->y2;
			offset_y += tmp;
			r->y1 += tmp;
			r->y2 += tmp;
		}
	}
	else																										// center vertically
	{
		r->y1 -= offset_y;
		r->y2 -= offset_y;
		offset_y = 0;
	}
}

//----------------------------------------------------------------------------------------
// Update preview area (start preview thread, ...)
// Function result:		-
// do_effect:					used to disable the preview thread while dragging
//----------------------------------------------------------------------------------------
Bool PreviewBmp::UpdateBitmap( Bool do_effect )
{
	BM_TILE	*draw = NULL;
	Bool		updated;
	LONG		w;
	LONG		h;

	updated = FALSE;
	w = GetWidth();																						// at the beginning the gadget might have no size
	h = GetHeight();

	if ( bm && bmp && w && h )
	{
		RECT32	clipped_src_rect;
		RECT32	src_rect;
		LONG		clipped_w;
		LONG		clipped_h;
		LONG		dx;
		LONG		dy;

		ClipOffsets( &src_rect );

		if ( apply_effect )																			// apply the effect?
		{
			BM_TILE	*thread_tile = NULL;

			if ( real_time )
			{
				preview_thread.End();																// stop the current preview_thread

				get_clipped_size( bm, &src_rect, w, h, &clipped_src_rect, &clipped_w, &clipped_h );
				thread_tile = BfBitmapTileGet( bm, &clipped_src_rect, clipped_w, clipped_h, TILE_BM_SCRATCH, 0 );

				if ( thread_tile )
				{
					if ( clipped_w < w )															// special case for preview_effect/paste_tile_into_bmp?
					{
						dx = ( clipped_src_rect.x1 - src_rect.x1 ) * w / ( src_rect.x2 - src_rect.x1 );
						
						thread_tile->xmax -= dx + thread_tile->xmin;
						thread_tile->xmin = -dx;
					}
					
					if ( clipped_h < h )															// special case for preview_effect/paste_tile_into_bmp?
					{
						dy = ( clipped_src_rect.y1 - src_rect.y1 ) * h / ( src_rect.y2 - src_rect.y1 );
						
						thread_tile->ymax -= dy + thread_tile->ymin;
						thread_tile->ymin = -dy;
					}

					if ( preview_effect( bm, &clipped_src_rect, thread_tile, this, FALSE, 0 ) == FILTER_OK )
						updated = TRUE;
				}
			}
			else
			{
				preview_thread.End();																// stop the current preview_thread
				if ( changed_settings && bmp_valid && ( last_x == offset_x ) && ( last_y == offset_y ))
				{
					darken_bmp( bm, bmp );
					Redraw();
					updated = TRUE;
				}
				else																								// moving the preview area
				{
					draw = get_preview_source( bm, &src_rect, w, h, TILE_BM_READ_WRITE, 0 );
					if ( draw )
					{
						if ( bmp_valid )
						{
							switch (get_PX_DEPTH(bm->image_px_format))
							{
								case PX_DEPTH_8:	blend_pattern<UCHAR>( draw , 0); break;
								case PX_DEPTH_16:	blend_pattern<UWORD>( draw , 0); break;
								case PX_DEPTH_32:	blend_pattern<SReal>( draw , 0); break;
							}
						}
		
						if ( BfConvertTileToBasebmp( draw, NULL, bmp, 0 ))
						{
							if ( bmp_valid == FALSE )
								darken_bmp( bm, bmp );

							bmp_valid = TRUE;
							Redraw();
							updated = TRUE;
							last_x = offset_x;
							last_y = offset_y;
						}
						BfBitmapTileDetach( bm, draw, FALSE );
						draw = NULL;
					}
				}
					
				if ( updated && do_effect )
				{
					get_clipped_size( bm, &src_rect, w, h, &clipped_src_rect, &clipped_w, &clipped_h );

					thread_tile = BfBitmapTileGet( bm, &clipped_src_rect, clipped_w, clipped_h, TILE_BM_SCRATCH, 0 );
					if ( thread_tile )
					{
						if ( clipped_w < w )														// special case for preview_effect/paste_tile_into_bmp?
						{
							dx = ( clipped_src_rect.x1 - src_rect.x1 ) * w / ( src_rect.x2 - src_rect.x1 );
							
							thread_tile->xmax -= dx + thread_tile->xmin;
							thread_tile->xmin = -dx;
						}
						
						if ( clipped_h < h )														// special case for preview_effect/paste_tile_into_bmp?
						{
							dy = ( clipped_src_rect.y1 - src_rect.y1 ) * h / ( src_rect.y2 - src_rect.y1 );
							
							thread_tile->ymax -= dy + thread_tile->ymin;
							thread_tile->ymin = -dy;
						}

						if ( changed_settings )													// settings changed?
							document_thread.End();												// then stop the document thread

						preview_thread.Init( bm, &clipped_src_rect, thread_tile, (FT_APPLY_EFFECT *) preview_effect, (BM_FILTER_SETTINGS*)this, FALSE );
						preview_thread.Start( TRUE );										// this tile has to be released by the preview_thread
						updated = TRUE;
					}
				}
			}
		}
		else
		{
			draw = get_preview_source( bm, &src_rect, w, h, TILE_BM_READ_ONLY, 0 );
			if ( draw )
			{
				if ( BfConvertTileToBasebmp( draw, NULL, bmp, 0 ))
				{
					Redraw();
					updated = TRUE;
				}
				BfBitmapTileDetach( bm, draw, FALSE );
				draw = NULL;
			}
		}
	}
	return( updated );
}

//----------------------------------------------------------------------------------------
// Set bitmap, filter function, parameters and redraw flag for preview area
// Function result:		-
// t_bm:							bitmap reference (used for allocating and freeing tiles)
// _apply_effect:			filter function
// t_settings:				filter settings
// redraw:						TRUE: redraw preview area
//----------------------------------------------------------------------------------------
void PreviewBmp::SetBitmap( BM_REF t_bm, FT_APPLY_EFFECT *_apply_effect, BM_FILTER_SETTINGS *t_settings, Bool redraw )
{
	apply_effect = _apply_effect;
	bm = t_bm;
	settings = t_settings;

	if ( bm )
	{
		ClipOffsets( 0 );
	}
	else
	{
		offset_x = 0;
		offset_y = 0;
	}
	if ( redraw )
		UpdateBitmap(TRUE);
}

//----------------------------------------------------------------------------------------
// Set the new bitmap offset relative to the center
// Function result:		-
// x:									x offset
// y:									y offset
// do_effect:					FALSE: show bitmap without effect	TRUE: apply effect
// scale_xy:					FALSE: x and y are already scaled TRUE: apply scale factor
//----------------------------------------------------------------------------------------
void PreviewBmp::SetOffset( LONG x, LONG y, Bool do_effect, Bool scale_xy )
{
	if ( scale_xy )
	{
		x = LONG(x*scale_factor[down_scale]);
		y = LONG(y*scale_factor[down_scale]);
	}

	if (( offset_x != x ) || ( offset_y != y ) || do_effect )
	{
		if ( bm )
		{
			offset_x = x;
			offset_y = y;

			ClipOffsets( 0 );
			UpdateBitmap( do_effect );
		}
		else
		{
			offset_x = 0;
			offset_y = 0;
		}
	}
}

//----------------------------------------------------------------------------------------
// Decrease preview zoom
// Function result:		-
// do_effect:					TRUE: apply effect when redrawing
// redraw:						TRUE: redraw preview bitmap
//----------------------------------------------------------------------------------------
LONG	PreviewBmp::Shrink( Bool do_effect, Bool redraw )
{
	if ( bm )
	{
		if ( down_scale < SCALE_MAX - 1 )
		{
			down_scale++;
			ClipOffsets( 0 );
	
			if ( redraw )
				UpdateBitmap( do_effect );
		}
	}
	return((LONG) (( 100.0 / scale_factor[down_scale] ) + 0.5 ));
}

//----------------------------------------------------------------------------------------
// Increase preview zoom
// Function result:		-
// do_effect:					TRUE: apply effect when redrawing
// redraw:						TRUE: redraw preview bitmap
//----------------------------------------------------------------------------------------
LONG	PreviewBmp::Grow( Bool do_effect, Bool redraw )
{
	if ( down_scale > 0 )
		down_scale -= 1;

	if ( redraw )
		UpdateBitmap( do_effect );

	return((LONG) (( 100.0 / scale_factor[down_scale] ) + 0.5 ));
}

//----------------------------------------------------------------------------------------
// Return zoom index
// Function result:		zoom index
//----------------------------------------------------------------------------------------
LONG	PreviewBmp::GetScale( void )
{
	return( down_scale );
}

//----------------------------------------------------------------------------------------
// De/activate document preview
// Function result:		-
// document_preview:	TRUE: start document preview thread after bitmap preview has finished
//----------------------------------------------------------------------------------------
void	PreviewBmp::SetDocumentPreview( Bool _document_preview )
{
	preview_thread.WaitforCompletion();												// wait until preview object is done
	document_thread.End();																		// stop the document preview

	if ( _document_preview )
	{
		changed_settings = TRUE;
		document_preview = TRUE;

		preview_effect( bm, 0, 0, this, FALSE, 0 );							// no preview; just start up the document thread
	}	
	else
	{
		document_preview = FALSE;

		BfDiscardChanges( bm );																	// discard the previous filter result
		BfUpdateView( bm );																			// restore view
	}
}

Bool	PreviewBmp::GetMinSize(LONG &w,LONG &h)
{
	return FALSE;
}

Bool PreviewBmp::InitValues(void)
{
	UpdateBitmap(TRUE);

	return TRUE;
}

void PreviewBmp::DrawMsg(LONG x1,LONG y1,LONG x2,LONG y2, const BaseContainer &msg)
{
	// skip the redraw in case if focus change
	LONG reason = msg.GetLong(BFM_DRAW_REASON);
	if (reason==BFM_GOTFOCUS || reason==BFM_LOSTFOCUS) 
		return;

	OffScreenOn();
	if ( bm && bmp )
		DrawBitmap( bmp, 0, 0, bmp->GetBw(), bmp->GetBh(), 0, 0, bmp->GetBw(), bmp->GetBh(), 0 );
}

Bool PreviewBmp::InputEvent( const BaseContainer &msg )
{
	LONG dev = msg.GetLong( BFM_INPUT_DEVICE );
	LONG chn = msg.GetLong( BFM_INPUT_CHANNEL );

	if ( dev == BFM_INPUT_MOUSE )
	{
		BaseContainer action( BFM_ACTION );
		action.SetLong( BFM_ACTION_ID, GetId());
		action.SetLong( 'ACT1', offset_x );
		action.SetLong( 'ACT2', offset_y );

		if ( chn == BFM_INPUT_MOUSELEFT )
		{
			BaseContainer z;
			LONG mxn = 0,myn=0;
			LONG mx = msg.GetLong( BFM_INPUT_X );
			LONG my = msg.GetLong( BFM_INPUT_Y );
			Real	fraction_x = 0;
			Real	fraction_y = 0;
			
			while ( GetInputState( BFM_INPUT_MOUSE, BFM_INPUT_MOUSELEFT, z ))
			{
				if ( z.GetLong( BFM_INPUT_VALUE ) == 0 ) break;

				mxn = z.GetLong( BFM_INPUT_X );
				myn = z.GetLong( BFM_INPUT_Y );

				if ( mxn != mx || myn != my )
				{
					Real	ox;
					Real	oy;

					ox = fraction_x + (Real) offset_x + (( mx - mxn ) * scale_factor[down_scale] );
					oy = fraction_y + (Real) offset_y + (( my - myn ) * scale_factor[down_scale] );
					
					fraction_x = ox;
					fraction_y = oy;
					
					if ( ox < 0 )
						ox = ceil( ox );
					else
						ox = floor( ox );

					if ( oy < 0 )
						oy = ceil( oy );
					else
						oy = floor( oy );

					fraction_x -= ox;
					fraction_y -= oy;

					SetOffset((LONG)ox,(LONG)oy, real_time, FALSE );

					mx = mxn;
					my = myn;
					action.SetLong( 'ACT1', offset_x );
					action.SetLong( 'ACT2', offset_y );
					action.SetLong( BFM_ACTION_INDRAG, TRUE );
					SendParentMessage( action );
				}
			}
			UpdateBitmap( TRUE );
			action.SetLong( BFM_ACTION_INDRAG, FALSE );
			SendParentMessage( action );
		}
	}
	return TRUE;
}

LONG PreviewBmp::Message( const BaseContainer &msg, BaseContainer &result )
{
	switch (msg.GetId())
	{
		case BFM_GETCURSORINFO:
//			if (!CheckPoint(m)) return;
//			if (CfIsDisabled(TRUE)) return;

			result.SetId(1);
			result.SetLong(RESULT_CURSOR,MOUSE_MOVE_HAND);
			return TRUE;
	}
	return GeUserArea::Message(msg,result);
}

static Bool	is_bigger_than_life( BM_REF bm, const RECT32 *src_rect )
{
	if (( src_rect->x1 < bm->image_rect.x1 ) || ( src_rect->y1 < bm->image_rect.y1 ) ||
			( src_rect->x2 > bm->image_rect.x2 ) || ( src_rect->y2 > bm->image_rect.y2 ))
		return( TRUE );
	else
		return( FALSE );
}

static void	get_clipped_size( BM_REF bm, const RECT32 *src_rect, const LONG w, const LONG h,  RECT32 *clipped_rect, LONG *clipped_w, LONG *clipped_h )
{
	*clipped_rect = *src_rect;
	
	if ( is_bigger_than_life( bm, src_rect ))
	{
		LONG	src_w;
		LONG	src_h;

		src_w = src_rect->x2 - src_rect->x1;
		src_h = src_rect->y2 - src_rect->y1;
		
		if ( clipped_rect->x1 < bm->image_rect.x1 )
			clipped_rect->x1 = bm->image_rect.x1;
		
		if ( clipped_rect->y1 < bm->image_rect.y1 )
			clipped_rect->y1 = bm->image_rect.y1;
		
		if ( clipped_rect->x2 > bm->image_rect.x2 )
			clipped_rect->x2 = bm->image_rect.x2;
		
		if ( clipped_rect->y2 > bm->image_rect.y2 )
			clipped_rect->y2 = bm->image_rect.y2;
		
		*clipped_w = w * ( clipped_rect->x2 - clipped_rect->x1 ) / src_w;
		*clipped_h = h * ( clipped_rect->y2 - clipped_rect->y1 ) / src_h;
	}
	else
	{
		*clipped_w = w;
		*clipped_h = h;
	}
}

//----------------------------------------------------------------------------------------
// Convert BM_TILE to BaseBitmap. If the tile is smaller, clear the borders of the BaseBitmap
// Function result:		FILTER_OK or FILTER_MEM_ERR
// bm:								bitmap reference
// dt:								bitmap tile (might be smaller than the BaseBitmap)
// bmp:								base bitmap (must equal size or bigger than the bitmap tile)
//----------------------------------------------------------------------------------------
static LONG	paste_tile_into_bmp( BM_REF bm, BM_TILE *dt, BaseBitmap *bmp )
{
	LONG	bmp_w;
	LONG	bmp_h;
	LONG	dw;
	LONG	dh;

	bmp_w = bmp->GetBw();
	bmp_h = bmp->GetBh();
	dw = dt->xmax - dt->xmin;
	dh = dt->ymax - dt->ymin;

	if (( bmp_w > dw ) || ( bmp_h > dh ))											// is the tile smaller than the preview area?
	{
		BM_TILE	*scratch_tile;
		RECT32	scratch_rect;
		
		scratch_rect.x1 = 0;
		scratch_rect.y1 = 0;
		scratch_rect.x2 = bmp_w;
		scratch_rect.y2 = bmp_h;

		scratch_tile = BfBitmapTileGet( bm, &scratch_rect, bmp_w, bmp_h, TILE_BM_SCRATCH, 0 );
		if ( scratch_tile )																			// scratch tile allocated?
		{
			UCHAR	*src;
			UCHAR	*dst;
			ULONG	bits;
			
			ClearMem( scratch_tile->addr, scratch_tile->width * ( scratch_tile->ymax - scratch_tile->ymin ));

			bits = get_PX_BITS( scratch_tile->px_format );
			src = (UCHAR *) dt->addr;
			dst = (UCHAR *) scratch_tile->addr;
			if ( dt->ymin < 0 )
				dst += scratch_tile->width * (- dt->ymin );					// please note: if the preview area is higher than the tile, ymin contains the negative offset to the top of the preview area! 
			if ( dt->xmin < 0 )
				dst += ( bits * ( - dt->xmin )) >> 3;								// please note: if the preview area is wider than the tile, xmin contains the negative offset to the left of the preview area! 
			
			dw = ( dw * bits ) >> 3;															// number of bytes to copy

			for ( ; dh > 0; dh-- )
			{
				CopyMem( src, dst, dw );
				src += dt->width;			
				dst += scratch_tile->width;			
			}

			BfConvertTileToBasebmp( scratch_tile, NULL, bmp, 0 );
			BfBitmapTileDetach( bm, scratch_tile, FALSE );
		}
		else
			return( FILTER_MEM_ERR );
	}
	else																											// convert directly
		BfConvertTileToBasebmp( dt, NULL, bmp, 0 );

	return( FILTER_OK );
}

//----------------------------------------------------------------------------------------
// Return a preview tile. If the tile is smaller than the preview area, clear the borders of the tile
// Function result:		pointer to allocated tile
// bm:								bitmap reference
// src_rect:					source area
// w:									
// dt:								bitmap tile (might be smaller than the BaseBitmap)
// bmp:								base bitmap (must equal size or bigger than the bitmap tile)
//----------------------------------------------------------------------------------------
static BM_TILE	*get_preview_source( BM_REF bm, const RECT32 *src_rect, LONG w, LONG h, LONG tile_mode, LONG flags )
{
	BM_TILE	*result_tile = NULL;

	if ( is_bigger_than_life( bm, src_rect ) == FALSE )
		return( BfBitmapTileGet( bm, src_rect, w, h, tile_mode, flags ));

	result_tile = BfBitmapTileGet( bm, src_rect, w, h, TILE_BM_SCRATCH, 0 );
	if ( result_tile )
	{
		RECT32	clipped_src_rect;
		BM_TILE	*clipped_tile = NULL;
		LONG	clipped_w;
		LONG	clipped_h;
		
		get_clipped_size( bm, src_rect, w, h, &clipped_src_rect, &clipped_w, &clipped_h );
	
		clipped_tile = BfBitmapTileGet( bm, &clipped_src_rect, clipped_w, clipped_h, tile_mode, 0 );
		if ( clipped_tile )
		{
			UCHAR	*src;
			UCHAR	*dst;
			ULONG	dw;
			ULONG	dh;
			
			ClearMem( result_tile->addr, result_tile->width * ( result_tile->ymax - result_tile->ymin ));

			src = (UCHAR *) clipped_tile->addr;
			dst = (UCHAR *) result_tile->addr;
			dst += result_tile->width * ( clipped_tile->ymin - result_tile->ymin );
			dst += ( get_PX_BITS( result_tile->px_format ) * ( clipped_tile->xmin - result_tile->xmin )) >> 3;
			
			dw = ( get_PX_BITS( result_tile->px_format ) * ( clipped_tile->xmax - clipped_tile->xmin )) >> 3;

			for ( dh = clipped_tile->ymax - clipped_tile->ymin; dh > 0; dh-- )
			{
				CopyMem( src, dst, dw );
				src += clipped_tile->width;			
				dst += result_tile->width;			
			}
			BfBitmapTileDetach( bm, clipped_tile, FALSE );
		}
		else
		{
			BfBitmapTileDetach( bm, result_tile, FALSE );
			result_tile = 0;
		}
	}
	return( result_tile );
}

//----------------------------------------------------------------------------------------
// FT_APPLY_EFFECT: Preview function that applies the effect and stores the result in a destination tile
// After the dialog preview is finished the thread for the document preview is started.
// Function result:		FILTER_OK or FILTER_MEM_ERR
// bm:								bitmap reference
// src_rect:					source area the effect should be applied on
// dt:								destination for storing the preview result (dispose of it when finished) or zero (start document thread)
// pb:								preview bitmap class
// update_view:				should be FALSE
// preview_thread:		filter thread class or zero (not threaded)
//----------------------------------------------------------------------------------------
LONG	preview_effect( BM_REF bm, const RECT32 *src_rect, BM_TILE *dt, PreviewBmp *pb, Bool update_view, BaseThread *preview_thread )
{
	LONG	err;

	err = FILTER_OK;

	if ( dt )																									// redraw the preview object?
	{
		err = pb->apply_effect( bm, src_rect, dt, pb->settings, update_view, preview_thread );
		if ( err  == FILTER_OK )
		{
			err = paste_tile_into_bmp( bm, dt, pb->bmp );					// paste tile into the existing bitmap
			BfBitmapTileDetach( bm, dt, FALSE );

			if ( err == FILTER_OK )
				pb->Redraw( preview_thread != 0 );									// send a message if this is a thread (immediate redraw does not work in this case)
		}
		else																										// filter was canceled, free tile
			BfBitmapTileDetach( bm, dt, FALSE );
	}

	if ( err == FILTER_OK )
	{
		if ( pb->changed_settings )
		{
			pb->changed_settings = FALSE;													// changed settings have been noticed
			if ( pb->document_preview )														// start thread for document preview?
			{
				pb->document_thread.Init( bm, 0, 0, pb->apply_effect, pb->settings, TRUE );
				pb->document_thread.Start( TRUE );												
			}
		}
	}
	
	return( err );
}

//----------------------------------------------------------------------------------------
// Apply checkboard pattern to the preview bitmap
// Function result:		-
// bm:								bitmap reference
// bmp:								base bitmap
//----------------------------------------------------------------------------------------
static void	darken_bmp( BM_REF bm, BaseBitmap *bmp )
{
	LONG	w;
	LONG	h;
	LONG	bpp;
	LONG	bits;
	LONG	width;
	UCHAR	*line;
	
	w = bmp->GetBw();
	h = bmp->GetBh();
	bits = bmp->GetBt();																			// bits per pixel
	width = bmp->GetBpz();																		// bytes per line
	bpp = bits >> 3;																					// bytes per pixel

	if ( bpp > 3 )
		bpp = 3;																								// BaseBitmap is using only three bytes per pixel; even if it is saying something different

	if ( width < ( w * 3 ))
		width = w * 3;

	line = (UCHAR *) GeAlloc( width );												// but the line width has to take the extra bytes into account (otherwise GetLine overwrites memory)
	if ( line )
	{
		LONG	y;

		w *= bpp;																								// # of bytes to be blended
		for ( y = 0; y < h; y++ )
		{
			bmp->GetLine( y, line );
			blend_line( line, y, w, bpp << 3 );
			bmp->SetLine( y, line, bits );
		}
		GeFree( line );
	}
}

//----------------------------------------------------------------------------------------
// Blend a line with a checkboard pattern
// Function result:		-
// dst:								line pointer
// y:									y-coordinate of the line
// w:									width of the line in bytes
// l:									length of the light/dark area in bytes
//----------------------------------------------------------------------------------------
static void	blend_line( UCHAR *dst, LONG y, LONG w, LONG l )
{
#define	PAT_WIDTH		8
#define	PAT_HEIGHT	8
#define	BLEND_WHITE( v )	((( v << 3 ) - v + 0xc0L ) >> 3 )
#define	BLEND_BLACK( v )	((( v << 3 ) - v + 0xa0L ) >> 3 )

	LONG	i;
	LONG	x;
	LONG	v;
	
	if ( y & PAT_HEIGHT )
	{
		for ( x = w; x > 0; )
		{
			for ( i = ( x > l ? l : x ); i > 0; i-- )
			{
				v = *dst;
				*dst++ = (UCHAR) BLEND_BLACK( v );
			}			
			x -= l;
	
			for ( i = ( x > l ? l : x ); i > 0; i-- )
			{
				v = *dst;
				*dst++ = (UCHAR) BLEND_WHITE( v );
			}			
			x -= l;
		}
	}
	else
	{
		for ( x = w; x > 0; )
		{
			for ( i = ( x > l ? l : x ); i > 0; i-- )
			{
				v = *dst;
				*dst++ = (UCHAR) BLEND_WHITE( v );
			}			
			x -= l;
	
			for ( i = ( x > l ? l : x ); i > 0; i-- )
			{
				v = *dst;
				*dst++ = (UCHAR) BLEND_BLACK( v );
			}			
			x -= l;
		}
	}

#undef	BLEND_BLACK
#undef	BLEND_WHITE
#undef	PAT_HEIGHT
#undef	PAT_WIDTH
}

PreviewGroup::PreviewGroup( void )
{
	dlg = 0;
	src_preview_id = 0; 
	dst_preview_id = 0;
	zoom_in_id = 0;
	zoom_out_id = 0;
	zoom_percent_id = 0;
	real_time = FALSE;
}

PreviewGroup::~PreviewGroup( void )
{
	BaseContainer	prefs;

	prefs = GetGlobalFilterPrefs( BF_PREFS_GLOBAL );

	if ( src_preview_id )
		prefs.SetLong( BF_PREFS_PREVIEW_PERCENT, src_preview.GetScale());
	else if ( dst_preview_id )
		prefs.SetLong( BF_PREFS_PREVIEW_PERCENT, dst_preview.GetScale());

	SetGlobalFilterPrefs( BF_PREFS_GLOBAL, prefs );
}

//----------------------------------------------------------------------------------------
// Set preview source bitmap
// Function result:		-
//----------------------------------------------------------------------------------------
void	PreviewGroup::Create( GeDialog *_dlg, LONG _group_id )
{
	BaseContainer	prefs;
	LONG	w;
	LONG	h;
	LONG	scale;
	Bool	do_hlayout;

	prefs = GetGlobalFilterPrefs( BF_PREFS_GLOBAL );

	w = prefs.GetLong( BF_PREFS_PREVIEW_WIDTH, BF_PREFS_PREVIEW_WIDTH_DFLT );
	h = prefs.GetLong( BF_PREFS_PREVIEW_HEIGHT, BF_PREFS_PREVIEW_HEIGHT_DFLT );
	scale = LONG( ( 100.0 / scale_factor[prefs.GetLong( BF_PREFS_PREVIEW_PERCENT, SCALE_ONE )] ) + 0.5);
	real_time = prefs.GetLong( BF_PREFS_PREVIEW_REALTIME, BF_PREFS_PREVIEW_REALTIME_DFLT );

	dlg = _dlg;

	if ( prefs.GetLong( BF_PREFS_SRC_PREVIEW, BF_PREFS_SRC_PREVIEW_DFLT ))
		src_preview_id = GAGDET_BMFILTER_SRC; 
	dst_preview_id = GAGDET_BMFILTER_DST;
	zoom_in_id = GAGDET_BMFILTER_GROW;
	zoom_out_id = GAGDET_BMFILTER_SHRINK;
	zoom_percent_id = GAGDET_BMFILTER_PERCENT;

	dlg->LayoutFlushGroup( _group_id );

	if (( src_preview_id == 0 ) || ( dst_preview_id == 0 ))
		do_hlayout = FALSE;																			// zoom icons are below the preview
	else
		do_hlayout = TRUE;																			// zoom icons are between the previews

	dlg->GroupBegin( 0, BFH_CENTER | BFV_TOP, !do_hlayout, do_hlayout, String(), 0 );
	{
		dlg->GroupBorderSpace( 0, 0, 0, 16 );										// add at bottom border

		if ( src_preview_id )
			AddPreview( dlg, w, h, src_preview, src_preview_id );
		else if ( dst_preview_id )
			AddPreview( dlg, w, h, dst_preview, dst_preview_id );

		if ( zoom_in_id && zoom_out_id )
		{
			dlg->GroupBegin( 0, BFV_CENTER | BFV_CENTER, do_hlayout, !do_hlayout, String(), 0 );
			{
				BitmapButtonCustomGui *zoomin  = NULL;
				BitmapButtonCustomGui *zoomout = NULL;

				dlg->GroupBorderSpace( 4, 4, 4, 4 );								// some room for the icons
				{
					BaseContainer settings;
					settings.SetBool(BITMAPBUTTON_BUTTON,TRUE);
					settings.SetBool(BITMAPBUTTON_BORDER,BORDER_OUT);
					zoomin  = (BitmapButtonCustomGui*)dlg->AddCustomGui(zoom_in_id,CUSTOMGUI_BITMAPBUTTON,String(),BFH_FIT,0,0,settings);
				}
				dlg->AddStaticText( zoom_percent_id, BFH_CENTER, SizeChr( 60 ), 0, LongToString( scale ) + "\\u0020\\u0025", 0 );
				{
					BaseContainer settings;
					settings.SetBool(BITMAPBUTTON_BUTTON,TRUE);
					settings.SetBool(BITMAPBUTTON_BORDER,BORDER_OUT);
					zoomout = (BitmapButtonCustomGui*)dlg->AddCustomGui(zoom_out_id,CUSTOMGUI_BITMAPBUTTON,String(),BFH_FIT,0,0,settings);
				}

				if (zoomin) 
				{
					IconData dat;
					GetIcon(RESOURCEIMAGE_BITMAPFILTERPLUS,&dat);
					zoomin->SetImage(&dat);
				}
				if (zoomout) 
				{
					IconData dat;
					GetIcon(RESOURCEIMAGE_BITMAPFILTERMINUS,&dat);
					zoomout->SetImage(&dat);
				}
			}
			dlg->GroupEnd();
		}

		if ( src_preview_id && dst_preview_id )
			AddPreview( dlg, w, h, dst_preview, dst_preview_id );
	}
	dlg->GroupEnd();

	dlg->LayoutChanged( _group_id );
}

//----------------------------------------------------------------------------------------
// Set preview source bitmap
// Function result:		-
//----------------------------------------------------------------------------------------
void	PreviewGroup::SetSource( BM_REF bm, BM_FILTER_SETTINGS *settings, Bool redraw )
{
	src_preview.SetBitmap( bm, 0, settings, redraw );
	src_preview.SetDragEffect( TRUE );
}

//----------------------------------------------------------------------------------------
// Set preview filter bitmap and filter function 
// Function result:		-
//----------------------------------------------------------------------------------------
void	PreviewGroup::SetDestination( BM_REF bm, FT_APPLY_EFFECT *apply_effect, BM_FILTER_SETTINGS *settings, Bool redraw, Bool document_preview )
{
	dst_preview.document_preview = document_preview;
	dst_preview.changed_settings = TRUE;

	dst_preview.SetBitmap( bm, apply_effect, settings, redraw );
	dst_preview.SetDragEffect( real_time );
}

void	PreviewGroup::UpdateSource( Bool do_effect )
{
	src_preview.UpdateBitmap( do_effect );
}

void	PreviewGroup::UpdateDestination( Bool do_effect )
{
	dst_preview.UpdateBitmap( do_effect );
}

//----------------------------------------------------------------------------------------
// Decrease zoom level
// Function result:		-
//----------------------------------------------------------------------------------------
void	PreviewGroup::ZoomOut( void )
{
	LONG	zoom_value;

	zoom_value = 0;
	if ( src_preview_id )
		zoom_value = src_preview.Shrink( FALSE, TRUE );

	if ( dst_preview_id )
		zoom_value = dst_preview.Shrink( TRUE, TRUE );

	if ( zoom_value )
		dlg->SetString( zoom_percent_id, LongToString( zoom_value ) + "\\u0020\\u0025" );
}

//----------------------------------------------------------------------------------------
// Increase zoom level
// Function result:		-
//----------------------------------------------------------------------------------------
void	PreviewGroup::ZoomIn( void )
{
	LONG	zoom_value;

	zoom_value = 0;
	if ( src_preview_id )
		zoom_value = src_preview.Grow( FALSE, TRUE );

	if ( dst_preview_id )
		zoom_value = dst_preview.Grow( TRUE, TRUE );

	if ( zoom_value )
		dlg->SetString( zoom_percent_id, LongToString( zoom_value ) + "\\u0020\\u0025" );
}

//----------------------------------------------------------------------------------------
// Handle events regarding the preview group
// Function result:		FALSE: event was for other gadgets TRUE: event was handled
//----------------------------------------------------------------------------------------
Bool	PreviewGroup::Command( LONG id, const BaseContainer &msg )
{
	if ( id == src_preview_id )
		dst_preview.SetOffset( msg.GetLong( 'ACT1' ), msg.GetLong( 'ACT2' ), real_time || !msg.GetLong( BFM_ACTION_INDRAG ), FALSE );
	else if ( id == dst_preview_id )
		src_preview.SetOffset( msg.GetLong( 'ACT1' ), msg.GetLong( 'ACT2' ), TRUE, FALSE );
	else if ( id == zoom_in_id )
		ZoomIn();
	else if ( id == zoom_out_id )
		ZoomOut();
	else
		return( FALSE );

	return( TRUE );
}

void	PreviewGroup::AddPreview( GeDialog *dlg, LONG w, LONG h, PreviewBmp &preview, LONG preview_id )
{
	dlg->GroupBegin( 0, BFV_CENTER | BFV_CENTER, 1, 0, String(), 0 );
	{
		dlg->GroupBorderNoTitle( BORDER_THIN_IN );
		dlg->AddUserArea( preview_id, BFH_LEFT, SizePix( w ), SizePix( h ));
	}
	dlg->GroupEnd();

	dlg->AttachUserArea( preview, preview_id );
}

//----------------------------------------------------------------------------------------
// De/activate document preview
// Function result:		-
// document_preview:	TRUE: start document preview thread FALSE: stop document preview and discard results
//----------------------------------------------------------------------------------------
void	PreviewGroup::SetDocumentPreview( Bool document_preview )
{
	dst_preview.SetDocumentPreview( document_preview );
}

//----------------------------------------------------------------------------------------
// Stop preview and document thread because of changed filter settings
// Function result:		-
//----------------------------------------------------------------------------------------
void	PreviewGroup::ChangedSettings( void )
{
	dst_preview.preview_thread.End();
	dst_preview.document_thread.End();

	dst_preview.changed_settings = TRUE;											// set dirty flag
}

//----------------------------------------------------------------------------------------
// Update (destination) preview area (after filter settings have been changed)
// Function result:		-
//----------------------------------------------------------------------------------------
void	PreviewGroup::Update( void )
{
	dst_preview.UpdateBitmap( TRUE );
}

//----------------------------------------------------------------------------------------
// Wait until the effect has been applied to the document
// Function result:		-
//----------------------------------------------------------------------------------------
void	PreviewGroup::FinishDocumentUpdate( void )
{
	if ( dst_preview.document_preview == FALSE )							// document preview disabled?
	{
		dst_preview.preview_thread.End();												// stop redraw in the preview area
		dst_preview.SetDocumentPreview( TRUE );									// start document preview thread
	}
	else
	{
		if ( dst_preview.preview_thread.IsRunning())						// is the preview running
		{
			if ( dst_preview.changed_settings )										// update the document?
			{
				if ( dst_preview.document_thread.IsRunning() == FALSE )	// is the document thread not running so far?
				{
					dst_preview.preview_thread.End();									// stop the preview
					dst_preview.SetDocumentPreview( TRUE );						// start the document thread
				}
			}
		}
	}

	dst_preview.document_thread.WaitforCompletion();					// wait until the document thread has finished
}

