1 /*
2  * Copyright (C) 2004-2009 Andrew Mihal
3  * Copyright (C) 2009-2016 Christoph Spiel
4  *
5  * This file is part of Enblend.
6  *
7  * Enblend is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * Enblend is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Enblend; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 
25 #ifndef ENFUSE_SOURCE
26 #error trying to compile enfuse.cc without having defined ENFUSE_SOURCE
27 #endif
28 #ifdef ENBLEND_SOURCE
29 #error must not define ENBLEND_SOURCE when compiling enfuse.cc
30 #endif
31 
32 #ifdef _WIN32
33 // Make sure we bring in windows.h the right way
34 #define _STLP_VERBOSE_AUTO_LINK
35 #define _USE_MATH_DEFINES
36 #define NOMINMAX
37 #define VC_EXTRALEAN
38 #include <windows.h>
39 #undef DIFFERENCE
40 #endif  // _WIN32
41 
42 #ifdef __GW32C__
43 #undef malloc
44 #define BOOST_NO_STDC_NAMESPACE 1
45 #endif
46 
47 #include <algorithm>
48 #include <iostream>
49 #include <list>
50 #include <memory>               // std::unique_ptr
51 #include <set>
52 #include <vector>
53 
54 #include <getopt.h>
55 extern "C" char *optarg;
56 extern "C" int optind;
57 
58 #ifndef _MSC_VER
59 #include <fenv.h>
60 #endif
61 
62 #include <signal.h>
63 #include <stdlib.h>
64 #include <string.h>
65 #include <tiffconf.h>
66 
67 #ifdef _WIN32
68 #include <io.h>
69 #endif
70 
71 #include <boost/tokenizer.hpp>
72 
73 #include <gsl/gsl_version.h>    // GSL_VERSION
74 
75 #include <lcms2.h>
76 #if !defined(LCMS_VERSION) || LCMS_VERSION < 2050
77 #error "Little CMS version 2.5 or later is required"
78 #endif
79 
80 #include "alternativepercentage.h"
81 #include "dynamic_loader.h"
82 #include "exposure_weight.h"
83 #include "global.h"
84 #include "layer_selection.h"
85 #include "parameter.h"
86 #include "selector.h"
87 #include "self_test.h"
88 #include "signature.h"
89 #include "tiff_message.h"
90 #ifdef _MSC_VER
91 #include "win32helpers/delayHelper.h"
92 #endif
93 
94 // Globals
95 const std::string command("enfuse");
96 
97 // Global values from command line parameters.
98 std::string OutputFileName(DEFAULT_OUTPUT_FILENAME);
99 int Verbose = 1;                //< default-verbosity-level 1
100 int ExactLevels = 0;            // 0 means: automatically calculate maximum
101 bool OneAtATime = true;
102 boundary_t WrapAround = OpenBoundaries;
103 bool GimpAssociatedAlphaHack = false;
104 blend_colorspace_t BlendColorspace = UndeterminedColorspace;
105 bool OutputSizeGiven = false;
106 int OutputWidthCmdLine = 0;
107 int OutputHeightCmdLine = 0;
108 int OutputOffsetXCmdLine = 0;
109 int OutputOffsetYCmdLine = 0;
110 std::string OutputCompression;
111 std::string OutputPixelType;
112 double WExposure = 1.0;         //< default-weight-exposure 1.0
113 double ExposureOptimum = 0.5;   //< default-exposure-optimum 0.5
114 double ExposureWidth = 0.2;     //< default-exposure-width 0.2
115 std::string ExposureWeightFunctionName("gaussian"); //< exposure-weight-function gaussian
116 ExposureWeight* ExposureWeightFunction = new exposure_weight::Gaussian(ExposureOptimum, ExposureWidth);
117 ExposureWeight::argument_list_t ExposureWeightFunctionArguments;
118 AlternativePercentage ExposureLowerCutoff(0.0, true); //< default-exposure-lower-cutoff 0%
119 CompactifiedAlternativePercentage ExposureUpperCutoff(100.0, true); //< default-exposure-upper-cutoff 100%
120 std::string ExposureLowerCutoffGrayscaleProjector("anti-value"); //< default-exposure-lower-cutoff-projector anti-value
121 std::string ExposureUpperCutoffGrayscaleProjector("value"); //< default-exposure-upper-cutoff-projector value
122 double WContrast = 0.0;         //< default-weight-contrast 0.0
123 double WSaturation = 0.2;       //< default-weight-saturation 0.2
124 double WEntropy = 0.0;          //< default-weight-entropy 0.0
125 bool WSaturationIsDefault = true;
126 int ContrastWindowSize = 5;     //< default-contrast-window-size 5
127 std::string GrayscaleProjector;
128 struct EdgeFilterConfiguration {double edgeScale, lceScale, lceFactor;} FilterConfig = {
129     0.0,                        //< default-edge-scale 0.0
130     0.0,                        //< default-lce-scale 0.0
131     0.0                         //< default-lce-factor 0.0
132 };
133 AlternativePercentage MinCurvature(0.0, false); //< default-minimum-curvature 0
134 int EntropyWindowSize = 3;      //< default-entropy-window-size 3
135 AlternativePercentage EntropyLowerCutoff(0.0, true); //< default-entropy-lower-cutoff 0%
136 CompactifiedAlternativePercentage EntropyUpperCutoff(100.0, true); //< default-entropy-upper-cutoff 100%
137 bool UseHardMask = false;
138 bool SaveMasks = false;
139 bool StopAfterMaskGeneration = false;
140 bool LoadMasks = false;
141 std::string SoftMaskTemplate("softmask-%n.tif"); //< default-soft-mask-template softmask-%n.tif
142 std::string HardMaskTemplate("hardmask-%n.tif"); //< default-hard-mask-template hardmask-%n.tif
143 
144 TiffResolution ImageResolution;
145 bool OutputIsValid = true;
146 
147 bool UseGPU = false;
148 namespace cl {class Context;}
149 cl::Context* GPUContext = nullptr;
150 namespace ocl {class BatchBuilder;}
151 ocl::BatchBuilder* BatchCompiler = nullptr;
152 
153 // Globals related to catching SIGINT
154 #ifndef _WIN32
155 sigset_t SigintMask;
156 #endif
157 
158 // Objects for ICC profiles
159 cmsHPROFILE InputProfile = nullptr;
160 cmsHPROFILE XYZProfile = nullptr;
161 cmsHPROFILE LabProfile = nullptr;
162 cmsHTRANSFORM InputToXYZTransform = nullptr;
163 cmsHTRANSFORM XYZToInputTransform = nullptr;
164 cmsHTRANSFORM InputToLabTransform = nullptr;
165 cmsHTRANSFORM LabToInputTransform = nullptr;
166 cmsViewingConditions ViewingConditions;
167 cmsHANDLE CIECAMTransform = nullptr;
168 cmsHPROFILE FallbackProfile = nullptr;
169 
170 Signature sig;
171 LayerSelectionHost LayerSelection;
172 
173 #include <vigra/imageinfo.hxx>
174 #include <vigra/impex.hxx>
175 #include <vigra/sized_int.hxx>
176 
177 #include <tiffio.h>
178 
179 #include "common.h"
180 #include "filespec.h"
181 #include "introspection.h"
182 #include "enfuse.h"
183 
184 #ifdef DMALLOC
185 #include "dmalloc.h"            // must be last #include
186 #endif
187 
188 #ifdef _WIN32
189 #define strdup _strdup
190 #endif
191 
192 
193 // Initialize data structures for precomputed entropy and logarithm.
194 template <typename InputPixelType, typename ResultPixelType>
195 size_t enblend::Histogram<InputPixelType, ResultPixelType>::precomputedSize = 0;
196 template <typename InputPixelType, typename ResultPixelType>
197 double* enblend::Histogram<InputPixelType, ResultPixelType>::precomputedLog = nullptr;
198 template <typename InputPixelType, typename ResultPixelType>
199 double* enblend::Histogram<InputPixelType, ResultPixelType>::precomputedEntropy = nullptr;
200 
201 
202 #define DUMP_GLOBAL_VARIABLES(...) dump_global_variables(__FILE__, __LINE__, ##__VA_ARGS__)
dump_global_variables(const char * file,unsigned line,std::ostream & out=std::cout)203 void dump_global_variables(const char* file, unsigned line,
204                            std::ostream& out = std::cout)
205 {
206     out <<
207         "+ " << file << ":" << line << ": state of global variables\n" <<
208         "+ Verbose = " << Verbose << ", option \"--verbose\"\n" <<
209         "+ OutputFileName = <" << OutputFileName << ">\n" <<
210         "+ ExactLevels = " << ExactLevels << "\n" <<
211         "+ UseGPU = " << UseGPU << "\n" <<
212         "+ OneAtATime = " << enblend::stringOfBool(OneAtATime) << ", option \"-a\"\n" <<
213         "+ WrapAround = " << enblend::stringOfWraparound(WrapAround) << ", option \"--wrap\"\n" <<
214         "+ GimpAssociatedAlphaHack = " << enblend::stringOfBool(GimpAssociatedAlphaHack) <<
215         ", option \"-g\"\n" <<
216         "+ BlendColorspace = " << BlendColorspace << ", option \"--blend-colorspace\"\n" <<
217         "+ FallbackProfile = " << (FallbackProfile ? enblend::profileDescription(FallbackProfile) : "[none]") <<
218         ", option \"--fallback-profile\"\n" <<
219         "+ OutputSizeGiven = " << enblend::stringOfBool(OutputSizeGiven) << ", option \"-f\"\n" <<
220         "+     OutputWidthCmdLine = " << OutputWidthCmdLine << ", argument to option \"-f\"\n" <<
221         "+     OutputHeightCmdLine = " << OutputHeightCmdLine << ", argument to option \"-f\"\n" <<
222         "+     OutputOffsetXCmdLine = " << OutputOffsetXCmdLine << ", argument to option \"-f\"\n" <<
223         "+     OutputOffsetYCmdLine = " << OutputOffsetYCmdLine << ", argument to option \"-f\"\n" <<
224         "+ WExposure = " << WExposure << ", argument to option \"--exposure-weight\"\n" <<
225         "+     ExposureOptimum = " << ExposureOptimum  << ", argument to option \"--exposure-optimum\"\n" <<
226         "+     ExposureWidth = " << ExposureWidth << ", argument to option \"--exposure-width\"\n" <<
227         "+ ExposureWeightFunctionName = " << ExposureWeightFunctionName << "\n" <<
228         "+ ExposureWeightFunctionArguments = {\n";
229     for (auto& a : ExposureWeightFunctionArguments) {out << "+    \"" << a << "\"\n";}
230     out <<
231         "+ }\n" <<
232         "+ ExposureLowerCutoff = {\n"
233         "+     value = " << ExposureLowerCutoff.value() << ",\n" <<
234         "+     is_percentage = " << enblend::stringOfBool(ExposureLowerCutoff.is_percentage()) << "\n" <<
235         "+ }, first argument to option \"--exposure-cutoff\"\n" <<
236         "+ ExposureUpperCutoff = {\n"
237         "+     value = " << ExposureUpperCutoff.value() << ",\n" <<
238         "+     is_percentage = " << enblend::stringOfBool(ExposureUpperCutoff.is_percentage()) << "\n" <<
239         "+ }, second argument to option \"--exposure-cutoff\"\n" <<
240         "+ ExposureLowerCutoffGrayscaleProjector = <" << ExposureLowerCutoffGrayscaleProjector <<
241         ">, third argument to option \"--exposure-cutoff\"\n" <<
242         "+ ExposureUpperCutoffGrayscaleProjector = <" << ExposureUpperCutoffGrayscaleProjector <<
243         ">, fourth argument to option \"--exposure-cutoff\"\n" <<
244         "+ WContrast = " << WContrast << ", argument to option \"--contrast-weight\"\n" <<
245         "+ WSaturation = " << WSaturation << ", argument to option \"--saturation-weight\"\n" <<
246         "+ WEntropy = " << WEntropy << ", argument to option \"--entropy-weight\"\n" <<
247         "+ WSaturationIsDefault = " << enblend::stringOfBool(WSaturationIsDefault) << "\n" <<
248         "+ ContrastWindowSize = " << ContrastWindowSize <<
249         ", argument to option \"--contrast-window-size\"\n" <<
250         "+ GrayscaleProjector = <" << GrayscaleProjector <<
251         ">, argument to option \"--gray-projector\"\n" <<
252         "+ FilterConfig = {\n" <<
253         "+     edgeScale = " << FilterConfig.edgeScale << ",\n" <<
254         "+     lceScale = " << FilterConfig.lceScale <<  ",\n" <<
255         "+     lceFactor = " << FilterConfig.lceFactor <<  "\n" <<
256         "+ }, arguments to option \"--contrast-edge-scale\"\n" <<
257         "+ MinCurvature = {\n"
258         "+     value = " << MinCurvature.value() << ",\n" <<
259         "+     is_percentage = " << enblend::stringOfBool(MinCurvature.is_percentage()) << "\n" <<
260         "+ }, arguments to option \"--contrast-min-curvature\"\n" <<
261         "+ EntropyWindowSize = " << EntropyWindowSize <<
262         ", argument to option \"--entropy-window-size\"\n" <<
263         "+ EntropyLowerCutoff = {\n"
264         "+     value = " << EntropyLowerCutoff.value() << ",\n" <<
265         "+     is_percentage = " << enblend::stringOfBool(EntropyLowerCutoff.is_percentage()) << "\n" <<
266         "+ }, first argument to option \"--entropy-cutoff\"\n" <<
267         "+ EntropyUpperCutoff = {\n"
268         "+     value = " << EntropyUpperCutoff.value() << ",\n" <<
269         "+     is_percentage = " << enblend::stringOfBool(EntropyUpperCutoff.is_percentage()) << "\n" <<
270         "+ }, second argument to option \"--entropy-cutoff\"\n" <<
271         "+ UseHardMask = " << enblend::stringOfBool(UseHardMask) <<
272         ", option \"--hard-mask\" or \"--soft-mask\"\n" <<
273         "+ SaveMasks = " << enblend::stringOfBool(SaveMasks) << ", option \"--save-masks\"\n" <<
274         "+     SoftMaskTemplate = <" << SoftMaskTemplate <<
275         ">, first argument to option \"--save-masks\"\n" <<
276         "+     HardMaskTemplate = <" << HardMaskTemplate <<
277         ">, second argument to option \"--save-masks\"\n" <<
278         "+ OutputCompression = <" << OutputCompression << ">, option \"--compression\"\n" <<
279         "+ OutputPixelType = <" << OutputPixelType << ">, option \"--depth\"\n" <<
280         "+ end of global variable dump\n";
281 }
282 
283 
284 void
printUsage(const bool error=true)285 printUsage(const bool error = true)
286 {
287     std::cout <<
288         "Usage: enfuse [options] [--output=IMAGE] INPUT...\n" <<
289         "Fuse INPUT images into a single IMAGE.\n" <<
290         "\n" <<
291         "INPUT... are image filenames or response filenames.  Response\n" <<
292         "filenames start with an \"" << RESPONSE_FILE_PREFIX_CHAR << "\" character.\n"
293         "\n" <<
294         "Options:\n" <<
295         "Common options:\n" <<
296         "  -l, --levels=LEVELS    limit number of blending LEVELS to use (1 to " << MAX_PYRAMID_LEVELS << ");\n" <<
297         "                         negative number of LEVELS decreases maximum;\n" <<
298         "                         \"auto\" restores the default automatic maximization\n" <<
299         "  -o, --output=FILE      write output to FILE; default: \"" << OutputFileName << "\"\n" <<
300         "  -v, --verbose[=LEVEL]  verbosely report progress; repeat to\n" <<
301         "                         increase verbosity or directly set to LEVEL\n" <<
302         "  --compression=COMPRESSION\n" <<
303         "                         set compression of output image to COMPRESSION,\n" <<
304         "                         where COMPRESSION is:\n" <<
305         "                         \"deflate\", \"jpeg\", \"lzw\", \"none\", \"packbits\", for TIFF files and\n" <<
306         "                         0 to 100, or \"jpeg\", \"jpeg-arith\" for JPEG files,\n" <<
307         "                         where \"jpeg\" and \"jpeg-arith\" accept a compression level\n" <<
308 #ifdef OPENCL
309         "  --gpu                  employ GPU in addition to CPU for selected computations; negate\n" <<
310         "                         with \"--no-gpu\"\n" <<
311 #endif
312         "\n" <<
313         "Advanced options:\n" <<
314         "  --blend-colorspace=COLORSPACE\n" <<
315         "                         force COLORSPACE for blending operations; Enfuse uses\n" <<
316         "                         \"CIELUV\" for images with ICC-profile and \"IDENTITY\" for\n" <<
317         "                         those without and also for all floating-point images;\n" <<
318         "                         other available blend color spaces are \"CIELAB\" and\n" <<
319         "                         \"CIECAM\"\n" <<
320         "  -c, --ciecam           use CIECAM02 to blend colors; disable with \"--no-ciecam\";\n" <<
321         "                         note that this option will be withdrawn in favor of\n" <<
322         "                         \"--blend-colorspace\"\n" <<
323         "  -d, --depth=DEPTH      set the number of bits per channel of the output\n" <<
324         "                         image, where DEPTH is \"8\", \"16\", \"32\", \"r32\", or \"r64\"\n" <<
325         "  -f WIDTHxHEIGHT[+xXOFFSET+yYOFFSET]\n" <<
326         "                         manually set the size and position of the output\n" <<
327         "                         image; useful for cropped and shifted input\n" <<
328         "                         TIFF images, such as those produced by Nona\n" <<
329         "  -g                     associated-alpha hack for Gimp (before version 2)\n" <<
330         "                         and Cinepaint\n" <<
331         "  -w, --wrap[=MODE]      wrap around image boundary, where MODE is \"none\",\n" <<
332         "                         \"horizontal\", \"vertical\", or \"both\"; default: " <<
333         enblend::stringOfWraparound(WrapAround) << ";\n" <<
334         "                         without argument the option selects horizontal wrapping\n" <<
335         "\n" <<
336         "Fusion options:\n" <<
337         "  --exposure-weight=WEIGHT\n" <<
338         "                         weight given to well-exposed pixels\n" <<
339         "                         (0 <= WEIGHT <= 1); default: " << WExposure << "\n" <<
340         "  --saturation-weight=WEIGHT\n" <<
341         "                         weight given to highly-saturated pixels\n" <<
342         "                         (0 <= WEIGHT <= 1); default: " << WSaturation << "\n" <<
343         "  --contrast-weight=WEIGHT\n" <<
344         "                         weight given to pixels in high-contrast neighborhoods\n" <<
345         "                         (0 <= WEIGHT <= 1); default: " << WContrast << "\n" <<
346         "  --entropy-weight=WEIGHT\n" <<
347         "                         weight given to pixels in high entropy neighborhoods\n" <<
348         "                         (0 <= WEIGHT <= 1); default: " << WEntropy << "\n" <<
349         "  --exposure-optimum=OPTIMUM\n" <<
350         "                         optimum exposure value, usually the maximum of the weighting\n" <<
351         "                         function (0 <= OPTIMUM <= 1); default: " << ExposureOptimum << "\n" <<
352         "  --exposure-width=WIDTH\n" <<
353         "                         characteristic width of the weighting function\n" <<
354         "                         (WIDTH > 0); default: " << ExposureWidth << "\n" <<
355         "  --soft-mask            average over all masks; this is the default\n" <<
356         "  --hard-mask            force hard blend masks and no averaging on finest\n" <<
357         "                         scale; this is especially useful for focus\n" <<
358         "                         stacks with thin and high contrast features,\n" <<
359         "                         but leads to increased noise\n" <<
360         "\n" <<
361         "Expert options:\n" <<
362         "  --save-masks[=SOFT-TEMPLATE[:HARD-TEMPLATE]]\n" <<
363         "                         save weight masks in SOFT-TEMPLATE and HARD-TEMPLATE;\n" <<
364         "                         conversion chars: \"%i\": mask index, \"%n\": mask number,\n" <<
365         "                         \"%p\": full path, \"%d\": dirname, \"%b\": basename,\n" <<
366         "                         \"%f\": filename, \"%e\": extension; lowercase characters\n" <<
367         "                         refer to input images uppercase to the output image\n" <<
368         "                         default: \"" << SoftMaskTemplate << "\":\"" << HardMaskTemplate << "\"\n" <<
369         "  --load-masks[=SOFT-TEMPLATE[:HARD-TEMPLATE]]\n" <<
370         "                         skip calculation of weight maps and use the ones\n" <<
371         "                         in the files matching the templates instead.  These\n" <<
372         "                         can be either hard or soft masks.  For template\n" <<
373         "                         syntax see \"--save-masks\";\n" <<
374         "                         default: \"" << SoftMaskTemplate << "\":\"" << HardMaskTemplate << "\"\n" <<
375         "  --fallback-profile=PROFILE-FILE\n" <<
376         "                         use the ICC profile from PROFILE-FILE instead of sRGB\n" <<
377         "  --layer-selector=ALGORITHM\n" <<
378         "                         set the layer selector ALGORITHM;\n" <<
379         "                         default: \"" << LayerSelection.name() << "\"; available algorithms are:\n";
380     for (selector::algorithm_list::const_iterator i = selector::algorithms.begin();
381          i != selector::algorithms.end();
382          ++i) {
383         std::cout << "                         \"" << (*i)->name() << "\": " << (*i)->description() << "\n";
384     }
385     std::cout <<
386 #ifdef OPENCL
387         "  --prefer-gpu=DEVICE    select DEVICE on auto-detected platform as GPU\n" <<
388         "  --prefer-gpu=PLATFORM:DEVICE\n" <<
389         "                         select DEVICE on PLATFORM as GPU\n" <<
390 #endif
391         "  --parameter=KEY1[=VALUE1][:KEY2[=VALUE2][:...]]\n" <<
392         "                         set one or more KEY-VALUE pairs\n" <<
393         "\n" <<
394         "Expert fusion options:\n" <<
395         "  --exposure-weight-function=WEIGHT-FUNCTION" <<
396 #ifdef HAVE_DYNAMICLOADER_IMPL
397         "    (1st form)" <<
398 #endif
399         "\n" <<
400         "                         select one of the built-in exposure WEIGHT-FUNCTIONs:\n" <<
401         "                         \"gaussian\", \"lorentzian\", \"half-sine\", \"full-sine\",\n" <<
402         "                         or \"bi-square\"; default: \"" << ExposureWeightFunctionName << "\"\n" <<
403 #ifdef HAVE_DYNAMICLOADER_IMPL
404         "  --exposure-weight-function=SHARED-OBJECT:SYMBOL[:ARGUMENT[:...]]    (2nd form)\n" <<
405         "                         load user-defined exposure weight function SYMBOL\n" <<
406         "                         from SHARED-OBJECT and optionally pass ARGUMENTs\n" <<
407 #endif
408         "  --exposure-cutoff=LOWERCUTOFF[:UPPERCUTOFF[:LOWERPROJECTOR[:UPPERPROJECTOR]]]\n" <<
409         "                         LOWERCUTOFF and UPPERCUTOFF are the values below\n" <<
410         "                         or above of which pixels are weighted with zero\n" <<
411         "                         weight in exposure weighting; append \"%\" signs\n" <<
412         "                         for relative values; default: " <<
413         ExposureLowerCutoff.str() << ":" << ExposureUpperCutoff.str() << ":" <<
414         ExposureLowerCutoffGrayscaleProjector << ":" << ExposureUpperCutoffGrayscaleProjector << "\n" <<
415         "  --contrast-window-size=SIZE\n" <<
416         "                         set window SIZE for local-contrast analysis\n" <<
417         "                         (SIZE >= 3); default: " << ContrastWindowSize  << "\n" <<
418         "  --contrast-edge-scale=EDGESCALE[:LCESCALE[:LCEFACTOR]]\n" <<
419         "                         set scale on which to look for edges; positive\n" <<
420         "                         LCESCALE switches on local contrast enhancement\n" <<
421         "                         by LCEFACTOR (EDGESCALE, LCESCALE, LCEFACTOR >= 0);\n" <<
422         "                         append \"%\" to LCESCALE for values relative to\n" <<
423         "                         EDGESCALE; append \"%\" to LCEFACTOR for relative\n" <<
424         "                         value; default: " <<
425         FilterConfig.edgeScale << ":" << FilterConfig.lceScale << ":" << FilterConfig.lceFactor << "\n" <<
426         "  --contrast-min-curvature=CURVATURE\n" <<
427         "                         minimum CURVATURE for an edge to qualify; append\n" <<
428         "                         \"%\" for relative values; default: " << MinCurvature.str() << "\n" <<
429         "  --gray-projector=PROJECTOR\n" <<
430         "                         apply gray-scale PROJECTOR in exposure or contrast\n" <<
431         "                         weighing, where PROJECTOR is one of \"anti-value\",\n" <<
432         "                         \"average\", \"l-star\", \"lightness\", \"luminance\",\n" <<
433         "                         \"pl-star\", \"value\", or\n" <<
434         "                         \"channel-mixer:RED-WEIGHT:GREEN-WEIGHT:BLUE-WEIGHT\";\n" <<
435         "                         default: \"" <<
436         enblend::MultiGrayscaleAccessor<vigra::UInt8, vigra::NumericTraits<vigra::UInt8>::Promote>::defaultGrayscaleAccessorName() << "\"\n" <<
437         "  --entropy-window-size=SIZE\n" <<
438         "                         set window SIZE for local entropy analysis\n" <<
439         "                         (SIZE >= 3); default: " << EntropyWindowSize  << "\n" <<
440         "  --entropy-cutoff=LOWERCUTOFF[:UPPERCUTOFF]\n" <<
441         "                         LOWERCUTOFF is the value below of which pixels are\n" <<
442         "                         treated as black and UPPERCUTOFF is the value above\n" <<
443         "                         of which pixels are treated as white in the entropy\n" <<
444         "                         weighting; append \"%\" signs for relative values;\n" <<
445         "                         default: " <<
446         EntropyLowerCutoff.str() << ":" << EntropyUpperCutoff.str() << "\n" <<
447         "\n" <<
448         "Information options:\n" <<
449         "  -h, --help             print this help message and exit\n" <<
450         "  -V, --version          output version information and exit\n" <<
451         "  --show-globbing-algorithms\n" <<
452         "                         show all globbing algorithms\n" <<
453 #ifdef OPENCL
454         "  --show-gpu-info        list all available GPUs according to their platform and device;\n" <<
455         "                         inform on current preferences\n" <<
456 #endif
457         "  --show-image-formats   show all recognized image formats and their filename\n" <<
458         "                         extensions\n" <<
459         "  --show-signature       show who compiled the binary when and on which machine\n" <<
460         "  --show-software-components\n" <<
461         "                         show the software components with which Enfuse was compiled\n" <<
462         "\n" <<
463         "Enfuse accepts arguments to any option in uppercase as\n" <<
464         "well as in lowercase letters.\n" <<
465 #if defined(CACHE_IMAGES) || \
466     defined(OPENMP) || \
467     (defined(OPENCL) && defined(PREFER_SEPARATE_OPENCL_SOURCE))
468         "\n" <<
469         "Environment:\n" <<
470 #endif
471 #ifdef CACHE_IMAGES
472         "  TMPDIR                 The TMPDIR environment variable points to the directory,\n" <<
473         "                         where to store ImageCache files.  If unset Enfuse uses \"/tmp\".\n" <<
474 #endif
475 #ifdef OPENMP
476         "  OMP_NUM_THREADS        The OMP_NUM_THREADS environment variable sets the number\n" <<
477         "                         of threads to use in OpenMP parallel regions.  If unset\n" <<
478         "                         Enfuse uses as many threads as there are CPUs.\n" <<
479         "  OMP_DYNAMIC            The OMP_DYNAMIC environment variable controls dynamic\n" <<
480         "                         adjustment of the number of threads to use in executing\n" <<
481         "                         OpenMP parallel regions.\n" <<
482 #endif
483 #if defined(OPENCL) && defined(PREFER_SEPARATE_OPENCL_SOURCE)
484         "  ENBLEND_OPENCL_PATH    The ENBLEND_OPENCL_PATH environment variable sets the search\n" <<
485         "                         path for OpenCL source files.  Note that the variable name is\n" <<
486         "                         ENBLEND_OPENCL_PATH for Enfuse, too." <<
487 #endif
488         "\n" <<
489         "Report bugs at <" PACKAGE_BUGREPORT ">." <<
490         std::endl;
491 
492     exit(error ? 1 : 0);
493 }
494 
495 
cleanup_output(void)496 void cleanup_output(void)
497 {
498     if (!OutputIsValid) {
499         std::cerr << command << ": info: remove invalid output image \"" << OutputFileName << "\"\n";
500         errno = 0;
501         if (unlink(OutputFileName.c_str()) != 0) {
502             std::cerr << command <<
503                 ": warning: could not remove invalid output image \"" << OutputFileName << "\": " <<
504                 enblend::errorMessage(errno) << "\n";
505         }
506     }
507 }
508 
509 
510 /** Make sure all cached file images get destroyed, and hence the
511  *  temporary files deleted, if we are killed.
512  */
sigint_handler(int sig)513 void sigint_handler(int sig)
514 {
515     std::cerr << std::endl << command << ": interrupted" << std::endl;
516 
517     cleanup_output();
518 
519 #if !defined(__GW32C__) && !defined(_WIN32)
520     struct sigaction action;
521     action.sa_handler = SIG_DFL;
522     action.sa_flags   = 0;
523     sigemptyset(&(action.sa_mask));
524     sigaction(sig, &action, nullptr);
525 #else
526     signal(sig, SIG_DFL);
527 #endif
528     raise(sig);
529 }
530 
531 
532 enum AllPossibleOptions {
533     VersionOption, HelpOption, LevelsOption, OutputOption, VerboseOption,
534     WrapAroundOption /* -w */, CompressionOption, LZWCompressionOption,
535     BlendColorspaceOption, CIECAM02Option, NoCIECAM02Option, FallbackProfileOption,
536     DepthOption, AssociatedAlphaOption /* -g */,
537     GPUOption, NoGPUOption, PreferredGPUOption,
538     SizeAndPositionOption /* -f */,
539     ExposureWeightOption, ExposureCutoffOption, SaturationWeightOption,
540     ContrastWeightOption, EntropyWeightOption,
541     ExposureOptimumOption, ExposureWidthOption,
542     ExposureWeightFunctionOption,
543     SoftMaskOption, HardMaskOption,
544     ContrastWindowSizeOption, GrayProjectorOption, EdgeScaleOption,
545     MinCurvatureOption, EntropyWindowSizeOption, EntropyCutoffOption,
546     DebugOption, SaveMasksOption, LoadMasksOption,
547     LayerSelectorOption,
548     ShowImageFormatsOption, ShowSignatureOption, ShowGlobbingAlgoInfoOption, ShowSoftwareComponentsInfoOption,
549     ShowGPUInfoOption,
550 };
551 
552 typedef std::set<enum AllPossibleOptions> OptionSetType;
553 
contains(const OptionSetType & optionSet,enum AllPossibleOptions anOption)554 bool contains(const OptionSetType& optionSet,
555               enum AllPossibleOptions anOption)
556 {
557     return optionSet.count(anOption) != 0;
558 }
559 
560 
561 /** Warn if options given at the command line have no effect. */
warn_of_ineffective_options(const OptionSetType & optionSet)562 void warn_of_ineffective_options(const OptionSetType& optionSet)
563 {
564     if (contains(optionSet, LoadMasksOption)) {
565         if (contains(optionSet, ExposureWeightOption)) {
566             std::cerr << command <<
567                 ": warning: option \"--exposure-weight\" has no effect with \"--load-masks\"" << std::endl;
568         }
569         if (contains(optionSet, ExposureCutoffOption)) {
570             std::cerr << command <<
571                 ": warning: option \"--exposure-cutoff\" has no effect with \"--load-masks\"" << std::endl;
572         }
573         if (contains(optionSet, SaturationWeightOption)) {
574             std::cerr << command <<
575                 ": warning: option \"--saturation-weight\" has no effect with \"--load-masks\"" << std::endl;
576         }
577         if (contains(optionSet, ContrastWeightOption)) {
578             std::cerr << command <<
579                 ": warning: option \"--contrast-weight\" has no effect with \"--load-masks\"" << std::endl;
580         }
581         if (contains(optionSet, EntropyWeightOption)) {
582             std::cerr << command <<
583                 ": warning: option \"--entropy-weight\" has no effect with \"--load-masks\"" << std::endl;
584         }
585         if (contains(optionSet, ExposureOptimumOption)) {
586             std::cerr << command <<
587                 ": warning: option \"--exposure-optimum\" has no effect with \"--load-masks\"" << std::endl;
588         }
589         if (contains(optionSet, ExposureWidthOption)) {
590             std::cerr << command <<
591                 ": warning: option \"--exposure-width\" has no effect with \"--load-masks\"" << std::endl;
592         }
593         if (contains(optionSet, ContrastWindowSizeOption)) {
594             std::cerr << command <<
595                 ": warning: option \"--contrast-window-size\" has no effect with \"--load-masks\"" << std::endl;
596         }
597         if (contains(optionSet, GrayProjectorOption)) {
598             std::cerr << command <<
599                 ": warning: option \"--gray-projector\" has no effect with \"--load-masks\"" << std::endl;
600         }
601         if (contains(optionSet, EdgeScaleOption)) {
602             std::cerr << command <<
603                 ": warning: option \"--contrast-edge-scale\" has no effect with \"--load-masks\"" << std::endl;
604         }
605         if (contains(optionSet, MinCurvatureOption)) {
606             std::cerr << command <<
607                 ": warning: option \"--contrast-min-curvature\" has no effect with \"--load-masks\"" << std::endl;
608         }
609         if (contains(optionSet, EntropyWindowSizeOption)) {
610             std::cerr << command <<
611                 ": warning: option \"--entropy-window-size\" has no effect with \"--load-masks\"" << std::endl;
612         }
613         if (contains(optionSet, EntropyCutoffOption)) {
614             std::cerr << command <<
615                 ": warning: option \"--entropy-cutoff\" has no effect with \"--load-masks\"" << std::endl;
616         }
617     }
618 
619     if (contains(optionSet, SaveMasksOption) && !contains(optionSet, OutputOption)) {
620         if (contains(optionSet, LevelsOption)) {
621             std::cerr << command <<
622                 ": warning: option \"--levels\" has no effect with \"--save-masks\" and no \"--output\"" <<
623                 std::endl;
624         }
625         if (contains(optionSet, WrapAroundOption)) {
626             std::cerr << command <<
627                 ": warning: option \"--wrap\" has no effect with \"--save-masks\" and no \"--output\"" <<
628                 std::endl;
629         }
630         if (contains(optionSet, CompressionOption)) {
631             std::cerr << command <<
632                 ": warning: option \"--compression\" has no effect with \"--save-masks\" and no \"--output\"" <<
633                 std::endl;
634         }
635         if (contains(optionSet, DepthOption)) {
636             std::cerr << command <<
637                 ": warning: option \"--depth\" has no effect with \"--save-masks\" and no \"--output\"" <<
638                 std::endl;
639         }
640         if (contains(optionSet, SizeAndPositionOption)) {
641             std::cerr << command <<
642                 ": warning: option \"-f\" has no effect with \"--save-masks\" and no \"--output\"" << std::endl;
643         }
644     }
645 
646     if (WExposure == 0.0 && contains(optionSet, ExposureWeightOption)) {
647         if (contains(optionSet, ExposureOptimumOption)) {
648             std::cerr << command <<
649                 ": warning: option \"--exposure-optimum\" has no effect as exposure weight\n" <<
650                 command <<
651                 ": warning: is zero" <<
652                 std::endl;
653         }
654         if (contains(optionSet, ExposureWidthOption)) {
655             std::cerr << command <<
656                 ": warning: option \"--exposure-width\" has no effect as exposure weight\n" <<
657                 command <<
658                 ": warning: is zero" <<
659                 std::endl;
660         }
661     }
662 
663     if (WExposure == 0.0 && contains(optionSet, ExposureCutoffOption)) {
664         std::cerr << command <<
665             ": warning: option \"--exposure-cutoff\" has no effect as exposure weight\n" <<
666             command <<
667             ": warning: is zero" <<
668             std::endl;
669     }
670 
671     if (WContrast == 0.0 && contains(optionSet, ContrastWindowSizeOption)) {
672         std::cerr << command <<
673             ": warning: option \"--contrast-window-size\" has no effect as contrast\n" <<
674             command <<
675             ": warning: weight is zero" <<
676             std::endl;
677     }
678 
679     if (WExposure == 0.0 && WContrast == 0.0 && contains(optionSet, GrayProjectorOption)) {
680         std::cerr << command <<
681             ": warning: option \"--gray-projector\" has no effect as exposure\n" <<
682             command <<
683             ": warning: and contrast weight both are zero" <<
684             std::endl;
685     }
686 
687     if (WContrast == 0.0) {
688         if (contains(optionSet, EdgeScaleOption)) {
689             std::cerr << command <<
690                 ": warning: option \"--contrast-edge-scale\" has no effect as contrast\n" <<
691                 command <<
692                 ": warning: weight is zero" <<
693                 std::endl;
694         }
695         if (contains(optionSet, MinCurvatureOption)) {
696             std::cerr << command <<
697                 ": warning: option \"--contrast-min-curvature\" has no effect as contrast\n" <<
698                 command <<
699                 ": warning: weight is zero" <<
700                 std::endl;
701         }
702     } else {
703         if (FilterConfig.edgeScale > 0.0 &&
704             contains(optionSet, ContrastWindowSizeOption) && MinCurvature.value() <= 0.0) {
705             std::cerr << command <<
706                 ": warning: option \"--contrast-window-size\" has no effect as\n" <<
707                 command <<
708                 ": warning: EDGESCALE in \"--contrast-edge-scale\" is positive and" <<
709                 command <<
710                 ": warning: \"--contrast-min-curvature\" is non-positive" <<
711                 std::endl;
712         }
713     }
714 
715     if (WEntropy == 0.0) {
716         if (contains(optionSet, EntropyWindowSizeOption)) {
717             std::cerr << command <<
718                 ": warning: option \"--entropy-window-size\" has no effect as\n" <<
719                 command <<
720                 ": warning: entropy weight is zero" <<
721                 std::endl;
722         }
723         if (contains(optionSet, EntropyCutoffOption)) {
724             std::cerr << command <<
725                 ": warning: option \"--entropy-cutoff\" has no effect as entropy\n" <<
726                 command <<
727                 ": warning: weight is zero" <<
728                 std::endl;
729         }
730     }
731 
732     if (contains(optionSet, CompressionOption) &&
733         !(enblend::getFileType(OutputFileName) == "TIFF" ||
734           enblend::getFileType(OutputFileName) == "JPEG")) {
735         std::cerr << command <<
736             ": warning: compression is not supported with output\n" <<
737             command <<
738             ": warning: file type \"" <<
739             enblend::getFileType(OutputFileName) << "\"" <<
740             std::endl;
741     }
742 
743     if (contains(optionSet, AssociatedAlphaOption) &&
744         enblend::getFileType(OutputFileName) != "TIFF") {
745         std::cerr << command <<
746             ": warning: option \"-g\" has no effect with output\n" <<
747             command <<
748             ": warning: file type \"" <<
749             enblend::getFileType(OutputFileName) << "\"" <<
750             std::endl;
751     }
752 
753 #ifdef OPENCL
754     if (!UseGPU && contains(optionSet, PreferredGPUOption)) {
755         std::cerr << command << ": warning: option \"--preferred-gpu\" has no effect without enabled GPU" << std::endl;
756     }
757 #else
758     if (contains(optionSet, GPUOption)) {
759         std::cerr << command << ": warning: option \"--gpu\" has no effect in this " << command << " binary,\n" <<
760             command << ": warning: because " << command << " was compiled without support for OpenCL" << std::endl;
761     }
762     if (contains(optionSet, NoGPUOption)) {
763         std::cerr << command << ": warning: option \"--no-gpu\" has no effect in this " << command << " binary,\n" <<
764             command << ": warning: because " << command << " was compiled without support for OpenCL" << std::endl;
765     }
766     if (contains(optionSet, PreferredGPUOption)) {
767         std::cerr << command << ": warning: option \"--prefer-gpu\" has no effect in this " << command << " binary,\n" <<
768             command << ": warning: because " << command << " was compiled without support for OpenCL" << std::endl;
769     }
770 #endif // OPENCL
771 }
772 
773 
774 void
fill_mask_templates(const char * an_option_argument,std::string & a_soft_mask_template,std::string & a_hard_mask_template,const std::string & an_option_name)775 fill_mask_templates(const char* an_option_argument,
776                     std::string& a_soft_mask_template, std::string& a_hard_mask_template,
777                     const std::string& an_option_name)
778 {
779     if (an_option_argument != nullptr && *an_option_argument != 0) {
780         boost::char_separator<char> separator(PATH_OPTION_DELIMITERS, "", boost::keep_empty_tokens);
781         const std::string arg(an_option_argument);
782         boost::tokenizer<boost::char_separator<char> > tokenizer(arg, separator);
783         boost::tokenizer<boost::char_separator<char> >::iterator token = tokenizer.begin();
784 
785         a_soft_mask_template = *token;
786         ++token;
787         if (token != tokenizer.end()) {
788             a_hard_mask_template = *token;
789         }
790 
791         ++token;
792         if (token != tokenizer.end()) {
793             std::cerr << command
794                       << ": warning: ignoring trailing garbage in \"" << an_option_name << "\"" << std::endl;
795         }
796     }
797 }
798 
799 
800 const struct option*
lookup_long_option_by_id(struct option * an_option_array,int a_long_option_id)801 lookup_long_option_by_id(struct option* an_option_array, int a_long_option_id)
802 {
803     assert(an_option_array != nullptr);
804 
805     struct option* opt = an_option_array;
806 
807     while (opt->name != 0) {
808         if (opt->val == a_long_option_id) {
809             return opt;
810         }
811         ++opt;
812     }
813 
814     return nullptr;
815 }
816 
817 
818 bool
short_option_requires_argument(const char * a_short_option_spec,char a_short_option)819 short_option_requires_argument(const char* a_short_option_spec, char a_short_option)
820 {
821     if (a_short_option != 0) {
822         const char* c = strchr(a_short_option_spec, a_short_option);
823 
824         if (c != nullptr) {
825             if (*(c + 1) == ':' && *(c + 2) != ':') {
826                 return true;
827             }
828         }
829     }
830 
831     return false;
832 }
833 
834 
835 #ifdef OPENCL
836 void
initialize_gpu_subsystem(size_t a_preferred_gpu_platform,size_t a_preferred_gpu_device)837 initialize_gpu_subsystem(size_t a_preferred_gpu_platform, size_t a_preferred_gpu_device)
838 {
839     static bool is_initialized = false;
840 
841     if (is_initialized) {
842         return;
843     }
844 
845     try {
846         cl::Platform platform = ocl::find_platform(a_preferred_gpu_platform);
847 
848         ocl::device_list_t devices;
849         ocl::prefer_device(platform, a_preferred_gpu_platform, a_preferred_gpu_device, devices);
850 
851         GPUContext = ocl::create_context(platform, devices);
852 
853         if (Verbose >= VERBOSE_OPENCL_MESSAGES) {
854             std::cerr << command << ": info: chose OpenCL platform #" << a_preferred_gpu_platform << ", ";
855 
856             std::string info;
857             platform.getInfo(CL_PLATFORM_VENDOR, &info);
858             std::cerr << info << ", ";
859             platform.getInfo(CL_PLATFORM_NAME, &info);
860             std::cerr << info << ", device #" << a_preferred_gpu_device << std::endl;
861         }
862 
863 #ifdef OPENMP
864         BatchCompiler = new ocl::ThreadedBatchBuilder;
865 #else
866         BatchCompiler = new ocl::SerialBatchBuilder;
867 #endif
868 
869         is_initialized = true;
870     } catch (ocl::runtime_error& an_exception) {
871         std::cerr <<
872             command << ": warning: " << an_exception.what() << ";\n" <<
873             command << ": warning: " << "    cannot enable GPU" << std::endl;
874     }
875 
876     if (!gpu_is_ok(GPUContext)) {
877         std::cerr <<
878             command << ": warning: at least one of Enfuse's GPU tests has failed\n" <<
879             command << ": note: refusing to activate GPU support" << std::endl;
880 
881         delete GPUContext;
882         delete BatchCompiler;
883         GPUContext = nullptr;
884         BatchCompiler = nullptr;
885     }
886 }
887 #endif // OPENCL
888 
889 
890 int
process_options(int argc,char ** argv)891 process_options(int argc, char** argv)
892 {
893     enum OptionId {
894         OPTION_ID_OFFSET = 1023,    // Ids start at 1024
895         UseGpuId,
896         NoUseGpuId,
897         PreferGpuId,
898         CompressionId,
899         WeightExposureId,
900         WeightContrastId,
901         WeightSaturationId,
902         ExposureOptimumId, ObsoleteExposureOptimumId,
903         ExposureWidthId, ObsoleteExposureWidthId,
904         ExposureWeightFunctionId,
905         MinCurvatureId,
906         EdgeScaleId,
907         ContrastWindowSizeId,
908         HardMaskId,
909         GrayProjectorId,
910         WeightEntropyId,
911         EntropyWindowSizeId,
912         EntropyCutoffId,
913         SoftMaskId,
914         VerboseId,
915         HelpId,
916         VersionId,
917         DepthId,
918         OutputId,
919         SaveMasksId,
920         WrapAroundId,
921         LevelsId,
922         BlendColorspaceId,
923         CiecamId,
924         NoCiecamId,
925         FallbackProfileId,
926         ExposureCutoffId,
927         LoadMasksId,
928         LayerSelectorId,
929         ParameterId,
930         NoParameterId,
931         ImageFormatsInfoId,
932         SignatureInfoId,
933         GlobbingAlgoInfoId,
934         SoftwareComponentsInfoId,
935         GPUInfoId
936     };
937 
938     static struct option long_options[] = {
939         {"gpu", no_argument, 0, UseGpuId},
940         {"no-gpu", no_argument, 0, NoUseGpuId},
941         {"prefer-gpu", required_argument, 0, PreferGpuId},
942         {"preferred-gpu", required_argument, 0, PreferGpuId}, // gramatically close alternative form
943         {"compression", required_argument, 0, CompressionId},
944         {"exposure-weight", required_argument, 0, WeightExposureId},
945         {"contrast-weight", required_argument, 0, WeightContrastId},
946         {"saturation-weight", required_argument, 0, WeightSaturationId},
947         {"exposure-mu", required_argument, 0, ObsoleteExposureOptimumId},
948         {"exposure-optimum", required_argument, 0, ExposureOptimumId},
949         {"exposure-sigma", required_argument, 0, ObsoleteExposureWidthId},
950         {"exposure-width", required_argument, 0, ExposureWidthId},
951         {"exposure-weight-function", required_argument, 0, ExposureWeightFunctionId},
952         {"contrast-min-curvature", required_argument, 0, MinCurvatureId},
953         {"contrast-edge-scale", required_argument, 0, EdgeScaleId},
954         {"contrast-window-size", required_argument, 0, ContrastWindowSizeId},
955         {"hard-mask", no_argument, 0, HardMaskId},
956         {"gray-projector", required_argument, 0, GrayProjectorId},
957         {"entropy-weight", required_argument, 0, WeightEntropyId},
958         {"entropy-window-size", required_argument, 0, EntropyWindowSizeId},
959         {"entropy-cutoff", required_argument, 0, EntropyCutoffId},
960         {"soft-mask", no_argument, 0, SoftMaskId},
961         {"verbose", optional_argument, 0, VerboseId},
962         {"help", no_argument, 0, HelpId},
963         {"version", no_argument, 0, VersionId},
964         {"depth", required_argument, 0, DepthId},
965         {"output", required_argument, 0, OutputId},
966         {"save-mask", optional_argument, 0, SaveMasksId}, // singular form: not documented, not deprecated
967         {"save-masks", optional_argument, 0, SaveMasksId},
968         {"wrap", optional_argument, 0, WrapAroundId},
969         {"blend-colorspace", required_argument, 0, BlendColorspaceId},
970         {"blend-color-space", required_argument, 0, BlendColorspaceId}, // dash form: not documented, not deprecated
971         {"levels", required_argument, 0, LevelsId},
972         {"ciecam", no_argument, 0, CiecamId},
973         {"no-ciecam", no_argument, 0, NoCiecamId},
974         {"fallback-profile", required_argument, 0, FallbackProfileId},
975         {"exposure-cutoff", required_argument, 0, ExposureCutoffId},
976         {"load-mask", optional_argument, 0, LoadMasksId}, // singular form: not documented, not deprecated
977         {"load-masks", optional_argument, 0, LoadMasksId},
978         {"layer-selector", required_argument, 0, LayerSelectorId},
979         {"parameter", required_argument, 0, ParameterId},
980         {"no-parameter", required_argument, 0, NoParameterId},
981         {"show-image-formats", no_argument, 0, ImageFormatsInfoId},
982         {"show-signature", no_argument, 0, SignatureInfoId},
983         {"show-globbing-algorithms", no_argument, 0, GlobbingAlgoInfoId},
984         {"show-software-components", no_argument, 0, SoftwareComponentsInfoId},
985         {"show-gpu-info", no_argument, 0, GPUInfoId},
986         {0, 0, 0, 0}
987     };
988 
989     bool failed = false;
990 
991     typedef enum {
992         PROCESS_COMPLETELY,
993         VERSION_ONLY, USAGE_ONLY,
994         IMAGE_FORMATS_ONLY, SIGNATURE_ONLY, GLOBBING_ALGOS_ONLY, SOFTWARE_COMPONENTS_ONLY,
995         GPU_INFO_COMPONENTS_ONLY
996     } print_only_task_id_t;
997     print_only_task_id_t print_only_task = PROCESS_COMPLETELY;
998 
999     OptionSetType optionSet;
1000 #ifdef OPENCL
1001     size_t preferredGPUPlatform = 0U; // We start enumerating platforms at 1 for user convenience.
1002                                       // Zero means we choose the first platform found, i.e. auto-detect.
1003     size_t preferredGPUDevice = 1U;   // We start enumerating platforms at 1 for user convenience.
1004 #endif
1005 
1006     static const char short_options[] = "Vb:cd:f:ghl:m:o:v::w::";
1007     opterr = 0;       // we have our own "unrecognized option" message
1008 
1009     while (true) {
1010         int option_index = -1;
1011         const int code = getopt_long(argc, argv, short_options,
1012                                      long_options, &option_index);
1013 
1014         if (code == -1) {
1015             break;
1016         }
1017 
1018         switch (code) {
1019         case UseGpuId:
1020             UseGPU = true;
1021             optionSet.insert(GPUOption);
1022             break;
1023 
1024         case NoUseGpuId:
1025             UseGPU = false;
1026             optionSet.insert(NoGPUOption);
1027             break;
1028 
1029         case PreferGpuId:
1030 #ifdef OPENCL
1031         {
1032             char* delimiter = strpbrk(optarg, NUMERIC_OPTION_DELIMITERS);
1033             if (delimiter == nullptr) {
1034                 preferredGPUDevice =
1035                     enblend::numberOfString(optarg, [](unsigned x) {return x >= 1U;},
1036                                             "preferred GPU device out of range", 1U);
1037             } else {
1038                 *delimiter = 0;
1039                 ++delimiter;
1040                 preferredGPUPlatform =
1041                     enblend::numberOfString(optarg, [](unsigned x) {return x >= 1U;},
1042                                             "preferred GPU platform out of range", 1U);
1043                 preferredGPUDevice =
1044                     enblend::numberOfString(delimiter, [](unsigned x) {return x >= 1U;},
1045                                             "preferred GPU device out of range", 1U);
1046             }
1047         }
1048 #endif
1049             optionSet.insert(PreferredGPUOption);
1050             break;
1051 
1052         case HardMaskId:
1053             UseHardMask = true;
1054             optionSet.insert(HardMaskOption);
1055             break;
1056 
1057         case SoftMaskId:
1058             UseHardMask = false;
1059             optionSet.insert(SoftMaskOption);
1060             break;
1061 
1062         case 'h': BOOST_FALLTHROUGH;
1063         case HelpId:
1064             print_only_task = USAGE_ONLY;
1065             optionSet.insert(HelpOption);
1066             break;
1067 
1068         case 'V': BOOST_FALLTHROUGH;
1069         case VersionId:
1070             print_only_task = VERSION_ONLY;
1071             optionSet.insert(VersionOption);
1072             break;
1073 
1074         case ImageFormatsInfoId:
1075             print_only_task = IMAGE_FORMATS_ONLY;
1076             optionSet.insert(ShowImageFormatsOption);
1077             break;
1078 
1079         case SignatureInfoId:
1080             print_only_task = SIGNATURE_ONLY;
1081             optionSet.insert(ShowSignatureOption);
1082             break;
1083 
1084         case GlobbingAlgoInfoId:
1085             print_only_task = GLOBBING_ALGOS_ONLY;
1086             optionSet.insert(ShowGlobbingAlgoInfoOption);
1087             break;
1088 
1089         case SoftwareComponentsInfoId:
1090             print_only_task = SOFTWARE_COMPONENTS_ONLY;
1091             optionSet.insert(ShowSoftwareComponentsInfoOption);
1092             break;
1093 
1094         case GPUInfoId:
1095             print_only_task = GPU_INFO_COMPONENTS_ONLY;
1096             optionSet.insert(ShowGPUInfoOption);
1097             break;
1098 
1099         case 'w': BOOST_FALLTHROUGH;
1100         case WrapAroundId:
1101             if (optarg != nullptr && *optarg != 0) {
1102                 WrapAround = enblend::wraparoundOfString(optarg);
1103                 if (WrapAround == UnknownWrapAround) {
1104                     std::cerr << command
1105                               << ": unrecognized wrap-around mode \"" << optarg << "\"\n" << std::endl;
1106                     failed = true;
1107                 }
1108             } else {
1109                 WrapAround = HorizontalStrip;
1110             }
1111             optionSet.insert(WrapAroundOption);
1112             break;
1113 
1114         case MinCurvatureId: {
1115             char *tail;
1116             errno = 0;
1117             MinCurvature.set_value(strtod(optarg, &tail));
1118             if (errno == 0) {
1119                 if (*tail == 0) {
1120                     MinCurvature.set_percentage(false);
1121                 } else if (strcmp(tail, "%") == 0) {
1122                     MinCurvature.set_percentage(true);
1123                 } else {
1124                     std::cerr << command << ": unrecognized minimum gradient \""
1125                               << optarg << "\" specification." << std::endl;
1126                     failed = true;
1127                 }
1128             } else {
1129                 std::cerr << command << ": illegal numeric format \""
1130                           << optarg << "\" for minimum gradient: "
1131                           << enblend::errorMessage(errno) << std::endl;
1132                 failed = true;
1133             }
1134             optionSet.insert(MinCurvatureOption);
1135             break;
1136         }
1137 
1138         case EdgeScaleId: {
1139             char* tail;
1140             boost::char_separator<char> separator(NUMERIC_OPTION_DELIMITERS, "", boost::keep_empty_tokens);
1141             const std::string arg(optarg);
1142             boost::tokenizer<boost::char_separator<char> > tokenizer(arg, separator);
1143             boost::tokenizer<boost::char_separator<char> >::iterator token = tokenizer.begin();
1144 
1145             if (token == tokenizer.end()) {
1146                 std::cerr << command << ": no scale given to \"--contrast-edge-scale\".  "
1147                           << "scale is required." << std::endl;
1148                 failed = true;
1149             } else {
1150                 errno = 0;
1151                 FilterConfig.edgeScale = strtod(token->c_str(), &tail);
1152                 if (errno == 0) {
1153                     if (*tail != 0) {
1154                         std::cerr << command << ": could not decode \"" << tail
1155                                   << "\" in edge scale specification \""
1156                                   << *token << "\" for edge scale." << std::endl;
1157                         failed = true;
1158                     }
1159                 } else {
1160                     std::cerr << command << ": illegal numeric format \""
1161                               << *token << "\" for edge scale: "
1162                               << enblend::errorMessage(errno) << std::endl;
1163                     failed = true;
1164                 }
1165 		++token;
1166             }
1167 
1168             if (token != tokenizer.end()) {
1169                 errno = 0;
1170                 FilterConfig.lceScale = strtod(token->c_str(), &tail);
1171                 if (errno == 0) {
1172                     if (strcmp(tail, "%") == 0) {
1173                         FilterConfig.lceScale *= FilterConfig.edgeScale / 100.0;
1174                     } else if (*tail != 0) {
1175                         std::cerr << command << ": could not decode \"" << tail
1176                                   << "\" in specification \"" << *token
1177                                   << "\" for LCE-scale." << std::endl;
1178                         failed = true;
1179                     }
1180                 } else {
1181                     std::cerr << command << ": illegal numeric format \""
1182                               << *token << "\" for LCE-Scale: "
1183                               << enblend::errorMessage(errno) << std::endl;
1184                     failed = true;
1185                 }
1186                 ++token;
1187             }
1188 
1189             if (token != tokenizer.end()) {
1190                 errno = 0;
1191                 FilterConfig.lceFactor = strtod(token->c_str(), &tail);
1192                 if (errno == 0) {
1193                     if (strcmp(tail, "%") == 0) {
1194                         FilterConfig.lceFactor /= 100.0;
1195                     } else if (*tail != 0) {
1196                         std::cerr << command << ": could not decode \"" << tail
1197                                   << "\" in specification \"" << *token
1198                                   << "\" for LCE-factor." << std::endl;
1199                         failed = true;
1200                     }
1201                 } else {
1202                     std::cerr << command << ": illegal numeric format \""
1203                               << *token << "\" for LCE-factor: "
1204                               << enblend::errorMessage(errno) << std::endl;
1205                     failed = true;
1206                 }
1207                 ++token;
1208             }
1209 
1210             if (token != tokenizer.end()) {
1211                 std::cerr << command << ": warning: ignoring trailing garbage \""
1212                           << *token << "\" in argument to \"--contrast-edge-scale\"" << std::endl;
1213             }
1214 
1215             optionSet.insert(EdgeScaleOption);
1216             break;
1217         }
1218 
1219         case EntropyCutoffId: {
1220             char* tail;
1221             boost::char_separator<char> separator(NUMERIC_OPTION_DELIMITERS, "", boost::keep_empty_tokens);
1222             const std::string arg(optarg);
1223             boost::tokenizer<boost::char_separator<char> > tokenizer(arg, separator);
1224             boost::tokenizer<boost::char_separator<char> >::iterator token = tokenizer.begin();
1225 
1226             if (token == tokenizer.end()) {
1227                 std::cerr << command << ": no scale given to \"--entropy-cutoff\".  "
1228                           << "lower cutoff is required." << std::endl;
1229                 failed = true;
1230             } else {
1231                 errno = 0;
1232                 EntropyLowerCutoff.set_value(strtod(token->c_str(), &tail));
1233                 if (errno == 0) {
1234                     if (*tail == 0) {
1235                         EntropyLowerCutoff.set_percentage(false);
1236                     } else if (strcmp(tail, "%") == 0) {
1237                         EntropyLowerCutoff.set_percentage(true);
1238                     } else {
1239                         std::cerr << command << ": unrecognized entropy's lower cutoff \""
1240                                   << tail << "\" in \"" << *token << "\"" << std::endl;
1241                         failed = true;
1242                     }
1243                 } else {
1244                     std::cerr << command << ": illegal numeric format \""
1245                               << *token << "\" of entropy's lower cutoff: "
1246                               << enblend::errorMessage(errno) << std::endl;
1247                     failed = true;
1248                 }
1249                 ++token;
1250             }
1251 
1252             if (token != tokenizer.end()) {
1253                 errno = 0;
1254                 EntropyUpperCutoff.set_value(strtod(token->c_str(), &tail));
1255                 if (errno == 0) {
1256                     if (*tail == 0) {
1257                         EntropyUpperCutoff.set_percentage(false);
1258                     } else if (strcmp(tail, "%") == 0) {
1259                         EntropyUpperCutoff.set_percentage(true);
1260                     } else {
1261                         std::cerr << command << ": unrecognized entropy's upper cutoff \""
1262                                   << tail << "\" in \"" << *token << "\"" << std::endl;
1263                         failed = true;
1264                     }
1265                 } else {
1266                     std::cerr << command << ": illegal numeric format \""
1267                               << *token << "\" of entropy's upper cutoff: "
1268                               << enblend::errorMessage(errno) << std::endl;
1269                     failed = true;
1270                 }
1271             }
1272 
1273             if (token != tokenizer.end()) {
1274                 std::cerr << command << ": warning: ignoring trailing garbage \""
1275                           << *token << "\" in argument to \"--entropy-cutoff\"" << std::endl;
1276             }
1277 
1278             optionSet.insert(EntropyCutoffOption);
1279             break;
1280         }
1281 
1282         case ExposureCutoffId: {
1283             char* tail;
1284             boost::char_separator<char> separator(NUMERIC_OPTION_DELIMITERS, "", boost::keep_empty_tokens);
1285             const std::string arg(optarg);
1286             boost::tokenizer<boost::char_separator<char> > tokenizer(arg, separator);
1287             boost::tokenizer<boost::char_separator<char> >::iterator token = tokenizer.begin();
1288 
1289             if (token == tokenizer.end()) {
1290                 std::cerr << command << ": no value given to \"--exposure-cutoff\";  "
1291                           << "lower cutoff is required." << std::endl;
1292                 failed = true;
1293             } else {
1294                 errno = 0;
1295                 ExposureLowerCutoff.set_value(strtod(token->c_str(), &tail));
1296                 if (errno == 0) {
1297                     if (*tail == 0) {
1298                         ExposureLowerCutoff.set_percentage(false);
1299                     } else if (strcmp(tail, "%") == 0) {
1300                         ExposureLowerCutoff.set_percentage(true);
1301                     } else {
1302                         std::cerr << command << ": unrecognized exposure's lower cutoff \""
1303                                   << tail << "\" in \"" << *token << "\"" << std::endl;
1304                         failed = true;
1305                     }
1306                 } else {
1307                     std::cerr << command << ": illegal numeric format \""
1308                               << *token << "\" of exposure's lower cutoff: "
1309                               << enblend::errorMessage(errno) << std::endl;
1310                     failed = true;
1311                 }
1312                 ++token;
1313             }
1314 
1315             if (token != tokenizer.end()) {
1316                 errno = 0;
1317                 ExposureUpperCutoff.set_value(strtod(token->c_str(), &tail));
1318                 if (errno == 0) {
1319                     if (*tail == 0) {
1320                         ExposureUpperCutoff.set_percentage(false);
1321                     } else if (strcmp(tail, "%") == 0) {
1322                         ExposureUpperCutoff.set_percentage(true);
1323                     } else {
1324                         std::cerr << command << ": unrecognized exposure's upper cutoff \""
1325                                   << tail << "\" in \"" << *token << "\"" << std::endl;
1326                         failed = true;
1327                     }
1328                 } else {
1329                     std::cerr << command << ": illegal numeric format \""
1330                               << *token << "\" of exposure's upper cutoff: "
1331                               << enblend::errorMessage(errno) << std::endl;
1332                     failed = true;
1333                 }
1334                 ++token;
1335             }
1336 
1337             if (token != tokenizer.end()) {
1338                 ExposureLowerCutoffGrayscaleProjector = *token;
1339                 ++token;
1340             }
1341 
1342             if (token != tokenizer.end()) {
1343                 ExposureUpperCutoffGrayscaleProjector = *token;
1344                 ++token;
1345             }
1346 
1347             if (token != tokenizer.end()) {
1348                 std::cerr << command << ": warning: ignoring trailing garbage \""
1349                           << *token << "\" in argument to \"--exposure-cutoff\"" << std::endl;
1350             }
1351 
1352             optionSet.insert(ExposureCutoffOption);
1353             break;
1354         }
1355 
1356         case CompressionId:
1357             if (optarg != nullptr && *optarg != 0) {
1358                 std::string upper_opt(optarg);
1359                 enblend::to_upper(upper_opt);
1360                 if (upper_opt == "NONE") {
1361                     ;           // stick with default
1362                 } else if (upper_opt == "DEFLATE" || upper_opt == "LZW" || upper_opt == "PACKBITS") {
1363                     OutputCompression = upper_opt;
1364                 } else if (upper_opt.find_first_not_of("0123456789") == std::string::npos) {
1365                     OutputCompression = "JPEG QUALITY=" + upper_opt;
1366                 } else if (enblend::starts_with(upper_opt, "JPEG") || enblend::starts_with(upper_opt, "JPEG-ARITH")) {
1367                     const std::string::size_type delimiter_position = upper_opt.find_first_of(NUMERIC_OPTION_DELIMITERS);
1368                     if (delimiter_position == std::string::npos) {
1369                         if (upper_opt == "JPEG" || upper_opt == "JPEG-ARITH") {
1370                             OutputCompression = upper_opt;
1371                         } else {
1372                             std::cerr << command << ": trailing garbage in JPEG compression \"" << optarg << "\"" << std::endl;
1373                             failed = true;
1374                         }
1375                     } else {
1376                         const std::string algorithm(upper_opt.substr(0, delimiter_position));
1377                         if (algorithm == "JPEG" || algorithm == "JPEG-ARITH") {
1378                             const std::string level(upper_opt.substr(delimiter_position + 1U));
1379                             if (level.length() >= 1U && level.find_first_not_of("0123456789") == std::string::npos) {
1380                                 upper_opt.replace(delimiter_position, 1U, " QUALITY=");
1381                                 OutputCompression = upper_opt;
1382                             } else {
1383                                 std::cerr << command << ": invalid JPEG compression level \"" << level << "\"" << std::endl;
1384                                 failed = true;
1385                             }
1386                         } else {
1387                             std::cerr << command << ": unrecognized JPEG compression \"" << optarg << "\"" << std::endl;
1388                             failed = true;
1389                         }
1390                     }
1391                 } else {
1392                     std::cerr << command << ": unrecognized compression \"" << optarg << "\"" << std::endl;
1393                     failed = true;
1394                 }
1395             } else {
1396                 std::cerr << command << ": option \"--compression\" requires an argument" << std::endl;
1397                 failed = true;
1398             }
1399             optionSet.insert(CompressionOption);
1400             break;
1401 
1402         case GrayProjectorId:
1403             if (optarg != nullptr && *optarg != 0) {
1404                 GrayscaleProjector = optarg;
1405             } else {
1406                 std::cerr << command << ": option \"--gray-projector\" requires an argument" << std::endl;
1407                 failed = true;
1408             }
1409             optionSet.insert(GrayProjectorOption);
1410             break;
1411 
1412         case 'd': BOOST_FALLTHROUGH;
1413         case DepthId:
1414             if (optarg != nullptr && *optarg != 0) {
1415                 OutputPixelType = enblend::outputPixelTypeOfString(optarg);
1416             } else {
1417                 std::cerr << command << ": options \"-d\" or \"--depth\" require arguments" << std::endl;
1418                 failed = true;
1419             }
1420             optionSet.insert(DepthOption);
1421             break;
1422 
1423         case 'o': BOOST_FALLTHROUGH;
1424         case OutputId:
1425             if (contains(optionSet, OutputOption)) {
1426                 std::cerr << command
1427                           << ": warning: more than one output file specified"
1428                           << std::endl;
1429             }
1430             if (optarg != nullptr && *optarg != 0) {
1431                 OutputFileName = optarg;
1432             } else {
1433                 std::cerr << command << ": options \"-o\" or \"--output\" require arguments" << std::endl;
1434                 failed = true;
1435             }
1436             optionSet.insert(OutputOption);
1437             break;
1438 
1439         case LoadMasksId:
1440             fill_mask_templates(optarg, SoftMaskTemplate, HardMaskTemplate, "--load-masks");
1441             LoadMasks = true;
1442             optionSet.insert(LoadMasksOption);
1443             break;
1444 
1445         case SaveMasksId:
1446             fill_mask_templates(optarg, SoftMaskTemplate, HardMaskTemplate, "--save-masks");
1447             SaveMasks = true;
1448             optionSet.insert(SaveMasksOption);
1449             break;
1450 
1451         case WeightExposureId:
1452             if (optarg != nullptr && *optarg != 0) {
1453                 WExposure = enblend::numberOfString(optarg,
1454                                                     [](double x) {return x >= 0.0;}, //< minimum-weight-exposure 0
1455                                                     "exposure weight less than 0; will use 0",
1456                                                     0.0,
1457                                                     [](double x) {return x <= 1.0;}, //< maximum-weight-exposure 1
1458                                                     "exposure weight greater than 1; will use 1",
1459                                                     1.0);
1460             } else {
1461                 std::cerr << command << ": option \"--exposure-weight\" requires an argument" << std::endl;
1462                 failed = true;
1463             }
1464             optionSet.insert(ExposureWeightOption);
1465             break;
1466 
1467         case WeightContrastId:
1468             if (optarg != nullptr && *optarg != 0) {
1469                 WContrast =
1470                     enblend::numberOfString(optarg,
1471                                             [](double x) {return x >= 0.0;}, //< minimum-weight-contrast 0
1472                                             "contrast weight less than 0; will use 0",
1473                                             0.0,
1474                                             [](double x) {return x <= 1.0;}, //< maximum-weight-contrast 1
1475                                             "contrast weight greater than 1; will use 1",
1476                                             0.0);
1477             } else {
1478                 std::cerr << command << ": option \"--contrast-weight\" requires an argument" << std::endl;
1479                 failed = true;
1480             }
1481             optionSet.insert(ContrastWeightOption);
1482             break;
1483 
1484         case WeightSaturationId:
1485             if (optarg != nullptr && *optarg != 0) {
1486                 WSaturation =
1487                     enblend::numberOfString(optarg,
1488                                             [](double x) {return x >= 0.0;}, //< minimum-weight-saturation 0
1489                                             "saturation weight less than 0; will use 0",
1490                                             0.0,
1491                                             [](double x) {return x <= 1.0;}, //< maximum-weight-saturation 1
1492                                             "saturation weight greater than 1; will use 1",
1493                                             1.0);
1494             } else {
1495                 std::cerr << command << ": option \"--saturation-weight\" requires an argument" << std::endl;
1496                 failed = true;
1497             }
1498             WSaturationIsDefault = false;
1499             optionSet.insert(SaturationWeightOption);
1500             break;
1501 
1502         case ObsoleteExposureOptimumId:
1503             std::cerr << command << ": info: option \"--exposure-mu\" is obsolete, prefer \"--exposure-optimum\"" << std::endl;
1504             BOOST_FALLTHROUGH;
1505         case ExposureOptimumId:
1506             if (optarg != nullptr && *optarg != 0) {
1507                 ExposureOptimum =
1508                     enblend::numberOfString(optarg,
1509                                             [](double x) {return x >= 0.0;}, //< minimum-exposure-optimum 0
1510                                             "exposure optimum value less than 0; will use 0",
1511                                             0.0,
1512                                             [](double x) {return x <= 1.0;}, //< maximum-exposure-optimum 1
1513                                             "exposure optimum value geater than 1; will use 1",
1514                                             1.0);
1515             } else {
1516                 std::cerr << command << ": option \"--exposure-optimum\" requires an argument" << std::endl;
1517                 failed = true;
1518             }
1519             optionSet.insert(ExposureOptimumOption);
1520             break;
1521 
1522 
1523         case ObsoleteExposureWidthId:
1524             std::cerr << command << ": info: option \"--exposure-sigma\" is obsolete, prefer \"--exposure-width\"" << std::endl;
1525             BOOST_FALLTHROUGH;
1526         case ExposureWidthId:
1527             if (optarg != nullptr && *optarg != 0) {
1528                 ExposureWidth =
1529                     enblend::numberOfString(optarg,
1530                                             [](double x) {return x > 0.0;}, //< minimum-exposure-width 0
1531                                             "exposure width less than 0; will use 1/1024",
1532                                             1.0 / 1024.0);
1533             } else {
1534                 std::cerr << command << ": option \"--exposure-width\" requires an argument" << std::endl;
1535                 failed = true;
1536             }
1537             optionSet.insert(ExposureWidthOption);
1538             break;
1539 
1540         case ExposureWeightFunctionId: {
1541             const std::string arg(optarg);
1542             boost::char_separator<char> separator(PATH_OPTION_DELIMITERS);
1543             boost::tokenizer<boost::char_separator<char> > tokenizer(arg, separator);
1544             boost::tokenizer<boost::char_separator<char> >::iterator token = tokenizer.begin();
1545 
1546             if (token == tokenizer.end()) {
1547                 std::cerr << command
1548                           << ": option \"--exposure-weight-function\" requires an argument" << std::endl;
1549                 failed = true;
1550             } else {
1551                 ExposureWeightFunctionName = *token;
1552                 ++token;
1553 #ifdef _WIN32
1554                 // special handling of absolute filenames on Windows
1555                 if (ExposureWeightFunctionName.length() == 1U && arg.length() > 2U && arg[1] == ':' && token != tokenizer.end())
1556                 {
1557                     ExposureWeightFunctionName.append(":");
1558                     ExposureWeightFunctionName.append(*token);
1559                     ++token;
1560                 }
1561 #endif
1562             }
1563 
1564             for (; token != tokenizer.end(); ++token) {
1565                 ExposureWeightFunctionArguments.push_back(*token);
1566             }
1567 
1568             optionSet.insert(ExposureWeightFunctionOption);
1569             break;
1570         }
1571 
1572         case WeightEntropyId:
1573             if (optarg != nullptr && *optarg != 0) {
1574                 WEntropy =
1575                     enblend::numberOfString(optarg,
1576                                             [](double x) {return x >= 0.0;}, //< minimum-weight-entropy 0
1577                                             "entropy weight less than 0; will use 0",
1578                                             0.0,
1579                                             [](double x) {return x <= 1.0;}, //< maximum-weight-entropy 1
1580                                             "entropy weight greater than 1; will use 1",
1581                                             1.0);
1582             } else {
1583                 std::cerr << command << ": option \"--entropy-weight\" requires an argument" << std::endl;
1584                 failed = true;
1585             }
1586             optionSet.insert(EntropyWeightOption);
1587             break;
1588 
1589         case 'v': BOOST_FALLTHROUGH;
1590         case VerboseId:
1591             if (optarg != nullptr && *optarg != 0) {
1592                 Verbose =
1593                     enblend::numberOfString(optarg,
1594                                             [](int x) {return x >= 0;}, //< minimum-verbosity-level 0
1595                                             "verbosity level less than 0; will use 0",
1596                                             0);
1597             } else {
1598                 Verbose++;
1599             }
1600             optionSet.insert(VerboseOption);
1601             break;
1602 
1603         case ContrastWindowSizeId:
1604             if (optarg != nullptr && *optarg != 0) {
1605                 ContrastWindowSize =
1606                     enblend::numberOfString(optarg,
1607                                             [](int x) {return x >= 3;}, //< minimum-contrast-window-size 3
1608                                             "contrast window size to small; will use size = 3",
1609                                             3);
1610                 if (ContrastWindowSize % 2 != 1) {
1611                     std::cerr << command << ": warning: contrast window size \""
1612                               << ContrastWindowSize << "\" is even; increasing size to next odd number"
1613                               << std::endl;
1614                     ContrastWindowSize++;
1615                 }
1616             } else {
1617                 std::cerr << command << ": option \"--contrast-window-size\" requires an argument" << std::endl;
1618                 failed = true;
1619             }
1620             optionSet.insert(ContrastWindowSizeOption);
1621             break;
1622 
1623         case EntropyWindowSizeId:
1624             if (optarg != nullptr && *optarg != 0) {
1625                 EntropyWindowSize =
1626                     enblend::numberOfString(optarg,
1627                                             [](int x) {return x >= 3;}, //< minimum-entropy-window-size 3
1628                                             "entropy window size to small; will use size = 3",
1629                                             3);
1630                 if (EntropyWindowSize % 2 != 1) {
1631                     std::cerr << command << ": warning: entropy window size \""
1632                               << EntropyWindowSize << "\" is even; increasing size to next odd number"
1633                               << std::endl;
1634                     EntropyWindowSize++;
1635                 }
1636             } else {
1637                 std::cerr << command << ": option \"--entropy-window-size\" requires an argument" << std::endl;
1638                 failed = true;
1639             }
1640             optionSet.insert(EntropyWindowSizeOption);
1641             break;
1642 
1643         case BlendColorspaceId:
1644             if (optarg != nullptr && *optarg != 0) {
1645                 std::string name(optarg);
1646                 enblend::to_upper(name);
1647                 if (name == "IDENTITY" || name == "ID" || name == "UNIT") {
1648                     BlendColorspace = IdentitySpace;
1649                 } else if (name == "LAB" || name == "CIELAB" || name == "LSTAR" || name == "L-STAR") {
1650                     BlendColorspace = CIELAB;
1651                 } else if (name == "LUV" || name == "CIELUV") {
1652                     BlendColorspace = CIELUV;
1653                 } else if (name == "CIECAM" || name == "CIECAM02" || name == "JCH") {
1654                     BlendColorspace = CIECAM;
1655                 } else {
1656                     std::cerr << command <<
1657                         ": unrecognized argument \"" << optarg << "\" of option \"--blend-colorspace\"" <<
1658                         std::endl;
1659                     failed = true;
1660                 }
1661             } else {
1662                 std::cerr << command << ": option \"--blend-colorspace\" requires an argument" << std::endl;
1663                 failed = true;
1664             }
1665             optionSet.insert(BlendColorspaceOption);
1666             break;
1667 
1668         case 'c': BOOST_FALLTHROUGH;
1669         case CiecamId:
1670             std::cerr <<
1671                 command << ": info: option \"--ciecam\" will be withdrawn in the next release\n" <<
1672                 command << ": note: prefer option \"--blend-colorspace\" to \"--ciecam\"" << std::endl;
1673             BlendColorspace = CIECAM;
1674             optionSet.insert(CIECAM02Option);
1675             break;
1676 
1677         case NoCiecamId:
1678             std::cerr <<
1679                 command << ": info: option \"--no-ciecam\" will be withdrawn in the next release\n" <<
1680                 command << ": note: prefer option \"--blend-colorspace\" to \"--no-ciecam\"" << std::endl;
1681             BlendColorspace = IdentitySpace;
1682             optionSet.insert(NoCIECAM02Option);
1683             break;
1684 
1685         case FallbackProfileId:
1686             if (enblend::can_open_file(optarg)) {
1687                 FallbackProfile = cmsOpenProfileFromFile(optarg, "r");
1688                 if (FallbackProfile == nullptr) {
1689                     std::cerr << command << ": failed to open fallback ICC profile file \"" << optarg << "\"\n";
1690                     exit(1);
1691                 }
1692             } else {
1693                 exit(1);
1694             }
1695             optionSet.insert(FallbackProfileOption);
1696             break;
1697 
1698         case 'f':
1699             if (optarg != nullptr && *optarg != 0) {
1700                 const int n = sscanf(optarg,
1701                                      "%dx%d+%d+%d",
1702                                      &OutputWidthCmdLine, &OutputHeightCmdLine,
1703                                      &OutputOffsetXCmdLine, &OutputOffsetYCmdLine);
1704                 if (n == 4) {
1705                     ; // ok: full geometry string
1706                 } else if (n == 2) {
1707                     OutputOffsetXCmdLine = 0;
1708                     OutputOffsetYCmdLine = 0;
1709                 } else {
1710                     std::cerr << command << ": option \"-f\" requires 2 or 4 arguments" << std::endl;
1711                     failed = true;
1712                 }
1713             } else {
1714                 std::cerr << command << ": option \"-f\" requires 2 or 4 arguments" << std::endl;
1715                 failed = true;
1716             }
1717             OutputSizeGiven = true;
1718             optionSet.insert(SizeAndPositionOption);
1719             break;
1720 
1721         case 'g':
1722             GimpAssociatedAlphaHack = true;
1723             optionSet.insert(AssociatedAlphaOption);
1724             break;
1725 
1726         case 'l': BOOST_FALLTHROUGH;
1727         case LevelsId:
1728             if (optarg != nullptr && *optarg != 0) {
1729                 std::string levels(optarg);
1730                 enblend::to_upper(levels);
1731                 if (levels == "AUTO" || levels == "AUTOMATIC") {
1732                     ExactLevels = 0;
1733                 } else if (levels.find_first_not_of("+-0123456789") != std::string::npos) {
1734                     std::cerr << command <<
1735                         ": options \"-l\" or \"--levels\" require an integer argument or \"auto\"" << std::endl;
1736                     failed = true;
1737                 } else {
1738                     std::ostringstream oss;
1739                     oss <<
1740                         "cannot use more than " << MAX_PYRAMID_LEVELS <<
1741                         " pyramid levels; will use at most " << MAX_PYRAMID_LEVELS << " levels";
1742                     ExactLevels =
1743                         enblend::numberOfString(optarg,
1744                                                 [](int x) {return x != 0;},
1745                                                 "cannot blend with zero levels; will use one level",
1746                                                 1,
1747                                                 [](int x) {return x <= MAX_PYRAMID_LEVELS;},
1748                                                 oss.str(),
1749                                                 MAX_PYRAMID_LEVELS);
1750                 }
1751             } else {
1752                 std::cerr << command << ": options \"-l\" or \"--levels\" require an argument" << std::endl;
1753                 failed = true;
1754             }
1755             optionSet.insert(LevelsOption);
1756             break;
1757 
1758         case LayerSelectorId: {
1759             selector::algorithm_list::const_iterator selector = selector::find_by_name(optarg);
1760             if (selector != selector::algorithms.end()) {
1761                 LayerSelection.set_selector(selector->get());
1762             } else {
1763                 std::cerr << command << ": unknown selector algorithm \"" << optarg << "\"";
1764                 exit(1);
1765             }
1766             optionSet.insert(LayerSelectorOption);
1767             break;
1768         }
1769 
1770         case ParameterId: {
1771             boost::char_separator<char> separator(NUMERIC_OPTION_DELIMITERS, "", boost::keep_empty_tokens);
1772             const std::string arg(optarg);
1773             boost::tokenizer<boost::char_separator<char> > tokenizer(arg, separator);
1774             boost::tokenizer<boost::char_separator<char> >::iterator token = tokenizer.begin();
1775 
1776             for (auto token = tokenizer.begin(); token != tokenizer.end(); ++token) {
1777                 std::string key;
1778                 std::string value;
1779                 size_t delimiter = token->find_first_of(ASSIGNMENT_CHARACTERS);
1780 
1781                 if (delimiter == std::string::npos) {
1782                     key = *token;
1783                     value.assign("1");
1784                 } else {
1785                     key = token->substr(0, delimiter);
1786                     value = token->substr(delimiter + 1);
1787                     if (value.empty()) {
1788                         std::cerr <<
1789                             command << ": parameter key \"" << key << "\" lacks a value;\n" <<
1790                             command << ": note: dangling assignment operator\n";
1791                         exit(1);
1792                     }
1793                 }
1794                 enblend::trim(key);
1795                 enblend::trim(value);
1796 
1797                 if (parameter::is_valid_identifier(key)) {
1798                     parameter::insert(key, value);
1799                 } else {
1800                     std::cerr << command << ": parameter key \"" << key << "\" is not a valid identifier\n";
1801                     exit(1);
1802                 }
1803             }
1804 
1805             break;
1806         }
1807 
1808         case NoParameterId: {
1809             boost::char_separator<char> separator(NUMERIC_OPTION_DELIMITERS, "", boost::keep_empty_tokens);
1810             const std::string arg(optarg);
1811             boost::tokenizer<boost::char_separator<char> > tokenizer(arg, separator);
1812             boost::tokenizer<boost::char_separator<char> >::iterator token = tokenizer.begin();
1813 
1814             for (auto token = tokenizer.begin(); token != tokenizer.end(); ++token) {
1815                 std::string key(*token);
1816                 enblend::trim(key);
1817 
1818                 if (key == "*") {
1819                     parameter::erase_all();
1820                 } else if (parameter::is_valid_identifier(key)) {
1821                     parameter::erase(key);
1822                 } else {
1823                     std::cerr << command << ": warning: key \"" << key <<
1824                         "\" is not a valid identifier; ignoring\n";
1825                 }
1826             }
1827 
1828             break;
1829         }
1830 
1831         case '?':
1832         {
1833             if (optopt == 0 || optopt == -1) {
1834                 const int failing_index = optind - 1;
1835 
1836                 std::cerr << command << ": unknown long option";
1837                 if (failing_index >= 0 && failing_index < argc) {
1838                     std::cerr << " \"" << argv[failing_index] << "\"";
1839                 }
1840                 std::cerr << std::endl;
1841             } else {
1842                 const struct option* failing_long_option = lookup_long_option_by_id(long_options, optopt);
1843 
1844                 if (failing_long_option == nullptr) {
1845                     if (short_option_requires_argument(short_options, optopt)) {
1846                         std::cerr << command
1847                                   << ": option \"-" << static_cast<char>(optopt) << "\" requires an argument"
1848                                   << std::endl;
1849                     } else {
1850                         std::cerr << command << ": unknown option ";
1851                         if (isprint(optopt)) {
1852                             std::cerr << "\"-" << static_cast<char>(optopt) << "\"";
1853                         } else {
1854                             std::cerr << "character 0x" << std::hex << optopt;
1855                         }
1856                         std::cerr << std::endl;
1857                     }
1858                 } else {
1859                     assert(failing_long_option->has_arg == required_argument);
1860                     std::cerr << command
1861                               << ": option \"--" << failing_long_option->name << "\" requires an argument"
1862                               << std::endl;
1863                 }
1864             }
1865 
1866             std::cerr << "Try \"enfuse --help\" for more information." << std::endl;
1867             exit(1);
1868         }
1869 
1870         default:
1871             std::cerr << command
1872                       << ": internal error: unhandled command line option"
1873                       << std::endl;
1874             exit(1);
1875         }
1876     }
1877 
1878     if (contains(optionSet, SaveMasksOption) && contains(optionSet, LoadMasksOption))
1879     {
1880         std::cerr << command
1881                   << ": options \"--load-masks\" and \"--save-masks\" are mutually exclusive" << std::endl;
1882         failed = true;
1883     }
1884 
1885     if (failed) {
1886         exit(1);
1887     }
1888 
1889     switch (print_only_task)
1890     {
1891     case VERSION_ONLY:
1892 #ifdef _MSC_VER
1893         // deinstall failure hook to catch errors in printVersion directly
1894         // so printVersion can run to end
1895         __pfnDliFailureHook2 = nullptr;
1896 #endif
1897         introspection::printVersion(argc, argv);
1898         break;                  // never reached
1899     case USAGE_ONLY:
1900         printUsage(false);
1901         break;                  // never reached
1902     case IMAGE_FORMATS_ONLY:
1903         introspection::printImageFormats();
1904         break;                  // never reached
1905     case SIGNATURE_ONLY:
1906         introspection::printSignature();
1907         break;                  // never reached
1908     case GLOBBING_ALGOS_ONLY:
1909         introspection::printGlobbingAlgos();
1910         break;                  // never reached
1911     case SOFTWARE_COMPONENTS_ONLY:
1912         introspection::printSoftwareComponents();
1913         break;                  // never reached
1914     case GPU_INFO_COMPONENTS_ONLY:
1915 #ifdef OPENCL
1916         std::cout << "Available, OpenCL-compatible platform(s) and their device(s)\n";
1917         ocl::print_opencl_information();
1918         ocl::print_gpu_preference(preferredGPUPlatform, preferredGPUDevice);
1919         exit(0);
1920 #else
1921         std::cerr <<
1922             command << ": option \"--show-gpu-info\" is not implemented in this binary,\n" <<
1923             command << ": because it was compiled without support for OpenCL" << std::endl;
1924         exit(1);
1925 #endif
1926         break;                  // never reached
1927 
1928     case PROCESS_COMPLETELY:
1929         break;
1930 
1931     default:
1932         NEVER_REACHED("switch control expression \"print_only_task\" out of range");
1933     }
1934 
1935     StopAfterMaskGeneration = contains(optionSet, SaveMasksOption) && !contains(optionSet, OutputOption);
1936 
1937     warn_of_ineffective_options(optionSet);
1938 
1939 #ifdef OPENCL
1940     if (UseGPU) {
1941         initialize_gpu_subsystem(preferredGPUPlatform, preferredGPUDevice);
1942     }
1943 #endif // OPENCL
1944 
1945     if (WExposure > 0.0)
1946     {
1947         ExposureWeightFunction =
1948             exposure_weight::make_weight_function(ExposureWeightFunctionName,
1949                                                   ExposureWeightFunctionArguments.begin(),
1950                                                   ExposureWeightFunctionArguments.end(),
1951                                                   ExposureOptimum, ExposureWidth);
1952 
1953         const exposure_weight::weight_function_check_t check_result =
1954             exposure_weight::check_weight_function(ExposureWeightFunction);
1955         if (check_result != exposure_weight::OK)
1956         {
1957             std::cerr << command <<
1958                 ": unusable exposure weight function \"" << ExposureWeightFunctionName << "\"\n";
1959 
1960             switch (check_result)
1961             {
1962             case exposure_weight::NEGATIVE:
1963                 std::cerr << command << ": note: at least one weight is negative" << std::endl;
1964                 exit(1);
1965 
1966             case exposure_weight::NON_UNIT:
1967                 std::cerr << command << ": note: at least one weight is larger than one" << std::endl;
1968                 exit(1);
1969 
1970             case exposure_weight::DEGENERATE:
1971                 std::cerr << command << ": note: too many zeros" << std::endl;
1972                 exit(1);
1973 
1974             case exposure_weight::OK:
1975                 throw never_reached("case indicates OK in error handler switch-expression");
1976             }
1977         }
1978     }
1979 
1980     if (parameter::as_boolean("dump-exposure-weight-function", false))
1981     {
1982         const int n = parameter::as_integer("exposure-weight-function-points", 21);
1983         exposure_weight::dump_weight_function(ExposureWeightFunction, std::min(std::max(n, 10), 10000));
1984         exit(0);
1985     }
1986 
1987     return optind;
1988 }
1989 
1990 
main(int argc,char ** argv)1991 int main(int argc, char** argv)
1992 {
1993 #ifdef _MSC_VER
1994     // Make sure the FPU is set to rounding mode so that the lrint
1995     // functions in float_cast.h will work properly.
1996     // See changes in vigra numerictraits.hxx
1997     _controlfp(_RC_NEAR, _MCW_RC);
1998     // install failure hook for delayed loading of dll
1999     __pfnDliFailureHook2 = delayHookFailureFunc;
2000 #else
2001     fesetround(FE_TONEAREST);
2002 #endif
2003 
2004 #ifndef _WIN32
2005     sigemptyset(&SigintMask);
2006     sigaddset(&SigintMask, SIGINT);
2007 
2008     struct sigaction action;
2009     action.sa_handler = sigint_handler;
2010     action.sa_flags   = 0;
2011     sigemptyset(&(action.sa_mask));
2012     sigaction(SIGINT, &action, nullptr);
2013 #else
2014     signal(SIGINT, sigint_handler);
2015 #endif
2016 
2017     if (atexit(cleanup_output) != 0) {
2018         std::cerr << command << ": warning: could not install cleanup routine\n";
2019     }
2020 
2021     sig.initialize();
2022 
2023     gsl_set_error_handler_off();
2024 
2025     TIFFSetWarningHandler(tiff_warning);
2026     TIFFSetErrorHandler(tiff_error);
2027 
2028     //< layer-selector all-layers
2029     LayerSelection.set_selector(selector::find_by_id(selector::id_t::AllLayersId)->get());
2030 
2031     if (!getopt_long_works_ok())
2032     {
2033         std::cerr << command << ": cannot reliably parse command line; giving up\n";
2034         exit(1);
2035     }
2036 
2037     int optind;
2038     try {
2039         optind = process_options(argc, argv);
2040     } catch (vigra::StdException& e) {
2041         std::cerr << command << ": error while processing command line options\n"
2042                   << command << ": " << e.what()
2043                   << std::endl;
2044         exit(1);
2045     }
2046 
2047     enblend::TraceableFileNameList inputTraceableFileNameList;
2048 
2049     // Remaining parameters are input files.
2050     while (optind < argc) {
2051         enblend::TraceableFileNameList files;
2052         enblend::unfold_filename(files, std::string(argv[optind]));
2053         inputTraceableFileNameList.insert(inputTraceableFileNameList.end(),
2054                                           files.begin(), files.end());
2055         optind++;
2056     }
2057 
2058     if (inputTraceableFileNameList.empty()) {
2059         std::cerr << command << ": no input files specified\n";
2060         exit(1);
2061     }
2062 
2063     if (parameter::as_boolean("dump-global-variables", false)) {
2064         DUMP_GLOBAL_VARIABLES();
2065     }
2066 
2067     sig.check();
2068 
2069     for (enblend::TraceableFileNameList::iterator i = inputTraceableFileNameList.begin();
2070          i != inputTraceableFileNameList.end();
2071          ++i) {
2072         if (!enblend::can_open_file((*i)->filename())) {
2073             (*i)->unroll_trace();
2074             exit(1);
2075         }
2076 
2077         if (!vigra::isImage((*i)->filename().c_str())) {
2078             std::cerr <<
2079                 command << ": cannot process \"" << (*i)->filename() << "\"; not recognized as an image\n" <<
2080                 command << ": info: possible causes:\n" <<
2081                 command << ": info: - An underlying image-processing library does not understand the\n" <<
2082                 command << ": info:   particular compression, sub-format, format extension, ...\n" <<
2083                 command << ": info: - Enfuse was not compiled with support for this format, which\n" <<
2084                 command << ": info:   can be checked with \"" << command << " --show-image-formats\".\n" <<
2085                 command << ": info: - The image is corrupted or it is incomplete/truncated.\n" <<
2086                 command << ": info: - It really is not an image.  Honesty, huh?\n";
2087             (*i)->unroll_trace();
2088             exit(1);
2089         }
2090     }
2091 
2092     LayerSelection.retrieve_image_information(inputTraceableFileNameList.begin(),
2093                                               inputTraceableFileNameList.end());
2094 
2095     // List of info structures for each input image.
2096     std::list<vigra::ImageImportInfo*> imageInfoList;
2097     std::list<vigra::ImageImportInfo*>::iterator imageInfoIterator;
2098 
2099     bool isColor = false;
2100     std::string pixelType;
2101     TiffResolution resolution;
2102     vigra::ImageImportInfo::ICCProfile iccProfile;
2103     vigra::Rect2D inputUnion;
2104 
2105     selector::layer_ordered_list_t viable_layers;
2106     selector::layer_ordered_list_t::const_iterator layer;
2107     unsigned layers = 0;       // total number of layers in image file
2108     enblend::FileNameList inputFileNameList;
2109     enblend::TraceableFileNameList::iterator inputFileNameIterator = inputTraceableFileNameList.begin();
2110     while (inputFileNameIterator != inputTraceableFileNameList.end()) {
2111         const std::string filename((*inputFileNameIterator)->filename());
2112         vigra::ImageImportInfo* inputInfo = nullptr;
2113         try {
2114             vigra::ImageImportInfo info(filename.c_str());
2115             if (layers == 0) { // OPTIMIZATION: call only once per file
2116                 layers = info.numImages();
2117                 LayerSelection.set_selector((*inputFileNameIterator)->selector());
2118                 viable_layers.clear();
2119                 viable_layers = LayerSelection.viable_layers(filename);
2120                 layer = viable_layers.begin();
2121 #ifdef DEBUG_FILESPEC
2122                 std::cout << "+ viable_layers(" << filename << ") are [ ";
2123                 std::copy(viable_layers.begin(), viable_layers.end(),
2124                           std::ostream_iterator<unsigned>(std::cout, " "));
2125                 std::cout << "]\n";
2126 #endif
2127             }
2128             inputInfo = new vigra::ImageImportInfo(info);
2129         } catch (vigra::ContractViolation& exception) {
2130             std::cerr <<
2131                 command << ": cannot load image \"" << filename << "\"\n" <<
2132                 command << ": " << exception.what() << "\n";
2133             if (enblend::maybe_response_file(filename)) {
2134                 std::cerr <<
2135                     command << ": note: maybe you meant a response file and forgot the initial '" <<
2136                     RESPONSE_FILE_PREFIX_CHAR << "'?\n";
2137             }
2138             exit(1);
2139         }
2140 
2141         assert(layer != viable_layers.end());
2142         inputInfo->setImageIndex(*layer - 1);
2143 
2144         if (Verbose >= VERBOSE_LAYER_SELECTION) {
2145             std::cerr << command << ": info: layer selector \"" << LayerSelection.name() << "\" accepts\n"
2146                       << command << ": info: layer " << *layer << " of " << layers << " in image \""
2147                       << filename << "\"\n";
2148         }
2149 
2150         // Save this image info in the list.
2151         imageInfoList.push_back(inputInfo);
2152         inputFileNameList.push_back(filename);
2153 
2154         if (Verbose >= VERBOSE_INPUT_IMAGE_INFO_MESSAGES) {
2155             std::cerr << command
2156                       << ": info: input image \""
2157                       << (*inputFileNameIterator)->filename()
2158                       << "\" "
2159                       << *layer << '/' << layers << ' ';
2160 
2161             if (inputInfo->isColor()) {
2162                 std::cerr << "RGB ";
2163             }
2164 
2165             if (!inputInfo->getICCProfile().empty()) {
2166                 std::cerr << "ICC ";
2167             }
2168 
2169             std::cerr << inputInfo->getPixelType()
2170                       << " position="
2171                       << inputInfo->getPosition().x
2172                       << "x"
2173                       << inputInfo->getPosition().y
2174                       << " "
2175                       << "size="
2176                       << inputInfo->width()
2177                       << "x"
2178                       << inputInfo->height()
2179                       << std::endl;
2180         }
2181 
2182         if (inputInfo->numExtraBands() < 1) {
2183             // Complain about lack of alpha channel.
2184             std::cerr << command
2185                       << ": info: input image \"" << (*inputFileNameIterator)->filename() << "\""
2186                       << enblend::optional_layer_name(*layer, layers)
2187                       << " does not have an alpha channel;\n";
2188             (*inputFileNameIterator)->unroll_trace();
2189             std::cerr << command
2190                       << ": note: assuming all pixels should contribute to the final image"
2191                       << std::endl;
2192         }
2193 
2194         // Get input image's position and size.
2195         vigra::Rect2D imageROI(vigra::Point2D(inputInfo->getPosition()),
2196                                vigra::Size2D(inputInfo->width(), inputInfo->height()));
2197 
2198         if (inputFileNameIterator == inputTraceableFileNameList.begin()) {
2199             // First input image
2200             inputUnion = imageROI;
2201             isColor = inputInfo->isColor();
2202             pixelType = inputInfo->getPixelType();
2203             resolution = TiffResolution(inputInfo->getXResolution(),
2204                                         inputInfo->getYResolution());
2205             iccProfile = inputInfo->getICCProfile();
2206             if (!iccProfile.empty()) {
2207                 InputProfile = cmsOpenProfileFromMem(iccProfile.data(), iccProfile.size());
2208                 if (InputProfile == nullptr) {
2209                     std::cerr << std::endl
2210                               << command << ": error parsing ICC profile data from file \""
2211                               << (*inputFileNameIterator)->filename()
2212                               << "\"" << enblend::optional_layer_name(*layer, layers) << std::endl;
2213                     (*inputFileNameIterator)->unroll_trace();
2214                     exit(1);
2215                 }
2216             }
2217         } else {
2218             // Second and later images
2219             inputUnion |= imageROI;
2220 
2221             if (isColor != inputInfo->isColor()) {
2222                 std::cerr << command << ": input image \""
2223                           << (*inputFileNameIterator)->filename() << "\""
2224                           << enblend::optional_layer_name(*layer, layers) << " is "
2225                           << (inputInfo->isColor() ? "color" : "grayscale") << "\n"
2226                           << command << ": but previous images are "
2227                           << (isColor ? "color" : "grayscale")
2228                           << std::endl;
2229                 (*inputFileNameIterator)->unroll_trace();
2230                 exit(1);
2231             }
2232             if (pixelType != inputInfo->getPixelType()) {
2233                 std::cerr << command << ": input image \""
2234                           << (*inputFileNameIterator)->filename() << "\""
2235                           << enblend::optional_layer_name(*layer, layers) << " has pixel type "
2236                           << inputInfo->getPixelType() << ",\n"
2237                           << command << ": but previous images have pixel type "
2238                           << pixelType
2239                           << std::endl;
2240                 (*inputFileNameIterator)->unroll_trace();
2241                 exit(1);
2242             }
2243             if (resolution !=
2244                 TiffResolution(inputInfo->getXResolution(), inputInfo->getYResolution())) {
2245                 std::cerr << command << ": info: input image \""
2246                           << (*inputFileNameIterator)->filename() << "\""
2247                           << enblend::optional_layer_name(*layer, layers) << " has resolution "
2248                           << inputInfo->getXResolution() << " dpi x "
2249                           << inputInfo->getYResolution() << " dpi,\n"
2250                           << command << ": info: but first image has resolution "
2251                           << resolution.x << " dpi x " << resolution.y << " dpi"
2252                           << std::endl;
2253                 (*inputFileNameIterator)->unroll_trace();
2254             }
2255             if (iccProfile != inputInfo->getICCProfile()) {
2256                 vigra::ImageImportInfo::ICCProfile mismatchProfile(inputInfo->getICCProfile());
2257                 cmsHPROFILE newProfile = nullptr;
2258                 if (!mismatchProfile.empty()) {
2259                     newProfile = cmsOpenProfileFromMem(mismatchProfile.data(), mismatchProfile.size());
2260                     if (newProfile == nullptr) {
2261                         std::cerr << std::endl
2262                                   << command << ": error parsing ICC profile data from file \""
2263                                   << (*inputFileNameIterator)->filename()
2264                                   << "\"" << enblend::optional_layer_name(*layer, layers) << std::endl;
2265                         (*inputFileNameIterator)->unroll_trace();
2266                         exit(1);
2267                     }
2268                 }
2269 
2270                 if (InputProfile == nullptr || newProfile == nullptr ||
2271                     enblend::profileDescription(InputProfile) != enblend::profileDescription(newProfile)) {
2272                     const std::string category(BlendColorspace <= IdentitySpace ? "warning" : "info");
2273                     std::cerr << std::endl << command << ": " << category << ": input image \""
2274                               << (*inputFileNameIterator)->filename()
2275                               << "\"" << enblend::optional_layer_name(*layer, layers) << "\n";
2276                     (*inputFileNameIterator)->unroll_trace();
2277                     std::cerr << command << ": " << category << ": has ";
2278                     if (newProfile) {
2279                         std::cerr << "ICC profile \"" << enblend::profileDescription(newProfile) << "\",\n";
2280                     } else {
2281                         std::cerr << "no ICC profile,\n";
2282                     }
2283                     std::cerr << command << ": " << category << ": but first image has ";
2284                     if (InputProfile) {
2285                         std::cerr << "ICC profile \"" << enblend::profileDescription(InputProfile) << "\";\n";
2286                     } else {
2287                         std::cerr << "no ICC profile;\n";
2288                     }
2289                     if (BlendColorspace <= IdentitySpace) {
2290                         std::cerr << command << ": " << category << ": blending images with different color spaces\n"
2291                                   << command << ": " << category << ": may have unexpected results\n";
2292                     }
2293                 }
2294             }
2295         }
2296 
2297         ++layer;
2298         if (layer == viable_layers.end()) {
2299 #ifdef DEBUG_FILESPEC
2300             std::cout << "+ next image\n";
2301 #endif
2302             layers = 0;
2303             ++inputFileNameIterator;
2304         } else {
2305 #ifdef DEBUG_FILESPEC
2306             std::cout << "+ next layer\n";
2307 #endif
2308             // We are about to process the next layer in the _same_
2309             // image.  The imageInfoList already has been updated, but
2310             // inputTraceableFileNameList still lacks the filename.
2311             inputTraceableFileNameList.insert(inputFileNameIterator, (*inputFileNameIterator)->clone());
2312         }
2313     }
2314 
2315     // Check that more than one input file was given.
2316     if (imageInfoList.size() <= 1) {
2317         const size_t n = inputTraceableFileNameList.size();
2318         const size_t m = imageInfoList.size();
2319 
2320         if (n > m) {
2321             std::cerr << command << ": warning: selector has rejected " << n - m << " out of " << n << " images\n";
2322         }
2323 
2324         switch (m) {
2325         case 0:
2326             std::cerr << command << ": no input images given\n";
2327             exit(1);
2328             break;
2329         case 1:
2330             std::cerr << command << ": warning: only one input image given;\n"
2331                       << command << ": note: Enfuse needs two or more overlapping input images in order to do\n"
2332                       << command << ": note: blending calculations.  The output will be the same as the input.\n";
2333             break;
2334         }
2335     }
2336 
2337     if (resolution == TiffResolution()) {
2338         std::cerr << command
2339                   << ": warning: no usable resolution found in first image \""
2340                   << (*inputTraceableFileNameList.begin())->filename() << "\";\n"
2341                   << command << ": note: Enfuse will assume " << DEFAULT_TIFF_RESOLUTION << " dpi\n";
2342         ImageResolution = TiffResolution(DEFAULT_TIFF_RESOLUTION, DEFAULT_TIFF_RESOLUTION);
2343     } else {
2344         ImageResolution = resolution;
2345     }
2346 
2347     // Create the Info for the output file.
2348     vigra::ImageExportInfo outputImageInfo(OutputFileName.c_str());
2349 
2350     if (!StopAfterMaskGeneration) {
2351         OutputIsValid = false;
2352 
2353         // Make sure that inputUnion is at least as big as given by the -f paramater.
2354         if (OutputSizeGiven) {
2355             inputUnion |= vigra::Rect2D(OutputOffsetXCmdLine,
2356                                         OutputOffsetYCmdLine,
2357                                         OutputOffsetXCmdLine + OutputWidthCmdLine,
2358                                         OutputOffsetYCmdLine + OutputHeightCmdLine);
2359         }
2360 
2361         if (!OutputCompression.empty()) {
2362             outputImageInfo.setCompression(OutputCompression.c_str());
2363         }
2364 
2365         // If not overridden by the command line, the pixel type of the
2366         // output image is the same as the input images'.  If the pixel
2367         // type is not supported by the output format, replace it with the
2368         // best match.
2369         {
2370             const std::string outputFileType = enblend::getFileType(OutputFileName);
2371             const std::string neededPixelType = OutputPixelType.empty() ? std::string(pixelType) : OutputPixelType;
2372             const std::string bestPixelType = enblend::bestPixelType(outputFileType, neededPixelType);
2373 
2374             if (neededPixelType != bestPixelType) {
2375                 std::cerr << command
2376                           << ": warning: "
2377                           << (OutputPixelType.empty() ? "deduced" : "requested")
2378                           << " output pixel type is \""
2379                           << neededPixelType
2380                           << "\", but image type \""
2381                           << outputFileType
2382                           << "\"\n"
2383                           << command << ": warning: supports \""
2384                           << bestPixelType
2385                           << "\" at best;  will use \""
2386                           << bestPixelType
2387                           << "\""
2388                           << std::endl;
2389             }
2390             outputImageInfo.setPixelType(bestPixelType.c_str());
2391             pixelType = enblend::maxPixelType(pixelType, bestPixelType);
2392         }
2393 
2394         // Set the output image ICC profile
2395         outputImageInfo.setICCProfile(iccProfile);
2396 
2397         if (BlendColorspace == UndeterminedColorspace &&
2398             !(iccProfile.empty() || enblend::isFloatingPoint(pixelType))) {
2399             BlendColorspace = CIELUV;
2400         }
2401 
2402         if (BlendColorspace == CIECAM || BlendColorspace == CIELAB || BlendColorspace == CIELUV) {
2403             if (enblend::isFloatingPoint(pixelType)) {
2404                 std::cerr << command <<
2405                     ": warning: blend color space for floating-point images is not \"identity\"" << std::endl;
2406             }
2407 
2408             if (InputProfile == nullptr) {
2409                 std::cerr << command << ": warning: input images do not have ICC profiles;\n";
2410                 if (FallbackProfile == nullptr) {
2411                     std::cerr << command << ": warning: assuming sRGB profile" << std::endl;
2412                     InputProfile = cmsCreate_sRGBProfile();
2413                 } else {
2414                     std::cerr << command << ": warning: using fallback profile \""
2415                               << enblend::profileDescription(FallbackProfile) << "\"" << std::endl;
2416                     InputProfile = FallbackProfile;
2417                     FallbackProfile = nullptr; // avoid double freeing
2418                 }
2419             }
2420             XYZProfile = cmsCreateXYZProfile();
2421 
2422             const unsigned input_profile_type =
2423                 enblend::profileChannels(InputProfile) > 1 ? TYPE_RGB_DBL : TYPE_GRAY_DBL;
2424 
2425             InputToXYZTransform = cmsCreateTransform(InputProfile, input_profile_type,
2426                                                      XYZProfile, TYPE_XYZ_DBL,
2427                                                      RENDERING_INTENT_FOR_BLENDING,
2428                                                      TRANSFORMATION_FLAGS_FOR_BLENDING);
2429             if (InputToXYZTransform == nullptr) {
2430                 std::cerr << command << ": error building color transform from \""
2431                           << enblend::profileName(InputProfile)
2432                           << " "
2433                           << enblend::profileDescription(InputProfile)
2434                           << "\" to XYZ space" << std::endl;
2435                 exit(1);
2436             }
2437 
2438             XYZToInputTransform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
2439                                                      InputProfile, input_profile_type,
2440                                                      RENDERING_INTENT_FOR_BLENDING,
2441                                                      TRANSFORMATION_FLAGS_FOR_BLENDING);
2442             if (XYZToInputTransform == nullptr) {
2443                 std::cerr << command
2444                           << ": error building color transform from XYZ space to \""
2445                           << enblend::profileName(InputProfile)
2446                           << " "
2447                           << enblend::profileDescription(InputProfile)
2448                           << "\"" << std::endl;
2449                 exit(1);
2450             }
2451 
2452             // P2 Viewing Conditions: D50, 500 lumens
2453             ViewingConditions.whitePoint.X = XYZ_SCALE * cmsD50_XYZ()->X;
2454             ViewingConditions.whitePoint.Y = XYZ_SCALE * cmsD50_XYZ()->Y;
2455             ViewingConditions.whitePoint.Z = XYZ_SCALE * cmsD50_XYZ()->Z;
2456             ViewingConditions.Yb = 20.0;
2457             ViewingConditions.La = 31.83;
2458             ViewingConditions.surround = AVG_SURROUND;
2459             ViewingConditions.D_value = 1.0;
2460 
2461             CIECAMTransform = cmsCIECAM02Init(nullptr, &ViewingConditions);
2462             if (!CIECAMTransform) {
2463                 std::cerr << std::endl
2464                           << command
2465                           << ": error initializing CIECAM02 transform"
2466                           << std::endl;
2467                 exit(1);
2468             }
2469 
2470             cmsCIExyY white_point;
2471             if (cmsIsTag(InputProfile, cmsSigMediaWhitePointTag)) {
2472                 cmsXYZ2xyY(&white_point,
2473                            (const cmsCIEXYZ*) cmsReadTag(InputProfile, cmsSigMediaWhitePointTag));
2474                 if (Verbose >= VERBOSE_COLOR_CONVERSION_MESSAGES) {
2475                     double temperature;
2476                     cmsTempFromWhitePoint(&temperature, &white_point);
2477                     std::cerr << command
2478                               << ": info: using white point of input profile at " << temperature << "K"
2479                               << std::endl;
2480                 }
2481             } else {
2482                 memcpy(&white_point, cmsD50_xyY(), sizeof(cmsCIExyY));
2483                 if (Verbose >= VERBOSE_COLOR_CONVERSION_MESSAGES) {
2484                     double temperature;
2485                     cmsTempFromWhitePoint(&temperature, &white_point);
2486                     std::cerr << command
2487                               << ": info: falling back to predefined (D50) white point at " << temperature << "K"
2488                               << std::endl;
2489                 }
2490             }
2491             LabProfile = cmsCreateLab2Profile(&white_point);
2492             InputToLabTransform = cmsCreateTransform(InputProfile, input_profile_type,
2493                                                      LabProfile, TYPE_Lab_DBL,
2494                                                      RENDERING_INTENT_FOR_BLENDING,
2495                                                      TRANSFORMATION_FLAGS_FOR_BLENDING);
2496             if (!InputToLabTransform) {
2497                 std::cerr << command << ": error building color transform from \""
2498                           << enblend::profileName(InputProfile)
2499                           << " "
2500                           << enblend::profileDescription(InputProfile)
2501                           << "\" to Lab space" << std::endl;
2502                 exit(1);
2503             }
2504             LabToInputTransform = cmsCreateTransform(LabProfile, TYPE_Lab_DBL,
2505                                                      InputProfile, input_profile_type,
2506                                                      RENDERING_INTENT_FOR_BLENDING,
2507                                                      TRANSFORMATION_FLAGS_FOR_BLENDING);
2508             if (!LabToInputTransform) {
2509                 std::cerr << command
2510                           << ": error building color transform from Lab space to \""
2511                           << enblend::profileName(InputProfile)
2512                           << " "
2513                           << enblend::profileDescription(InputProfile)
2514                           << "\"" << std::endl;
2515                 exit(1);
2516             }
2517         } else {
2518             if (FallbackProfile != nullptr) {
2519                 std::cerr << command <<
2520                     ": warning: fusing in identity space; option \"--fallback-profile\" has no effect" <<
2521                     std::endl;
2522             }
2523         }
2524 
2525         // The size of the output image.
2526         if (Verbose >= VERBOSE_INPUT_UNION_SIZE_MESSAGES) {
2527             std::cerr << command
2528                       << ": info: output image size: "
2529                       << inputUnion
2530                       << std::endl;
2531         }
2532 
2533         // Set the output image position and resolution.
2534         outputImageInfo.setXResolution(ImageResolution.x);
2535         outputImageInfo.setYResolution(ImageResolution.y);
2536         outputImageInfo.setPosition(inputUnion.upperLeft());
2537 
2538         // Sanity check on the output image file.
2539         try {
2540             // This seems to be a reasonable way to check if
2541             // the output file is going to work after blending
2542             // is done.
2543             encoder(outputImageInfo);
2544         } catch (vigra::StdException& e) {
2545             std::cerr << std::endl
2546                       << command
2547                       << ": error opening output file \""
2548                       << OutputFileName
2549                       << "\";\n"
2550                       << command
2551                       << ": "
2552                       << e.what()
2553                       << std::endl;
2554             exit(1);
2555         }
2556 
2557         if (!OutputPixelType.empty()) {
2558             pixelType = enblend::maxPixelType(pixelType, OutputPixelType);
2559         }
2560     }
2561 
2562     // Invoke templatized blender.
2563     try {
2564         if (isColor) {
2565             if      (pixelType == "UINT8")  enblend::enfuseMain<vigra::RGBValue<vigra::UInt8 > >(inputFileNameList, imageInfoList, outputImageInfo, inputUnion);
2566 #ifndef DEBUG_8BIT_ONLY
2567             else if (pixelType == "UINT16") enblend::enfuseMain<vigra::RGBValue<vigra::UInt16> >(inputFileNameList, imageInfoList, outputImageInfo, inputUnion);
2568             else if (pixelType == "INT16")  enblend::enfuseMain<vigra::RGBValue<vigra::Int16 > >(inputFileNameList, imageInfoList, outputImageInfo, inputUnion);
2569             else if (pixelType == "UINT32") enblend::enfuseMain<vigra::RGBValue<vigra::UInt32> >(inputFileNameList, imageInfoList, outputImageInfo, inputUnion);
2570             else if (pixelType == "INT32")  enblend::enfuseMain<vigra::RGBValue<vigra::Int32 > >(inputFileNameList, imageInfoList, outputImageInfo, inputUnion);
2571             else if (pixelType == "FLOAT")  enblend::enfuseMain<vigra::RGBValue<float > >(inputFileNameList, imageInfoList, outputImageInfo, inputUnion);
2572             else if (pixelType == "DOUBLE") enblend::enfuseMain<vigra::RGBValue<double> >(inputFileNameList, imageInfoList, outputImageInfo, inputUnion);
2573 #endif
2574             else {
2575                 std::cerr << command << ": RGB images with pixel type \""
2576                           << pixelType
2577                           << "\" are not supported"
2578                           << std::endl;
2579                 exit(1);
2580             }
2581         } else {
2582             if (!WSaturationIsDefault && (WSaturation != 0.0)) {
2583                 std::cerr << command
2584                           << ": warning: \"--WSaturation\" is not applicable to grayscale images;\n"
2585                           << command
2586                           << ": warning: this parameter will have no effect"
2587                           << std::endl;
2588                 WSaturation = 0.0;
2589             }
2590             if      (pixelType == "UINT8")  enblend::enfuseMain<vigra::UInt8 >(inputFileNameList, imageInfoList, outputImageInfo, inputUnion);
2591 #ifndef DEBUG_8BIT_ONLY
2592             else if (pixelType == "UINT16") enblend::enfuseMain<vigra::UInt16>(inputFileNameList, imageInfoList, outputImageInfo, inputUnion);
2593             else if (pixelType == "INT16")  enblend::enfuseMain<vigra::Int16 >(inputFileNameList, imageInfoList, outputImageInfo, inputUnion);
2594             else if (pixelType == "UINT32") enblend::enfuseMain<vigra::UInt32>(inputFileNameList, imageInfoList, outputImageInfo, inputUnion);
2595             else if (pixelType == "INT32")  enblend::enfuseMain<vigra::Int32 >(inputFileNameList, imageInfoList, outputImageInfo, inputUnion);
2596             else if (pixelType == "FLOAT")  enblend::enfuseMain<float >(inputFileNameList, imageInfoList, outputImageInfo, inputUnion);
2597             else if (pixelType == "DOUBLE") enblend::enfuseMain<double>(inputFileNameList, imageInfoList, outputImageInfo, inputUnion);
2598 #endif
2599             else {
2600                 std::cerr << command
2601                           << ": black&white images with pixel type \""
2602                           << pixelType
2603                           << "\" are not supported"
2604                           << std::endl;
2605                 exit(1);
2606             }
2607         }
2608 
2609         for (auto x : imageInfoList) {
2610             delete x;
2611         }
2612         for (auto x : inputTraceableFileNameList) {
2613             delete x;
2614         }
2615     } catch (std::bad_alloc& e) {
2616         std::cerr << std::endl
2617                   << command << ": out of memory\n"
2618                   << command << ": " << e.what()
2619                   << std::endl;
2620         exit(1);
2621     } catch (vigra::StdException& e) {
2622         std::cerr << std::endl
2623                   << command << ": an exception occured\n"
2624                   << command << ": " << e.what()
2625                   << std::endl;
2626         exit(1);
2627     }
2628 
2629 #ifdef OPENCL
2630     delete GPUContext;
2631 #endif // OPENCL
2632 
2633     if (FallbackProfile) {cmsCloseProfile(FallbackProfile);}
2634     if (LabProfile) {cmsCloseProfile(LabProfile);}
2635     if (InputToLabTransform) {cmsCIECAM02Done(InputToLabTransform);}
2636     if (LabToInputTransform) {cmsCIECAM02Done(LabToInputTransform);}
2637     if (CIECAMTransform) {cmsCIECAM02Done(CIECAMTransform);}
2638     if (InputToXYZTransform) {cmsDeleteTransform(InputToXYZTransform);}
2639     if (XYZToInputTransform) {cmsDeleteTransform(XYZToInputTransform);}
2640     if (XYZProfile) {cmsCloseProfile(XYZProfile);}
2641     if (InputProfile) {cmsCloseProfile(InputProfile);}
2642 
2643     delete ExposureWeightFunction;
2644 
2645     // Success.
2646     return 0;
2647 }
2648