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