#include	"scaleblt.h"
#include	"scale.h"
#include	"mergelin.h"
#include	"scalelin.h"

//----------------------------------------------------------------------------------------
// private functions
//----------------------------------------------------------------------------------------
static void	scaleblt_clip( SCALE_PARAM *p, LONG clip_xmin, LONG clip_ymin, LONG clip_xmax, LONG clip_ymax );
static void	do_scale_blt( SCALE_PARAM *scale_p,
													LONG src_x1, LONG src_y1, LONG src_x2, LONG src_y2,
													LONG dst_x1, LONG dst_y1, LONG dst_x2, LONG dst_y2 );
static LONG	scale_buf_alloc( SCALE_PARAM *p,
														 LONG src_x1, LONG src_x2,
														 LONG dst_x1, LONG dst_x2,
														 LONG shrink_vertical, BM_TILE *bm );
static void	scale_buf_free( SCALE_PARAM *p, ULONG *scale_buf );

//----------------------------------------------------------------------------------------
// Scaling BitBlt
// Function result:		BLT_OK, BLT_MEM_ERR or BLT_PX_FORMAT_ERR
// bfn:								memory allocation
// in_src_bm:					source bitmap
// in_dst_bm:					destination bitmap
// src_rect:					source rectangle
// dst_rect:					destination rectangle
// clip_rect:					clipping rectangle (for clipping the destination)
//----------------------------------------------------------------------------------------
LONG	scale_blt( BLT_MEM *bfn,
								 const BM_TILE *in_src_bm, const BM_TILE *in_dst_bm,
								 const RECT32 *src_rect, const RECT32 *dst_rect,
							 	 const RECT32 *clip_rect )
{
#define	src_bm				(scale_p.src_bm)
#define	scale_buf_bm	(scale_p.blt_p.src)
#define	dst_bm				(scale_p.blt_p.dst)
#define	sw						(scale_p.sw)
#define	sh						(scale_p.sh)
#define	dw						(scale_p.dw)
#define	dh						(scale_p.dh)
#define	merge_lines		(scale_p.merge_lines)
#define	scale_line		(scale_p.scale_line)

	SCALE_PARAM	scale_p;
	LONG	err;

	scale_p.bfn = bfn;

	src_bm = *in_src_bm;
	dst_bm = *in_dst_bm;

	scale_p.src_rect = src_rect;
	scale_p.dst_rect = dst_rect;
	scale_p.blt_p.dst_rect = dst_rect;

	sw = src_rect->x2 - 1 - src_rect->x1 + 1;									// error for step to the next destination pixel (= src width)
	sh = src_rect->y2 - 1 - src_rect->y1 + 1;									// error for step to the next destination line (= src height)

	dw = dst_rect->x2 - 1 - dst_rect->x1 + 1;									// error for step to the next source pixel (= dst width)
	dh = dst_rect->y2 - 1 - dst_rect->y1 + 1;									// error for step to the next source line (= dst height)

	scale_buf_bm = src_bm;
	merge_lines = (MERGE_LINES *)  0;

	if ( sh > dh )																						// shrink vertically?
	{
		LONG	used_bits;
		LONG	bits;

		bits = get_PX_BITS( scale_buf_bm.px_format );
		used_bits = get_PX_USED( scale_buf_bm.px_format );
		
		switch ((int) get_PX_CMPNTS( scale_buf_bm.px_format ))	// choose the right merging routine
		{
			case	1:
			{
				if ( bits < 8 )
					merge_lines = (MERGE_LINES *) merge_lines_px1_8;
				else if ( bits == 8 )
					merge_lines = (MERGE_LINES *) merge_lines_G8_G8;

				break;
			}
			case	2:
			{
				if ( bits == 16 )
				{
					merge_lines = (MERGE_LINES *) merge_lines_AB16_AB16;
					if ( scale_buf_bm.color_space & CSPACE_ALPHA_FLAG )
						merge_lines = (MERGE_LINES *) merge_lines_AG16_AG16;
				}
				break;
			}
			case	3:
			{
				if ( bits == 16 )
					merge_lines = (MERGE_LINES *) merge_lines_xRGB15_xRGB15;
				else if ( bits == 24 )
					merge_lines = (MERGE_LINES *) merge_lines_RGB24_RGB24;
				else if ( bits == 32 )
				{
					if ( used_bits == 24 )
						merge_lines = (MERGE_LINES *) merge_lines_xRGB24_xRGB24;
				}

				break;
			}
			case	4:
			{
				if ( bits == 32 )
				{
					merge_lines = (MERGE_LINES *) merge_lines_CMYK32_CMYK32;

					if ( scale_buf_bm.color_space & CSPACE_ALPHA_FLAG )
						merge_lines = (MERGE_LINES *) merge_lines_ARGB32_ARGB32;
				}				
				break;
			}
		}
		if ( merge_lines == (MERGE_LINES *) 0 )
			return( BLT_PX_FORMAT_ERR );
	}

	scale_line = (SCALE_LINE *) 0;
	if ( sw > dw )																						// shrink horizontally?
	{
		LONG	used_bits;
		LONG	bits;

		bits = get_PX_BITS( scale_buf_bm.px_format );
		used_bits = get_PX_USED( scale_buf_bm.px_format );
		
		switch ((int) get_PX_CMPNTS( scale_buf_bm.px_format ))
		{
			case	1:
			{
				if ( bits < 8 )
					scale_line = (SCALE_LINE *) shrink_line_px1_8;
				else if ( bits == 8 )
					scale_line = (SCALE_LINE *) shrink_line_A8;

				break;
			}
			case	2:
			{

				if ( bits == 16 )
				{
					scale_line = (SCALE_LINE *) shrink_line_AB16;
					if ( scale_buf_bm.color_space & CSPACE_ALPHA_FLAG )
						scale_line = (SCALE_LINE *) shrink_line_AG16;
				}
				break;
			}
			case	3:
			{
				if ( bits == 16 )
					scale_line = (SCALE_LINE *) shrink_line_xABC15;
				else if ( bits == 24 )
					scale_line = (SCALE_LINE *) shrink_line_ABC24;
				else if ( bits == 32 )
				{
					if ( used_bits == 24 )
						scale_line = (SCALE_LINE *) shrink_line_xABC24;
				}

				break;
			}
			case	4:
			{
				if ( bits == 32 )
				{
					scale_line = (SCALE_LINE *) shrink_line_ABCD32;
					if ( scale_buf_bm.color_space & CSPACE_ALPHA_FLAG )
						scale_line = (SCALE_LINE *) shrink_line_ARGB32;
				}
				break;
			}
		}
	}
	else
	{
		switch ((int) get_PX_BITS( scale_buf_bm.px_format ))
		{
			case	1:
			case	2:
			case	4:
			case	8:	scale_line = (SCALE_LINE *) grow_line_px1_8;	break;
			case	16:	scale_line = (SCALE_LINE *) grow_line_px16; break;
			case	24:	scale_line = (SCALE_LINE *) grow_line_px24; break;
			case	32:	scale_line = (SCALE_LINE *) grow_line_px32; break;
		}
	}

	if ( scale_line )																					// can we handle this pixel format?
	{
		if ( scale_buf_alloc( &scale_p,
													src_rect->x1 - src_bm.xmin, src_rect->x2 - src_bm.xmin - 1,
													dst_rect->x1 - dst_bm.xmin, dst_rect->x2 - dst_bm.xmin - 1,
													sh > dh, (BM_TILE *) &scale_buf_bm ))
		{
			if ( blt_init( &scale_p.blt_p, bfn, T_LOGIC_COPY ))
			{
				scaleblt_clip( &scale_p, clip_rect->x1, clip_rect->y1, clip_rect->x2, clip_rect->y2 );
				err = BLT_OK;
				
				blt_reset( &scale_p.blt_p, bfn );
			}
			else
				err = BLT_MEM_ERR;

			scale_buf_free( &scale_p, (ULONG *) scale_buf_bm.addr );
		}
		else
			err = BLT_MEM_ERR;
	}
	else
		err = BLT_PX_FORMAT_ERR;
		
	return( err );

#undef	scale_line
#undef	merge_lines
#undef	dh
#undef	dw
#undef	sh
#undef	sw
#undef	dst_bm
#undef	scale_buf_bm
#undef	src_bm
}

//----------------------------------------------------------------------------------------
// Clip source and destination coordinates according to clip_xmin/ymin/xmax/ymax
// and call the blt function
// Function result:		-
// p:									context
// clip_xmin:					.
// clip_ymin:					.
// clip_xmax:					.
// clip_ymax:					clipping rectangle (continous coordinates)
//----------------------------------------------------------------------------------------
static void	scaleblt_clip( SCALE_PARAM *p, LONG clip_xmin, LONG clip_ymin, LONG clip_xmax, LONG clip_ymax )
{
	LONG	src_x1;
	LONG	src_y1;
	LONG	src_x2;
	LONG	src_y2;
	LONG	dst_x1;
	LONG	dst_y1;
	LONG	dst_x2;
	LONG	dst_y2;
	LONG	src_width;
	LONG	src_height;
	LONG	dst_width;
	LONG	dst_height;
	LONG	offset;
	LONG	xmin_round;
	LONG	ymin_round;
	LONG	xmax_round;
	LONG	ymax_round;

	src_x1 = p->src_rect->x1;
	src_y1 = p->src_rect->y1;
	src_x2 = p->src_rect->x2;
	src_y2 = p->src_rect->y2;

	dst_x1 = p->dst_rect->x1;
	dst_y1 = p->dst_rect->y1;
	dst_x2 = p->dst_rect->x2;
	dst_y2 = p->dst_rect->y2;

	src_width = src_x2 - src_x1;															// source width
	src_height = src_y2 - src_y1;
	dst_width = dst_x2 - dst_x1;
	dst_height = dst_y2 - dst_y1;

	src_x2--;																									// discrete x2
	src_y2--;
	dst_x2--;
	dst_y2--;

	xmin_round = 0;
	ymin_round = 0;
	xmax_round = 0;
	ymax_round = 0;

	if ( dst_width < src_width )
	{
		xmin_round = dst_width - 1;															// dst_dx
		xmax_round = src_width - 1;															// src_dx
	}
	if ( dst_height < src_height )
	{
		ymin_round = dst_height - 1;														// dst_dy
		ymax_round = src_height - 1;														// src_dy
	}

// clip dst_x2 and dst_y2 before dst_x1 and dst_y1
// because the unclipped src_x1, src_y1, dst_x1 and 
// dst_y1 are needed

	if ( dst_x2 >= clip_xmax )
	{
		offset = clip_xmax - 1 - dst_x1;
		if ( offset < 0 )																				// not visible?
			return;
		offset *= src_width;																		// ###NOTE: 64 bits are necessary if the bitmap width exceeds 32K pixels
		offset += xmax_round;																		// shrinking: xmax_round = src_dx else: xmax_round = 0
		src_x2 = src_x1 + ( offset / dst_width );
		dst_x2 = clip_xmax - 1;																	// discrete coordinate
	}

	if ( dst_y2 >= clip_ymax )
	{
		offset = clip_ymax - 1 - dst_y1;
		if ( offset < 0 )																				// not visible?
			return;
		offset *= src_height;																		// ###NOTE: 64 bits are necessary if the bitmap height exceeds 32K pixels
		offset += ymax_round;
		src_y2 = src_y1 + ( offset / dst_height );
		dst_y2 = clip_ymax - 1;
	}

	if ( dst_x1 < clip_xmin )
	{
		offset = clip_xmin - dst_x1;
		offset *= src_width;																		// ###NOTE: 64 bits are necessary if the bitmap width exceeds 32K pixels
		offset += xmin_round;
		src_x1 += offset / dst_width;
		dst_x1 = clip_xmin;
	}

	if ( dst_y1 < clip_ymin )
	{
		offset = clip_ymin - dst_y1;
		offset *= src_height;																		// ###NOTE: 64 bits are necessary if the bitmap height exceeds 32K pixels
		offset += ymin_round;
		src_y1 += offset / dst_height;
		dst_y1 = clip_ymin;
	}

	if ( dst_x1 > dst_x2 )																		// not visible?
		return;
	if ( dst_y1 > dst_y2 )
		return;

	do_scale_blt( p, src_x1, src_y1, src_x2, src_y2, dst_x1, dst_y1, dst_x2, dst_y2 );
}

//----------------------------------------------------------------------------------------
// Scaling BitBlt, vertical action
// Function result:		-
// scale_p:						context
// src_...						clipped discrete source coordinates
// dst_...						clipped discrete destination coordinates
//----------------------------------------------------------------------------------------
static void	do_scale_blt( SCALE_PARAM *scale_p,
													LONG src_x1, LONG src_y1, LONG src_x2, LONG src_y2,
													LONG dst_x1, LONG dst_y1, LONG dst_x2, LONG dst_y2 )
{
#define	src_bm				(&scale_p->src_bm)
#define	scale_buf_bm	(&scale_p->blt_p.src)
#define	dst_bm				(&scale_p->blt_p.dst)
#define	merge_lines		(scale_p->merge_lines)
#define	scale_line		(scale_p->scale_line)

	BLT_LINE	*blt_line;
	ULONG	*src;
	LONG	src_bit_offset;
	LONG	scale_bit_offset;
	LONG	merge_bit_offset;
	LONG	offset;

	LONG	hcnt;
	LONG	he;
	LONG	sw;
	LONG	dw;

	LONG	vcnt;
	LONG	ve;
	LONG	sh;
	LONG	dh;

	(void) src_y2;																						// not used (because of sh)

	sw = scale_p->sw;																					// error for step to the next destination pixel (= src width)
	sh = scale_p->sh;																					// error for step to the next destination line (= src height)

	dw = scale_p->dw;																					// error for step to the next source pixel (= dst width)
	dh = scale_p->dh;																					// error for step to the next source line (= dst height)

	hcnt = dst_x2 - dst_x1;
	vcnt = dst_y2 - dst_y1;

	src_bit_offset = ( src_x1 - src_bm->xmin ) * get_PX_BITS( src_bm->px_format );
	scale_bit_offset = ( dst_x1 - dst_bm->xmin ) * get_PX_BITS( scale_buf_bm->px_format );
	merge_bit_offset = ( src_x1 - src_bm->xmin ) * get_PX_BITS( scale_buf_bm->px_format );
	merge_bit_offset &= 31;
	scale_bit_offset &= 31;

	scale_p->blt_p.wr_mode |= T_PREPARELINE;									// prepare blitting single lines
	blt_line = bitblt( &scale_p->blt_p,
										 scale_bit_offset * get_PX_BITS( scale_buf_bm->px_format ), 0, 0, 0,
										 dst_x1, dst_y1, dst_x2, dst_y2 );

	src = (ULONG *)((UCHAR *) src_bm->addr + ( src_bm->width * ( src_y1 - src_bm->ymin )));
	src += src_bit_offset >> 5;
	src_bit_offset &= 31;
	
// get start value for horizontal scaling
	he = 0;

	offset = src_x1 - scale_p->src_rect->x1;
	if ( offset )																							// is x source clipped?
		he += offset * dw;																			// adjust discriminator
		
	offset = dst_x1 - scale_p->dst_rect->x1;
	if ( offset )																							// is x destination clipped?
		he -= offset * sw;																			// adjust discriminator

	if ( sw > dw )																						// shrink?
		he -= sw;
	else																											// grow
	{
		he = - he;
		he -= dw;
	}

// get start value for vertical scaling
	ve = 0;

	offset = src_y1 - scale_p->src_rect->y1;
	if ( offset )																							// is y source clipped?
		ve +=  offset * dh;																			// adjust discriminator
		
	offset = dst_y1 - scale_p->dst_rect->y1;
	if ( offset )																							// is y destination clipped?
		ve -= offset * sh;																			// adjust discriminator

	if ( sh > dh )																						// shrink?
	{
		LONG	src_cnt;
		LONG	buf_cnt;
		LONG	src_line_offset;

		src_cnt = ((( src_x2 + 1 ) * (LONG) get_PX_BITS( src_bm->px_format )) + 31 ) >> 5;
		src_cnt -= ( src_x1 * (LONG) get_PX_BITS( src_bm->px_format )) >> 5;
		src_cnt -= 1;																						// # of source words - 1 between src_x1 and src_x2

		buf_cnt = ((( src_x2 + 1 ) * (LONG) get_PX_BITS( scale_buf_bm->px_format )) + 31 ) >> 5;
		buf_cnt -= ( src_x1 * (LONG) get_PX_BITS( scale_buf_bm->px_format )) >> 5;
		buf_cnt -= 1;

		src_line_offset = src_bm->width - (( src_cnt + 1 ) << 2 );

		ve -= sh;

		while ( vcnt >= 0 )
		{
			LONG	line_cnt;
			ULONG	*merge_addr;

			line_cnt  = 0;

			while ( ve < 0 )																			// determine # of source lines
			{
				line_cnt++;
				ve += dh;
			}

			merge_addr = merge_lines( line_cnt - 1, buf_cnt,
															  get_PX_BITS( src_bm->px_format ), src_bit_offset, src_line_offset, scale_buf_bm->width,
															  src, (ULONG *) scale_buf_bm->addr );

			scale_line( he, sw, dw, hcnt,
								  get_PX_BITS( scale_buf_bm->px_format ), merge_bit_offset, scale_bit_offset,
									merge_addr, (ULONG *) scale_buf_bm->addr );

			bltline_incdst( &scale_p->blt_p, blt_line );					// output of the scaled line

			src = (ULONG *) ((UCHAR *) src + ( line_cnt * src_bm->width ));
			
			ve -= sh;																							// adjust discriminator
			vcnt--;
		}
	}
	else
	{
		ve = - ve;
		ve -= dh;

		scale_line( he, sw, dw, hcnt,
					 			get_PX_BITS( src_bm->px_format ), src_bit_offset, scale_bit_offset,
								src, (ULONG *) scale_buf_bm->addr );

		while ( vcnt >= 0 )
		{
			bltline_incdst( &scale_p->blt_p, blt_line );					// output of the scaled line

			ve += sh;																							// adjust discriminator
			 
			if ( ve >= 0 )																				// next source line?
			{
				src = (ULONG *) ((UCHAR *) src + src_bm->width );
				ve -= dh;																						// adjust discriminator

				if ( vcnt > 0 )
				{
					scale_line( he, sw, dw, hcnt,
										  get_PX_BITS( src_bm->px_format ), src_bit_offset, scale_bit_offset,
											src, (ULONG *) scale_buf_bm->addr );
				}
			}
			vcnt--;
		}
	}

#undef	scale_line
#undef	merge_lines
#undef	dst_bm
#undef	scale_buf_bm
#undef	src_bm
}

//----------------------------------------------------------------------------------------
// Allocate a buffer for merge_lines() and scale_line()
// Function result:		TRUE/FALSE
// p:									context
// src_x1:						left discrete x coordinate (source)
// src_x2:						right discrete x coordinate (source)
// dst_x1:						left discrete x coordinate (destination)
// dst_x2:						right discrete x coordinate (destination)
// shrink_vertical:		TRUE: additional space for merging the lines is needed
// bm:								structure for the buffer
//----------------------------------------------------------------------------------------
static LONG	scale_buf_alloc( SCALE_PARAM *p, 
										  LONG src_x1, LONG src_x2,
										  LONG dst_x1, LONG dst_x2,
										  LONG shrink_vertical, BM_TILE *bm )
{
	LONG	len;

	len = ((( dst_x2 + 1 ) * get_PX_BITS( bm->px_format )) + 31 ) >> 5;
	len -= (( dst_x1 * get_PX_BITS( bm->px_format )) >> 5 );

	if ( shrink_vertical )																		// merge_lines?
	{
		LONG	src_cnt;

		src_cnt = ( src_x2 + 32 - ( src_x1 & ~31 )) & ~31;
		len = 2 * src_cnt;																			// for up to four components width 8 bit per component (merging needs 16 bit per component)
		if ( bm->color_space & CSPACE_ALPHA_FLAG )
			len += len;																						// with alpha we need 32 bit for merging
	}

	len <<= 2;																								// length in bytes

	bm->addr = (UCHAR *) p->bfn->malloc( len );
	bm->width = len;
	bm->xmin = 0;
	bm->ymin = 0;
	bm->xmax = ( len << 3 ) / get_PX_BITS( bm->px_format );
	bm->ymax = 1;

	return( bm->addr != 0 );
}

//----------------------------------------------------------------------------------------
// Free scale buffer
// Function result:		-
// p:									context
// scale_buf:					pointer to the buffer
//----------------------------------------------------------------------------------------
static void	scale_buf_free( SCALE_PARAM *p, ULONG *scale_buf )
{
	if ( scale_buf )
		p->bfn->mfree( scale_buf );
}
