1 /*
2  *  This file is part of RawTherapee.
3  *
4  *
5  *  RawTherapee is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  RawTherapee is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with RawTherapee.  If not, see <https://www.gnu.org/licenses/>.
17  */
18 #ifdef WIN32
19 #include <windows.h>
20 #endif
21 
22 #include "cachemanager.h"
23 #include "multilangmgr.h"
24 #include "thumbnail.h"
25 #include <sstream>
26 #include <iomanip>
27 #include <cstdio>
28 #include <cstdlib>
29 #include "../rtengine/colortemp.h"
30 #include "../rtengine/imagedata.h"
31 #include "../rtengine/procparams.h"
32 #include "../rtengine/rtthumbnail.h"
33 #include <glib/gstdio.h>
34 
35 #include "../rtengine/dynamicprofile.h"
36 #include "../rtengine/profilestore.h"
37 #include "../rtengine/settings.h"
38 #include "../rtexif/rtexif.h"
39 #include "guiutils.h"
40 #include "batchqueue.h"
41 #include "extprog.h"
42 #include "pathutils.h"
43 #include "paramsedited.h"
44 #include "procparamchangers.h"
45 
46 using namespace rtengine::procparams;
47 
Thumbnail(CacheManager * cm,const Glib::ustring & fname,CacheImageData * cf)48 Thumbnail::Thumbnail(CacheManager* cm, const Glib::ustring& fname, CacheImageData* cf) :
49     fname(fname),
50     cfs(*cf),
51     cachemgr(cm),
52     ref(1),
53     enqueueNumber(0),
54     tpp(nullptr),
55     pparams(new ProcParams),
56     pparamsValid(false),
57     imageLoading(false),
58     lastImg(nullptr),
59     lastW(0),
60     lastH(0),
61     lastScale(0),
62     initial_(false)
63 {
64 
65     loadProcParams ();
66 
67     // should be safe to use the unprotected version of loadThumbnail, since we are in the constructor
68     _loadThumbnail ();
69     generateExifDateTimeStrings ();
70 
71     if (cfs.rankOld >= 0) {
72         // rank and inTrash were found in cache (old style), move them over to pparams
73 
74         // try to load the last saved parameters from the cache or from the paramfile file
75         createProcParamsForUpdate(false, false); // this can execute customprofilebuilder to generate param file
76 
77         // TODO? should we call notifylisterners_procParamsChanged here?
78 
79         setRank(cfs.rankOld);
80         setStage(cfs.inTrashOld);
81     }
82 
83     delete tpp;
84     tpp = nullptr;
85 }
86 
Thumbnail(CacheManager * cm,const Glib::ustring & fname,const std::string & md5)87 Thumbnail::Thumbnail(CacheManager* cm, const Glib::ustring& fname, const std::string& md5) :
88     fname(fname),
89     cachemgr(cm),
90     ref(1),
91     enqueueNumber(0),
92     tpp(nullptr),
93     pparams(new ProcParams),
94     pparamsValid(false),
95     imageLoading(false),
96     lastImg(nullptr),
97     lastW(0),
98     lastH(0),
99     lastScale(0.0),
100     initial_(true)
101 {
102 
103 
104     cfs.md5 = md5;
105     loadProcParams ();
106     _generateThumbnailImage ();
107     cfs.recentlySaved = false;
108 
109     initial_ = false;
110 
111     delete tpp;
112     tpp = nullptr;
113 }
114 
_generateThumbnailImage()115 void Thumbnail::_generateThumbnailImage ()
116 {
117 
118     //  delete everything loaded into memory
119     delete tpp;
120     tpp = nullptr;
121     delete [] lastImg;
122     lastImg = nullptr;
123     tw = -1;
124     th = options.maxThumbnailHeight;
125     imgRatio = -1.;
126 
127     // generate thumbnail image
128     const std::string ext = getExtension(fname).lowercase();
129 
130     if (ext.empty()) {
131         return;
132     }
133 
134     cfs.supported = false;
135     cfs.exifValid = false;
136     cfs.timeValid = false;
137 
138     if (ext == "jpg" || ext == "jpeg") {
139         infoFromImage (fname);
140         tpp = rtengine::Thumbnail::loadFromImage (fname, tw, th, 1, pparams->wb.equal);
141 
142         if (tpp) {
143             cfs.format = FT_Jpeg;
144         }
145     } else if (ext == "png") {
146         tpp = rtengine::Thumbnail::loadFromImage (fname, tw, th, 1, pparams->wb.equal);
147 
148         if (tpp) {
149             cfs.format = FT_Png;
150         }
151     } else if (ext == "tif" || ext == "tiff") {
152         infoFromImage (fname);
153         tpp = rtengine::Thumbnail::loadFromImage (fname, tw, th, 1, pparams->wb.equal);
154 
155         if (tpp) {
156             cfs.format = FT_Tiff;
157         }
158     } else {
159         // RAW works like this:
160         //  1. if we are here it's because we aren't in the cache so load the JPG
161         //     image out of the RAW. Mark as "quick".
162         //  2. if we don't find that then just grab the real image.
163         bool quick = false;
164         rtengine::RawMetaDataLocation ri;
165 
166         rtengine::eSensorType sensorType = rtengine::ST_NONE;
167         if ( initial_ && options.internalThumbIfUntouched) {
168             quick = true;
169             tpp = rtengine::Thumbnail::loadQuickFromRaw (fname, ri, sensorType, tw, th, 1, TRUE);
170         }
171 
172         if ( tpp == nullptr ) {
173             quick = false;
174             tpp = rtengine::Thumbnail::loadFromRaw (fname, ri, sensorType, tw, th, 1, pparams->wb.equal, TRUE);
175         }
176 
177         cfs.sensortype = sensorType;
178         if (tpp) {
179             cfs.format = FT_Raw;
180             cfs.thumbImgType = quick ? CacheImageData::QUICK_THUMBNAIL : CacheImageData::FULL_THUMBNAIL;
181             infoFromImage (fname, std::unique_ptr<rtengine::RawMetaDataLocation>(new rtengine::RawMetaDataLocation(ri)));
182         }
183     }
184 
185     if (tpp) {
186         tpp->getAutoWBMultipliers(cfs.redAWBMul, cfs.greenAWBMul, cfs.blueAWBMul);
187         _saveThumbnail ();
188         cfs.supported = true;
189 
190         cfs.save (getCacheFileName ("data", ".txt"));
191 
192         generateExifDateTimeStrings ();
193     }
194 }
195 
isSupported()196 bool Thumbnail::isSupported ()
197 {
198     return cfs.supported;
199 }
200 
getProcParams()201 const ProcParams& Thumbnail::getProcParams ()
202 {
203     MyMutex::MyLock lock(mutex);
204     return getProcParamsU();
205 }
206 
207 // Unprotected version of getProcParams, when
getProcParamsU()208 const ProcParams& Thumbnail::getProcParamsU ()
209 {
210     if (pparamsValid) {
211         return *pparams;
212     } else {
213         *pparams = *(ProfileStore::getInstance()->getDefaultProcParams (getType() == FT_Raw));
214 
215         if (pparams->wb.method == "Camera") {
216             double ct;
217             getCamWB (ct, pparams->wb.green);
218             pparams->wb.temperature = ct;
219         } else if (pparams->wb.method == "Auto") {
220             double ct;
221             getAutoWB (ct, pparams->wb.green, pparams->wb.equal, pparams->wb.tempBias);
222             pparams->wb.temperature = ct;
223         }
224     }
225 
226     return *pparams; // there is no valid pp to return, but we have to return something
227 }
228 
229 /** @brief  Create default params on demand and returns a new updatable object
230  *
231  *  The loaded profile may be partial, but it return a complete ProcParams (i.e. without ParamsEdited)
232  *
233  *  @param returnParams Ask to return a pointer to a ProcParams object if true
234  *  @param force True if the profile has to be re-generated even if it already exists
235  *  @param flaggingMode True if the ProcParams will be created because the file browser is being flagging an image
236  *                      (rang, to trash, color labels). This parameter is passed to the CPB.
237  *
238  *  @return Return a pointer to a ProcPamas structure to be updated if returnParams is true and if everything went fine, NULL otherwise.
239  */
createProcParamsForUpdate(bool returnParams,bool force,bool flaggingMode)240 rtengine::procparams::ProcParams* Thumbnail::createProcParamsForUpdate(bool returnParams, bool force, bool flaggingMode)
241 {
242 
243     // try to load the last saved parameters from the cache or from the paramfile file
244     ProcParams* ldprof = nullptr;
245 
246     Glib::ustring defProf = getType() == FT_Raw ? options.defProfRaw : options.defProfImg;
247 
248     const CacheImageData* cfs = getCacheImageData();
249     Glib::ustring defaultPparamsPath = options.findProfilePath(defProf);
250     const bool create = (!hasProcParams() || force);
251     const bool run_cpb = !options.CPBPath.empty() && !defaultPparamsPath.empty() && cfs && cfs->exifValid && create;
252 
253     const Glib::ustring outFName =
254         (options.paramsLoadLocation == PLL_Input && options.saveParamsFile) ?
255         fname + paramFileExtension :
256         getCacheFileName("profiles", paramFileExtension);
257 
258     if (!run_cpb) {
259         if (defProf == DEFPROFILE_DYNAMIC && create && cfs && cfs->exifValid) {
260             rtengine::FramesMetaData* imageMetaData;
261             if (getType() == FT_Raw) {
262                 // Should we ask all frame's MetaData ?
263                 imageMetaData = rtengine::FramesMetaData::fromFile (fname, std::unique_ptr<rtengine::RawMetaDataLocation>(new rtengine::RawMetaDataLocation(rtengine::Thumbnail::loadMetaDataFromRaw(fname))), true);
264             } else {
265                 // Should we ask all frame's MetaData ?
266                 imageMetaData = rtengine::FramesMetaData::fromFile (fname, nullptr, true);
267             }
268             PartialProfile *pp = ProfileStore::getInstance()->loadDynamicProfile(imageMetaData);
269             delete imageMetaData;
270             int err = pp->pparams->save(outFName);
271             pp->deleteInstance();
272             delete pp;
273             if (!err) {
274                 loadProcParams();
275             }
276         } else if (create && defProf != DEFPROFILE_DYNAMIC) {
277             const PartialProfile *p = ProfileStore::getInstance()->getProfile(defProf);
278             if (p && !p->pparams->save(outFName)) {
279                 loadProcParams();
280             }
281         }
282     } else {
283         // First generate the communication file, with general values and EXIF metadata
284         rtengine::FramesMetaData* imageMetaData;
285 
286         if (getType() == FT_Raw) {
287             // Should we ask all frame's MetaData ?
288             imageMetaData = rtengine::FramesMetaData::fromFile (fname, std::unique_ptr<rtengine::RawMetaDataLocation>(new rtengine::RawMetaDataLocation(rtengine::Thumbnail::loadMetaDataFromRaw(fname))), true);
289         } else {
290             // Should we ask all frame's MetaData ?
291             imageMetaData = rtengine::FramesMetaData::fromFile (fname, nullptr, true);
292         }
293 
294         static int index = 0; // Will act as unique identifier during the session
295         Glib::ustring tmpFileName( Glib::build_filename(options.cacheBaseDir, Glib::ustring::compose("CPB_temp_%1.txt", index++)) );
296 
297         const rtexif::TagDirectory* exifDir = nullptr;
298 
299         if (imageMetaData && (exifDir = imageMetaData->getRootExifData())) {
300             exifDir->CPBDump(tmpFileName, fname, outFName,
301                              defaultPparamsPath == DEFPROFILE_INTERNAL ? DEFPROFILE_INTERNAL : Glib::build_filename(defaultPparamsPath, Glib::path_get_basename(defProf) + paramFileExtension),
302                              cfs,
303                              flaggingMode);
304         }
305         delete imageMetaData;
306 
307         // For the filename etc. do NOT use streams, since they are not UTF8 safe
308         Glib::ustring cmdLine = options.CPBPath + Glib::ustring(" \"") + tmpFileName + Glib::ustring("\"");
309 
310         if (rtengine::settings->verbose) {
311             printf("Custom profile builder's command line: %s\n", Glib::ustring(cmdLine).c_str());
312         }
313 
314         bool success = ExtProgStore::spawnCommandSync (cmdLine);
315 
316         // Now they SHOULD be there (and potentially "partial"), so try to load them and store it as a full procparam
317         if (success) {
318             loadProcParams();
319         }
320 
321         g_remove (tmpFileName.c_str ());
322     }
323 
324     if (returnParams && hasProcParams()) {
325         ldprof = new ProcParams ();
326         *ldprof = getProcParams ();
327     }
328 
329     return ldprof;
330 }
331 
notifylisterners_procParamsChanged(int whoChangedIt)332 void Thumbnail::notifylisterners_procParamsChanged(int whoChangedIt)
333 {
334     for (size_t i = 0; i < listeners.size(); i++) {
335         listeners[i]->procParamsChanged (this, whoChangedIt);
336     }
337 }
338 
339 /*
340  * Load the procparams from the cache or from the sidecar file (priority set in
341  * the Preferences).
342  *
343  * The result is a complete ProcParams with default values merged with the values
344  * from the loaded ProcParams (sidecar or cache file).
345 */
loadProcParams()346 void Thumbnail::loadProcParams ()
347 {
348     MyMutex::MyLock lock(mutex);
349 
350     pparamsValid = false;
351     pparams->setDefaults();
352 
353     if (options.paramsLoadLocation == PLL_Input) {
354         // try to load it from params file next to the image file
355         const int ppres = pparams->load(fname + paramFileExtension);
356         pparamsValid = !ppres && pparams->ppVersion >= 220;
357 
358         // if no success, try to load the cached version of the procparams
359         if (!pparamsValid) {
360             pparamsValid = !pparams->load(getCacheFileName("profiles", paramFileExtension));
361         }
362     } else {
363         // try to load it from cache
364         pparamsValid = !pparams->load(getCacheFileName("profiles", paramFileExtension));
365 
366         // if no success, try to load it from params file next to the image file
367         if (!pparamsValid) {
368             const int ppres = pparams->load(fname + paramFileExtension);
369             pparamsValid = !ppres && pparams->ppVersion >= 220;
370         }
371     }
372 }
373 
clearProcParams(int whoClearedIt)374 void Thumbnail::clearProcParams (int whoClearedIt)
375 {
376 
377     /*  Clarification on current "clear profile" functionality:
378         a. if rank/colorlabel/inTrash are NOT set,
379         the "clear profile" will delete the pp3 file (as before).
380 
381         b. if any of the rank/colorlabel/inTrash ARE set,
382         the "clear profile" will lead to execution of ProcParams::setDefaults
383         (the CPB is NOT called) to set the params values and will preserve
384         rank/colorlabel/inTrash in the param file. */
385 
386     {
387         MyMutex::MyLock lock(mutex);
388 
389         // preserve rank, colorlabel and inTrash across clear
390         int rank = getRank();
391         int colorlabel = getColorLabel();
392         int inTrash = getStage();
393 
394 
395         cfs.recentlySaved = false;
396         pparamsValid = false;
397 
398         //TODO: run though customprofilebuilder?
399         // probably not as this is the only option to set param values to default
400 
401         // reset the params to defaults
402         pparams->setDefaults();
403 
404         // and restore rank and inTrash
405         setRank(rank);
406         pparamsValid = cfs.rating != rank;
407         setColorLabel(colorlabel);
408         setStage(inTrash);
409 
410         // params could get validated by rank/inTrash values restored above
411         if (pparamsValid) {
412             updateCache();
413         } else {
414             // remove param file from cache
415             Glib::ustring fname_ = getCacheFileName ("profiles", paramFileExtension);
416             g_remove (fname_.c_str ());
417 
418             // remove param file located next to the file
419             fname_ = fname + paramFileExtension;
420             g_remove (fname_.c_str ());
421 
422             fname_ = removeExtension(fname) + paramFileExtension;
423             g_remove (fname_.c_str ());
424 
425             if (cfs.format == FT_Raw && options.internalThumbIfUntouched && cfs.thumbImgType != CacheImageData::QUICK_THUMBNAIL) {
426                 // regenerate thumbnail, ie load the quick thumb again. For the rare formats not supporting quick thumbs this will
427                 // be a bit slow as a new full thumbnail will be generated unnecessarily, but currently there is no way to pre-check
428                 // if the format supports quick thumbs.
429                 initial_ = true;
430                 _generateThumbnailImage();
431                 initial_ = false;
432             }
433         }
434 
435     } // end of mutex lock
436 
437     for (size_t i = 0; i < listeners.size(); i++) {
438         listeners[i]->procParamsChanged (this, whoClearedIt);
439     }
440 }
441 
hasProcParams() const442 bool Thumbnail::hasProcParams () const
443 {
444 
445     return pparamsValid;
446 }
447 
setProcParams(const ProcParams & pp,ParamsEdited * pe,int whoChangedIt,bool updateCacheNow,bool resetToDefault)448 void Thumbnail::setProcParams (const ProcParams& pp, ParamsEdited* pe, int whoChangedIt, bool updateCacheNow, bool resetToDefault)
449 {
450     const bool needsReprocessing =
451            resetToDefault
452         || pparams->toneCurve != pp.toneCurve
453         || pparams->labCurve != pp.labCurve
454         || pparams->localContrast != pp.localContrast
455         || pparams->rgbCurves != pp.rgbCurves
456         || pparams->colorToning != pp.colorToning
457         || pparams->vibrance != pp.vibrance
458         || pparams->wb != pp.wb
459         || pparams->colorappearance != pp.colorappearance
460         || pparams->epd != pp.epd
461         || pparams->fattal != pp.fattal
462         || pparams->sh != pp.sh
463         || pparams->crop != pp.crop
464         || pparams->coarse != pp.coarse
465         || pparams->commonTrans != pp.commonTrans
466         || pparams->rotate != pp.rotate
467         || pparams->distortion != pp.distortion
468         || pparams->lensProf != pp.lensProf
469         || pparams->perspective != pp.perspective
470         || pparams->gradient != pp.gradient
471         || pparams->pcvignette != pp.pcvignette
472         || pparams->cacorrection != pp.cacorrection
473         || pparams->vignetting != pp.vignetting
474         || pparams->chmixer != pp.chmixer
475         || pparams->blackwhite != pp.blackwhite
476         || pparams->icm != pp.icm
477         || pparams->hsvequalizer != pp.hsvequalizer
478         || pparams->filmSimulation != pp.filmSimulation
479         || pparams->softlight != pp.softlight
480         || pparams->dehaze != pp.dehaze
481         || pparams->filmNegative != pp.filmNegative
482         || whoChangedIt == FILEBROWSER
483         || whoChangedIt == BATCHEDITOR;
484 
485     {
486         MyMutex::MyLock lock(mutex);
487 
488         if (*pparams != pp) {
489             cfs.recentlySaved = false;
490         } else if (pparamsValid && !updateCacheNow) {
491             // nothing to do
492             return;
493         }
494 
495         // do not update rank, colorlabel and inTrash
496         const int rank = getRank();
497         const int colorlabel = getColorLabel();
498         const int inTrash = getStage();
499 
500         if (pe) {
501             pe->combine(*pparams, pp, true);
502         } else {
503             *pparams = pp;
504         }
505 
506         pparamsValid = true;
507 
508         setRank(rank);
509         setColorLabel(colorlabel);
510         setStage(inTrash);
511 
512         if (updateCacheNow) {
513             updateCache();
514         }
515     } // end of mutex lock
516 
517     if (needsReprocessing) {
518         for (size_t i = 0; i < listeners.size(); i++) {
519             listeners[i]->procParamsChanged (this, whoChangedIt);
520         }
521     }
522 }
523 
isRecentlySaved() const524 bool Thumbnail::isRecentlySaved () const
525 {
526 
527     return cfs.recentlySaved;
528 }
529 
imageDeveloped()530 void Thumbnail::imageDeveloped ()
531 {
532 
533     cfs.recentlySaved = true;
534     cfs.save (getCacheFileName ("data", ".txt"));
535 
536     if (options.saveParamsCache) {
537         pparams->save (getCacheFileName ("profiles", paramFileExtension));
538     }
539 }
540 
imageEnqueued()541 void Thumbnail::imageEnqueued ()
542 {
543 
544     enqueueNumber++;
545 }
546 
imageRemovedFromQueue()547 void Thumbnail::imageRemovedFromQueue ()
548 {
549 
550     enqueueNumber--;
551 }
552 
isEnqueued() const553 bool Thumbnail::isEnqueued () const
554 {
555 
556     return enqueueNumber > 0;
557 }
558 
isPixelShift() const559 bool Thumbnail::isPixelShift () const
560 {
561     return cfs.isPixelShift;
562 }
isHDR() const563 bool Thumbnail::isHDR () const
564 {
565     return cfs.isHDR;
566 }
567 
increaseRef()568 void Thumbnail::increaseRef ()
569 {
570     MyMutex::MyLock lock(mutex);
571     ++ref;
572 }
573 
decreaseRef()574 void Thumbnail::decreaseRef ()
575 {
576     {
577         MyMutex::MyLock lock(mutex);
578 
579         if ( ref == 0 ) {
580             return;
581         }
582 
583         if ( --ref != 0 ) {
584             return;
585         }
586     }
587     cachemgr->closeThumbnail (this);
588 }
589 
getThumbnailWidth(const int h,const rtengine::procparams::ProcParams * pparams) const590 int Thumbnail::getThumbnailWidth (const int h, const rtengine::procparams::ProcParams *pparams) const
591 {
592     int tw_ = tw;
593     int th_ = th;
594     float imgRatio_ = imgRatio;
595 
596     if (pparams) {
597         int ppCoarse = pparams->coarse.rotate;
598 
599         if (ppCoarse >= 180) {
600             ppCoarse -= 180;
601         }
602 
603         int thisCoarse = this->pparams->coarse.rotate;
604 
605         if (thisCoarse >= 180) {
606             thisCoarse -= 180;
607         }
608 
609         if (thisCoarse != ppCoarse) {
610             // different orientation -> swapping width & height
611             std::swap(th_, tw_);
612             if (imgRatio_ >= 0.0001f) {
613                 imgRatio_ = 1.f / imgRatio_;
614             }
615         }
616     }
617 
618     if (imgRatio_ > 0.f) {
619         return imgRatio_ * h;
620     } else {
621         return tw_ * h / th_;
622     }
623 }
624 
getFinalSize(const rtengine::procparams::ProcParams & pparams,int & w,int & h)625 void Thumbnail::getFinalSize (const rtengine::procparams::ProcParams& pparams, int& w, int& h)
626 {
627     MyMutex::MyLock lock(mutex);
628 
629     // WARNING: When downscaled, the ratio have loosed a lot of precision, so we can't get back the exact initial dimensions
630     double fw = lastW * lastScale;
631     double fh = lastH * lastScale;
632 
633     if (pparams.coarse.rotate == 90 || pparams.coarse.rotate == 270) {
634         fh = lastW * lastScale;
635         fw = lastH * lastScale;
636     }
637 
638     if (!pparams.resize.enabled) {
639         w = fw;
640         h = fh;
641     } else {
642         w = (int)(fw + 0.5);
643         h = (int)(fh + 0.5);
644     }
645 }
646 
getOriginalSize(int & w,int & h)647 void Thumbnail::getOriginalSize (int& w, int& h)
648 {
649     w = tw;
650     h = th;
651 }
652 
processThumbImage(const rtengine::procparams::ProcParams & pparams,int h,double & scale)653 rtengine::IImage8* Thumbnail::processThumbImage (const rtengine::procparams::ProcParams& pparams, int h, double& scale)
654 {
655 
656     MyMutex::MyLock lock(mutex);
657 
658     if ( tpp == nullptr ) {
659         _loadThumbnail();
660 
661         if ( tpp == nullptr ) {
662             return nullptr;
663         }
664     }
665 
666     rtengine::IImage8* image = nullptr;
667 
668     if ( cfs.thumbImgType == CacheImageData::QUICK_THUMBNAIL ) {
669         // RAW internal thumbnail, no profile yet: just do some rotation etc.
670         image = tpp->quickProcessImage (pparams, h, rtengine::TI_Nearest);
671     } else {
672         // Full thumbnail: apply profile
673         // image = tpp->processImage (pparams, h, rtengine::TI_Bilinear, cfs.getCamera(), cfs.focalLen, cfs.focalLen35mm, cfs.focusDist, cfs.shutter, cfs.fnumber, cfs.iso, cfs.expcomp, scale );
674         image = tpp->processImage (pparams, static_cast<rtengine::eSensorType>(cfs.sensortype), h, rtengine::TI_Bilinear, &cfs, scale );
675     }
676 
677     tpp->getDimensions(lastW, lastH, lastScale);
678 
679     delete tpp;
680     tpp = nullptr;
681     return image;
682 }
683 
upgradeThumbImage(const rtengine::procparams::ProcParams & pparams,int h,double & scale)684 rtengine::IImage8* Thumbnail::upgradeThumbImage (const rtengine::procparams::ProcParams& pparams, int h, double& scale)
685 {
686 
687     MyMutex::MyLock lock(mutex);
688 
689     if ( cfs.thumbImgType != CacheImageData::QUICK_THUMBNAIL ) {
690         return nullptr;
691     }
692 
693     _generateThumbnailImage();
694 
695     if ( tpp == nullptr ) {
696         return nullptr;
697     }
698 
699     // rtengine::IImage8* image = tpp->processImage (pparams, h, rtengine::TI_Bilinear, cfs.getCamera(), cfs.focalLen, cfs.focalLen35mm, cfs.focusDist, cfs.shutter, cfs.fnumber, cfs.iso, cfs.expcomp,  scale );
700     rtengine::IImage8* image = tpp->processImage (pparams, static_cast<rtengine::eSensorType>(cfs.sensortype), h, rtengine::TI_Bilinear, &cfs, scale );
701     tpp->getDimensions(lastW, lastH, lastScale);
702 
703     delete tpp;
704     tpp = nullptr;
705     return image;
706 }
707 
generateExifDateTimeStrings()708 void Thumbnail::generateExifDateTimeStrings ()
709 {
710 
711     exifString = "";
712     dateTimeString = "";
713 
714     if (!cfs.exifValid) {
715         return;
716     }
717 
718     exifString = Glib::ustring::compose ("f/%1 %2s %3%4 %5mm", Glib::ustring(rtengine::FramesData::apertureToString(cfs.fnumber)), Glib::ustring(rtengine::FramesData::shutterToString(cfs.shutter)), M("QINFO_ISO"), cfs.iso, Glib::ustring::format(std::setw(3), std::fixed, std::setprecision(2), cfs.focalLen));
719 
720     if (options.fbShowExpComp && cfs.expcomp != "0.00" && !cfs.expcomp.empty()) { // don't show exposure compensation if it is 0.00EV;old cache files do not have ExpComp, so value will not be displayed.
721         exifString = Glib::ustring::compose ("%1 %2EV", exifString, cfs.expcomp);    // append exposure compensation to exifString
722     }
723 
724     std::string dateFormat = options.dateFormat;
725     std::ostringstream ostr;
726     bool spec = false;
727 
728     for (size_t i = 0; i < dateFormat.size(); i++)
729         if (spec && dateFormat[i] == 'y') {
730             ostr << cfs.year;
731             spec = false;
732         } else if (spec && dateFormat[i] == 'm') {
733             ostr << (int)cfs.month;
734             spec = false;
735         } else if (spec && dateFormat[i] == 'd') {
736             ostr << (int)cfs.day;
737             spec = false;
738         } else if (dateFormat[i] == '%') {
739             spec = true;
740         } else {
741             ostr << (char)dateFormat[i];
742             spec = false;
743         }
744 
745     ostr << " " << (int)cfs.hour;
746     ostr << ":" << std::setw(2) << std::setfill('0') << (int)cfs.min;
747     ostr << ":" << std::setw(2) << std::setfill('0') << (int)cfs.sec;
748 
749     dateTimeString = ostr.str ();
750 }
751 
getExifString() const752 const Glib::ustring& Thumbnail::getExifString () const
753 {
754 
755     return exifString;
756 }
757 
getDateTimeString() const758 const Glib::ustring& Thumbnail::getDateTimeString () const
759 {
760 
761     return dateTimeString;
762 }
763 
getAutoWB(double & temp,double & green,double equal,double tempBias)764 void Thumbnail::getAutoWB (double& temp, double& green, double equal, double tempBias)
765 {
766     if (cfs.redAWBMul != -1.0) {
767         rtengine::ColorTemp ct(cfs.redAWBMul, cfs.greenAWBMul, cfs.blueAWBMul, equal);
768         temp = ct.getTemp();
769         green = ct.getGreen();
770     } else {
771         temp = green = -1.0;
772     }
773 }
774 
775 
getType()776 ThFileType Thumbnail::getType ()
777 {
778 
779     return (ThFileType) cfs.format;
780 }
781 
infoFromImage(const Glib::ustring & fname,std::unique_ptr<rtengine::RawMetaDataLocation> rml)782 int Thumbnail::infoFromImage (const Glib::ustring& fname, std::unique_ptr<rtengine::RawMetaDataLocation> rml)
783 {
784     rtengine::FramesMetaData* idata = rtengine::FramesMetaData::fromFile (fname, std::move(rml));
785 
786     if (!idata) {
787         return 0;
788     }
789 
790     int deg = 0;
791     cfs.timeValid = false;
792     cfs.exifValid = false;
793 
794     if (idata->hasExif()) {
795         cfs.shutter      = idata->getShutterSpeed ();
796         cfs.fnumber      = idata->getFNumber ();
797         cfs.focalLen     = idata->getFocalLen ();
798         cfs.focalLen35mm = idata->getFocalLen35mm ();
799         cfs.focusDist    = idata->getFocusDist ();
800         cfs.iso          = idata->getISOSpeed ();
801         cfs.expcomp      = idata->expcompToString (idata->getExpComp(), false); // do not mask Zero expcomp
802         cfs.isHDR        = idata->getHDR ();
803         cfs.isPixelShift = idata->getPixelShift ();
804         cfs.frameCount   = idata->getFrameCount ();
805         cfs.sampleFormat = idata->getSampleFormat ();
806         cfs.year         = 1900 + idata->getDateTime().tm_year;
807         cfs.month        = idata->getDateTime().tm_mon + 1;
808         cfs.day          = idata->getDateTime().tm_mday;
809         cfs.hour         = idata->getDateTime().tm_hour;
810         cfs.min          = idata->getDateTime().tm_min;
811         cfs.sec          = idata->getDateTime().tm_sec;
812         cfs.timeValid    = true;
813         cfs.exifValid    = true;
814         cfs.lens         = idata->getLens();
815         cfs.camMake      = idata->getMake();
816         cfs.camModel     = idata->getModel();
817         cfs.rating       = idata->getRating();
818 
819         if (idata->getOrientation() == "Rotate 90 CW") {
820             deg = 90;
821         } else if (idata->getOrientation() == "Rotate 180") {
822             deg = 180;
823         } else if (idata->getOrientation() == "Rotate 270 CW") {
824             deg = 270;
825         }
826     } else {
827         cfs.lens     = "Unknown";
828         cfs.camMake  = "Unknown";
829         cfs.camModel = "Unknown";
830     }
831 
832     // get image filetype
833     std::string::size_type idx;
834     idx = fname.rfind('.');
835 
836     if(idx != std::string::npos) {
837         cfs.filetype = fname.substr(idx + 1);
838     } else {
839         cfs.filetype = "";
840     }
841 
842     delete idata;
843     return deg;
844 }
845 
846 /*
847  * Read all thumbnail's data from the cache; build and save them if doesn't exist - NON PROTECTED
848  * This includes:
849  *  - image's bitmap (*.rtti)
850  *  - auto exposure's histogram (full thumbnail only)
851  *  - embedded profile (full thumbnail only)
852  *  - LiveThumbData section of the data file
853  */
_loadThumbnail(bool firstTrial)854 void Thumbnail::_loadThumbnail(bool firstTrial)
855 {
856 
857     tw = -1;
858     th = options.maxThumbnailHeight;
859     delete tpp;
860     tpp = new rtengine::Thumbnail ();
861     tpp->isRaw = (cfs.format == (int) FT_Raw);
862 
863     // load supplementary data
864     bool succ = tpp->readData (getCacheFileName ("data", ".txt"));
865 
866     if (succ) {
867         tpp->getAutoWBMultipliers(cfs.redAWBMul, cfs.greenAWBMul, cfs.blueAWBMul);
868     }
869 
870     // thumbnail image
871     succ = succ && tpp->readImage (getCacheFileName ("images", ""));
872 
873     if (!succ && firstTrial) {
874         _generateThumbnailImage ();
875 
876         if (cfs.supported && firstTrial) {
877             _loadThumbnail (false);
878         }
879 
880         if (tpp == nullptr) {
881             return;
882         }
883     } else if (!succ) {
884         delete tpp;
885         tpp = nullptr;
886         return;
887     }
888 
889     if ( cfs.thumbImgType == CacheImageData::FULL_THUMBNAIL ) {
890         if(!tpp->isAeValid()) {
891             // load aehistogram
892             tpp->readAEHistogram (getCacheFileName ("aehistograms", ""));
893         }
894 
895         // load embedded profile
896         tpp->readEmbProfile (getCacheFileName ("embprofiles", ".icc"));
897 
898         tpp->init ();
899     }
900 
901     if (!initial_) {
902         tw = tpp->getImageWidth (getProcParamsU(), th, imgRatio);    // this might return 0 if image was just building
903     }
904 }
905 
906 /*
907  * Read all thumbnail's data from the cache; build and save them if doesn't exist - MUTEX PROTECTED
908  * This includes:
909  *  - image's bitmap (*.rtti)
910  *  - auto exposure's histogram (full thumbnail only)
911  *  - embedded profile (full thumbnail only)
912  *  - LiveThumbData section of the data file
913  */
loadThumbnail(bool firstTrial)914 void Thumbnail::loadThumbnail (bool firstTrial)
915 {
916     MyMutex::MyLock lock(mutex);
917     _loadThumbnail(firstTrial);
918 }
919 
920 /*
921  * Save thumbnail's data to the cache - NON PROTECTED
922  * This includes:
923  *  - image's bitmap (*.rtti)
924  *  - auto exposure's histogram (full thumbnail only)
925  *  - embedded profile (full thumbnail only)
926  *  - LiveThumbData section of the data file
927  */
_saveThumbnail()928 void Thumbnail::_saveThumbnail ()
929 {
930 
931     if (!tpp) {
932         return;
933     }
934 
935     g_remove (getCacheFileName ("images", ".rtti").c_str ());
936 
937     // save thumbnail image
938     tpp->writeImage (getCacheFileName ("images", ""));
939 
940     if(!tpp->isAeValid()) {
941         // save aehistogram
942         tpp->writeAEHistogram (getCacheFileName ("aehistograms", ""));
943     }
944     // save embedded profile
945     tpp->writeEmbProfile (getCacheFileName ("embprofiles", ".icc"));
946 
947     // save supplementary data
948     tpp->writeData (getCacheFileName ("data", ".txt"));
949 }
950 
951 /*
952  * Save thumbnail's data to the cache - MUTEX PROTECTED
953  * This includes:
954  *  - image's bitmap (*.rtti)
955  *  - auto exposure's histogram (full thumbnail only)
956  *  - embedded profile (full thumbnail only)
957  *  - LiveThumbData section of the data file
958  */
saveThumbnail()959 void Thumbnail::saveThumbnail ()
960 {
961     MyMutex::MyLock lock(mutex);
962     _saveThumbnail();
963 }
964 
965 /*
966  * Update the cached files
967  *  - updatePParams==true (default)        : write the procparams file (sidecar or cache, depending on the options)
968  *  - updateCacheImageData==true (default) : write the CacheImageData values in the cache folder,
969  *                                           i.e. some General, DateTime, ExifInfo, File info and ExtraRawInfo,
970  */
updateCache(bool updatePParams,bool updateCacheImageData)971 void Thumbnail::updateCache (bool updatePParams, bool updateCacheImageData)
972 {
973 
974     if (updatePParams && pparamsValid) {
975         pparams->save (
976             options.saveParamsFile  ? fname + paramFileExtension : "",
977             options.saveParamsCache ? getCacheFileName ("profiles", paramFileExtension) : "",
978             true
979         );
980     }
981 
982     if (updateCacheImageData) {
983         cfs.save (getCacheFileName ("data", ".txt"));
984     }
985 }
986 
~Thumbnail()987 Thumbnail::~Thumbnail ()
988 {
989     mutex.lock();
990 
991     delete [] lastImg;
992     delete tpp;
993     mutex.unlock();
994 }
995 
getCacheFileName(const Glib::ustring & subdir,const Glib::ustring & fext) const996 Glib::ustring Thumbnail::getCacheFileName (const Glib::ustring& subdir, const Glib::ustring& fext) const
997 {
998     return cachemgr->getCacheFileName (subdir, fname, fext, cfs.md5);
999 }
1000 
setFileName(const Glib::ustring & fn)1001 void Thumbnail::setFileName (const Glib::ustring &fn)
1002 {
1003 
1004     fname = fn;
1005     cfs.md5 = cachemgr->getMD5 (fname);
1006 }
1007 
getRank() const1008 int Thumbnail::getRank  () const
1009 {
1010     // prefer the user-set rank over the embedded Rating
1011     // pparams->rank == -1 means that there is no saved rank yet, so we should
1012     // next look for the embedded Rating metadata.
1013     if (pparams->rank != -1) {
1014         return pparams->rank;
1015     } else {
1016         return cfs.rating;
1017     }
1018 }
1019 
setRank(int rank)1020 void Thumbnail::setRank  (int rank)
1021 {
1022     pparams->rank = rank;
1023     pparamsValid = true;
1024 }
1025 
getColorLabel() const1026 int Thumbnail::getColorLabel  () const
1027 {
1028     return pparams->colorlabel;
1029 }
1030 
setColorLabel(int colorlabel)1031 void Thumbnail::setColorLabel  (int colorlabel)
1032 {
1033     if (pparams->colorlabel != colorlabel) {
1034         pparams->colorlabel = colorlabel;
1035         pparamsValid = true;
1036     }
1037 }
1038 
getStage() const1039 int Thumbnail::getStage () const
1040 {
1041     return pparams->inTrash;
1042 }
1043 
setStage(bool stage)1044 void Thumbnail::setStage (bool stage)
1045 {
1046     if (pparams->inTrash != stage) {
1047         pparams->inTrash = stage;
1048         pparamsValid = true;
1049     }
1050 }
1051 
addThumbnailListener(ThumbnailListener * tnl)1052 void Thumbnail::addThumbnailListener (ThumbnailListener* tnl)
1053 {
1054 
1055     increaseRef();
1056     listeners.push_back (tnl);
1057 }
1058 
removeThumbnailListener(ThumbnailListener * tnl)1059 void Thumbnail::removeThumbnailListener (ThumbnailListener* tnl)
1060 {
1061 
1062     std::vector<ThumbnailListener*>::iterator f = std::find (listeners.begin(), listeners.end(), tnl);
1063 
1064     if (f != listeners.end()) {
1065         listeners.erase (f);
1066         decreaseRef();
1067     }
1068 }
1069 
1070 // Calculates the standard filename for the automatically named batch result
1071 // and opens it in OS default viewer
1072 // destination: 1=Batch conf. file; 2=batch out dir; 3=RAW dir
1073 // Return: Success?
openDefaultViewer(int destination)1074 bool Thumbnail::openDefaultViewer(int destination)
1075 {
1076 
1077 #ifdef WIN32
1078     Glib::ustring openFName;
1079 
1080     if (destination == 1) {
1081         openFName = Glib::ustring::compose ("%1.%2", BatchQueue::calcAutoFileNameBase(fname), options.saveFormatBatch.format);
1082 
1083         if (Glib::file_test (openFName, Glib::FILE_TEST_EXISTS)) {
1084             wchar_t *wfilename = (wchar_t*)g_utf8_to_utf16 (openFName.c_str(), -1, NULL, NULL, NULL);
1085             ShellExecuteW(NULL, L"open", wfilename, NULL, NULL, SW_SHOWMAXIMIZED );
1086             g_free(wfilename);
1087         } else {
1088             printf("%s not found\n", openFName.data());
1089             return false;
1090         }
1091     } else {
1092         openFName = destination == 3 ? fname
1093                     : Glib::ustring::compose ("%1.%2", BatchQueue::calcAutoFileNameBase(fname), options.saveFormatBatch.format);
1094 
1095         printf("Opening %s\n", openFName.c_str());
1096 
1097         if (Glib::file_test (openFName, Glib::FILE_TEST_EXISTS)) {
1098             // Output file exists, so open explorer and select output file
1099             wchar_t* org = (wchar_t*)g_utf8_to_utf16 (Glib::ustring::compose("/select,\"%1\"", openFName).c_str(), -1, NULL, NULL, NULL);
1100             wchar_t* par = new wchar_t[wcslen(org) + 1];
1101             wcscpy(par, org);
1102 
1103             // In this case the / disturbs
1104             wchar_t* p = par + 1; // skip the first backslash
1105 
1106             while (*p != 0) {
1107                 if (*p == L'/') {
1108                     *p = L'\\';
1109                 }
1110 
1111                 p++;
1112             }
1113 
1114             ShellExecuteW(NULL, L"open", L"explorer.exe", par, NULL, SW_SHOWNORMAL );
1115 
1116             delete[] par;
1117             g_free(org);
1118         } else if (Glib::file_test (Glib::path_get_dirname(openFName), Glib::FILE_TEST_EXISTS)) {
1119             // Out file does not exist, but directory
1120             wchar_t *wfilename = (wchar_t*)g_utf8_to_utf16 (Glib::path_get_dirname(openFName).c_str(), -1, NULL, NULL, NULL);
1121             ShellExecuteW(NULL, L"explore", wfilename, NULL, NULL, SW_SHOWNORMAL );
1122             g_free(wfilename);
1123         } else {
1124             printf("File and dir not found\n");
1125             return false;
1126         }
1127     }
1128 
1129     return true;
1130 
1131 #else
1132     // TODO: Add more OSes here
1133     printf("Automatic opening not supported on this OS\n");
1134     return false;
1135 #endif
1136 
1137 }
1138 
imageLoad(bool loading)1139 bool Thumbnail::imageLoad(bool loading)
1140 {
1141     MyMutex::MyLock lock(mutex);
1142     bool previous = imageLoading;
1143 
1144     if( loading && !previous ) {
1145         imageLoading = true;
1146         return true;
1147     } else if( !loading ) {
1148         imageLoading = false;
1149     }
1150 
1151     return false;
1152 }
1153 
getCamWB(double & temp,double & green) const1154 void Thumbnail::getCamWB(double& temp, double& green) const
1155 {
1156     if (tpp) {
1157         tpp->getCamWB  (temp, green);
1158     } else {
1159         temp = green = -1.0;
1160     }
1161 }
1162 
getSpotWB(int x,int y,int rect,double & temp,double & green)1163 void Thumbnail::getSpotWB(int x, int y, int rect, double& temp, double& green)
1164 {
1165     if (tpp) {
1166         tpp->getSpotWB (getProcParams(), x, y, rect, temp, green);
1167     } else {
1168         temp = green = -1.0;
1169     }
1170 }
1171 
applyAutoExp(rtengine::procparams::ProcParams & pparams)1172 void Thumbnail::applyAutoExp (rtengine::procparams::ProcParams& pparams)
1173 {
1174     if (tpp) {
1175         tpp->applyAutoExp (pparams);
1176     }
1177 }
1178 
getCacheImageData()1179 const CacheImageData* Thumbnail::getCacheImageData()
1180 {
1181     return &cfs;
1182 }
1183 
getMD5() const1184 std::string Thumbnail::getMD5() const
1185 {
1186     return cfs.md5;
1187 }
1188 
isQuick() const1189 bool Thumbnail::isQuick() const
1190 {
1191     return cfs.thumbImgType == CacheImageData::QUICK_THUMBNAIL;
1192 }
1193 
isPParamsValid() const1194 bool Thumbnail::isPParamsValid() const
1195 {
1196     return pparamsValid;
1197 }
1198