summaryrefslogtreecommitdiff
path: root/common/imageutils.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /common/imageutils.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'common/imageutils.cpp')
-rw-r--r--common/imageutils.cpp2301
1 files changed, 2301 insertions, 0 deletions
diff --git a/common/imageutils.cpp b/common/imageutils.cpp
new file mode 100644
index 0000000..c9ea318
--- /dev/null
+++ b/common/imageutils.cpp
@@ -0,0 +1,2301 @@
+//========= 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"
+#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
+#include "jpeglib/jpeglib.h"
+#undef JPEGLIB_USE_STDIO
+
+#include "libpng/png.h"
+
+#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:
+//-----------------------------------------------------------------------------
+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 );
+}
+
+
+// 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 )
+
+ //
+ // !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 )
+ 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
+}
+
+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 );
+}
+
+
+unsigned char *ImgUtl_ReadPNGAsRGBA( const char *pngPath, int &width, int &height, ConversionErrorType &errcode )
+{
+#if !defined( _X360 )
+
+ // Just load the whole file into a memory buffer
+ CUtlBuffer bufFileContents;
+ if ( !g_pFullFileSystem->ReadFile( pngPath, NULL, bufFileContents ) )
+ {
+ errcode = CE_CANT_OPEN_SOURCE_FILE;
+ return NULL;
+ }
+
+ // 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 )
+
+ 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");
+ 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 );
+#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));
+#else
+ 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;
+}
+
+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
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initialize destination --- called by jpeg_start_compress
+// before any data is actually written.
+//-----------------------------------------------------------------------------
+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;
+}
+
+//-----------------------------------------------------------------------------
+// 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 )
+{
+ 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;
+}
+
+ConversionErrorType ImgUtl_WriteRGBAAsJPEGToBuffer( const unsigned char *pRGBAData, int nWidth, int nHeight, CUtlBuffer &bufOutData, int nStride )
+{
+#if !defined( _X360 )
+
+ 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 )
+{
+ 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;
+}
+
+ConversionErrorType ImgUtl_SavePNGBitmapToBuffer( 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_WriteRGBAAsPNGToBuffer(
+ bitmap.GetBits(),
+ bitmap.Width(),
+ bitmap.Height(),
+ fileData,
+ bitmap.Stride()
+ );
+ return result;
+}
+
+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()
+ );
+}