/*
img_utils.c - image common tools
Copyright (C) 2007 Uncle Mike

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
*/

#include "imagelib.h"
#include "mathlib.h"
#include "mod_local.h"
#include "gl_export.h"

convar_t *gl_round_down;

#define LERPBYTE( i )	r = resamplerow1[i]; out[i] = (byte)(((( resamplerow2[i] - r ) * lerp)>>16 ) + r )
#define FILTER_SIZE		5

uint d_8toQ1table[256];
uint d_8toHLtable[256];
uint d_8to24table[256];

qboolean q1palette_init = false;
qboolean hlpalette_init = false;

static byte palette_q1[768] =
{
0,0,0,15,15,15,31,31,31,47,47,47,63,63,63,75,75,75,91,91,91,107,107,107,123,123,123,139,139,139,155,155,155,171,
171,171,187,187,187,203,203,203,219,219,219,235,235,235,15,11,7,23,15,11,31,23,11,39,27,15,47,35,19,55,43,23,63,
47,23,75,55,27,83,59,27,91,67,31,99,75,31,107,83,31,115,87,31,123,95,35,131,103,35,143,111,35,11,11,15,19,19,27,
27,27,39,39,39,51,47,47,63,55,55,75,63,63,87,71,71,103,79,79,115,91,91,127,99,99,139,107,107,151,115,115,163,123,
123,175,131,131,187,139,139,203,0,0,0,7,7,0,11,11,0,19,19,0,27,27,0,35,35,0,43,43,7,47,47,7,55,55,7,63,63,7,71,71,
7,75,75,11,83,83,11,91,91,11,99,99,11,107,107,15,7,0,0,15,0,0,23,0,0,31,0,0,39,0,0,47,0,0,55,0,0,63,0,0,71,0,0,79,
0,0,87,0,0,95,0,0,103,0,0,111,0,0,119,0,0,127,0,0,19,19,0,27,27,0,35,35,0,47,43,0,55,47,0,67,55,0,75,59,7,87,67,7,
95,71,7,107,75,11,119,83,15,131,87,19,139,91,19,151,95,27,163,99,31,175,103,35,35,19,7,47,23,11,59,31,15,75,35,19,
87,43,23,99,47,31,115,55,35,127,59,43,143,67,51,159,79,51,175,99,47,191,119,47,207,143,43,223,171,39,239,203,31,255,
243,27,11,7,0,27,19,0,43,35,15,55,43,19,71,51,27,83,55,35,99,63,43,111,71,51,127,83,63,139,95,71,155,107,83,167,123,
95,183,135,107,195,147,123,211,163,139,227,179,151,171,139,163,159,127,151,147,115,135,139,103,123,127,91,111,119,
83,99,107,75,87,95,63,75,87,55,67,75,47,55,67,39,47,55,31,35,43,23,27,35,19,19,23,11,11,15,7,7,187,115,159,175,107,
143,163,95,131,151,87,119,139,79,107,127,75,95,115,67,83,107,59,75,95,51,63,83,43,55,71,35,43,59,31,35,47,23,27,35,
19,19,23,11,11,15,7,7,219,195,187,203,179,167,191,163,155,175,151,139,163,135,123,151,123,111,135,111,95,123,99,83,
107,87,71,95,75,59,83,63,51,67,51,39,55,43,31,39,31,23,27,19,15,15,11,7,111,131,123,103,123,111,95,115,103,87,107,
95,79,99,87,71,91,79,63,83,71,55,75,63,47,67,55,43,59,47,35,51,39,31,43,31,23,35,23,15,27,19,11,19,11,7,11,7,255,
243,27,239,223,23,219,203,19,203,183,15,187,167,15,171,151,11,155,131,7,139,115,7,123,99,7,107,83,0,91,71,0,75,55,
0,59,43,0,43,31,0,27,15,0,11,7,0,0,0,255,11,11,239,19,19,223,27,27,207,35,35,191,43,43,175,47,47,159,47,47,143,47,
47,127,47,47,111,47,47,95,43,43,79,35,35,63,27,27,47,19,19,31,11,11,15,43,0,0,59,0,0,75,7,0,95,7,0,111,15,0,127,23,
7,147,31,7,163,39,11,183,51,15,195,75,27,207,99,43,219,127,59,227,151,79,231,171,95,239,191,119,247,211,139,167,123,
59,183,155,55,199,195,55,231,227,87,127,191,255,171,231,255,215,255,255,103,0,0,139,0,0,179,0,0,215,0,0,255,0,0,255,
243,147,255,247,199,255,255,255,159,91,83
};

// this is used only for particle colors
static byte palette_hl[768] =
{
0,0,0,15,15,15,31,31,31,47,47,47,63,63,63,75,75,75,91,91,91,107,107,107,123,123,123,139,139,139,155,155,155,171,
171,171,187,187,187,203,203,203,219,219,219,235,235,235,15,11,7,23,15,11,31,23,11,39,27,15,47,35,19,55,43,23,63,
47,23,75,55,27,83,59,27,91,67,31,99,75,31,107,83,31,115,87,31,123,95,35,131,103,35,143,111,35,11,11,15,19,19,27,
27,27,39,39,39,51,47,47,63,55,55,75,63,63,87,71,71,103,79,79,115,91,91,127,99,99,139,107,107,151,115,115,163,123,
123,175,131,131,187,139,139,203,0,0,0,7,7,0,11,11,0,19,19,0,27,27,0,35,35,0,43,43,7,47,47,7,55,55,7,63,63,7,71,71,
7,75,75,11,83,83,11,91,91,11,99,99,11,107,107,15,7,0,0,15,0,0,23,0,0,31,0,0,39,0,0,47,0,0,55,0,0,63,0,0,71,0,0,79,
0,0,87,0,0,95,0,0,103,0,0,111,0,0,119,0,0,127,0,0,19,19,0,27,27,0,35,35,0,47,43,0,55,47,0,67,55,0,75,59,7,87,67,7,
95,71,7,107,75,11,119,83,15,131,87,19,139,91,19,151,95,27,163,99,31,175,103,35,35,19,7,47,23,11,59,31,15,75,35,19,
87,43,23,99,47,31,115,55,35,127,59,43,143,67,51,159,79,51,175,99,47,191,119,47,207,143,43,223,171,39,239,203,31,255,
243,27,11,7,0,27,19,0,43,35,15,55,43,19,71,51,27,83,55,35,99,63,43,111,71,51,127,83,63,139,95,71,155,107,83,167,123,
95,183,135,107,195,147,123,211,163,139,227,179,151,171,139,163,159,127,151,147,115,135,139,103,123,127,91,111,119,
83,99,107,75,87,95,63,75,87,55,67,75,47,55,67,39,47,55,31,35,43,23,27,35,19,19,23,11,11,15,7,7,187,115,159,175,107,
143,163,95,131,151,87,119,139,79,107,127,75,95,115,67,83,107,59,75,95,51,63,83,43,55,71,35,43,59,31,35,47,23,27,35,
19,19,23,11,11,15,7,7,219,195,187,203,179,167,191,163,155,175,151,139,163,135,123,151,123,111,135,111,95,123,99,83,
107,87,71,95,75,59,83,63,51,67,51,39,55,43,31,39,31,23,27,19,15,15,11,7,111,131,123,103,123,111,95,115,103,87,107,
95,79,99,87,71,91,79,63,83,71,55,75,63,47,67,55,43,59,47,35,51,39,31,43,31,23,35,23,15,27,19,11,19,11,7,11,7,255,
243,27,239,223,23,219,203,19,203,183,15,187,167,15,171,151,11,155,131,7,139,115,7,123,99,7,107,83,0,91,71,0,75,55,
0,59,43,0,43,31,0,27,15,0,11,7,0,0,0,255,11,11,239,19,19,223,27,27,207,35,35,191,43,43,175,47,47,159,47,47,143,47,
47,127,47,47,111,47,47,95,43,43,79,35,35,63,27,27,47,19,19,31,11,11,15,43,0,0,59,0,0,75,7,0,95,7,0,111,15,0,127,23,
7,147,31,7,163,39,11,183,51,15,195,75,27,207,99,43,219,127,59,227,151,79,231,171,95,239,191,119,247,211,139,167,123,
59,183,155,55,199,195,55,231,227,87,0,255,0,171,231,255,215,255,255,103,0,0,139,0,0,179,0,0,215,0,0,255,0,0,255,243,
147,255,247,199,255,255,255,159,91,83
};

static float FILTER[NUM_FILTERS][FILTER_SIZE][FILTER_SIZE] = 
{ 
{ // regular blur 
{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, 
{ 0.0f, 1.0f, 1.0f, 1.0f, 0.0f }, 
{ 0.0f, 1.0f, 1.0f, 1.0f, 0.0f }, 
{ 0.0f, 1.0f, 1.0f, 1.0f, 0.0f }, 
{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, 
}, 
{ // light blur 
{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, 
{ 0.0f, 1.0f, 1.0f, 1.0f, 0.0f }, 
{ 0.0f, 1.0f, 4.0f, 1.0f, 0.0f }, 
{ 0.0f, 1.0f, 1.0f, 1.0f, 0.0f }, 
{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, 
}, 
{ // find edges 
{ 0.0f,  0.0f,  0.0f,  0.0f, 0.0f }, 
{ 0.0f, -1.0f, -1.0f, -1.0f, 0.0f }, 
{ 0.0f, -1.0f,  8.0f, -1.0f, 0.0f }, 
{ 0.0f, -1.0f, -1.0f, -1.0f, 0.0f }, 
{ 0.0f,  0.0f,  0.0f,  0.0f, 0.0f }, 
}, 
{ // emboss  
{-0.7f, -0.7f, -0.7f, -0.7f, 0.0f }, 
{-0.7f, -0.7f, -0.7f,  0.0f, 0.7f }, 
{-0.7f, -0.7f,  0.0f,  0.7f, 0.7f }, 
{-0.7f,  0.0f,  0.7f,  0.7f, 0.7f }, 
{ 0.0f,  0.7f,  0.7f,  0.7f, 0.7f }, 
}
}; 

/*
=============================================================================

	XASH3D LOAD IMAGE FORMATS

=============================================================================
*/
// stub
static const loadpixformat_t load_null[] =
{
{ NULL, NULL, NULL, IL_HINT_NO }
};

static const loadpixformat_t load_game[] =
{
{ "%s%s.%s", "tga", Image_LoadTGA, IL_HINT_NO },	// hl vgui menus
{ "%s%s.%s", "bmp", Image_LoadBMP, IL_HINT_NO },	// WON menu images
{ "%s%s.%s", "mip", Image_LoadMIP, IL_HINT_NO },	// hl textures from wad or buffer
{ "%s%s.%s", "mdl", Image_LoadMDL, IL_HINT_HL },	// hl studio model skins
{ "%s%s.%s", "spr", Image_LoadSPR, IL_HINT_HL },	// hl sprite frames
{ "%s%s.%s", "lmp", Image_LoadLMP, IL_HINT_HL },	// hl menu images (cached.wad etc)
{ "%s%s.%s", "fnt", Image_LoadFNT, IL_HINT_HL },	// hl console font (fonts.wad etc)
{ "%s%s.%s", "pal", Image_LoadPAL, IL_HINT_NO },	// install studio\sprite palette
{ NULL, NULL, NULL, IL_HINT_NO }
};

/*
=============================================================================

	XASH3D SAVE IMAGE FORMATS

=============================================================================
*/
// stub
static const savepixformat_t save_null[] =
{
{ NULL, NULL, NULL }
};

// Xash3D normal instance
static const savepixformat_t save_game[] =
{
{ "%s%s.%s", "tga", Image_SaveTGA },		// tga screenshots
{ "%s%s.%s", "bmp", Image_SaveBMP },		// bmp levelshots or screenshots
{ NULL, NULL, NULL }
};

void Image_Init( void )
{
	// init pools
	host.imagepool = Mem_AllocPool( "ImageLib Pool" );
	gl_round_down = Cvar_Get( "gl_round_down", "0", CVAR_GLCONFIG, "down size non-power of two textures" );

	// install image formats (can be re-install later by Image_Setup)
	switch( host.type )
	{
	case HOST_NORMAL:
		image.cmd_flags = IL_USE_LERPING|IL_ALLOW_OVERWRITE;		
		image.loadformats = load_game;
		image.saveformats = save_game;
		break;
	default:	// all other instances not using imagelib or will be reinstalling later
		image.loadformats = load_null;
		image.saveformats = save_null;
		break;
	}

	image.tempbuffer = NULL;
}

void Image_Shutdown( void )
{
	Mem_Check(); // check for leaks
	Mem_FreePool( &host.imagepool );
}

byte *Image_Copy( size_t size )
{
	byte	*out;

	out = Mem_Alloc( host.imagepool, size );
	Q_memcpy( out, image.tempbuffer, size );

	return out; 
}

/*
=================
Image_CheckFlag
=================
*/
qboolean Image_CheckFlag( int bit )
{
	if( image.force_flags & bit )
		return true;

	if( image.cmd_flags & bit )
		return true;

	return false;
}

/*
=================
Image_SetForceFlags
=================
*/
void Image_SetForceFlags( uint flags )
{
	image.force_flags = flags;
}

/*
=================
Image_RoundDimensions
=================
*/
void Image_RoundDimensions( int *width, int *height )
{
	// find nearest power of two, rounding down if desired
	*width = NearestPOW( *width, gl_round_down->integer );
	*height = NearestPOW( *height, gl_round_down->integer );
}

qboolean Image_ValidSize( const char *name )
{
	if( image.width > IMAGE_MAXWIDTH || image.height > IMAGE_MAXHEIGHT || image.width <= 0 || image.height <= 0 )
	{
		MsgDev( D_ERROR, "Image: %s has invalid sizes %i x %i\n", name, image.width, image.height );
		return false;
	}
	return true;
}

qboolean Image_LumpValidSize( const char *name )
{
	if( image.width > LUMP_MAXWIDTH || image.height > LUMP_MAXHEIGHT || image.width <= 0 || image.height <= 0 )
	{
		MsgDev(D_WARN, "Image_LumpValidSize: (%s) dims out of range[%dx%d]\n", name, image.width,image.height );
		return false;
	}
	return true;
}

/*
=============
Image_ComparePalette
=============
*/
int Image_ComparePalette( const byte *pal )
{
	if( pal == NULL )
		return PAL_INVALID;
	else if( !Q_memcmp( palette_q1, pal, 768 ))
		return PAL_QUAKE1;
	else if( !Q_memcmp( palette_hl, pal, 768 ))
		return PAL_HALFLIFE;
	return PAL_CUSTOM;		
}

void Image_SetPalette( const byte *pal, uint *d_table )
{
	int	i;
	byte	rgba[4];
	
	// setup palette
	switch( image.d_rendermode )
	{
	case LUMP_DECAL:
		for( i = 0; i < 256; i++ )
		{
			rgba[0] = pal[765];
			rgba[1] = pal[766];
			rgba[2] = pal[767];
			rgba[3] = i;
			d_table[i] = *(uint *)rgba;
		}
		break;
	case LUMP_TRANSPARENT:
		for( i = 0; i < 256; i++ )
		{
			rgba[0] = pal[i*3+0];
			rgba[1] = pal[i*3+1];
			rgba[2] = pal[i*3+2];
			rgba[3] = pal[i] == 255 ? pal[i] : 0xFF;
			d_table[i] = *(uint *)rgba;
		}
		break;
	case LUMP_QFONT:
		for( i = 0; i < 256; i++ )
		{
			rgba[0] = pal[i*3+0];
			rgba[1] = pal[i*3+1];
			rgba[2] = pal[i*3+2];
			rgba[3] = 0xFF;
			d_table[i] = *(uint *)rgba;
		}
		break;
	case LUMP_NORMAL:
		for( i = 0; i < 256; i++ )
		{
			rgba[0] = pal[i*3+0];
			rgba[1] = pal[i*3+1];
			rgba[2] = pal[i*3+2];
			rgba[3] = 0xFF;
			d_table[i] = *(uint *)rgba;
		}
		break;
	case LUMP_EXTENDED:
		for( i = 0; i < 256; i++ )
		{
			rgba[0] = pal[i*4+0];
			rgba[1] = pal[i*4+1];
			rgba[2] = pal[i*4+2];
			rgba[3] = pal[i*4+3];
			d_table[i] = *(uint *)rgba;
		}
		break;	
	}
}

void Image_GetPaletteQ1( void )
{
	image.d_rendermode = LUMP_NORMAL;

	if( !q1palette_init )
	{
		Image_SetPalette( palette_q1, d_8toQ1table );
		d_8toQ1table[255] = 0; // 255 is transparent
		q1palette_init = true;
	}
	image.d_currentpal = d_8toQ1table;
}

void Image_GetPaletteHL( void )
{
	image.d_rendermode = LUMP_NORMAL;

	if( !hlpalette_init )
	{
		Image_SetPalette( palette_hl, d_8toHLtable );
		d_8toHLtable[255] = 0; // 255 is transparent
		hlpalette_init = true;
	}
	image.d_currentpal = d_8toHLtable;
}

void Image_GetPaletteBMP( const byte *pal )
{
	image.d_rendermode = LUMP_EXTENDED;

	if( pal )
	{
		Image_SetPalette( pal, d_8to24table );
		image.d_currentpal = d_8to24table;
	}
}

void Image_GetPaletteLMP( const byte *pal, int rendermode )
{
	image.d_rendermode = rendermode;

	if( pal )
	{
		Image_SetPalette( pal, d_8to24table );
		if( rendermode != LUMP_DECAL )
			d_8to24table[255] &= 0xFFFFFF;
		image.d_currentpal = d_8to24table;
	}
	else if( rendermode == LUMP_QFONT )
	{
		// quake1 base palette and font palette have some diferences
		Image_SetPalette( palette_q1, d_8to24table );
		d_8to24table[0] = 0;
		image.d_currentpal = d_8to24table;
	}
	else Image_GetPaletteHL(); // default half-life palette          
}

void Image_ConvertPalTo24bit( rgbdata_t *pic )
{
	byte	*pal32, *pal24;
	byte	*converted;
	int	i;

	if( !pic->palette )
	{
		MsgDev( D_ERROR, "Image_ConvertPalTo24bit: no palette found\n" );
		return;
	}

	if( pic->type == PF_INDEXED_24 )
	{
		MsgDev( D_ERROR, "Image_ConvertPalTo24bit: palette already converted\n" );
		return;
	}

	pal24 = converted = Mem_Alloc( host.imagepool, 768 );
	pal32 = pic->palette;

	for( i = 0; i < 256; i++, pal24 += 3, pal32 += 4 )
	{
		pal24[0] = pal32[0];
		pal24[1] = pal32[1];
		pal24[2] = pal32[2];
	}

	Mem_Free( pic->palette );
	pic->palette = converted;
	pic->type = PF_INDEXED_24;
}

void Image_CopyPalette32bit( void )
{
	if( image.palette ) return; // already created ?
	image.palette = Mem_Alloc( host.imagepool, 1024 );
	Q_memcpy( image.palette, image.d_currentpal, 1024 );
}

void Image_PaletteHueReplace( byte *palSrc, int newHue, int start, int end )
{
	float	r, g, b;
	float	maxcol, mincol;
	float	hue, val, sat;
	int	i;

	hue = (float)(newHue * ( 360.0f / 255 ));

	for( i = start; i <= end; i++ )
	{
		r = palSrc[i*3+0];
		g = palSrc[i*3+1];
		b = palSrc[i*3+2];
		
		maxcol = max( max( r, g ), b ) / 255.0f;
		mincol = min( min( r, g ), b ) / 255.0f;
		
		val = maxcol;
		sat = (maxcol - mincol) / maxcol;

		mincol = val * (1.0f - sat);

		if( hue <= 120.0f )
		{
			b = mincol;
			if( hue < 60 )
			{
				r = val;
				g = mincol + hue * (val - mincol) / (120.0f - hue);
			}
			else
			{
				g = val;
				r = mincol + (120.0f - hue) * (val - mincol) / hue;
			}
		}
		else if( hue <= 240.0f )
		{
			r = mincol;
			if( hue < 180.0f )
			{
				g = val;
				b = mincol + (hue - 120.0f) * (val - mincol) / (240.0f - hue);
			}
			else
			{
				b = val;
				g = mincol + (240.0f - hue) * (val - mincol) / (hue - 120.0f);
			}
		}
		else
		{
			g = mincol;
			if( hue < 300.0f )
			{
				b = val;
				r = mincol + (hue - 240.0f) * (val - mincol) / (360.0f - hue);
			}
			else
			{
				r = val;
				b = mincol + (360.0f - hue) * (val - mincol) / (hue - 240.0f);
			}
		}

		palSrc[i*3+0] = (byte)(r * 255);
		palSrc[i*3+1] = (byte)(g * 255);
		palSrc[i*3+2] = (byte)(b * 255);
	}
}

void Image_CopyParms( rgbdata_t *src )
{
	Image_Reset();

	image.width = src->width;
	image.height = src->height;
	image.type = src->type;
	image.flags = src->flags;
	image.size = src->size;
	image.palette = src->palette;	// may be NULL

	Q_memcpy( image.fogParams, src->fogParams, sizeof( image.fogParams ));
}

/*
============
Image_Copy8bitRGBA

NOTE: must call Image_GetPaletteXXX before used
============
*/
qboolean Image_Copy8bitRGBA( const byte *in, byte *out, int pixels )
{
	int	*iout = (int *)out;
	byte	*fin = (byte *)in;
	rgba_t	*col;
	int	i;

	if( !image.d_currentpal )
	{
		MsgDev( D_ERROR, "Image_Copy8bitRGBA: no palette set\n" );
		return false;
	}

	if( !in )
	{
		MsgDev( D_ERROR, "Image_Copy8bitRGBA: no input image\n" );
		return false;
	}

	// this is a base image with luma - clear luma pixels
	if( image.flags & IMAGE_HAS_LUMA )
	{
		for( i = 0; i < image.width * image.height; i++ )
			fin[i] = fin[i] < 224 ? fin[i] : 0;
	}

	while( pixels >= 8 )
	{
		iout[0] = image.d_currentpal[in[0]];
		iout[1] = image.d_currentpal[in[1]];
		iout[2] = image.d_currentpal[in[2]];
		iout[3] = image.d_currentpal[in[3]];
		iout[4] = image.d_currentpal[in[4]];
		iout[5] = image.d_currentpal[in[5]];
		iout[6] = image.d_currentpal[in[6]];
		iout[7] = image.d_currentpal[in[7]];

		col = (rgba_t *)iout;
		if( col[0] != col[1] || col[1] != col[2] )
			image.flags |= IMAGE_HAS_COLOR;

		in += 8;
		iout += 8;
		pixels -= 8;
	}

	if( pixels & 4 )
	{
		iout[0] = image.d_currentpal[in[0]];
		iout[1] = image.d_currentpal[in[1]];
		iout[2] = image.d_currentpal[in[2]];
		iout[3] = image.d_currentpal[in[3]];
		in += 4;
		iout += 4;
	}

	if( pixels & 2 )
	{
		iout[0] = image.d_currentpal[in[0]];
		iout[1] = image.d_currentpal[in[1]];
		in += 2;
		iout += 2;
	}

	if( pixels & 1 ) // last byte
		iout[0] = image.d_currentpal[in[0]];

	image.type = PF_RGBA_32;	// update image type;
	return true;
}

static void Image_Resample32LerpLine( const byte *in, byte *out, int inwidth, int outwidth )
{
	int	j, xi, oldx = 0, f, fstep, endx, lerp;

	fstep = (int)(inwidth * 65536.0f / outwidth);
	endx = (inwidth-1);

	for( j = 0, f = 0; j < outwidth; j++, f += fstep )
	{
		xi = f>>16;
		if( xi != oldx )
		{
			in += (xi - oldx) * 4;
			oldx = xi;
		}
		if( xi < endx )
		{
			lerp = f & 0xFFFF;
			*out++ = (byte)((((in[4] - in[0]) * lerp)>>16) + in[0]);
			*out++ = (byte)((((in[5] - in[1]) * lerp)>>16) + in[1]);
			*out++ = (byte)((((in[6] - in[2]) * lerp)>>16) + in[2]);
			*out++ = (byte)((((in[7] - in[3]) * lerp)>>16) + in[3]);
		}
		else // last pixel of the line has no pixel to lerp to
		{
			*out++ = in[0];
			*out++ = in[1];
			*out++ = in[2];
			*out++ = in[3];
		}
	}
}

static void Image_Resample24LerpLine( const byte *in, byte *out, int inwidth, int outwidth )
{
	int	j, xi, oldx = 0, f, fstep, endx, lerp;

	fstep = (int)(inwidth * 65536.0f / outwidth);
	endx = (inwidth-1);

	for( j = 0, f = 0; j < outwidth; j++, f += fstep )
	{
		xi = f>>16;

		if( xi != oldx )
		{
			in += (xi - oldx) * 3;
			oldx = xi;
		}

		if( xi < endx )
		{
			lerp = f & 0xFFFF;
			*out++ = (byte)((((in[3] - in[0]) * lerp)>>16) + in[0]);
			*out++ = (byte)((((in[4] - in[1]) * lerp)>>16) + in[1]);
			*out++ = (byte)((((in[5] - in[2]) * lerp)>>16) + in[2]);
		}
		else // last pixel of the line has no pixel to lerp to
		{
			*out++ = in[0];
			*out++ = in[1];
			*out++ = in[2];
		}
	}
}

void Image_Resample32Lerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight )
{
	const byte *inrow;
	int	i, j, r, yi, oldy = 0, f, fstep, lerp, endy = (inheight - 1);
	int	inwidth4 = inwidth * 4;
	int	outwidth4 = outwidth * 4;
	byte	*out = (byte *)outdata;
	byte	*resamplerow1;
	byte	*resamplerow2;

	fstep = (int)(inheight * 65536.0f / outheight);

	resamplerow1 = (byte *)Mem_Alloc( host.imagepool, outwidth * 4 * 2);
	resamplerow2 = resamplerow1 + outwidth * 4;

	inrow = (const byte *)indata;

	Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth );
	Image_Resample32LerpLine( inrow + inwidth4, resamplerow2, inwidth, outwidth );

	for( i = 0, f = 0; i < outheight; i++, f += fstep )
	{
		yi = f>>16;

		if( yi < endy )
		{
			lerp = f & 0xFFFF;
			if( yi != oldy )
			{
				inrow = (byte *)indata + inwidth4 * yi;
				if (yi == oldy+1) Q_memcpy( resamplerow1, resamplerow2, outwidth4 );
				else Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth );
				Image_Resample32LerpLine( inrow + inwidth4, resamplerow2, inwidth, outwidth );
				oldy = yi;
			}

			j = outwidth - 4;

			while( j >= 0 )
			{
				LERPBYTE( 0);
				LERPBYTE( 1);
				LERPBYTE( 2);
				LERPBYTE( 3);
				LERPBYTE( 4);
				LERPBYTE( 5);
				LERPBYTE( 6);
				LERPBYTE( 7);
				LERPBYTE( 8);
				LERPBYTE( 9);
				LERPBYTE(10);
				LERPBYTE(11);
				LERPBYTE(12);
				LERPBYTE(13);
				LERPBYTE(14);
				LERPBYTE(15);
				out += 16;
				resamplerow1 += 16;
				resamplerow2 += 16;
				j -= 4;
			}

			if( j & 2 )
			{
				LERPBYTE( 0);
				LERPBYTE( 1);
				LERPBYTE( 2);
				LERPBYTE( 3);
				LERPBYTE( 4);
				LERPBYTE( 5);
				LERPBYTE( 6);
				LERPBYTE( 7);
				out += 8;
				resamplerow1 += 8;
				resamplerow2 += 8;
			}

			if( j & 1 )
			{
				LERPBYTE( 0);
				LERPBYTE( 1);
				LERPBYTE( 2);
				LERPBYTE( 3);
				out += 4;
				resamplerow1 += 4;
				resamplerow2 += 4;
			}

			resamplerow1 -= outwidth4;
			resamplerow2 -= outwidth4;
		}
		else
		{
			if( yi != oldy )
			{
				inrow = (byte *)indata + inwidth4*yi;
				if( yi == oldy + 1 ) Q_memcpy( resamplerow1, resamplerow2, outwidth4 );
				else Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth);
				oldy = yi;
			}

			Q_memcpy( out, resamplerow1, outwidth4 );
		}
	}

	Mem_Free( resamplerow1 );
}

void Image_Resample32Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight )
{
	int	i, j;
	uint	frac, fracstep;
	int	*inrow, *out = (int *)outdata; // relies on int being 4 bytes

	fracstep = inwidth * 0x10000 / outwidth;

	for( i = 0; i < outheight; i++)
	{
		inrow = (int *)indata + inwidth * (i * inheight / outheight);
		frac = fracstep>>1;
		j = outwidth - 4;

		while( j >= 0 )
		{
			out[0] = inrow[frac >> 16];frac += fracstep;
			out[1] = inrow[frac >> 16];frac += fracstep;
			out[2] = inrow[frac >> 16];frac += fracstep;
			out[3] = inrow[frac >> 16];frac += fracstep;
			out += 4;
			j -= 4;
		}

		if( j & 2 )
		{
			out[0] = inrow[frac >> 16];frac += fracstep;
			out[1] = inrow[frac >> 16];frac += fracstep;
			out += 2;
		}

		if( j & 1 )
		{
			out[0] = inrow[frac >> 16];frac += fracstep;
			out += 1;
		}
	}
}

void Image_Resample24Lerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight )
{
	const byte *inrow;
	int	i, j, r, yi, oldy, f, fstep, lerp, endy = (inheight - 1);
	int	inwidth3 = inwidth * 3;
	int	outwidth3 = outwidth * 3;
	byte	*out = (byte *)outdata;
	byte	*resamplerow1;
	byte	*resamplerow2;
	
	fstep = (int)(inheight * 65536.0f / outheight);

	resamplerow1 = (byte *)Mem_Alloc( host.imagepool, outwidth * 3 * 2 );
	resamplerow2 = resamplerow1 + outwidth*3;

	inrow = (const byte *)indata;
	oldy = 0;
	Image_Resample24LerpLine( inrow, resamplerow1, inwidth, outwidth );
	Image_Resample24LerpLine( inrow + inwidth3, resamplerow2, inwidth, outwidth );

	for( i = 0, f = 0; i < outheight; i++, f += fstep )
	{
		yi = f>>16;

		if( yi < endy )
		{
			lerp = f & 0xFFFF;
			if( yi != oldy )
			{
				inrow = (byte *)indata + inwidth3 * yi;
				if( yi == oldy + 1) Q_memcpy( resamplerow1, resamplerow2, outwidth3 );
				else Image_Resample24LerpLine( inrow, resamplerow1, inwidth, outwidth );
				Image_Resample24LerpLine( inrow + inwidth3, resamplerow2, inwidth, outwidth );
				oldy = yi;
			}

			j = outwidth - 4;

			while( j >= 0 )
			{
				LERPBYTE( 0);
				LERPBYTE( 1);
				LERPBYTE( 2);
				LERPBYTE( 3);
				LERPBYTE( 4);
				LERPBYTE( 5);
				LERPBYTE( 6);
				LERPBYTE( 7);
				LERPBYTE( 8);
				LERPBYTE( 9);
				LERPBYTE(10);
				LERPBYTE(11);
				out += 12;
				resamplerow1 += 12;
				resamplerow2 += 12;
				j -= 4;
			}

			if( j & 2 )
			{
				LERPBYTE( 0);
				LERPBYTE( 1);
				LERPBYTE( 2);
				LERPBYTE( 3);
				LERPBYTE( 4);
				LERPBYTE( 5);
				out += 6;
				resamplerow1 += 6;
				resamplerow2 += 6;
			}

			if( j & 1 )
			{
				LERPBYTE( 0);
				LERPBYTE( 1);
				LERPBYTE( 2);
				out += 3;
				resamplerow1 += 3;
				resamplerow2 += 3;
			}

			resamplerow1 -= outwidth3;
			resamplerow2 -= outwidth3;
		}
		else
		{
			if( yi != oldy )
			{
				inrow = (byte *)indata + inwidth3*yi;
				if( yi == oldy + 1) Q_memcpy( resamplerow1, resamplerow2, outwidth3 );
				else Image_Resample24LerpLine( inrow, resamplerow1, inwidth, outwidth );
				oldy = yi;
			}

			Q_memcpy( out, resamplerow1, outwidth3 );
		}
	}

	Mem_Free( resamplerow1 );
}

void Image_Resample24Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight )
{
	uint	frac, fracstep;
	int	i, j, f, inwidth3 = inwidth * 3;
	byte	*inrow, *out = (byte *)outdata;

	fracstep = inwidth * 0x10000 / outwidth;

	for( i = 0; i < outheight; i++)
	{
		inrow = (byte *)indata + inwidth3 * (i * inheight / outheight);
		frac = fracstep>>1;
		j = outwidth - 4;

		while( j >= 0 )
		{
			f = (frac >> 16)*3;
			*out++ = inrow[f+0];
			*out++ = inrow[f+1];
			*out++ = inrow[f+2];
			frac += fracstep;
			f = (frac >> 16)*3;
			*out++ = inrow[f+0];
			*out++ = inrow[f+1];
			*out++ = inrow[f+2];
			frac += fracstep;
			f = (frac >> 16)*3;
			*out++ = inrow[f+0];
			*out++ = inrow[f+1];
			*out++ = inrow[f+2];
			frac += fracstep;
			f = (frac >> 16)*3;
			*out++ = inrow[f+0];
			*out++ = inrow[f+1];
			*out++ = inrow[f+2];
			frac += fracstep;
			j -= 4;
		}

		if( j & 2 )
		{
			f = (frac >> 16)*3;
			*out++ = inrow[f+0];
			*out++ = inrow[f+1];
			*out++ = inrow[f+2];
			frac += fracstep;
			f = (frac >> 16)*3;
			*out++ = inrow[f+0];
			*out++ = inrow[f+1];
			*out++ = inrow[f+2];
			frac += fracstep;
			out += 2;
		}

		if( j & 1 )
		{
			f = (frac >> 16)*3;
			*out++ = inrow[f+0];
			*out++ = inrow[f+1];
			*out++ = inrow[f+2];
			frac += fracstep;
			out += 1;
		}
	}
}

void Image_Resample8Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight )
{
	int	i, j;
	byte	*in, *inrow;
	uint	frac, fracstep;
	byte	*out = (byte *)outdata;

	in = (byte *)indata;
	fracstep = inwidth * 0x10000 / outwidth;

	for( i = 0; i < outheight; i++, out += outwidth )
	{
		inrow = in + inwidth*(i*inheight/outheight);
		frac = fracstep>>1;

		for( j = 0; j < outwidth; j++ )
		{
			out[j] = inrow[frac>>16];
			frac += fracstep;
		}
	}
}

/*
================
Image_Resample
================
*/
byte *Image_ResampleInternal( const void *indata, int inwidth, int inheight, int outwidth, int outheight, int type, qboolean *resampled )
{
	qboolean	quality = Image_CheckFlag( IL_USE_LERPING );

	// nothing to resample ?
	if( inwidth == outwidth && inheight == outheight )
	{
		*resampled = false;
		return (byte *)indata;
	}

	// alloc new buffer
	switch( type )
	{
	case PF_INDEXED_24:
	case PF_INDEXED_32:
		image.tempbuffer = (byte *)Mem_Realloc( host.imagepool, image.tempbuffer, outwidth * outheight );
		Image_Resample8Nolerp( indata, inwidth, inheight, image.tempbuffer, outwidth, outheight );
		break;		
	case PF_RGB_24:
	case PF_BGR_24:
		image.tempbuffer = (byte *)Mem_Realloc( host.imagepool, image.tempbuffer, outwidth * outheight * 3 );
		if( quality ) Image_Resample24Lerp( indata, inwidth, inheight, image.tempbuffer, outwidth, outheight );
		else Image_Resample24Nolerp( indata, inwidth, inheight, image.tempbuffer, outwidth, outheight );
		break;
	case PF_RGBA_32:
	case PF_BGRA_32:
		image.tempbuffer = (byte *)Mem_Realloc( host.imagepool, image.tempbuffer, outwidth * outheight * 4 );
		if( quality ) Image_Resample32Lerp( indata, inwidth, inheight, image.tempbuffer, outwidth, outheight );
		else Image_Resample32Nolerp( indata, inwidth, inheight, image.tempbuffer, outwidth, outheight );
		break;
	default:
		MsgDev( D_WARN, "Image_Resample: unsupported format %s\n", PFDesc[type].name );
		*resampled = false;
		return (byte *)indata;	
	}

	*resampled = true;
	return image.tempbuffer;
}

/*
================
Image_Flood
================
*/
byte *Image_FloodInternal( const byte *indata, int inwidth, int inheight, int outwidth, int outheight, int type, qboolean *resampled )
{
	int	samples = PFDesc[type].bpp;
	int	newsize, x, y, i;
	byte	*in, *out;

	// nothing to reflood ?
	if( inwidth == outwidth && inheight == outheight )
	{
		*resampled = false;
		return (byte *)indata;
	}

	// alloc new buffer
	switch( type )
	{
	case PF_INDEXED_24:
	case PF_INDEXED_32:
	case PF_RGB_24:
	case PF_BGR_24:
	case PF_BGRA_32:
	case PF_RGBA_32:
		in = ( byte *)indata;
		newsize = outwidth * outheight * samples;
		out = image.tempbuffer = (byte *)Mem_Realloc( host.imagepool, image.tempbuffer, newsize );
		break;
	default:
		MsgDev( D_WARN, "Image_Flood: unsupported format %s\n", PFDesc[type].name );
		*resampled = false;
		return (byte *)indata;	
	}

	if( samples == 1 ) Q_memset( out, 0xFF, newsize );	// last palette color
	else Q_memset( out, 0x00808080, newsize );		// gray (alpha leaved 0x00)

	for( y = 0; y < outheight; y++ )
	{
		for( x = 0; y < inheight && x < outwidth; x++ )
		{
			for( i = 0; i < samples; i++ )
			{
				if( x < inwidth )
					*out++ = *in++;
				else *out++;
			}
		}
	}

	*resampled = true;
	return image.tempbuffer;
}

/*
================
Image_Flip
================
*/
byte *Image_FlipInternal( const byte *in, word *srcwidth, word *srcheight, int type, int flags )
{
	int	i, x, y;
	word	width = *srcwidth;
	word	height = *srcheight; 
	int	samples = PFDesc[type].bpp;
	qboolean	flip_x = ( flags & IMAGE_FLIP_X ) ? true : false;
	qboolean	flip_y = ( flags & IMAGE_FLIP_Y ) ? true : false;
	qboolean	flip_i = ( flags & IMAGE_ROT_90 ) ? true : false;
	int	row_inc = ( flip_y ? -samples : samples ) * width;
	int	col_inc = ( flip_x ? -samples : samples );
	int	row_ofs = ( flip_y ? ( height - 1 ) * width * samples : 0 );
	int	col_ofs = ( flip_x ? ( width - 1 ) * samples : 0 );
	const byte *p, *line;
	byte	*out;

	// nothing to process
	if( !( flags & (IMAGE_FLIP_X|IMAGE_FLIP_Y|IMAGE_ROT_90)))
		return (byte *)in;

	switch( type )
	{
	case PF_INDEXED_24:
	case PF_INDEXED_32:
	case PF_RGB_24:
	case PF_BGR_24:
	case PF_RGBA_32:
	case PF_BGRA_32:
		image.tempbuffer = Mem_Realloc( host.imagepool, image.tempbuffer, width * height * samples );
		break;
	default:
		MsgDev( D_WARN, "Image_Flip: unsupported format %s\n", PFDesc[type].name );
		return (byte *)in;	
	}

	out = image.tempbuffer;

	if( flip_i )
	{
		for( x = 0, line = in + col_ofs; x < width; x++, line += col_inc )
			for( y = 0, p = line + row_ofs; y < height; y++, p += row_inc, out += samples )
				for( i = 0; i < samples; i++ )
					out[i] = p[i];
	}
	else
	{
		for( y = 0, line = in + row_ofs; y < height; y++, line += row_inc )
			for( x = 0, p = line + col_ofs; x < width; x++, p += col_inc, out += samples )
				for( i = 0; i < samples; i++ )
					out[i] = p[i];
	}

	// update dims
	if( flags & IMAGE_ROT_90 )
	{
		*srcwidth = height;
		*srcheight = width;		
	}
	else
	{
		*srcwidth = width;
		*srcheight = height;	
	}

	return image.tempbuffer;
}

byte *Image_CreateLumaInternal( byte *fin, int width, int height, int type, int flags )
{
	byte	*out;
	int	i;

	if(!( flags & IMAGE_HAS_LUMA ))
	{
		MsgDev( D_WARN, "Image_MakeLuma: image doesn't has luma pixels\n" );
		return (byte *)fin;	  
	}

	switch( type )
	{
	case PF_INDEXED_24:
	case PF_INDEXED_32:
		out = image.tempbuffer = Mem_Realloc( host.imagepool, image.tempbuffer, width * height );
		for( i = 0; i < width * height; i++ )
			*out++ = fin[i] >= 224 ? fin[i] : 0;
		break;
	case PF_RGB_24:
	case PF_BGR_24:
		// clearing any gray pixels
		for( i = 0; i < width * height; i++ )
		{
			if( fin[i*3+0] < 32 ) fin[i*3+0] = 0;
			if( fin[i*3+1] < 32 ) fin[i*3+1] = 0;
			if( fin[i*3+2] < 32 ) fin[i*3+2] = 0;
		}
		return (byte *)fin;
	case PF_RGBA_32:
	case PF_BGRA_32:
		// clearing any gray pixels
		for( i = 0; i < width * height; i++ )
		{
			if( fin[i*4+0] < 32 ) fin[i*4+0] = 0;
			if( fin[i*4+1] < 32 ) fin[i*4+1] = 0;
			if( fin[i*4+2] < 32 ) fin[i*4+2] = 0;
		}
		return (byte *)fin;
	default:
		// another formats does ugly result :(
		MsgDev( D_WARN, "Image_MakeLuma: unsupported format %s\n", PFDesc[type].name );
		return (byte *)fin;	
	}

	return image.tempbuffer;
}

qboolean Image_AddIndexedImageToPack( const byte *in, int width, int height )
{
	int	mipsize = width * height;
	qboolean	expand_to_rgba = true;

	if( Image_CheckFlag( IL_KEEP_8BIT ))
		expand_to_rgba = false;
	else if( host.type == HOST_NORMAL && ( image.flags & ( IMAGE_HAS_LUMA|IMAGE_QUAKESKY )))
		expand_to_rgba = false;

	image.size = mipsize;

	if( expand_to_rgba ) image.size *= 4;
	else Image_CopyPalette32bit(); 

	// reallocate image buffer
	image.rgba = Mem_Alloc( host.imagepool, image.size );	
	if( !expand_to_rgba ) Q_memcpy( image.rgba, in, image.size );
	else if( !Image_Copy8bitRGBA( in, image.rgba, mipsize ))
		return false; // probably pallette not installed

	return true;
}

/*
=============
Image_Decompress

force to unpack any image to 32-bit buffer
=============
*/
qboolean Image_Decompress( const byte *data )
{
	byte	*fin, *fout;
	int	i, size; 

	if( !data ) return false;
	fin = (byte *)data;

	size = image.width * image.height * 4;
	image.tempbuffer = Mem_Realloc( host.imagepool, image.tempbuffer, size );
	fout = image.tempbuffer;

	switch( PFDesc[image.type].format )
	{
	case PF_INDEXED_24:
		if( image.flags & IMAGE_HAS_ALPHA )
		{
			if( image.flags & IMAGE_COLORINDEX )
				Image_GetPaletteLMP( image.palette, LUMP_DECAL ); 
			else Image_GetPaletteLMP( image.palette, LUMP_TRANSPARENT ); 
		}
		else Image_GetPaletteLMP( image.palette, LUMP_NORMAL );
		// intentional falltrough
	case PF_INDEXED_32:
		if( !image.d_currentpal ) image.d_currentpal = (uint *)image.palette;
		if( !Image_Copy8bitRGBA( fin, fout, image.width * image.height ))
			return false;
		break;
	case PF_BGR_24:
		for (i = 0; i < image.width * image.height; i++ )
		{
			fout[(i<<2)+0] = fin[i*3+2];
			fout[(i<<2)+1] = fin[i*3+1];
			fout[(i<<2)+2] = fin[i*3+0];
			fout[(i<<2)+3] = 255;
		}
		break;
	case PF_RGB_24:
		for (i = 0; i < image.width * image.height; i++ )
		{
			fout[(i<<2)+0] = fin[i*3+0];
			fout[(i<<2)+1] = fin[i*3+1];
			fout[(i<<2)+2] = fin[i*3+2];
			fout[(i<<2)+3] = 255;
		}
		break;
	case PF_BGRA_32:
		for( i = 0; i < image.width * image.height; i++ )
		{
			fout[i*4+0] = fin[i*4+2];
			fout[i*4+1] = fin[i*4+1];
			fout[i*4+2] = fin[i*4+0];
			fout[i*4+3] = fin[i*4+3];
		}
		break;
	case PF_RGBA_32:
		// fast default case
		Q_memcpy( fout, fin, size );
		break;
	default: return false;
	}

	// set new size
	image.size = size;

	return true;
}

rgbdata_t *Image_DecompressInternal( rgbdata_t *pic )
{
	// quick case to reject unneeded conversions
	if( pic->type == PF_RGBA_32 )
		return pic;

	Image_CopyParms( pic );
	image.size = image.ptr = 0;

	Image_Decompress( pic->buffer );

	// now we can change type to RGBA
	pic->type = PF_RGBA_32;

	pic->buffer = Mem_Realloc( host.imagepool, pic->buffer, image.size );
	Q_memcpy( pic->buffer, image.tempbuffer, image.size );
	if( pic->palette ) Mem_Free( pic->palette );
	pic->flags = image.flags;
	pic->palette = NULL;

	return pic;
}

rgbdata_t *Image_LightGamma( rgbdata_t *pic, float texGamma )
{
	byte	*in = (byte *)pic->buffer;
	byte	gammatable[256];
	int	i, inf;
	double	f;

	if( pic->type != PF_RGBA_32 )
		return pic;

	texGamma = bound( 1.8f, texGamma, 3.0f );

	// build the gamma table
	for( i = 0; i < 256; i++ )
	{
		f = 255.0 * pow(( float )i / 255.0f, 2.2f / texGamma );
		inf = (int)(f + 0.5f);
		gammatable[i] = bound( 0, inf, 255 );
	}

	for( i = 0; i < pic->width * pic->height; i++, in += 4 )
	{
		in[0] = gammatable[in[0]];
		in[1] = gammatable[in[1]];
		in[2] = gammatable[in[2]];
	}

	return pic;
}

qboolean Image_RemapInternal( rgbdata_t *pic, int topColor, int bottomColor )
{
	switch( pic->type )
	{
	case PF_INDEXED_24:
		break;
	case PF_INDEXED_32:
		Image_ConvertPalTo24bit( pic );
		break;
	default:
		MsgDev( D_ERROR, "Image_Remap: unsupported format %s\n", PFDesc[pic->type].name );
		return false;
	}

	if( !pic->palette )
	{
		MsgDev( D_ERROR, "Image_Remap: palette is missed\n" );
		return false;
	}

	// g-cont. preview images has a swapped top and bottom colors. I don't know why.
	Image_PaletteHueReplace( pic->palette, topColor, SUIT_HUE_START, SUIT_HUE_END );
	Image_PaletteHueReplace( pic->palette, bottomColor, PLATE_HUE_START, PLATE_HUE_END );

	return true;
}

/* 
================== 
Image_ApplyFilter

Applies a 5 x 5 filtering matrix to the texture, then runs it through a simulated OpenGL texture environment 
blend with the original data to derive a new texture.  Freaky, funky, and *f--king* *fantastic*.  You can do 
reasonable enough "fake bumpmapping" with this baby... 

Filtering algorithm from http://www.student.kuleuven.ac.be/~m0216922/CG/filtering.html 
All credit due 
================== 
*/
qboolean Image_ApplyFilter( rgbdata_t *pic, int filter, float factor, float bias, flFlags_t flags, GLenum blendFunc )
{ 
	int	i, x, y; 
	uint	*fin, *fout; 
	size_t	size;

	// first expand the image into 32-bit buffer
	pic = Image_DecompressInternal( pic );

	size = image.width * image.height * 4;
	image.tempbuffer = Mem_Realloc( host.imagepool, image.tempbuffer, size );
	fout = (uint *)image.tempbuffer;
	fin = (uint *)pic->buffer;

	for( x = 0; x < image.width; x++ ) 
	{ 
		for( y = 0; y < image.height; y++ ) 
		{ 
			vec3_t	vout = { 0.0f, 0.0f, 0.0f }; 
			int	pos_x, pos_y;

			for( pos_x = 0; pos_x < FILTER_SIZE; pos_x++ ) 
			{ 
				for( pos_y = 0; pos_y < FILTER_SIZE; pos_y++ ) 
				{ 
					int	img_x = (x - (FILTER_SIZE / 2) + pos_x + image.width) % image.width; 
					int	img_y = (y - (FILTER_SIZE / 2) + pos_y + image.height) % image.height; 

					// casting's a unary operation anyway, so the othermost set of brackets in the left part 
					// of the rvalue should not be necessary... but i'm paranoid when it comes to C... 
					vout[0] += ((float)((byte *)&fin[img_y * image.width + img_x])[0]) * FILTER[filter][pos_x][pos_y]; 
					vout[1] += ((float)((byte *)&fin[img_y * image.width + img_x])[1]) * FILTER[filter][pos_x][pos_y]; 
					vout[2] += ((float)((byte *)&fin[img_y * image.width + img_x])[2]) * FILTER[filter][pos_x][pos_y]; 
				} 
			} 

			// multiply by factor, add bias, and clamp 
			for( i = 0; i < 3; i++ ) 
			{ 
				vout[i] *= factor; 
				vout[i] += bias; 
				vout[i] = bound( 0.0f, vout[i], 255.0f );
			} 

			if( flags & FILTER_GRAYSCALE ) 
			{ 
				// NTSC greyscale conversion standard 
				float avg = (vout[0] * 30.0f + vout[1] * 59.0f + vout[2] * 11.0f) / 100.0f; 

				// divide by 255 so GL operations work as expected 
				vout[0] = avg / 255.0f; 
				vout[1] = avg / 255.0f; 
				vout[2] = avg / 255.0f; 
			} 

			// write to temp - first, write data in (to get the alpha channel quickly and 
			// easily, which will be left well alone by this particular operation...!) 
			fout[y * image.width + x] = fin[y * image.width + x]; 

			// now write in each element, applying the blend operator.  blend 
			// operators are based on standard OpenGL TexEnv modes, and the 
			// formulas are derived from the OpenGL specs (http://www.opengl.org). 
			for( i = 0; i < 3; i++ ) 
			{ 
				// divide by 255 so GL operations work as expected 
				float	src = ((float)((byte *)&fin[y * image.width + x])[i]) / 255.0f; 
				float	tmp;

				switch( blendFunc ) 
				{ 
				case GL_ADD: 
					tmp = vout[i] + src; 
					break; 
				case GL_BLEND: 
					// default is FUNC_ADD here 
					// CsS + CdD works out as Src * Dst * 2 
					tmp = vout[i] * src * 2.0f; 
					break; 
				case GL_DECAL: 
					// same as GL_REPLACE unless there's alpha, which we ignore for this 
				case GL_REPLACE: 
					tmp = vout[i]; 
					break; 
				case GL_ADD_SIGNED: 
					tmp = (vout[i] + src) - 0.5f; 
					break; 
				case GL_MODULATE: 
				default:	// same as default 
					tmp = vout[i] * src; 
					break; 
				} 

				// multiply back by 255 to get the proper byte scale 
				tmp *= 255.0f; 

				// bound the temp target again now, cos the operation may have thrown it out 
				tmp = bound( 0.0f, tmp, 255.0f );
				// and copy it in 
				((byte *)&fout[y * image.width + x])[i] = (byte)tmp; 
			} 
		} 
	} 

	// copy result back
	Q_memcpy( fin, fout, size );

	return true;
}

qboolean Image_Process( rgbdata_t **pix, int width, int height, float gamma, uint flags, imgfilter_t *filter )
{
	rgbdata_t	*pic = *pix;
	qboolean	result = true;
	byte	*out;
				
	// check for buffers
	if( !pic || !pic->buffer )
	{
		MsgDev( D_WARN, "Image_Process: NULL image\n" );
		image.force_flags = 0;
		return false;
	}

	if( !flags && !filter )
	{
		// clear any force flags
		image.force_flags = 0;
		return false; // no operation specfied
	}

	if( flags & IMAGE_MAKE_LUMA )
	{
		out = Image_CreateLumaInternal( pic->buffer, pic->width, pic->height, pic->type, pic->flags );
		if( pic->buffer != out ) Q_memcpy( pic->buffer, image.tempbuffer, pic->size );
		pic->flags &= ~IMAGE_HAS_LUMA;
	}

	if( flags & IMAGE_REMAP )
	{
		// NOTE: user should keep copy of indexed image manually for new changes
		if( Image_RemapInternal( pic, width, height ))
			pic = Image_DecompressInternal( pic );
	}

	// update format to RGBA if any
	if( flags & IMAGE_FORCE_RGBA ) pic = Image_DecompressInternal( pic );
	if( flags & IMAGE_LIGHTGAMMA ) pic = Image_LightGamma( pic, gamma );

	if( filter ) Image_ApplyFilter( pic, filter->filter, filter->factor, filter->bias, filter->flags, filter->blendFunc );

	out = Image_FlipInternal( pic->buffer, &pic->width, &pic->height, pic->type, flags );
	if( pic->buffer != out ) Q_memcpy( pic->buffer, image.tempbuffer, pic->size );

	if(( flags & IMAGE_RESAMPLE && width > 0 && height > 0 ) || ( flags & IMAGE_ROUND ) || ( flags & IMAGE_ROUNDFILLER ))
	{
		qboolean	resampled = false;
		int	w, h;

		if(( flags & IMAGE_ROUND ) || ( flags & IMAGE_ROUNDFILLER ))
		{
			w = pic->width;
			h = pic->height;

			// round to nearest pow
			// NOTE: images with dims less than 8x8 may causing problems
			if( flags & IMAGE_ROUNDFILLER )
			{
				// roundfiller always must roundup
				w = NearestPOW( w, false );
				h = NearestPOW( h, false );
			}
			else Image_RoundDimensions( &w, &h );

			w = bound( 8, w, IMAGE_MAXWIDTH );	// 8 - 4096
			h = bound( 8, h, IMAGE_MAXHEIGHT);	// 8 - 4096
		}
		else
		{
			// custom size (user choise without limitations)
			w = bound( 1, width, IMAGE_MAXWIDTH );	// 1 - 4096
			h = bound( 1, height, IMAGE_MAXHEIGHT);	// 1 - 4096
		}

		if( flags & IMAGE_ROUNDFILLER )
	         		out = Image_FloodInternal( pic->buffer, pic->width, pic->height, w, h, pic->type, &resampled );
		else out = Image_ResampleInternal((uint *)pic->buffer, pic->width, pic->height, w, h, pic->type, &resampled );

		if( resampled ) // resampled or filled
		{
			MsgDev( D_NOTE, "Image_Resample: from[%d x %d] to [%d x %d]\n", pic->width, pic->height, w, h );
			pic->width = w, pic->height = h;
			pic->size = w * h * PFDesc[pic->type].bpp;
			Mem_Free( pic->buffer );		// free original image buffer
			pic->buffer = Image_Copy( pic->size );	// unzone buffer (don't touch image.tempbuffer)
		}
		else
		{
			// not a resampled or filled
			result = false;
		}
	}

	// quantize image
	if( flags & IMAGE_QUANTIZE ) pic = Image_Quantize( pic );
	if( flags & IMAGE_PALTO24 ) Image_ConvertPalTo24bit( pic );

	*pix = pic;

	// clear any force flags
	image.force_flags = 0;

	return result;
}