1 /** 2 * @file StitchingExecutor.cpp 3 * @brief implementation of CommandQueue creating for stitching engine 4 * 5 * @author T. Modes 6 */ 7 8 /* This is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU General Public 10 * License as published by the Free Software Foundation; either 11 * version 2 of the License, or (at your option) any later version. 12 * 13 * This software is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Lesser General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public 19 * License along with this software. If not, see 20 * <http://www.gnu.org/licenses/>. 21 * 22 */ 23 24 #include "base_wx/platform.h" 25 #include "StitchingExecutor.h" 26 #include <list> 27 #include <wx/config.h> 28 #include <wx/translation.h> 29 #include <wx/arrstr.h> 30 #include <wx/filefn.h> 31 #include <wx/txtstrm.h> 32 #include <wx/wfstream.h> 33 #include "hugin_utils/utils.h" 34 #include "hugin_base/panotools/PanoToolsUtils.h" 35 #include "hugin_base/panodata/PanoramaOptions.h" 36 #include "hugin_base/algorithms/basic/LayerStacks.h" 37 #include "base_wx/wxPlatform.h" 38 #include "base_wx/LensTools.h" 39 #include "hugin/config_defaults.h" 40 41 namespace HuginQueue 42 { 43 namespace detail 44 { 45 // contains some helper functions 46 /** returns an array of filenames with numbers */ GetNumberedFilename(const wxString & prefix,const wxString & postfix,const HuginBase::UIntSet & img)47 wxArrayString GetNumberedFilename(const wxString& prefix, const wxString& postfix, const HuginBase::UIntSet& img) 48 { 49 wxArrayString filenames; 50 for (HuginBase::UIntSet::const_iterator it = img.begin(); it != img.end(); ++it) 51 { 52 filenames.Add(wxString::Format(wxT("%s%04u%s"), prefix.c_str(), *it, postfix.c_str())); 53 }; 54 return filenames; 55 }; 56 57 /** append all strings from input array to output array */ AddToArray(const wxArrayString & input,wxArrayString & output)58 void AddToArray(const wxArrayString& input, wxArrayString& output) 59 { 60 for (size_t i = 0; i < input.size(); ++i) 61 { 62 output.Add(input[i]); 63 }; 64 }; 65 66 /** generate the final argfile 67 @return full name of generated argfile 68 */ GenerateFinalArgfile(const HuginBase::Panorama & pano,const wxString & projectName,const wxConfigBase * config,const HuginBase::UIntSet & images,const double exifToolVersion)69 wxString GenerateFinalArgfile(const HuginBase::Panorama & pano, const wxString& projectName, const wxConfigBase* config, const HuginBase::UIntSet& images, const double exifToolVersion) 70 { 71 wxString argfileInput = config->Read(wxT("/output/FinalArgfile"), wxEmptyString); 72 const bool generateGPanoTags = (config->Read(wxT("/output/writeGPano"), HUGIN_EXIFTOOL_CREATE_GPANO) == 1l) && (exifToolVersion >= 9.09); 73 pano_projection_features proj; 74 const HuginBase::PanoramaOptions &opts = pano.getOptions(); 75 const bool readProjectionName = panoProjectionFeaturesQuery(opts.getProjection(), &proj) != 0; 76 // build placeholder map 77 struct Placeholder 78 { 79 wxString placeholder; 80 wxString value; 81 Placeholder(const wxString& holder, const wxString& newValue) 82 { 83 placeholder = holder; 84 value = newValue; 85 }; 86 }; 87 std::list<Placeholder> placeholders; 88 #ifdef _WIN32 89 const wxString linebreak(wxT("
")); 90 #else 91 const wxString linebreak(wxT("
")); 92 #endif 93 if (readProjectionName) 94 { 95 // %projectionNumber have to be processed before %projection, otherwise it does not work 96 placeholders.push_back(Placeholder(wxT("%projectionNumber"), wxString::Format(wxT("%d"), opts.getProjection()))); 97 placeholders.push_back(Placeholder(wxT("%projection"), wxString(proj.name, wxConvLocal))); 98 }; 99 // fill in some placeholders 100 placeholders.push_back(Placeholder(wxT("%hfov"), wxString::Format(wxT("%.0f"), opts.getHFOV()))); 101 placeholders.push_back(Placeholder(wxT("%vfov"), wxString::Format(wxT("%.0f"), opts.getVFOV()))); 102 placeholders.push_back(Placeholder(wxT("%ev"), wxString::Format(wxT("%.2f"), opts.outputExposureValue))); 103 placeholders.push_back(Placeholder(wxT("%nrImages"), wxString::Format(wxT("%lu"), (unsigned long)images.size()))); 104 placeholders.push_back(Placeholder(wxT("%nrAllImages"), wxString::Format(wxT("%lu"), (unsigned long)pano.getNrOfImages()))); 105 placeholders.push_back(Placeholder(wxT("%fullwidth"), wxString::Format(wxT("%u"), opts.getWidth()))); 106 placeholders.push_back(Placeholder(wxT("%fullheight"), wxString::Format(wxT("%u"), opts.getHeight()))); 107 placeholders.push_back(Placeholder(wxT("%width"), wxString::Format(wxT("%d"), opts.getROI().width()))); 108 placeholders.push_back(Placeholder(wxT("%height"), wxString::Format(wxT("%d"), opts.getROI().height()))); 109 wxFileName projectFilename(projectName); 110 placeholders.push_back(Placeholder(wxT("%projectname"), projectFilename.GetFullName())); 111 // now open the final argfile 112 wxFileName tempArgfileFinal(wxFileName::CreateTempFileName(GetConfigTempDir(config) + wxT("he"))); 113 wxFFileOutputStream outputStream(tempArgfileFinal.GetFullPath()); 114 wxTextOutputStream outputFile(outputStream); 115 // write argfile 116 outputFile << wxT("-Software=Hugin ") << wxString(hugin_utils::GetHuginVersion().c_str(), wxConvLocal) << endl; 117 outputFile << wxT("-E") << endl; 118 outputFile << wxT("-UserComment<${UserComment}") << linebreak; 119 if (readProjectionName) 120 { 121 outputFile << wxT("Projection: ") << wxString(proj.name, wxConvLocal) << wxT(" (") << opts.getProjection() << wxT(")") << linebreak; 122 }; 123 outputFile << wxT("FOV: ") << wxString::Format(wxT("%.0f"), opts.getHFOV()) << wxT(" x ") << wxString::Format(wxT("%.0f"), opts.getVFOV()) << linebreak; 124 outputFile << wxT("Ev: ") << wxString::Format(wxT("%.2f"), opts.outputExposureValue) << endl; 125 outputFile << wxT("-f") << endl; 126 if (generateGPanoTags) 127 { 128 //GPano tags are only indented for equirectangular images 129 if (opts.getProjection() == HuginBase::PanoramaOptions::EQUIRECTANGULAR) 130 { 131 const vigra::Rect2D roi = opts.getROI(); 132 int left = roi.left(); 133 int top = roi.top(); 134 int width = roi.width(); 135 int height = roi.height(); 136 137 int fullWidth = opts.getWidth(); 138 if (opts.getHFOV()<360) 139 { 140 fullWidth = static_cast<int>(opts.getWidth() * 360.0 / opts.getHFOV()); 141 left += (fullWidth - opts.getWidth()) / 2; 142 }; 143 int fullHeight = opts.getHeight(); 144 if (opts.getVFOV()<180) 145 { 146 fullHeight = static_cast<int>(opts.getHeight() * 180.0 / opts.getVFOV()); 147 top += (fullHeight - opts.getHeight()) / 2; 148 }; 149 outputFile << wxT("-UsePanoramaViewer=True") << endl; 150 outputFile << wxT("-StitchingSoftware=Hugin") << endl; 151 outputFile << wxT("-ProjectionType=equirectangular") << endl; 152 outputFile << wxT("-CroppedAreaLeftPixels=") << left << endl; 153 outputFile << wxT("-CroppedAreaTopPixels=") << top << endl; 154 outputFile << wxT("-CroppedAreaImageWidthPixels=") << width << endl; 155 outputFile << wxT("-CroppedAreaImageHeightPixels=") << height << endl; 156 outputFile << wxT("-FullPanoWidthPixels=") << fullWidth << endl; 157 outputFile << wxT("-FullPanoHeightPixels=") << fullHeight << endl; 158 outputFile << wxT("-SourcePhotosCount=") << static_cast<wxUint32>(pano.getNrOfImages()) << endl; 159 }; 160 }; 161 // now open the input file and append it 162 if (!argfileInput.IsEmpty()) 163 { 164 if (wxFileExists(argfileInput)) 165 { 166 wxFileInputStream inputFileStream(argfileInput); 167 wxTextInputStream input(inputFileStream); 168 while (inputFileStream.IsOk() && !inputFileStream.Eof()) 169 { 170 wxString line = input.ReadLine(); 171 // replace all placeholders 172 for (auto variable : placeholders) 173 { 174 line.Replace(variable.placeholder, variable.value, true); 175 }; 176 // now append to existing argfile 177 outputFile << line << endl; 178 }; 179 }; 180 }; 181 return tempArgfileFinal.GetFullPath(); 182 }; 183 PrintDetailInfo(const HuginBase::Panorama & pano,const HuginBase::PanoramaOptions & opts,const HuginBase::UIntSet & allActiveImages,const wxString & prefix,const wxString & bindir,wxConfigBase * config,double & exiftoolVersion)184 wxString PrintDetailInfo(const HuginBase::Panorama& pano, const HuginBase::PanoramaOptions& opts, const HuginBase::UIntSet& allActiveImages, const wxString& prefix, const wxString& bindir, wxConfigBase* config, double& exiftoolVersion) 185 { 186 wxString output; 187 const wxString wxEndl(wxT("\n")); 188 output 189 << wxT("============================================") << wxEndl 190 << _("Stitching panorama...") << wxEndl 191 << wxT("============================================") << wxEndl 192 << wxEndl 193 << _("Platform:") << wxT(" ") << wxGetOsDescription() << wxEndl 194 << _("Version:") << wxT(" ") << wxString(hugin_utils::GetHuginVersion().c_str(), wxConvLocal) << wxEndl 195 << _("Working directory:") << wxT(" ") << wxFileName::GetCwd() << wxEndl 196 << _("Output prefix:") << wxT(" ") << prefix << wxEndl 197 << wxEndl; 198 if (opts.outputLDRBlended || opts.outputLDRExposureBlended || opts.outputLDRExposureLayersFused || 199 opts.outputHDRBlended || opts.outputLDRExposureLayers) 200 { 201 switch (opts.blendMode) 202 { 203 case HuginBase::PanoramaOptions::ENBLEND_BLEND: 204 { 205 wxArrayString version; 206 if (wxExecute(wxEscapeFilename(GetExternalProgram(config, bindir, wxT("enblend"))) + wxT(" --version"), version, wxEXEC_SYNC) == 0l) 207 { 208 output << _("Blender:") << wxT(" ") << version[0] << wxEndl; 209 } 210 else 211 { 212 output << _("Blender:") << wxT(" ") << _("Unknown blender (enblend --version failed)") << wxEndl; 213 }; 214 }; 215 break; 216 case HuginBase::PanoramaOptions::INTERNAL_BLEND: 217 default: // switch to internal blender for all other cases, not exposed in GUI 218 output << _("Blender:") << wxT(" ") << _("internal") << wxEndl; 219 break; 220 }; 221 }; 222 if (opts.outputLDRExposureBlended || opts.outputLDRExposureLayersFused || opts.outputLDRStacks ) 223 { 224 wxArrayString version; 225 if (wxExecute(wxEscapeFilename(GetExternalProgram(config, bindir, wxT("enfuse"))) + wxT(" --version"), version, wxEXEC_SYNC) == 0l) 226 { 227 output << _("Exposure fusion:") << wxT(" ") << version[0] << wxEndl; 228 } 229 else 230 { 231 output << _("Exposure fusion:") << wxT(" ") << _("Unknown exposure fusion (enfuse --version failed)") << wxEndl; 232 }; 233 }; 234 if (config->Read(wxT("/output/useExiftool"), HUGIN_USE_EXIFTOOL) == 1l) 235 { 236 wxArrayString version; 237 if (wxExecute(wxEscapeFilename(GetExternalProgram(config, bindir, wxT("exiftool"))) + wxT(" -ver"), version, wxEXEC_SYNC) == 0l) 238 { 239 output << _("ExifTool version:") << wxT(" ") << version[0] << wxEndl; 240 version[0].ToCDouble(&exiftoolVersion); 241 } 242 else 243 { 244 output << _("ExifTool:") << wxT(" ") << _("FAILED") << wxEndl; 245 exiftoolVersion = 1; 246 }; 247 }; 248 output 249 << wxEndl 250 << _("Number of active images:") << wxT(" ") << allActiveImages.size() << wxEndl 251 << wxString::Format(_("Output exposure value: %.1f"), opts.outputExposureValue) << wxEndl 252 << wxString::Format(_("Canvas size: %dx%d"), opts.getSize().width(), opts.getSize().height()) << wxEndl 253 << wxString::Format(_("ROI: (%d, %d) - (%d, %d)"), opts.getROI().left(), opts.getROI().top(), opts.getROI().right(), opts.getROI().bottom()) << wxT(" ") << wxEndl 254 << wxString::Format(_("FOV: %.0fx%.0f"), opts.getHFOV(), opts.getVFOV()) << wxEndl; 255 pano_projection_features proj; 256 const bool readProjectionName = panoProjectionFeaturesQuery(opts.getProjection(), &proj) != 0; 257 if (readProjectionName) 258 { 259 output 260 << _("Projection:") << wxT(" ") << wxGetTranslation(wxString(proj.name, wxConvLocal)) 261 << wxT("(") << opts.getProjection() << wxT(")") << wxEndl; 262 } 263 else 264 { 265 output 266 << _("Projection:") << wxT(" ") << opts.getProjection() << wxEndl; 267 }; 268 output 269 << _("Using GPU for remapping:") << wxT(" ") << (opts.remapUsingGPU ? _("true") : _("false")) << wxEndl 270 << wxEndl; 271 if (opts.outputLDRBlended || opts.outputLDRExposureBlended || opts.outputLDRExposureLayersFused || opts.outputHDRBlended) 272 { 273 output << _("Panorama Outputs:") << wxEndl; 274 if (opts.outputLDRBlended) 275 { 276 output << wxT("* ") << _("Exposure corrected, low dynamic range") << wxEndl; 277 }; 278 if (opts.outputLDRExposureBlended) 279 { 280 output << wxT("* ") << _("Exposure fused from stacks") << wxEndl; 281 }; 282 if (opts.outputLDRExposureLayersFused) 283 { 284 output << wxT("* ") << _("Exposure fused from any arrangement") << wxEndl; 285 }; 286 if (opts.outputHDRBlended) 287 { 288 output << wxT("* ") << _("High dynamic range") << wxEndl; 289 }; 290 output << wxEndl; 291 }; 292 if (opts.outputLDRLayers || opts.outputLDRExposureRemapped || opts.outputHDRLayers) 293 { 294 output << _("Remapped Images:") << wxEndl; 295 if (opts.outputLDRBlended) 296 { 297 output << wxT("* ") << _("Exposure corrected, low dynamic range") << wxEndl; 298 }; 299 if (opts.outputLDRExposureRemapped) 300 { 301 output << wxT("* ") << _("No exposure correction, low dynamic range") << wxEndl; 302 }; 303 if (opts.outputHDRLayers) 304 { 305 output << wxT("* ") << _("High dynamic range") << wxEndl; 306 }; 307 output << wxEndl; 308 }; 309 if (opts.outputLDRStacks || opts.outputHDRStacks) 310 { 311 output << _("Combined stacks:") << wxEndl; 312 if (opts.outputLDRStacks) 313 { 314 output << wxT("* ") << _("Exposure fused stacks") << wxEndl; 315 }; 316 if (opts.outputHDRStacks) 317 { 318 output << wxT("* ") << _("High dynamic range") << wxEndl; 319 }; 320 output << wxEndl; 321 }; 322 if (opts.outputLDRExposureLayers) 323 { 324 output << _("Layers:") << wxEndl 325 << wxT("* ") << _("Blended layers of similar exposure, without exposure correction") << wxEndl 326 << wxEndl; 327 }; 328 const HuginBase::SrcPanoImage img = pano.getImage(*allActiveImages.begin()); 329 output << _("First input image") << wxEndl 330 << _("Number:") << wxT(" ") << *allActiveImages.begin() << wxEndl 331 << _("Filename:") << wxT(" ") << img.getFilename() << wxEndl 332 << wxString::Format(_("Size: %dx%d"), img.getWidth(), img.getHeight()) << wxEndl 333 << _("Projection:") << wxT(" ") << getProjectionString(img) << wxEndl 334 << _("Response type:") << wxT(" ") << getResponseString(img) << wxEndl 335 << wxString::Format(_("HFOV: %.0f"), img.getHFOV()) << wxEndl 336 << wxString::Format(_("Exposure value: %.1f"), img.getExposureValue()) << wxEndl 337 << wxEndl; 338 return output; 339 }; 340 341 /** build quoted filename list for verdandi */ GetQuotedFilenamesStringForVerdandi(const wxArrayString & files,const HuginBase::Panorama & pano,const HuginBase::UIntSetVector & stacks,const int referenceImage,const bool hardSeam)342 wxString GetQuotedFilenamesStringForVerdandi(const wxArrayString& files, const HuginBase::Panorama& pano, const HuginBase::UIntSetVector& stacks, const int referenceImage, const bool hardSeam) 343 { 344 // if output is hard seam we keep the order 345 if (hardSeam) 346 { 347 return GetQuotedFilenamesString(files); 348 }; 349 // user wants a blended seam, we need to figure out the correct order 350 int refImage = 0; 351 // first build a subpano which contains only one image per stack of the original pano 352 HuginBase::UIntSet stackImgs; 353 for (size_t i = 0; i < stacks.size(); ++i) 354 { 355 if (set_contains(stacks[i], referenceImage)) 356 { 357 refImage = i; 358 }; 359 stackImgs.insert(*(stacks[i].begin())); 360 }; 361 // now create the subpano, don't forget to delete at end 362 HuginBase::PanoramaData* subpano = pano.getNewSubset(stackImgs); 363 HuginBase::UIntSet subpanoImgs; 364 fill_set(subpanoImgs, 0, stackImgs.size() - 1); 365 // find the blend order 366 HuginBase::UIntVector blendOrder = HuginBase::getEstimatedBlendingOrder(*subpano, subpanoImgs, refImage); 367 delete subpano; 368 // now build the string in the correct order 369 wxString s; 370 for (size_t i = 0; i < blendOrder.size();++i) 371 { 372 s.Append(wxEscapeFilename(files[blendOrder[i]]) + wxT(" ")); 373 }; 374 return s; 375 }; 376 } // namespace detail 377 GetStitchingCommandQueue(const HuginBase::Panorama & pano,const wxString & ExePath,const wxString & project,const wxString & prefix,wxString & statusText,wxArrayString & outputFiles,wxArrayString & tempFilesDelete,std::ostream & errStream)378 CommandQueue* GetStitchingCommandQueue(const HuginBase::Panorama & pano, const wxString& ExePath, const wxString& project, const wxString& prefix, wxString& statusText, wxArrayString& outputFiles, wxArrayString& tempFilesDelete, std::ostream& errStream) 379 { 380 CommandQueue* commands = new CommandQueue; 381 const HuginBase::UIntSet allActiveImages = getImagesinROI(pano, pano.getActiveImages()); 382 if (allActiveImages.empty()) 383 { 384 errStream << "ERROR: No active images in ROI. Nothing to do." << std::endl; 385 return commands; 386 } 387 std::vector<HuginBase::UIntSet> stacks; 388 389 // check options, not all are currently supported 390 HuginBase::PanoramaOptions opts = pano.getOptions(); 391 wxConfigBase* config = wxConfigBase::Get(); 392 opts.remapUsingGPU = config->Read(wxT("/Nona/UseGPU"), HUGIN_NONA_USEGPU) == 1; 393 if (opts.remapper != HuginBase::PanoramaOptions::NONA) 394 { 395 errStream << "ERROR: Only nona remappper is supported by hugin_executor." << std::endl; 396 return commands; 397 }; 398 if (opts.blendMode != HuginBase::PanoramaOptions::ENBLEND_BLEND && opts.blendMode != HuginBase::PanoramaOptions::INTERNAL_BLEND) 399 { 400 errStream << "ERROR: Only enblend and internal remappper are currently supported by hugin_executor." << std::endl; 401 return commands; 402 }; 403 if (opts.hdrMergeMode != HuginBase::PanoramaOptions::HDRMERGE_AVERAGE) 404 { 405 errStream << "ERROR: Only hdr merger HDRMERGE_AVERAGE is currently supported by hugin_executor." << std::endl; 406 return commands; 407 }; 408 double exiftoolVersion; 409 statusText=detail::PrintDetailInfo(pano, opts, allActiveImages, prefix, ExePath, config, exiftoolVersion); 410 // prepare some often needed variables 411 const wxString quotedProject(wxEscapeFilename(project)); 412 // prepare nona arguments 413 wxString nonaArgs(wxT("-v ")); 414 wxString enLayersCompressionArgs; 415 if (!opts.outputLayersCompression.empty()) 416 { 417 nonaArgs.Append(wxT("-z ") + opts.outputLayersCompression + wxT(" ")); 418 enLayersCompressionArgs.Append(wxT(" --compression=") + opts.outputLayersCompression + wxT(" ")); 419 } 420 else 421 { 422 if (opts.outputImageType == "jpg") 423 { 424 nonaArgs.Append(wxT("-z LZW ")); 425 } 426 }; 427 if (opts.remapUsingGPU) 428 { 429 nonaArgs.Append(wxT("-g ")); 430 }; 431 // prepare enblend arguments 432 wxString enblendArgs; 433 if (opts.blendMode == HuginBase::PanoramaOptions::ENBLEND_BLEND) 434 { 435 enblendArgs.Append(opts.enblendOptions); 436 if ((opts.getHFOV() == 360.0) && (opts.getWidth()==opts.getROI().width())) 437 { 438 enblendArgs.Append(wxT(" -w")); 439 }; 440 const vigra::Rect2D roi (opts.getROI()); 441 if (roi.top() != 0 || roi.left() != 0) 442 { 443 enblendArgs << wxT(" -f") << roi.width() << wxT("x") << roi.height() << wxT("+") << roi.left() << wxT("+") << roi.top(); 444 } 445 else 446 { 447 enblendArgs << wxT(" -f") << roi.width() << wxT("x") << roi.height(); 448 }; 449 enblendArgs.Append(wxT(" ")); 450 }; 451 // prepare internal blending arguments 452 wxString verdandiArgs; 453 if (opts.blendMode == HuginBase::PanoramaOptions::INTERNAL_BLEND) 454 { 455 verdandiArgs.Append(opts.verdandiOptions); 456 if ((opts.getHFOV() == 360.0) && (opts.getWidth() == opts.getROI().width())) 457 { 458 verdandiArgs.Append(wxT(" -w")); 459 }; 460 }; 461 // prepare the compression switches 462 wxString finalCompressionArgs; 463 if (opts.outputImageType == "tif" && !opts.outputImageTypeCompression.empty()) 464 { 465 finalCompressionArgs << wxT(" --compression=") << opts.outputImageTypeCompression; 466 } 467 else 468 { 469 if (opts.outputImageType == "jpg") 470 { 471 finalCompressionArgs << wxT(" --compression=") << opts.quality; 472 }; 473 }; 474 finalCompressionArgs.Append(wxT(" ")); 475 // prepare enfuse arguments 476 wxString enfuseArgs(opts.enfuseOptions + wxT(" ")); 477 if ((opts.getHFOV() == 360.0) && (opts.getWidth() == opts.getROI().width())) 478 { 479 enfuseArgs.Append(wxT(" -w")); 480 }; 481 const vigra::Rect2D roi (opts.getROI()); 482 if (roi.top() != 0 || roi.left() != 0) 483 { 484 enfuseArgs << wxT(" -f") << roi.width() << wxT("x") << roi.height() << wxT("+") << roi.left() << wxT("+") << roi.top(); 485 } 486 else 487 { 488 enfuseArgs << wxT(" -f") << roi.width() << wxT("x") << roi.height(); 489 }; 490 enfuseArgs.Append(wxT(" ")); 491 492 // prepare exiftool args 493 const bool copyMetadata = config->Read(wxT("/output/useExiftool"), HUGIN_USE_EXIFTOOL) == 1l; 494 wxString exiftoolArgs; 495 wxString exiftoolArgsFinal; 496 if (copyMetadata) 497 { 498 exiftoolArgs = wxT("-overwrite_original -TagsFromFile "); 499 exiftoolArgs.Append(wxEscapeFilename(wxString(pano.getImage(0).getFilename().c_str(), HUGIN_CONV_FILENAME))); 500 // required tags, can not be overwritten 501 exiftoolArgs.Append(wxT(" -WhitePoint -ColorSpace")); 502 wxString exiftoolArgfile = config->Read(wxT("/output/CopyArgfile"), wxEmptyString); 503 if (exiftoolArgfile.IsEmpty()) 504 { 505 exiftoolArgfile = wxString(std::string(hugin_utils::GetDataDir() + "hugin_exiftool_copy.arg").c_str(), HUGIN_CONV_FILENAME); 506 }; 507 wxFileName argfile(exiftoolArgfile); 508 argfile.Normalize(); 509 exiftoolArgs.Append(wxT(" -@ ") + wxEscapeFilename(argfile.GetFullPath()) + wxT(" ")); 510 wxString finalArgfile = detail::GenerateFinalArgfile(pano, project, config, allActiveImages, exiftoolVersion); 511 if (!finalArgfile.IsEmpty()) 512 { 513 exiftoolArgsFinal.Append(wxT(" -@ ") + wxEscapeFilename(finalArgfile) + wxT(" ")); 514 tempFilesDelete.Add(finalArgfile); 515 }; 516 }; 517 wxArrayString filesForFullExiftool; 518 wxArrayString filesForCopyTagsExiftool; 519 520 // normal output 521 if (opts.outputLDRBlended || opts.outputLDRLayers) 522 { 523 const wxArrayString remappedImages(detail::GetNumberedFilename(prefix, wxT(".tif"), allActiveImages)); 524 const wxString finalFilename(prefix + wxT(".") + opts.outputImageType); 525 if (opts.blendMode == HuginBase::PanoramaOptions::INTERNAL_BLEND && opts.outputLDRBlended) 526 { 527 wxString finalNonaArgs(wxT("-v -r ldr ")); 528 if (opts.remapUsingGPU) 529 { 530 finalNonaArgs.Append(wxT("-g ")); 531 } 532 if (!opts.verdandiOptions.empty()) 533 { 534 finalNonaArgs.Append(opts.verdandiOptions); 535 finalNonaArgs.Append(wxT(" ")); 536 }; 537 if (opts.outputImageType == "tif") 538 { 539 finalNonaArgs.Append(wxT("-m TIFF ")); 540 if (!opts.outputImageTypeCompression.empty()) 541 { 542 finalNonaArgs.Append(wxT("-z ") + opts.outputImageTypeCompression + wxT(" ")); 543 }; 544 } 545 else 546 { 547 if (opts.outputImageType == "jpg") 548 { 549 finalNonaArgs.Append(wxT("-m JPEG -z ")); 550 finalNonaArgs << opts.quality << wxT(" "); 551 } 552 else 553 { 554 if (opts.outputImageType == "png") 555 { 556 finalNonaArgs.Append(wxT("-m PNG ")); 557 } 558 else 559 { 560 errStream << "ERROR: Invalid output image type found." << std::endl; 561 return commands; 562 }; 563 }; 564 }; 565 if (opts.outputLDRLayers) 566 { 567 finalNonaArgs.Append(wxT("--save-intermediate-images ")); 568 detail::AddToArray(remappedImages, outputFiles); 569 } 570 finalNonaArgs.Append(wxT("-o ") + wxEscapeFilename(prefix) + wxT(" ") + quotedProject); 571 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("nona")), 572 finalNonaArgs, _("Remapping and blending LDR images..."))); 573 outputFiles.Add(finalFilename); 574 if (copyMetadata) 575 { 576 filesForFullExiftool.Add(finalFilename); 577 }; 578 } 579 else 580 { 581 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("nona")), 582 nonaArgs + wxT("-r ldr -m TIFF_m -o ") + wxEscapeFilename(prefix) + wxT(" ") + quotedProject, 583 _("Remapping LDR images..."))); 584 detail::AddToArray(remappedImages, outputFiles); 585 if (opts.outputLDRBlended) 586 { 587 if (opts.blendMode == HuginBase::PanoramaOptions::ENBLEND_BLEND) 588 { 589 wxString finalEnblendArgs(enblendArgs + finalCompressionArgs); 590 finalEnblendArgs.Append(wxT(" -o ") + wxEscapeFilename(finalFilename) + wxT(" -- ")); 591 commands->push_back(new NormalCommand( 592 GetExternalProgram(config, ExePath, wxT("enblend")), 593 finalEnblendArgs + wxT(" ") + GetQuotedFilenamesString(remappedImages), 594 _("Blending images...")) 595 ); 596 outputFiles.Add(finalFilename); 597 if (copyMetadata) 598 { 599 filesForFullExiftool.Add(finalFilename); 600 }; 601 }; 602 if (!opts.outputLDRLayers) 603 { 604 detail::AddToArray(remappedImages, tempFilesDelete); 605 }; 606 }; 607 }; 608 }; 609 // exposure fusion output 610 if (opts.outputLDRExposureRemapped || opts.outputLDRStacks || opts.outputLDRExposureLayers || 611 opts.outputLDRExposureBlended || opts.outputLDRExposureLayersFused) 612 { 613 const wxArrayString remappedImages = detail::GetNumberedFilename(prefix + wxT("_exposure_layers_"), wxT(".tif"), allActiveImages); 614 std::vector<HuginBase::UIntSet> exposureLayers; 615 wxArrayString exposureLayersFiles; 616 if (opts.outputLDRExposureLayers || opts.outputLDRExposureLayersFused) 617 { 618 exposureLayers = getExposureLayers(pano, allActiveImages, opts); 619 }; 620 if (opts.blendMode == HuginBase::PanoramaOptions::INTERNAL_BLEND && 621 (opts.outputLDRExposureLayers || opts.outputLDRExposureLayersFused)) 622 { 623 // directly export exposure layers by nona 624 wxString finalNonaArgs(nonaArgs); 625 if (!opts.verdandiOptions.empty()) 626 { 627 finalNonaArgs.Append(opts.verdandiOptions); 628 finalNonaArgs.Append(wxT(" ")); 629 }; 630 finalNonaArgs.append(wxT("-r ldr --create-exposure-layers --ignore-exposure -o ") + wxEscapeFilename(prefix + wxT("_exposure_"))); 631 if (opts.outputLDRExposureRemapped || opts.outputLDRStacks || opts.outputLDRExposureBlended) 632 { 633 finalNonaArgs.append(wxT(" --save-intermediate-images --intermediate-suffix=layers_")); 634 detail::AddToArray(remappedImages, outputFiles); 635 if (!opts.outputLDRExposureRemapped) 636 { 637 detail::AddToArray(remappedImages, tempFilesDelete); 638 } 639 }; 640 finalNonaArgs.append(wxT(" ")); 641 finalNonaArgs.append(quotedProject); 642 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("nona")), 643 finalNonaArgs, _("Remapping LDR images and blending exposure layers..."))); 644 HuginBase::UIntSet exposureLayersNumber; 645 fill_set(exposureLayersNumber, 0, exposureLayers.size() - 1); 646 exposureLayersFiles = detail::GetNumberedFilename(prefix + wxT("_exposure_"), wxT(".tif"), exposureLayersNumber); 647 detail::AddToArray(exposureLayersFiles, outputFiles); 648 if (!opts.outputLDRExposureLayers) 649 { 650 detail::AddToArray(exposureLayersFiles, tempFilesDelete); 651 }; 652 if (copyMetadata && opts.outputLDRExposureLayers) 653 { 654 detail::AddToArray(exposureLayersFiles, filesForCopyTagsExiftool); 655 }; 656 } 657 else 658 { 659 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("nona")), 660 nonaArgs + wxT("-r ldr -m TIFF_m --ignore-exposure -o ") + wxEscapeFilename(prefix + wxT("_exposure_layers_")) + wxT(" ") + quotedProject, 661 _("Remapping LDR images without exposure correction..."))); 662 detail::AddToArray(remappedImages, outputFiles); 663 if (!opts.outputLDRExposureRemapped) 664 { 665 detail::AddToArray(remappedImages, tempFilesDelete); 666 }; 667 if (opts.outputLDRExposureLayers || opts.outputLDRExposureLayersFused) 668 { 669 // blending exposure layers, then fusing 670 // fuse all exposure layers 671 for (unsigned exposureLayer = 0; exposureLayer < exposureLayers.size(); ++exposureLayer) 672 { 673 const wxArrayString exposureLayersImgs = detail::GetNumberedFilename(prefix + wxT("_exposure_layers_"), wxT(".tif"), exposureLayers[exposureLayer]); 674 const wxString exposureLayerImgName = wxString::Format(wxT("%s_exposure_%04u%s"), prefix.c_str(), exposureLayer, wxT(".tif")); 675 exposureLayersFiles.Add(exposureLayerImgName); 676 outputFiles.Add(exposureLayerImgName); 677 // variant with internal blender is handled before, so we need only enblend 678 commands->push_back(new NormalCommand( 679 GetExternalProgram(config, ExePath, wxT("enblend")), 680 enblendArgs + enLayersCompressionArgs + wxT(" -o ") + wxEscapeFilename(exposureLayerImgName) + wxT(" -- ") + GetQuotedFilenamesString(exposureLayersImgs), 681 wxString::Format(_("Blending exposure layer %u..."), exposureLayer)) 682 ); 683 if (copyMetadata && opts.outputLDRExposureLayers) 684 { 685 filesForCopyTagsExiftool.Add(exposureLayerImgName); 686 }; 687 if (!opts.outputLDRExposureLayers) 688 { 689 tempFilesDelete.Add(exposureLayerImgName); 690 }; 691 }; 692 }; 693 }; 694 if (opts.outputLDRExposureLayersFused) 695 { 696 wxString finalEnfuseArgs(enfuseArgs + finalCompressionArgs); 697 const wxString fusedExposureLayersFilename(prefix + wxT("_blended_fused.") + opts.outputImageType); 698 finalEnfuseArgs.Append(wxT(" -o ") + wxEscapeFilename(fusedExposureLayersFilename) + wxT(" -- ")); 699 commands->push_back(new NormalCommand( 700 GetExternalProgram(config, ExePath, wxT("enfuse")), 701 finalEnfuseArgs + wxT(" ")+GetQuotedFilenamesString(exposureLayersFiles), 702 _("Fusing all exposure layers...")) 703 ); 704 outputFiles.Add(fusedExposureLayersFilename); 705 if (copyMetadata) 706 { 707 filesForFullExiftool.Add(fusedExposureLayersFilename); 708 }; 709 }; 710 if (opts.outputLDRStacks || opts.outputLDRExposureBlended) 711 { 712 // fusing stacks, then blending 713 stacks = getHDRStacks(pano, allActiveImages, opts); 714 wxArrayString stackedImages; 715 // fuse all stacks 716 for (unsigned stackNr = 0; stackNr < stacks.size(); ++stackNr) 717 { 718 const wxArrayString stackImgs = detail::GetNumberedFilename(prefix + wxT("_exposure_layers_"), wxT(".tif"), stacks[stackNr]); 719 const wxString stackImgName = wxString::Format(wxT("%s_stack_ldr_%04u%s"), prefix.c_str(), stackNr, wxT(".tif")); 720 outputFiles.Add(stackImgName); 721 stackedImages.Add(stackImgName); 722 commands->push_back(new NormalCommand( 723 GetExternalProgram(config, ExePath, wxT("enfuse")), 724 enfuseArgs + enLayersCompressionArgs + wxT(" -o ") + wxEscapeFilename(stackImgName) + wxT(" -- ") + GetQuotedFilenamesString(stackImgs), 725 wxString::Format(_("Fusing stack number %u..."), stackNr)) 726 ); 727 if (copyMetadata && opts.outputLDRStacks) 728 { 729 filesForCopyTagsExiftool.Add(stackImgName); 730 }; 731 if (!opts.outputLDRStacks) 732 { 733 tempFilesDelete.Add(stackImgName); 734 }; 735 }; 736 if (opts.outputLDRExposureBlended) 737 { 738 const wxString fusedStacksFilename(prefix + wxT("_fused.") + opts.outputImageType); 739 switch (opts.blendMode) 740 { 741 case HuginBase::PanoramaOptions::ENBLEND_BLEND: 742 { 743 wxString finalEnblendArgs(enblendArgs + finalCompressionArgs); 744 finalEnblendArgs.Append(wxT(" -o ") + wxEscapeFilename(fusedStacksFilename) + wxT(" -- ")); 745 commands->push_back(new NormalCommand( 746 GetExternalProgram(config, ExePath, wxT("enblend")), 747 finalEnblendArgs+wxT(" ")+GetQuotedFilenamesString(stackedImages), 748 _("Blending all stacks...")) 749 ); 750 }; 751 break; 752 case HuginBase::PanoramaOptions::INTERNAL_BLEND: 753 default: // switch to internal blender for all other cases, not exposed in GUI 754 { 755 wxString finalVerdandiArgs(verdandiArgs + finalCompressionArgs); 756 finalVerdandiArgs.Append(wxT(" -o ") + wxEscapeFilename(fusedStacksFilename)); 757 finalVerdandiArgs.Append(wxT(" -- ") + detail::GetQuotedFilenamesStringForVerdandi(stackedImages, pano, stacks, opts.colorReferenceImage, opts.verdandiOptions.find("--seam=blend") == std::string::npos)); 758 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("verdandi")), 759 finalVerdandiArgs, _("Blending all stacks..."))); 760 }; 761 break; 762 }; 763 outputFiles.Add(fusedStacksFilename); 764 if (copyMetadata) 765 { 766 filesForFullExiftool.Add(fusedStacksFilename); 767 }; 768 }; 769 }; 770 }; 771 // hdr output 772 if (opts.outputHDRLayers || opts.outputHDRStacks || opts.outputHDRBlended) 773 { 774 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("nona")), 775 nonaArgs + wxT("-r hdr -m EXR_m -o ") + wxEscapeFilename(prefix + wxT("_hdr_")) + wxT(" ") + quotedProject, 776 _("Remapping HDR images..."))); 777 const wxArrayString remappedHDR = detail::GetNumberedFilename(prefix + wxT("_hdr_"), wxT(".exr"), allActiveImages); 778 const wxArrayString remappedHDRComp = detail::GetNumberedFilename(prefix + wxT("_hdr_"), wxT("_gray.pgm"), allActiveImages); 779 detail::AddToArray(remappedHDR, outputFiles); 780 detail::AddToArray(remappedHDRComp, outputFiles); 781 if (opts.outputHDRStacks || opts.outputHDRBlended) 782 { 783 // merging stacks, then blending 784 if (stacks.empty()) 785 { 786 stacks = getHDRStacks(pano, allActiveImages, opts); 787 }; 788 wxArrayString stackedImages; 789 // merge all stacks 790 for (unsigned stackNr = 0; stackNr < stacks.size(); ++stackNr) 791 { 792 const wxArrayString stackImgs = detail::GetNumberedFilename(prefix + wxT("_hdr_"), wxT(".exr"), stacks[stackNr]); 793 const wxString stackImgName = wxString::Format(wxT("%s_stack_hdr_%04u%s"), prefix.c_str(), stackNr, wxT(".exr")); 794 stackedImages.Add(stackImgName); 795 outputFiles.Add(stackImgName); 796 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("hugin_hdrmerge")), 797 opts.hdrmergeOptions + wxT(" -o ") + wxEscapeFilename(stackImgName) + wxT(" -- ") + GetQuotedFilenamesString(stackImgs), 798 wxString::Format(_("Merging HDR stack number %u..."), stackNr))); 799 if (!opts.outputHDRStacks) 800 { 801 tempFilesDelete.Add(stackImgName); 802 }; 803 }; 804 if (opts.outputHDRBlended) 805 { 806 const wxString mergedStacksFilename(prefix + wxT("_hdr.") + opts.outputImageTypeHDR); 807 wxString finalBlendArgs(wxT(" -o ") + wxEscapeFilename(mergedStacksFilename) + wxT(" -- ")); 808 switch (opts.blendMode) 809 { 810 case HuginBase::PanoramaOptions::ENBLEND_BLEND: 811 commands->push_back(new NormalCommand( 812 GetExternalProgram(config, ExePath, wxT("enblend")), 813 enblendArgs + finalBlendArgs + wxT(" ") + GetQuotedFilenamesString(stackedImages), 814 _("Blending HDR stacks...")) 815 ); 816 break; 817 case HuginBase::PanoramaOptions::INTERNAL_BLEND: 818 default: // switch to internal blender for all other cases, not exposed in GUI 819 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("verdandi")), 820 verdandiArgs + finalBlendArgs + detail::GetQuotedFilenamesStringForVerdandi(stackedImages, pano, stacks, opts.colorReferenceImage, opts.verdandiOptions.find("--seam=blend") == std::string::npos), 821 _("Blending HDR stacks..."))); 822 break; 823 }; 824 outputFiles.Add(mergedStacksFilename); 825 if (copyMetadata) 826 { 827 filesForFullExiftool.Add(mergedStacksFilename); 828 }; 829 }; 830 }; 831 if (!opts.outputHDRLayers) 832 { 833 detail::AddToArray(remappedHDR, tempFilesDelete); 834 detail::AddToArray(remappedHDRComp, tempFilesDelete); 835 }; 836 }; 837 // update metadata 838 if (!filesForCopyTagsExiftool.IsEmpty()) 839 { 840 commands->push_back(new OptionalCommand(GetExternalProgram(config, ExePath, wxT("exiftool")), 841 exiftoolArgs + GetQuotedFilenamesString(filesForCopyTagsExiftool), 842 _("Updating metadata..."))); 843 }; 844 if (!filesForFullExiftool.IsEmpty()) 845 { 846 commands->push_back(new OptionalCommand(GetExternalProgram(config, ExePath, wxT("exiftool")), 847 exiftoolArgs + exiftoolArgsFinal + GetQuotedFilenamesString(filesForFullExiftool), 848 _("Updating metadata..."))); 849 }; 850 return commands; 851 }; 852 853 // now the user defined stitching engine 854 // we read the settings from an ini file and construct our CommandQueue 855 namespace detail 856 { 857 // add program with argment to queue 858 // do some checks on the way 859 // if an error occurs or the input is not valid, the queue is cleared and the function returns false AddBlenderCommand(CommandQueue * queue,const wxString & ExePath,const wxString & prog,const int & stepNr,const wxString & arguments,const wxString & description,std::ostream & errStream)860 bool AddBlenderCommand(CommandQueue* queue, const wxString& ExePath, const wxString& prog, 861 const int& stepNr, const wxString& arguments, const wxString& description, std::ostream& errStream) 862 { 863 if (prog.IsEmpty()) 864 { 865 errStream << "ERROR: Step " << stepNr << " has no program name specified." << std::endl; 866 CleanQueue(queue); 867 return false; 868 } 869 // check program name 870 // get full path for some internal commands 871 wxString program; 872 if (prog.CmpNoCase(wxT("verdandi")) == 0) 873 { 874 program = GetInternalProgram(ExePath, wxT("verdandi")); 875 } 876 else 877 { 878 if (prog.CmpNoCase(wxT("hugin_hdrmerge")) == 0) 879 { 880 program = GetInternalProgram(ExePath, wxT("hugin_hdrmerge")); 881 } 882 else 883 { 884 program = prog; 885 } 886 }; 887 // now add to queue 888 queue->push_back(new NormalCommand(program, arguments, description)); 889 return true; 890 }; 891 892 // replace the %prefix% placeholder, with optional postfix 893 // the string is modified in place 894 // return true on success, false if there are errors ReplacePrefixPlaceholder(wxString & args,const wxString prefix)895 bool ReplacePrefixPlaceholder(wxString& args, const wxString prefix) 896 { 897 int prefixPos = args.Find("%prefix"); 898 while (prefixPos != wxNOT_FOUND) 899 { 900 const wxString nextChar = args.Mid(prefixPos + 7, 1); 901 if (nextChar == "%") 902 { 903 args.Replace("%prefix%", wxEscapeFilename(prefix), true); 904 } 905 else 906 { 907 if (nextChar == ",") 908 { 909 const int closingPercent = args.Mid(prefixPos + 8).Find("%"); 910 if (closingPercent < 2) 911 { 912 return false; 913 }; 914 wxString postfix = args.Mid(prefixPos + 8, closingPercent); 915 args.Replace("%prefix," + postfix + "%", wxEscapeFilename(prefix + postfix), true); 916 } 917 else 918 { 919 return false; 920 }; 921 }; 922 prefixPos = args.Find("%prefix"); 923 }; 924 return true; 925 }; 926 927 // replace the %with% or %height% placeholder ReplaceWidthHeightPlaceHolder(wxString & args,const wxString name,int value)928 bool ReplaceWidthHeightPlaceHolder(wxString& args, const wxString name, int value) 929 { 930 int pos = args.Find("%" + name); 931 while (pos != wxNOT_FOUND) 932 { 933 const wxString nextChar = args.Mid(pos + 1 + name.Len(), 1); 934 if (nextChar == "%") 935 { 936 args.Replace("%" + name + "%", wxString::Format("%d", value), true); 937 } 938 else 939 { 940 if (nextChar == "*") 941 { 942 const int closingPercent = args.Mid(pos + 2 + name.Len()).Find("%"); 943 if (closingPercent < 2) 944 { 945 return false; 946 }; 947 wxString factorString = args.Mid(pos + 2 + name.Len(), closingPercent); 948 double factor; 949 if (!factorString.ToCDouble(&factor)) 950 { 951 return false; 952 }; 953 args.Replace("%" + name + "*" + factorString + "%", wxString::Format("%d", hugin_utils::roundi(factor*value)), true); 954 } 955 else 956 { 957 return false; 958 }; 959 }; 960 pos = args.Find("%" + name); 961 }; 962 return true; 963 }; 964 965 } 966 GetStitchingCommandQueueUserOutput(const HuginBase::Panorama & pano,const wxString & ExePath,const wxString & project,const wxString & prefix,const wxString & outputSettings,wxString & statusText,wxArrayString & outputFiles,wxArrayString & tempFilesDelete,std::ostream & errStream)967 CommandQueue* GetStitchingCommandQueueUserOutput(const HuginBase::Panorama & pano, const wxString& ExePath, const wxString& project, const wxString& prefix, const wxString& outputSettings, wxString& statusText, wxArrayString& outputFiles, wxArrayString& tempFilesDelete, std::ostream& errStream) 968 { 969 CommandQueue* commands = new CommandQueue; 970 const HuginBase::UIntSet allActiveImages = getImagesinROI(pano, pano.getActiveImages()); 971 if (allActiveImages.empty()) 972 { 973 errStream << "ERROR: No active images in ROI. Nothing to do." << std::endl; 974 return commands; 975 } 976 wxFileInputStream input(outputSettings); 977 if (!input.IsOk()) 978 { 979 errStream << "ERROR: Can not open file \"" << outputSettings.mb_str(wxConvLocal) << "\"." << std::endl; 980 return commands; 981 } 982 wxFileConfig settings(input); 983 long stepCount; 984 settings.Read(wxT("/General/StepCount"), &stepCount, 0); 985 if (stepCount == 0) 986 { 987 errStream << "ERROR: User-setting does not define any output steps." << std::endl; 988 return commands; 989 } 990 const wxString desc = GetSettingStringTranslated(&settings, wxT("/General/Description"), wxEmptyString); 991 if (desc.IsEmpty()) 992 { 993 statusText = wxString::Format(_("Stitching using \"%s\""), outputSettings.c_str()); 994 } 995 else 996 { 997 statusText = wxString::Format(_("Stitching using \"%s\""), desc.c_str()); 998 }; 999 wxString intermediateImageType = GetSettingString(&settings, wxT("/General/IntermediateImageType"), wxT(".tif")); 1000 // add point if missing 1001 if (intermediateImageType.Left(1).Cmp(wxT("."))!=0) 1002 { 1003 intermediateImageType.Prepend(wxT(".")); 1004 } 1005 // prepare some often needed variables/strings 1006 const HuginBase::PanoramaOptions opts = pano.getOptions(); 1007 const bool needsWrapSwitch = (opts.getHFOV() == 360.0) && (opts.getWidth() == opts.getROI().width()); 1008 const vigra::Rect2D roi(opts.getROI()); 1009 wxString sizeString; 1010 if (roi.top() != 0 || roi.left() != 0) 1011 { 1012 sizeString << roi.width() << wxT("x") << roi.height() << wxT("+") << roi.left() << wxT("+") << roi.top(); 1013 } 1014 else 1015 { 1016 sizeString << roi.width() << wxT("x") << roi.height(); 1017 }; 1018 const wxArrayString remappedImages(detail::GetNumberedFilename(prefix, intermediateImageType, allActiveImages)); 1019 wxArrayString inputImages; 1020 for (auto& i : allActiveImages) 1021 { 1022 inputImages.Add(wxString(pano.getImage(i).getFilename().c_str(), HUGIN_CONV_FILENAME)); 1023 }; 1024 1025 std::vector<HuginBase::UIntSet> exposureLayers; 1026 wxArrayString exposureLayersFiles; 1027 1028 std::vector<HuginBase::UIntSet> stacks; 1029 wxArrayString stacksFiles; 1030 1031 // now iterate all steps 1032 for (size_t i = 0; i < stepCount; ++i) 1033 { 1034 wxString stepString(wxT("/Step")); 1035 stepString << i; 1036 if (!settings.HasGroup(stepString)) 1037 { 1038 errStream << "ERROR: Output specifies " << stepCount << " steps, but step " << i << " is missing in configuration." << std::endl; 1039 CleanQueue(commands); 1040 return commands; 1041 } 1042 settings.SetPath(stepString); 1043 const wxString stepType=GetSettingString(&settings, wxT("Type")); 1044 if (stepType.IsEmpty()) 1045 { 1046 errStream << "ERROR: \"" << stepString.mb_str(wxConvLocal) << "\" has no type defined." << std::endl; 1047 CleanQueue(commands); 1048 return commands; 1049 }; 1050 wxString args = GetSettingString(&settings, wxT("Arguments")); 1051 if (args.IsEmpty()) 1052 { 1053 errStream << "ERROR: Step " << i << " has no arguments given." << std::endl; 1054 CleanQueue(commands); 1055 return commands; 1056 } 1057 const wxString description = GetSettingStringTranslated(&settings, wxT("Description")); 1058 if (stepType.CmpNoCase(wxT("remap")) == 0) 1059 { 1060 // build nona command 1061 const bool outputLayers = (settings.Read(wxT("OutputExposureLayers"), 0l) == 1l); 1062 if (outputLayers) 1063 { 1064 args.Append(wxT(" --create-exposure-layers -o ") + wxEscapeFilename(prefix + wxT("_layer"))); 1065 } 1066 else 1067 { 1068 args.Append(wxT(" -o ") + wxEscapeFilename(prefix)); 1069 }; 1070 args.Append(wxT(" ") + wxEscapeFilename(project)); 1071 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("nona")), 1072 args, description)); 1073 if (outputLayers) 1074 { 1075 if (exposureLayers.empty()) 1076 { 1077 exposureLayers = getExposureLayers(pano, allActiveImages, opts); 1078 HuginBase::UIntSet exposureLayersNumber; 1079 fill_set(exposureLayersNumber, 0, exposureLayers.size() - 1); 1080 exposureLayersFiles = detail::GetNumberedFilename(prefix + wxT("_layer"), intermediateImageType, exposureLayersNumber); 1081 }; 1082 detail::AddToArray(exposureLayersFiles, outputFiles); 1083 if (settings.Read(wxT("Keep"), 0l) == 0l) 1084 { 1085 detail::AddToArray(exposureLayersFiles, tempFilesDelete); 1086 }; 1087 } 1088 else 1089 { 1090 const wxArrayString remappedHDRComp = detail::GetNumberedFilename(prefix, wxT("_gray.pgm"), allActiveImages); 1091 const bool hdrOutput = args.MakeLower().Find(wxT("-r hdr")) != wxNOT_FOUND; 1092 detail::AddToArray(remappedImages, outputFiles); 1093 if (hdrOutput) 1094 { 1095 detail::AddToArray(remappedHDRComp, outputFiles); 1096 }; 1097 if (settings.Read(wxT("Keep"), 0l) == 0l) 1098 { 1099 detail::AddToArray(remappedImages, tempFilesDelete); 1100 if (hdrOutput) 1101 { 1102 detail::AddToArray(remappedHDRComp, tempFilesDelete); 1103 }; 1104 }; 1105 }; 1106 } 1107 else 1108 { 1109 if (stepType.CmpNoCase(wxT("merge")) == 0) 1110 { 1111 // build a merge command 1112 wxString resultFile = GetSettingString(&settings, wxT("Result")); 1113 if (resultFile.IsEmpty()) 1114 { 1115 errStream << "ERROR: Step " << i << " has no result file specified." << std::endl; 1116 CleanQueue(commands); 1117 return commands; 1118 }; 1119 resultFile.Replace(wxT("%prefix%"), prefix, true); 1120 if (args.Replace(wxT("%result%"), wxEscapeFilename(resultFile), true) == 0) 1121 { 1122 errStream << "ERROR: Step " << i << " has missing %result% placeholder in arguments." << std::endl; 1123 CleanQueue(commands); 1124 return commands; 1125 }; 1126 const wxString BlenderInput = GetSettingString(&settings, wxT("Input"), wxT("all")); 1127 // set the input images depending on the input 1128 if (BlenderInput.CmpNoCase(wxT("all")) == 0) 1129 { 1130 if (args.Replace(wxT("%input%"), GetQuotedFilenamesString(remappedImages), true) == 0) 1131 { 1132 errStream << "ERROR: Step " << i << " has missing %input% placeholder in arguments." << std::endl; 1133 CleanQueue(commands); 1134 return commands; 1135 }; 1136 } 1137 else 1138 { 1139 if (BlenderInput.CmpNoCase(wxT("stacks")) == 0) 1140 { 1141 if (stacks.empty()) 1142 { 1143 stacks= HuginBase::getHDRStacks(pano, allActiveImages, opts); 1144 HuginBase::UIntSet stackNumbers; 1145 fill_set(stackNumbers, 0, stacks.size() - 1); 1146 stacksFiles = detail::GetNumberedFilename(prefix + wxT("_stack"), intermediateImageType, stackNumbers); 1147 }; 1148 if (args.Replace(wxT("%input%"), GetQuotedFilenamesString(stacksFiles), true) == 0) 1149 { 1150 errStream << "ERROR: Step " << i << " has missing %input% placeholder in arguments." << std::endl; 1151 CleanQueue(commands); 1152 return commands; 1153 }; 1154 } 1155 else 1156 { 1157 if (BlenderInput.CmpNoCase(wxT("layers")) == 0) 1158 { 1159 if (exposureLayers.empty()) 1160 { 1161 exposureLayers = getExposureLayers(pano, allActiveImages, opts); 1162 HuginBase::UIntSet exposureLayersNumber; 1163 fill_set(exposureLayersNumber, 0, exposureLayers.size() - 1); 1164 exposureLayersFiles = detail::GetNumberedFilename(prefix + wxT("_layer"), intermediateImageType, exposureLayersNumber); 1165 }; 1166 if (args.Replace(wxT("%input%"), GetQuotedFilenamesString(exposureLayersFiles), true) == 0) 1167 { 1168 errStream << "ERROR: Step " << i << " has missing %input% placeholder in arguments." << std::endl; 1169 CleanQueue(commands); 1170 return commands; 1171 }; 1172 } 1173 else 1174 { 1175 errStream << "ERROR: Step " << i << " has invalid input type: \"" << BlenderInput.mb_str(wxConvLocal) << "\"." << std::endl; 1176 CleanQueue(commands); 1177 return commands; 1178 }; 1179 }; 1180 }; 1181 args.Replace(wxT("%size%"), sizeString, true); 1182 wxString wrapSwitch = GetSettingString(&settings, wxT("WrapArgument")); 1183 if (needsWrapSwitch && !wrapSwitch.IsEmpty()) 1184 { 1185 args.Prepend(wrapSwitch + wxT(" ")); 1186 } 1187 if (!detail::AddBlenderCommand(commands, ExePath, GetSettingString(&settings, wxT("Program")), i, 1188 args, description, errStream)) 1189 { 1190 return commands; 1191 }; 1192 outputFiles.Add(resultFile); 1193 if (settings.Read(wxT("Keep"), 1l) == 0l) 1194 { 1195 tempFilesDelete.Add(resultFile); 1196 }; 1197 } 1198 else 1199 { 1200 if (stepType.CmpNoCase(wxT("stack")) == 0) 1201 { 1202 // build command for each stack 1203 if (stacks.empty()) 1204 { 1205 stacks = HuginBase::getHDRStacks(pano, allActiveImages, opts); 1206 HuginBase::UIntSet stackNumbers; 1207 fill_set(stackNumbers, 0, stacks.size() - 1); 1208 stacksFiles = detail::GetNumberedFilename(prefix + wxT("_stack"), intermediateImageType, stackNumbers); 1209 }; 1210 const bool clean = (settings.Read(wxT("Keep"), 0l) == 0l); 1211 args.Replace(wxT("%size%"), sizeString, true); 1212 // now iterate each stack 1213 for (size_t stackNr = 0; stackNr < stacks.size(); ++stackNr) 1214 { 1215 wxString finalArgs(args); 1216 wxArrayString remappedStackImages = detail::GetNumberedFilename(prefix, intermediateImageType, stacks[stackNr]); 1217 if (finalArgs.Replace(wxT("%input%"), GetQuotedFilenamesString(remappedStackImages), true) == 0) 1218 { 1219 errStream << "ERROR: Step " << i << " has missing %input% placeholder in arguments." << std::endl; 1220 CleanQueue(commands); 1221 return commands; 1222 }; 1223 if (finalArgs.Replace(wxT("%output%"), wxEscapeFilename(stacksFiles[stackNr]), true) == 0) 1224 { 1225 errStream << "ERROR: Step " << i << " has missing %output% placeholder in arguments." << std::endl; 1226 CleanQueue(commands); 1227 return commands; 1228 }; 1229 if (!detail::AddBlenderCommand(commands, ExePath, GetSettingString(&settings, wxT("Program")), i, 1230 finalArgs, description, errStream)) 1231 { 1232 return commands; 1233 }; 1234 outputFiles.Add(stacksFiles[stackNr]); 1235 if (clean) 1236 { 1237 tempFilesDelete.Add(stacksFiles[stackNr]); 1238 }; 1239 } 1240 } 1241 else 1242 { 1243 if (stepType.CmpNoCase(wxT("layer")) == 0) 1244 { 1245 // build command for each exposure layer 1246 if (exposureLayers.empty()) 1247 { 1248 exposureLayers = HuginBase::getExposureLayers(pano, allActiveImages, opts); 1249 HuginBase::UIntSet exposureLayersNumber; 1250 fill_set(exposureLayersNumber, 0, exposureLayers.size() - 1); 1251 exposureLayersFiles = detail::GetNumberedFilename(prefix + wxT("_layer"), intermediateImageType, exposureLayersNumber); 1252 }; 1253 const bool clean = (settings.Read(wxT("Keep"), 0l) == 0l); 1254 args.Replace(wxT("%size%"), sizeString, true); 1255 // iterate all exposure layers 1256 for (size_t exposureLayerNr = 0; exposureLayerNr < exposureLayers.size(); ++exposureLayerNr) 1257 { 1258 wxString finalArgs(args); 1259 wxArrayString remappedLayerImages = detail::GetNumberedFilename(prefix, intermediateImageType, exposureLayers[exposureLayerNr]); 1260 if (finalArgs.Replace(wxT("%input%"), GetQuotedFilenamesString(remappedLayerImages), true) == 0) 1261 { 1262 errStream << "ERROR: Step " << i << " has missing %input% placeholder in arguments." << std::endl; 1263 CleanQueue(commands); 1264 return commands; 1265 }; 1266 if (finalArgs.Replace(wxT("%output%"), wxEscapeFilename(exposureLayersFiles[exposureLayerNr]), true) == 0) 1267 { 1268 errStream << "ERROR: Step " << i << " has missing %output% placeholder in arguments." << std::endl; 1269 CleanQueue(commands); 1270 return commands; 1271 }; 1272 if (!detail::AddBlenderCommand(commands, ExePath, GetSettingString(&settings, wxT("Program")), i, 1273 finalArgs, description, errStream)) 1274 { 1275 return commands; 1276 }; 1277 outputFiles.Add(exposureLayersFiles[exposureLayerNr]); 1278 if (clean) 1279 { 1280 tempFilesDelete.Add(exposureLayersFiles[exposureLayerNr]); 1281 }; 1282 } 1283 } 1284 else 1285 { 1286 if (stepType.CmpNoCase(wxT("modify")) == 0) 1287 { 1288 // build a modify command 1289 wxString inputFiles = GetSettingString(&settings, wxT("File")); 1290 if (inputFiles.IsEmpty()) 1291 { 1292 errStream << "ERROR: Step " << i << " has no input/output file specified." << std::endl; 1293 CleanQueue(commands); 1294 return commands; 1295 }; 1296 if (args.Find(wxT("%file%")) == wxNOT_FOUND) 1297 { 1298 errStream << "ERROR: Step " << i << " has missing %file% placeholder in arguments." << std::endl; 1299 CleanQueue(commands); 1300 return commands; 1301 }; 1302 args.Replace(wxT("%project%"), wxEscapeFilename(project), true); 1303 if (!detail::ReplacePrefixPlaceholder(args, prefix)) 1304 { 1305 errStream << "ERROR: Step " << i << " has invalid %prefix% placeholder in arguments." << std::endl; 1306 CleanQueue(commands); 1307 return commands; 1308 }; 1309 if (!detail::ReplaceWidthHeightPlaceHolder(args, "width", opts.getWidth())) 1310 { 1311 errStream << "ERROR: Step " << i << " has invalid %width% placeholder in arguments." << std::endl; 1312 CleanQueue(commands); 1313 return commands; 1314 } 1315 if (!detail::ReplaceWidthHeightPlaceHolder(args, "height", opts.getHeight())) 1316 { 1317 errStream << "ERROR: Step " << i << " has invalid %height% placeholder in arguments." << std::endl; 1318 CleanQueue(commands); 1319 return commands; 1320 } 1321 const wxString progName = GetSettingString(&settings, wxT("Program")); 1322 if (progName.IsEmpty()) 1323 { 1324 errStream << "ERROR: Step " << i << " has no program name specified." << std::endl; 1325 CleanQueue(commands); 1326 return commands; 1327 }; 1328 #ifdef __WXMAC__ 1329 // check if program can be found in bundle 1330 const wxString prog = GetExternalProgram(wxConfig::Get(), ExePath, progName); 1331 #elif defined __WXMSW__ 1332 1333 const wxString prog = MSWGetProgname(ExePath, progName); 1334 #else 1335 const wxString prog = progName; 1336 #endif 1337 if (inputFiles.CmpNoCase(wxT("all")) == 0) 1338 { 1339 for (size_t imgNr = 0; imgNr < remappedImages.size(); ++imgNr) 1340 { 1341 wxString finalArgs(args); 1342 finalArgs.Replace(wxT("%file%"), wxEscapeFilename(remappedImages[imgNr]), true); 1343 finalArgs.Replace(wxT("%sourceimage%"), wxEscapeFilename(inputImages[imgNr]), true); 1344 commands->push_back(new NormalCommand(prog, finalArgs, description)); 1345 }; 1346 } 1347 else 1348 { 1349 if (inputFiles.CmpNoCase(wxT("stacks")) == 0) 1350 { 1351 if (stacks.empty()) 1352 { 1353 errStream << "ERROR: Step " << i << " requests to modify stacks, but no stack was created before." << std::endl; 1354 CleanQueue(commands); 1355 return commands; 1356 }; 1357 for (size_t stackNr = 0; stackNr < stacksFiles.size(); ++stackNr) 1358 { 1359 wxString finalArgs(args); 1360 finalArgs.Replace(wxT("%file%"), wxEscapeFilename(stacksFiles[stackNr]), true); 1361 commands->push_back(new NormalCommand(prog, finalArgs, description)); 1362 }; 1363 } 1364 else 1365 { 1366 if (inputFiles.CmpNoCase(wxT("layers")) == 0) 1367 { 1368 if (exposureLayers.empty()) 1369 { 1370 errStream << "ERROR: Step " << i << " requests to modify exposure layers, but no exposure layer was created before." << std::endl; 1371 CleanQueue(commands); 1372 return commands; 1373 }; 1374 for (size_t layerNr = 0; layerNr < exposureLayersFiles.size(); ++layerNr) 1375 { 1376 wxString finalArgs(args); 1377 finalArgs.Replace(wxT("%file%"), wxEscapeFilename(exposureLayersFiles[layerNr]), true); 1378 commands->push_back(new NormalCommand(prog, finalArgs, description)); 1379 }; 1380 } 1381 else 1382 { 1383 inputFiles.Replace(wxT("%prefix%"), prefix, true); 1384 args.Replace(wxT("%file%"), wxEscapeFilename(inputFiles), true); 1385 commands->push_back(new NormalCommand(prog , args, description)); 1386 }; 1387 }; 1388 }; 1389 } 1390 else 1391 { 1392 if (stepType.CmpNoCase(wxT("exiftool")) == 0) 1393 { 1394 wxString resultFile = GetSettingString(&settings, wxT("Result")); 1395 if (resultFile.IsEmpty()) 1396 { 1397 errStream << "ERROR: Step " << i << " has no result file specified." << std::endl; 1398 CleanQueue(commands); 1399 return commands; 1400 }; 1401 resultFile.Replace(wxT("%prefix%"), prefix, true); 1402 if (args.Replace(wxT("%result%"), wxEscapeFilename(resultFile), true) == 0) 1403 { 1404 errStream << "ERROR: Step " << i << " has missing %result% placeholder in arguments." << std::endl; 1405 CleanQueue(commands); 1406 return commands; 1407 }; 1408 args.Replace(wxT("%image0%"), wxEscapeFilename(wxString(pano.getImage(0).getFilename().c_str(), HUGIN_CONV_FILENAME)), true); 1409 commands->push_back(new OptionalCommand(GetExternalProgram(wxConfigBase::Get(), ExePath, wxT("exiftool")), 1410 args, description)); 1411 } 1412 else 1413 { 1414 errStream << "ERROR: Step " << i << " has unknown Type \"" << stepType.mb_str(wxConvLocal) << "\"." << std::endl; 1415 CleanQueue(commands); 1416 return commands; 1417 }; 1418 }; 1419 }; 1420 }; 1421 }; 1422 }; 1423 }; 1424 return commands; 1425 } 1426 1427 /** return a wxString with all files in files quoted */ GetQuotedFilenamesString(const wxArrayString & files)1428 wxString GetQuotedFilenamesString(const wxArrayString& files) 1429 { 1430 wxString s; 1431 for (size_t i = 0; i < files.size(); ++i) 1432 { 1433 s.Append(wxEscapeFilename(files[i]) + wxT(" ")); 1434 }; 1435 return s; 1436 }; 1437 1438 }; // namespace 1439