#include "global.h"
#include "RageSurfaceUtils.h"
#include "RageSurface.h"
#include "RageUtil.h"
#include "RageLog.h"
#include "RageFile.h"

uint32_t RageSurfaceUtils::decodepixel( const uint8_t *p, int bpp )
{
    switch(bpp)
	{
    case 1: return *p;
    case 2: return *(uint16_t *)p;
	case 3:
        if( BYTE_ORDER == BIG_ENDIAN )
            return p[0] << 16 | p[1] << 8 | p[2];
        else
            return p[0] | p[1] << 8 | p[2] << 16;

    case 4: return *(uint32_t *)p;
    default: return 0;       /* shouldn't happen, but avoids warnings */
    }
}

void RageSurfaceUtils::encodepixel( uint8_t *p, int bpp, uint32_t pixel )
{
    switch(bpp)
	{
    case 1: *p = uint8_t(pixel); break;
    case 2: *(uint16_t *)p = uint16_t(pixel); break;
    case 3:
        if( BYTE_ORDER == BIG_ENDIAN )
		{
            p[0] = uint8_t((pixel >> 16) & 0xff);
            p[1] = uint8_t((pixel >> 8) & 0xff);
            p[2] = uint8_t(pixel & 0xff);
        } else {
            p[0] = uint8_t(pixel & 0xff);
            p[1] = uint8_t((pixel >> 8) & 0xff);
            p[2] = uint8_t((pixel >> 16) & 0xff);
        }
        break;
    case 4: *(uint32_t *)p = pixel; break;
    }
}

/* Get and set colors without scaling to 0..255. */
void RageSurfaceUtils::GetRawRGBAV( uint32_t pixel, const RageSurfaceFormat &fmt, uint8_t *v )
{
	if( fmt.BytesPerPixel == 1 )
	{
		v[0] = fmt.palette->colors[pixel].r;
		v[1] = fmt.palette->colors[pixel].g;
		v[2] = fmt.palette->colors[pixel].b;
		v[3] = fmt.palette->colors[pixel].a;
	} else {
		v[0] = uint8_t((pixel & fmt.Rmask) >> fmt.Rshift);
		v[1] = uint8_t((pixel & fmt.Gmask) >> fmt.Gshift);
		v[2] = uint8_t((pixel & fmt.Bmask) >> fmt.Bshift);
		v[3] = uint8_t((pixel & fmt.Amask) >> fmt.Ashift);
	}
}

void RageSurfaceUtils::GetRawRGBAV( const uint8_t *p, const RageSurfaceFormat &fmt, uint8_t *v )
{
	uint32_t pixel = decodepixel( p, fmt.BytesPerPixel );
	GetRawRGBAV( pixel, fmt, v );
}

void RageSurfaceUtils::GetRGBAV( uint32_t pixel, const RageSurface *src, uint8_t *v )
{
	GetRawRGBAV(pixel, src->fmt, v);
	const RageSurfaceFormat *fmt = src->format;
	for( int c = 0; c < 4; ++c )
		v[c] = v[c] << fmt->Loss[c];

	// Correct for surfaces that don't have an alpha channel.
	if( fmt->Loss[3] == 8 )
		v[3] = 255;
}

void RageSurfaceUtils::GetRGBAV( const uint8_t *p, const RageSurface *src, uint8_t *v )
{
	uint32_t pixel = decodepixel(p, src->format->BytesPerPixel);
	if( src->format->BytesPerPixel == 1 ) // paletted
	{
		memcpy( v, &src->format->palette->colors[pixel], sizeof(RageSurfaceColor));
	}
	else	// RGBA
		GetRGBAV(pixel, src, v);	
}


/* Inverse of GetRawRGBAV. */
uint32_t RageSurfaceUtils::SetRawRGBAV( const RageSurfaceFormat *fmt, const uint8_t *v )
{
	return 	v[0] << fmt->Rshift |
			v[1] << fmt->Gshift |
			v[2] << fmt->Bshift |
			v[3] << fmt->Ashift;
}

void RageSurfaceUtils::SetRawRGBAV( uint8_t *p, const RageSurface *src, const uint8_t *v )
{
	uint32_t pixel = SetRawRGBAV(src->format, v);
	encodepixel(p, src->format->BytesPerPixel, pixel);
}

/* Inverse of GetRGBAV. */
uint32_t RageSurfaceUtils::SetRGBAV( const RageSurfaceFormat *fmt, const uint8_t *v )
{
	return 	(v[0] >> fmt->Loss[0]) << fmt->Shift[0] |
			(v[1] >> fmt->Loss[1]) << fmt->Shift[1] |
			(v[2] >> fmt->Loss[2]) << fmt->Shift[2] |
			(v[3] >> fmt->Loss[3]) << fmt->Shift[3];
}

void RageSurfaceUtils::SetRGBAV( uint8_t *p, const RageSurface *src, const uint8_t *v )
{
	uint32_t pixel = SetRGBAV(src->format, v);
	encodepixel(p, src->format->BytesPerPixel, pixel);
}


void RageSurfaceUtils::GetBitsPerChannel( const RageSurfaceFormat *fmt, uint32_t bits[4] )
{
	/* The actual bits stored in each color is 8-loss.  */
	for( int c = 0; c < 4; ++c )
		bits[c] = 8 - fmt->Loss[c];
}

void RageSurfaceUtils::CopySurface( const RageSurface *src, RageSurface *dest )
{
	/* Copy the palette, if we have one. */
	if( src->format->BitsPerPixel == 8 && dest->format->BitsPerPixel == 8 )
	{
		ASSERT( dest->fmt.palette );
		*dest->fmt.palette = *src->fmt.palette;
	}

	Blit( src, dest, -1, -1 );
}

bool RageSurfaceUtils::ConvertSurface( RageSurface *src, RageSurface *&dst,
		int width, int height, int bpp,
		uint32_t R, uint32_t G, uint32_t B, uint32_t A )
{
    dst = CreateSurface( width, height, bpp, R, G, B, A );

	/* If the formats are the same, no conversion is needed.  Ignore the palette. */
	if( width == src->w && height == src->h && src->format->Equivalent( *dst->format ) )
	{
		delete dst;
		dst = NULL;
		return false;
	}

	CopySurface( src, dst );
	return true;
}

void RageSurfaceUtils::ConvertSurface(RageSurface *&image,
		int width, int height, int bpp,
		uint32_t R, uint32_t G, uint32_t B, uint32_t A)
{
    RageSurface *ret_image;
	if( !ConvertSurface( image, ret_image, width, height, bpp, R, G, B, A ) )
		return;

	delete image;
	image = ret_image;
}


/* Local helper for FixHiddenAlpha. */
static void FindAlphaRGB(const RageSurface *img, uint8_t &r, uint8_t &g, uint8_t &b, bool reverse)
{
	r = g = b = 0;
	
	/* If we have no alpha, there's no alpha color. */
	if( img->format->BitsPerPixel > 8 && !img->format->Amask )
		return;

	/* Eww.  Sorry.  Iterate front-to-back or in reverse. */
	for(int y = reverse? img->h-1:0;
		reverse? (y >=0):(y < img->h); reverse? (--y):(++y))
	{
		uint8_t *row = (uint8_t *)img->pixels + img->pitch*y;
		if(reverse)
			row += img->format->BytesPerPixel * (img->w-1);

		for(int x = 0; x < img->w; ++x)
		{
			uint32_t val = RageSurfaceUtils::decodepixel(row, img->format->BytesPerPixel);
			if( img->format->BitsPerPixel == 8 )
			{
				if( img->format->palette->colors[val].a )
				{
					/* This color isn't fully transparent, so grab it. */
					r = img->format->palette->colors[val].r;
					g = img->format->palette->colors[val].g;
					b = img->format->palette->colors[val].b;
					return;
				}
			}
			else
			{
				if( val & img->format->Amask )
				{
					/* This color isn't fully transparent, so grab it. */
					img->format->GetRGB( val, &r, &g, &b );
					return;
				}
			}

			if( reverse )
				row -= img->format->BytesPerPixel;
			else
				row += img->format->BytesPerPixel;
		}
	}

	/* Huh?  The image is completely transparent. */
	r = g = b = 0;
}

/* Local helper for FixHiddenAlpha. Set the underlying RGB values of all pixels in
 * img that are completely transparent. */
static void SetAlphaRGB(const RageSurface *img, uint8_t r, uint8_t g, uint8_t b)
{
	/* If it's a paletted surface, all we have to do is change the palette. */
	if( img->format->BitsPerPixel == 8 )
	{
		for( int c = 0; c < img->format->palette->ncolors; ++c )
		{
			if( img->format->palette->colors[c].a )
				continue;
			img->format->palette->colors[c].r = r;
			img->format->palette->colors[c].g = g;
			img->format->palette->colors[c].b = b;
		}
		return;
	}


	/* If it's RGBA and there's no alpha channel, we have nothing to do. */
	if( img->format->BitsPerPixel > 8 && !img->format->Amask )
		return;

	uint32_t trans;
	img->format->MapRGBA( r, g, b, 0, trans );
	for( int y = 0; y < img->h; ++y )
	{
		uint8_t *row = img->pixels + img->pitch*y;

		for( int x = 0; x < img->w; ++x )
		{
			uint32_t val = RageSurfaceUtils::decodepixel( row, img->format->BytesPerPixel );
			if( val != trans && !(val&img->format->Amask) )
			{
				RageSurfaceUtils::encodepixel( row, img->format->BytesPerPixel, trans );
			}

			row += img->format->BytesPerPixel;
		}
	}
}

/* When we scale up images (which we always do in high res), pixels
 * that are completely transparent can be blended with opaque pixels,
 * causing their RGB elements to show.  This is visible in many textures
 * as a pixel-wide border in the wrong color.  This is tricky to fix.
 * We need to set the RGB components of completely transparent pixels
 * to a reasonable color.
 *
 * Most images have a single border color.  For these, the transparent
 * color is easy: search through the image top-bottom-left-right,
 * find the first non-transparent pixel, and pull out its RGB.
 *
 * A few images don't.  We can only make a guess here.  What we'll do
 * is, after the above search, do the same in reverse (bottom-top-right-
 * left).  If the color we find is different, we'll just set the border
 * color to black.  
 */
void RageSurfaceUtils::FixHiddenAlpha( RageSurface *img )
{
	/* If there are no alpha bits, there's nothing to fix. */
	if( img->format->BitsPerPixel != 8 && img->format->Amask == 0 )
		return;

	uint8_t r, g, b;
	FindAlphaRGB(img, r, g, b, false);

	uint8_t cr, cg, cb; /* compare */
	FindAlphaRGB(img, cr, cg, cb, true);

	if( cr != r || cg != g || cb != b )
		r = g = b = 0;

	SetAlphaRGB(img, r, g, b);
}

/* Scan the surface to see what level of alpha it uses.  This can be used to
 * find the best surface format for a texture; eg. a TRAIT_BOOL_TRANSPARENCY or
 * TRAIT_NO_TRANSPARENCY surface can use RGB5A1 instead of RGBA4 for greater
 * color resolution; a TRAIT_NO_TRANSPARENCY could also use R5G6B5. */
int RageSurfaceUtils::FindSurfaceTraits( const RageSurface *img )
{
	const int NEEDS_NO_ALPHA=0, NEEDS_BOOL_ALPHA=1, NEEDS_FULL_ALPHA=2;
	int alpha_type = NEEDS_NO_ALPHA;

	uint32_t max_alpha;
	if( img->format->BitsPerPixel == 8 )
	{
		/* Short circuit if we already know we have no transparency. */
		bool bHaveNonOpaque = false;
		for( int c = 0; !bHaveNonOpaque && c < img->format->palette->ncolors; ++c )
		{
			if( img->format->palette->colors[c].a != 0xFF )
				bHaveNonOpaque = true;
		}

		if( !bHaveNonOpaque )
			return TRAIT_NO_TRANSPARENCY;

		max_alpha = 0xFF;
	}
	else
	{
		/* Short circuit if we already know we have no transparency. */
		if( img->format->Amask == 0 )
			return TRAIT_NO_TRANSPARENCY;

		max_alpha = img->format->Amask;
	}

	for(int y = 0; y < img->h; ++y)
	{
		uint8_t *row = (uint8_t *)img->pixels + img->pitch*y;

		for(int x = 0; x < img->w; ++x)
		{
			uint32_t val = decodepixel(row, img->format->BytesPerPixel);

			uint32_t alpha;
			if( img->format->BitsPerPixel == 8 )
				alpha = img->format->palette->colors[val].a;
			else
				alpha = (val & img->format->Amask);

			if( alpha == 0 )
				alpha_type = max( alpha_type, NEEDS_BOOL_ALPHA );
			else if( alpha != max_alpha )
				alpha_type = max( alpha_type, NEEDS_FULL_ALPHA );

			row += img->format->BytesPerPixel;
		}
	}

	int ret = 0;
	switch( alpha_type ) 
	{
	case NEEDS_NO_ALPHA:	ret |= TRAIT_NO_TRANSPARENCY;	break;
	case NEEDS_BOOL_ALPHA:	ret |= TRAIT_BOOL_TRANSPARENCY;	break;
	case NEEDS_FULL_ALPHA:									break;
	default:	ASSERT(0);
	}

	return ret;
}


/* Local helper for BlitTransform. */
static inline void GetRawRGBAV_XY( const RageSurface *src, uint8_t *v, int x, int y )
{
	const uint8_t *srcp = (const uint8_t *) src->pixels + (y * src->pitch);
	const uint8_t *srcpx = srcp + (x * src->fmt.BytesPerPixel);

	RageSurfaceUtils::GetRawRGBAV( srcpx, src->fmt, v );
}

static inline float scale( float x, float l1, float h1, float l2, float h2 )
{
	return ((x - l1) / (h1 - l1) * (h2 - l2) + l2);
}

/* Completely unoptimized. */
void RageSurfaceUtils::BlitTransform( const RageSurface *src, RageSurface *dst, 
					const float fCoords[8] /* TL, BR, BL, TR */ )
{
	ASSERT( src->format->BytesPerPixel == dst->format->BytesPerPixel );

	const float Coords[8] = {
		(fCoords[0] * (src->w)), (fCoords[1] * (src->h)),
		(fCoords[2] * (src->w)), (fCoords[3] * (src->h)),
		(fCoords[4] * (src->w)), (fCoords[5] * (src->h)),
		(fCoords[6] * (src->w)), (fCoords[7] * (src->h))
	};

	const int TL_X = 0, TL_Y = 1, BL_X = 2, BL_Y = 3,
			  BR_X = 4, BR_Y = 5, TR_X = 6, TR_Y = 7;

	for( int y = 0; y < dst->h; ++y )
	{
		uint8_t *dstp = (uint8_t *) dst->pixels + (y * dst->pitch); /* line */
		uint8_t *dstpx = dstp; /* pixel */

		const float start_y = scale(float(y), 0, float(dst->h), Coords[TL_Y], Coords[BL_Y]);
		const float end_y = scale(float(y), 0, float(dst->h), Coords[TR_Y], Coords[BR_Y]);

		const float start_x = scale(float(y), 0, float(dst->h), Coords[TL_X], Coords[BL_X]);
		const float end_x = scale(float(y), 0, float(dst->h), Coords[TR_X], Coords[BR_X]);

		for( int x = 0; x < dst->w; ++x )
		{
			const float src_xp = scale(float(x), 0, float(dst->w), start_x, end_x);
			const float src_yp = scale(float(x), 0, float(dst->w), start_y, end_y);

			/* If the surface is two pixels wide, src_xp is 0..2.  .5 indicates
			 * pixel[0]; 1 indicates 50% pixel[0], 50% pixel[1]; 1.5 indicates
			 * pixel[1]; 2 indicates 50% pixel[1], 50% pixel[2] (which is clamped
			 * to pixel[1]). */
			int src_x[2], src_y[2];
			src_x[0] = (int) truncf(src_xp - 0.5f);
			src_x[1] = src_x[0] + 1;

			src_y[0] = (int) truncf(src_yp - 0.5f);
			src_y[1] = src_y[0] + 1;

			/* Emulate GL_REPEAT. */
			src_x[0] = clamp(src_x[0], 0, src->w);
			src_x[1] = clamp(src_x[1], 0, src->w);
			src_y[0] = clamp(src_y[0], 0, src->h);
			src_y[1] = clamp(src_y[1], 0, src->h);

			/* Decode our four pixels. */
			uint8_t v[4][4];
			GetRawRGBAV_XY(src, v[0], src_x[0], src_y[0]);
			GetRawRGBAV_XY(src, v[1], src_x[0], src_y[1]);
			GetRawRGBAV_XY(src, v[2], src_x[1], src_y[0]);
			GetRawRGBAV_XY(src, v[3], src_x[1], src_y[1]);

			/* Distance from the pixel chosen: */
			float weight_x = src_xp - (src_x[0] + 0.5f);
			float weight_y = src_yp - (src_y[0] + 0.5f);

			/* Filter: */
			uint8_t out[4] = { 0,0,0,0 };
			for(int i = 0; i < 4; ++i)
			{
				float sum = 0;
				sum += v[0][i] * (1-weight_x) * (1-weight_y);
				sum += v[1][i] * (1-weight_x) * (weight_y);
				sum += v[2][i] * (weight_x)   * (1-weight_y);
				sum += v[3][i] * (weight_x)   * (weight_y);
				out[i] = (uint8_t) clamp( lrintf(sum), 0, 255 );
			}

			/* If the source has no alpha, set the destination to opaque. */
			if( src->format->Amask == 0 )
				out[3] = uint8_t( dst->format->Amask >> dst->format->Ashift );

			SetRawRGBAV(dstpx, dst, out);

			dstpx += dst->format->BytesPerPixel;
		}
	}
}


/*
 * Simplified:
 *
 * No source alpha.
 * Palette -> palette blits assume the palette is identical (no mapping).
 * No color key.
 * No general blitting rects.
 */

static bool blit_same_type( const RageSurface *src_surf, const RageSurface *dst_surf, int width, int height )
{
	if( src_surf->format->BytesPerPixel != dst_surf->format->BytesPerPixel ||
		src_surf->format->Rmask != dst_surf->format->Rmask ||
		src_surf->format->Gmask != dst_surf->format->Gmask ||
		src_surf->format->Bmask != dst_surf->format->Bmask ||
		src_surf->format->Amask != dst_surf->format->Amask )
		return false;

	const uint8_t *src = src_surf->pixels;
	uint8_t *dst = dst_surf->pixels;

	/* If possible, memcpy the whole thing. */
	if( src_surf->w == width && dst_surf->w == width && src_surf->pitch == dst_surf->pitch )
	{
        memcpy( dst, src, height*src_surf->pitch );
		return true;
	}

	/* The rows don't line up, so memcpy row by row. */
	while( height-- )
	{
        memcpy( dst, src, width*src_surf->format->BytesPerPixel );
        src += src_surf->pitch;
        dst += dst_surf->pitch;
	}

	return true;
}

/* Rescaling blit with no ckey.  This is used to update movies in
 * D3D, so optimization is very important. */
static bool blit_rgba_to_rgba( const RageSurface *src_surf, const RageSurface *dst_surf, int width, int height )
{
	if( src_surf->format->BytesPerPixel == 1 || dst_surf->format->BytesPerPixel == 1 )
		return false;

	const uint8_t *src = src_surf->pixels;
	uint8_t *dst = dst_surf->pixels;

	/* Bytes to skip at the end of a line. */
	const int srcskip = src_surf->pitch - width*src_surf->format->BytesPerPixel;
	const int dstskip = dst_surf->pitch - width*dst_surf->format->BytesPerPixel;

	const uint32_t *src_shifts = src_surf->format->Shift;
	const uint32_t *dst_shifts = dst_surf->format->Shift;
	const uint32_t *src_masks = src_surf->format->Mask;
	const uint32_t *dst_masks = dst_surf->format->Mask;

	uint8_t lookup[4][256];
	for( int c = 0; c < 4; ++c )
	{
		const uint32_t max_src_val = src_masks[c] >> src_shifts[c];
		const uint32_t max_dst_val = dst_masks[c] >> dst_shifts[c];
		ASSERT( max_src_val <= 0xFF );
		ASSERT( max_dst_val <= 0xFF );

		if( src_masks[c] == 0 )
		{
			/* The source is missing a channel.  Alpha defaults to opaque, other
			 * channels default to 0. */
			if( c == 3 )
				lookup[c][0] = (uint8_t) max_dst_val;
			else
				lookup[c][0] = 0;
		} else {
			/*
			 * Calculate a color conversion table.  There are a few ways we can do
			 * this (each list is the resulting table for 4->2 bit):
			 *
			 * SCALE( i, 0, max_src_val+1, 0, max_dst_val+1 );
			 * { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3 }
			 * SCALE( i, 0, max_src_val, 0, max_dst_val );
			 * { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3 }
			 * lrintf( ((float) i / max_src_val) * max_dst_val )
			 * { 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3 }
			 *
			 * We use the first for increasing resolution, since it gives the most even
			 * distribution.
			 *
			 * 2->4 bit:
			 * SCALE( i, 0, max_src_val+1, 0, max_dst_val+1 );
			 * { 0, 4, 8, 12 }
			 * SCALE( i, 0, max_src_val, 0, max_dst_val );
			 * { 0, 5, 10, 15 }
			 * lrintf( ((float) i / max_src_val) * max_dst_val )
			 * { 0, 5, 10, 15 }
			 *
			 * The latter two are equivalent and give an even distribution; we use the
			 * second, since the first doesn't scale max_src_val to max_dst_val.
			 *
			 * Having separate formulas for increasing and decreasing resolution seems
			 * strange; what's wrong here?
			 */
			if( max_src_val > max_dst_val )
				for( uint32_t i = 0; i <= max_src_val; ++i )
					lookup[c][i] = (uint8_t) SCALE( i, 0, max_src_val+1, 0, max_dst_val+1 );
			else
				for( uint32_t i = 0; i <= max_src_val; ++i )
					lookup[c][i] = (uint8_t) SCALE( i, 0, max_src_val, 0, max_dst_val );
		}
	}

	while( height-- )
	{
		int x = 0;
		while( x++ < width )
		{
			unsigned int pixel = RageSurfaceUtils::decodepixel( src, src_surf->format->BytesPerPixel );

			/* Convert pixel to the destination format. */
			unsigned int opixel = 0;
			for( int c = 0; c < 4; ++c )
			{
				int src = (pixel & src_masks[c]) >> src_shifts[c];
				opixel |= lookup[c][src] << dst_shifts[c];
			}

			/* Store it. */
			RageSurfaceUtils::encodepixel( dst, dst_surf->format->BytesPerPixel, opixel );

			src += src_surf->format->BytesPerPixel;
			dst += dst_surf->format->BytesPerPixel;
		}

		src += srcskip;
		dst += dstskip;
	}

	return true;
}

static bool blit_generic( const RageSurface *src_surf, const RageSurface *dst_surf, int width, int height )
{
	if( src_surf->format->BytesPerPixel != 1 || dst_surf->format->BytesPerPixel == 1 )
		return false;

	const uint8_t *src = src_surf->pixels;
	uint8_t *dst = dst_surf->pixels;

	/* Bytes to skip at the end of a line. */
	const int srcskip = src_surf->pitch - width*src_surf->format->BytesPerPixel;
	const int dstskip = dst_surf->pitch - width*dst_surf->format->BytesPerPixel;

	while( height-- )
	{
		int x = 0;
		while( x++ < width )
		{
			unsigned int pixel = RageSurfaceUtils::decodepixel( src, src_surf->format->BytesPerPixel );

			uint8_t colors[4];
				/* Convert pixel to the destination RGBA. */
				colors[0] = src_surf->format->palette->colors[pixel].r;
				colors[1] = src_surf->format->palette->colors[pixel].g;
				colors[2] = src_surf->format->palette->colors[pixel].b;
				colors[3] = src_surf->format->palette->colors[pixel].a;
			pixel = RageSurfaceUtils::SetRGBAV(dst_surf->format, colors);

			/* Store it. */
			RageSurfaceUtils::encodepixel( dst, dst_surf->format->BytesPerPixel, pixel );

			src += src_surf->format->BytesPerPixel;
			dst += dst_surf->format->BytesPerPixel;
		}

		src += srcskip;
		dst += dstskip;
	}

	return true;
}

/* Blit src onto dst. */
void RageSurfaceUtils::Blit( const RageSurface *src, RageSurface *dst, int width, int height )
{
	if(width == -1)
		width = src->w;
	if(height == -1)
		height = src->h;
	width = min(src->w, dst->w);
	height = min(src->h, dst->h);

	/* Try each blit until we find one that works; run them in order of efficiency,
	 * so we use the fastest blit possible. */
	do
	{
		/* RGBA->RGBA with the same format, or PAL->PAL.  Simple copy. */
		if( blit_same_type(src, dst, width, height) )
			break;

		/* RGBA->RGBA with different formats. */
		if( blit_rgba_to_rgba(src, dst, width, height) )
			break;

		/* PAL->RGBA. */
		if( blit_generic(src, dst, width, height) )
			break;

		/* We don't do RGBA->PAL. */
		ASSERT(0);
	} while(0);

	/*
	 * The destination surface may be larger than the source.  For example, we may be
	 * blitting a 200x200 image onto a 256x256 surface for OpenGL.  Normally, that extra
	 * space isn't actually used; we'll only render the image space.  However, bilinear
	 * filtering will cause the lines of pixels at 201x... and ...x201 to be visible. We
	 * need to make sure those pixels make sense.
	 *
	 * Previously, we just cleared the image to transparent or the color key.  This
	 * has two problems.  First, we may not have space for a color key (an image with
	 * 256 non-transparent palette colors).  Second, that's not completely correct;
	 * it'll force the outside border of the image to filter to transparent.  If the image
	 * is being tiled with another image, that may leave seams.
	 *
	 * (In some cases, filtering to transparent is preferable, particularly when displaying
	 * a sprite in perspective.  If you want that, add blank space to the image explicitly.)
	 *
	 * Copy the last column (200x... -> 201x...), then the last row (...x200 -> ...x201).
	 */

	CorrectBorderPixels( dst, width, height );
}

/*
 * If only width x height of img is actually going to be used, and there's extra
 * space on the surface, duplicate the last row and column to ensure that we don't
 * pull in unexpected data when rendering with bilinear filtering.
 *
 * We do this if there's memory available, even if that space extends outside
 * of the image (in the per-line padding or after the end).  This way, surfaces
 * can be padded to power-of-two dimensions by the image loaders, and if no other
 * adjustments are needed, they can be passed directly to the renderer without
 * doing any extra copies.
 */
void RageSurfaceUtils::CorrectBorderPixels( RageSurface *img, int width, int height )
{
	if( width*img->fmt.BytesPerPixel < img->pitch )
	{
		/* Duplicate the last column. */
		int offset = img->format->BytesPerPixel * (width-1);
		uint8_t *p = (uint8_t *) img->pixels + offset;

		for( int y = 0; y < height; ++y )
		{
			uint32_t pixel = decodepixel( p, img->format->BytesPerPixel );
			encodepixel( p+img->format->BytesPerPixel, img->format->BytesPerPixel, pixel );

			p += img->pitch;
		}
	}

	if( height < img->h )
	{
		/* Duplicate the last row. */
		uint8_t *srcp = img->pixels;
		srcp += img->pitch * (height-1);
		memcpy( srcp + img->pitch, srcp, img->pitch );
	}
}

struct SurfaceHeader
{
	int width, height, pitch;
	int Rmask, Gmask, Bmask, Amask;
	int bpp;
};

/* Save and load RageSurfaces to disk, in a very fast, nonportable way. */
bool RageSurfaceUtils::SaveSurface( const RageSurface *img, CString file )
{
	RageFile f;
	if( !f.Open( file, RageFile::WRITE ) )
		return false;

	SurfaceHeader h;
	memset( &h, 0, sizeof(h) );

	h.height = img->h;
	h.width = img->w;
	h.pitch = img->pitch;
	h.Rmask = img->format->Rmask;
	h.Gmask = img->format->Gmask;
	h.Bmask = img->format->Bmask;
	h.Amask = img->format->Amask;
	h.bpp = img->format->BitsPerPixel;

	f.Write( &h, sizeof(h) );

	if( h.bpp == 8 )
	{
		f.Write( &img->format->palette->ncolors, sizeof(img->format->palette->ncolors) );
		f.Write( img->format->palette->colors, img->format->palette->ncolors * sizeof(RageSurfaceColor) );
	}

	f.Write( img->pixels, img->h * img->pitch );
	
	return true;
}

RageSurface *RageSurfaceUtils::LoadSurface( CString file )
{
	RageFile f;
	if( !f.Open( file ) )
		return false;

	SurfaceHeader h;
	if( f.Read( &h, sizeof(h) ) != sizeof(h) )
		return NULL;
	
	RageSurfacePalette palette;
	if( h.bpp == 8 )
	{
		if( f.Read( &palette.ncolors, sizeof(palette.ncolors) ) != sizeof(palette.ncolors) )
			return NULL;
		ASSERT_M( palette.ncolors <= 256, ssprintf("%i", palette.ncolors) );
		if( f.Read( palette.colors, palette.ncolors * sizeof(RageSurfaceColor) ) != int(palette.ncolors * sizeof(RageSurfaceColor)) )
			return NULL;
	}

	/* Create the surface. */
	RageSurface *img = CreateSurface( h.width, h.height, h.bpp,
			h.Rmask, h.Gmask, h.Bmask, h.Amask );
	ASSERT( img );

	/* If the pitch has changed, this surface is either corrupt, or was
	 * created with a different version whose CreateSurface() behavior
	 * was different. */
	if( h.pitch != img->pitch )
	{
		LOG->Trace( "Error loading \"%s\": expected pitch %i, got %i (%ibpp, %i width)",
				file.c_str(), h.pitch, img->pitch, h.bpp, h.width );
		delete img;
		return NULL;
	}

	if( f.Read( img->pixels, h.height * h.pitch ) != h.height * h.pitch )
	{
		delete img;
		return NULL;
	}

	/* Set the palette. */
	if( h.bpp == 8 )
		*img->fmt.palette = palette;

	return img;
}

/*
 * This converts an image to a special 8-bit paletted format.  The palette is set up
 * so that palette indexes look like regular, packed components.
 *
 * For example, an image with 8 bits of grayscale and 0 bits of alpha has a palette
 * that looks like { 0,0,0,255 }, { 1,1,1,255 }, { 2,2,2,255 }, ... { 255,255,255,255 }.
 * This results in index components that can be treated as grayscale values.
 *
 * An image with 2 bits of grayscale and 2 bits of alpha look like
 * { 0,0,0,0  }, { 85,85,85,0  }, { 170,170,170,0  }, { 255,255,255,0  },
 * { 0,0,0,85 }, { 85,85,85,85 }, { 170,170,170,85 }, { 255,255,255,85 }, ...
 *
 * This results in index components that can be pulled apart like regular packed
 * values: the first two bits of the index are the grayscale component, and the next
 * two bits are the alpha component.
 *
 * This gives us a generic way to handle arbitrary 8-bit texture formats.
 */
RageSurface *RageSurfaceUtils::PalettizeToGrayscale( const RageSurface *src_surf, int GrayBits, int AlphaBits )
{
	AlphaBits = min( AlphaBits, 8-src_surf->format->Loss[3] );
	
	const int TotalBits = GrayBits + AlphaBits;
	ASSERT( TotalBits <= 8 );

	RageSurface *dst_surf = CreateSurface(src_surf->w, src_surf->h,
		8, 0,0,0,0 );

	/* Set up the palette. */
	const int TotalColors = 1 << TotalBits;
	const int Ivalues = 1 << GrayBits;					// number of intensity values
	const int Ishift = 0;								// intensity shift
	const int Imask = ((1 << GrayBits) - 1) << Ishift;	// intensity mask
	const int Iloss = 8-GrayBits;

	const int Avalues = 1 << AlphaBits;					// number of alpha values
	const int Ashift = GrayBits;						// alpha shift
	const int Amask = ((1 << AlphaBits) - 1) << Ashift;	// alpha mask
	const int Aloss = 8-AlphaBits;

	for( int index = 0; index < TotalColors; ++index )
	{
		const int I = (index & Imask) >> Ishift;
		const int A = (index & Amask) >> Ashift;

		int ScaledI;
		if( Ivalues == 1 )
			ScaledI = 255; // if only one intensity value, always fullbright
		else
			ScaledI = clamp( int(roundf(I * (255.0f / (Ivalues-1)))), 0, 255 );

		int ScaledA;
		if( Avalues == 1 )
			ScaledA = 255; // if only one alpha value, always opaque
		else
			ScaledA = clamp( int(roundf(A * (255.0f / (Avalues-1)))), 0, 255 );

		RageSurfaceColor c;
		c.r = uint8_t(ScaledI);
		c.g = uint8_t(ScaledI);
		c.b = uint8_t(ScaledI);
		c.a = uint8_t(ScaledA);

		dst_surf->fmt.palette->colors[index] = c;
	}

	const uint8_t *src = src_surf->pixels;
	uint8_t *dst = dst_surf->pixels;

	int height = src_surf->h;
	int width = src_surf->w;

	/* Bytes to skip at the end of a line. */
	const int srcskip = src_surf->pitch - width*src_surf->format->BytesPerPixel;
	const int dstskip = dst_surf->pitch - width*dst_surf->format->BytesPerPixel;

	while( height-- )
	{
		int x = 0;
		while( x++ < width )
		{
			unsigned int pixel = decodepixel( src, src_surf->format->BytesPerPixel );

			uint8_t colors[4];
			GetRGBAV(pixel, src_surf, colors);

			int Ival = 0;
			Ival += colors[0];
			Ival += colors[1];
			Ival += colors[2];
			Ival /= 3;

			pixel = (Ival >> Iloss) << Ishift |
					(colors[3] >> Aloss) << Ashift;

			/* Store it. */
			*dst = uint8_t(pixel);

			src += src_surf->format->BytesPerPixel;
			dst += dst_surf->format->BytesPerPixel;
		}

		src += srcskip;
		dst += dstskip;
	}

	return dst_surf;
}


RageSurface *RageSurfaceUtils::MakeDummySurface( int height, int width )
{
    RageSurface *ret_image = CreateSurface( width, height, 8, 0,0,0,0 );

	RageSurfaceColor pink( 0xFF, 0x10, 0xFF, 0xFF );
	ret_image->fmt.palette->colors[0] = pink;

	memset( ret_image->pixels, 0, ret_image->h*ret_image->pitch );

	return ret_image;
}

/* HACK:  Some banners and textures have #F800F8 as the color key. Search the edge
 * it; if we find it, use that as the color key. */
static bool ImageUsesOffHotPink( const RageSurface *img )
{
	uint32_t OffHotPink;
	if( !img->format->MapRGBA( 0xF8, 0, 0xF8, 0xFF, OffHotPink ) )
		return false;

	const uint8_t *p = img->pixels;
	for( int x = 0; x < img->w; ++x )
	{
		uint32_t val = RageSurfaceUtils::decodepixel( p, img->format->BytesPerPixel );
		if( val == OffHotPink )
			return true;
		p += img->format->BytesPerPixel;
	}

	p = img->pixels;
	p += img->pitch * (img->h-1);
	for( int i=0; i < img->w; i++ )
	{
		uint32_t val = RageSurfaceUtils::decodepixel( p, img->format->BytesPerPixel );
		if( val == OffHotPink )
			return true;
		p += img->format->BytesPerPixel;
	}
	return false;
}

/* Set #FF00FF and #F800F8 to transparent.  img may be reallocated if it has no alpha
 * bits. */
void RageSurfaceUtils::ApplyHotPinkColorKey( RageSurface *&img )
{
	if( img->format->BitsPerPixel == 8 )
	{
		uint32_t color;
		if( img->format->MapRGBA( 0xF8, 0, 0xF8, 0xFF, color ) )
			img->format->palette->colors[ color ].a = 0;
		if( img->format->MapRGBA( 0xFF, 0, 0xFF, 0xFF, color ) )
			img->format->palette->colors[ color ].a = 0;
		return;
	}

	/* RGBA. Make sure we have alpha. */
	if( img->format->Amask == 0 )
	{
		/* We don't have any alpha.  Try to enable it without copying. */
		/* XXX: need to scan the surface and make sure the new alpha bit is always 1 */
		/*
		const int used_bits = img->format->Rmask | img->format->Gmask | img->format->Bmask;
		
		for( int i = 0; img->format->Amask == 0 && i < img->format->BitsPerPixel; ++i )
		{
			if( (used_bits & (1<<i)) )
			{
				img->format->Amask = 1<<i;
				img->format->Aloss = 7;
				img->format->Ashift = (uint8_t) i;
			}
		}
		*/
		/* If we didn't have any free bits, convert to make room. */
		if( img->format->Amask == 0  )
			ConvertSurface( img, img->w, img->h,
				32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF );
	}

	uint32_t HotPink;

	bool bHaveColorKey;
	if( ImageUsesOffHotPink(img) )
		bHaveColorKey = img->format->MapRGBA( 0xF8, 0, 0xF8, 0xFF, HotPink );
	else
		bHaveColorKey = img->format->MapRGBA( 0xFF, 0, 0xFF, 0xFF, HotPink );
	if( !bHaveColorKey )
		return;

	for( int y = 0; y < img->h; ++y )
	{
		uint8_t *row = img->pixels + img->pitch*y;

		for( int x = 0; x < img->w; ++x )
		{
			uint32_t val = decodepixel( row, img->format->BytesPerPixel );
			if( val == HotPink )
				encodepixel( row, img->format->BytesPerPixel, 0 );

			row += img->format->BytesPerPixel;
		}
	}
}

void RageSurfaceUtils::FlipVertically( RageSurface *img )
{
	const int pitch = img->pitch;
	const int bytes_per_row = img->format->BytesPerPixel * img->w;
	char *row = new char[bytes_per_row];

	for( int y=0; y < img->h/2; y++ )
	{
		int y2 = img->h-1-y;
		memcpy( row, img->pixels + pitch * y, bytes_per_row );
		memcpy( img->pixels + pitch * y, img->pixels + pitch * y2, bytes_per_row );
		memcpy( img->pixels + pitch * y2, row, bytes_per_row  );
	}

	delete [] row;
}

/*
 * (c) 2001-2004 Glenn Maynard, Chris Danford
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons to
 * whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
 * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
 * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
 * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
