2020-04-22 16:56:21 +00:00
//========= 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"
2022-09-17 15:25:20 +00:00
# elif UTILS
// OwO
2020-04-22 16:56:21 +00:00
# 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
2022-09-17 15:25:20 +00:00
# if ANDROID
# include "android/jpeglib/jpeglib.h"
# else
2020-04-22 16:56:21 +00:00
# include "jpeglib/jpeglib.h"
2022-09-17 15:25:20 +00:00
# endif
2020-04-22 16:56:21 +00:00
# undef JPEGLIB_USE_STDIO
2022-09-17 15:25:20 +00:00
# if HAVE_PNG
2022-09-20 15:17:55 +00:00
# if ANDROID || WIN32
2020-04-22 16:56:21 +00:00
# include "libpng/png.h"
2022-09-17 15:25:20 +00:00
# else
# include <png.h>
# endif
# endif
2020-04-22 16:56:21 +00:00
# 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:
//-----------------------------------------------------------------------------
2022-08-19 20:19:53 +00:00
# if HAVE_JPEG
2020-04-22 16:56:21 +00:00
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 ) ;
}
2022-08-19 20:19:53 +00:00
# endif
2020-04-22 16:56:21 +00:00
// 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 )
{
2022-08-19 20:19:53 +00:00
# if !defined( _X360 ) && HAVE_JPEG
2020-04-22 16:56:21 +00:00
//
// !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 )
{
2022-08-19 20:19:53 +00:00
# if !defined( _X360 ) && HAVE_JPEG
2020-04-22 16:56:21 +00:00
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
}
2022-08-19 20:19:53 +00:00
# if HAVE_PNG
2020-04-22 16:56:21 +00:00
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 ) ;
}
2022-08-19 20:19:53 +00:00
# endif
2020-04-22 16:56:21 +00:00
unsigned char * ImgUtl_ReadPNGAsRGBA ( const char * pngPath , int & width , int & height , ConversionErrorType & errcode )
{
2022-08-19 20:19:53 +00:00
# if !defined( _X360 ) && HAVE_PNG
2020-04-22 16:56:21 +00:00
// Just load the whole file into a memory buffer
CUtlBuffer bufFileContents ;
2022-09-17 15:25:20 +00:00
# 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
2020-04-22 16:56:21 +00:00
if ( ! g_pFullFileSystem - > ReadFile ( pngPath , NULL , bufFileContents ) )
{
errcode = CE_CANT_OPEN_SOURCE_FILE ;
return NULL ;
}
2022-09-17 15:25:20 +00:00
# endif
2020-04-22 16:56:21 +00:00
// 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 )
{
2022-08-19 20:19:53 +00:00
# if !defined( _X360 ) && HAVE_PNG
2020-04-22 16:56:21 +00:00
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.
2022-09-17 15:25:20 +00:00
CSysModule * vtexmod = Sys_LoadModule ( " vtex_dll " DLL_EXT_STRING ) ;
2020-04-22 16:56:21 +00:00
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 ) ;
2022-09-17 15:25:20 +00:00
# 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 ) ;
2020-04-22 16:56:21 +00:00
# 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 ) ) ;
2022-09-17 15:25:20 +00:00
# elif !UTILS
2020-04-22 16:56:21 +00:00
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 ) ;
2022-09-17 15:25:20 +00:00
2020-04-22 16:56:21 +00:00
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 ;
}
2022-09-17 15:25:20 +00:00
# if HAVE_PNG
2020-04-22 16:56:21 +00:00
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
}
2022-08-19 20:19:53 +00:00
# endif
2020-04-22 16:56:21 +00:00
//-----------------------------------------------------------------------------
// Purpose: Initialize destination --- called by jpeg_start_compress
// before any data is actually written.
//-----------------------------------------------------------------------------
2022-08-19 20:19:53 +00:00
# if HAVE_JPEG
2020-04-22 16:56:21 +00:00
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 ;
}
2022-08-19 20:19:53 +00:00
# endif
2020-04-22 16:56:21 +00:00
//-----------------------------------------------------------------------------
// 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 )
{
2022-08-19 20:19:53 +00:00
# if HAVE_JPEG
2020-04-22 16:56:21 +00:00
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 ) ;
2022-08-19 20:19:53 +00:00
2020-04-22 16:56:21 +00:00
return CE_SUCCESS ;
2022-08-19 20:19:53 +00:00
# else
return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED ;
# endif
2020-04-22 16:56:21 +00:00
}
ConversionErrorType ImgUtl_WriteRGBAAsJPEGToBuffer ( const unsigned char * pRGBAData , int nWidth , int nHeight , CUtlBuffer & bufOutData , int nStride )
{
2022-08-19 20:19:53 +00:00
# if !defined( _X360 ) && HAVE_JPEG
2020-04-22 16:56:21 +00:00
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 )
{
2022-08-19 20:19:53 +00:00
# if HAVE_PNG
2020-04-22 16:56:21 +00:00
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 ;
2022-08-19 20:19:53 +00:00
# else
return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED ;
# endif
2020-04-22 16:56:21 +00:00
}
ConversionErrorType ImgUtl_SavePNGBitmapToBuffer ( CUtlBuffer & fileData , const Bitmap_t & bitmap )
{
2022-08-19 20:19:53 +00:00
# if HAVE_PNG
2020-04-22 16:56:21 +00:00
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 ;
2022-08-19 20:19:53 +00:00
# else
return CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED ;
# endif
2020-04-22 16:56:21 +00:00
}
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 ( )
) ;
}