text: /*
      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;
  }
  