//========= Copyright Valve Corporation, All rights reserved. ============//
//
// NOTE: To make use of this file, g_pFullFileSystem must be defined, or you can modify
// this source to take an IFileSystem * as input.
//
//=======================================================================================//

// @note Tom Bui: we need to use fopen below in the jpeg code, so we can't have this on...
#ifdef PROTECTED_THINGS_ENABLE
#if !defined( POSIX )
#undef fopen
#endif // POSIX
#endif

#if defined( WIN32 ) && !defined( _X360 )
#include <windows.h> // SRC only!!
#elif defined( POSIX )
#include <stdio.h>
#include <sys/stat.h>
#ifdef OSX
#include <copyfile.h>
#endif
#endif

#include "imageutils.h"
#include "filesystem.h"
#include "utlbuffer.h"
#include "bitmap/bitmap.h"
#include "vtf/vtf.h"

// clang3 on OSX folks the attribute into the prototype, causing a compile failure
// filed radar bug 10397783
#if ( __clang_major__ == 3 )
#include <setjmp.h>
extern void longjmp( jmp_buf, int ) __attribute__((noreturn));
#endif


#ifdef ENGINE_DLL
	#include "common.h"
#elif CLIENT_DLL
	// @note Tom Bui: instead of forcing the project to include EngineInterface.h...
	#include "cdll_int.h"
	// engine interface singleton accessors
	extern IVEngineClient *engine;
	extern class IGameUIFuncs *gameuifuncs;
	extern class IEngineSound *enginesound;
	extern class IMatchmaking *matchmaking;
	extern class IXboxSystem  *xboxsystem;
	extern class IAchievementMgr *achievementmgr; 
	extern class CSteamAPIContext *steamapicontext;
#elif REPLAY_DLL
	#include "replay/ienginereplay.h"
	extern IEngineReplay *g_pEngine;
#elif ENGINE_DLL
	#include "EngineInterface.h"
#elif UTILS
	// OwO
#else
	#include "cdll_int.h"
	extern IVEngineClient *engine;
#endif

// use the JPEGLIB_USE_STDIO define so that we can read in jpeg's from outside the game directory tree.
#define JPEGLIB_USE_STDIO
#if ANDROID
#include "android/jpeglib/jpeglib.h"
#elif defined WIN32
#include "jpeglib/jpeglib.h"
#else
#include <jpeglib.h>
#endif

#undef JPEGLIB_USE_STDIO


#if HAVE_PNG

#if ANDROID || WIN32
#include "libpng/png.h"
#else
#include <png.h>
#endif

#endif

#include <setjmp.h>

#include "bitmap/tgawriter.h"
#include "ivtex.h"
#ifdef WIN32
#include <io.h>
#endif
#ifdef OSX
#include <copyfile.h>
#endif

#ifndef WIN32
#define DeleteFile(s)	remove(s)
#endif

#if defined( _X360 )
#include "xbox/xbox_win32stubs.h"
#endif



// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
#if HAVE_JPEG

struct ValveJpegErrorHandler_t 
{
	// The default manager
	struct jpeg_error_mgr	m_Base;
	// For handling any errors
	jmp_buf					m_ErrorContext;
};

#define JPEG_OUTPUT_BUF_SIZE  4096   

struct JPEGDestinationManager_t
{
	struct jpeg_destination_mgr pub; // public fields

	CUtlBuffer  *pBuffer;       // target/final buffer
	byte        *buffer;        // start of temp buffer
};


//-----------------------------------------------------------------------------
// Purpose: We'll override the default error handler so we can deal with errors without having to exit the engine
//-----------------------------------------------------------------------------
static void ValveJpegErrorHandler( j_common_ptr cinfo )
{
	ValveJpegErrorHandler_t *pError = reinterpret_cast< ValveJpegErrorHandler_t * >( cinfo->err );

	char buffer[ JMSG_LENGTH_MAX ];

	/* Create the message */
	( *cinfo->err->format_message )( cinfo, buffer );

	Warning( "%s\n", buffer );

	// Bail
	longjmp( pError->m_ErrorContext, 1 );
}
#endif

// convert the JPEG file given to a TGA file at the given output path.
ConversionErrorType ImgUtl_ConvertJPEGToTGA( const char *jpegpath, const char *tgaPath, bool bRequirePowerOfTwo )
{
#if !defined( _X360 ) && HAVE_JPEG

	//
	// !FIXME! This really probably should use ImgUtl_ReadJPEGAsRGBA, to avoid duplicated code.
	//

	struct jpeg_decompress_struct jpegInfo;
	struct ValveJpegErrorHandler_t jerr;
	JSAMPROW row_pointer[1];
	int row_stride;
	int cur_row = 0;

	// image attributes
	int image_height;
	int image_width;

	// open the jpeg image file.
	FILE *infile = fopen(jpegpath, "rb");
	if (infile == NULL)
	{
		return CE_CANT_OPEN_SOURCE_FILE;
	}

	// setup error to print to stderr.
	jpegInfo.err = jpeg_std_error(&jerr.m_Base);

	jpegInfo.err->error_exit = &ValveJpegErrorHandler;

	// create the decompress struct.
	jpeg_create_decompress(&jpegInfo);

	if ( setjmp( jerr.m_ErrorContext ) )
	{
		// Get here if there is any error
		jpeg_destroy_decompress( &jpegInfo );

		fclose(infile);

		return CE_ERROR_PARSING_SOURCE;
	}

	jpeg_stdio_src(&jpegInfo, infile);

	// read in the jpeg header and make sure that's all good.
	if (jpeg_read_header(&jpegInfo, TRUE) != JPEG_HEADER_OK)
	{
		fclose(infile);
		return CE_ERROR_PARSING_SOURCE;
	}

	// start the decompress with the jpeg engine.
	if ( !jpeg_start_decompress(&jpegInfo) )
	{
		jpeg_destroy_decompress(&jpegInfo);
		fclose(infile);
		return CE_ERROR_PARSING_SOURCE;
	}

	// Check for valid width and height (ie. power of 2 and print out an error and exit if not).
	if ( ( bRequirePowerOfTwo && ( !IsPowerOfTwo(jpegInfo.image_height) || !IsPowerOfTwo(jpegInfo.image_width) ) )
		|| jpegInfo.output_components != 3 )
	{
		jpeg_destroy_decompress(&jpegInfo);
		fclose( infile );
		return CE_SOURCE_FILE_SIZE_NOT_SUPPORTED;
	}

	// now that we've started the decompress with the jpeg lib, we have the attributes of the
	// image ready to be read out of the decompress struct.
	row_stride = jpegInfo.output_width * jpegInfo.output_components;
	image_height = jpegInfo.image_height;
	image_width = jpegInfo.image_width;
	int mem_required = jpegInfo.image_height * jpegInfo.image_width * jpegInfo.output_components;

	// allocate the memory to read the image data into.
	unsigned char *buf = (unsigned char *)malloc(mem_required);
	if (buf == NULL)
	{
		jpeg_destroy_decompress(&jpegInfo);
		fclose(infile);
		return CE_MEMORY_ERROR;
	}

	// read in all the scan lines of the image into our image data buffer.
	bool working = true;
	while (working && (jpegInfo.output_scanline < jpegInfo.output_height))
	{
		row_pointer[0] = &(buf[cur_row * row_stride]);
		if ( !jpeg_read_scanlines(&jpegInfo, row_pointer, 1) )
		{
			working = false;
		}
		++cur_row;
	}

	if (!working)
	{
		free(buf);
		jpeg_destroy_decompress(&jpegInfo);
		fclose(infile);
		return CE_ERROR_PARSING_SOURCE;
	}

	jpeg_finish_decompress(&jpegInfo);

	fclose(infile);
	
	// ok, at this point we have read in the JPEG image to our buffer, now we need to write it out as a TGA file.
	CUtlBuffer outBuf;
	bool bRetVal = TGAWriter::WriteToBuffer( buf, outBuf, image_width, image_height, IMAGE_FORMAT_RGB888, IMAGE_FORMAT_RGB888 );
	if ( bRetVal )
	{
		if ( !g_pFullFileSystem->WriteFile( tgaPath, NULL, outBuf ) )
		{
			bRetVal = false;
		}
	}

	free(buf);
	return bRetVal ? CE_SUCCESS : CE_ERROR_WRITING_OUTPUT_FILE;

#else
	return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
#endif
}

// convert the bmp file given to a TGA file at the given destination path.
ConversionErrorType ImgUtl_ConvertBMPToTGA(const char *bmpPath, const char *tgaPath)
{
	if ( !IsPC() )
		return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;

#ifdef WIN32

	int nWidth, nHeight;
	ConversionErrorType result;
	unsigned char *pBufRGBA = ImgUtl_ReadBMPAsRGBA( bmpPath, nWidth, nHeight, result );
	if ( result != CE_SUCCESS)
	{
		Assert( !pBufRGBA );
		free( pBufRGBA );
		return result;
	}
	Assert( pBufRGBA );

	// write out the TGA file using the RGB data buffer.
	CUtlBuffer outBuf;
	bool retval = TGAWriter::WriteToBuffer(pBufRGBA, outBuf, nWidth, nHeight, IMAGE_FORMAT_RGBA8888, IMAGE_FORMAT_RGB888);
	free( pBufRGBA );

	if ( retval )
	{
		if ( !g_pFullFileSystem->WriteFile( tgaPath, NULL, outBuf ) )
		{
			retval = false;
		}
	}

	return retval ? CE_SUCCESS : CE_ERROR_WRITING_OUTPUT_FILE;

#else // WIN32
	return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
#endif
}

unsigned char *ImgUtl_ReadVTFAsRGBA( const char *vtfPath, int &width, int &height, ConversionErrorType &errcode )
{
	// Just load the whole file into a memory buffer
	CUtlBuffer bufFileContents;
	if ( !g_pFullFileSystem->ReadFile( vtfPath, NULL, bufFileContents ) )
	{
		errcode = CE_CANT_OPEN_SOURCE_FILE;
		return NULL;
	}

	IVTFTexture *pVTFTexture = CreateVTFTexture();
	if ( !pVTFTexture->Unserialize( bufFileContents ) )
	{
		DestroyVTFTexture( pVTFTexture );
		errcode = CE_ERROR_PARSING_SOURCE;
		return NULL;
	}

	width = pVTFTexture->Width();
	height = pVTFTexture->Height();
	pVTFTexture->ConvertImageFormat( IMAGE_FORMAT_RGBA8888, false );

	int nMemSize = ImageLoader::GetMemRequired( width, height, 1, IMAGE_FORMAT_RGBA8888, false );
	unsigned char *pMemImage = (unsigned char *)malloc(nMemSize);
	if ( pMemImage == NULL )
	{
		DestroyVTFTexture( pVTFTexture );
		errcode = CE_MEMORY_ERROR;
		return NULL;
	}
	Q_memcpy( pMemImage, pVTFTexture->ImageData(), nMemSize );

	DestroyVTFTexture( pVTFTexture );

	errcode = CE_SUCCESS;
	return pMemImage;
}

// read a TGA header from the current point in the file stream.
static void ImgUtl_ReadTGAHeader(FILE *infile, TGAHeader &header)
{
	if (infile == NULL)
	{
		return;
	}

	fread(&header.identsize, sizeof(header.identsize), 1, infile);
	fread(&header.colourmaptype, sizeof(header.colourmaptype), 1, infile);
	fread(&header.imagetype, sizeof(header.imagetype), 1, infile);
	fread(&header.colourmapstart, sizeof(header.colourmapstart), 1, infile);
	fread(&header.colourmaplength, sizeof(header.colourmaplength), 1, infile);
	fread(&header.colourmapbits, sizeof(header.colourmapbits), 1, infile);
	fread(&header.xstart, sizeof(header.xstart), 1, infile);
	fread(&header.ystart, sizeof(header.ystart), 1, infile);
	fread(&header.width, sizeof(header.width), 1, infile);
	fread(&header.height, sizeof(header.height), 1, infile);
	fread(&header.bits, sizeof(header.bits), 1, infile);
	fread(&header.descriptor, sizeof(header.descriptor), 1, infile);
}

// write a TGA header to the current point in the file stream.
static void WriteTGAHeader(FILE *outfile, TGAHeader &header)
{
	if (outfile == NULL)
	{
		return;
	}

	fwrite(&header.identsize, sizeof(header.identsize), 1, outfile);
	fwrite(&header.colourmaptype, sizeof(header.colourmaptype), 1, outfile);
	fwrite(&header.imagetype, sizeof(header.imagetype), 1, outfile);
	fwrite(&header.colourmapstart, sizeof(header.colourmapstart), 1, outfile);
	fwrite(&header.colourmaplength, sizeof(header.colourmaplength), 1, outfile);
	fwrite(&header.colourmapbits, sizeof(header.colourmapbits), 1, outfile);
	fwrite(&header.xstart, sizeof(header.xstart), 1, outfile);
	fwrite(&header.ystart, sizeof(header.ystart), 1, outfile);
	fwrite(&header.width, sizeof(header.width), 1, outfile);
	fwrite(&header.height, sizeof(header.height), 1, outfile);
	fwrite(&header.bits, sizeof(header.bits), 1, outfile);
	fwrite(&header.descriptor, sizeof(header.descriptor), 1, outfile);
}

// reads in a TGA file and converts it to 32 bit RGBA color values in a memory buffer.
unsigned char * ImgUtl_ReadTGAAsRGBA(const char *tgaPath, int &width, int &height, ConversionErrorType &errcode, TGAHeader &tgaHeader )
{
	FILE *tgaFile = fopen(tgaPath, "rb");
	if (tgaFile == NULL)
	{
		errcode = CE_CANT_OPEN_SOURCE_FILE;
		return NULL;
	}

	// read header for TGA file.
	ImgUtl_ReadTGAHeader(tgaFile, tgaHeader);

	if (
		( tgaHeader.imagetype != 2 ) // image type 2 is uncompressed RGB, other types not supported.
		|| ( tgaHeader.descriptor & 0x10 ) // Origin on righthand side (flipped horizontally from common sense) --- nobody ever uses this
		|| ( tgaHeader.bits != 24 && tgaHeader.bits != 32 ) // Must be 24- ot 32-bit
	)
	{
		fclose(tgaFile);

		errcode = CE_SOURCE_FILE_TGA_FORMAT_NOT_SUPPORTED;
		return NULL;
	}

	int tgaDataSize = tgaHeader.width * tgaHeader.height * tgaHeader.bits / 8;
	unsigned char *tgaData = (unsigned char *)malloc(tgaDataSize);
	if (tgaData == NULL)
	{
		fclose(tgaFile);

		errcode = CE_MEMORY_ERROR;
		return NULL;
	}

	fread(tgaData, 1, tgaDataSize, tgaFile);

	fclose(tgaFile);

	width = tgaHeader.width;
	height = tgaHeader.height;
	int numPixels = tgaHeader.width * tgaHeader.height;

	if (tgaHeader.bits == 24)
	{
		// image needs to be converted to a 32-bit image.

		unsigned char *retBuf = (unsigned char *)malloc(numPixels * 4);
		if (retBuf == NULL)
		{
			free(tgaData);

			errcode = CE_MEMORY_ERROR;
			return NULL;
		}

		// convert from BGR to RGBA color format.
		for (int index = 0; index < numPixels; ++index)
		{
			retBuf[index * 4] = tgaData[index * 3 + 2];
			retBuf[index * 4 + 1] = tgaData[index * 3 + 1];
			retBuf[index * 4 + 2] = tgaData[index * 3];
			retBuf[index * 4 + 3] = 0xff;
		}

		free(tgaData);
		tgaData = retBuf;
		tgaHeader.bits = 32;
	}
	else if (tgaHeader.bits == 32)
	{
		// Swap blue and red to convert BGR -> RGB
		for (int index = 0; index < numPixels; ++index)
		{
			V_swap( tgaData[index*4], tgaData[index*4 + 2] );
		}
	}

	// Flip image vertically if necessary
	if ( !( tgaHeader.descriptor & 0x20 ) )
	{
		int y0 = 0;
		int y1 = height-1;
		int iStride = width*4;
		while ( y0 < y1 )
		{
			unsigned char *ptr0 = tgaData + y0*iStride;
			unsigned char *ptr1 = tgaData + y1*iStride;
			for ( int i = 0 ; i < iStride ; ++i )
			{
				V_swap( ptr0[i], ptr1[i] );
			}
			++y0;
			--y1;
		}
		tgaHeader.descriptor |= 0x20;
	}

	errcode = CE_SUCCESS;
	return tgaData;
}

unsigned char *ImgUtl_ReadJPEGAsRGBA( const char *jpegPath, int &width, int &height, ConversionErrorType &errcode )
{
#if !defined( _X360 ) && HAVE_JPEG
	struct jpeg_decompress_struct jpegInfo;
	struct ValveJpegErrorHandler_t jerr;
	JSAMPROW row_pointer[1];
	int row_stride;
	int cur_row = 0;

	// image attributes
	int image_height;
	int image_width;

	// open the jpeg image file.
	FILE *infile = fopen(jpegPath, "rb");
	if (infile == NULL)
	{
		errcode = CE_CANT_OPEN_SOURCE_FILE;
		return NULL;
	}

	//CJpegSourceMgr src;
	//FileHandle_t fileHandle = g_pFullFileSystem->Open( jpegPath, "rb" );
	//if ( fileHandle == FILESYSTEM_INVALID_HANDLE )
	//{
	//	errcode = CE_CANT_OPEN_SOURCE_FILE;
	//	return NULL;
	//}
	//if ( !src.Init( g_pFullFileSystem, fileHandle ) ) {
	//	errcode = CE_CANT_OPEN_SOURCE_FILE;
	//	g_pFullFileSystem->Close( fileHandle );
	//	return NULL;
	//}

	// setup error to print to stderr.
	memset( &jpegInfo, 0, sizeof( jpegInfo ) );
	jpegInfo.err = jpeg_std_error(&jerr.m_Base);

	jpegInfo.err->error_exit = &ValveJpegErrorHandler;

	// create the decompress struct.
	jpeg_create_decompress(&jpegInfo);

	if ( setjmp( jerr.m_ErrorContext ) )
	{
		// Get here if there is any error
		jpeg_destroy_decompress( &jpegInfo );

		fclose( infile );
		//g_pFullFileSystem->Close( fileHandle );

		errcode = CE_ERROR_PARSING_SOURCE;
		return NULL;
	}

	jpeg_stdio_src(&jpegInfo, infile);
	//jpegInfo.src = &src;

	// read in the jpeg header and make sure that's all good.
	if (jpeg_read_header(&jpegInfo, TRUE) != JPEG_HEADER_OK)
	{
		fclose( infile );
		//g_pFullFileSystem->Close( fileHandle );
		errcode = CE_ERROR_PARSING_SOURCE;
		return NULL;
	}

	// start the decompress with the jpeg engine.
	if ( !jpeg_start_decompress(&jpegInfo) )
	{
		jpeg_destroy_decompress(&jpegInfo);
		fclose( infile );
		//g_pFullFileSystem->Close( fileHandle );
		errcode = CE_ERROR_PARSING_SOURCE;
		return NULL;
	}

	// We only support 24-bit JPEG's
	if ( jpegInfo.out_color_space != JCS_RGB || jpegInfo.output_components != 3 )
	{
		jpeg_destroy_decompress(&jpegInfo);
		fclose( infile );
		//g_pFullFileSystem->Close( fileHandle );
		errcode = CE_SOURCE_FILE_SIZE_NOT_SUPPORTED;
		return NULL;
	}

	// now that we've started the decompress with the jpeg lib, we have the attributes of the
	// image ready to be read out of the decompress struct.
	row_stride = jpegInfo.output_width * 4;
	image_height = jpegInfo.image_height;
	image_width = jpegInfo.image_width;
	int mem_required = jpegInfo.image_height * row_stride;

	// allocate the memory to read the image data into.
	unsigned char *buf = (unsigned char *)malloc(mem_required);
	if (buf == NULL)
	{
		jpeg_destroy_decompress(&jpegInfo);
		fclose( infile );
		//g_pFullFileSystem->Close( fileHandle );
		errcode = CE_MEMORY_ERROR;
		return NULL;
	}

	// read in all the scan lines of the image into our image data buffer.
	bool working = true;
	while (working && (jpegInfo.output_scanline < jpegInfo.output_height))
	{
		unsigned char *pRow = &(buf[cur_row * row_stride]);
		row_pointer[0] = pRow;
		if ( !jpeg_read_scanlines(&jpegInfo, row_pointer, 1) )
		{
			working = false;
		}

		// Expand the row RGB -> RGBA
		for ( int x = image_width-1 ; x >= 0 ; --x )
		{
			pRow[x*4+3] = 0xff;
			pRow[x*4+2] = pRow[x*3+2];
			pRow[x*4+1] = pRow[x*3+1];
			pRow[x*4] = pRow[x*3];
		}

		++cur_row;
	}

	// Clean up
	fclose( infile );
	//g_pFullFileSystem->Close( fileHandle );
	jpeg_destroy_decompress(&jpegInfo);

	// Check success status
	if (!working)
	{
		free(buf);
		errcode = CE_ERROR_PARSING_SOURCE;
		return NULL;
	}

	// OK!
	width = image_width;
	height = image_height;
	errcode = CE_SUCCESS;
	return buf;

#else
	errcode = CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
	return NULL;
#endif
}

#if HAVE_PNG
static void ReadPNGData( png_structp png_ptr, png_bytep outBytes, png_size_t byteCountToRead )
{

	// Cast pointer
	CUtlBuffer *pBuf = (CUtlBuffer *)png_get_io_ptr( png_ptr );
	Assert( pBuf );

	// Check for IO error
	if ( pBuf->TellGet() + (int)byteCountToRead > pBuf->TellPut() )
	{
		// Attempt to read past the end of the buffer.
		// Use longjmp to report the error
		png_longjmp( png_ptr, 1 );
	}

	// Read the bytes
	pBuf->Get( outBytes, byteCountToRead );
}
#endif

unsigned char *ImgUtl_ReadPNGAsRGBA( const char *pngPath, int &width, int &height, ConversionErrorType &errcode )
{
#if !defined( _X360 ) && HAVE_PNG

	// Just load the whole file into a memory buffer
	CUtlBuffer bufFileContents;

#if UTILS
	static char buf[8192];
	FILE *readfile = fopen(pngPath, "rb");
	if( !readfile )
	{
		errcode = CE_CANT_OPEN_SOURCE_FILE;
		return NULL;
	}

	size_t size;
	while( ( size = fread(buf, 1, sizeof(buf), readfile ) ) > 0 )
		bufFileContents.Put( buf, size );

	// Load it
	return ImgUtl_ReadPNGAsRGBAFromBuffer( bufFileContents, width, height, errcode );
#else
	if ( !g_pFullFileSystem->ReadFile( pngPath, NULL, bufFileContents ) )
	{
		errcode = CE_CANT_OPEN_SOURCE_FILE;
		return NULL;
	}
#endif

	// Load it
	return ImgUtl_ReadPNGAsRGBAFromBuffer( bufFileContents, width, height, errcode );

#else
	errcode = CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
	return NULL;
#endif
}

unsigned char		*ImgUtl_ReadPNGAsRGBAFromBuffer( CUtlBuffer &buffer, int &width, int &height, ConversionErrorType &errcode )
{
#if !defined( _X360 ) && HAVE_PNG

	png_const_bytep pngData = (png_const_bytep)buffer.Base();
	if (png_sig_cmp( pngData, 0, 8))
	{
        errcode = CE_ERROR_PARSING_SOURCE;
		return NULL;
	}

	png_structp png_ptr = NULL;
	png_infop info_ptr = NULL;

    /* could pass pointers to user-defined error handlers instead of NULLs: */

    png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL );
    if (!png_ptr)
    {
        errcode = CE_MEMORY_ERROR;
		return NULL;
	}

	unsigned char *pResultData = NULL;
	png_bytepp  row_pointers = NULL;

    info_ptr = png_create_info_struct( png_ptr );
    if ( !info_ptr ) 
	{
        errcode = CE_MEMORY_ERROR;
fail:
        png_destroy_read_struct( &png_ptr, &info_ptr, NULL );
        if ( row_pointers )
        {
			free( row_pointers );
		}
        if ( pResultData )
        {
			free( pResultData );
		}
        return NULL;
    }

    /* setjmp() must be called in every function that calls a PNG-reading
     * libpng function */

    if ( setjmp( png_jmpbuf(png_ptr) ) ) 
	{
        errcode = CE_ERROR_PARSING_SOURCE;
        goto fail;
    }

	png_set_read_fn( png_ptr, &buffer, ReadPNGData );
    png_read_info( png_ptr, info_ptr );  /* read all PNG info up to image data */


    /* alternatively, could make separate calls to png_get_image_width(),
     * etc., but want bit_depth and color_type for later [don't care about
     * compression_type and filter_type => NULLs] */

	int bit_depth;
	int color_type;
	uint32 png_width;
	uint32 png_height;

	png_get_IHDR( png_ptr, info_ptr, &png_width, &png_height, &bit_depth, &color_type, NULL, NULL, NULL );

	width = png_width;
	height = png_height;

    png_uint_32 rowbytes;

    /* expand palette images to RGB, low-bit-depth grayscale images to 8 bits,
     * transparency chunks to full alpha channel; strip 16-bit-per-sample
     * images to 8 bits per sample; and convert grayscale to RGB[A] */

    if (color_type == PNG_COLOR_TYPE_PALETTE)
        png_set_expand( png_ptr );
    if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
        png_set_expand( png_ptr );
    if (png_get_valid( png_ptr, info_ptr, PNG_INFO_tRNS ) )
        png_set_expand( png_ptr );
    if (bit_depth == 16)
        png_set_strip_16( png_ptr );
    if (color_type == PNG_COLOR_TYPE_GRAY ||
        color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
        png_set_gray_to_rgb( png_ptr );

	// Force in an alpha channel
	if ( !( color_type & PNG_COLOR_MASK_ALPHA ) )
	{
		png_set_add_alpha(png_ptr, 255, PNG_FILLER_AFTER);
	}

  /*
	double gamma;
  if (png_get_gAMA(png_ptr, info_ptr, &gamma))
        png_set_gamma(png_ptr, display_exponent, gamma);

*/
    /* all transformations have been registered; now update info_ptr data,
     * get rowbytes and channels, and allocate image memory */

    png_read_update_info( png_ptr, info_ptr );

    rowbytes = png_get_rowbytes( png_ptr, info_ptr );
    png_byte channels = (int)png_get_channels( png_ptr, info_ptr );
	if ( channels != 4 )
	{
		Assert( channels == 4 );
        errcode = CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
        goto fail;
	}

	row_pointers = (png_bytepp)malloc( height*sizeof(png_bytep) );
	pResultData = (unsigned char *)malloc( rowbytes*height );

	if ( row_pointers == NULL || pResultData == NULL ) 
	{
        errcode = CE_MEMORY_ERROR;
        goto fail;
    }

    /* set the individual row_pointers to point at the correct offsets */

    for ( int i = 0;  i < height;  ++i)
        row_pointers[i] = pResultData + i*rowbytes;

    /* now we can go ahead and just read the whole image */

    png_read_image( png_ptr, row_pointers );

    png_read_end(png_ptr, NULL);

	free( row_pointers );
	row_pointers = NULL;

	// Clean up
	png_destroy_read_struct( &png_ptr, &info_ptr, NULL );

	// OK!
	width = png_width;
	height = png_height;
	errcode = CE_SUCCESS;
	return pResultData;

#else
	errcode = CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
	return NULL;
#endif
}

unsigned char *ImgUtl_ReadBMPAsRGBA( const char *bmpPath, int &width, int &height, ConversionErrorType &errcode )
{
#ifdef WIN32
	// Load up bitmap
	HBITMAP hBitmap = (HBITMAP)LoadImage(NULL, bmpPath, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE | LR_DEFAULTSIZE);

	// Handle failure
	if ( hBitmap == NULL)
	{

		// !KLUDGE! Try to detect what went wrong
		FILE *fp = fopen( bmpPath, "rb" );
		if (fp == NULL)
		{
			errcode = CE_CANT_OPEN_SOURCE_FILE;
		}
		else
		{
			errcode = CE_ERROR_PARSING_SOURCE;
		}
		return NULL;
	}

	BITMAP bitmap;

	GetObject(hBitmap, sizeof(bitmap), &bitmap);

	BITMAPINFO *bitmapInfo;

	bool bUseColorTable = false;
	if (bitmap.bmBitsPixel == 24 || bitmap.bmBitsPixel == 32)
	{
		bitmapInfo = (BITMAPINFO *)malloc(sizeof(BITMAPINFO));
	}
	else if (bitmap.bmBitsPixel == 8 || bitmap.bmBitsPixel == 4 || bitmap.bmBitsPixel == 1)
	{
		int colorsUsed = 1 << bitmap.bmBitsPixel;
		bitmapInfo = (BITMAPINFO *)malloc(colorsUsed * sizeof(RGBQUAD) + sizeof(BITMAPINFO));
		bUseColorTable = true;
	}
	else
	{
		DeleteObject(hBitmap);
		errcode = CE_SOURCE_FILE_BMP_FORMAT_NOT_SUPPORTED;
		return NULL;
	}

	memset(bitmapInfo, 0, sizeof(BITMAPINFO));
	bitmapInfo->bmiHeader.biSize = sizeof(bitmapInfo->bmiHeader);
	if (bUseColorTable)
	{
		bitmapInfo->bmiHeader.biBitCount = bitmap.bmBitsPixel; // need to specify the bits per pixel so GDI will generate a color table for us.
	}

	HDC dc = CreateCompatibleDC(NULL);

	int retcode = GetDIBits(dc, hBitmap, 0, bitmap.bmHeight, NULL, bitmapInfo, DIB_RGB_COLORS);

	DeleteDC(dc);

	if (retcode == 0)
	{
		// error getting the bitmap info for some reason.
		free(bitmapInfo);
		errcode = CE_SOURCE_FILE_BMP_FORMAT_NOT_SUPPORTED;
		return NULL;
	}

	int nDestStride = 4 * bitmap.bmWidth;
	int mem_required = nDestStride * bitmap.bmHeight;  // mem required for copying the data out into RGBA format.

	unsigned char *buf = (unsigned char *)malloc(mem_required);
	if (buf == NULL)
	{
		free(bitmapInfo);
		errcode = CE_MEMORY_ERROR;
		return NULL;
	}

	if (bitmapInfo->bmiHeader.biBitCount == 32)
	{
		for (int y = 0; y < bitmap.bmHeight; ++y)
		{
			unsigned char *pDest = buf + nDestStride * ( ( bitmap.bmHeight - 1 ) - y ); // BMPs are stored upside down
			const unsigned char *pSrc = (unsigned char *)(bitmap.bmBits) + (y * bitmap.bmWidthBytes);

			for (int x = 0; x < bitmap.bmWidth; ++x)
			{

				// Swap BGR -> RGB while copying data
				pDest[0] = pSrc[2]; // R
				pDest[1] = pSrc[1]; // G
				pDest[2] = pSrc[0]; // B
				pDest[3] = pSrc[3]; // A

				pSrc += 4;
				pDest += 4;
			}
		}
	}
	else if (bitmapInfo->bmiHeader.biBitCount == 24)
	{
		for (int y = 0; y < bitmap.bmHeight; ++y)
		{
			unsigned char *pDest = buf + nDestStride * ( ( bitmap.bmHeight - 1 ) - y ); // BMPs are stored upside down
			const unsigned char *pSrc = (unsigned char *)(bitmap.bmBits) + (y * bitmap.bmWidthBytes);

			for (int x = 0; x < bitmap.bmWidth; ++x)
			{

				// Swap BGR -> RGB while copying data
				pDest[0] = pSrc[2]; // R
				pDest[1] = pSrc[1]; // G
				pDest[2] = pSrc[0]; // B
				pDest[3] = 0xff; // A

				pSrc += 3;
				pDest += 4;
			}
		}
	}
	else if (bitmapInfo->bmiHeader.biBitCount == 8)
	{
		// 8-bit 256 color bitmap.
		for (int y = 0; y < bitmap.bmHeight; ++y)
		{
			unsigned char *pDest = buf + nDestStride * ( ( bitmap.bmHeight - 1 ) - y ); // BMPs are stored upside down
			const unsigned char *pSrc = (unsigned char *)(bitmap.bmBits) + (y * bitmap.bmWidthBytes);

			for (int x = 0; x < bitmap.bmWidth; ++x)
			{

				// compute the color map entry for this pixel
				int colorTableEntry = *pSrc;

				// get the color for this color map entry.
				RGBQUAD *rgbQuad = &(bitmapInfo->bmiColors[colorTableEntry]);

				// copy the color values for this pixel to the destination buffer.
				pDest[0] = rgbQuad->rgbRed;
				pDest[1] = rgbQuad->rgbGreen;
				pDest[2] = rgbQuad->rgbBlue;
				pDest[3] = 0xff;

				++pSrc;
				pDest += 4;
			}
		}
	}
	else if (bitmapInfo->bmiHeader.biBitCount == 4)
	{
		// 4-bit 16 color bitmap.
		for (int y = 0; y < bitmap.bmHeight; ++y)
		{
			unsigned char *pDest = buf + nDestStride * ( ( bitmap.bmHeight - 1 ) - y ); // BMPs are stored upside down
			const unsigned char *pSrc = (unsigned char *)(bitmap.bmBits) + (y * bitmap.bmWidthBytes);

			// Two pixels at a time
			for (int x = 0; x < bitmap.bmWidth; x += 2)
			{

				// get the color table entry for this pixel
				int colorTableEntry = (0xf0 & *pSrc) >> 4;

				// get the color values for this pixel's color table entry.
				RGBQUAD *rgbQuad = &(bitmapInfo->bmiColors[colorTableEntry]);

				// copy the pixel's color values to the destination buffer.
				pDest[0] = pSrc[2]; // R
				pDest[1] = pSrc[1]; // G
				pDest[2] = pSrc[0]; // B
				pDest[3] = 0xff; // A

				// make sure we haven't reached the end of the row.
				if ((x + 1) > bitmap.bmWidth)
				{
					break;
				}

				pDest += 4;

				// get the color table entry for this pixel.
				colorTableEntry = 0x0f & *pSrc;

				// get the color values for this pixel's color table entry.
				rgbQuad = &(bitmapInfo->bmiColors[colorTableEntry]);

				// copy the pixel's color values to the destination buffer.
				pDest[0] = pSrc[2]; // R
				pDest[1] = pSrc[1]; // G
				pDest[2] = pSrc[0]; // B
				pDest[3] = 0xff; // A

				++pSrc;
				pDest += 4;
			}
		}
	}
	else if (bitmapInfo->bmiHeader.biBitCount == 1)
	{
		// 1-bit monochrome bitmap.
		for (int y = 0; y < bitmap.bmHeight; ++y)
		{
			unsigned char *pDest = buf + nDestStride * ( ( bitmap.bmHeight - 1 ) - y ); // BMPs are stored upside down
			const unsigned char *pSrc = (unsigned char *)(bitmap.bmBits) + (y * bitmap.bmWidthBytes);

			// Eight pixels at a time
			int x = 0;
			while (x < bitmap.bmWidth)
			{

				RGBQUAD *rgbQuad = NULL;
				int bitMask = 0x80;

				// go through all 8 bits in this byte to get all 8 pixel colors.
				do
				{
					// get the value of the bit for this pixel.
					int bit = *pSrc & bitMask;

					// bit will either be 0 or non-zero since there are only two colors.
					if (bit == 0)
					{
						rgbQuad = &(bitmapInfo->bmiColors[0]);
					}
					else
					{
						rgbQuad = &(bitmapInfo->bmiColors[1]);
					}

					// copy this pixel's color values into the destination buffer.
					pDest[0] = pSrc[2]; // R
					pDest[1] = pSrc[1]; // G
					pDest[2] = pSrc[0]; // B
					pDest[3] = 0xff; // A
					pDest += 4;

					// go to the next pixel.
					++x;
					bitMask = bitMask >> 1;
				} while ((x < bitmap.bmWidth) && (bitMask > 0));

				++pSrc;
			}
		}
	}
	else
	{
		free(bitmapInfo);
		free(buf);
		DeleteObject(hBitmap);
		errcode = CE_SOURCE_FILE_BMP_FORMAT_NOT_SUPPORTED;
		return NULL;
	}

	free(bitmapInfo);
	DeleteObject(hBitmap);

	// OK!
	width = bitmap.bmWidth;
	height = bitmap.bmHeight;
	errcode = CE_SUCCESS;
	return buf;
#else
	errcode = CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
	return NULL;
#endif
}

unsigned char *ImgUtl_ReadImageAsRGBA( const char *path, int &width, int &height, ConversionErrorType &errcode )
{

	// Split out the file extension
	const char *pExt = V_GetFileExtension( path );
	if ( pExt )
	{
		if ( !Q_stricmp(pExt, "vtf") )
		{
			return ImgUtl_ReadVTFAsRGBA( path, width, height, errcode );
		}
		if ( !Q_stricmp(pExt, "bmp") )
		{
			return ImgUtl_ReadBMPAsRGBA( path, width, height, errcode );
		}
		if ( !Q_stricmp(pExt, "jpg") || !Q_stricmp(pExt, "jpeg") )
		{
			return ImgUtl_ReadJPEGAsRGBA( path, width, height, errcode );
		}
		if ( !Q_stricmp(pExt, "png") )
		{
			return ImgUtl_ReadPNGAsRGBA( path, width, height, errcode );
		}
		if ( !Q_stricmp(pExt, "tga") )
		{
			TGAHeader header;
			return ImgUtl_ReadTGAAsRGBA( path, width, height, errcode, header );
		}
	}

	errcode = CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
	return NULL;
}

// resizes the file specified by tgaPath so that it has dimensions that are
// powers-of-two and is equal to or smaller than (nMaxWidth)x(nMaxHeight).
// also converts from 24-bit RGB to 32-bit RGB (with 8-bit alpha)
ConversionErrorType ImgUtl_ConvertTGA(const char *tgaPath, int nMaxWidth/*=-1*/, int nMaxHeight/*=-1*/)
{
	int tgaWidth = 0, tgaHeight = 0;
	ConversionErrorType errcode;
	TGAHeader tgaHeader;
	unsigned char *srcBuffer = ImgUtl_ReadTGAAsRGBA(tgaPath, tgaWidth, tgaHeight, errcode, tgaHeader);

	if (srcBuffer == NULL)
	{
		return errcode;
	}

	int paddedImageWidth, paddedImageHeight;

	if ((tgaWidth <= 0) || (tgaHeight <= 0))
	{
		free(srcBuffer);
		return CE_ERROR_PARSING_SOURCE;
	}

	// get the nearest power of two that is greater than the width of the image.
	paddedImageWidth = tgaWidth;
	if (!IsPowerOfTwo(paddedImageWidth))
	{
		// width is not a power of two, calculate the next highest power of two value.
		int i = 1;
		while (paddedImageWidth > 1)
		{
			paddedImageWidth = paddedImageWidth >> 1;
			++i;
		}

		paddedImageWidth = paddedImageWidth << i;
	}

	// make sure the width is less than or equal to nMaxWidth
	if (nMaxWidth != -1 && paddedImageWidth > nMaxWidth)
	{
		paddedImageWidth = nMaxWidth;
	}

	// get the nearest power of two that is greater than the height of the image
	paddedImageHeight = tgaHeight;
	if (!IsPowerOfTwo(paddedImageHeight))
	{
		// height is not a power of two, calculate the next highest power of two value.
		int i = 1;
		while (paddedImageHeight > 1)
		{
			paddedImageHeight = paddedImageHeight >> 1;
			++i;
		}

		paddedImageHeight = paddedImageHeight << i;
	}

	// make sure the height is less than or equal to nMaxHeight
	if (nMaxHeight != -1 && paddedImageHeight > nMaxHeight)
	{
		paddedImageHeight = nMaxHeight;
	}

	// compute the amount of stretching that needs to be done to both width and height to get the image to fit.
	float widthRatio = (float)paddedImageWidth / tgaWidth;
	float heightRatio = (float)paddedImageHeight / tgaHeight;

	int finalWidth;
	int finalHeight;

	// compute the final dimensions of the stretched image.
	if (widthRatio < heightRatio)
	{
		finalWidth = paddedImageWidth;
		finalHeight = (int)(tgaHeight * widthRatio + 0.5f);
		// i.e.  for 1x1 size pixels in the resized image we will take color from sourceRatio x sourceRatio sized pixels in the source image.
	}
	else if (heightRatio < widthRatio)
	{
		finalHeight = paddedImageHeight;
		finalWidth = (int)(tgaWidth * heightRatio + 0.5f);
	}
	else
	{
		finalHeight = paddedImageHeight;
		finalWidth = paddedImageWidth;
	}

	unsigned char *resizeBuffer = (unsigned char *)malloc(finalWidth * finalHeight * 4);

	// do the actual stretching
	ImgUtl_StretchRGBAImage(srcBuffer, tgaWidth, tgaHeight, resizeBuffer, finalWidth, finalHeight);

	free(srcBuffer);  // don't need this anymore.

	///////////////////////////////////////////////////////////////////////
	///// need to pad the image so both dimensions are power of two's /////
	///////////////////////////////////////////////////////////////////////
	unsigned char *finalBuffer = (unsigned char *)malloc(paddedImageWidth * paddedImageHeight * 4);
	ImgUtl_PadRGBAImage(resizeBuffer, finalWidth, finalHeight, finalBuffer, paddedImageWidth, paddedImageHeight);

	FILE *outfile = fopen(tgaPath, "wb");
	if (outfile == NULL)
	{
		free(resizeBuffer);
		free(finalBuffer);

		return CE_ERROR_WRITING_OUTPUT_FILE;
	}

	tgaHeader.width = paddedImageWidth;
	tgaHeader.height = paddedImageHeight;

	WriteTGAHeader(outfile, tgaHeader);

	// Write the image data --- remember that TGA uses BGRA data
	int numPixels = paddedImageWidth * paddedImageHeight;
	for (int i = 0 ; i < numPixels ; ++i )
	{
		fputc( finalBuffer[i*4 + 2], outfile ); // B
		fputc( finalBuffer[i*4 + 1], outfile ); // G
		fputc( finalBuffer[i*4    ], outfile ); // R
		fputc( finalBuffer[i*4 + 3], outfile ); // A
	}

	fclose(outfile);

	free(resizeBuffer);
	free(finalBuffer);

	return CE_SUCCESS;
}

// resize by stretching (or compressing) an RGBA image pointed to by srcBuf into the buffer pointed to by destBuf.
// the buffers are assumed to be sized appropriately to accomidate RGBA images of the given widths and heights.
ConversionErrorType ImgUtl_StretchRGBAImage(const unsigned char *srcBuf, const int srcWidth, const int srcHeight,
									 unsigned char *destBuf, const int destWidth, const int destHeight)
{
	if ((srcBuf == NULL) || (destBuf == NULL))
	{
		return CE_CANT_OPEN_SOURCE_FILE;
	}

	int destRow,destColumn;

	float ratioX = (float)srcWidth / (float)destWidth;
	float ratioY = (float)srcHeight / (float)destHeight;

	// loop through all the pixels in the destination image.
	for (destRow = 0; destRow < destHeight; ++destRow)
	{
		for (destColumn = 0; destColumn < destWidth; ++destColumn)
		{
			// calculate the center of the pixel in the source image.
			float srcCenterX = ratioX * (destColumn + 0.5f);
			float srcCenterY = ratioY * (destRow + 0.5f);

			// calculate the starting and ending coords for this destination pixel in the source image.
			float srcStartX = srcCenterX - (ratioX / 2.0f);
			if (srcStartX < 0.0f)
			{
				srcStartX = 0.0f; // this should never happen, but just in case.
			}

			float srcStartY = srcCenterY - (ratioY / 2.0f);
			if (srcStartY < 0.0f)
			{
				srcStartY = 0.0f; // this should never happen, but just in case.
			}

			float srcEndX = srcCenterX + (ratioX / 2.0f);
			if (srcEndX > srcWidth)
			{
				srcEndX = srcWidth; // this should never happen, but just in case.
			}

			float srcEndY = srcCenterY + (ratioY / 2.0f);
			if (srcEndY > srcHeight)
			{
				srcEndY = srcHeight; // this should never happen, but just in case.
			}

			// Calculate the percentage of each source pixels' contribution to the destination pixel color.

			float srcCurrentX; // initialized at the start of the y loop.
			float srcCurrentY = srcStartY;

			float destRed = 0.0f;
			float destGreen = 0.0f;
			float destBlue = 0.0f;
			float destAlpha = 0.0f;

			//// loop for the parts of the source image that will contribute color to the destination pixel.
			while (srcCurrentY < srcEndY)
			{
				float srcCurrentEndY = (float)((int)srcCurrentY + 1);
				if (srcCurrentEndY > srcEndY)
				{
					srcCurrentEndY = srcEndY;
				}

				float srcCurrentHeight = srcCurrentEndY - srcCurrentY;

				srcCurrentX = srcStartX;

				while (srcCurrentX < srcEndX)
				{
					float srcCurrentEndX = (float)((int)srcCurrentX + 1);
					if (srcCurrentEndX > srcEndX)
					{
						srcCurrentEndX = srcEndX;
					}
					float srcCurrentWidth = srcCurrentEndX - srcCurrentX;

					// compute the percentage of the destination pixel's color this source pixel will contribute.
					float srcColorPercentage = (srcCurrentWidth / ratioX) * (srcCurrentHeight / ratioY);

					int srcCurrentPixelX = (int)srcCurrentX;
					int srcCurrentPixelY = (int)srcCurrentY;

					// get the color values for this source pixel.
					unsigned char srcCurrentRed = srcBuf[(srcCurrentPixelY * srcWidth * 4) + (srcCurrentPixelX * 4)];
					unsigned char srcCurrentGreen = srcBuf[(srcCurrentPixelY * srcWidth * 4) + (srcCurrentPixelX * 4) + 1];
					unsigned char srcCurrentBlue = srcBuf[(srcCurrentPixelY * srcWidth * 4) + (srcCurrentPixelX * 4) + 2];
					unsigned char srcCurrentAlpha = srcBuf[(srcCurrentPixelY * srcWidth * 4) + (srcCurrentPixelX * 4) + 3];

					// add the color contribution from this source pixel to the destination pixel.
					destRed += srcCurrentRed * srcColorPercentage;
					destGreen += srcCurrentGreen * srcColorPercentage;
					destBlue += srcCurrentBlue * srcColorPercentage;
					destAlpha += srcCurrentAlpha * srcColorPercentage;

					srcCurrentX = srcCurrentEndX;
				}

				srcCurrentY = srcCurrentEndY;
			}

			// assign the computed color to the destination pixel, round to the nearest value.  Make sure the value doesn't exceed 255.
			destBuf[(destRow * destWidth * 4) + (destColumn * 4)] = min((int)(destRed + 0.5f), 255);
			destBuf[(destRow * destWidth * 4) + (destColumn * 4) + 1] = min((int)(destGreen + 0.5f), 255);
			destBuf[(destRow * destWidth * 4) + (destColumn * 4) + 2] = min((int)(destBlue + 0.5f), 255);
			destBuf[(destRow * destWidth * 4) + (destColumn * 4) + 3] = min((int)(destAlpha + 0.5f), 255);
		} // column loop
	} // row loop

	return CE_SUCCESS;
}

ConversionErrorType ImgUtl_PadRGBAImage(const unsigned char *srcBuf, const int srcWidth, const int srcHeight,
								 unsigned char *destBuf, const int destWidth, const int destHeight)
{
	if ((srcBuf == NULL) || (destBuf == NULL))
	{
		return CE_CANT_OPEN_SOURCE_FILE;
	}

	memset(destBuf, 0, destWidth * destHeight * 4);

	if ((destWidth < srcWidth) || (destHeight < srcHeight))
	{
		return CE_ERROR_PARSING_SOURCE;
	}

	if ((srcWidth == destWidth) && (srcHeight == destHeight))
	{
		// no padding is needed, just copy the buffer straight over and call it done.
		memcpy(destBuf, srcBuf, destWidth * destHeight * 4);
		return CE_SUCCESS;
	}

	if (destWidth == srcWidth)
	{
		// only the top and bottom of the image need padding.
		// do this separately since we can do this more efficiently than the other cases.
		int numRowsToPad = (destHeight - srcHeight) / 2;
		memcpy(destBuf + (numRowsToPad * destWidth * 4), srcBuf, srcWidth * srcHeight * 4);
	}
	else
	{
		int numColumnsToPad = (destWidth - srcWidth) / 2;
		int numRowsToPad = (destHeight - srcHeight) / 2;
		int lastRow = numRowsToPad + srcHeight;
		int row;
		for (row = numRowsToPad; row < lastRow; ++row)
		{
			unsigned char * destOffset = destBuf + (row * destWidth * 4) + (numColumnsToPad * 4);
			const unsigned char * srcOffset = srcBuf + ((row - numRowsToPad) * srcWidth * 4);
			memcpy(destOffset, srcOffset, srcWidth * 4);
		}
	}

	return CE_SUCCESS;
}

// convert TGA file at the given location to a VTF file of the same root name at the same location.
ConversionErrorType ImgUtl_ConvertTGAToVTF(const char *tgaPath, int nMaxWidth/*=-1*/, int nMaxHeight/*=-1*/ )
{
	FILE *infile = fopen(tgaPath, "rb");
	if (infile == NULL)
	{
		Msg( "Failed to open TGA: %s\n", tgaPath);
		return CE_CANT_OPEN_SOURCE_FILE;
	}

	// read out the header of the image.
	TGAHeader header;
	ImgUtl_ReadTGAHeader(infile, header);

	// check to make sure that the TGA has the proper dimensions and size.
	if (!IsPowerOfTwo(header.width) || !IsPowerOfTwo(header.height))
	{
		fclose(infile);
		Msg( "Failed to open TGA - size dimensions (%d, %d) not power of 2: %s\n", header.width, header.height, tgaPath);
		return CE_SOURCE_FILE_SIZE_NOT_SUPPORTED;
	}

	// check to make sure that the TGA isn't too big, if we care.
	if ( ( nMaxWidth != -1 && header.width > nMaxWidth ) || ( nMaxHeight != -1 && header.height > nMaxHeight ) )
	{
		fclose(infile);
		Msg( "Failed to open TGA - dimensions too large (%d, %d) (max: %d, %d): %s\n", header.width, header.height, nMaxWidth, nMaxHeight, tgaPath);
		return CE_SOURCE_FILE_SIZE_NOT_SUPPORTED;
	}

	int imageMemoryFootprint = header.width * header.height * header.bits / 8;

	CUtlBuffer inbuf(0, imageMemoryFootprint);

	// read in the image
	int nBytesRead = fread(inbuf.Base(), imageMemoryFootprint, 1, infile);

	fclose(infile);
	inbuf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead );

	// load vtex_dll.dll and get the interface to it.
	CSysModule *vtexmod = Sys_LoadModule("vtex_dll" DLL_EXT_STRING);
	if (vtexmod == NULL)
	{
		Msg( "Failed to open TGA conversion module vtex_dll: %s\n", tgaPath);
		return CE_ERROR_LOADING_DLL;
	}

	CreateInterfaceFn factory = Sys_GetFactory(vtexmod);
	if (factory == NULL)
	{
		Sys_UnloadModule(vtexmod);
		Msg( "Failed to open TGA conversion module vtex_dll Factory: %s\n", tgaPath);
		return CE_ERROR_LOADING_DLL;
	}

	IVTex *vtex = (IVTex *)factory(IVTEX_VERSION_STRING, NULL);
	if (vtex == NULL)
	{
		Sys_UnloadModule(vtexmod);
		Msg( "Failed to open TGA conversion module vtex_dll Factory (is null): %s\n", tgaPath);
		return CE_ERROR_LOADING_DLL;
	}

	char *vtfParams[4];

	// the 0th entry is skipped cause normally thats the program name.
	vtfParams[0] = "";
	vtfParams[1] = "-quiet";
	vtfParams[2] = "-dontusegamedir";
	vtfParams[3] = (char *)tgaPath;

	// call vtex to do the conversion.
	vtex->VTex(4, vtfParams);  // how do we know this works?

	Sys_UnloadModule(vtexmod);

	return CE_SUCCESS;
}

static void DoCopyFile( const char *source, const char *destination )
{
#if defined( WIN32 )
	CopyFile( source, destination, true );
#elif defined( OSX )
	copyfile( source, destination, NULL, COPYFILE_ALL );
#elif defined( ENGINE_DLL )
	::COM_CopyFile( source, destination );
#elif REPLAY_DLL
	g_pEngine->CopyFile( source, destination );
#elif UTILS
	static char buf[16384];
	FILE *readfile = fopen(source, "rb");
	FILE *writefile = fopen(destination, "wb");

	size_t size = 0;
	while( (size = fread(buf, sizeof(buf), 1, readfile)) != 0 )
		fwrite(buf, size, 1, writefile);

	fclose(readfile);
	fclose(writefile);
#else
	engine->CopyLocalFile( source, destination );
#endif
}

static void DoDeleteFile( const char *filename )
{
#ifdef WIN32
	DeleteFile( filename );
#else
	unlink( filename );
#endif
}

ConversionErrorType	ImgUtl_ConvertToVTFAndDumpVMT( const char *pInPath, const char *pMaterialsSubDir, int nMaxWidth/*=-1*/, int nMaxHeight/*=-1*/ )
{
#ifndef _XBOX
	if ((pInPath == NULL) || (pInPath[0] == 0))
	{
		return CE_ERROR_PARSING_SOURCE;
	}

	ConversionErrorType nErrorCode = CE_SUCCESS;

	// get the extension of the file we're to convert
	char extension[MAX_PATH];
	const char *constchar = pInPath + strlen(pInPath);
	while ((constchar > pInPath) && (*(constchar-1) != '.'))
	{
		--constchar;
	}
	Q_strncpy(extension, constchar, MAX_PATH);

	bool deleteIntermediateTGA = false;
	bool deleteIntermediateVTF = false;
	bool convertTGAToVTF = true;
	char tgaPath[MAX_PATH*2];
	char *c;
	bool failed = false;

	Q_strncpy(tgaPath, pInPath, sizeof(tgaPath));

	// Construct a TGA version if necessary
	if (stricmp(extension, "tga"))
	{
		//  It is not a TGA file, so create a temporary file name for the TGA you have to create

		c = tgaPath + strlen(tgaPath);
		while ((c > tgaPath) && (*(c-1) != '\\') && (*(c-1) != '/'))
		{
			--c;
		}
		*c = 0;

		char origpath[MAX_PATH*2];
		Q_strncpy(origpath, tgaPath, sizeof(origpath));

		//  Look for an empty temp file - find the first one that doesn't exist.
		int index = 0;
		do {
			Q_snprintf(tgaPath, sizeof(tgaPath), "%stemp%d.tga", origpath, index);
			++index;
		} while (_access(tgaPath, 0) != -1);


		//  Convert the other formats to TGA

		//  jpeg files
		//
		if (!stricmp(extension, "jpg") || !stricmp(extension, "jpeg"))
		{
			// convert from the jpeg file format to the TGA file format
			nErrorCode = ImgUtl_ConvertJPEGToTGA(pInPath, tgaPath, false);
			if (nErrorCode == CE_SUCCESS)
			{
				deleteIntermediateTGA = true;
			}
			else
			{
				failed = true;
			}
		}
		//  bmp files
		//
		else if (!stricmp(extension, "bmp"))
		{
			// convert from the bmp file format to the TGA file format
			nErrorCode = ImgUtl_ConvertBMPToTGA(pInPath, tgaPath);

			if (nErrorCode == CE_SUCCESS)
			{
				deleteIntermediateTGA = true;
			}
			else
			{
				failed = true;
			}
		}
		//  vtf files
		//
		else if (!stricmp(extension, "vtf"))
		{
			// if the file is already in the vtf format there's no need to convert it.
			convertTGAToVTF = false;
			
		}
	}

	// if we now have a TGA file, convert it to VTF 
	if (convertTGAToVTF && !failed)
	{
		nErrorCode = ImgUtl_ConvertTGA( tgaPath, nMaxWidth, nMaxHeight ); // resize TGA so that it has power-of-two dimensions with a max size of (nMaxWidth)x(nMaxHeight).
		if (nErrorCode != CE_SUCCESS)
		{
			failed = true;
		}

		if (!failed)
		{
			char tempPath[MAX_PATH*2];
			Q_strncpy(tempPath, tgaPath, sizeof(tempPath));

			nErrorCode = ImgUtl_ConvertTGAToVTF( tempPath, nMaxWidth, nMaxHeight );
			if (nErrorCode == CE_SUCCESS)
			{
				deleteIntermediateVTF = true;
			}
			else
			{
				Msg( "Failed to convert TGA to VTF: %s\n", tempPath);
				failed = true;
			}
		}
	}

	//  At this point everything should be a VTF file

	char finalPath[MAX_PATH*2];
	finalPath[0] = 0;
	char vtfPath[MAX_PATH*2];
	vtfPath[0] = 0;


	//  If we haven't failed so far, create a VMT to go with this VTF 
	if (!failed)
	{

		//  If I had to convert from another filetype (i.e. the original was NOT a .vtf)
		if ( convertTGAToVTF )
		{

			Q_strncpy(vtfPath, tgaPath, sizeof(vtfPath));

			// rename the tga file to be a vtf file.
			c = vtfPath + strlen(vtfPath);
			while ((c > vtfPath) && (*(c-1) != '.'))
			{
				--c;
			}
			*c = 0;
			Q_strncat(vtfPath, "vtf", sizeof(vtfPath), COPY_ALL_CHARACTERS);

		} 
		else
		{
			// We were handed a vtf file originally, so use it.
			Q_strncpy(vtfPath, pInPath, sizeof(vtfPath));
		}

		// get the vtfFilename from the path.
		const char *vtfFilename = pInPath + strlen(pInPath);
		while ((vtfFilename > pInPath) && (*(vtfFilename-1) != '\\') && (*(vtfFilename-1) != '/'))
		{
			--vtfFilename;
		}

		// Create a safe version of pOutDir with corrected slashes
		char szOutDir[MAX_PATH*2];
		V_strcpy_safe( szOutDir, IsPosix() ? "/materials/" : "\\materials\\" );
		if ( pMaterialsSubDir[0] == '\\' || pMaterialsSubDir[0] == '/' )
			pMaterialsSubDir = pMaterialsSubDir + 1;
		V_strcat_safe(szOutDir, pMaterialsSubDir, sizeof(szOutDir) );
		Q_StripTrailingSlash( szOutDir );
		Q_AppendSlash( szOutDir, sizeof(szOutDir) );
		Q_FixSlashes( szOutDir, CORRECT_PATH_SEPARATOR );

#ifdef ENGINE_DLL
		Q_strncpy(finalPath, com_gamedir, sizeof(finalPath));
#elif REPLAY_DLL
		Q_strncpy(finalPath, g_pEngine->GetGameDir(), sizeof(finalPath));
#elif !UTILS
		Q_strncpy(finalPath, engine->GetGameDirectory(), sizeof(finalPath));
#endif
		Q_strncat(finalPath, szOutDir, sizeof(finalPath), COPY_ALL_CHARACTERS);
		Q_strncat(finalPath, vtfFilename, sizeof(finalPath), COPY_ALL_CHARACTERS);

		c = finalPath + strlen(finalPath);
		while ((c > finalPath) && (*(c-1) != '.'))
		{
			--c;
		}
		*c = 0;
		Q_strncat(finalPath,"vtf", sizeof(finalPath), COPY_ALL_CHARACTERS);

		// make sure the directory exists before we try to copy the file.
		g_pFullFileSystem->CreateDirHierarchy(szOutDir + 1, "GAME");
		//g_pFullFileSystem->CreateDirHierarchy("materials/VGUI/logos/", "GAME");

		// write out the spray VMT file.
		if ( strcmp(vtfPath, finalPath) )  // If they're not already the same
		{
			nErrorCode = ImgUtl_WriteGenericVMT(finalPath, pMaterialsSubDir);
			if (nErrorCode != CE_SUCCESS)
			{
				failed = true;
			}

			if (!failed)
			{
				// copy vtf file to the final location, only if we're not already in vtf

				DoCopyFile( vtfPath, finalPath );
			}
		}
	}

	// delete the intermediate VTF file if one was made.
	if (deleteIntermediateVTF)
	{
		DoDeleteFile( vtfPath );
		
		// the TGA->VTF conversion process generates a .txt file if one wasn't already there.
		// in this case, delete the .txt file.
		c = vtfPath + strlen(vtfPath);
		while ((c > vtfPath) && (*(c-1) != '.'))
		{
			--c;
		}
		Q_strncpy(c, "txt", sizeof(vtfPath)-(c-vtfPath));

		DoDeleteFile( vtfPath );
	}

	// delete the intermediate TGA file if one was made.
	if (deleteIntermediateTGA)
	{
		DoDeleteFile( tgaPath );
	}

	return nErrorCode;
#endif
}

ConversionErrorType ImgUtl_WriteGenericVMT( const char *vtfPath, const char *pMaterialsSubDir )
{
	if (vtfPath == NULL || pMaterialsSubDir == NULL )
	{
		return CE_ERROR_WRITING_OUTPUT_FILE;
	}

	// make the vmt filename
	char vmtPath[MAX_PATH*4];
	Q_strncpy(vmtPath, vtfPath, sizeof(vmtPath));
	char *c = vmtPath + strlen(vmtPath);
	while ((c > vmtPath) && (*(c-1) != '.'))
	{
		--c;
	}
	Q_strncpy(c, "vmt", sizeof(vmtPath) - (c - vmtPath));

	// get the root filename for the vtf file
	char filename[MAX_PATH];
	while ((c > vmtPath) && (*(c-1) != '/') && (*(c-1) != '\\'))
	{
		--c;
	}

	int i = 0;
	while ((*c != 0) && (*c != '.'))
	{
		filename[i++] = *(c++);
	}
	filename[i] = 0;

	// create the vmt file.
	FILE *vmtFile = fopen(vmtPath, "w");
	if (vmtFile == NULL)
	{
		return CE_ERROR_WRITING_OUTPUT_FILE;
	}

	// make a copy of the subdir and remove any trailing slash
	char szMaterialsSubDir[ MAX_PATH*2 ];
	V_strcpy_safe( szMaterialsSubDir, pMaterialsSubDir );
	V_StripTrailingSlash( szMaterialsSubDir );

	// fix slashes
	V_FixSlashes( szMaterialsSubDir );

	// write the contents of the file.
	fprintf(vmtFile, "\"UnlitGeneric\"\n{\n\t\"$basetexture\"	\"%s%c%s\"\n\t\"$translucent\" \"1\"\n\t\"$ignorez\" \"1\"\n\t\"$vertexcolor\" \"1\"\n\t\"$vertexalpha\" \"1\"\n}\n", szMaterialsSubDir, CORRECT_PATH_SEPARATOR, filename);

	fclose(vmtFile);

	return CE_SUCCESS;
}

#if HAVE_PNG
static void WritePNGData( png_structp png_ptr, png_bytep inBytes, png_size_t byteCountToWrite )
{

	// Cast pointer
	CUtlBuffer *pBuf = (CUtlBuffer *)png_get_io_ptr( png_ptr );
	Assert( pBuf );

	// Write the bytes
	pBuf->Put( inBytes, byteCountToWrite );

	// What?  Put() returns void.  No way to detect error?
}

static void FlushPNGData( png_structp png_ptr )
{
	// We're writing to a memory buffer, it's a NOP
}

ConversionErrorType ImgUtl_WriteRGBAAsPNGToBuffer( const unsigned char *pRGBAData, int nWidth, int nHeight, CUtlBuffer &bufOutData, int nStride )
{
#if !defined( _X360 )
	// Auto detect image stride
	if ( nStride <= 0 )
	{
		nStride = nWidth*4;
	}

    /* could pass pointers to user-defined error handlers instead of NULLs: */
	png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
		NULL, NULL, NULL);
	if (png_ptr == NULL)
	{
		return CE_MEMORY_ERROR;
	}

	ConversionErrorType errcode = CE_MEMORY_ERROR;

	png_bytepp  row_pointers = NULL;

	png_infop info_ptr = png_create_info_struct(png_ptr);
    if ( !info_ptr ) 
	{
        errcode = CE_MEMORY_ERROR;
fail:
		if ( row_pointers )
		{
			free( row_pointers );
		}
        png_destroy_write_struct( &png_ptr, &info_ptr );
        return errcode;
    }

	// We'll use the default setjmp / longjmp error handling.
    if ( setjmp( png_jmpbuf(png_ptr) ) ) 
	{
		// Error "writing".  But since we're writing to a memory bufferm,
		// that just means we must have run out of memory
        errcode = CE_MEMORY_ERROR;
        goto fail;
    }

	// Setup stream writing callbacks
	png_set_write_fn(png_ptr, (void *)&bufOutData, WritePNGData, FlushPNGData);

	// Setup info structure
	png_set_IHDR(png_ptr, info_ptr, nWidth, nHeight, 8, PNG_COLOR_TYPE_RGB_ALPHA,
		PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

	// !FIXME! Here we really should scan for the common case of
	// an opaque image (all alpha=255) and strip the alpha channel
	// in that case.

	// Write the file header information.
	png_write_info(png_ptr, info_ptr);

	row_pointers = (png_bytepp)malloc( nHeight*sizeof(png_bytep) );
	if ( row_pointers == NULL  ) 
	{
        errcode = CE_MEMORY_ERROR;
        goto fail;
    }

    /* set the individual row_pointers to point at the correct offsets */
    for ( int i = 0;  i < nHeight;  ++i)
        row_pointers[i] = const_cast<unsigned char *>(pRGBAData + i*nStride);

	// Write the image
	png_write_image(png_ptr, row_pointers);

	/* It is REQUIRED to call this to finish writing the rest of the file */
	png_write_end(png_ptr, info_ptr);

	// Clean up, and we're done
	free( row_pointers );
	row_pointers = NULL;
	png_destroy_write_struct(&png_ptr, &info_ptr);
	return CE_SUCCESS;
#else
	return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
#endif
}
#endif

//-----------------------------------------------------------------------------
// Purpose:  Initialize destination --- called by jpeg_start_compress
//  before any data is actually written.
//-----------------------------------------------------------------------------
#if HAVE_JPEG
METHODDEF(void) init_destination (j_compress_ptr cinfo)
{
	JPEGDestinationManager_t *dest = ( JPEGDestinationManager_t *) cinfo->dest;

	// Allocate the output buffer --- it will be released when done with image
	dest->buffer = (byte *)
		(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
		JPEG_OUTPUT_BUF_SIZE * sizeof(byte));

	dest->pub.next_output_byte = dest->buffer;
	dest->pub.free_in_buffer = JPEG_OUTPUT_BUF_SIZE;
}

//-----------------------------------------------------------------------------
// Purpose: Empty the output buffer --- called whenever buffer fills up.
// Input  : boolean - 
//-----------------------------------------------------------------------------
METHODDEF(boolean) empty_output_buffer (j_compress_ptr cinfo)
{
	JPEGDestinationManager_t *dest = ( JPEGDestinationManager_t * ) cinfo->dest;

	CUtlBuffer *buf = dest->pBuffer;

	// Add some data
	buf->Put( dest->buffer, JPEG_OUTPUT_BUF_SIZE );

	dest->pub.next_output_byte = dest->buffer;
	dest->pub.free_in_buffer = JPEG_OUTPUT_BUF_SIZE;

	return TRUE;
}

//-----------------------------------------------------------------------------
// Purpose: Terminate destination --- called by jpeg_finish_compress
// after all data has been written.  Usually needs to flush buffer.
//
// NB: *not* called by jpeg_abort or jpeg_destroy; surrounding
// application must deal with any cleanup that should happen even
// for error exit.
//-----------------------------------------------------------------------------
METHODDEF(void) term_destination (j_compress_ptr cinfo)
{
	JPEGDestinationManager_t *dest = (JPEGDestinationManager_t *) cinfo->dest;
	size_t datacount = JPEG_OUTPUT_BUF_SIZE - dest->pub.free_in_buffer;

	CUtlBuffer *buf = dest->pBuffer;

	/* Write any data remaining in the buffer */
	if (datacount > 0) 
	{
		buf->Put( dest->buffer, datacount );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Set up functions for writing data to a CUtlBuffer instead of FILE *
//-----------------------------------------------------------------------------
GLOBAL(void) jpeg_UtlBuffer_dest (j_compress_ptr cinfo, CUtlBuffer *pBuffer )
{
    JPEGDestinationManager_t *dest;
    
    /* The destination object is made permanent so that multiple JPEG images
    * can be written to the same file without re-executing jpeg_stdio_dest.
    * This makes it dangerous to use this manager and a different destination
    * manager serially with the same JPEG object, because their private object
    * sizes may be different.  Caveat programmer.
    */
    if (cinfo->dest == NULL) {  /* first time for this JPEG object? */
        cinfo->dest = (struct jpeg_destination_mgr *)
            (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
            sizeof(JPEGDestinationManager_t));
    }
    
    dest = ( JPEGDestinationManager_t * ) cinfo->dest;

    dest->pub.init_destination      = init_destination;
    dest->pub.empty_output_buffer   = empty_output_buffer;
    dest->pub.term_destination      = term_destination;
    dest->pBuffer                   = pBuffer;
}
#endif

//-----------------------------------------------------------------------------
// Purpose: Write three channel RGB data to a JPEG file
//-----------------------------------------------------------------------------
bool ImgUtl_WriteRGBToJPEG( unsigned char *pSrcBuf, unsigned int nSrcWidth, unsigned int nSrcHeight, const char *lpszFilename )
{
#if HAVE_JPEG
	CUtlBuffer dstBuf;

	JSAMPROW row_pointer[1];     // pointer to JSAMPLE row[s]
	int row_stride;              // physical row width in image buffer

	// stderr handler
	struct jpeg_error_mgr jerr;

	// compression data structure
	struct jpeg_compress_struct cinfo;

	row_stride = nSrcWidth * 3; // JSAMPLEs per row in image_buffer

	// point at stderr
	cinfo.err = jpeg_std_error(&jerr);

	// create compressor
	jpeg_create_compress(&cinfo);

	// Hook CUtlBuffer to compression
	jpeg_UtlBuffer_dest(&cinfo, &dstBuf );

	// image width and height, in pixels
	cinfo.image_width = nSrcWidth;
	cinfo.image_height = nSrcHeight;
	// RGB is 3 component
	cinfo.input_components = 3;
	// # of color components per pixel
	cinfo.in_color_space = JCS_RGB;

	// Apply settings
	jpeg_set_defaults(&cinfo);
	jpeg_set_quality(&cinfo, 100, TRUE );

	// Start compressor
	jpeg_start_compress(&cinfo, TRUE);

	// Write scanlines
	while ( cinfo.next_scanline < cinfo.image_height ) 
	{
		row_pointer[ 0 ] = &pSrcBuf[ cinfo.next_scanline * row_stride ];
		jpeg_write_scanlines( &cinfo, row_pointer, 1 );
	}

	// Finalize image
	jpeg_finish_compress(&cinfo);

	// Cleanup
	jpeg_destroy_compress(&cinfo);

	return CE_SUCCESS;
#else
	return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
#endif
}

ConversionErrorType ImgUtl_WriteRGBAAsJPEGToBuffer( const unsigned char *pRGBAData, int nWidth, int nHeight, CUtlBuffer &bufOutData, int nStride )
{
#if !defined( _X360 ) && HAVE_JPEG

	JSAMPROW row_pointer[1];     // pointer to JSAMPLE row[s]
	int row_stride;              // physical row width in image buffer

	// stderr handler
	struct jpeg_error_mgr jerr;

	// compression data structure
	struct jpeg_compress_struct cinfo;

	row_stride = nWidth * 4;

	// point at stderr
	cinfo.err = jpeg_std_error(&jerr);

	// create compressor
	jpeg_create_compress(&cinfo);

	// Hook CUtlBuffer to compression
	jpeg_UtlBuffer_dest(&cinfo, &bufOutData );

	// image width and height, in pixels
	cinfo.image_width = nWidth;
	cinfo.image_height = nHeight;
	// RGB is 3 component
	cinfo.input_components = 3;
	// # of color components per pixel
	cinfo.in_color_space = JCS_RGB;

	// Apply settings
	jpeg_set_defaults(&cinfo);
	jpeg_set_quality(&cinfo, 100, TRUE );

	// Start compressor
	jpeg_start_compress(&cinfo, TRUE);

	// Write scanlines
	unsigned char *pDstRow = (unsigned char *)malloc( sizeof(unsigned char) * nWidth * 4 );
	while ( cinfo.next_scanline < cinfo.image_height ) 
	{
		const unsigned char *pSrcRow = &(pRGBAData[cinfo.next_scanline * row_stride]);
		// convert row from RGBA to RGB
		for ( int x = nWidth-1 ; x >= 0 ; --x )
		{
			pDstRow[x*3+2] = pSrcRow[x*4+2];
			pDstRow[x*3+1] = pSrcRow[x*4+1];
			pDstRow[x*3] = pSrcRow[x*4];
		}
		row_pointer[ 0 ] = pDstRow;
		jpeg_write_scanlines( &cinfo, row_pointer, 1 );
	}

	// Finalize image
	jpeg_finish_compress(&cinfo);

	// Cleanup
	jpeg_destroy_compress(&cinfo);

	free( pDstRow );

	return CE_SUCCESS;
#else
	return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
#endif
}

ConversionErrorType ImgUtl_LoadBitmap( const char *pszFilename, Bitmap_t &bitmap )
{
	bitmap.Clear();
	ConversionErrorType nErrorCode;
	int width, height;
	unsigned char *buffer = ImgUtl_ReadImageAsRGBA( pszFilename, width, height, nErrorCode );
	if ( nErrorCode != CE_SUCCESS )
	{
		return nErrorCode;
	}

	// Install the buffer into the bitmap, and transfer ownership
	bitmap.SetBuffer( width, height, IMAGE_FORMAT_RGBA8888, buffer, true, width*4 );
	return CE_SUCCESS;
}

static ConversionErrorType ImgUtl_LoadJPEGBitmapFromBuffer( CUtlBuffer &fileData, Bitmap_t &bitmap )
{
	// @todo implement
	return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
}

static ConversionErrorType ImgUtl_SaveJPEGBitmapToBuffer( CUtlBuffer &fileData, const Bitmap_t &bitmap )
{
	if ( !bitmap.IsValid() )
	{
		Assert( bitmap.IsValid() );
		return CE_CANT_OPEN_SOURCE_FILE;
	}

	// Sorry, only RGBA8888 supported right now
	if ( bitmap.Format() != IMAGE_FORMAT_RGBA8888 )
	{
		Assert( bitmap.Format() == IMAGE_FORMAT_RGBA8888 );
		return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
	}

	// Do it
	ConversionErrorType result = ImgUtl_WriteRGBAAsJPEGToBuffer(
		bitmap.GetBits(),
		bitmap.Width(),
		bitmap.Height(),
		fileData,
		bitmap.Stride()
		);
	return result;
}

ConversionErrorType ImgUtl_LoadBitmapFromBuffer( CUtlBuffer &fileData, Bitmap_t &bitmap, ImageFileFormat eImageFileFormat )
{
	switch ( eImageFileFormat )
	{
	case kImageFileFormat_PNG:
		return ImgUtl_LoadPNGBitmapFromBuffer( fileData, bitmap );
	case kImageFileFormat_JPG:
		return ImgUtl_LoadJPEGBitmapFromBuffer( fileData, bitmap );
	}
	return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
}

ConversionErrorType ImgUtl_SaveBitmapToBuffer( CUtlBuffer &fileData, const Bitmap_t &bitmap, ImageFileFormat eImageFileFormat )
{
	switch ( eImageFileFormat )
	{
	case kImageFileFormat_PNG:
		return ImgUtl_SavePNGBitmapToBuffer( fileData, bitmap );
	case kImageFileFormat_JPG:
		return ImgUtl_SaveJPEGBitmapToBuffer( fileData, bitmap );
	}
	return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
}

ConversionErrorType ImgUtl_LoadPNGBitmapFromBuffer( CUtlBuffer &fileData, Bitmap_t &bitmap )
{
#if HAVE_PNG
	bitmap.Clear();
	ConversionErrorType nErrorCode;
	int width, height;
	unsigned char *buffer = ImgUtl_ReadPNGAsRGBAFromBuffer( fileData, width, height, nErrorCode );
	if ( nErrorCode != CE_SUCCESS )
	{
		return nErrorCode;
	}

	// Install the buffer into the bitmap, and transfer ownership
	bitmap.SetBuffer( width, height, IMAGE_FORMAT_RGBA8888, buffer, true, width*4 );
	return CE_SUCCESS;
#else
	return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
#endif
}

ConversionErrorType ImgUtl_SavePNGBitmapToBuffer( CUtlBuffer &fileData, const Bitmap_t &bitmap )
{
#if HAVE_PNG
	if ( !bitmap.IsValid() )
	{
		Assert( bitmap.IsValid() );
		return CE_CANT_OPEN_SOURCE_FILE;
	}

	// Sorry, only RGBA8888 supported right now
	if ( bitmap.Format() != IMAGE_FORMAT_RGBA8888 )
	{
		Assert( bitmap.Format() == IMAGE_FORMAT_RGBA8888 );
		return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
	}

	// Do it
	ConversionErrorType result = ImgUtl_WriteRGBAAsPNGToBuffer(
		bitmap.GetBits(),
		bitmap.Width(),
		bitmap.Height(),
		fileData,
		bitmap.Stride()
	);
	return result;
#else
	return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
#endif
}

ConversionErrorType ImgUtl_ResizeBitmap( Bitmap_t &destBitmap, int nWidth, int nHeight, const Bitmap_t *pImgSource )
{

	// Check for resizing in place, then save off data into a temp
	Bitmap_t temp;
	if ( pImgSource == NULL || pImgSource == &destBitmap )
	{
		temp.MakeLogicalCopyOf( destBitmap, destBitmap.GetOwnsBuffer() );
		pImgSource = &temp;
	}

	// No source image?
	if ( !pImgSource->IsValid() )
	{
		Assert( pImgSource->IsValid() );
		return CE_CANT_OPEN_SOURCE_FILE;
	}

	// Sorry, we're using an existing rescaling routine that
	// only withs for RGBA images with assumed stride
	if (
		pImgSource->Format() != IMAGE_FORMAT_RGBA8888
		|| pImgSource->Stride() != pImgSource->Width()*4
	) {
		Assert( pImgSource->Format() == IMAGE_FORMAT_RGBA8888 );
		Assert( pImgSource->Stride() == pImgSource->Width()*4 );
		return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED;
	}

	// Allocate buffer
	destBitmap.Init( nWidth, nHeight, IMAGE_FORMAT_RGBA8888 );

	// Something wrong?
	if ( !destBitmap.IsValid() )
	{
		Assert( destBitmap.IsValid() );
		return CE_MEMORY_ERROR;
	}

	// Do it
	return ImgUtl_StretchRGBAImage(
		pImgSource->GetBits(), pImgSource->Width(), pImgSource->Height(),
		destBitmap.GetBits(), destBitmap.Width(), destBitmap.Height()
	);
}
