// Image processing

#ifndef img_h
#define img_h

namespace img
	{
	using namespace vecmath;

	struct image_t
		{
		unsigned width;
		unsigned height;
		float4_t* pixels;
		};

	image_t create( unsigned const width, unsigned const height );
	void destroy( image_t* const image );

	image_t from_ARGB32( unsigned width, unsigned height, const unsigned* pixels );
	void to_ARGB32( const image_t* image, unsigned* pixels );

	float4 sample_clamp( const image_t* const image, float2 const p );


	void adjust_brightness( image_t* image, float value );
	void adjust_contrast( image_t* image, float value );
	void adjust_saturation( image_t* image, float value );
	void sharpen( image_t* image, float radius = 0.1f, float blend = 1.0f );

	//*** process ***

	template< typename OPERATION> void process( img::image_t* image )
		{
		float4_t* ptr = image->pixels;
		unsigned const count = image->width * image->height;
		for( unsigned i = 0; i < count; ++i )
			{
			*ptr = OPERATION::process( *ptr );
			++ptr;
			}
		}


	//*** process ***

	template< typename OPERATION, typename P0 > void process( img::image_t* image, P0& p0 )
		{
		float4_t* ptr = image->pixels;
		unsigned const count = image->width * image->height;
		for( unsigned i = 0; i < count; ++i )
			{
			*ptr = OPERATION::process( *ptr, p0 );
			++ptr;
			}
		}


	}

#endif /* img_h */


#include <malloc.h>
#include <assert.h>
#include <math.h>
#include <float.h>

#define clamp( a, b, c )	( min( max( (a), (b) ), (c) ) )


//*** is_valid ***

static bool is_valid( const img::image_t* const image )
	{
	return image && image->pixels && image->width > 0 && image->height > 0;
	}


//*** create ***

img::image_t img::create( unsigned const width, unsigned const height )
	{
	image_t image;
	image.width = width;
	image.height = height;
	image.pixels = 0;

	unsigned const pixel_count = width * height;
	assert( pixel_count > 0 && "Invalid size" );
	if( pixel_count > 0 )
		{
		image.pixels = (float4_t*) malloc( pixel_count * sizeof( image.pixels[ 0] ) );
		}

	return image;
	}


//*** destroy ***

void img::destroy( image_t* const image )
	{
	assert( is_valid( image ) && "Invalid image" );
	if( is_valid( image ) )
		{
		free( image->pixels );
		image->width = 0;
		image->height = 0;
		image->pixels = 0;
		}
	}


//*** from_ARGB32 ***

img::image_t img::from_ARGB32( unsigned const width, unsigned const height, const unsigned* const pixels )
	{
	assert( pixels && "Invalid pixel data" );
	image_t image = create( width, height );

	if( is_valid( &image ) && pixels )
		{
		const unsigned* src = pixels;
		float4_t* dst = image.pixels;
		unsigned const count = width * height;
		for( unsigned i = 0; i < count; ++i )
			{
			unsigned const c = *src;
			float const a = (float) (( c & 0xff000000 ) >> 24 );
			float const r = (float) (( c & 0x00ff0000 ) >> 16 );
			float const g = (float) (( c & 0x0000ff00 ) >> 8  );
			float const b = (float) (( c & 0x000000ff ) 	    );

			const float  GAMMA_TO_LINEAR = 2.2f;
			*dst = float4( pow( float3( r, g, b ) / 255.0f, float3( GAMMA_TO_LINEAR ) ), a / 255.0f );
			++src;
			++dst;
			}
		}

	return image;
	}


//*** to_ARGB32 ***

void img::to_ARGB32( const image_t* const image, unsigned* const pixels )
	{
	assert( is_valid( image ) && "Invalid image" );
	assert( pixels && "Invalid pixel data" );

	if( is_valid( image ) && pixels )
		{
		const float4_t* src = image->pixels;
		unsigned* dst = pixels;
		unsigned const count = image->width * image->height;
		for( unsigned i = 0; i < count; ++i )
			{
			const float LINEAR_TO_GAMMA = 1.0f / 2.2f;
			float4_t const f = pow( *src, float4(LINEAR_TO_GAMMA) );
			unsigned const r = clamp( (unsigned ) ( f.x * 256.0f ), 0, 255 );
			unsigned const g = clamp( (unsigned ) ( f.y * 256.0f ), 0, 255 );
			unsigned const b = clamp( (unsigned ) ( f.z * 256.0f ), 0, 255 );
			unsigned const a = clamp( (unsigned ) ( f.w * 256.0f ), 0, 255 );
			*dst = ( a << 24 ) | ( r << 16 ) | ( g << 8 ) | b;
			++src;
			++dst;
			}
		}
	}



//*** sample_clamp ***

img::float4 img::sample_clamp( const image_t* const image, img::float2 const p )
	{
	assert( is_valid( image ) && "Invalid image" );

	if( is_valid( image ) )
		{
		float const maxw = (float) ( image->width - 1 );
		float const maxh = (float) ( image->height - 1 );

		float2 const f = floor( p );
		float2 const d = p - f;

		unsigned const x1 = (unsigned) clamp( f.x, 0.0f, maxw );
		unsigned const y1 = (unsigned) clamp( f.y, 0.0f, maxh );
		unsigned const x2 = (unsigned) clamp( f.x + 1.0f, 0.0f, maxw );
		unsigned const y2 = (unsigned) clamp( f.y + 1.0f, 0.0f, maxh );

		float4 const c1 = image->pixels[ x1 + y1 * image->width ];
		float4 const c2 = image->pixels[ x2 + y1 * image->width ];
		float4 const c3 = image->pixels[ x1 + y2 * image->width ];
		float4 const c4 = image->pixels[ x2 + y2 * image->width ];

		return lerp( lerp( c1, c2, d.x ), lerp( c3, c4, d.x ), d.y );
		}

	return float4(0.0f);
	}



//*** op_brightness ***

struct op_brightness
	{
	static img::float4 process( img::float4 color, float value )
		{		
		img::float3 rgb( color.x, color.y, color.z );
		rgb += value;
		return img::float4( rgb, color.w );
		}
	};


//*** op_contrast ***

struct op_contrast
	{
	static img::float4 process( img::float4 color, float value )
		{
		img::float3 rgb( color.x, color.y, color.z );
		rgb = ( ( rgb - 0.5f ) * value ) + 0.5f;	
		return img::float4( rgb, color.w );
		}
	};


//*** RGB_to_HSV ***

static img::float3 RGB_to_HSV( img::float3 rgb )
	{
	float H;
	float S;
	float V;

	float cmin = min( rgb.x, min( rgb.y, rgb.z ) );	//Min. value of RGB
	float cmax = max( rgb.x, max( rgb.y, rgb.z ) );	//Max. value of RGB
	float cdel = cmax - cmin;	//Delta RGB value 

	V = cmax;

	if( cdel == 0 )	//This is a gray, no chroma...
		{
		H = 0;	//HSV results from 0 to 1
		S = 0;
		}
	else	//Chromatic data...
		{
		S = cdel / cmax;

		float rdel = ( ( ( cmax - rgb.x ) / 6.0f ) + ( cdel / 2.0f ) ) / cdel;
		float gdel = ( ( ( cmax - rgb.y ) / 6.0f ) + ( cdel / 2.0f ) ) / cdel;
		float bdel = ( ( ( cmax - rgb.z ) / 6.0f ) + ( cdel / 2.0f ) ) / cdel;

		if( rgb.x == cmax ) 
			{
			H = bdel - gdel;
			}
		else if( rgb.y == cmax ) 
			{
			H = ( 1.0f / 3.0f ) + rdel - bdel;
			}
		else
			{
			H = ( 2.0f / 3.0f ) + gdel - rdel;
			}

		if ( H < 0.0f )
			{
			H += 1.0f;
			}
		if ( H > 1.0f ) 
			{
			H -= 1.0f;
			}
		}
	return img::float3( H, S, V );
	}


//*** HSV_to_RGB ***

static img::float3 HSV_to_RGB( img::float3 hsv )
	{
	float R;
	float G;
	float B;

	float H = hsv.x;
	float S = hsv.y;
	float V = hsv.z;
	
	if( S == 0.0f )	// HSV from 0 to 1
		{
		R = V;
		G = V;
		B = V;
		}
	else
		{
		float h = H * 6.0f;
		if( h == 6.0f ) 
			{
			h = 0.0f;	//H must be < 1
			}
		float i = vecmath::floor( h );
		float v1 = V * ( 1.0f - S );
		float v2 = V * ( 1.0f - S * ( h - i ) );
		float v3 = V * ( 1.0f - S * ( 1.0f - ( h - i ) ) );

		switch( (int) i )
			{
			case 0: { R = V  ; G = v3 ; B = v1 ; } break;
			case 1: { R = v2 ; G = V  ; B = v1 ; } break;
			case 2: { R = v1 ; G = V  ; B = v3 ; } break;
			case 3: { R = v1 ; G = v2 ; B = V  ; } break;
			case 4: { R = v3 ; G = v1 ; B = V  ; } break;
			default:{ R = V  ; G = v1 ; B = v2 ; } break;
			}
		}
	return img::float3( R, G, B );
	}


//*** op_hue ***

struct op_hue
	{
	static img::float4 process( img::float4 color, float value )
		{
		img::float3 rgb( color.x, color.y, color.z );
		img::float3 hsv = RGB_to_HSV( rgb );
		hsv.x = fmodf( hsv.x + value, 1.0f );
		if( hsv.x < 0 )
			{
			hsv.x += 1.0f;
			}
		rgb = HSV_to_RGB( hsv );
		return img::float4( rgb, color.w );
		}
	};


//*** op_saturation ***

struct op_saturation
	{
	static img::float4 process( img::float4 color, float value )
		{
		img::float3 rgb( color.x, color.y, color.z );
		img::float3 hsv = RGB_to_HSV( rgb );
		hsv.y = min( max( hsv.y + value, 0.0f ), 1.0f );
		rgb = HSV_to_RGB( hsv );
		return img::float4( rgb, color.w );
		}
	};


//*** adjust_brightness ***

void img::adjust_brightness( image_t* image, float value )
	{
	assert( is_valid( image ) && "Invalid image" );
	if( is_valid( image ) )
		{
		process<op_brightness>( image, value );
		}
	}


//*** adjust_contrast ***

void img::adjust_contrast( image_t* image, float value )
	{
	assert( is_valid( image ) && "Invalid image" );
	if( is_valid( image ) )
		{
		process<op_contrast>( image, value );
		}
	}


//*** adjust_saturation ***

void img::adjust_saturation( image_t* image, float value )
	{
	assert( is_valid( image ) && "Invalid image" );
	if( is_valid( image ) )
		{
		process<op_saturation>( image, value );
		}
	}


//*** sharpen ***

void img::sharpen( image_t* image, float radius, float blend )
	{
	assert( is_valid( image ) && "Invalid image" );
	
	const int filterWidth = 3;
	const int filterHeight = 3;

	float3 filter[ filterWidth ] [ filterHeight ] = 
		{
		{ float3( -1.0f ), float3( -1.0f ), float3( -1.0f ), },
		{ float3( -1.0f ), float3(  9.0f ), float3( -1.0f ), },
		{ float3( -1.0f ), float3( -1.0f ), float3( -1.0f ), },
		};

	unsigned const pixel_count = image->width * image->height;
	float4_t* result = (float4_t*) malloc( pixel_count * sizeof( float4_t ) );
    
	float4_t* src = image->pixels;
	float4_t* dst = result;
	for( unsigned y = 0; y < image->height; ++y )
		{
		for( unsigned x = 0; x < image->width; ++x )
			{
			if( src->w > 0)
				{
				float3 acc( 0.0f );
				for( int fx = -1; fx <= 1; ++fx )
					{
					for( int fy = -1; fy <= 1; ++fy )
						{
						float2 i = float2( (float) x, (float) y ) + float2( (float) fx, (float) fy ) * radius;
						float4 s = sample_clamp( image, i );
						if( s.w < FLT_EPSILON )
							{
							s = *src;
							}
						acc += float3( s.xyz() ) * filter[ 1 + fx ] [ 1 + fy ];
						}
					}
				*dst = float4( lerp( float4( *src ).xyz(), acc, blend ), src->w );
				}
			else
				{
				*dst = *src;
				}

			++src;
			++dst;
			}
		}

	free( image->pixels );
	image->pixels = result;
	}

