436 lines
9.8 KiB
C
436 lines
9.8 KiB
C
|
/*
|
||
|
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 = 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 * 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.0f;
|
||
|
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 * 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.0f;
|
||
|
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;
|
||
|
}
|