From dfd3f70112364dd3122bba8910a7c73b666835b8 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 11 Mar 2024 19:07:12 +0100 Subject: [PATCH] nearblack: use GDALArgumentParser --- apps/gdal_utils_priv.h | 16 ++- apps/nearblack_bin.cpp | 83 +++--------- apps/nearblack_lib.cpp | 281 ++++++++++++++++++++++------------------- 3 files changed, 178 insertions(+), 202 deletions(-) diff --git a/apps/gdal_utils_priv.h b/apps/gdal_utils_priv.h index b34f7a3cd4e7..193ed3a047ab 100644 --- a/apps/gdal_utils_priv.h +++ b/apps/gdal_utils_priv.h @@ -97,13 +97,6 @@ struct GDALDEMProcessingOptionsForBinary int bQuiet; }; -struct GDALNearblackOptionsForBinary -{ - char *pszInFile; - char *pszOutFile; - int bQuiet; -}; - struct GDALBuildVRTOptionsForBinary { int nSrcFiles; @@ -230,6 +223,15 @@ struct GDALTileIndexOptionsForBinary bool bQuiet = false; }; +struct GDALNearblackOptionsForBinary +{ + std::string osInFile{}; + std::string osOutFile{}; + bool bQuiet = false; +}; + +std::string CPL_DLL GDALNearblackOptionsGetParserUsage(); + #endif /* #ifndef DOXYGEN_SKIP */ #endif /* GDAL_UTILS_PRIV_H_INCLUDED */ diff --git a/apps/nearblack_bin.cpp b/apps/nearblack_bin.cpp index 5ff9a1fbc544..12b209ff510c 100644 --- a/apps/nearblack_bin.cpp +++ b/apps/nearblack_bin.cpp @@ -35,46 +35,16 @@ /* Usage() */ /************************************************************************/ -static void Usage(bool bIsError, const char *pszErrorMsg = nullptr) +static void Usage(const char *pszErrorMsg = nullptr) { - fprintf(bIsError ? stderr : stdout, - "Usage: nearblack [--help] [--help-general]\n" - " [-of ] [-white | [-color " - ",,...]...]\n" - " [-near ] [-nb ]\n" - " [-setalpha] [-setmask] [-alg twopasses|floodfill]\n" - " [-o ] [-q] [-co =]... \n"); + fprintf(stderr, "%s\n\n", GDALNearblackOptionsGetParserUsage().c_str()); if (pszErrorMsg != nullptr) fprintf(stderr, "\nFAILURE: %s\n", pszErrorMsg); - exit(bIsError ? 1 : 0); + exit(1); } -/************************************************************************/ -/* GDALNearblackOptionsForBinaryNew() */ -/************************************************************************/ - -static GDALNearblackOptionsForBinary *GDALNearblackOptionsForBinaryNew() -{ - return static_cast( - CPLCalloc(1, sizeof(GDALNearblackOptionsForBinary))); -} - -/************************************************************************/ -/* GDALNearblackOptionsForBinaryFree() */ -/************************************************************************/ - -static void GDALNearblackOptionsForBinaryFree( - GDALNearblackOptionsForBinary *psOptionsForBinary) -{ - if (psOptionsForBinary) - { - CPLFree(psOptionsForBinary->pszInFile); - CPLFree(psOptionsForBinary->pszOutFile); - CPLFree(psOptionsForBinary); - } -} /************************************************************************/ /* main() */ /************************************************************************/ @@ -98,44 +68,23 @@ MAIN_START(argc, argv) if (argc < 1) exit(-argc); - for (int i = 0; i < argc; i++) - { - if (EQUAL(argv[i], "--utility_version")) - { - printf("%s was compiled against GDAL %s and " - "is running against GDAL %s\n", - argv[0], GDAL_RELEASE_NAME, GDALVersionInfo("RELEASE_NAME")); - CSLDestroy(argv); - return 0; - } - else if (EQUAL(argv[i], "--help")) - { - Usage(false); - } - } - - GDALNearblackOptionsForBinary *psOptionsForBinary = - GDALNearblackOptionsForBinaryNew(); + GDALNearblackOptionsForBinary sOptionsForBinary; GDALNearblackOptions *psOptions = - GDALNearblackOptionsNew(argv + 1, psOptionsForBinary); + GDALNearblackOptionsNew(argv + 1, &sOptionsForBinary); CSLDestroy(argv); if (psOptions == nullptr) { - Usage(true); + Usage(); } - if (!(psOptionsForBinary->bQuiet)) + if (!(sOptionsForBinary.bQuiet)) { GDALNearblackOptionsSetProgress(psOptions, GDALTermProgress, nullptr); } - if (psOptionsForBinary->pszInFile == nullptr) - Usage(true, "No input file specified."); - - if (psOptionsForBinary->pszOutFile == nullptr) - psOptionsForBinary->pszOutFile = - CPLStrdup(psOptionsForBinary->pszInFile); + if (sOptionsForBinary.osOutFile.empty()) + sOptionsForBinary.osOutFile = sOptionsForBinary.osInFile; /* -------------------------------------------------------------------- */ /* Open input file. */ @@ -144,15 +93,14 @@ MAIN_START(argc, argv) GDALDatasetH hOutDS = nullptr; bool bCloseRetDS = false; - if (strcmp(psOptionsForBinary->pszOutFile, psOptionsForBinary->pszInFile) == - 0) + if (sOptionsForBinary.osOutFile == sOptionsForBinary.osInFile) { - hInDS = GDALOpen(psOptionsForBinary->pszInFile, GA_Update); + hInDS = GDALOpen(sOptionsForBinary.osInFile.c_str(), GA_Update); hOutDS = hInDS; } else { - hInDS = GDALOpen(psOptionsForBinary->pszInFile, GA_ReadOnly); + hInDS = GDALOpen(sOptionsForBinary.osInFile.c_str(), GA_ReadOnly); bCloseRetDS = true; } @@ -160,10 +108,10 @@ MAIN_START(argc, argv) exit(1); int bUsageError = FALSE; - GDALDatasetH hRetDS = GDALNearblack(psOptionsForBinary->pszOutFile, hOutDS, - hInDS, psOptions, &bUsageError); + GDALDatasetH hRetDS = GDALNearblack(sOptionsForBinary.osOutFile.c_str(), + hOutDS, hInDS, psOptions, &bUsageError); if (bUsageError) - Usage(true); + Usage(); int nRetCode = hRetDS ? 0 : 1; if (GDALClose(hInDS) != CE_None) @@ -174,7 +122,6 @@ MAIN_START(argc, argv) nRetCode = 1; } GDALNearblackOptionsFree(psOptions); - GDALNearblackOptionsForBinaryFree(psOptionsForBinary); GDALDestroyDriverManager(); diff --git a/apps/nearblack_lib.cpp b/apps/nearblack_lib.cpp index 4e0ef1540939..8735f6bba6ea 100644 --- a/apps/nearblack_lib.cpp +++ b/apps/nearblack_lib.cpp @@ -31,6 +31,7 @@ #include "gdal_utils.h" #include "gdal_utils_priv.h" #include "commonutils.h" +#include "gdalargumentparser.h" #include #include @@ -746,7 +747,149 @@ static bool IsInt(const char *pszArg) } /************************************************************************/ -/* GDALNearblackOptionsNew() */ +/* GDALNearblackOptionsGetParser() */ +/************************************************************************/ + +static std::unique_ptr +GDALNearblackOptionsGetParser(GDALNearblackOptions *psOptions, + GDALNearblackOptionsForBinary *psOptionsForBinary) +{ + auto argParser = std::make_unique( + "nearblack", /* bForBinary=*/psOptionsForBinary != nullptr); + + argParser->add_description( + _("Convert nearly black/white borders to black.")); + + argParser->add_epilog(_( + "For more details, consult https://gdal.org/programs/nearblack.html")); + + argParser->add_output_format_argument(psOptions->osFormat); + + argParser->add_quiet_argument( + psOptionsForBinary ? &(psOptionsForBinary->bQuiet) : nullptr); + + argParser->add_creation_options_argument(psOptions->aosCreationOptions); + + auto &oOutputFileArg = + argParser->add_argument("-o") + .metavar("") + .help(_("The name of the output file to be created.")); + if (psOptionsForBinary) + oOutputFileArg.store_into(psOptionsForBinary->osOutFile); + + { + auto &group = argParser->add_mutually_exclusive_group(); + group.add_argument("-white") + .store_into(psOptions->bNearWhite) + .help(_("Search for nearly white (255) pixels instead of nearly " + "black pixels.")); + + group.add_argument("-color") + .append() + .metavar("") + .action( + [psOptions](const std::string &s) + { + Color oColor; + + /***** tokenize the arg on , *****/ + + const CPLStringList aosTokens( + CSLTokenizeString2(s.c_str(), ",", 0)); + + /***** loop over the tokens *****/ + + for (int iToken = 0; iToken < aosTokens.size(); iToken++) + { + + /***** ensure the token is an int and add it to the color *****/ + + if (IsInt(aosTokens[iToken])) + { + oColor.push_back(atoi(aosTokens[iToken])); + } + else + { + throw std::invalid_argument( + "Colors must be valid integers."); + } + } + + /***** check if the number of bands is consistent *****/ + + if (!psOptions->oColors.empty() && + psOptions->oColors.front().size() != oColor.size()) + { + throw std::invalid_argument( + "all -color args must have the same number of " + "values.\n"); + } + + /***** add the color to the colors *****/ + + psOptions->oColors.push_back(oColor); + }) + .help(_("Search for pixels near the specified color")); + } + + argParser->add_argument("-nb") + .store_into(psOptions->nMaxNonBlack) + .metavar("") + .default_value(psOptions->nMaxNonBlack) + .nargs(1) + .help(_("Number of consecutive non-black pixels")); + + argParser->add_argument("-near") + .store_into(psOptions->nNearDist) + .metavar("") + .default_value(psOptions->nNearDist) + .nargs(1) + .help(_("Select how far from black, white or custom colors the pixel " + "values can be and still considered")); + + argParser->add_argument("-setalpha") + .store_into(psOptions->bSetAlpha) + .help(_("Adds an alpha band if needed")); + + argParser->add_argument("-setmask") + .store_into(psOptions->bSetMask) + .help(_("Adds a mask band to the output file if -o is used, or to the " + "input file otherwise")); + + argParser->add_argument("-alg") + .choices("floodfill", "twopasses") + .metavar("floodfill|twopasses") + .action([psOptions](const std::string &s) + { psOptions->bFloodFill = EQUAL(s.c_str(), "floodfill"); }) + .help(_("Selects the algorithm to apply.")); + + if (psOptionsForBinary) + { + argParser->add_argument("input_file") + .metavar("") + .store_into(psOptionsForBinary->osInFile) + .help(_("The input file. Any GDAL supported format, any number of " + "bands, normally 8bit Byte bands.")); + } + + return argParser; +} + +/************************************************************************/ +/* GDALNearblackOptionsGetParserUsage() */ +/************************************************************************/ + +std::string GDALNearblackOptionsGetParserUsage() +{ + GDALNearblackOptions sOptions; + GDALNearblackOptionsForBinary sOptionsForBinary; + auto argParser = + GDALNearblackOptionsGetParser(&sOptions, &sOptionsForBinary); + return argParser->usage(); +} + +/************************************************************************/ +/* GDALNearblackOptionsNew() */ /************************************************************************/ /** @@ -771,133 +914,17 @@ GDALNearblackOptionsNew(char **papszArgv, { auto psOptions = std::make_unique(); - /* -------------------------------------------------------------------- */ - /* Handle command line arguments. */ - /* -------------------------------------------------------------------- */ - const int argc = CSLCount(papszArgv); - for (int i = 0; papszArgv != nullptr && i < argc; i++) - { - if (i < argc - 1 && - (EQUAL(papszArgv[i], "-of") || EQUAL(papszArgv[i], "-f"))) - { - ++i; - psOptions->osFormat = papszArgv[i]; - } + auto argParser = + GDALNearblackOptionsGetParser(psOptions.get(), psOptionsForBinary); - else if (EQUAL(papszArgv[i], "-q") || EQUAL(papszArgv[i], "-quiet")) - { - if (psOptionsForBinary) - psOptionsForBinary->bQuiet = TRUE; - } - else if (i + 1 < argc && EQUAL(papszArgv[i], "-co")) - { - psOptions->aosCreationOptions.AddString(papszArgv[++i]); - } - else if (i + 1 < argc && EQUAL(papszArgv[i], "-o")) - { - i++; - if (psOptionsForBinary) - { - CPLFree(psOptionsForBinary->pszOutFile); - psOptionsForBinary->pszOutFile = CPLStrdup(papszArgv[i]); - } - } - else if (EQUAL(papszArgv[i], "-white")) - { - psOptions->bNearWhite = true; - } - - /***** -color c1,c2,c3...cn *****/ - - else if (i + 1 < argc && EQUAL(papszArgv[i], "-color")) - { - Color oColor; - - /***** tokenize the arg on , *****/ - - const CPLStringList aosTokens( - CSLTokenizeString2(papszArgv[++i], ",", 0)); - - /***** loop over the tokens *****/ - - for (int iToken = 0; iToken < aosTokens.size(); iToken++) - { - - /***** ensure the token is an int and add it to the color *****/ - - if (IsInt(aosTokens[iToken])) - { - oColor.push_back(atoi(aosTokens[iToken])); - } - else - { - CPLError(CE_Failure, CPLE_AppDefined, - "Colors must be valid integers."); - return nullptr; - } - } - - /***** check if the number of bands is consistent *****/ - - if (!psOptions->oColors.empty() && - psOptions->oColors.front().size() != oColor.size()) - { - CPLError( - CE_Failure, CPLE_AppDefined, - "all -color args must have the same number of values.\n"); - return nullptr; - } - - /***** add the color to the colors *****/ - - psOptions->oColors.push_back(oColor); - } - else if (i + 1 < argc && EQUAL(papszArgv[i], "-nb")) - { - psOptions->nMaxNonBlack = atoi(papszArgv[++i]); - } - else if (i + 1 < argc && EQUAL(papszArgv[i], "-near")) - { - psOptions->nNearDist = atoi(papszArgv[++i]); - } - else if (EQUAL(papszArgv[i], "-setalpha")) - { - psOptions->bSetAlpha = true; - } - else if (EQUAL(papszArgv[i], "-setmask")) - { - psOptions->bSetMask = true; - } - else if (i + 1 < argc && EQUAL(papszArgv[i], "-alg")) - { - const char *pszAlg = papszArgv[++i]; - if (EQUAL(pszAlg, "floodfill")) - psOptions->bFloodFill = true; - else if (EQUAL(pszAlg, "twopasses")) - psOptions->bFloodFill = false; - else - { - CPLError(CE_Failure, CPLE_NotSupported, - "Unsupported algorithm '%s'", papszArgv[i]); - return nullptr; - } - } - else if (papszArgv[i][0] == '-') - { - CPLError(CE_Failure, CPLE_NotSupported, "Unknown option name '%s'", - papszArgv[i]); - return nullptr; - } - else if (psOptionsForBinary && psOptionsForBinary->pszInFile == nullptr) - { - psOptionsForBinary->pszInFile = CPLStrdup(papszArgv[i]); - } - else - { - CPLError(CE_Failure, CPLE_NotSupported, - "Too many command options '%s'", papszArgv[i]); - return nullptr; - } + try + { + argParser->parse_args_without_binary_name(papszArgv); + } + catch (const std::exception &err) + { + CPLError(CE_Failure, CPLE_AppDefined, "%s", err.what()); + return nullptr; } return psOptions.release();