/*************************************************************************
 * FastSprite Example Code
 *
 * File: DrawImage.cpp
 *
 * This file contains the function to draw a sprite
 *
 * Author: Ashley Roll - ash@digitalnemesis.com
 *
 * Copyright (c) Digital Nemesis Pty Ltd, 1999. All Rights Reserved
 * Permission is granted to use and distribute this document and related
 * source code free of charge provided this and all
 * similar messages and copyrights in the related files remains intact.
 *************************************************************************/

#if !defined(_Image_H_INCLUDED_)
#include "Image.h"
#endif

bool Graphics::DrawImage( Image* img, int dx, int dy, int sx, int sy, int width, int height)
{
	UInt16*		pScreen;
	UInt16*		pData;
	Int16*		pCodes;
	UInt32		w, h;
	UInt16		visRunLen;	// visible portion length of run
	UInt16		runEnd;		// x value at end of run

	// actual iterator values for drawing and cliping the image
	UInt32		startDestX, startDestY;	// X & Y of starting point of destination rect
	UInt32		startSrcX, startSrcY;	// X & Y of start point in source rect
	UInt32		srcPitch;		// distance between rows in src (in UInt16s)
	UInt32		destPitch;		// distance between rows in destination (in UInt16s)
	UInt32		srcWidth, srcHeight;	// actual number of pixels to draw from the src
	UInt32		endSrcX, endSrcY;	// end X and Y values for the source rect

	// check if the image will be visible
	if( (dx + width) <= 0 )
		return true;	// not visible

	if( dx >= Width() )
		return true;	// not visible

	if( (dy + height) <= 0 )
		return true;	// not visible

	if( dy >= Height() )
		return true;	// not visible

	// adjust the start point on the destination to clip left and top
	if( dx < 0 ) {
		startDestX = 0;
		startSrcX = -dx + sx;
		srcWidth = width + dx;	// reduce the width to draw (dx is -ve)
	} else {
		startDestX = dx;
		startSrcX = sx;
		srcWidth = width;
	}
	if( dy < 0 ) {
		startDestY = 0;
		startSrcY = -dy + sy;
		srcHeight = height + dy;	// reduce the height to draw (dy is -ve)
	} else {
		startDestY = dy;
		startSrcY = sy;
		srcHeight = height;
	}

	// calculate the width to draw
	if( (startDestX + srcWidth) >= (UInt32)Width() )
		srcWidth = Width() - startDestX;

	// calculate the height to draw
	if( (startDestY + srcHeight) >= (UInt32)Height() )
		srcHeight = Height() - startDestY;

	// end source points
	endSrcX = startSrcX + srcWidth;
	endSrcY = startSrcY + srcHeight;

	// calculate the pitches for the source and destination
	srcPitch = img->Width() - srcWidth;
	destPitch = (GetScreenPitch()/sizeof(UInt16)) - srcWidth;

	// access the destination starting at the correct position
	pScreen = GetScreenPointer();
	if(pScreen == NULL)
		return false;

	pScreen += (startDestY * Width()) + startDestX;

	// access the source start at the correct position
	pData = img->Bits();
	pCodes = img->Codes();

	// is this a basic or a transparent image
	if( pCodes == NULL) {
		pData += (startSrcY * img->Width()) + startSrcX;

		for( h = 0; h < srcHeight; h++) {
			for( w = 0; w < srcWidth; w++) {
				*pScreen++ = *pData++;
			}
			// adjust the src and dest for the start of the next line
			pScreen += destPitch;
			pData += srcPitch;
		}
	} else {
		// as we are processing the codes, we need a counter to contain the
		// current value so we don't destroy the original
		Int16		curCodeVal;

		// initialise the width and height drawn. This is used to track
		// our current position in the source image
		w = 0;
		h = 0;
		curCodeVal = 0;

		// continue to process codes until we find two sequential zeros as this
		// marks the end of the bitmap
		while( ! (pCodes[0] == 0 && pCodes[1] == 0) ) {
			// fetch the current code and increment the
			// codes pointer to the next code.
			curCodeVal = *pCodes++;

			// is it a transparent or a normal code or end of line
			if( curCodeVal == 0 ) {
				// end of line
				h++;		// next "current" line
				w = 0;

				// test if we are beyond endSrcY. Stop drawing if we are
				if( h >= endSrcY )
					break;

				// step the screen pointer over to the begining of the next line
				// if we are actually drawing yet
				if( h > startSrcY )
					pScreen += destPitch;
			} else if( curCodeVal < 0 ) {
				// transparent pixel run
				curCodeVal = - curCodeVal;	// make for easier processing

				// is this line invisible? if so then we can discard this
				// transparent pixel run as it doesn't affect either the data
				// pointer or the screen pointer. No need to bother with
				// testing if we are beyond the endSrcY as this is done elsewhere
				if( h < startSrcY )
					continue;

				// if we are beyond the endSrcX width already, we can discard this
				// transparent pixel run as it doesn't affect either the data 
				// pointer or the screen pointer. 
				// We need to update the w position before we finish
				runEnd = w + curCodeVal;
				if( w > endSrcX ) {
					w = runEnd;
					continue;
				}

				// if the run will not take us into the visible area between
				// startSrcX and endSrcX, we can discard the transparent pixel run
				// We need to update the w position before we finish
				if( runEnd < startSrcX ) {
					w = runEnd;
					continue;
				}

				// work out the number of pixels in the run that are actually
				// visible between startSrcX and endSrcX
				visRunLen = curCodeVal;
				if( w < startSrcX )
					visRunLen -= startSrcX - w;		// clip before the start x
				
				if( runEnd > endSrcX )
					visRunLen -= runEnd - endSrcX;	// clip after the end x

				// visRunLen now contains the number of pixels we will actually
				// "draw" over.
				// These pixels are transparent, so we don't actually draw then, 
				// just update our pointers and other state info
				pScreen += visRunLen;
				w = runEnd;
			} else {
				// normal pixel run

				// is this line invisible? if so we can discard this pixel run 
				// but we MUST eat up the pixels in the data stream first
				if( h < startSrcY ) {
					// increment the data pointer over the number of pixel we would have drawn
					pData += curCodeVal;	
					continue;
				}

				// if we are beyond the endSrcX width already, we can discard this
				// pixel run after we eat up the pixel in the data stream
				// We need to update the w position before we finish
				runEnd = w + curCodeVal;
				if( w > endSrcX ) {
					// increment the data pointer over the number of pixel we would have drawn
					w = runEnd;
					pData += curCodeVal;	
					continue;
				}

				// if the run will not take us into the visible area between
				// startSrcX and endSrcX, we can discard the pixel run after 
				// eating up the pixels.
				// We need to update the w position before we finish
				if( runEnd < startSrcX ) {
					// increment the data pointer over the number of pixel we would have drawn
					w = runEnd;
					pData += curCodeVal;	
					continue;
				}

				// work out the number of pixels in the run that are actually
				// visible between startSrcX and endSrcX
				visRunLen = curCodeVal;
				if( w < startSrcX ) {
					visRunLen -= startSrcX - w;		// clip before the start x
					// skip the pixels that are invisible before startSrcX and
					// remember that we have already used up these pixels by
					// adjusting curCodeVal which is used later to skip data pixels
					// after endSrcX
					pData += startSrcX - w;
					curCodeVal -= startSrcX - w;
				}
				
				if( runEnd > endSrcX )
					visRunLen -= runEnd - endSrcX;	// clip after the end x

				// calculate the number of pixel that will remain undrawn from this
				// run after we draw the visible pixels so we can skip the data elements
				// after the draw. We will store this in curCodeVal
				curCodeVal -= visRunLen;

				// visRunLen now contains the number of pixels we will actually
				// "draw" over. pScreen points to the first pixel to be writen 
				// and pData points to the actual data, therefore we can simply 
				// copy visRunLen pixels then update the current w value.
				while( visRunLen > 0 ) {
					*pScreen++ = *pData++;
					visRunLen--;
				}

				// adjust the new width
				w = runEnd;
				
				// use up any undrawn pixels at the end of the run
				// we use curCodeVal here as we adjusted it if we skipped any
				// pixels before
				pData += curCodeVal;
			}
		}
	}

	// return success
	return true;
}
