Files
UnrealEngine/Engine/Source/ThirdParty/etc2comp/EtcTool/EtcTool.cpp
2025-05-18 13:04:45 +08:00

834 lines
21 KiB
C++

/*
* Copyright 2015 The Etc2Comp Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS (1)
#endif
/*
since this code will be used on a wide varity of platforms and configurations
its important to have some sort of sanity check for the amount of threads that can be used.
change this macro to suit your configuration. This will be the maximum amount of threads
that can be created.
*/
#define MAX_JOBS 1024
#define RUN_MEM_TEST 0
#include "EtcConfig.h"
#include "Etc.h"
#include "EtcFilter.h"
#include "EtcTool.h"
#include "EtcSourceImage.h"
#include "EtcFile.h"
#include "EtcMath.h"
#include "EtcImage.h"
#include "EtcErrorMetric.h"
#include "EtcBlock4x4EncodingBits.h"
#include "EtcAnalysis.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
using namespace Etc;
#if ETC_WINDOWS
const char *ETC_MKDIR_COMMAND = "mkdir";
int strcasecmp(const char *s1, const char *s2)
{
return _stricmp(s1, s2);
}
#else
const char *ETC_MKDIR_COMMAND = "mkdir -p";
#endif
#if RUN_MEM_TEST
#include "EtcMemTest.h"
#endif
class Commands
{
public:
static const unsigned int MIN_JOBS = 8;
Commands(void)
{
pstrSourceFilename = nullptr;
pstrOutputFilename = nullptr;
format = Image::Format::DEFAULT;
pstrAnalysisDirectory = nullptr;
uiComparisons = 0;
for (unsigned int uiComparison = 0; uiComparison < Analysis::MAX_COMPARISONS; uiComparison++)
{
apstrCompareFilename[uiComparison] = nullptr;
}
fEffort = ETCCOMP_DEFAULT_EFFORT_LEVEL;
//Rec. 709 or BT.709...the default
e_ErrMetric = ErrorMetric::BT709;
uiJobs = MIN_JOBS;
//these are ignored if they are < 0
i_hPixel = -1;
i_vPixel = -1;
verboseOutput = false;
boolNormalizeXYZ = false;
mipmaps = 1;
mipFilterFlags = Etc::FILTER_WRAP_NONE;
}
bool ProcessCommandLineArguments(int a_iArgs, const char *a_apstrArgs[]);
void PrintUsageMessage(void);
static void FixSlashes(char *a_pstr);
char *pstrSourceFilename;
char *pstrOutputFilename;
Image::Format format;
char *pstrAnalysisDirectory;
char *formatType;
unsigned int uiComparisons;
char *apstrCompareFilename[Analysis::MAX_COMPARISONS];
float fEffort;
ErrorMetric e_ErrMetric;
unsigned int uiJobs; // for threading
bool verboseOutput;
//when both of these are >= 0 then single block mode is on
int i_hPixel;
int i_vPixel;
bool boolNormalizeXYZ;
int mipmaps;
unsigned int mipFilterFlags;
};
#include "EtcFileHeader.h"
// ----------------------------------------------------------------------------------------------------
//
int main(int argc, const char * argv[])
{
static const bool USE_C_INTERFACE = false;
// this code tests for memory leaks
#if RUN_MEM_TEST
RunMemTest(true, 100);
printf("an extra line to see how the memory is free'd\n");
printf("all done!\n");
exit(0);
#endif
Commands commands;
bool boolPrintUsage = commands.ProcessCommandLineArguments(argc, argv);
if (boolPrintUsage)
{
commands.PrintUsageMessage();
exit(1);
}
if (commands.verboseOutput)
{
printf("SourceImage: %s\n", commands.pstrSourceFilename);
}
SourceImage sourceimage(commands.pstrSourceFilename, commands.i_hPixel, commands.i_vPixel);
if (commands.boolNormalizeXYZ)
{
sourceimage.NormalizeXYZ();
}
unsigned int uiSourceWidth = sourceimage.GetWidth();
unsigned int uiSourceHeight = sourceimage.GetHeight();
if(commands.mipmaps != 1)
{
int iEncodingTime_ms;
// Calculate the maximum number of possible mipmaps
{
int dim = (uiSourceWidth < uiSourceHeight)?uiSourceWidth:uiSourceHeight;
int maxMips = 0;
while(dim >= 1)
{
maxMips++;
dim >>= 1;
}
if( commands.mipmaps == 0 || commands.mipmaps > maxMips)
{
commands.mipmaps = maxMips;
}
}
Etc::RawImage *pMipmapImages = new Etc::RawImage[commands.mipmaps];
if (commands.verboseOutput)
{
printf("Encoding:\n");
printf(" effort = %.f\n", commands.fEffort);
printf(" encoding = %s\n", Image::EncodingFormatToString(commands.format));
printf(" error metric: %s\n", ErrorMetricToString(commands.e_ErrMetric));
}
Etc::EncodeMipmaps((float *)sourceimage.GetPixels(),
uiSourceWidth, uiSourceHeight,
commands.format,
commands.e_ErrMetric,
commands.fEffort,
commands.uiJobs,
MAX_JOBS,
commands.mipmaps,
commands.mipFilterFlags,
pMipmapImages,
&iEncodingTime_ms);
if (commands.verboseOutput)
{
printf(" encode time = %dms\n", iEncodingTime_ms);
printf("EncodedImage: %s\n", commands.pstrOutputFilename);
}
Etc::File etcfile(commands.pstrOutputFilename, Etc::File::Format::INFER_FROM_FILE_EXTENSION,
commands.format,
commands.mipmaps,
pMipmapImages,
uiSourceWidth, uiSourceHeight );
etcfile.Write();
delete [] pMipmapImages;
}
else if (USE_C_INTERFACE)
{
unsigned char *paucEncodingBits;
unsigned int uiEncodingBitsBytes;
unsigned int uiExtendedWidth;
unsigned int uiExtendedHeight;
int iEncodingTime_ms;
if (commands.verboseOutput)
{
printf("Encoding:\n");
printf(" effort = %.f\n", commands.fEffort);
printf(" encoding = %s\n", Image::EncodingFormatToString(commands.format));
printf(" error metric: %s\n", ErrorMetricToString(commands.e_ErrMetric));
}
Etc::Encode((float *)sourceimage.GetPixels(),
uiSourceWidth, uiSourceHeight,
commands.format,
commands.e_ErrMetric,
commands.fEffort,
commands.uiJobs,
MAX_JOBS,
&paucEncodingBits, &uiEncodingBitsBytes,
&uiExtendedWidth, &uiExtendedHeight,
&iEncodingTime_ms);
if (commands.verboseOutput)
{
printf(" encode time = %dms\n", iEncodingTime_ms);
printf("EncodedImage: %s\n", commands.pstrOutputFilename);
}
Etc::File etcfile(commands.pstrOutputFilename, Etc::File::Format::INFER_FROM_FILE_EXTENSION,
commands.format,
paucEncodingBits, uiEncodingBitsBytes,
uiSourceWidth, uiSourceHeight,
uiExtendedWidth, uiExtendedHeight);
etcfile.Write();
}
else
{
if (commands.verboseOutput)
{
printf("Encoding:\n");
printf(" effort = %.f%%\n", commands.fEffort);
printf(" encoding = %s\n", Image::EncodingFormatToString(commands.format));
printf(" error metric: %s\n", ErrorMetricToString(commands.e_ErrMetric));
}
Etc::Image image((float *)sourceimage.GetPixels(),
uiSourceWidth, uiSourceHeight,
commands.e_ErrMetric);
image.m_bVerboseOutput = commands.verboseOutput;
Etc::Image::EncodingStatus encStatus = Etc::Image::EncodingStatus::SUCCESS;
encStatus = image.Encode(commands.format, commands.e_ErrMetric, commands.fEffort, commands.uiJobs,MAX_JOBS);
if (commands.verboseOutput)
{
printf(" encode time = %dms\n", image.GetEncodingTimeMs());
printf("EncodedImage: %s\n", commands.pstrOutputFilename);
printf("status bitfield: %u\n", encStatus);
}
Etc::File etcfile(commands.pstrOutputFilename, Etc::File::Format::INFER_FROM_FILE_EXTENSION,
commands.format,
image.GetEncodingBits(), image.GetEncodingBitsBytes(),
image.GetSourceWidth(), image.GetSourceHeight(),
image.GetExtendedWidth(), image.GetExtendedHeight());
etcfile.Write();
if (commands.pstrAnalysisDirectory)
{
if (commands.verboseOutput)
{
printf("Analysis: %s\n", commands.pstrAnalysisDirectory);
}
Analysis analysis(&image, commands.pstrAnalysisDirectory);
for (unsigned int uiComparison = 0; uiComparison < commands.uiComparisons; uiComparison++)
{
analysis.Compare(commands.apstrCompareFilename[uiComparison], commands.i_hPixel, commands.i_vPixel);
}
}
}
return 0;
}
// ----------------------------------------------------------------------------------------------------
// return true if usage message should be printed
//
bool Commands::ProcessCommandLineArguments(int a_iArgs, const char *a_apstrArgs[])
{
static const bool DEBUG_PRINT = false;
if (a_iArgs == 1)
{
printf("Error: missing arguments\n");
return true;
}
for (int iArg = 1; iArg < a_iArgs; iArg++)
{
if (DEBUG_PRINT)
{
printf("%s: %u %s\n", a_apstrArgs[0], iArg, a_apstrArgs[iArg]);
}
if (strcmp(a_apstrArgs[iArg], "-analyze") == 0)
{
++iArg;
if (iArg >= (a_iArgs))
{
printf("Error: missing folder parameter for -analyze\n");
return true;
}
else
{
pstrAnalysisDirectory = new char[strlen(a_apstrArgs[iArg]) + 1];
strcpy(pstrAnalysisDirectory, a_apstrArgs[iArg]);
FixSlashes(pstrAnalysisDirectory);
}
}
else if (strcmp(a_apstrArgs[iArg], "-argfile") == 0)
{
static const unsigned int MAX_LINE_CHARS = 1000;
static const unsigned int MAX_ARGFILE_ARGS = 100;
++iArg;
if (iArg >= (a_iArgs))
{
printf("Error: missing file parameter for -argfile\n");
return true;
}
else
{
FILE *pfile = fopen(a_apstrArgs[iArg], "rt");
if (pfile == nullptr)
{
printf("Error: couldn't open argfile (%s)\n", a_apstrArgs[iArg]);
return true;
}
char **apstrArgs = new char *[MAX_ARGFILE_ARGS];
assert(apstrArgs);
// add null executable name
apstrArgs[0] = const_cast<char *>("");
int iArgs = 1;
// read in tokens
{
char *pcTokens = new char[MAX_LINE_CHARS + 1];
assert(pcTokens);
char *pcToken = nullptr;
// read in each line
while (fgets(pcTokens, MAX_LINE_CHARS, pfile))
{
// skip over lines with '#' in first char
if (pcTokens[0] == '#')
{
continue;
}
// abort remainder of argfile with '%' in first char
if (pcTokens[0] == '%')
{
break;
}
pcToken = strtok(pcTokens, " \n\r");
if (pcToken != nullptr)
{
apstrArgs[iArgs] = new char[strlen(pcToken) + 1];
strcpy(apstrArgs[iArgs], pcToken);
iArgs++;
}
while (pcToken != nullptr)
{
pcToken = strtok(nullptr, " \n");
if (pcToken != nullptr)
{
apstrArgs[iArgs] = new char[strlen(pcToken) + 1];
strcpy(apstrArgs[iArgs], pcToken);
iArgs++;
}
}
}
delete[] pcTokens;
}
fclose(pfile);
bool boolErrors = ProcessCommandLineArguments(iArgs, const_cast<const char **>(apstrArgs));
for (iArg = 1; iArg < iArgs; iArg++)
{
delete[] apstrArgs[iArg];
}
delete[] apstrArgs;
if (boolErrors)
{
return true;
}
}
}
//used for debugging...select a single block to encode
//supply the horiz and very pos of the block
else if (strcmp(a_apstrArgs[iArg], "-blockAtHV") == 0)
{
++iArg;
//make sure we have two more args after -block
if (iArg + 1 >= (a_iArgs))
{
printf("Error: missing horiz and vert position of pixel for single block mode \n");
return true;
}
i_hPixel = atoi(a_apstrArgs[iArg]);
++iArg;
i_vPixel = atoi(a_apstrArgs[iArg]);
}
else if (strcmp(a_apstrArgs[iArg], "-compare") == 0)
{
++iArg;
if (iArg >= (a_iArgs))
{
printf("Error: missing comprison_image parameter for -compare\n");
return true;
}
else
{
if (uiComparisons >= Analysis::MAX_COMPARISONS)
{
printf("Error: too many comparisons\n");
return true;
}
char **ppstrCompareFilename = &apstrCompareFilename[uiComparisons++];
*ppstrCompareFilename = new char[strlen(a_apstrArgs[iArg]) + 1];
strcpy(*ppstrCompareFilename, a_apstrArgs[iArg]);
FixSlashes(*ppstrCompareFilename);
}
}
else if (strcmp(a_apstrArgs[iArg], "-effort") == 0)
{
++iArg;
if (iArg >= (a_iArgs))
{
printf("Error: missing amount parameter for -effort\n");
return true;
}
else
{
float f;
int iScans = sscanf(a_apstrArgs[iArg], "%f", &f);
if (iScans != 1)
{
printf("Error: couldn't parse amount for -effort (%s)\n", a_apstrArgs[iArg]);
return true;
}
else
{
fEffort = f;
}
}
}
else if (strcmp(a_apstrArgs[iArg], "-errormetric") == 0)
{
++iArg;
if (iArg >= (a_iArgs))
{
printf("Error: missing error metric type %s\n", a_apstrArgs[iArg]);
return true;
}
else
{
if (strcmp(a_apstrArgs[iArg], "rgba") == 0)
{
e_ErrMetric = ErrorMetric::RGBA;
}
else if (strcmp(a_apstrArgs[iArg], "rgbx") == 0)
{
e_ErrMetric = ErrorMetric::RGBX;
}
else if (strcmp(a_apstrArgs[iArg], "rec709") == 0)
{
e_ErrMetric = ErrorMetric::REC709;
}
else if (strcmp(a_apstrArgs[iArg], "numeric") == 0)
{
e_ErrMetric = ErrorMetric::NUMERIC;
}
else if (strcmp(a_apstrArgs[iArg], "normalxyz") == 0 ||
strcmp(a_apstrArgs[iArg], "normalXYZ") == 0)
{
e_ErrMetric = ErrorMetric::NORMALXYZ;
}
else
{
printf("unrecognized error metric (%s), using numeric\n", a_apstrArgs[iArg]);
e_ErrMetric = ErrorMetric::NUMERIC;
}
}
}
else if (strcmp(a_apstrArgs[iArg], "-format") == 0)
{
++iArg;
if (iArg >= (a_iArgs))
{
printf("Error: missing etc_format parameter for -format\n");
return true;
}
else
{
formatType = new char[strlen(a_apstrArgs[iArg])+1];
strcpy(formatType,a_apstrArgs[iArg]);
if (strcmp(a_apstrArgs[iArg], "ETC1") == 0)
{
format = Image::Format::ETC1;
}
else if (strcmp(a_apstrArgs[iArg], "RGB8") == 0)
{
format = Image::Format::RGB8;
}
else if (strcmp(a_apstrArgs[iArg], "SRGB8") == 0)
{
format = Image::Format::SRGB8;
}
else if (strcmp(a_apstrArgs[iArg], "RGBA8") == 0)
{
format = Image::Format::RGBA8;
}
else if (strcmp(a_apstrArgs[iArg], "SRGBA8") == 0)
{
format = Image::Format::SRGBA8;
}
else if (strcmp(a_apstrArgs[iArg], "R11") == 0)
{
format = Image::Format::R11;
}
else if (strcmp(a_apstrArgs[iArg], "SIGNED_R11") == 0)
{
format = Image::Format::SIGNED_R11;
}
else if (strcmp(a_apstrArgs[iArg], "RG11") == 0)
{
format = Image::Format::RG11;
}
else if (strcmp(a_apstrArgs[iArg], "SIGNED_RG11") == 0)
{
format = Image::Format::SIGNED_RG11;
}
else if (strcmp(a_apstrArgs[iArg], "RGB8A1") == 0)
{
format = Image::Format::RGB8A1;
}
else if (strcmp(a_apstrArgs[iArg], "SRGB8A1") == 0)
{
format = Image::Format::SRGB8A1;
}
else
{
printf("Error: unknown etc_format parameter for -format\n");
format = Image::Format::UNKNOWN;
return true;
}
}
}
else if (strcmp(a_apstrArgs[iArg], "-help") == 0)
{
return true;
}
else if (strcmp(a_apstrArgs[iArg], "-j") == 0 ||
strcmp(a_apstrArgs[iArg], "-jobs") == 0)
{
++iArg;
if (iArg >= (a_iArgs))
{
printf("Error: missing job count for %s\n", a_apstrArgs[iArg]);
return true;
}
else
{
unsigned int ui;
int iScans = sscanf(a_apstrArgs[iArg], "%u", &ui);
if (iScans != 1)
{
printf("Error: couldn't parse job count for %s (%s)\n", a_apstrArgs[iArg-1], a_apstrArgs[iArg]);
return true;
}
else
{
if (ui < MIN_JOBS)
{
ui = MIN_JOBS;
}
uiJobs = ui;
}
}
}
else if (strcmp(a_apstrArgs[iArg], "-normalizexyz") == 0 ||
strcmp(a_apstrArgs[iArg], "-normalizeXYZ") == 0)
{
boolNormalizeXYZ = true;
}
else if (strcmp(a_apstrArgs[iArg], "-output") == 0)
{
++iArg;
if (iArg >= (a_iArgs))
{
printf("Error: missing encoded_image parameter for -output\n");
return true;
}
else
{
pstrOutputFilename = new char[strlen(a_apstrArgs[iArg]) + 1];
strcpy(pstrOutputFilename, a_apstrArgs[iArg]);
//take the output file name and extract the directory path so we can create the directory if nescacary
char *ptrOutputDir = nullptr;
FixSlashes(pstrOutputFilename);
for (int c = (int)strlen(pstrOutputFilename); c > 0; c--)
{
//find the last slash, to get the name of the directory
if (pstrOutputFilename[c] == ETC_PATH_SLASH)
{
c++;
ptrOutputDir = new char[c];
strncpy(ptrOutputDir, pstrOutputFilename, c);
ptrOutputDir[c] = '\0';
CreateNewDir(ptrOutputDir);
break;
}
}
if (ptrOutputDir == nullptr)
{
printf("couldnt find a place to put converted images\n");
exit(1);
}
}
}
else if (strcmp(a_apstrArgs[iArg], "-verbose") == 0 ||
strcmp(a_apstrArgs[iArg], "-v") == 0)
{
verboseOutput = true;
}
else if (strcmp(a_apstrArgs[iArg], "-mipmaps") == 0 ||
strcmp(a_apstrArgs[iArg], "-m") == 0)
{
++iArg;
if (iArg >= (a_iArgs))
{
printf("Error: missing mipmap number parameter for -mipmaps\n");
return true;
}
else
{
unsigned int ui;
int result = sscanf(a_apstrArgs[iArg], "%u", &ui);
if (result == 1)
{
mipmaps = ui;
}
else
{
printf("Error: -mipmaps argument needs to be a number\n");
return true;
}
}
}
else if (strcmp(a_apstrArgs[iArg], "-mipwrap") == 0 ||
strcmp(a_apstrArgs[iArg], "-w") == 0)
{
++iArg;
if (iArg >= (a_iArgs))
{
printf("Error: missing parameter for -mipwrap\n");
return true;
}
else
{
if ( 0 == strcmp(a_apstrArgs[iArg], "x") )
{
mipFilterFlags = Etc::FILTER_WRAP_X;
}
else if (0 == strcmp(a_apstrArgs[iArg], "y"))
{
mipFilterFlags = Etc::FILTER_WRAP_Y;
}
else if (0 == strcmp(a_apstrArgs[iArg], "xy"))
{
mipFilterFlags = Etc::FILTER_WRAP_X | Etc::FILTER_WRAP_Y;
}
}
}
else if (a_apstrArgs[iArg][0] == '-')
{
printf("Error: unknown option (%s)\n", a_apstrArgs[iArg]);
return true;
}
else if (a_apstrArgs[iArg][0] == '\r')
{
continue;
}
else
{
if (pstrSourceFilename != nullptr)
{
printf("Error: only support one source_image (%s)\n", a_apstrArgs[iArg]);
return true;
}
pstrSourceFilename = new char[strlen(a_apstrArgs[iArg])+1];
strcpy(pstrSourceFilename, a_apstrArgs[iArg]);
}
}
if (pstrSourceFilename == nullptr)
{
printf("Error: missing source_image\n");
return true;
}
if (pstrOutputFilename == nullptr)
{
printf("Error: missing -output encoded_image\n");
return true;
}
if (uiComparisons > 0 && pstrAnalysisDirectory == nullptr)
{
printf("Error: -compare is only valid with -analyze\n");
return true;
}
return false;
}
// ----------------------------------------------------------------------------------------------------
//
void Commands::FixSlashes(char *a_pstr)
{
while (*a_pstr)
{
if (*a_pstr == ETC_BAD_PATH_SLASH)
{
*a_pstr = ETC_PATH_SLASH;
}
a_pstr++;
}
}
// ----------------------------------------------------------------------------------------------------
// print usage message and exit
//
void Commands::PrintUsageMessage(void)
{
printf("Usage: etctool.exe source_image [options ...] -output <output_file>\n");
printf("Options:\n");
printf(" -analyze <analysis_folder>\n");
printf(" -argfile <arg_file> additional command line arguments\n");
printf(" -blockAtHV <H V> encodes a single block that contains the\n");
printf(" pixel specified by the H V coordinates\n");
printf(" -compare <comparison_image> compares source_image to comparison_image\n");
printf(" -effort <amount> number between 0 and 100\n");
printf(" -errormetric <error_metric> specify the error metric, the options are\n");
printf(" rgba, rgbx, rec709, numeric and normalxyz\n");
printf(" -format <etc_format> ETC1, RGB8, SRGB8, RGBA8, SRGB8, RGB8A1,\n");
printf(" SRGB8A1 or R11\n");
printf(" -help prints this message\n");
printf(" -jobs or -j <thread_count> specifies the number of threads (default=1)\n");
printf(" -normalizexyz normalize RGB to have a length of 1\n");
printf(" -verbose or -v shows status information during the encoding\n");
printf(" process\n");
printf(" -mipmaps or -m <mip_count> sets the maximum number of mipaps to generate (default=1)\n");
printf(" -mipwrap or -w <x|y|xy> sets the mipmap filter wrap mode (default=clamp)\n");
printf("\n");
exit(1);
}
// ----------------------------------------------------------------------------------------------------
//
void CreateNewDir(const char *path)
{
char strCommand[300];
#if ETC_WINDOWS
sprintf_s(strCommand, "if not exist %s %s %s", path, ETC_MKDIR_COMMAND, path);
#else
sprintf(strCommand, "%s %s", ETC_MKDIR_COMMAND, path);
#endif
int iResult = system(strCommand);
if (iResult != 0)
{
printf("Error: couldn't create directory (%s)\n", path);
exit(0);
}
}
// ----------------------------------------------------------------------------------------------------
//