// -*- tab-width: 4; -*- // vi: set sw=2 ts=4 sts=4 expandtab: // Copyright 2019-2020 The Khronos Group, Inc. // SPDX-License-Identifier: Apache-2.0 // To use, download from http://www.billbaxter.com/projects/imdebug/ // Put imdebug.dll in %SYSTEMROOT% (usually C:\WINDOWS), imdebug.h in // ../../include, imdebug.lib in ../../build/msvs//vs & // add ..\imdebug.lib to the libraries list in the project properties. #define IMAGE_DEBUG 0 #include "ktxapp.h" #include #include #include #include #include #include #include "argparser.h" #include "version.h" #if (IMAGE_DEBUG) && defined(_DEBUG) && defined(_WIN32) && !defined(_WIN32_WCE) # include "imdebug.h" #elif defined(IMAGE_DEBUG) && IMAGE_DEBUG # undef IMAGE_DEBUG # define IMAGE_DEBUG 0 #endif #if defined(_MSC_VER) #define strncasecmp _strnicmp #define fileno _fileno #define isatty _isatty #endif #if IMAGE_DEBUG static void dumpImage(char* name, int width, int height, int components, int componentSize, bool isLuminance, unsigned char* srcImage); #endif using namespace std; /** @page ktx2ktx2 ktx2ktx2 @~English Create a KTX 2 file from a KTX file. @section ktx2ktx2_synopsis SYNOPSIS ktx2ktx2 [options] [@e infile ...] @section ktx2ktx2_description DESCRIPTION @b ktx2ktx2 creates Khronos texture format version 2 files (KTX2) from Khronos texture format version 1 files. @b ktx2ktx2 reads each named @e infile. Output files have the same name as the input but with the extension changed to @c .ktx2. When @b infile is not specified, a single file will be read from stdin and the output written to standard out. If unrecognized metadata with keys beginning "KTX" or "ktx" is found in the input file, it is dropped and a warning is written to standard error. The following options are available:
-b, \--rewritebado
Rewrite bad orientation metadata. Some in-the-wild KTX files have orientation metadata with the key "KTXOrientation" instead of KTXorientaion. This option will rewrite such bad metadata instead of dropping it.
-o outfile, --output=outfile
Name the output file @e outfile. If @e outfile is 'stdout', output will be written to stdout. If there is more than 1 input file, the command prints its usage message and exits.
-f, \--force
If the destination file already exists, remove it and create a new file, without prompting for confirmation regardless of its permissions.
@snippet{doc} ktxapp.h ktxApp options @section ktx2ktx2_exitstatus EXIT STATUS @b ktx2ktx2 exits 0 on success, 1 on command line errors and 2 on functional errors. @section ktx2ktx2_history HISTORY @par Version 4.0 - Initial version. @section ktx2ktx2_author AUTHOR Mark Callow, github.com/MarkCallow */ #define QUOTE(x) #x #define STR(x) QUOTE(x) std::string myversion(STR(KTX2KTX2_VERSION)); std::string mydefversion(STR(KTX2KTX2_DEFAULT_VERSION)); class ktxUpgrader : public ktxApp { public: ktxUpgrader(); virtual int main(int argc, char* argv[]); virtual void usage(); protected: virtual bool processOption(argparser& parser, int opt); void validateOptions(); struct commandOptions : public ktxApp::commandOptions { bool useStdout; bool force; bool rewriteBadOrientation; commandOptions() { useStdout = false; force = false; rewriteBadOrientation = false; } } options; }; ktxUpgrader::ktxUpgrader() : ktxApp(myversion, mydefversion, options) { argparser::option my_option_list[] = { { "force", argparser::option::no_argument, NULL, 'f' }, { "outfile", argparser::option::required_argument, NULL, 'o' }, { "rewritebado", argparser::option::no_argument, NULL, 'b' }, }; const int lastOptionIndex = sizeof(my_option_list) / sizeof(argparser::option); option_list.insert(option_list.begin(), my_option_list, my_option_list + lastOptionIndex); short_opts += "bd:fo:"; } void ktxUpgrader::usage() { cerr << "Usage: " << name << " [options] [ ...]\n" "\n" " infile The source ktx file. The output is written to a file of the\n" " same name with the extension changed to '.ktx2'. If it is not\n" " specified input will be read from stdin and the converted texture\n" " written to stdout.\n" "\n" " Options are:\n" "\n" " -b, --rewritebado\n" " Rewrite bad orientation metadata. Some in-the-wild KTX files\n" " have orientation metadata with the key \"KTXOrientation\"\n" " instead of \"KTXorientaion\". This option will rewrite such\n" " bad metadata instead of dropping it.\n" " -o outfile, --output=outfile\n" " Name the output file outfile. If @e outfile is 'stdout', output\n" " will be written to stdout. If there is more than 1 infile,\n" " the command prints its usage message and exits.\n" " -f, --force If the output file already exists, remove it and create a\n" " new file, without prompting for confirmation regardless of\n" " its permissions.\n"; ktxApp::usage(); } static ktxUpgrader ktx2ktx2; ktxApp& theApp = ktx2ktx2; int ktxUpgrader::main(int argc, char* argv[]) { FILE *inf, *outf = nullptr; KTX_error_code result; ktxTexture1* texture = 0; int exitCode = 0; processCommandLine(argc, argv); validateOptions(); std::vector::const_iterator it; for (it = options.infiles.begin(); it < options.infiles.end(); it++) { string infile = *it; string outfile; if (infile.compare("-") == 0) { inf = stdin; #if defined(_WIN32) /* Set "stdin" to have binary mode */ (void)_setmode( _fileno( stdin ), _O_BINARY ); #endif } else { inf = fopenUTF8(infile, "rb"); } if (inf) { if (infile.compare("-") && !options.useStdout && !options.outfile.length()) { size_t dot; outfile = infile; dot = outfile.find_last_of('.'); if (dot !=string::npos) { outfile.erase(dot,string::npos); } outfile += ".ktx2"; } else if (options.outfile.length()) { outfile = options.outfile; } if (options.useStdout) { outf = stdout; #if defined(_WIN32) /* Set "stdout" to have binary mode */ (void)_setmode( _fileno( stdout ), _O_BINARY ); #endif } else if (outfile.length()) { outf = fopen_write_if_not_exists(outfile); } if (!outf && errno == EEXIST) { bool force = options.force; if (!force) { if (isatty(fileno(stdin))) { char answer; cout << "Output file " << options.outfile << " exists. Overwrite? [Y or n] "; cin >> answer; if (answer == 'Y') { force = true; } } } if (force) { outf = fopenUTF8(outfile, "wb"); } } if (outf) { result = ktxTexture1_CreateFromStdioStream(inf, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &texture); if (result != KTX_SUCCESS) { if (result == KTX_UNKNOWN_FILE_FORMAT) { cerr << infile << " is not a KTX v1 file." << endl; } else if (result != KTX_SUCCESS) { cerr << name << " failed to create ktxTexture from " << infile << ": " << ktxErrorString(result) << endl; } (void)fclose(inf); (void)fclose(outf); (void)unlinkUTF8(options.outfile.c_str()); exitCode = 2; goto cleanup; } // Some in-the-wild KTX files have incorrect KTXOrientation // Warn about dropping invalid metadata. ktxHashListEntry* pEntry; for (pEntry = texture->kvDataHead; pEntry != NULL; pEntry = ktxHashList_Next(pEntry)) { unsigned int keyLen; char* key; ktxHashListEntry_GetKey(pEntry, &keyLen, &key); if (strncasecmp(key, "KTX", 3) == 0) { if (strcmp(key, KTX_ORIENTATION_KEY) && strcmp(key, KTX_WRITER_KEY)) { if (strcmp(key, "KTXOrientation") == 0 && options.rewriteBadOrientation) { unsigned int orientLen; char* orientation; ktxHashListEntry_GetValue(pEntry, &orientLen, (void**)&orientation); ktxHashList_AddKVPair(&texture->kvDataHead, KTX_ORIENTATION_KEY, orientLen, orientation); } else { cerr << name << ": Warning: Dropping unrecognized " << "metadata \"" << key << "\"" << std::endl; } ktxHashList_DeleteEntry(&texture->kvDataHead, pEntry); } } } // Add required writer metadata. std::stringstream writer; writeId(writer, options.test != 0); ktxHashList_AddKVPair(&texture->kvDataHead, KTX_WRITER_KEY, (ktx_uint32_t)writer.str().length() + 1, writer.str().c_str()); result = ktxTexture1_WriteKTX2ToStdioStream(texture, outf); ktxTexture_Destroy(ktxTexture(texture)); (void)fclose(inf); (void)fclose(outf); if (result != KTX_SUCCESS) { cerr << name << " failed to write KTX2 file; " << ktxErrorString(result) << endl; (void)unlinkUTF8(options.outfile.c_str()); exitCode = 2; goto cleanup; } } else { cerr << name << " could not open output file \"" << (options.outfile.length() ? options.outfile.c_str() : "stdout") << "\". " << strerror(errno) << endl; exitCode = 2; goto cleanup; } } else { cerr << name << " could not open input file \"" << (infile.compare("-") ? infile : "stdin") << "\". " << strerror(errno) << endl; exitCode = 2; goto cleanup; } } cleanup: return exitCode; } void ktxUpgrader::validateOptions() { if (options.infiles.size() > 1 && options.outfile.length()) { cerr << "Can't use -o when there are multiple infiles." << endl; usage(); exit(1); } } /* * @brief process a command line option * * @return true if option processed, false otherwise. * * @param[in] parser, an @c argparser holding the options to process. * @param[in,out] options commandOptions struct in which option information * is set. */ bool ktxUpgrader::processOption(argparser& parser, int opt) { switch (opt) { case 'b': options.rewriteBadOrientation = true; break; case 'f': options.force = true; break; case 'o': options.outfile = parser.optarg; if (!options.outfile.compare("stdout")) { options.useStdout = true; } else { size_t dot; dot = options.outfile.find_last_of('.'); if (dot ==string::npos) { options.outfile += ".ktx2"; } } break; default: return false; } return true; } #if IMAGE_DEBUG static void dumpImage(char* name, int width, int height, int components, int componentSize, bool isLuminance, unsigned char* srcImage) { char formatstr[2048]; char *imagefmt; char *fmtname; int bitsPerComponent = componentSize == 2 ? 16 : 8; switch (components) { case 1: if (isLuminance) { imagefmt = "lum b="; fmtname = "LUMINANCE"; } else { imagefmt = "a b="; fmtname = "ALPHA"; } break; case 2: imagefmt = "luma b="; fmtname = "LUMINANCE_ALPHA"; break; case 3: imagefmt = "rgb b="; fmtname = "RGB"; break; case 4: imagefmt = "rgba b="; fmtname = "RGBA"; break; default: assert(0); } sprintf(formatstr, "%s%d w=%%d h=%%d t=\'%s %s%d\' %%p", imagefmt, bitsPerComponent, name, fmtname, bitsPerComponent); imdebug(formatstr, width, height, srcImage); } #endif