/*
    Jonathan Dummer

    image helper functions

    MIT license
*/

#include "image_helper.h"
#include <stdlib.h>
#include <math.h>

/*	Upscaling the image uses simple bilinear interpolation	*/
int
	up_scale_image
	(
		const unsigned char* const orig,
		int width, int height, int channels,
		unsigned char* resampled,
		int resampled_width, int resampled_height
	)
{
	float dx, dy;
	int x, y, c;

    /* error(s) check	*/
    if ( 	(width < 1) || (height < 1) ||
            (resampled_width < 2) || (resampled_height < 2) ||
            (channels < 1) ||
            (NULL == orig) || (NULL == resampled) )
    {
        /*	signify badness	*/
        return 0;
    }
    /*
		for each given pixel in the new map, find the exact location
		from the original map which would contribute to this guy
	*/
    dx = (width - 1.0f) / (resampled_width - 1.0f);
    dy = (height - 1.0f) / (resampled_height - 1.0f);
    for ( y = 0; y < resampled_height; ++y )
    {
    	/* find the base y index and fractional offset from that	*/
    	float sampley = y * dy;
    	int inty = (int)sampley;
    	/*	if( inty < 0 ) { inty = 0; } else	*/
		if( inty > height - 2 ) { inty = height - 2; }
		sampley -= inty;
        for ( x = 0; x < resampled_width; ++x )
        {
			float samplex = x * dx;
			int intx = (int)samplex;
			int base_index;
			/* find the base x index and fractional offset from that	*/
			/*	if( intx < 0 ) { intx = 0; } else	*/
			if( intx > width - 2 ) { intx = width - 2; }
			samplex -= intx;
			/*	base index into the original image	*/
			base_index = (inty * width + intx) * channels;
            for ( c = 0; c < channels; ++c )
            {
            	/*	do the sampling	*/
				float value = 0.5f;
				value += orig[base_index]
							*(1.0f-samplex)*(1.0f-sampley);
				value += orig[base_index+channels]
							*(samplex)*(1.0f-sampley);
				value += orig[base_index+width*channels]
							*(1.0f-samplex)*(sampley);
				value += orig[base_index+width*channels+channels]
							*(samplex)*(sampley);
				/*	move to the next channel	*/
				++base_index;
            	/*	save the new value	*/
            	resampled[y*resampled_width*channels+x*channels+c] =
						(unsigned char)(value);
            }
        }
    }
    /*	done	*/
    return 1;
}

int
	mipmap_image
	(
		const unsigned char* const orig,
		int width, int height, int channels,
		unsigned char* resampled,
		int block_size_x, int block_size_y
	)
{
	int mip_width, mip_height;
	int i, j, c;

	/*	error check	*/
	if( (width < 1) || (height < 1) ||
		(channels < 1) || (orig == NULL) ||
		(resampled == NULL) ||
		(block_size_x < 1) || (block_size_y < 1) )
	{
		/*	nothing to do	*/
		return 0;
	}
	mip_width = width / block_size_x;
	mip_height = height / block_size_y;
	if( mip_width < 1 )
	{
		mip_width = 1;
	}
	if( mip_height < 1 )
	{
		mip_height = 1;
	}
	for( j = 0; j < mip_height; ++j )
	{
		for( i = 0; i < mip_width; ++i )
		{
			for( c = 0; c < channels; ++c )
			{
				const int index = (j*block_size_y)*width*channels + (i*block_size_x)*channels + c;
				int sum_value;
				int u,v;
				int u_block = block_size_x;
				int v_block = block_size_y;
				int block_area;
				/*	do a bit of checking so we don't over-run the boundaries
					(necessary for non-square textures!)	*/
				if( block_size_x * (i+1) > width )
				{
					u_block = width - i*block_size_y;
				}
				if( block_size_y * (j+1) > height )
				{
					v_block = height - j*block_size_y;
				}
				block_area = u_block*v_block;
				/*	for this pixel, see what the average
					of all the values in the block are.
					note: start the sum at the rounding value, not at 0	*/
				sum_value = block_area >> 1;
				for( v = 0; v < v_block; ++v )
				for( u = 0; u < u_block; ++u )
				{
					sum_value += orig[index + v*width*channels + u*channels];
				}
				resampled[j*mip_width*channels + i*channels + c] = sum_value / block_area;
			}
		}
	}
	return 1;
}

int
	scale_image_RGB_to_NTSC_safe
	(
		unsigned char* orig,
		int width, int height, int channels
	)
{
	const float scale_lo = 16.0f - 0.499f;
	const float scale_hi = 235.0f + 0.499f;
	int i, j;
	int nc = channels;
	unsigned char scale_LUT[256];
	/*	error check	*/
	if( (width < 1) || (height < 1) ||
		(channels < 1) || (orig == NULL) )
	{
		/*	nothing to do	*/
		return 0;
	}
	/*	set up the scaling Look Up Table	*/
	for( i = 0; i < 256; ++i )
	{
		scale_LUT[i] = (unsigned char)((scale_hi - scale_lo) * i / 255.0f + scale_lo);
	}
	/*	for channels = 2 or 4, ignore the alpha component	*/
	nc -= 1 - (channels & 1);
	/*	OK, go through the image and scale any non-alpha components	*/
	for( i = 0; i < width*height*channels; i += channels )
	{
		for( j = 0; j < nc; ++j )
		{
			orig[i+j] = scale_LUT[orig[i+j]];
		}
	}
	return 1;
}

unsigned char clamp_byte( int x ) { return ( (x) < 0 ? (0) : ( (x) > 255 ? 255 : (x) ) ); }

/*
	This function takes the RGB components of the image
	and converts them into YCoCg.  3 components will be
	re-ordered to CoYCg (for optimum DXT1 compression),
	while 4 components will be ordered CoCgAY (for DXT5
	compression).
*/
int
	convert_RGB_to_YCoCg
	(
		unsigned char* orig,
		int width, int height, int channels
	)
{
	int i;
	/*	error check	*/
	if( (width < 1) || (height < 1) ||
		(channels < 3) || (channels > 4) ||
		(orig == NULL) )
	{
		/*	nothing to do	*/
		return -1;
	}
	/*	do the conversion	*/
	if( channels == 3 )
	{
		for( i = 0; i < width*height*3; i += 3 )
		{
			int r = orig[i+0];
			int g = (orig[i+1] + 1) >> 1;
			int b = orig[i+2];
			int tmp = (2 + r + b) >> 2;
			/*	Co	*/
			orig[i+0] = clamp_byte( 128 + ((r - b + 1) >> 1) );
			/*	Y	*/
			orig[i+1] = clamp_byte( g + tmp );
			/*	Cg	*/
			orig[i+2] = clamp_byte( 128 + g - tmp );
		}
	} else
	{
		for( i = 0; i < width*height*4; i += 4 )
		{
			int r = orig[i+0];
			int g = (orig[i+1] + 1) >> 1;
			int b = orig[i+2];
			unsigned char a = orig[i+3];
			int tmp = (2 + r + b) >> 2;
			/*	Co	*/
			orig[i+0] = clamp_byte( 128 + ((r - b + 1) >> 1) );
			/*	Cg	*/
			orig[i+1] = clamp_byte( 128 + g - tmp );
			/*	Alpha	*/
			orig[i+2] = a;
			/*	Y	*/
			orig[i+3] = clamp_byte( g + tmp );
		}
	}
	/*	done	*/
	return 0;
}

/*
	This function takes the YCoCg components of the image
	and converts them into RGB.  See above.
*/
int
	convert_YCoCg_to_RGB
	(
		unsigned char* orig,
		int width, int height, int channels
	)
{
	int i;
	/*	error check	*/
	if( (width < 1) || (height < 1) ||
		(channels < 3) || (channels > 4) ||
		(orig == NULL) )
	{
		/*	nothing to do	*/
		return -1;
	}
	/*	do the conversion	*/
	if( channels == 3 )
	{
		for( i = 0; i < width*height*3; i += 3 )
		{
			int co = orig[i+0] - 128;
			int y  = orig[i+1];
			int cg = orig[i+2] - 128;
			/*	R	*/
			orig[i+0] = clamp_byte( y + co - cg );
			/*	G	*/
			orig[i+1] = clamp_byte( y + cg );
			/*	B	*/
			orig[i+2] = clamp_byte( y - co - cg );
		}
	} else
	{
		for( i = 0; i < width*height*4; i += 4 )
		{
			int co = orig[i+0] - 128;
			int cg = orig[i+1] - 128;
			unsigned char a  = orig[i+2];
			int y  = orig[i+3];
			/*	R	*/
			orig[i+0] = clamp_byte( y + co - cg );
			/*	G	*/
			orig[i+1] = clamp_byte( y + cg );
			/*	B	*/
			orig[i+2] = clamp_byte( y - co - cg );
			/*	A	*/
			orig[i+3] = a;
		}
	}
	/*	done	*/
	return 0;
}

float
find_max_RGBE
(
	unsigned char *image,
    int width, int height
)
{
	float max_val = 0.0f;
	unsigned char *img = image;
	int i, j;
	for( i = width * height; i > 0; --i )
	{
		/* float scale = powf( 2.0f, img[3] - 128.0f ) / 255.0f; */
		float scale = (float)ldexp( 1.0f / 255.0f, (int)(img[3]) - 128 );
		for( j = 0; j < 3; ++j )
		{
			if( img[j] * scale > max_val )
			{
				max_val = img[j] * scale;
			}
		}
		/* next pixel */
		img += 4;
	}
	return max_val;
}

int
RGBE_to_RGBdivA
(
    unsigned char *image,
    int width, int height,
    int rescale_to_max
)
{
	/* local variables */
	int i, iv;
	unsigned char *img = image;
	float scale = 1.0f;
	/* error check */
	if( (!image) || (width < 1) || (height < 1) )
	{
		return 0;
	}
	/* convert (note: no negative numbers, but 0.0 is possible) */
	if( rescale_to_max )
	{
		scale = 255.0f / find_max_RGBE( image, width, height );
	}
	for( i = width * height; i > 0; --i )
	{
		/* decode this pixel, and find the max */
		float r,g,b,e, m;
		/* e = scale * powf( 2.0f, img[3] - 128.0f ) / 255.0f; */
		e = scale * (float)ldexp( 1.0f / 255.0f, (int)(img[3]) - 128 );
		r = e * img[0];
		g = e * img[1];
		b = e * img[2];
		m = (r > g) ? r : g;
		m = (b > m) ? b : m;
		/* and encode it into RGBdivA */
		iv = (m != 0.0f) ? (int)(255.0f / m) : 1;
		iv = (iv < 1) ? 1 : iv;
		img[3] = (iv > 255) ? 255 : iv;
		iv = (int)(img[3] * r + 0.5f);
		img[0] = (iv > 255) ? 255 : iv;
		iv = (int)(img[3] * g + 0.5f);
		img[1] = (iv > 255) ? 255 : iv;
		iv = (int)(img[3] * b + 0.5f);
		img[2] = (iv > 255) ? 255 : iv;
		/* and on to the next pixel */
		img += 4;
	}
	return 1;
}

int
RGBE_to_RGBdivA2
(
    unsigned char *image,
    int width, int height,
    int rescale_to_max
)
{
	/* local variables */
	int i, iv;
	unsigned char *img = image;
	float scale = 1.0f;
	/* error check */
	if( (!image) || (width < 1) || (height < 1) )
	{
		return 0;
	}
	/* convert (note: no negative numbers, but 0.0 is possible) */
	if( rescale_to_max )
	{
		scale = 255.0f * 255.0f / find_max_RGBE( image, width, height );
	}
	for( i = width * height; i > 0; --i )
	{
		/* decode this pixel, and find the max */
		float r,g,b,e, m;
		/* e = scale * powf( 2.0f, img[3] - 128.0f ) / 255.0f; */
		e = scale * (float)ldexp( 1.0f / 255.0f, (int)(img[3]) - 128 );
		r = e * img[0];
		g = e * img[1];
		b = e * img[2];
		m = (r > g) ? r : g;
		m = (b > m) ? b : m;
		/* and encode it into RGBdivA */
		iv = (m != 0.0f) ? (int)sqrtf( 255.0f * 255.0f / m ) : 1;
		iv = (iv < 1) ? 1 : iv;
		img[3] = (iv > 255) ? 255 : iv;
		iv = (int)(img[3] * img[3] * r / 255.0f + 0.5f);
		img[0] = (iv > 255) ? 255 : iv;
		iv = (int)(img[3] * img[3] * g / 255.0f + 0.5f);
		img[1] = (iv > 255) ? 255 : iv;
		iv = (int)(img[3] * img[3] * b / 255.0f + 0.5f);
		img[2] = (iv > 255) ? 255 : iv;
		/* and on to the next pixel */
		img += 4;
	}
	return 1;
}
