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