From 19d9bc7ba036c5dfd25bb92dc741939070e6bc36 Mon Sep 17 00:00:00 2001 From: Franz Trischberger Date: Fri, 30 Nov 2018 14:31:00 +0200 Subject: [PATCH 1/3] Add new option to set a fixed size for brackets. Bracketed sets will not get computed automatically. Closes #146 --- src/Launcher.cpp | 69 +++++++++++++++++++++++++++++------------ src/LoadSaveOptions.hpp | 3 +- 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/src/Launcher.cpp b/src/Launcher.cpp index a01b419..cabd37d 100644 --- a/src/Launcher.cpp +++ b/src/Launcher.cpp @@ -20,8 +20,10 @@ * */ +#include #include #include +#include #include #include #include @@ -73,27 +75,45 @@ struct CoutProgressIndicator : public ProgressIndicator { list Launcher::getBracketedSets() { list result; - list> dateNames; - for (QString & name : generalOptions.fileNames) { - ImageIO::QDateInterval interval = ImageIO::getImageCreationInterval(name); - if (interval.start.isValid()) { - dateNames.emplace_back(interval, name); - } else { - // We cannot get time information, process it alone - result.push_back(generalOptions); - result.back().fileNames.clear(); - result.back().fileNames.push_back(name); + if (generalOptions.imagesPerBracket > 0) { + if (generalOptions.fileNames.size() % generalOptions.imagesPerBracket != 0) { + cerr << QCoreApplication::translate("LoadSave", "Number of files not a multiple of number per bracketed set (-s). Aborting."); + exit(EXIT_FAILURE); } - } - dateNames.sort(); - ImageIO::QDateInterval lastInterval; - for (auto & dateName : dateNames) { - if (lastInterval.start.isNull() || lastInterval.difference(dateName.first) > generalOptions.batchGap) { - result.push_back(generalOptions); - result.back().fileNames.clear(); + + while(!generalOptions.fileNames.empty()) { + LoadOptions opt = generalOptions; + auto oIt = opt.fileNames.begin(); + auto goIt = generalOptions.fileNames.begin(); + std::advance(oIt, generalOptions.imagesPerBracket); + std::advance(goIt, generalOptions.imagesPerBracket); + opt.fileNames.erase(oIt, opt.fileNames.end()); + generalOptions.fileNames.erase(generalOptions.fileNames.begin(), goIt); + result.push_back(opt); + } + } else { + list> dateNames; + for (QString & name : generalOptions.fileNames) { + ImageIO::QDateInterval interval = ImageIO::getImageCreationInterval(name); + if (interval.start.isValid()) { + dateNames.emplace_back(interval, name); + } else { + // We cannot get time information, process it alone + result.push_back(generalOptions); + result.back().fileNames.clear(); + result.back().fileNames.push_back(name); + } + } + dateNames.sort(); + ImageIO::QDateInterval lastInterval; + for (auto & dateName : dateNames) { + if (lastInterval.start.isNull() || lastInterval.difference(dateName.first) > generalOptions.batchGap) { + result.push_back(generalOptions); + result.back().fileNames.clear(); + } + result.back().fileNames.push_back(dateName.second); + lastInterval = dateName.first; } - result.back().fileNames.push_back(dateName.second); - lastInterval = dateName.first; } int setNum = 0; for (auto & i : result) { @@ -175,6 +195,15 @@ void Launcher::parseCommandLine() { generalOptions.crop = false; } else if (string("--batch") == argv[i] || string("-B") == argv[i]) { generalOptions.batch = true; + } else if (string("-s") == argv[i]) { + if (++i < argc) { + try { + int value = stoi(argv[i]); + generalOptions.imagesPerBracket = value; + } catch (std::invalid_argument & e) { + cerr << tr("Invalid %1 parameter, falling back to interval-based bracketing set creation.").arg(argv[i - 1]) << endl; + } + } } else if (string("--single") == argv[i]) { generalOptions.withSingles = true; } else if (string("--help") == argv[i]) { @@ -258,6 +287,8 @@ void Launcher::showHelp() { cout << " " << "-a " << tr("Calculates the output file name as") << " %id[-1]/%iF[0]-%in[-1].dng." << endl; cout << " " << "-B|--batch " << tr("Batch mode: Input images are automatically grouped into bracketed sets,") << endl; cout << " " << " " << tr("by comparing the creation time. Implies -a if no output file name is given.") << endl; + cout << " " << "-s NUM_IMAGES " << tr("Fixed number of images per bracket set. Use together with -B.") << endl; + cout << " " << " " << tr("Creation time will be ignored.") << endl; cout << " " << "-g gap " << tr("Batch gap, maximum difference in seconds between two images of the same set.") << endl; cout << " " << "--single " << tr("Include single images in batch mode (the default is to skip them.)") << endl; cout << " " << "-b BPS " << tr("Bits per sample, can be 16, 24 or 32.") << endl; diff --git a/src/LoadSaveOptions.hpp b/src/LoadSaveOptions.hpp index 5c3db3d..7d6fce0 100644 --- a/src/LoadSaveOptions.hpp +++ b/src/LoadSaveOptions.hpp @@ -35,9 +35,10 @@ struct LoadOptions { bool useCustomWl; uint16_t customWl; bool batch; + int imagesPerBracket; double batchGap; bool withSingles; - LoadOptions() : align(true), crop(true), useCustomWl(false), customWl(16383), batch(false), batchGap(2.0), + LoadOptions() : align(true), crop(true), useCustomWl(false), customWl(16383), batch(false), imagesPerBracket(-1), batchGap(2.0), withSingles(false) {} }; From 341ea86e4e2ced6959699dd29d041889c0e63e34 Mon Sep 17 00:00:00 2001 From: Franz Trischberger Date: Thu, 3 Oct 2019 12:19:34 +0300 Subject: [PATCH 2/3] Port manual commandline parsing to QCommandLineParser This commit drops the "-a" option as it wasn't used besides the useGUI check. Use -B for batch conversion and automatic naming of generated DNG files. Otherwise it's compatible. Also add validation of parameters. --- src/Launcher.cpp | 374 +++++++++++++++++++++++----------------- src/Launcher.hpp | 2 +- src/LoadSaveOptions.hpp | 3 +- src/MainWindow.hpp | 5 +- 4 files changed, 221 insertions(+), 163 deletions(-) diff --git a/src/Launcher.cpp b/src/Launcher.cpp index cabd37d..f833301 100644 --- a/src/Launcher.cpp +++ b/src/Launcher.cpp @@ -20,7 +20,6 @@ * */ -#include #include #include #include @@ -29,6 +28,8 @@ #include #include #include +#include +#include #include "Launcher.hpp" #include "ImageIO.hpp" #ifndef NO_GUI @@ -41,7 +42,7 @@ using namespace std; namespace hdrmerge { -Launcher::Launcher(int argc, char * argv[]) : argc(argc), argv(argv), help(false) { +Launcher::Launcher(int argc, char * argv[]) : argc(argc), argv(argv), useGui(true) { Log::setOutputStream(cout); saveOptions.previewSize = 2; } @@ -175,170 +176,228 @@ int Launcher::automaticMerge() { void Launcher::parseCommandLine() { auto tr = [&] (const char * text) { return QCoreApplication::translate("Help", text); }; - for (int i = 1; i < argc; ++i) { - if (string("-o") == argv[i]) { - if (++i < argc) { - saveOptions.fileName = argv[i]; - } - } else if (string("-m") == argv[i]) { - if (++i < argc) { - saveOptions.maskFileName = argv[i]; - saveOptions.saveMask = true; - } - } else if (string("-v") == argv[i]) { - Log::setMinimumPriority(1); - } else if (string("-vv") == argv[i]) { - Log::setMinimumPriority(0); - } else if (string("--no-align") == argv[i]) { - generalOptions.align = false; - } else if (string("--no-crop") == argv[i]) { - generalOptions.crop = false; - } else if (string("--batch") == argv[i] || string("-B") == argv[i]) { - generalOptions.batch = true; - } else if (string("-s") == argv[i]) { - if (++i < argc) { - try { - int value = stoi(argv[i]); - generalOptions.imagesPerBracket = value; - } catch (std::invalid_argument & e) { - cerr << tr("Invalid %1 parameter, falling back to interval-based bracketing set creation.").arg(argv[i - 1]) << endl; - } - } - } else if (string("--single") == argv[i]) { - generalOptions.withSingles = true; - } else if (string("--help") == argv[i]) { - help = true; - } else if (string("-b") == argv[i]) { - if (++i < argc) { - try { - int value = stoi(argv[i]); - if (value == 32 || value == 24 || value == 16) saveOptions.bps = value; - } catch (std::invalid_argument & e) { - cerr << tr("Invalid %1 parameter, using default.").arg(argv[i - 1]) << endl; - } - } - } else if (string("-w") == argv[i]) { - if (++i < argc) { - try { - generalOptions.customWl = stoi(argv[i]); - generalOptions.useCustomWl = true; - } catch (std::invalid_argument & e) { - cerr << tr("Invalid %1 parameter, using default.").arg(argv[i - 1]) << endl; - generalOptions.useCustomWl = false; - } - } - } else if (string("-g") == argv[i]) { - if (++i < argc) { - try { - generalOptions.batchGap = stod(argv[i]); - } catch (std::invalid_argument & e) { - cerr << tr("Invalid %1 parameter, using default.").arg(argv[i - 1]) << endl; - } - } - } else if (string("-r") == argv[i]) { - if (++i < argc) { - try { - saveOptions.featherRadius = stoi(argv[i]); - } catch (std::invalid_argument & e) { - cerr << tr("Invalid %1 parameter, using default.").arg(argv[i - 1]) << endl; - } - } - } else if (string("-p") == argv[i]) { - if (++i < argc) { - string previewWidth(argv[i]); - if (previewWidth == "full") { - saveOptions.previewSize = 2; - } else if (previewWidth == "half") { - saveOptions.previewSize = 1; - } else if (previewWidth == "none") { - saveOptions.previewSize = 0; - } else { - cerr << tr("Invalid %1 parameter, using default.").arg(argv[i - 1]) << endl; - } - } - } else if (argv[i][0] != '-') { - generalOptions.fileNames.push_back(QString::fromLocal8Bit(argv[i])); - } - } -} - -void Launcher::showHelp() { - auto tr = [&] (const char * text) { return QCoreApplication::translate("Help", text); }; - cout << tr("Usage") << ": HDRMerge [--help] [OPTIONS ...] [RAW_FILES ...]" << endl; - cout << tr("Merges RAW_FILES into an HDR DNG raw image.") << endl; + QCommandLineParser parser; + parser.setApplicationDescription( + tr("Merges raw_files into an HDR DNG raw image.\n") + #ifndef NO_GUI - cout << tr("If neither -a nor -o, nor --batch options are given, the GUI will be presented.") << endl; + tr("If neither -o nor --batch options are given, the GUI will be presented.\n") + #endif - cout << tr("If similar options are specified, only the last one prevails.") << endl; - cout << endl; - cout << tr("Options:") << endl; - cout << " " << "--help " << tr("Shows this message.") << endl; - cout << " " << "-o OUT_FILE " << tr("Sets OUT_FILE as the output file name.") << endl; - cout << " " << " " << tr("The following parameters are accepted, most useful in batch mode:") << endl; - cout << " " << " - %if[n]: " << tr("Replaced by the base file name of image n. Image file names") << endl; - cout << " " << " " << tr("are first sorted in lexicographical order. Besides, n = -1 is the") << endl; - cout << " " << " " << tr("last image, n = -2 is the previous to the last image, and so on.") << endl; - cout << " " << " - %iF[n]: " << tr("Replaced by the base file name of image n without the extension.") << endl; - cout << " " << " - %id[n]: " << tr("Replaced by the directory name of image n.") << endl; - cout << " " << " - %in[n]: " << tr("Replaced by the numerical suffix of image n, if it exists.") << endl; - cout << " " << " " << tr("For instance, in IMG_1234.CR2, the numerical suffix would be 1234.") << endl; - cout << " " << " - %%: " << tr("Replaced by a single %.") << endl; - cout << " " << "-a " << tr("Calculates the output file name as") << " %id[-1]/%iF[0]-%in[-1].dng." << endl; - cout << " " << "-B|--batch " << tr("Batch mode: Input images are automatically grouped into bracketed sets,") << endl; - cout << " " << " " << tr("by comparing the creation time. Implies -a if no output file name is given.") << endl; - cout << " " << "-s NUM_IMAGES " << tr("Fixed number of images per bracket set. Use together with -B.") << endl; - cout << " " << " " << tr("Creation time will be ignored.") << endl; - cout << " " << "-g gap " << tr("Batch gap, maximum difference in seconds between two images of the same set.") << endl; - cout << " " << "--single " << tr("Include single images in batch mode (the default is to skip them.)") << endl; - cout << " " << "-b BPS " << tr("Bits per sample, can be 16, 24 or 32.") << endl; - cout << " " << "--no-align " << tr("Do not auto-align source images.") << endl; - cout << " " << "--no-crop " << tr("Do not crop the output image to the optimum size.") << endl; - cout << " " << "-m MASK_FILE " << tr("Saves the mask to MASK_FILE as a PNG image.") << endl; - cout << " " << " " << tr("Besides the parameters accepted by -o, it also accepts:") << endl; - cout << " " << " - %of: " << tr("Replaced by the base file name of the output file.") << endl; - cout << " " << " - %od: " << tr("Replaced by the directory name of the output file.") << endl; - cout << " " << "-r radius " << tr("Mask blur radius, to soften transitions between images. Default is 3 pixels.") << endl; - cout << " " << "-p size " << tr("Preview size. Can be full, half or none.") << endl; - cout << " " << "-v " << tr("Verbose mode.") << endl; - cout << " " << "-vv " << tr("Debug mode.") << endl; - cout << " " << "-w whitelevel " << tr("Use custom white level.") << endl; - cout << " " << "RAW_FILES " << tr("The input raw files.") << endl; -} + tr("If similar options are specified, only the last one prevails.\n") + ); + + parser.addPositionalArgument("files", "The input raw files", "[raw_files...]"); + + QCommandLineOption outputOpt("o", + tr("Sets as the output file name. " + "If not set it falls back to '%1'.\n" + "The following parameters are accepted, most useful in batch mode:").arg("%id[-1]/%iF[0]-%in[-1].dng") + + "\n- %if[n]: " + tr("Replaced by the base file name of image n. Image file names " + "are first sorted in lexicographical order. Besides, n = -1 is the " + "last image, n = -2 is the previous to the last image, and so on.") + + "\n- %iF[n]: " + tr("Replaced by the base file name of image n without the extension.") + + "\n- %id[n]: " + tr("Replaced by the directory name of image n.") + + "\n- %in[n]: " + tr("Replaced by the numerical suffix of image n, if it exists." + "For instance, in IMG_1234.CR2, the numerical suffix would be 1234.") + + "\n- %%: " + tr("Replaced by a single %."), + "file"); + parser.addOption(outputOpt); + + QCommandLineOption maskFileOpt("m", + tr("Saves the mask to as a PNG image.\n" + "Besides the parameters accepted by -o, it also accepts:") + + "\n- %of: " + tr("Replaced by the base file name of the output file.") + + "\n- %od: " + tr("Replaced by the directory name of the output file."), + "file"); + parser.addOption(maskFileOpt); + + QCommandLineOption logLevelOpt({"l", "loglevel"}, + tr("The level of logging output you want to recieve."), + "verbose|debug"); + parser.addOption(logLevelOpt); + + QCommandLineOption noAlignOpt("no-align", + tr("Do not auto-align source images.")); + parser.addOption(noAlignOpt); + + QCommandLineOption noCropOpt("no-crop", + tr("Do not crop the output image to the optimum size.")); + parser.addOption(noCropOpt); + + QCommandLineOption batchOpt({"B","batch"}, + tr("Batch mode: Input images are automatically grouped into bracketed sets, " + "by comparing the creation time.") + ); + parser.addOption(batchOpt); + + QCommandLineOption bracketSizeOpt({"s", "bracket-size"}, + tr("Fixed number of images per bracket set. Use together with -B. " + "Creation time and --single option will be ignored."), + "integer", QString::number(-1)); + parser.addOption(bracketSizeOpt); + + QCommandLineOption singleOpt("single", + tr("Include single images in batch mode (the default is to skip them.)")); + parser.addOption(singleOpt); + + QCommandLineOption helpOpt = parser.addHelpOption(); + + QCommandLineOption bitOpt("b", + tr("Bits per sample."), + "16|24|32", QString::number(16)); + parser.addOption(bitOpt); + + QCommandLineOption whiteLevelOpt("w", + tr("Use custom white level."), "integer", "16383"); + parser.addOption(whiteLevelOpt); + + QCommandLineOption gapOpt("g", + tr("Batch gap, maximum difference in seconds between two images of the same set."), + "double", QString::number(2.0)); + parser.addOption(gapOpt); + + QCommandLineOption featherRadiusOpt("r", + tr("Mask blur radius, to soften transitions between images. Default is 3 pixels."), + "integer", QString::number(3)); + parser.addOption(featherRadiusOpt); + + QCommandLineOption previewSizeOpt("p", + tr("Size of the preview. Default is none."), + "full|half|none", "none"); + parser.addOption(previewSizeOpt); + + parser.process(QCoreApplication::arguments()); + + bool ok; // to check conversions from QString + + char const * errorStart = ">>> ERROR >>> ERROR >>> ERROR >>>\n"; + char const * errorStop = "\n<<< ERROR <<< ERROR <<< ERROR <<<"; + /** + * Use this when the user passes an invalid parameter, like "-b=17" (b:16;24;32) + */ + auto invalidParamHandler = [&] (const QCommandLineOption& opt) { + cerr << errorStart << + tr("Invalid parameter '%1' for option '-%2'. " + "Please read the help for valid parameters.") + .arg(parser.value(opt)) + .arg(opt.names().at(0)) + << errorStop << endl; + // exit right away, creating HDR takes a lot of time + // and just falling back to default is probably not what the user wants... + parser.showHelp(EXIT_FAILURE); + }; -bool Launcher::checkGUI() { - int numFiles = 0; - bool useGUI = true; - for (int i = 1; i < argc; ++i) { - if (string("-o") == argv[i]) { - if (++i < argc) { - useGUI = false; + /** + * Use this when the user passes an invalid type, like "-b=sixteen" (b:integer) + */ + auto invalidParamTypeHandler = [&] (const QCommandLineOption& opt) { + cerr << errorStart << + tr("Invalid parameter type for option '-%1'. " + "Please read the help for the valid type.") + .arg(opt.names().at(0)) + << errorStop << endl; + parser.showHelp(EXIT_FAILURE); + }; + + generalOptions.fileNames = parser.positionalArguments(); + + saveOptions.fileName = parser.value(outputOpt); + if (parser.isSet(maskFileOpt)) { + saveOptions.saveMask = true; + saveOptions.maskFileName = parser.value(outputOpt); + } + if (parser.isSet(logLevelOpt)) { + QString value = parser.value(logLevelOpt); + if (value == "verbose" || value == "v") { + Log::setMinimumPriority(1); + } else if (value == "debug" || value == "d") { + Log::setMinimumPriority(0); + } else { + invalidParamHandler(logLevelOpt); + } + } + generalOptions.align = !parser.isSet(noAlignOpt); + generalOptions.crop = !parser.isSet(noCropOpt); + generalOptions.batch = parser.isSet(batchOpt); + { + QString value = parser.value(bracketSizeOpt); + int iVal = value.toInt(&ok); + if (ok) { + generalOptions.imagesPerBracket = iVal; + } else { + invalidParamTypeHandler(bracketSizeOpt); + } + } + generalOptions.withSingles = parser.isSet(singleOpt); + { + QString value = parser.value(gapOpt); + double dVal = value.toDouble(&ok); + if (ok) { + generalOptions.batchGap = dVal; + } else { + invalidParamTypeHandler(gapOpt); + } + } + { + QString value = parser.value(featherRadiusOpt); + int iVal = value.toInt(&ok); + if (ok) { + saveOptions.featherRadius = iVal; + } else { + invalidParamTypeHandler(featherRadiusOpt); + } + } + { + generalOptions.useCustomWl = parser.isSet(whiteLevelOpt); + QString value = parser.value(whiteLevelOpt); + uint iVal = value.toUInt(&ok); + if (ok) { + generalOptions.customWl = iVal; + } else { + invalidParamTypeHandler(whiteLevelOpt); + } + } + { + QString previewSize = parser.value(previewSizeOpt); + if (previewSize == "none" || previewSize == "n") { + saveOptions.previewSize = 0; + } else if (previewSize == "half" || previewSize =="h") { + saveOptions.previewSize = 1; + } else if (previewSize == "full" || previewSize == "f") { + saveOptions.previewSize = 2; + } else { + invalidParamHandler(previewSizeOpt); + } + } + { + QString value = parser.value(bitOpt); + int iVal = value.toInt(&ok); + if (ok) { + if (iVal == 32 || iVal == 24 || iVal == 16) { + saveOptions.bps = iVal; + } else { + invalidParamHandler(bitOpt); } - } else if (string("-a") == argv[i]) { - useGUI = false; - } else if (string("--batch") == argv[i]) { - useGUI = false; - } else if (string("-B") == argv[i]) { - useGUI = false; - } else if (string("--help") == argv[i]) { - return false; - } else if (argv[i][0] != '-') { - numFiles++; + } else { + invalidParamTypeHandler(bitOpt); } } - return useGUI || numFiles == 0; -} - -int Launcher::run() { -#ifndef NO_GUI - bool useGUI = checkGUI(); +#ifdef NO_GUI + useGui = false; #else - bool useGUI = false; - help = checkGUI(); + if (parser.isSet(outputOpt) || parser.isSet(batchOpt)) { + useGui = false; + } #endif - QApplication app(argc, argv, useGUI); + if (!useGui && generalOptions.fileNames.isEmpty()) { + parser.showHelp(EXIT_FAILURE); + } +} + +int Launcher::run() { + QApplication app(argc, argv); // Settings QCoreApplication::setOrganizationName("J.Celaya"); @@ -357,10 +416,7 @@ int Launcher::run() { parseCommandLine(); Log::debug("Using LibRaw ", libraw_version()); - if (help) { - showHelp(); - return 0; - } else if (useGUI) { + if (useGui) { return startGUI(); } else { return automaticMerge(); diff --git a/src/Launcher.hpp b/src/Launcher.hpp index 093643b..924a7cf 100644 --- a/src/Launcher.hpp +++ b/src/Launcher.hpp @@ -48,7 +48,7 @@ class Launcher { char ** argv; LoadOptions generalOptions; SaveOptions saveOptions; - bool help; + bool useGui; }; } // namespace hdrmerge diff --git a/src/LoadSaveOptions.hpp b/src/LoadSaveOptions.hpp index 7d6fce0..f14cd02 100644 --- a/src/LoadSaveOptions.hpp +++ b/src/LoadSaveOptions.hpp @@ -25,11 +25,12 @@ #include #include +#include namespace hdrmerge { struct LoadOptions { - std::vector fileNames; + QStringList fileNames; bool align; bool crop; bool useCustomWl; diff --git a/src/MainWindow.hpp b/src/MainWindow.hpp index 2329f4e..97079af 100644 --- a/src/MainWindow.hpp +++ b/src/MainWindow.hpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -47,7 +48,7 @@ class MainWindow : public QMainWindow { MainWindow(); void closeEvent(QCloseEvent * event); - void preload(const std::vector & o) { + void preload(QStringList & o) { preloadFiles = o; } @@ -110,7 +111,7 @@ private slots: QLabel * statusLabel; ImageIO io; - std::vector preloadFiles; + QStringList preloadFiles; }; } // namespace hdrmerge From 3ee7c03106297fcb614e9c552b794b1f4ee9e4ff Mon Sep 17 00:00:00 2001 From: Franz Trischberger Date: Mon, 7 Oct 2019 09:16:16 +0300 Subject: [PATCH 3/3] CmdLineArgs refactoring: batch -> grouping Grouping can be done with all: behaviour when "--batch" was not set but only "-o" auto: old "--batch" behaviour manual: specify the size of each group/bracketed set of images --- src/Launcher.cpp | 264 ++++++++++++++++++++++------------------ src/Launcher.hpp | 8 +- src/LoadSaveOptions.hpp | 10 +- src/MainWindow.hpp | 2 +- 4 files changed, 159 insertions(+), 125 deletions(-) diff --git a/src/Launcher.cpp b/src/Launcher.cpp index f833301..2d944af 100644 --- a/src/Launcher.cpp +++ b/src/Launcher.cpp @@ -38,17 +38,15 @@ #include "Log.hpp" #include -using namespace std; - namespace hdrmerge { Launcher::Launcher(int argc, char * argv[]) : argc(argc), argv(argv), useGui(true) { - Log::setOutputStream(cout); + Log::setOutputStream(std::cout); saveOptions.previewSize = 2; } -int Launcher::startGUI() { +int Launcher::startGUI() const { #ifndef NO_GUI // Create main window MainWindow mw; @@ -66,35 +64,42 @@ int Launcher::startGUI() { struct CoutProgressIndicator : public ProgressIndicator { virtual void advance(int percent, const char * message, const char * arg) { if (arg) { - Log::progress('[', setw(3), percent, "%] ", QCoreApplication::translate("LoadSave", message).arg(arg)); + Log::progress('[', std::setw(3), percent, "%] ", QCoreApplication::translate("LoadSave", message).arg(arg)); } else { - Log::progress('[', setw(3), percent, "%] ", QCoreApplication::translate("LoadSave", message)); + Log::progress('[', std::setw(3), percent, "%] ", QCoreApplication::translate("LoadSave", message)); } } }; -list Launcher::getBracketedSets() { - list result; - if (generalOptions.imagesPerBracket > 0) { - if (generalOptions.fileNames.size() % generalOptions.imagesPerBracket != 0) { - cerr << QCoreApplication::translate("LoadSave", "Number of files not a multiple of number per bracketed set (-s). Aborting."); - exit(EXIT_FAILURE); - } +std::list Launcher::getBracketedSets() const { + std::list result; + if (generalOptions.grouping == LoadOptions::Grouping::MANUAL) { + LoadOptions globalOptions = generalOptions; + while(!globalOptions.fileNames.empty()) { + LoadOptions opt = globalOptions; + + // opt.fileNames and globalOptions.fileNames are equal + // *optIt == *globaloptIt + auto optIt = opt.fileNames.begin(); + auto globaloptIt = globalOptions.fileNames.begin(); + + // move both iterators by the number of images per bracket + std::advance(optIt, globalOptions.imagesPerBracket); + std::advance(globaloptIt, globalOptions.imagesPerBracket); - while(!generalOptions.fileNames.empty()) { - LoadOptions opt = generalOptions; - auto oIt = opt.fileNames.begin(); - auto goIt = generalOptions.fileNames.begin(); - std::advance(oIt, generalOptions.imagesPerBracket); - std::advance(goIt, generalOptions.imagesPerBracket); - opt.fileNames.erase(oIt, opt.fileNames.end()); - generalOptions.fileNames.erase(generalOptions.fileNames.begin(), goIt); + // we want to keep only the first imagesPerBracket file names + // erase the tail + opt.fileNames.erase(optIt, opt.fileNames.end()); + + // the first imagesPerBracket file names found their new home in opt + // Erase them from globalOptions + globalOptions.fileNames.erase(globalOptions.fileNames.begin(), globaloptIt); result.push_back(opt); } } else { - list> dateNames; - for (QString & name : generalOptions.fileNames) { + std::list> dateNames; + for (const QString & name : generalOptions.fileNames) { ImageIO::QDateInterval interval = ImageIO::getImageCreationInterval(name); if (interval.start.isValid()) { dateNames.emplace_back(interval, name); @@ -128,10 +133,10 @@ list Launcher::getBracketedSets() { } -int Launcher::automaticMerge() { +int Launcher::automaticMerge() const { auto tr = [&] (const char * text) { return QCoreApplication::translate("LoadSave", text); }; - list optionsSet; - if (generalOptions.batch) { + std::list optionsSet; + if (generalOptions.grouping > LoadOptions::Grouping::ALL) { optionsSet = getBracketedSets(); } else { optionsSet.push_back(generalOptions); @@ -150,9 +155,9 @@ int Launcher::automaticMerge() { int format = result & 1; int i = result >> 1; if (format) { - cerr << tr("Error loading %1, it has a different format.").arg(options.fileNames[i]) << endl; + std::cerr << tr("Error loading %1, it has a different format.").arg(options.fileNames[i]) << std::endl; } else { - cerr << tr("Error loading %1, file not found.").arg(options.fileNames[i]) << endl; + std::cerr << tr("Error loading %1, file not found.").arg(options.fileNames[i]) << std::endl; } result = 1; continue; @@ -181,14 +186,14 @@ void Launcher::parseCommandLine() { parser.setApplicationDescription( tr("Merges raw_files into an HDR DNG raw image.\n") + #ifndef NO_GUI - tr("If neither -o nor --batch options are given, the GUI will be presented.\n") + + tr("If neither -o nor --group options are given, the GUI will be presented.\n") + #endif tr("If similar options are specified, only the last one prevails.\n") ); parser.addPositionalArgument("files", "The input raw files", "[raw_files...]"); - QCommandLineOption outputOpt("o", + const QCommandLineOption outputOption("o", tr("Sets as the output file name. " "If not set it falls back to '%1'.\n" "The following parameters are accepted, most useful in batch mode:").arg("%id[-1]/%iF[0]-%in[-1].dng") + @@ -197,169 +202,189 @@ void Launcher::parseCommandLine() { "last image, n = -2 is the previous to the last image, and so on.") + "\n- %iF[n]: " + tr("Replaced by the base file name of image n without the extension.") + "\n- %id[n]: " + tr("Replaced by the directory name of image n.") + - "\n- %in[n]: " + tr("Replaced by the numerical suffix of image n, if it exists." + "\n- %in[n]: " + tr("Replaced by the numerical suffix of image n, if it exists. " "For instance, in IMG_1234.CR2, the numerical suffix would be 1234.") + "\n- %%: " + tr("Replaced by a single %."), "file"); - parser.addOption(outputOpt); + parser.addOption(outputOption); - QCommandLineOption maskFileOpt("m", + const QCommandLineOption maskFileOption("m", tr("Saves the mask to as a PNG image.\n" "Besides the parameters accepted by -o, it also accepts:") + "\n- %of: " + tr("Replaced by the base file name of the output file.") + "\n- %od: " + tr("Replaced by the directory name of the output file."), "file"); - parser.addOption(maskFileOpt); + parser.addOption(maskFileOption); - QCommandLineOption logLevelOpt({"l", "loglevel"}, - tr("The level of logging output you want to recieve."), + const QCommandLineOption logLevelOption({"l", "loglevel"}, + tr("The level of logging output you want to receive."), "verbose|debug"); - parser.addOption(logLevelOpt); + parser.addOption(logLevelOption); - QCommandLineOption noAlignOpt("no-align", + const QCommandLineOption noAlignOption("no-align", tr("Do not auto-align source images.")); - parser.addOption(noAlignOpt); + parser.addOption(noAlignOption); - QCommandLineOption noCropOpt("no-crop", + const QCommandLineOption noCropOption("no-crop", tr("Do not crop the output image to the optimum size.")); - parser.addOption(noCropOpt); - - QCommandLineOption batchOpt({"B","batch"}, - tr("Batch mode: Input images are automatically grouped into bracketed sets, " - "by comparing the creation time.") - ); - parser.addOption(batchOpt); + parser.addOption(noCropOption); + + const QCommandLineOption groupOption({"G", "group"}, + tr("Determines how the input images are grouped.") + + "\n- all, a: " + tr("Process all input images as a single group. Default.") + + "\n- auto, t: " + tr("Group images automatically based on their time of creation (Exif DateTimeOriginal).") + + "\n- manual, m: " + tr("Group images manually based on a number of images per group."), + "all|auto|manual", + "all"); + parser.addOption(groupOption); + + const QCommandLineOption bracketSizeOption({"s", "bracket-size"}, + tr("Fixed number of images per bracket set. " + "Only used when grouping manually. " + "Creation time and --single option will be ignored. " + "The total number of images passed to HDRMerge must be a multiple of this number.") + + " Default: 3", + "integer", QString::number(3)); + parser.addOption(bracketSizeOption); - QCommandLineOption bracketSizeOpt({"s", "bracket-size"}, - tr("Fixed number of images per bracket set. Use together with -B. " - "Creation time and --single option will be ignored."), - "integer", QString::number(-1)); - parser.addOption(bracketSizeOpt); + const QCommandLineOption gapOption({"g", "gap"}, + tr("Maximum gap in seconds between chronologically adjacent images. " + "If the gap is larger, a new group is created. " + "Only used when grouping automatically or all.") + + " Default: 3.0", + "double", QString::number(3.0)); + parser.addOption(gapOption); - QCommandLineOption singleOpt("single", - tr("Include single images in batch mode (the default is to skip them.)")); - parser.addOption(singleOpt); + const QCommandLineOption singleOption("single", + tr("Include single images when specified. Only used when grouping automatically. " + "Off by default.")); + parser.addOption(singleOption); - QCommandLineOption helpOpt = parser.addHelpOption(); + const QCommandLineOption helpOption = parser.addHelpOption(); - QCommandLineOption bitOpt("b", + const QCommandLineOption bitOption("b", tr("Bits per sample."), "16|24|32", QString::number(16)); - parser.addOption(bitOpt); + parser.addOption(bitOption); - QCommandLineOption whiteLevelOpt("w", + const QCommandLineOption whiteLevelOption("w", tr("Use custom white level."), "integer", "16383"); - parser.addOption(whiteLevelOpt); - - QCommandLineOption gapOpt("g", - tr("Batch gap, maximum difference in seconds between two images of the same set."), - "double", QString::number(2.0)); - parser.addOption(gapOpt); + parser.addOption(whiteLevelOption); - QCommandLineOption featherRadiusOpt("r", + const QCommandLineOption featherRadiusOption("r", tr("Mask blur radius, to soften transitions between images. Default is 3 pixels."), "integer", QString::number(3)); - parser.addOption(featherRadiusOpt); + parser.addOption(featherRadiusOption); - QCommandLineOption previewSizeOpt("p", + const QCommandLineOption previewSizeOption("p", tr("Size of the preview. Default is none."), "full|half|none", "none"); - parser.addOption(previewSizeOpt); + parser.addOption(previewSizeOption); parser.process(QCoreApplication::arguments()); bool ok; // to check conversions from QString - char const * errorStart = ">>> ERROR >>> ERROR >>> ERROR >>>\n"; - char const * errorStop = "\n<<< ERROR <<< ERROR <<< ERROR <<<"; + const char* const errorHint = "There were errors in your commandline options:"; /** * Use this when the user passes an invalid parameter, like "-b=17" (b:16;24;32) */ - auto invalidParamHandler = [&] (const QCommandLineOption& opt) { - cerr << errorStart << + const auto invalidParameterHandler = + [&tr, &parser, errorHint] (const QCommandLineOption& opt) { + std::cerr << parser.helpText() << '\n' << + tr(errorHint) << '\n' << tr("Invalid parameter '%1' for option '-%2'. " "Please read the help for valid parameters.") .arg(parser.value(opt)) - .arg(opt.names().at(0)) - << errorStop << endl; + .arg(opt.names().at(0)) << std::endl; // exit right away, creating HDR takes a lot of time // and just falling back to default is probably not what the user wants... - parser.showHelp(EXIT_FAILURE); + exit(EXIT_FAILURE); }; /** * Use this when the user passes an invalid type, like "-b=sixteen" (b:integer) */ - auto invalidParamTypeHandler = [&] (const QCommandLineOption& opt) { - cerr << errorStart << + const auto invalidParameterTypeHandler = + [&tr, &parser, errorHint] (const QCommandLineOption& opt) { + std::cerr << parser.helpText() << '\n' << + tr(errorHint) << '\n' << tr("Invalid parameter type for option '-%1'. " "Please read the help for the valid type.") - .arg(opt.names().at(0)) - << errorStop << endl; - parser.showHelp(EXIT_FAILURE); + .arg(opt.names().at(0)) << std::endl; + exit(EXIT_FAILURE); }; generalOptions.fileNames = parser.positionalArguments(); - saveOptions.fileName = parser.value(outputOpt); - if (parser.isSet(maskFileOpt)) { + saveOptions.fileName = parser.value(outputOption); + if (parser.isSet(maskFileOption)) { saveOptions.saveMask = true; - saveOptions.maskFileName = parser.value(outputOpt); + saveOptions.maskFileName = parser.value(outputOption); } - if (parser.isSet(logLevelOpt)) { - QString value = parser.value(logLevelOpt); + if (parser.isSet(logLevelOption)) { + const QString value = parser.value(logLevelOption); if (value == "verbose" || value == "v") { Log::setMinimumPriority(1); } else if (value == "debug" || value == "d") { Log::setMinimumPriority(0); } else { - invalidParamHandler(logLevelOpt); + invalidParameterHandler(logLevelOption); + } + } + generalOptions.align = !parser.isSet(noAlignOption); + generalOptions.crop = !parser.isSet(noCropOption); + + if (parser.isSet(groupOption) || parser.isSet(outputOption)) { + const QString value = parser.value(groupOption); + if (value == "all" || value == "a") { + generalOptions.grouping = LoadOptions::Grouping::ALL; + } else if (value == "auto" || value == "t") { + generalOptions.grouping = LoadOptions::Grouping::AUTO; + } else if (value == "manual" || value == "m") { + generalOptions.grouping = LoadOptions::Grouping::MANUAL; + } else { + invalidParameterHandler(groupOption); } } - generalOptions.align = !parser.isSet(noAlignOpt); - generalOptions.crop = !parser.isSet(noCropOpt); - generalOptions.batch = parser.isSet(batchOpt); + { - QString value = parser.value(bracketSizeOpt); - int iVal = value.toInt(&ok); + const int value = parser.value(bracketSizeOption).toInt(&ok); if (ok) { - generalOptions.imagesPerBracket = iVal; + generalOptions.imagesPerBracket = value; } else { - invalidParamTypeHandler(bracketSizeOpt); + invalidParameterTypeHandler(bracketSizeOption); } } - generalOptions.withSingles = parser.isSet(singleOpt); + generalOptions.withSingles = parser.isSet(singleOption); { - QString value = parser.value(gapOpt); - double dVal = value.toDouble(&ok); + const double value = parser.value(gapOption).toDouble(&ok); if (ok) { - generalOptions.batchGap = dVal; + generalOptions.batchGap = value; } else { - invalidParamTypeHandler(gapOpt); + invalidParameterTypeHandler(gapOption); } } { - QString value = parser.value(featherRadiusOpt); - int iVal = value.toInt(&ok); + const int value = parser.value(featherRadiusOption).toInt(&ok); if (ok) { - saveOptions.featherRadius = iVal; + saveOptions.featherRadius = value; } else { - invalidParamTypeHandler(featherRadiusOpt); + invalidParameterTypeHandler(featherRadiusOption); } } { - generalOptions.useCustomWl = parser.isSet(whiteLevelOpt); - QString value = parser.value(whiteLevelOpt); - uint iVal = value.toUInt(&ok); + generalOptions.useCustomWl = parser.isSet(whiteLevelOption); + const uint value = parser.value(whiteLevelOption).toUInt(&ok); if (ok) { - generalOptions.customWl = iVal; + generalOptions.customWl = value; } else { - invalidParamTypeHandler(whiteLevelOpt); + invalidParameterTypeHandler(whiteLevelOption); } } { - QString previewSize = parser.value(previewSizeOpt); + const QString previewSize = parser.value(previewSizeOption); if (previewSize == "none" || previewSize == "n") { saveOptions.previewSize = 0; } else if (previewSize == "half" || previewSize =="h") { @@ -367,27 +392,32 @@ void Launcher::parseCommandLine() { } else if (previewSize == "full" || previewSize == "f") { saveOptions.previewSize = 2; } else { - invalidParamHandler(previewSizeOpt); + invalidParameterHandler(previewSizeOption); } } { - QString value = parser.value(bitOpt); - int iVal = value.toInt(&ok); + const int value = parser.value(bitOption).toInt(&ok); if (ok) { - if (iVal == 32 || iVal == 24 || iVal == 16) { - saveOptions.bps = iVal; + if (value == 32 || value == 24 || value == 16) { + saveOptions.bps = value; } else { - invalidParamHandler(bitOpt); + invalidParameterHandler(bitOption); } } else { - invalidParamTypeHandler(bitOpt); + invalidParameterTypeHandler(bitOption); } } + if (generalOptions.grouping == LoadOptions::Grouping::MANUAL && + generalOptions.fileNames.size() % generalOptions.imagesPerBracket != 0) { + std::cerr << tr("Number of files not a multiple of number per bracketed set (-s). Aborting."); + exit(EXIT_FAILURE); + } + #ifdef NO_GUI useGui = false; #else - if (parser.isSet(outputOpt) || parser.isSet(batchOpt)) { + if (generalOptions.grouping > LoadOptions::Grouping::UNSET) { useGui = false; } #endif diff --git a/src/Launcher.hpp b/src/Launcher.hpp index 924a7cf..7017ecc 100644 --- a/src/Launcher.hpp +++ b/src/Launcher.hpp @@ -38,11 +38,9 @@ class Launcher { int run(); private: - bool checkGUI(); - int startGUI(); - int automaticMerge(); - void showHelp(); - std::list getBracketedSets(); + int startGUI() const; + int automaticMerge() const; + std::list getBracketedSets() const; int argc; char ** argv; diff --git a/src/LoadSaveOptions.hpp b/src/LoadSaveOptions.hpp index f14cd02..0cd8754 100644 --- a/src/LoadSaveOptions.hpp +++ b/src/LoadSaveOptions.hpp @@ -35,11 +35,17 @@ struct LoadOptions { bool crop; bool useCustomWl; uint16_t customWl; - bool batch; + enum class Grouping { + UNSET, + ALL, + AUTO, + MANUAL + }; + Grouping grouping; int imagesPerBracket; double batchGap; bool withSingles; - LoadOptions() : align(true), crop(true), useCustomWl(false), customWl(16383), batch(false), imagesPerBracket(-1), batchGap(2.0), + LoadOptions() : align(true), crop(true), useCustomWl(false), customWl(16383), grouping(Grouping::UNSET), imagesPerBracket(3), batchGap(3.0), withSingles(false) {} }; diff --git a/src/MainWindow.hpp b/src/MainWindow.hpp index 97079af..1efede8 100644 --- a/src/MainWindow.hpp +++ b/src/MainWindow.hpp @@ -48,7 +48,7 @@ class MainWindow : public QMainWindow { MainWindow(); void closeEvent(QCloseEvent * event); - void preload(QStringList & o) { + void preload(const QStringList & o) { preloadFiles = o; }