2015-12-18 12:02:31 +00:00
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
# include "color_modifier.h"
# include "color_mask.h"
# include "hls_bank_texture_info.h"
# define HAS_INFO_GENERATION 0
# if HAS_INFO_GENERATION
# include "info_color_generation.h"
# include "info_mask_generation.h"
# endif
# include <nel/misc/types_nl.h>
# include <nel/misc/config_file.h>
# include <nel/misc/path.h>
# include <nel/misc/file.h>
# include <nel/misc/bitmap.h>
# include <nel/misc/debug.h>
# include <time.h>
using namespace NLMISC ;
using namespace std ;
string DivideBy2Dir = " /d4/ " ;
//string HlsInfoDir= "hlsInfo/";
// ========================================================================================================
// This tool is for creating various colored texture from a base texture.
// Parts of a base texture can have hue, contrast, luminosity shifting etc.
// Each part is defined by a mask. The red component of it is considered as an alpha value (not the alpha, because it is faster to create a grey texture with photoshop..)
// The result is serialized in png or tga files.
// ========================================================================================================
// why this tool ? : it is useful to create various colored cloth and skin textures
// Not all hardware allow it to manage that at runtime (lack for palettized textures or pixel shaders...)
//=========================================================================================================
/// describes the building infos
struct CBuildInfo
{
2015-12-28 15:44:35 +00:00
std : : string InputPath ;
std : : string OutputPath ;
std : : string HlsInfoPath ;
std : : string CachePath ;
std : : vector < std : : string > BitmapExtensions ; // the supported extension for bitmaps
std : : string OutputFormat ; // png or tga
std : : string DefaultSeparator ;
TColorMaskVect ColorMasks ;
2015-12-18 12:02:31 +00:00
// how to shift right the size of the src Bitmap for the .hlsinfo
2015-12-28 15:44:35 +00:00
uint LowDefShift ;
2016-01-06 12:22:47 +00:00
uint OptimizeTextures ; // 0 = don't check, 1 = check
2015-12-18 12:02:31 +00:00
} ;
/// Temporary
static void validateCgiInfo ( ) ;
static void validateGtmInfo ( ) ;
/// Temporary
/** Build the infos we need from a config file
* It build a list of masks infos
*/
static void BuildMasksFromConfigFile ( NLMISC : : CConfigFile & cf ,
TColorMaskVect & colorMasks ) ;
/// Build the colored versions
static void BuildColoredVersions ( const CBuildInfo & bi ) ;
///
static void BuildColoredVersionForOneBitmap ( const CBuildInfo & bi , const std : : string & fileNameWithExtension , bool mustDivideBy2 ) ;
/** Check if building if reneeded by looking in the cache directory.
* If the texture is found in the cache it is just copied
*/
static bool CheckIfNeedRebuildColoredVersionForOneBitmap ( const CBuildInfo & bi , const std : : string & fileNameWithExtension , bool mustDivideBy2 ) ;
/// replace slashes by the matching os value in a file name
static std : : string replaceSlashes ( const std : : string & src )
{
std : : string result = src ;
for ( uint k = 0 ; k < result . size ( ) ; + + k )
# ifdef NL_OS_WINDOWS
if ( result [ k ] = = ' / ' ) result [ k ] = ' \\ ' ;
# else
if ( result [ k ] = = ' \\ ' ) result [ k ] = ' / ' ;
# endif
return result ;
}
///=====================================================
int main ( int argc , char * argv [ ] )
{
// Filter addSearchPath
NLMISC : : createDebug ( ) ;
//"panoply.cfg" "gtm" "fyros"
# if HAS_INFO_GENERATION
if ( ! strcmp ( argv [ 2 ] , " gtm " ) | | ! strcmp ( argv [ 2 ] , " cgi " ) )
{
NLMISC : : CConfigFile cf ;
std : : string _Path_Input_TexBases ;
std : : string _Path_Input_Masks ;
std : : string _Path_Output_MasksOptimized ;
std : : string _Path_Output_Gtm ;
std : : string _Path_Output_Cgi ;
try
{
/// load the config file
cf . load ( argv [ 1 ] ) ;
/// look paths
try
{
NLMISC : : CConfigFile : : CVar & additionnal_paths = cf . getVar ( " additionnal_paths " ) ;
for ( uint k = 0 ; k < ( uint ) additionnal_paths . size ( ) ; + + k )
{
NLMISC : : CPath : : addSearchPath ( NLMISC : : CPath : : standardizePath ( additionnal_paths . asString ( k ) ) , true , false ) ;
}
}
catch ( const NLMISC : : EUnknownVar & )
{
}
/// repertory of textures bases (no-colorized)
try
{
_Path_Input_TexBases = NLMISC : : CPath : : standardizePath ( cf . getVar ( " input_path_texbase " ) . asString ( ) ) ;
}
catch ( const NLMISC : : EUnknownVar & )
{
}
/// repertory of masks (original)
try
{
_Path_Input_Masks = NLMISC : : CPath : : standardizePath ( cf . getVar ( " input_path_mask " ) . asString ( ) ) ;
}
catch ( const NLMISC : : EUnknownVar & )
{
}
/// optimized masks output directory created
try
{
_Path_Output_MasksOptimized = NLMISC : : CPath : : standardizePath ( cf . getVar ( " output_path_mask_optimized " ) . asString ( ) ) ;
}
catch ( const NLMISC : : EUnknownVar & )
{
}
/// file of infos about colorization average for the client
try
{
_Path_Output_Cgi = NLMISC : : CPath : : standardizePath ( cf . getVar ( " output_path_cgi " ) . asString ( ) ) ;
}
catch ( const NLMISC : : EUnknownVar & )
{
}
/// file of infos about multiplexing texture for the client
try
{
_Path_Output_Gtm = NLMISC : : CPath : : standardizePath ( cf . getVar ( " output_path_gtm " ) . asString ( ) ) ;
}
catch ( const NLMISC : : EUnknownVar & )
{
}
}
catch ( const std : : exception & e )
{
nlerror ( " Panoply building failed: %s " , e . what ( ) ) ;
return - 1 ;
}
/// oriented program
if ( ! strcmp ( argv [ 2 ] , " gtm " ) ) /// masks optimized
{
CInfoMaskGeneration infoMaskGen ( _Path_Input_TexBases ,
_Path_Input_Masks ,
_Path_Output_MasksOptimized ,
_Path_Output_Gtm ,
argv [ 3 ] ,
1 ) ;
infoMaskGen . init ( ) ;
infoMaskGen . process ( ) ;
}
else if ( ! strcmp ( argv [ 2 ] , " cgi " ) ) /// colorized information
{
CInfoColorGeneration infoColor ( _Path_Input_TexBases ,
_Path_Input_Masks ,
_Path_Output_Cgi ,
argv [ 3 ] ,
1 ) ;
infoColor . init ( ) ;
infoColor . process ( ) ;
}
}
else
{
# endif
NLMISC : : InfoLog - > addNegativeFilter ( " adding the path " ) ;
if ( argc ! = 2 )
{
nlinfo ( " Usage : %s [config_file name] " , argv [ 0 ] ) ;
return - 1 ;
}
CBuildInfo bi ;
/////////////////////////////////////////
// reads infos from the config files //
/////////////////////////////////////////
NLMISC : : CConfigFile cf ;
try
{
/// load the config file
cf . load ( argv [ 1 ] ) ;
/// colors masks
BuildMasksFromConfigFile ( cf , bi . ColorMasks ) ;
/// look paths
try
{
NLMISC : : CConfigFile : : CVar & additionnal_paths = cf . getVar ( " additionnal_paths " ) ;
for ( uint k = 0 ; k < ( uint ) additionnal_paths . size ( ) ; + + k )
{
NLMISC : : CPath : : addSearchPath ( NLMISC : : CPath : : standardizePath ( additionnal_paths . asString ( k ) ) ) ;
}
}
catch ( const NLMISC : : EUnknownVar & )
{
}
/// input
try
{
bi . InputPath = NLMISC : : CPath : : standardizePath ( cf . getVar ( " input_path " ) . asString ( ) ) ;
}
catch ( const NLMISC : : EUnknownVar & )
{
}
/// output
try
{
bi . OutputPath = NLMISC : : CPath : : standardizePath ( cf . getVar ( " output_path " ) . asString ( ) ) ;
}
catch ( const NLMISC : : EUnknownVar & )
{
}
/// hls info path
try
{
bi . HlsInfoPath = NLMISC : : CPath : : standardizePath ( cf . getVar ( " hls_info_path " ) . asString ( ) ) ;
}
catch ( const NLMISC : : EUnknownVar & )
{
bi . HlsInfoPath = " hlsInfo/ " ;
}
/// output
try
{
bi . CachePath = NLMISC : : CPath : : standardizePath ( cf . getVar ( " cache_path " ) . asString ( ) ) ;
}
catch ( const NLMISC : : EUnknownVar & )
{
}
/// output format
try
{
bi . OutputFormat = " . " + cf . getVar ( " output_format " ) . asString ( ) ;
}
catch ( const NLMISC : : EUnknownVar & )
{
bi . OutputFormat = " .tga " ;
}
/// default ascii character for unused masks
try
{
bi . DefaultSeparator = cf . getVar ( " default_separator " ) . asString ( ) ;
}
catch ( const NLMISC : : EUnknownVar & )
{
bi . DefaultSeparator = ' _ ' ;
}
/// extension for bitmaps
try
{
NLMISC : : CConfigFile : : CVar & bitmap_extensions = cf . getVar ( " bitmap_extensions " ) ;
for ( uint k = 0 ; k < ( uint ) bitmap_extensions . size ( ) ; + + k )
{
2016-01-04 13:03:11 +00:00
std : : string ext = " . " + NLMISC : : toLower ( bitmap_extensions . asString ( k ) ) ;
2015-12-18 12:02:31 +00:00
if ( std : : find ( bi . BitmapExtensions . begin ( ) , bi . BitmapExtensions . end ( ) , ext ) = = bi . BitmapExtensions . end ( ) )
{
bi . BitmapExtensions . push_back ( ext ) ;
}
}
}
catch ( const NLMISC : : EUnknownVar & )
{
bi . BitmapExtensions [ 0 ] . resize ( 1 ) ;
bi . BitmapExtensions [ 0 ] = bi . OutputFormat ;
}
try
{
bi . LowDefShift = cf . getVar ( " low_def_shift " ) . asInt ( ) ;
}
catch ( const NLMISC : : EUnknownVar & )
{
// tranform 512*512 to 64*64 by default
bi . LowDefShift = 3 ;
}
2015-12-28 15:44:35 +00:00
try
{
bi . OptimizeTextures = cf . getVar ( " optimize_textures " ) . asInt ( ) ;
}
catch ( const NLMISC : : EUnknownVar & )
{
2016-01-06 12:22:47 +00:00
// don't check files by default
2015-12-28 15:44:35 +00:00
bi . OptimizeTextures = 0 ;
}
2015-12-18 12:02:31 +00:00
}
catch ( const std : : exception & e )
{
nlerror ( " Panoply building failed: %s " , e . what ( ) ) ;
return - 1 ;
}
////////////////////////////////
// Build the colored versions //
////////////////////////////////
try
{
BuildColoredVersions ( bi ) ;
}
catch ( const std : : exception & e )
{
nlerror ( " Something went wrong while building bitmap: %s " , e . what ( ) ) ;
return - 1 ;
}
return 0 ;
# if HAS_INFO_GENERATION
}
# endif
}
///======================================================
# if HAS_INFO_GENERATION
static void validateCgiInfo ( )
{
NLMISC : : CIFile f ;
vector < StrInfoTexColor > temp ;
uint version ;
try
{
f . open ( CPath : : lookup ( " info_color_texbase_fyros.cgi " ) ) ;
f . serialCont ( temp ) ;
}
catch ( const std : : exception & e )
{
nlerror ( " Panoply building failed: %s " , e . what ( ) ) ;
}
uint16 a = temp . size ( ) ;
f . close ( ) ;
}
///======================================================
static void validateGtmInfo ( )
{
}
# endif
///======================================================
static void BuildMasksFromConfigFile ( NLMISC : : CConfigFile & cf ,
TColorMaskVect & colorMasks )
{
/// get a list of the alpha mask extensions
NLMISC : : CConfigFile : : CVar & mask_extensions = cf . getVar ( " mask_extensions " ) ;
colorMasks . resize ( mask_extensions . size ( ) ) ;
/// For each kind of mask, build a list of the color modifiers
for ( uint k = 0 ; k < ( uint ) mask_extensions . size ( ) ; + + k )
{
colorMasks [ k ] . MaskExt = mask_extensions . asString ( k ) ;
NLMISC : : CConfigFile : : CVar & luminosities = cf . getVar ( colorMasks [ k ] . MaskExt + " _luminosities " ) ;
NLMISC : : CConfigFile : : CVar & contrasts = cf . getVar ( colorMasks [ k ] . MaskExt + " _constrasts " ) ;
NLMISC : : CConfigFile : : CVar & hues = cf . getVar ( colorMasks [ k ] . MaskExt + " _hues " ) ;
NLMISC : : CConfigFile : : CVar & lightness = cf . getVar ( colorMasks [ k ] . MaskExt + " _lightness " ) ;
NLMISC : : CConfigFile : : CVar & saturation = cf . getVar ( colorMasks [ k ] . MaskExt + " _saturations " ) ;
NLMISC : : CConfigFile : : CVar & colorIDs = cf . getVar ( colorMasks [ k ] . MaskExt + " _color_id " ) ;
if ( luminosities . size ( ) ! = contrasts . size ( )
| | luminosities . size ( ) ! = hues . size ( )
| | luminosities . size ( ) ! = lightness . size ( )
| | luminosities . size ( ) ! = saturation . size ( )
| | luminosities . size ( ) ! = colorIDs . size ( )
)
{
throw NLMISC : : Exception ( " All color descriptors must have the same number of arguments " ) ;
}
colorMasks [ k ] . CMs . resize ( luminosities . size ( ) ) ;
for ( uint l = 0 ; l < ( uint ) luminosities . size ( ) ; + + l )
{
CColorModifier & cm = colorMasks [ k ] . CMs [ l ] ;
cm . Contrast = contrasts . asFloat ( l ) ;
cm . Luminosity = luminosities . asFloat ( l ) ;
cm . Hue = hues . asFloat ( l ) ;
cm . Lightness = lightness . asFloat ( l ) ;
cm . Saturation = saturation . asFloat ( l ) ;
cm . ColID = colorIDs . asString ( l ) ;
}
}
}
///======================================================
static void BuildColoredVersions ( const CBuildInfo & bi )
{
if ( ! NLMISC : : CFile : : isExists ( bi . InputPath ) )
{
nlerror ( " Path not found: %s " , bi . InputPath . c_str ( ) ) ;
return ;
}
for ( uint sizeVersion = 0 ; sizeVersion < 2 ; sizeVersion + + )
{
std : : vector < std : : string > files ;
if ( sizeVersion = = 0 )
// get the original (not to dvide) dir
NLMISC : : CPath : : getPathContent ( bi . InputPath , false , false , true , files ) ;
else
// get the dir content with texture that must be divided by 2.
NLMISC : : CPath : : getPathContent ( bi . InputPath + DivideBy2Dir , false , false , true , files ) ;
// For all files found
for ( uint k = 0 ; k < files . size ( ) ; + + k )
{
for ( uint l = 0 ; l < bi . BitmapExtensions . size ( ) ; + + l )
{
2016-01-04 13:03:11 +00:00
std : : string fileExt = " . " + NLMISC : : toLower ( NLMISC : : CFile : : getExtension ( files [ k ] ) ) ;
2015-12-18 12:02:31 +00:00
if ( fileExt = = bi . BitmapExtensions [ l ] )
{
//nlwarning("Processing : %s ", files[k].c_str());
try
{
if ( CheckIfNeedRebuildColoredVersionForOneBitmap ( bi , NLMISC : : CFile : : getFilename ( files [ k ] ) ,
sizeVersion = = 1 ) )
{
BuildColoredVersionForOneBitmap ( bi ,
NLMISC : : CFile : : getFilename ( files [ k ] ) ,
sizeVersion = = 1 ) ;
}
else
{
//nlwarning(("No need to rebuild " + NLMISC::CFile::getFilename(files[k])).c_str());
}
}
catch ( const std : : exception & e )
{
nlerror ( " Processing of %s failed: %s " , files [ k ] . c_str ( ) , e . what ( ) ) ;
}
}
}
}
}
}
/// used to loop throiugh the process, avoiding unused masks
struct CLoopInfo
{
NLMISC : : CBitmap Mask ;
uint Counter ;
uint MaskID ;
} ;
///======================================================
static bool CheckIfNeedRebuildColoredVersionForOneBitmap ( const CBuildInfo & bi , const std : : string & fileNameWithExtension ,
bool mustDivideBy2 )
{
if ( bi . CachePath . empty ( ) ) return true ;
uint32 srcDate = ( uint32 ) NLMISC : : CFile : : getFileModificationDate ( replaceSlashes ( bi . InputPath + fileNameWithExtension ) ) ;
static std : : vector < CLoopInfo > masks ;
/// check the needed masks
masks . clear ( ) ;
std : : string fileName = NLMISC : : CFile : : getFilenameWithoutExtension ( fileNameWithExtension ) ;
2016-01-04 13:03:11 +00:00
std : : string fileExt = NLMISC : : toLower ( NLMISC : : CFile : : getExtension ( fileNameWithExtension ) ) ;
2015-12-18 12:02:31 +00:00
for ( uint k = 0 ; k < bi . ColorMasks . size ( ) ; + + k )
{
std : : string maskName = fileName + " _ " + bi . ColorMasks [ k ] . MaskExt + " . " + fileExt ;
std : : string maskFileName = NLMISC : : CPath : : lookup ( maskName ,
false , false ) ;
if ( ! maskFileName . empty ( ) ) // found the mask ?
{
CLoopInfo li ;
li . Counter = 0 ;
li . MaskID = k ;
if ( NLMISC : : CFile : : fileExists ( maskFileName ) )
{
srcDate = std : : max ( srcDate , ( uint32 ) NLMISC : : CFile : : getFileModificationDate ( replaceSlashes ( maskFileName ) ) ) ;
masks . push_back ( li ) ;
}
}
}
// get hls info version that is in the cache. if not possible, must rebuild
std : : string outputHLSInfo = bi . HlsInfoPath + fileName + " .hlsinfo " ;
std : : string cacheHLSInfo = bi . CachePath + fileName + " .hlsinfo " ;
if ( ! NLMISC : : CFile : : fileExists ( cacheHLSInfo . c_str ( ) ) )
return true ;
else
{
// Must now if was moved beetween normal dir and d4/ dir.
CHLSBankTextureInfo hlsInfo ;
// read .hlsInfo cache
CIFile f ;
if ( ! f . open ( cacheHLSInfo ) )
return true ;
f . serial ( hlsInfo ) ;
f . close ( ) ;
// check if same DividedBy2 Flag.
if ( hlsInfo . DividedBy2 ! = mustDivideBy2 )
return true ;
// ok, can move the cache
if ( ! NLMISC : : CFile : : moveFile ( outputHLSInfo , cacheHLSInfo ) )
{
nlerror ( " Couldn't move %s to %s " , cacheHLSInfo . c_str ( ) , outputHLSInfo . c_str ( ) ) ;
return true ;
}
}
/// check is each generated texture has the same date or is more recent
for ( ; ; )
{
uint l ;
std : : string outputFileName = fileName ;
/// build current tex name
for ( l = 0 ; l < masks . size ( ) ; + + l )
{
uint maskID = masks [ l ] . MaskID ;
uint colorID = masks [ l ] . Counter ;
/// complete the file name
outputFileName + = bi . DefaultSeparator + bi . ColorMasks [ maskID ] . CMs [ colorID ] . ColID ;
}
// compare date
std : : string searchName = replaceSlashes ( bi . CachePath + outputFileName + bi . OutputFormat ) ;
if ( ( uint32 ) NLMISC : : CFile : : getFileModificationDate ( searchName ) < srcDate )
{
return true ; // not found or more old => need rebuild
}
// get version that is in the cache
std : : string cacheDest = bi . OutputPath + outputFileName + bi . OutputFormat ;
if ( ! NLMISC : : CFile : : moveFile ( cacheDest , searchName ) )
{
nlerror ( " Couldn't move %s to %s " , searchName . c_str ( ) , cacheDest . c_str ( ) ) ;
return true ;
}
/// increment counters
for ( l = 0 ; l < ( uint ) masks . size ( ) ; + + l )
{
+ + ( masks [ l ] . Counter ) ;
/// check if we have done all colors for this mask
if ( masks [ l ] . Counter = = bi . ColorMasks [ masks [ l ] . MaskID ] . CMs . size ( ) )
{
masks [ l ] . Counter = 0 ;
}
else
{
break ;
}
}
if ( l = = masks . size ( ) ) break ; // all cases dones
}
return false ; // nothing to rebuild
}
///======================================================
static void BuildColoredVersionForOneBitmap ( const CBuildInfo & bi , const std : : string & fileNameWithExtension ,
bool mustDivideBy2 )
{
uint32 depth ;
NLMISC : : CBitmap srcBitmap ;
NLMISC : : CBitmap resultBitmap ;
/// **** load the src bitmap
{
// where to load it.
string actualInputPath ;
if ( mustDivideBy2 )
actualInputPath = bi . InputPath + DivideBy2Dir ;
else
actualInputPath = bi . InputPath ;
// load
std : : string fullInputBitmapPath = actualInputPath + fileNameWithExtension ;
NLMISC : : CIFile is ;
try
{
if ( is . open ( fullInputBitmapPath ) )
{
2016-01-05 16:12:29 +00:00
// 8 bits textures are grayscale
srcBitmap . loadGrayscaleAsAlpha ( false ) ;
2015-12-18 12:02:31 +00:00
depth = srcBitmap . load ( is ) ;
2015-12-28 15:44:35 +00:00
is . close ( ) ;
2015-12-18 12:02:31 +00:00
if ( depth = = 0 | | srcBitmap . getPixels ( ) . empty ( ) )
{
throw NLMISC : : Exception ( " Failed to load bitmap " ) ;
}
2016-01-06 12:22:47 +00:00
// if bitmap is RGBA but has an alpha channel fully opaque (255),
2015-12-28 15:44:35 +00:00
// we can save it as RGB to optimize it
2016-01-06 12:22:47 +00:00
uint8 value = 0 ;
if ( bi . OptimizeTextures > 0 & & depth = = 32 & & srcBitmap . isAlphaUniform ( & value ) & & value = = 255 )
2015-12-28 15:44:35 +00:00
{
2016-01-06 12:22:47 +00:00
nlwarning ( " Texture %s can be optimized, run textures_optimizer " , fullInputBitmapPath . c_str ( ) ) ;
2015-12-28 15:44:35 +00:00
}
2015-12-18 12:02:31 +00:00
if ( srcBitmap . PixelFormat ! = NLMISC : : CBitmap : : RGBA )
{
srcBitmap . convertToType ( NLMISC : : CBitmap : : RGBA ) ;
}
}
else
{
nlerror ( " Unable to open %s. Processing next " , fullInputBitmapPath . c_str ( ) ) ;
return ;
}
}
catch ( const NLMISC : : Exception & e )
{
nlerror ( " File or format error with %s (%s). Processing next... " , fullInputBitmapPath . c_str ( ) , e . what ( ) ) ;
return ;
}
}
/// **** Build and prepare build of the .hlsinfo to write.
CHLSBankTextureInfo hlsInfo ;
CBitmap hlsInfoSrcBitmap ;
hlsInfoSrcBitmap = srcBitmap ;
// reduce size of the bitmap of LowDef shift
uint reduceShift = bi . LowDefShift ;
if ( reduceShift > 0 )
{
uint w = hlsInfoSrcBitmap . getWidth ( ) > > reduceShift ;
uint h = hlsInfoSrcBitmap . getHeight ( ) > > reduceShift ;
w = max ( w , 1U ) ;
h = max ( h , 1U ) ;
hlsInfoSrcBitmap . resample ( w , h ) ;
}
// Compress DXTC5 src bitmap
hlsInfo . SrcBitmap . build ( hlsInfoSrcBitmap ) ;
// Store info about if where in d4/ dir or not
hlsInfo . DividedBy2 = mustDivideBy2 ;
/// **** check the needed masks
static std : : vector < CLoopInfo > masks ;
masks . clear ( ) ;
std : : string fileName = NLMISC : : CFile : : getFilenameWithoutExtension ( fileNameWithExtension ) ;
2016-01-04 13:03:11 +00:00
std : : string fileExt = NLMISC : : toLower ( NLMISC : : CFile : : getExtension ( fileNameWithExtension ) ) ;
2015-12-18 12:02:31 +00:00
uint k ;
for ( k = 0 ; k < bi . ColorMasks . size ( ) ; + + k )
{
std : : string maskName = fileName + " _ " + bi . ColorMasks [ k ] . MaskExt + " . " + fileExt ;
2015-12-28 15:44:35 +00:00
std : : string maskFileName = NLMISC : : CPath : : lookup ( maskName , false , false ) ;
2015-12-18 12:02:31 +00:00
if ( ! maskFileName . empty ( ) ) // found the mask ?
{
CLoopInfo li ;
li . Counter = 0 ;
li . MaskID = k ;
/// try to load the bitmap
NLMISC : : CIFile is ;
try
{
if ( is . open ( maskFileName ) )
{
2015-12-28 15:44:35 +00:00
// masks are always opaque, if the mask is 8bits, it's in grayscale
li . Mask . loadGrayscaleAsAlpha ( false ) ;
uint8 maskDepth = li . Mask . load ( is ) ;
is . close ( ) ;
if ( maskDepth = = 0 | | li . Mask . getPixels ( ) . empty ( ) )
2015-12-18 12:02:31 +00:00
{
throw NLMISC : : Exception ( " Failed to load mask " ) ;
}
2016-01-06 12:22:47 +00:00
// display a warning if checks enabled
if ( li . Mask . getPixelFormat ( ) = = CBitmap : : RGBA & & bi . OptimizeTextures > 0 & & ! li . Mask . isGrayscale ( ) )
2015-12-28 15:44:35 +00:00
{
2016-01-06 12:22:47 +00:00
nlwarning ( " Mask %s is using colors, results may by incorrect! Run textures_optimizer to fix it. " , maskFileName . c_str ( ) ) ;
2016-01-04 13:03:11 +00:00
}
2016-01-03 17:16:07 +00:00
2016-01-04 13:03:11 +00:00
// convert image to real grayscale
if ( li . Mask . PixelFormat ! = NLMISC : : CBitmap : : Luminance )
{
li . Mask . convertToType ( NLMISC : : CBitmap : : Luminance ) ;
}
2016-01-03 17:16:07 +00:00
2015-12-18 12:02:31 +00:00
/// make sure the mask has the same size
if ( li . Mask . getWidth ( ) ! = srcBitmap . getWidth ( )
| | li . Mask . getHeight ( ) ! = srcBitmap . getHeight ( ) )
{
throw NLMISC : : Exception ( " Bitmap and mask do not have the same size " ) ;
}
masks . push_back ( li ) ;
}
else
{
nlerror ( " Unable to open %s. Processing next " , maskFileName . c_str ( ) ) ;
return ;
}
}
catch ( const std : : exception & e )
{
nlerror ( " Error with %s: %s. Aborting this bitmap processing " , maskFileName . c_str ( ) , e . what ( ) ) ;
return ;
}
}
}
// **** Add the masks to the .hlsInfo
hlsInfo . Masks . resize ( masks . size ( ) ) ;
for ( k = 0 ; k < masks . size ( ) ; + + k )
{
CLoopInfo & li = masks [ k ] ;
CBitmap tmp = li . Mask ;
tmp . resample ( hlsInfoSrcBitmap . getWidth ( ) , hlsInfoSrcBitmap . getHeight ( ) ) ;
hlsInfo . Masks [ k ] . build ( tmp ) ;
}
// **** generate each texture
// NB : if there are no masks the texture just will be copied
for ( ; ; )
{
resultBitmap = srcBitmap ;
uint l ;
std : : string outputFileName = fileName ;
// Add an instance entry to the hlsInfo
uint instId = ( uint ) hlsInfo . Instances . size ( ) ;
hlsInfo . Instances . resize ( instId + 1 ) ;
CHLSBankTextureInfo : : CTextureInstance & hlsTextInstance = hlsInfo . Instances [ instId ] ;
hlsTextInstance . Mods . resize ( masks . size ( ) ) ;
/// build current tex
for ( l = 0 ; l < masks . size ( ) ; + + l )
{
uint maskID = masks [ l ] . MaskID ;
uint colorID = masks [ l ] . Counter ;
/// get the color modifier
const CColorModifier & cm = bi . ColorMasks [ maskID ] . CMs [ colorID ] ;
/// apply the mask
float deltaHueApplied ;
cm . convertBitmap ( resultBitmap , resultBitmap , masks [ l ] . Mask , deltaHueApplied ) ;
/// save the setup in hlsInfo
hlsTextInstance . Mods [ l ] . DHue = deltaHueApplied ;
hlsTextInstance . Mods [ l ] . DLum = cm . Lightness ;
hlsTextInstance . Mods [ l ] . DSat = cm . Saturation ;
/// complete the file name
outputFileName + = bi . DefaultSeparator + bi . ColorMasks [ maskID ] . CMs [ colorID ] . ColID ;
}
// save good hlsInfo instance name
hlsTextInstance . Name = outputFileName + bi . OutputFormat ;
nlinfo ( " Writing %s " , outputFileName . c_str ( ) ) ;
/// Save the result. We let propagate exceptions (if there's no more space disk it useless to continue...)
{
2016-01-04 13:03:11 +00:00
std : : string fullOutputPath = bi . OutputPath + outputFileName + bi . OutputFormat ;
2015-12-18 12:02:31 +00:00
try
{
NLMISC : : COFile os ;
if ( os . open ( fullOutputPath ) )
{
// divide by 2 when needed.
if ( mustDivideBy2 )
resultBitmap . resample ( ( resultBitmap . getWidth ( ) + 1 ) / 2 , ( resultBitmap . getHeight ( ) + 1 ) / 2 ) ;
// write the file
if ( bi . OutputFormat = = " .png " )
{
resultBitmap . writePNG ( os , depth ) ;
}
else
{
resultBitmap . writeTGA ( os , depth ) ;
}
}
else
{
nlerror ( " Couldn't open %s for writing " , fullOutputPath . c_str ( ) ) ;
}
}
catch ( const NLMISC : : EStream & e )
{
nlerror ( " Couldn't write %s: %s " , fullOutputPath . c_str ( ) , e . what ( ) ) ;
}
}
/// increment counters
for ( l = 0 ; l < ( uint ) masks . size ( ) ; + + l )
{
+ + ( masks [ l ] . Counter ) ;
/// check if we have done all colors for this mask
if ( masks [ l ] . Counter = = bi . ColorMasks [ masks [ l ] . MaskID ] . CMs . size ( ) )
{
masks [ l ] . Counter = 0 ;
}
else
{
break ;
}
}
if ( l = = masks . size ( ) ) break ; // all cases dones
}
// **** save the TMP hlsInfo
std : : string fullHlsInfoPath = bi . HlsInfoPath + fileName + " .hlsinfo " ;
NLMISC : : COFile os ;
if ( os . open ( fullHlsInfoPath ) )
{
os . serial ( hlsInfo ) ;
}
else
{
nlerror ( " Couldn't write %s " , fullHlsInfoPath . c_str ( ) ) ;
}
}