// NeL - MMORPG Framework
// 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 .
#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
#include
#include
#include
#include
#include
#include
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
{
std::string InputPath;
std::string OutputPath;
std::string HlsInfoPath;
std::string CachePath;
std::vector BitmapExtensions; // the supported extension for bitmaps
std::string OutputFormat; // png or tga
std::string DefaultSeparator;
TColorMaskVect ColorMasks;
// how to shift right the size of the src Bitmap for the .hlsinfo
uint LowDefShift;
uint OptimizeTextures; // 0 = don't optimize, 1 = check, 2 = optimize
};
/// 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)
{
std::string ext = "." + bitmap_extensions.asString(k);
ext = NLMISC::strupr(ext);
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;
}
try
{
bi.OptimizeTextures = cf.getVar ("optimize_textures").asInt();
}
catch (const NLMISC::EUnknownVar &)
{
// don't optimize files by default
bi.OptimizeTextures = 0;
}
}
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 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 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)
{
std::string fileExt = "." + NLMISC::strupr(NLMISC::CFile::getExtension(files[k]));
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 masks;
/// check the needed masks
masks.clear();
std::string fileName = NLMISC::CFile::getFilenameWithoutExtension(fileNameWithExtension);
std::string fileExt = NLMISC::strupr(NLMISC::CFile::getExtension(fileNameWithExtension));
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))
{
depth = srcBitmap.load(is);
is.close();
if (depth == 0 || srcBitmap.getPixels().empty())
{
throw NLMISC::Exception("Failed to load bitmap");
}
// if bitmap is RGBA but has an alpha channel fully transparent,
// we can save it as RGB to optimize it
if (bi.OptimizeTextures > 0 && depth == 32)
{
uint32 size = srcBitmap.getSize(0);
if (size > 0)
{
// get a pointer on original data
uint8 *data = srcBitmap.getPixels().getPtr();
// pointer on first alpha value
uint8 *tmp = data + 3;
uint8 *endData = data + size;
uint8 value = *tmp;
// check if all alphas have the same value
while(tmp < endData && *tmp == value) tmp += 4;
// texture can be converted if all alphas are 0 or 255
if (tmp >= endData && (value == 255 || value == 0))
{
if (bi.OptimizeTextures > 1)
{
// original depth is now 24 bits, since we discarded alpha channel
depth = 24;
// if texture is fully transparent, make it fully opaque
if (value == 0)
{
tmp = data + 3;
while(tmp < endData)
{
*tmp = 255;
tmp += 4;
}
}
NLMISC::COFile os;
if (os.open(fullInputBitmapPath))
{
nlwarning("Optimizing texture %s...", fullInputBitmapPath.c_str());
std::string ext = CFile::getExtension(fullInputBitmapPath);
// resave the texture in optimized same format
if (ext == "png")
{
srcBitmap.writePNG(os, 24);
}
else if (ext == "tga")
{
srcBitmap.writeTGA(os, 24);
}
else
{
nlwarning("Don't support %s format for texture, unable to save it", ext.c_str());
}
}
}
else
{
nlwarning("Texture %s can be optimized", fullInputBitmapPath.c_str());
}
}
}
}
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 masks;
masks.clear();
std::string fileName = NLMISC::CFile::getFilenameWithoutExtension(fileNameWithExtension);
std::string fileExt = NLMISC::strupr(NLMISC::CFile::getExtension(fileNameWithExtension));
uint k;
for (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;
/// try to load the bitmap
NLMISC::CIFile is;
try
{
if (is.open(maskFileName))
{
// 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())
{
throw NLMISC::Exception("Failed to load mask");
}
// masks can be converted to grayscale files
if (bi.OptimizeTextures > 0 && maskDepth > 8)
{
if (bi.OptimizeTextures > 1)
{
NLMISC::COFile os;
if (os.open(maskFileName))
{
std::string ext = CFile::getExtension(maskFileName);
nlwarning("Optimizing mask %s...", maskFileName.c_str());
// resave the texture in optimized same format
if (ext == "png")
{
li.Mask.writePNG(os, 8);
}
else if (ext == "tga")
{
li.Mask.writeTGA(os, 8);
}
else
{
nlwarning("Don't support %s format for mask, unable to save it", ext.c_str());
}
}
}
else
{
nlwarning("Mask %s can be optimized", maskFileName.c_str());
}
}
if (li.Mask.PixelFormat != NLMISC::CBitmap::RGBA)
{
li.Mask.convertToType(NLMISC::CBitmap::RGBA);
}
/// 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...)
{
std::string fullOutputPath = bi.OutputPath + "/" + outputFileName + bi.OutputFormat;
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());
}
}