1 /*
2  *  This file is part of RawTherapee.
3  *
4  *  Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
5  *
6  *  RawTherapee is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  RawTherapee is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with RawTherapee.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 #include <clocale>
20 
21 #include <lcms2.h>
22 
23 #include <glib/gstdio.h>
24 
25 #include <glibmm/ustring.h>
26 #include <glibmm/fileutils.h>
27 #include <glibmm/keyfile.h>
28 
29 #include "cieimage.h"
30 #include "color.h"
31 #include "colortemp.h"
32 #include "curves.h"
33 #include "dcp.h"
34 #include "iccstore.h"
35 #include "image8.h"
36 #include "improcfun.h"
37 #include "jpeg.h"
38 #include "labimage.h"
39 #include "median.h"
40 #include "procparams.h"
41 #include "rawimage.h"
42 #include "rawimagesource.h"
43 #include "rtengine.h"
44 #include "rtthumbnail.h"
45 #include "settings.h"
46 #include "stdimagesource.h"
47 #include "StopWatch.h"
48 #include "utils.h"
49 
50 namespace
51 {
52 
checkRawImageThumb(const rtengine::RawImage & raw_image)53 bool checkRawImageThumb (const rtengine::RawImage& raw_image)
54 {
55     if (!raw_image.is_supportedThumb()) {
56         return false;
57     }
58 
59     const ssize_t length =
60         fdata (raw_image.get_thumbOffset(), raw_image.get_file())[1] != 0xD8 && raw_image.is_ppmThumb()
61         ? raw_image.get_thumbWidth() * raw_image.get_thumbHeight() * (raw_image.get_thumbBPS() / 8) * 3
62         : raw_image.get_thumbLength();
63 
64     return raw_image.get_thumbOffset() + length <= raw_image.get_file()->size;
65 }
66 
67 
scale_colors(rtengine::RawImage * ri,float scale_mul[4],float cblack[4],bool multiThread)68 void scale_colors (rtengine::RawImage *ri, float scale_mul[4], float cblack[4], bool multiThread)
69 {
70     DCraw::dcrawImage_t image = ri->get_image();
71     const int height = ri->get_iheight();
72     const int width = ri->get_iwidth();
73     const int top_margin = ri->get_topmargin();
74     const int left_margin = ri->get_leftmargin();
75     const int raw_width = ri->get_rawwidth();
76     const bool isFloat = ri->isFloat();
77     const float * const float_raw_image = ri->get_FloatRawImage();
78 
79     if (ri->isBayer()) {
80 #ifdef _OPENMP
81         #pragma omp parallel for if(multiThread)
82 #endif
83         for (int row = 0; row < height; ++row) {
84             unsigned c0 = ri->FC (row, 0);
85             unsigned c1 = ri->FC (row, 1);
86             int col = 0;
87 
88             for (; col < width - 1; col += 2) {
89                 float val0;
90                 float val1;
91                 if (isFloat) {
92                     val0 = float_raw_image[(row + top_margin) * raw_width + col + left_margin];
93                     val1 = float_raw_image[(row + top_margin) * raw_width + col + left_margin + 1];
94                 } else {
95                     val0 = image[row * width + col][c0];
96                     val1 = image[row * width + col + 1][c1];
97                 }
98                 val0 -= cblack[c0];
99                 val1 -= cblack[c1];
100                 val0 *= scale_mul[c0];
101                 val1 *= scale_mul[c1];
102                 image[row * width + col][c0] = rtengine::CLIP (val0);
103                 image[row * width + col + 1][c1] = rtengine::CLIP (val1);
104             }
105 
106             if (col < width) { // in case width is odd
107                 float val0;
108                 if (isFloat) {
109                     val0 = float_raw_image[(row + top_margin) * raw_width + col + left_margin];
110                 } else {
111                     val0 = image[row * width + col][c0];
112                 }
113                 val0 -= cblack[c0];
114                 val0 *= scale_mul[c0];
115                 image[row * width + col][c0] = rtengine::CLIP (val0);
116             }
117         }
118     } else if (ri->isXtrans()) {
119 #ifdef _OPENMP
120         #pragma omp parallel for if(multiThread)
121 #endif
122         for (int row = 0; row < height; ++row) {
123             unsigned c[6];
124             for (int i = 0; i < 6; ++i) {
125                 c[i] = ri->XTRANSFC (row, i);
126             }
127 
128             int col = 0;
129 
130             for (; col < width - 5; col += 6) {
131                 for (int i = 0; i < 6; ++i) {
132                     const unsigned ccol = c[i];
133                     float val;
134                     if (isFloat) {
135                         val = float_raw_image[(row + top_margin) * raw_width + col + i + left_margin];
136                     } else {
137                         val = image[row * width + col + i][ccol];
138                     }
139                     val -= cblack[ccol];
140                     val *= scale_mul[ccol];
141                     image[row * width + col + i][ccol] = rtengine::CLIP (val);
142                 }
143             }
144 
145             for (; col < width; ++col) { // remaining columns
146                 const unsigned ccol = ri->XTRANSFC (row, col);
147                 float val;
148                 if (isFloat) {
149                     val = float_raw_image[(row + top_margin) * raw_width + col + left_margin];
150                 } else {
151                     val = image[row * width + col][ccol];
152                 }
153                 val -= cblack[ccol];
154                 val *= scale_mul[ccol];
155                 image[row * width + col][ccol] = rtengine::CLIP (val);
156             }
157         }
158     } else if (isFloat) {
159 #ifdef _OPENMP
160         #pragma omp parallel for if(multiThread)
161 #endif
162         for (int row = 0; row < height; ++row) {
163             for (int col = 0; col < width; ++col) {
164                 for (int i = 0; i < ri->get_colors(); ++i) {
165                     float val = float_raw_image[(row + top_margin) * raw_width + col + left_margin + i];
166                     val -= cblack[i];
167                     val *= scale_mul[i];
168                     image[row * width + col][i] = val;
169                 }
170             }
171         }
172     } else {
173         const int size = ri->get_iheight() * ri->get_iwidth();
174 
175 #ifdef _OPENMP
176         #pragma omp parallel for if(multiThread)
177 #endif
178         for (int i = 0; i < size; ++i) {
179             for (int j = 0; j < 4; ++j) {
180                 float val = image[i][j];
181                 val -= cblack[j];
182                 val *= scale_mul[j];
183                 image[i][j] = rtengine::CLIP (val);
184             }
185         }
186     }
187 }
188 
189 }
190 
191 namespace rtengine
192 {
193 
194 using namespace procparams;
195 
loadFromImage(const Glib::ustring & fname,int & w,int & h,int fixwh,double wbEq,bool inspectorMode)196 Thumbnail* Thumbnail::loadFromImage (const Glib::ustring& fname, int &w, int &h, int fixwh, double wbEq, bool inspectorMode)
197 {
198 
199     StdImageSource imgSrc;
200 
201     if (imgSrc.load (fname)) {
202         return nullptr;
203     }
204 
205     ImageIO* img = imgSrc.getImageIO();
206 
207     // agriggio -- hotfix for #3794, to be revised once a proper solution is implemented
208     if (std::max(img->getWidth(), img->getHeight()) / std::min(img->getWidth(), img->getHeight()) >= 10) {
209         return nullptr;
210     }
211 
212     Thumbnail* tpp = new Thumbnail ();
213 
214     unsigned char* data;
215     img->getEmbeddedProfileData (tpp->embProfileLength, data);
216 
217     if (data && tpp->embProfileLength) {
218         tpp->embProfileData = new unsigned char [tpp->embProfileLength];
219         memcpy (tpp->embProfileData, data, tpp->embProfileLength);
220     }
221 
222     tpp->scaleForSave = 8192;
223     tpp->defGain = 1.0;
224     tpp->gammaCorrected = false;
225     tpp->isRaw = 0;
226     memset (tpp->colorMatrix, 0, sizeof (tpp->colorMatrix));
227     tpp->colorMatrix[0][0] = 1.0;
228     tpp->colorMatrix[1][1] = 1.0;
229     tpp->colorMatrix[2][2] = 1.0;
230 
231     if (inspectorMode) {
232         // Special case, meaning that we want a full sized thumbnail image (e.g. for the Inspector feature)
233         w = img->getWidth();
234         h = img->getHeight();
235         tpp->scale = 1.;
236     } else {
237         if (fixwh == 1) {
238             w = h * img->getWidth() / img->getHeight();
239             tpp->scale = (double)img->getHeight() / h;
240         } else {
241             h = w * img->getHeight() / img->getWidth();
242             tpp->scale = (double)img->getWidth() / w;
243         }
244     }
245 
246     // bilinear interpolation
247     if (tpp->thumbImg) {
248         delete tpp->thumbImg;
249         tpp->thumbImg = nullptr;
250     }
251 
252     if (inspectorMode) {
253         // we want an Image8
254         if (img->getType() == rtengine::sImage8) {
255             // copy the image
256             Image8 *srcImg = static_cast<Image8*> (img);
257             Image8 *thImg = new Image8 (w, h);
258             srcImg->copyData (thImg);
259             tpp->thumbImg = thImg;
260         } else {
261             // copy the image with a conversion
262             tpp->thumbImg = resizeTo<Image8> (w, h, TI_Bilinear, img);
263         }
264     } else {
265         // we want the same image type than the source file
266         tpp->thumbImg = resizeToSameType (w, h, TI_Bilinear, img);
267 
268         // histogram computation
269         tpp->aeHistCompression = 3;
270         tpp->aeHistogram (65536 >> tpp->aeHistCompression);
271 
272         double avg_r = 0;
273         double avg_g = 0;
274         double avg_b = 0;
275         int n = 0;
276 
277         if (img->getType() == rtengine::sImage8) {
278             Image8 *image = static_cast<Image8*> (img);
279             image->computeHistogramAutoWB (avg_r, avg_g, avg_b, n, tpp->aeHistogram, tpp->aeHistCompression);
280         } else if (img->getType() == sImage16) {
281             Image16 *image = static_cast<Image16*> (img);
282             image->computeHistogramAutoWB (avg_r, avg_g, avg_b, n, tpp->aeHistogram, tpp->aeHistCompression);
283         } else if (img->getType() == sImagefloat) {
284             Imagefloat *image = static_cast<Imagefloat*> (img);
285             image->computeHistogramAutoWB (avg_r, avg_g, avg_b, n, tpp->aeHistogram, tpp->aeHistCompression);
286         } else {
287             printf ("loadFromImage: Unsupported image type \"%s\"!\n", img->getType());
288         }
289 
290         ProcParams paramsForAutoExp; // Dummy for constructor
291         ImProcFunctions ipf (&paramsForAutoExp, false);
292         ipf.getAutoExp (tpp->aeHistogram, tpp->aeHistCompression, 0.02, tpp->aeExposureCompensation, tpp->aeLightness, tpp->aeContrast, tpp->aeBlack, tpp->aeHighlightCompression, tpp->aeHighlightCompressionThreshold);
293         tpp->aeValid = true;
294 
295         if (n > 0) {
296             ColorTemp cTemp;
297 
298             tpp->redAWBMul   = avg_r / double (n);
299             tpp->greenAWBMul = avg_g / double (n);
300             tpp->blueAWBMul  = avg_b / double (n);
301             tpp->wbEqual = wbEq;
302             tpp->wbTempBias = 0.0;
303 
304             cTemp.mul2temp (tpp->redAWBMul, tpp->greenAWBMul, tpp->blueAWBMul, tpp->wbEqual, tpp->autoWBTemp, tpp->autoWBGreen);
305         }
306 
307         tpp->init ();
308     }
309 
310     return tpp;
311 }
312 
313 
314 namespace {
315 
load_inspector_mode(const Glib::ustring & fname,RawMetaDataLocation & rml,eSensorType & sensorType,int & w,int & h)316 Image8 *load_inspector_mode(const Glib::ustring &fname, RawMetaDataLocation &rml, eSensorType &sensorType, int &w, int &h)
317 {
318     BENCHFUN
319 
320     RawImageSource src;
321     int err = src.load(fname, true);
322     if (err) {
323         return nullptr;
324     }
325 
326     src.getFullSize(w, h);
327     sensorType = src.getSensorType();
328 
329     ProcParams neutral;
330     neutral.raw.bayersensor.method = RAWParams::BayerSensor::getMethodString(RAWParams::BayerSensor::Method::FAST);
331     neutral.raw.xtranssensor.method = RAWParams::XTransSensor::getMethodString(RAWParams::XTransSensor::Method::FAST);
332     neutral.icm.inputProfile = "(camera)";
333     neutral.icm.workingProfile = settings->srgb;
334 
335     src.preprocess(neutral.raw, neutral.lensProf, neutral.coarse, false);
336     double thresholdDummy = 0.f;
337     src.demosaic(neutral.raw, false, thresholdDummy);
338 
339     PreviewProps pp(0, 0, w, h, 1);
340 
341     Imagefloat tmp(w, h);
342     src.getImage(src.getWB(), TR_NONE, &tmp, pp, neutral.toneCurve, neutral.raw);
343     src.convertColorSpace(&tmp, neutral.icm, src.getWB());
344 
345     Image8 *img = new Image8(w, h);
346     const float f = 255.f/65535.f;
347 #ifdef _OPENMP
348     #pragma omp parallel for
349 #endif
350     for (int y = 0; y < h; ++y) {
351         for (int x = 0; x < w; ++x) {
352             float r = tmp.r(y, x);
353             float g = tmp.g(y, x);
354             float b = tmp.b(y, x);
355             // avoid magenta highlights
356             if (r > MAXVALF && b > MAXVALF) {
357                 float v = CLIP((r + g + b) / 3.f) * f;
358                 img->r(y, x) = img->g(y, x) = img->b(y, x) = v;
359             } else {
360                 img->r(y, x) = Color::gamma_srgbclipped(r) * f;
361                 img->g(y, x) = Color::gamma_srgbclipped(g) * f;
362                 img->b(y, x) = Color::gamma_srgbclipped(b) * f;
363             }
364         }
365     }
366 
367     return img;
368 }
369 
370 } // namespace
371 
loadQuickFromRaw(const Glib::ustring & fname,RawMetaDataLocation & rml,eSensorType & sensorType,int & w,int & h,int fixwh,bool rotate,bool inspectorMode,bool forHistogramMatching)372 Thumbnail* Thumbnail::loadQuickFromRaw (const Glib::ustring& fname, RawMetaDataLocation& rml, eSensorType &sensorType, int &w, int &h, int fixwh, bool rotate, bool inspectorMode, bool forHistogramMatching)
373 {
374     Thumbnail* tpp = new Thumbnail ();
375     tpp->isRaw = 1;
376     memset (tpp->colorMatrix, 0, sizeof (tpp->colorMatrix));
377     tpp->colorMatrix[0][0] = 1.0;
378     tpp->colorMatrix[1][1] = 1.0;
379     tpp->colorMatrix[2][2] = 1.0;
380 
381     if (inspectorMode && !forHistogramMatching && settings->thumbnail_inspector_mode == Settings::ThumbnailInspectorMode::RAW) {
382         Image8 *img = load_inspector_mode(fname, rml, sensorType, w, h);
383         if (!img) {
384             delete tpp;
385             return nullptr;
386         }
387 
388         tpp->scale = 1.;
389         tpp->thumbImg = img;
390 
391         return tpp;
392     }
393 
394     RawImage *ri = new RawImage (fname);
395     unsigned int imageNum = 0;
396     int r = ri->loadRaw (false, imageNum, false);
397 
398     if ( r ) {
399         delete tpp;
400         delete ri;
401         sensorType = ST_NONE;
402         return nullptr;
403     }
404 
405     sensorType = ri->getSensorType();
406 
407     rml.exifBase = ri->get_exifBase();
408     rml.ciffBase = ri->get_ciffBase();
409     rml.ciffLength = ri->get_ciffLen();
410 
411     Image8* img = new Image8 ();
412     // No sample format detection occurred earlier, so we set them here,
413     // as they are mandatory for the setScanline method
414     img->setSampleFormat (IIOSF_UNSIGNED_CHAR);
415     img->setSampleArrangement (IIOSA_CHUNKY);
416 
417     int err = 1;
418 
419     // See if it is something we support
420     if (checkRawImageThumb (*ri)) {
421         const char* data ((const char*)fdata (ri->get_thumbOffset(), ri->get_file()));
422 
423         if ( (unsigned char)data[1] == 0xd8 ) {
424             err = img->loadJPEGFromMemory (data, ri->get_thumbLength());
425         } else if (ri->is_ppmThumb()) {
426             err = img->loadPPMFromMemory (data, ri->get_thumbWidth(), ri->get_thumbHeight(), ri->get_thumbSwap(), ri->get_thumbBPS());
427         }
428     }
429 
430     // did we succeed?
431     if ( err ) {
432         if (settings->verbose) {
433             std::cout << "Could not extract thumb from " << fname.c_str() << std::endl;
434         }
435         delete tpp;
436         delete img;
437         delete ri;
438         return nullptr;
439     }
440 
441     if (inspectorMode) {
442         // Special case, meaning that we want a full sized thumbnail image (e.g. for the Inspector feature)
443         w = img->getWidth();
444         h = img->getHeight();
445         tpp->scale = 1.;
446 
447         if (!forHistogramMatching && settings->thumbnail_inspector_mode == Settings::ThumbnailInspectorMode::RAW_IF_NOT_JPEG_FULLSIZE && float(std::max(w, h))/float(std::max(ri->get_width(), ri->get_height())) < 0.9f) {
448             delete img;
449             delete ri;
450 
451             img = load_inspector_mode(fname, rml, sensorType, w, h);
452             if (!img) {
453                 delete tpp;
454                 return nullptr;
455             }
456 
457             tpp->scale = 1.;
458             tpp->thumbImg = img;
459 
460             return tpp;
461         }
462     } else {
463         if (fixwh == 1) {
464             w = h * img->getWidth() / img->getHeight();
465             tpp->scale = (double)img->getHeight() / h;
466         } else {
467             h = w * img->getHeight() / img->getWidth();
468             tpp->scale = (double)img->getWidth() / w;
469         }
470     }
471 
472     if (tpp->thumbImg) {
473         delete tpp->thumbImg;
474         tpp->thumbImg = nullptr;
475     }
476 
477     if (inspectorMode) {
478         tpp->thumbImg = img;
479     } else {
480         tpp->thumbImg = resizeTo<Image8> (w, h, TI_Nearest, img);
481         delete img;
482     }
483 
484     if (rotate && ri->get_rotateDegree() > 0) {
485         std::string fname = ri->get_filename();
486         std::string suffix = fname.length() > 4 ? fname.substr (fname.length() - 3) : "";
487 
488         for (unsigned int i = 0; i < suffix.length(); i++) {
489             suffix[i] = std::tolower (suffix[i]);
490         }
491 
492         // Leaf .mos, Mamiya .mef and Phase One .iiq files have thumbnails already rotated.
493         if (suffix != "mos" && suffix != "mef" && suffix != "iiq")  {
494             tpp->thumbImg->rotate (ri->get_rotateDegree());
495             // width/height may have changed after rotating
496             w = tpp->thumbImg->getWidth();
497             h = tpp->thumbImg->getHeight();
498         }
499     }
500 
501     if (!inspectorMode) {
502         tpp->init ();
503     }
504 
505     delete ri;
506 
507     return tpp;
508 }
509 
510 #define FISRED(filter,row,col) \
511     ((filter >> ((((row) << 1 & 14) + ((col) & 1)) << 1) & 3)==0 || !filter)
512 #define FISGREEN(filter,row,col) \
513     ((filter >> ((((row) << 1 & 14) + ((col) & 1)) << 1) & 3)==1 || !filter)
514 #define FISBLUE(filter,row,col) \
515     ((filter >> ((((row) << 1 & 14) + ((col) & 1)) << 1) & 3)==2 || !filter)
516 
loadMetaDataFromRaw(const Glib::ustring & fname)517 RawMetaDataLocation Thumbnail::loadMetaDataFromRaw (const Glib::ustring& fname)
518 {
519     RawMetaDataLocation rml;
520     rml.exifBase = -1;
521     rml.ciffBase = -1;
522     rml.ciffLength = -1;
523 
524     RawImage ri (fname);
525     unsigned int imageNum = 0;
526 
527     int r = ri.loadRaw (false, imageNum);
528 
529     if ( !r ) {
530         rml.exifBase = ri.get_exifBase();
531         rml.ciffBase = ri.get_ciffBase();
532         rml.ciffLength = ri.get_ciffLen();
533     }
534 
535     return rml;
536 }
537 
loadFromRaw(const Glib::ustring & fname,RawMetaDataLocation & rml,eSensorType & sensorType,int & w,int & h,int fixwh,double wbEq,bool rotate,bool forHistogramMatching)538 Thumbnail* Thumbnail::loadFromRaw (const Glib::ustring& fname, RawMetaDataLocation& rml, eSensorType &sensorType, int &w, int &h, int fixwh, double wbEq, bool rotate, bool forHistogramMatching)
539 {
540     RawImage *ri = new RawImage (fname);
541     unsigned int tempImageNum = 0;
542 
543     int r = ri->loadRaw (1, tempImageNum, 0);
544 
545     if ( r ) {
546         delete ri;
547         sensorType = ST_NONE;
548         return nullptr;
549     }
550 
551     if (ri->getFrameCount() == 7) {
552         // special case for Hasselblad H6D-100cMS pixelshift files
553         // first frame is not bayer, load second frame
554         int r = ri->loadRaw (1, 1, 0);
555 
556         if ( r ) {
557             delete ri;
558             sensorType = ST_NONE;
559             return nullptr;
560         }
561     }
562     sensorType = ri->getSensorType();
563 
564     int width = ri->get_width();
565     int height = ri->get_height();
566     rtengine::Thumbnail* tpp = new rtengine::Thumbnail;
567 
568     tpp->isRaw = true;
569     tpp->embProfile = nullptr;
570     tpp->embProfileData = nullptr;
571     tpp->embProfileLength = ri->get_profileLen();
572 
573     if (ri->get_profileLen())
574         tpp->embProfile = cmsOpenProfileFromMem (ri->get_profile(),
575                           ri->get_profileLen()); //\ TODO check if mutex is needed
576 
577     tpp->redMultiplier = ri->get_pre_mul (0);
578     tpp->greenMultiplier = ri->get_pre_mul (1);
579     tpp->blueMultiplier = ri->get_pre_mul (2);
580 
581     float pre_mul[4], scale_mul[4], cblack[4];
582     ri->get_colorsCoeff (pre_mul, scale_mul, cblack, false);
583     scale_colors (ri, scale_mul, cblack, forHistogramMatching); // enable multithreading when forHistogramMatching is true
584 
585     ri->pre_interpolate();
586 
587     rml.exifBase = ri->get_exifBase();
588     rml.ciffBase = ri->get_ciffBase();
589     rml.ciffLength = ri->get_ciffLen();
590 
591     tpp->camwbRed = tpp->redMultiplier / pre_mul[0]; //ri->get_pre_mul(0);
592     tpp->camwbGreen = tpp->greenMultiplier / pre_mul[1]; //ri->get_pre_mul(1);
593     tpp->camwbBlue = tpp->blueMultiplier / pre_mul[2]; //ri->get_pre_mul(2);
594     //tpp->defGain = 1.0 / min(ri->get_pre_mul(0), ri->get_pre_mul(1), ri->get_pre_mul(2));
595     tpp->defGain = max (scale_mul[0], scale_mul[1], scale_mul[2], scale_mul[3]) / min (scale_mul[0], scale_mul[1], scale_mul[2], scale_mul[3]);
596     tpp->defGain *= std::pow(2, ri->getBaselineExposure());
597 
598     tpp->gammaCorrected = true;
599 
600     unsigned filter = ri->get_filters();
601     int firstgreen = 1;
602 
603     // locate first green location in the first row
604     if (ri->getSensorType() == ST_BAYER)
605         while (!FISGREEN (filter, 1, firstgreen) && firstgreen < 3) {
606             firstgreen++;
607         }
608 
609     int skip = 1;
610 
611     if (ri->get_FujiWidth() != 0) {
612         if (fixwh == 1) { // fix height, scale width
613             skip = ((ri->get_height() - ri->get_FujiWidth()) / sqrt (0.5) - firstgreen - 1) / h;
614         } else {
615             skip = (ri->get_FujiWidth() / sqrt (0.5) - firstgreen - 1) / w;
616         }
617     } else {
618         if (fixwh == 1) { // fix height, scale width
619             skip = (ri->get_height() - firstgreen - 1) / h;
620         } else {
621             skip = (ri->get_width() - firstgreen - 1) / w;
622         }
623     }
624 
625     if (skip % 2) {
626         skip--;
627     }
628 
629     if (skip < 2) {
630         skip = 2;
631     }
632 
633     int hskip = skip, vskip = skip;
634 
635     if (!ri->get_model().compare ("D1X")) {
636         hskip *= 2;
637     }
638 
639     int rofs = 0;
640     int tmpw = (width - 2) / hskip;
641     int tmph = (height - 2) / vskip;
642 
643     DCraw::dcrawImage_t image = ri->get_image();
644 
645     Imagefloat* tmpImg = new Imagefloat (tmpw, tmph);
646 
647     if (ri->getSensorType() == ST_BAYER) {
648         // demosaicing! (sort of)
649         for (int row = 1, y = 0; row < height - 1 && y < tmph; row += vskip, y++) {
650             rofs = row * width;
651 
652             for (int col = firstgreen, x = 0; col < width - 1 && x < tmpw; col += hskip, x++) {
653                 int ofs = rofs + col;
654                 int g = image[ofs][1];
655                 int r, b;
656 
657                 if (FISRED (filter, row, col + 1)) {
658                     r = (image[ofs + 1    ][0] + image[ofs - 1    ][0]) >> 1;
659                     b = (image[ofs + width][2] + image[ofs - width][2]) >> 1;
660                 } else {
661                     b = (image[ofs + 1    ][2] + image[ofs - 1    ][2]) >> 1;
662                     r = (image[ofs + width][0] + image[ofs - width][0]) >> 1;
663                 }
664 
665                 tmpImg->r (y, x) = r;
666                 tmpImg->g (y, x) = g;
667                 tmpImg->b (y, x) = b;
668             }
669         }
670     } else if (ri->get_colors() == 1) {
671         for (int row = 1, y = 0; row < height - 1 && y < tmph; row += vskip, y++) {
672             rofs = row * width;
673 
674             for (int col = firstgreen, x = 0; col < width - 1 && x < tmpw; col
675                     += hskip, x++) {
676                 int ofs = rofs + col;
677                 tmpImg->r (y, x) = tmpImg->g (y, x) = tmpImg->b (y, x) = image[ofs][0];
678             }
679         }
680     } else {
681         if (ri->getSensorType() == ST_FUJI_XTRANS) {
682             for ( int row = 1, y = 0; row < height - 1 && y < tmph; row += vskip, y++) {
683                 rofs = row * width;
684 
685                 for ( int col = 1, x = 0; col < width - 1 && x < tmpw; col += hskip, x++ ) {
686                     int ofs = rofs + col;
687                     float sum[3] = {};
688                     int c;
689 
690                     for (int v = -1; v <= 1; v++) {
691                         for (int h = -1; h <= 1; h++) {
692                             c = ri->XTRANSFC (row + v, col + h);
693                             sum[c] += image[ofs + v * width + h][c];
694                         }
695                     }
696 
697                     c = ri->XTRANSFC (row, col);
698 
699                     switch (c) {
700                         case 0:
701                             tmpImg->r (y, x) = image[ofs][0];
702                             tmpImg->g (y, x) = sum[1] / 5.f;
703                             tmpImg->b (y, x) = sum[2] / 3.f;
704                             break;
705 
706                         case 1:
707                             tmpImg->r (y, x) = sum[0] / 2.f;
708                             tmpImg->g (y, x) = image[ofs][1];
709                             tmpImg->b (y, x) = sum[2] / 2.f;
710                             break;
711 
712                         case 2:
713                             tmpImg->r (y, x) = sum[0] / 3.f;
714                             tmpImg->g (y, x) = sum[1] / 5.f;
715                             tmpImg->b (y, x) = image[ofs][2];
716                             break;
717                     }
718                 }
719             }
720         } else {
721             int iwidth = ri->get_iwidth();
722             int iheight = ri->get_iheight();
723             int left_margin = ri->get_leftmargin();
724             firstgreen += left_margin;
725             int top_margin = ri->get_topmargin();
726             int wmax = tmpw;
727             int hmax = tmph;
728 
729             if ((ri->get_maker() == "Sigma" || ri->get_maker() == "Pentax" || ri->get_maker() == "Sony") && ri->DNGVERSION()) { // Hack to prevent sigma dng files from crashing
730                 wmax = (width - 2 - left_margin) / hskip;
731                 hmax = (height - 2 - top_margin) / vskip;
732             }
733 
734             int y = 0;
735 
736             for (int row = 1 + top_margin; row < iheight + top_margin  - 1 && y < hmax; row += vskip, y++) {
737                 rofs = row * iwidth;
738 
739                 int x = 0;
740 
741                 for (int col = firstgreen; col < iwidth + left_margin - 1 && x < wmax; col += hskip, x++) {
742                     int ofs = rofs + col;
743                     tmpImg->r (y, x) = image[ofs][0];
744                     tmpImg->g (y, x) = image[ofs][1];
745                     tmpImg->b (y, x) = image[ofs][2];
746                 }
747 
748                 for (; x < tmpw; ++x) {
749                     tmpImg->r (y, x) = tmpImg->g (y, x) = tmpImg->b (y, x) = 0;
750                 }
751             }
752 
753             for (; y < tmph; ++y) {
754                 for (int x = 0; x < tmpw; ++x) {
755                     tmpImg->r (y, x) = tmpImg->g (y, x) = tmpImg->b (y, x) = 0;
756                 }
757             }
758         }
759     }
760 
761     if (ri->get_FujiWidth() != 0) {
762         int fw = ri->get_FujiWidth() / hskip;
763         double step = sqrt (0.5);
764         int wide = fw / step;
765         int high = (tmph - fw) / step;
766         Imagefloat* fImg = new Imagefloat (wide, high);
767         float r, c;
768 
769         for (int row = 0; row < high; row++)
770             for (int col = 0; col < wide; col++) {
771                 int ur = r = fw + (row - col) * step;
772                 int uc = c = (row + col) * step;
773 
774                 if (ur > tmph - 2 || uc > tmpw - 2) {
775                     continue;
776                 }
777 
778                 double fr = r - ur;
779                 double fc = c - uc;
780                 fImg->r (row, col) = (tmpImg->r (ur, uc) * (1 - fc) + tmpImg->r (ur, uc + 1) * fc) * (1 - fr)   +   (tmpImg->r (ur + 1, uc) * (1 - fc) + tmpImg->r (ur + 1, uc + 1) * fc) * fr;
781                 fImg->g (row, col) = (tmpImg->g (ur, uc) * (1 - fc) + tmpImg->g (ur, uc + 1) * fc) * (1 - fr)   +   (tmpImg->g (ur + 1, uc) * (1 - fc) + tmpImg->g (ur + 1, uc + 1) * fc) * fr;
782                 fImg->b (row, col) = (tmpImg->b (ur, uc) * (1 - fc) + tmpImg->b (ur, uc + 1) * fc) * (1 - fr)   +   (tmpImg->b (ur + 1, uc) * (1 - fc) + tmpImg->b (ur + 1, uc + 1) * fc) * fr;
783             }
784 
785         delete tmpImg;
786         tmpImg = fImg;
787         tmpw = wide;
788         tmph = high;
789     }
790 
791     const bool rotate_90 =
792         rotate
793         && (
794             ri->get_rotateDegree() == 90
795             || ri->get_rotateDegree() == 270
796         );
797 
798     if (rotate_90) {
799         std::swap (tmpw, tmph);
800     }
801 
802     if (fixwh == 1) { // fix height, scale width
803         w = tmpw * h / tmph;
804     } else {
805         h = tmph * w / tmpw;
806     }
807 
808     if (tpp->thumbImg) {
809         delete tpp->thumbImg;
810     }
811 
812     if (rotate_90) {
813         tpp->thumbImg = resizeTo<Image16> (h, w, TI_Bilinear, tmpImg);
814     } else {
815         tpp->thumbImg = resizeTo<Image16> (w, h, TI_Bilinear, tmpImg);
816     }
817 
818     delete tmpImg;
819 
820 
821     if (ri->get_FujiWidth() != 0) {
822         tpp->scale = (double) (height - ri->get_FujiWidth()) * 2.0 / (rotate_90 ? w : h);
823     } else {
824         tpp->scale = (double) height / (rotate_90 ? w : h);
825     }
826     if(!forHistogramMatching) { // we don't need this for histogram matching
827 
828         // generate histogram for auto exposure, also calculate autoWB
829         tpp->aeHistCompression = 3;
830         tpp->aeHistogram(65536 >> tpp->aeHistCompression);
831         tpp->aeHistogram.clear();
832 
833         const unsigned int add = filter ? 1 : 4 / ri->get_colors();
834 
835         double pixSum[3] = {0.0};
836         unsigned int n[3] = {0};
837         const double compression = pow(2.0, tpp->aeHistCompression);
838         const double camWb[3] = {tpp->camwbRed / compression, tpp->camwbGreen / compression, tpp->camwbBlue / compression};
839         const double clipval = 64000.0 / tpp->defGain;
840 
841         for (int i = 32; i < height - 32; i++) {
842             int start, end;
843 
844             if (ri->get_FujiWidth() != 0) {
845                 int fw = ri->get_FujiWidth();
846                 start = ABS (fw - i) + 32;
847                 end = min (height + width - fw - i, fw + i) - 32;
848             } else {
849                 start = 32;
850                 end = width - 32;
851             }
852 
853             if (ri->get_colors() == 1) {
854                 for (int j = start; j < end; j++) {
855                     tpp->aeHistogram[image[i * width + j][0] >> tpp->aeHistCompression]++;
856                 }
857             } else if (ri->getSensorType() == ST_BAYER) {
858                 int c0 = ri->FC(i, start);
859                 int c1 = ri->FC(i, start + 1);
860                 int j = start;
861                 int n0 = 0;
862                 int n1 = 0;
863                 double pixSum0 = 0.0;
864                 double pixSum1 = 0.0;
865                 for (; j < end - 1; j+=2) {
866                     double v0 = image[i * width + j][c0];
867                     tpp->aeHistogram[(int)(camWb[c0] * v0)]++;
868                     if (v0 <= clipval) {
869                         pixSum0 += v0;
870                         n0++;
871                     }
872                     double v1 = image[i * width + j + 1][c1];
873                     tpp->aeHistogram[(int)(camWb[c1] * v1)]++;
874                     if (v1 <= clipval) {
875                         pixSum1 += v1;
876                         n1++;
877                     }
878                 }
879                 if (j < end) {
880                     double v0 = image[i * width + j][c0];
881                     tpp->aeHistogram[(int)(camWb[c0] * v0)]++;
882                     if (v0 <= clipval) {
883                         pixSum0 += v0;
884                         n0++;
885                     }
886                 }
887                 n[c0] += n0;
888                 n[c1] += n1;
889                 pixSum[c0] += pixSum0;
890                 pixSum[c1] += pixSum1;
891             } else if (ri->getSensorType() == ST_FUJI_XTRANS) {
892                 int c[6];
893                 for(int cc = 0; cc < 6; ++cc) {
894                     c[cc] = ri->XTRANSFC(i, start + cc);
895                 }
896                 int j = start;
897                 for (; j < end - 5; j += 6) {
898                     for(int cc = 0; cc < 6; ++cc) {
899                         double d = image[i * width + j + cc][c[cc]];
900                         tpp->aeHistogram[(int)(camWb[c[cc]] * d)]++;
901                         if (d <= clipval) {
902                             pixSum[c[cc]] += d;
903                             n[c[cc]]++;
904                         }
905                     }
906                 }
907                 for (; j < end; j++) {
908                     if (ri->ISXTRANSGREEN (i, j)) {
909                         double d = image[i * width + j][1];
910                         tpp->aeHistogram[(int)(camWb[1] * d)]++;
911                         if (d <= clipval) {
912                             pixSum[1] += d;
913                             n[1]++;
914                         }
915                     } else if (ri->ISXTRANSRED (i, j)) {
916                         double d = image[i * width + j][0];
917                         tpp->aeHistogram[(int)(camWb[0] * d)]++;
918                         if (d <= clipval) {
919                             pixSum[0] += d;
920                             n[0]++;
921                         }
922                     } else if (ri->ISXTRANSBLUE (i, j)) {
923                         double d = image[i * width + j][2];
924                         tpp->aeHistogram[(int)(camWb[2] * d)]++;
925                         if (d <= clipval) {
926                             pixSum[2] += d;
927                             n[2]++;
928                         }
929                     }
930                 }
931             } else { /* if(ri->getSensorType()==ST_FOVEON) */
932                 for (int j = start; j < end; j++) {
933                     double r = image[i * width + j][0];
934                     if (r <= clipval) {
935                         pixSum[0] += r;
936                         n[0]++;
937                     }
938                     double g = image[i * width + j][1];
939                     if (g <= clipval) {
940                         pixSum[1] += g;
941                         n[1]++;
942                     }
943                     tpp->aeHistogram[((int)g) >> tpp->aeHistCompression] += add;
944                     double b = image[i * width + j][2];
945                     if (b <= clipval) {
946                         pixSum[2] += b;
947                         n[2]++;
948                     }
949                     tpp->aeHistogram[((int) (b * 0.5f)) >> tpp->aeHistCompression] += add;
950                 }
951             }
952         }
953         ProcParams paramsForAutoExp; // Dummy for constructor
954         ImProcFunctions ipf (&paramsForAutoExp, false);
955         ipf.getAutoExp (tpp->aeHistogram, tpp->aeHistCompression, 0.02, tpp->aeExposureCompensation, tpp->aeLightness, tpp->aeContrast, tpp->aeBlack, tpp->aeHighlightCompression, tpp->aeHighlightCompressionThreshold);
956         tpp->aeValid = true;
957 
958         if (ri->get_colors() == 1) {
959             pixSum[0] = pixSum[1] = pixSum[2] = 1.;
960             n[0] = n[1] = n[2] = 1;
961         }
962         pixSum[0] *= tpp->defGain;
963         pixSum[1] *= tpp->defGain;
964         pixSum[2] *= tpp->defGain;
965 
966         double reds = pixSum[0] / std::max(n[0], 1u) * tpp->camwbRed;
967         double greens = pixSum[1] / std::max(n[1], 1u) * tpp->camwbGreen;
968         double blues = pixSum[2] / std::max(n[2], 1u) * tpp->camwbBlue;
969 
970         tpp->redAWBMul   = ri->get_rgb_cam (0, 0) * reds + ri->get_rgb_cam (0, 1) * greens + ri->get_rgb_cam (0, 2) * blues;
971         tpp->greenAWBMul = ri->get_rgb_cam (1, 0) * reds + ri->get_rgb_cam (1, 1) * greens + ri->get_rgb_cam (1, 2) * blues;
972         tpp->blueAWBMul  = ri->get_rgb_cam (2, 0) * reds + ri->get_rgb_cam (2, 1) * greens + ri->get_rgb_cam (2, 2) * blues;
973         tpp->wbEqual = wbEq;
974         tpp->wbTempBias = 0.0;
975 
976         ColorTemp cTemp;
977         cTemp.mul2temp (tpp->redAWBMul, tpp->greenAWBMul, tpp->blueAWBMul, tpp->wbEqual, tpp->autoWBTemp, tpp->autoWBGreen);
978     }
979 
980     if (rotate && ri->get_rotateDegree() > 0) {
981         tpp->thumbImg->rotate (ri->get_rotateDegree());
982     }
983 
984     for (int a = 0; a < 3; a++)
985         for (int b = 0; b < 3; b++) {
986             tpp->colorMatrix[a][b] = ri->get_rgb_cam (a, b);
987         }
988 
989     tpp->init();
990     delete ri;
991     return tpp;
992 }
993 #undef FISRED
994 #undef FISGREEN
995 #undef FISBLUE
996 
init()997 void Thumbnail::init ()
998 {
999     RawImageSource::inverse33 (colorMatrix, iColorMatrix);
1000     //colorMatrix is rgb_cam
1001     memset (cam2xyz, 0, sizeof (cam2xyz));
1002 
1003     for (int i = 0; i < 3; i++)
1004         for (int j = 0; j < 3; j++)
1005             for (int k = 0; k < 3; k++) {
1006                 cam2xyz[i][j] += xyz_sRGB[i][k] * colorMatrix[k][j];
1007             }
1008 
1009     camProfile = ICCStore::getInstance()->createFromMatrix (cam2xyz, false, "Camera");
1010 }
1011 
Thumbnail()1012 Thumbnail::Thumbnail () :
1013     camProfile (nullptr),
1014     iColorMatrix{},
1015     cam2xyz{},
1016     thumbImg (nullptr),
1017     camwbRed (1.0),
1018     camwbGreen (1.0),
1019     camwbBlue (1.0),
1020     redAWBMul (-1.0),
1021     greenAWBMul (-1.0),
1022     blueAWBMul (-1.0),
1023     autoWBTemp (2700),
1024     autoWBGreen (1.0),
1025     wbEqual (-1.0),
1026     wbTempBias (0.0),
1027     aeHistCompression (3),
1028     aeValid(false),
1029     aeExposureCompensation(0.0),
1030     aeLightness(0),
1031     aeContrast(0),
1032     aeBlack(0),
1033     aeHighlightCompression(0),
1034     aeHighlightCompressionThreshold(0),
1035     embProfileLength (0),
1036     embProfileData (nullptr),
1037     embProfile (nullptr),
1038     redMultiplier (1.0),
1039     greenMultiplier (1.0),
1040     blueMultiplier (1.0),
1041     scale (1.0),
1042     defGain (1.0),
1043     scaleForSave (8192),
1044     gammaCorrected (false),
1045     colorMatrix{},
1046     isRaw (true)
1047 {
1048 }
1049 
~Thumbnail()1050 Thumbnail::~Thumbnail ()
1051 {
1052 
1053     delete thumbImg;
1054     //delete [] aeHistogram;
1055     delete [] embProfileData;
1056 
1057     if (embProfile) {
1058         cmsCloseProfile (embProfile);
1059     }
1060 
1061     if (camProfile) {
1062         cmsCloseProfile (camProfile);
1063     }
1064 }
1065 
1066 // Simple processing of RAW internal JPGs
quickProcessImage(const procparams::ProcParams & params,int rheight,rtengine::TypeInterpolation interp)1067 IImage8* Thumbnail::quickProcessImage (const procparams::ProcParams& params, int rheight, rtengine::TypeInterpolation interp)
1068 {
1069 
1070     int rwidth;
1071 
1072     if (params.coarse.rotate == 90 || params.coarse.rotate == 270) {
1073         rwidth = rheight;
1074         rheight = thumbImg->getHeight() * rwidth / thumbImg->getWidth();
1075     } else {
1076         rwidth = thumbImg->getWidth() * rheight / thumbImg->getHeight();
1077     }
1078 
1079     Image8* baseImg = resizeTo<Image8> (rwidth, rheight, interp, thumbImg);
1080 
1081     if (params.coarse.rotate) {
1082         baseImg->rotate (params.coarse.rotate);
1083     }
1084 
1085     if (params.coarse.hflip) {
1086         baseImg->hflip ();
1087     }
1088 
1089     if (params.coarse.vflip) {
1090         baseImg->vflip ();
1091     }
1092 
1093     return baseImg;
1094 }
1095 
1096 // Full thumbnail processing, second stage if complete profile exists
processImage(const procparams::ProcParams & params,eSensorType sensorType,int rheight,TypeInterpolation interp,const FramesMetaData * metadata,double & myscale,bool forMonitor,bool forHistogramMatching)1097 IImage8* Thumbnail::processImage (const procparams::ProcParams& params, eSensorType sensorType, int rheight, TypeInterpolation interp, const FramesMetaData *metadata, double& myscale, bool forMonitor, bool forHistogramMatching)
1098 {
1099     unsigned int imgNum = 0;
1100     if (isRaw) {
1101         if (sensorType == ST_BAYER) {
1102             imgNum = rtengine::LIM<unsigned int>(params.raw.bayersensor.imageNum, 0, metadata->getFrameCount() - 1);
1103         } else if (sensorType == ST_FUJI_XTRANS) {
1104             //imgNum = rtengine::LIM<unsigned int>(params.raw.xtranssensor.imageNum, 0, metadata->getFrameCount() - 1)
1105         }
1106     }
1107     std::string camName = metadata->getCamera(imgNum);
1108     float shutter = metadata->getShutterSpeed(imgNum);
1109     float fnumber = metadata->getFNumber(imgNum);
1110     float iso = metadata->getISOSpeed(imgNum);
1111     float fcomp = metadata->getExpComp(imgNum);
1112 
1113     // check if the WB's equalizer value has changed
1114     if (wbEqual < (params.wb.equal - 5e-4) || wbEqual > (params.wb.equal + 5e-4) || wbTempBias < (params.wb.tempBias - 5e-4) || wbTempBias > (params.wb.tempBias + 5e-4)) {
1115         wbEqual = params.wb.equal;
1116         wbTempBias = params.wb.tempBias;
1117         // recompute the autoWB
1118         ColorTemp cTemp;
1119         cTemp.mul2temp (redAWBMul, greenAWBMul, blueAWBMul, wbEqual, autoWBTemp, autoWBGreen);
1120         autoWBTemp += autoWBTemp * wbTempBias;
1121     }
1122 
1123     // compute WB multipliers
1124     ColorTemp currWB = ColorTemp (params.wb.temperature, params.wb.green, params.wb.equal, params.wb.method);
1125 
1126     if (!params.wb.enabled) {
1127         currWB = ColorTemp();
1128     } else if (params.wb.method == "Camera") {
1129         //recall colorMatrix is rgb_cam
1130         double cam_r = colorMatrix[0][0] * camwbRed + colorMatrix[0][1] * camwbGreen + colorMatrix[0][2] * camwbBlue;
1131         double cam_g = colorMatrix[1][0] * camwbRed + colorMatrix[1][1] * camwbGreen + colorMatrix[1][2] * camwbBlue;
1132         double cam_b = colorMatrix[2][0] * camwbRed + colorMatrix[2][1] * camwbGreen + colorMatrix[2][2] * camwbBlue;
1133         currWB = ColorTemp (cam_r, cam_g, cam_b, params.wb.equal);
1134     } else if (params.wb.method == "Auto") {
1135         currWB = ColorTemp (autoWBTemp, autoWBGreen, wbEqual, "Custom");
1136     }
1137 
1138     double rm, gm, bm;
1139     if (currWB.getTemp() < 0) {
1140         rm = redMultiplier;
1141         gm = greenMultiplier;
1142         bm = blueMultiplier;
1143     } else {
1144         double r, g, b;
1145         currWB.getMultipliers (r, g, b);
1146         //iColorMatrix is cam_rgb
1147         rm = iColorMatrix[0][0] * r + iColorMatrix[0][1] * g + iColorMatrix[0][2] * b;
1148         gm = iColorMatrix[1][0] * r + iColorMatrix[1][1] * g + iColorMatrix[1][2] * b;
1149         bm = iColorMatrix[2][0] * r + iColorMatrix[2][1] * g + iColorMatrix[2][2] * b;
1150     }
1151     rm = camwbRed / rm;
1152     gm = camwbGreen / gm;
1153     bm = camwbBlue / bm;
1154     double mul_lum = 0.299 * rm + 0.587 * gm + 0.114 * bm;
1155     float rmi, gmi, bmi;
1156 
1157     rmi = rm * defGain / mul_lum;
1158     gmi = gm * defGain / mul_lum;
1159     bmi = bm * defGain / mul_lum;
1160 
1161     // The RAW exposure is not reflected since it's done in preprocessing. If we only have e.g. the cached thumb,
1162     // that is already preprocessed. So we simulate the effect here roughly my modifying the exposure accordingly
1163     if (isRaw) {
1164         rmi *= params.raw.expos;
1165         gmi *= params.raw.expos;
1166         bmi *= params.raw.expos;
1167     }
1168 
1169     // resize to requested width and perform coarse transformation
1170     int rwidth;
1171 
1172     if (params.coarse.rotate == 90 || params.coarse.rotate == 270) {
1173         rwidth = rheight;
1174         rheight = int (size_t (thumbImg->getHeight()) * size_t (rwidth) / size_t (thumbImg->getWidth()));
1175     } else {
1176         rwidth = int (size_t (thumbImg->getWidth()) * size_t (rheight) / size_t (thumbImg->getHeight()));
1177     }
1178 
1179 
1180     Imagefloat* baseImg = resizeTo<Imagefloat> (rwidth, rheight, interp, thumbImg);
1181 
1182     if (isRaw && params.filmNegative.enabled) {
1183         processFilmNegative(params, baseImg, rwidth, rheight, rmi, gmi, bmi);
1184     }
1185 
1186     if (params.coarse.rotate) {
1187         baseImg->rotate (params.coarse.rotate);
1188         rwidth = baseImg->getWidth();
1189         rheight = baseImg->getHeight();
1190     }
1191 
1192     if (params.coarse.hflip) {
1193         baseImg->hflip ();
1194     }
1195 
1196     if (params.coarse.vflip) {
1197         baseImg->vflip ();
1198     }
1199 
1200 
1201     // apply white balance and raw white point (simulated)
1202     for (int i = 0; i < rheight; i++) {
1203 #ifdef _OPENMP
1204         #pragma omp simd
1205 #endif
1206 
1207         for (int j = 0; j < rwidth; j++) {
1208             float red = baseImg->r (i, j) * rmi;
1209             float green = baseImg->g (i, j) * gmi;
1210             float blue = baseImg->b (i, j) * bmi;
1211 
1212             // avoid magenta highlights if highlight recovery is enabled
1213             if (params.toneCurve.hrenabled && red > MAXVALF && blue > MAXVALF) {
1214                 baseImg->r(i, j) = baseImg->g(i, j) = baseImg->b(i, j) = CLIP((red + green + blue) / 3.f);
1215             } else {
1216                 baseImg->r(i, j) = CLIP(red);
1217                 baseImg->g(i, j) = CLIP(green);
1218                 baseImg->b(i, j) = CLIP(blue);
1219             }
1220         }
1221     }
1222 
1223     // if luma denoise has to be done for thumbnails, it should be right here
1224 
1225     // perform color space transformation
1226 
1227     if (isRaw) {
1228         double pre_mul[3] = { redMultiplier, greenMultiplier, blueMultiplier };
1229         RawImageSource::colorSpaceConversion (baseImg, params.icm, currWB, pre_mul, embProfile, camProfile, cam2xyz, camName );
1230     } else {
1231         StdImageSource::colorSpaceConversion (baseImg, params.icm, embProfile, thumbImg->getSampleFormat());
1232     }
1233 
1234     int fw = baseImg->getWidth();
1235     int fh = baseImg->getHeight();
1236     //ColorTemp::CAT02 (baseImg, &params)   ;//perhaps not good!
1237 
1238     ImProcFunctions ipf (&params, forHistogramMatching); // enable multithreading when forHistogramMatching is true
1239     ipf.setScale (sqrt (double (fw * fw + fh * fh)) / sqrt (double (thumbImg->getWidth() * thumbImg->getWidth() + thumbImg->getHeight() * thumbImg->getHeight()))*scale);
1240     ipf.updateColorProfiles (ICCStore::getInstance()->getDefaultMonitorProfileName(), RenderingIntent(settings->monitorIntent), false, false);
1241 
1242     LUTu hist16 (65536);
1243 
1244     ipf.firstAnalysis (baseImg, params, hist16);
1245 
1246     ipf.dehaze(baseImg);
1247     ipf.ToneMapFattal02(baseImg);
1248 
1249     // perform transform
1250     int origFW;
1251     int origFH;
1252     double tscale = 0.0;
1253     getDimensions (origFW, origFH, tscale);
1254     if (ipf.needsTransform(origFW * tscale + 0.5, origFH * tscale + 0.5, 0, metadata)) {
1255         Imagefloat* trImg = new Imagefloat (fw, fh);
1256         ipf.transform (baseImg, trImg, 0, 0, 0, 0, fw, fh, origFW * tscale + 0.5, origFH * tscale + 0.5, metadata, 0, true); // Raw rotate degree not detectable here
1257         delete baseImg;
1258         baseImg = trImg;
1259     }
1260 
1261     // RGB processing
1262     double  expcomp = params.toneCurve.expcomp;
1263     int     bright = params.toneCurve.brightness;
1264     int     contr = params.toneCurve.contrast;
1265     int     black = params.toneCurve.black;
1266     int     hlcompr = params.toneCurve.hlcompr;
1267     int     hlcomprthresh = params.toneCurve.hlcomprthresh;
1268 
1269     if (params.toneCurve.autoexp) {
1270         if (aeValid) {
1271             expcomp = aeExposureCompensation;
1272             bright = aeLightness;
1273             contr = aeContrast;
1274             black = aeBlack;
1275             hlcompr = aeHighlightCompression;
1276             hlcomprthresh = aeHighlightCompressionThreshold;
1277         } else if (aeHistogram) {
1278             ipf.getAutoExp (aeHistogram, aeHistCompression, 0.02, expcomp, bright, contr, black, hlcompr, hlcomprthresh);
1279         }
1280     }
1281 
1282     LUTf curve1 (65536);
1283     LUTf curve2 (65536);
1284     LUTf curve (65536);
1285 
1286     LUTf satcurve (65536);
1287     LUTf lhskcurve (65536);
1288     LUTf lumacurve (32770, 0); // lumacurve[32768] and lumacurve[32769] will be set to 32768 and 32769 later to allow linear interpolation
1289     LUTf clcurve (65536);
1290     LUTf clToningcurve;
1291     LUTf cl2Toningcurve;
1292     LUTu dummy;
1293 
1294     ToneCurve customToneCurve1, customToneCurve2;
1295     ColorGradientCurve ctColorCurve;
1296     OpacityCurve ctOpacityCurve;
1297 
1298     ColorAppearance customColCurve1;
1299     ColorAppearance customColCurve2;
1300     ColorAppearance customColCurve3;
1301     ToneCurve customToneCurvebw1;
1302     ToneCurve customToneCurvebw2;
1303     CurveFactory::complexCurve (expcomp, black / 65535.0, hlcompr, hlcomprthresh,
1304                                 params.toneCurve.shcompr, bright, contr,
1305                                 params.toneCurve.curve,
1306                                 params.toneCurve.curve2,
1307                                 hist16, curve1, curve2, curve, dummy, customToneCurve1, customToneCurve2, 16);
1308 
1309     LUTf rCurve;
1310     LUTf gCurve;
1311     LUTf bCurve;
1312     CurveFactory::RGBCurve (params.rgbCurves.rcurve, rCurve, 16);
1313     CurveFactory::RGBCurve (params.rgbCurves.gcurve, gCurve, 16);
1314     CurveFactory::RGBCurve (params.rgbCurves.bcurve, bCurve, 16);
1315 
1316     bool opautili = false;
1317 
1318     if (params.colorToning.enabled) {
1319         TMatrix wprof = ICCStore::getInstance()->workingSpaceMatrix (params.icm.workingProfile);
1320         double wp[3][3] = {
1321             {wprof[0][0], wprof[0][1], wprof[0][2]},
1322             {wprof[1][0], wprof[1][1], wprof[1][2]},
1323             {wprof[2][0], wprof[2][1], wprof[2][2]}
1324         };
1325         params.colorToning.getCurves (ctColorCurve, ctOpacityCurve, wp, opautili);
1326 
1327         clToningcurve (65536);
1328         CurveFactory::curveToning (params.colorToning.clcurve, clToningcurve, scale == 1 ? 1 : 16);
1329 
1330         cl2Toningcurve (65536);
1331         CurveFactory::curveToning (params.colorToning.cl2curve, cl2Toningcurve, scale == 1 ? 1 : 16);
1332     }
1333 
1334     if (params.blackwhite.enabled) {
1335         CurveFactory::curveBW (params.blackwhite.beforeCurve, params.blackwhite.afterCurve, hist16, dummy, customToneCurvebw1, customToneCurvebw2, 16);
1336     }
1337 
1338     double rrm, ggm, bbm;
1339     float autor, autog, autob;
1340     float satLimit = float (params.colorToning.satProtectionThreshold) / 100.f * 0.7f + 0.3f;
1341     float satLimitOpacity = 1.f - (float (params.colorToning.saturatedOpacity) / 100.f);
1342 
1343     if (params.colorToning.enabled  && params.colorToning.autosat && params.colorToning.method != "LabGrid") { //for colortoning evaluation of saturation settings
1344         float moyS = 0.f;
1345         float eqty = 0.f;
1346         ipf.moyeqt (baseImg, moyS, eqty);//return image : mean saturation and standard dev of saturation
1347         //printf("moy=%f ET=%f\n", moyS,eqty);
1348         float satp = ((moyS + 1.5f * eqty) - 0.3f) / 0.7f; //1.5 sigma ==> 93% pixels with high saturation -0.3 / 0.7 convert to Hombre scale
1349 
1350         if (satp >= 0.92f) {
1351             satp = 0.92f;    //avoid values too high (out of gamut)
1352         }
1353 
1354         if (satp <= 0.15f) {
1355             satp = 0.15f;    //avoid too low values
1356         }
1357 
1358         satLimit = 100.f * satp;
1359 
1360         satLimitOpacity = 100.f * (moyS - 0.85f * eqty); //-0.85 sigma==>20% pixels with low saturation
1361     }
1362 
1363     autor = autog = autob = -9000.f; // This will ask to compute the "auto" values for the B&W tool
1364 
1365     LabImage* labView = new LabImage (fw, fh);
1366     DCPProfile *dcpProf = nullptr;
1367     DCPProfileApplyState as;
1368 
1369     if (isRaw) {
1370         cmsHPROFILE dummy;
1371         RawImageSource::findInputProfile (params.icm.inputProfile, nullptr, camName, &dcpProf, dummy);
1372 
1373         if (dcpProf) {
1374             dcpProf->setStep2ApplyState (params.icm.workingProfile, params.icm.toneCurve, params.icm.applyLookTable, params.icm.applyBaselineExposureOffset, as);
1375         }
1376     }
1377 
1378     LUTu histToneCurve;
1379     ipf.rgbProc (baseImg, labView, nullptr, curve1, curve2, curve, params.toneCurve.saturation, rCurve, gCurve, bCurve, satLimit, satLimitOpacity, ctColorCurve, ctOpacityCurve, opautili, clToningcurve, cl2Toningcurve, customToneCurve1, customToneCurve2, customToneCurvebw1, customToneCurvebw2, rrm, ggm, bbm, autor, autog, autob, expcomp, hlcompr, hlcomprthresh, dcpProf, as, histToneCurve);
1380 
1381     // freeing up some memory
1382     customToneCurve1.Reset();
1383     customToneCurve2.Reset();
1384     ctColorCurve.Reset();
1385     ctOpacityCurve.Reset();
1386     customToneCurvebw1.Reset();
1387     customToneCurvebw2.Reset();
1388 
1389     // luminance histogram update
1390     if (params.labCurve.contrast != 0) {
1391         hist16.clear();
1392 
1393         for (int i = 0; i < fh; i++)
1394             for (int j = 0; j < fw; j++) {
1395                 hist16[ (int) ((labView->L[i][j]))]++;
1396             }
1397     }
1398 
1399 
1400     // luminance processing
1401 //  ipf.EPDToneMap(labView,0,6);
1402 
1403     bool utili;
1404     CurveFactory::complexLCurve (params.labCurve.brightness, params.labCurve.contrast, params.labCurve.lcurve,
1405                                  hist16, lumacurve, dummy, 16, utili);
1406 
1407     bool clcutili;
1408     CurveFactory::curveCL (clcutili, params.labCurve.clcurve, clcurve, 16);
1409 
1410     bool autili, butili, ccutili, cclutili;
1411     CurveFactory::complexsgnCurve (autili, butili, ccutili, cclutili, params.labCurve.acurve, params.labCurve.bcurve, params.labCurve.cccurve,
1412                                    params.labCurve.lccurve, curve1, curve2, satcurve, lhskcurve, 16);
1413 
1414     ipf.chromiLuminanceCurve (nullptr, 1, labView, labView, curve1, curve2, satcurve, lhskcurve, clcurve, lumacurve, utili, autili, butili, ccutili, cclutili, clcutili, dummy, dummy);
1415 
1416     ipf.vibrance (labView);
1417     ipf.labColorCorrectionRegions(labView);
1418 
1419     if ((params.colorappearance.enabled && !params.colorappearance.tonecie) || !params.colorappearance.enabled) {
1420         ipf.EPDToneMap (labView, 5, 6);
1421     }
1422 
1423     ipf.softLight(labView);
1424 
1425     if (params.colorappearance.enabled) {
1426         CurveFactory::curveLightBrightColor (
1427             params.colorappearance.curve,
1428             params.colorappearance.curve2,
1429             params.colorappearance.curve3,
1430             hist16, dummy,
1431             dummy, dummy,
1432             customColCurve1,
1433             customColCurve2,
1434             customColCurve3,
1435             16);
1436 
1437         bool execsharp = false;
1438         float d, dj, yb;
1439         float fnum = fnumber;// F number
1440         float fiso = iso;// ISO
1441         float fspeed = shutter;//speed
1442         float adap;
1443 
1444         if (fnum < 0.3f || fiso < 5.f || fspeed < 0.00001f)
1445             //if no exif data or wrong
1446         {
1447             adap = 2000.f;
1448         } else {
1449             float E_V = fcomp + log2 ((fnum * fnum) / fspeed / (fiso / 100.f));
1450             float expo2 = params.toneCurve.expcomp; // exposure compensation in tonecurve ==> direct EV
1451             E_V += expo2;
1452             float expo1;//exposure raw white point
1453             expo1 = log2 (params.raw.expos); //log2 ==>linear to EV
1454             E_V += expo1;
1455             adap = powf (2.f, E_V - 3.f); //cd / m2
1456             //end calculation adaptation scene luminosity
1457         }
1458 
1459         LUTf CAMBrightCurveJ;
1460         LUTf CAMBrightCurveQ;
1461         float CAMMean;
1462         int sk;
1463         sk = 16;
1464         int rtt = 0;
1465         CieImage* cieView = new CieImage (fw, fh);
1466         CAMMean = NAN;
1467         CAMBrightCurveJ.dirty = true;
1468         CAMBrightCurveQ.dirty = true;
1469         ipf.ciecam_02float (cieView, adap, 1, 2, labView, &params, customColCurve1, customColCurve2, customColCurve3, dummy, dummy, CAMBrightCurveJ, CAMBrightCurveQ, CAMMean, 5, sk, execsharp, d, dj, yb, rtt);
1470         delete cieView;
1471     }
1472 
1473     // color processing
1474     //ipf.colorCurve (labView, labView);
1475 
1476     // obtain final image
1477     Image8* readyImg = nullptr;
1478     if (forMonitor) {
1479         readyImg = new Image8 (fw, fh);
1480         ipf.lab2monitorRgb (labView, readyImg);
1481     } else {
1482         readyImg = ipf.lab2rgb(labView, 0, 0, fw, fh, params.icm, false);
1483     }
1484     delete labView;
1485     delete baseImg;
1486 
1487     // calculate scale
1488     if (params.coarse.rotate == 90 || params.coarse.rotate == 270) {
1489         myscale = scale * thumbImg->getWidth() / fh;
1490     } else {
1491         myscale = scale * thumbImg->getHeight() / fh;
1492     }
1493 
1494     myscale = 1.0 / myscale;
1495     // apply crop
1496     if (params.crop.enabled) {
1497         int ix = 0;
1498         for (int i = 0; i < fh; ++i) {
1499             for (int j = 0; j < fw; ++j) {
1500                 if (i < params.crop.y * myscale || i > (params.crop.y + params.crop.h) * myscale || j < params.crop.x * myscale || j > (params.crop.x + params.crop.w) * myscale) {
1501                     readyImg->data[ix++] /= 3;
1502                     readyImg->data[ix++] /= 3;
1503                     readyImg->data[ix++] /= 3;
1504                 } else {
1505                     ix += 3;
1506                 }
1507             }
1508         }
1509     }
1510 
1511 
1512     return readyImg;
1513 }
1514 
getImageWidth(const procparams::ProcParams & params,int rheight,float & ratio)1515 int Thumbnail::getImageWidth (const procparams::ProcParams& params, int rheight, float &ratio)
1516 {
1517     if (!thumbImg) {
1518         return 0;    // Can happen if thumb is just building and GUI comes in with resize wishes
1519     }
1520 
1521     int rwidth;
1522 
1523     if (params.coarse.rotate == 90 || params.coarse.rotate == 270) {
1524         ratio = (float) (thumbImg->getHeight()) / (float) (thumbImg->getWidth());
1525     } else {
1526         ratio = (float) (thumbImg->getWidth()) / (float) (thumbImg->getHeight());
1527     }
1528 
1529     rwidth = (int) (ratio * (float)rheight);
1530 
1531     return rwidth;
1532 }
1533 
getDimensions(int & w,int & h,double & scaleFac)1534 void Thumbnail::getDimensions (int& w, int& h, double& scaleFac)
1535 {
1536     if (thumbImg) {
1537         w = thumbImg->getWidth();
1538         h = thumbImg->getHeight();
1539         scaleFac = scale;
1540     } else {
1541         w = 0;
1542         h = 0;
1543         scale = 1;
1544     }
1545 }
1546 
getCamWB(double & temp,double & green)1547 void Thumbnail::getCamWB (double& temp, double& green)
1548 {
1549 
1550     double cam_r = colorMatrix[0][0] * camwbRed + colorMatrix[0][1] * camwbGreen + colorMatrix[0][2] * camwbBlue;
1551     double cam_g = colorMatrix[1][0] * camwbRed + colorMatrix[1][1] * camwbGreen + colorMatrix[1][2] * camwbBlue;
1552     double cam_b = colorMatrix[2][0] * camwbRed + colorMatrix[2][1] * camwbGreen + colorMatrix[2][2] * camwbBlue;
1553     ColorTemp currWB = ColorTemp (cam_r, cam_g, cam_b, 1.0);  // we do not take the equalizer into account here, because we want camera's WB
1554     temp = currWB.getTemp ();
1555     green = currWB.getGreen ();
1556 }
1557 
getAutoWB(double & temp,double & green,double equal,double tempBias)1558 void Thumbnail::getAutoWB (double& temp, double& green, double equal, double tempBias)
1559 {
1560 
1561     if (equal != wbEqual || tempBias != wbTempBias) {
1562         // compute the values depending on equal
1563         ColorTemp cTemp;
1564         wbEqual = equal;
1565         wbTempBias = tempBias;
1566         // compute autoWBTemp and autoWBGreen
1567         cTemp.mul2temp (redAWBMul, greenAWBMul, blueAWBMul, wbEqual, autoWBTemp, autoWBGreen);
1568         autoWBTemp += autoWBTemp * tempBias;
1569     }
1570 
1571     temp = autoWBTemp;
1572     green = autoWBGreen;
1573 }
1574 
getAutoWBMultipliers(double & rm,double & gm,double & bm)1575 void Thumbnail::getAutoWBMultipliers (double& rm, double& gm, double& bm)
1576 {
1577     rm = redAWBMul;
1578     gm = greenAWBMul;
1579     bm = blueAWBMul;
1580 }
1581 
applyAutoExp(procparams::ProcParams & params)1582 void Thumbnail::applyAutoExp (procparams::ProcParams& params)
1583 {
1584 
1585     if (params.toneCurve.autoexp && aeHistogram) {
1586         ImProcFunctions ipf (&params, false);
1587         ipf.getAutoExp (aeHistogram, aeHistCompression, params.toneCurve.clip, params.toneCurve.expcomp,
1588                         params.toneCurve.brightness, params.toneCurve.contrast, params.toneCurve.black, params.toneCurve.hlcompr, params.toneCurve.hlcomprthresh);
1589     }
1590 }
1591 
getSpotWB(const procparams::ProcParams & params,int xp,int yp,int rect,double & rtemp,double & rgreen)1592 void Thumbnail::getSpotWB (const procparams::ProcParams& params, int xp, int yp, int rect, double& rtemp, double& rgreen)
1593 {
1594 
1595     std::vector<Coord2D> points, red, green, blue;
1596 
1597     for (int i = yp - rect; i <= yp + rect; i++)
1598         for (int j = xp - rect; j <= xp + rect; j++) {
1599             points.push_back (Coord2D (j, i));
1600         }
1601 
1602     int fw = thumbImg->getWidth(), fh = thumbImg->getHeight();
1603 
1604     if (params.coarse.rotate == 90 || params.coarse.rotate == 270) {
1605         fw = thumbImg->getHeight();
1606         fh = thumbImg->getWidth();
1607     }
1608 
1609     ImProcFunctions ipf (&params, false);
1610     ipf.transCoord (fw, fh, points, red, green, blue);
1611     int tr = getCoarseBitMask (params.coarse);
1612     // calculate spot wb (copy & pasted from stdimagesource)
1613     double reds = 0, greens = 0, blues = 0;
1614     int rn = 0, gn = 0, bn = 0;
1615     thumbImg->getSpotWBData (reds, greens, blues, rn, gn, bn, red, green, blue, tr);
1616     reds = reds / rn * camwbRed;
1617     greens = greens / gn * camwbGreen;
1618     blues = blues / bn * camwbBlue;
1619 
1620     double rm = colorMatrix[0][0] * reds + colorMatrix[0][1] * greens + colorMatrix[0][2] * blues;
1621     double gm = colorMatrix[1][0] * reds + colorMatrix[1][1] * greens + colorMatrix[1][2] * blues;
1622     double bm = colorMatrix[2][0] * reds + colorMatrix[2][1] * greens + colorMatrix[2][2] * blues;
1623 
1624     ColorTemp ct (rm, gm, bm, params.wb.equal);
1625     rtemp = ct.getTemp ();
1626     rgreen = ct.getGreen ();
1627 }
transformPixel(int x,int y,int tran,int & tx,int & ty)1628 void Thumbnail::transformPixel (int x, int y, int tran, int& tx, int& ty)
1629 {
1630 
1631     int W = thumbImg->getWidth();
1632     int H = thumbImg->getHeight();
1633     int sw = W, sh = H;
1634 
1635     if ((tran & TR_ROT) == TR_R90 || (tran & TR_ROT) == TR_R270) {
1636         sw = H;
1637         sh = W;
1638     }
1639 
1640     int ppx = x, ppy = y;
1641 
1642     if (tran & TR_HFLIP) {
1643         ppx = sw - 1 - x ;
1644     }
1645 
1646     if (tran & TR_VFLIP) {
1647         ppy = sh - 1 - y;
1648     }
1649 
1650     tx = ppx;
1651     ty = ppy;
1652 
1653     if ((tran & TR_ROT) == TR_R180) {
1654         tx = W - 1 - ppx;
1655         ty = H - 1 - ppy;
1656     } else if ((tran & TR_ROT) == TR_R90) {
1657         tx = ppy;
1658         ty = H - 1 - ppx;
1659     } else if ((tran & TR_ROT) == TR_R270) {
1660         tx = W - 1 - ppy;
1661         ty = ppx;
1662     }
1663 
1664     tx /= scale;
1665     ty /= scale;
1666 }
1667 
getGrayscaleHistEQ(int trim_width)1668 unsigned char* Thumbnail::getGrayscaleHistEQ (int trim_width)
1669 {
1670     if (!thumbImg) {
1671         return nullptr;
1672     }
1673 
1674     if (thumbImg->getWidth() < trim_width) {
1675         return nullptr;
1676     }
1677 
1678     // to utilize the 8 bit color range of the thumbnail we brighten it and apply gamma correction
1679     unsigned char* tmpdata = new unsigned char[thumbImg->getHeight() * trim_width];
1680     int ix = 0;
1681 
1682     if (gammaCorrected) {
1683         // if it's gamma correct (usually a RAW), we have the problem that there is a lot noise etc. that makes the maximum way too high.
1684         // Strategy is limit a certain percent of pixels so the overall picture quality when scaling to 8 bit is way better
1685         const double BurnOffPct = 0.03; // *100 = percent pixels that may be clipped
1686 
1687         // Calc the histogram
1688         unsigned int* hist16 = new unsigned int [65536];
1689         memset (hist16, 0, sizeof (int) * 65536);
1690 
1691         if (thumbImg->getType() == sImage8) {
1692             Image8 *image = static_cast<Image8*> (thumbImg);
1693             image->calcGrayscaleHist (hist16);
1694         } else if (thumbImg->getType() == sImage16) {
1695             Image16 *image = static_cast<Image16*> (thumbImg);
1696             image->calcGrayscaleHist (hist16);
1697         } else if (thumbImg->getType() == sImagefloat) {
1698             Imagefloat *image = static_cast<Imagefloat*> (thumbImg);
1699             image->calcGrayscaleHist (hist16);
1700         } else {
1701             printf ("getGrayscaleHistEQ #1: Unsupported image type \"%s\"!\n", thumbImg->getType());
1702         }
1703 
1704         // Go down till we cut off that many pixels
1705         unsigned long cutoff = thumbImg->getHeight() * thumbImg->getHeight() * 4 * BurnOffPct;
1706 
1707         int max_;
1708         unsigned long sum = 0;
1709 
1710         for (max_ = 65535; max_ > 16384 && sum < cutoff; max_--) {
1711             sum += hist16[max_];
1712         }
1713 
1714         delete[] hist16;
1715 
1716         scaleForSave = 65535 * 8192 / max_;
1717 
1718         // Correction and gamma to 8 Bit
1719         if (thumbImg->getType() == sImage8) {
1720             Image8 *image = static_cast<Image8*> (thumbImg);
1721 
1722             for (int i = 0; i < thumbImg->getHeight(); i++)
1723                 for (int j = (thumbImg->getWidth() - trim_width) / 2; j < trim_width + (thumbImg->getWidth() - trim_width) / 2; j++) {
1724                     unsigned short r_, g_, b_;
1725                     image->convertTo (image->r (i, j), r_);
1726                     image->convertTo (image->g (i, j), g_);
1727                     image->convertTo (image->b (i, j), b_);
1728                     int r = Color::gammatabThumb[min (r_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1729                     int g = Color::gammatabThumb[min (g_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1730                     int b = Color::gammatabThumb[min (b_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1731                     tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16;
1732                 }
1733         } else if (thumbImg->getType() == sImage16) {
1734             Image16 *image = static_cast<Image16*> (thumbImg);
1735 
1736             for (int i = 0; i < thumbImg->getHeight(); i++)
1737                 for (int j = (thumbImg->getWidth() - trim_width) / 2; j < trim_width + (thumbImg->getWidth() - trim_width) / 2; j++) {
1738                     unsigned short r_, g_, b_;
1739                     image->convertTo (image->r (i, j), r_);
1740                     image->convertTo (image->g (i, j), g_);
1741                     image->convertTo (image->b (i, j), b_);
1742                     int r = Color::gammatabThumb[min (r_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1743                     int g = Color::gammatabThumb[min (g_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1744                     int b = Color::gammatabThumb[min (b_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1745                     tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16;
1746                 }
1747         } else if (thumbImg->getType() == sImagefloat) {
1748             Imagefloat *image = static_cast<Imagefloat*> (thumbImg);
1749 
1750             for (int i = 0; i < thumbImg->getHeight(); i++)
1751                 for (int j = (thumbImg->getWidth() - trim_width) / 2; j < trim_width + (thumbImg->getWidth() - trim_width) / 2; j++) {
1752                     unsigned short r_, g_, b_;
1753                     image->convertTo (image->r (i, j), r_);
1754                     image->convertTo (image->g (i, j), g_);
1755                     image->convertTo (image->b (i, j), b_);
1756                     int r = Color::gammatabThumb[min (r_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1757                     int g = Color::gammatabThumb[min (g_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1758                     int b = Color::gammatabThumb[min (b_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1759                     tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16;
1760                 }
1761         }
1762     } else {
1763         // If it's not gamma corrected (usually a JPG) we take the normal maximum
1764         int max = 0;
1765 
1766         if (thumbImg->getType() == sImage8) {
1767             Image8 *image = static_cast<Image8*> (thumbImg);
1768             unsigned char max_ = 0;
1769 
1770             for (int row = 0; row < image->getHeight(); row++)
1771                 for (int col = 0; col < image->getWidth(); col++) {
1772                     if (image->r (row, col) > max_) {
1773                         max_ = image->r (row, col);
1774                     }
1775 
1776                     if (image->g (row, col) > max_) {
1777                         max_ = image->g (row, col);
1778                     }
1779 
1780                     if (image->b (row, col) > max_) {
1781                         max_ = image->b (row, col);
1782                     }
1783                 }
1784 
1785             image->convertTo (max_, max);
1786 
1787             if (max < 16384) {
1788                 max = 16384;
1789             }
1790 
1791             scaleForSave = 65535 * 8192 / max;
1792 
1793             // Correction and gamma to 8 Bit
1794             for (int i = 0; i < image->getHeight(); i++)
1795                 for (int j = (image->getWidth() - trim_width) / 2; j < trim_width + (image->getWidth() - trim_width) / 2; j++) {
1796                     unsigned short rtmp, gtmp, btmp;
1797                     image->convertTo (image->r (i, j), rtmp);
1798                     image->convertTo (image->g (i, j), gtmp);
1799                     image->convertTo (image->b (i, j), btmp);
1800                     int r = rtmp * scaleForSave >> 21;
1801                     int g = gtmp * scaleForSave >> 21;
1802                     int b = btmp * scaleForSave >> 21;
1803                     tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16;
1804                 }
1805         } else if (thumbImg->getType() == sImage16) {
1806             Image16 *image = static_cast<Image16*> (thumbImg);
1807             unsigned short max_ = 0;
1808 
1809             for (int row = 0; row < image->getHeight(); row++)
1810                 for (int col = 0; col < image->getWidth(); col++) {
1811                     if (image->r (row, col) > max_) {
1812                         max_ = image->r (row, col);
1813                     }
1814 
1815                     if (image->g (row, col) > max_) {
1816                         max_ = image->g (row, col);
1817                     }
1818 
1819                     if (image->b (row, col) > max_) {
1820                         max_ = image->b (row, col);
1821                     }
1822                 }
1823 
1824             image->convertTo (max_, max);
1825 
1826             if (max < 16384) {
1827                 max = 16384;
1828             }
1829 
1830             scaleForSave = 65535 * 8192 / max;
1831 
1832             // Correction and gamma to 8 Bit
1833             for (int i = 0; i < image->getHeight(); i++)
1834                 for (int j = (image->getWidth() - trim_width) / 2; j < trim_width + (image->getWidth() - trim_width) / 2; j++) {
1835                     unsigned short rtmp, gtmp, btmp;
1836                     image->convertTo (image->r (i, j), rtmp);
1837                     image->convertTo (image->g (i, j), gtmp);
1838                     image->convertTo (image->b (i, j), btmp);
1839                     int r = rtmp * scaleForSave >> 21;
1840                     int g = gtmp * scaleForSave >> 21;
1841                     int b = btmp * scaleForSave >> 21;
1842                     tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16;
1843                 }
1844         } else if (thumbImg->getType() == sImagefloat) {
1845             Imagefloat *image = static_cast<Imagefloat*> (thumbImg);
1846             float max_ = 0.f;
1847 
1848             for (int row = 0; row < image->getHeight(); row++)
1849                 for (int col = 0; col < image->getWidth(); col++) {
1850                     if (image->r (row, col) > max_) {
1851                         max_ = image->r (row, col);
1852                     }
1853 
1854                     if (image->g (row, col) > max_) {
1855                         max_ = image->g (row, col);
1856                     }
1857 
1858                     if (image->b (row, col) > max_) {
1859                         max_ = image->b (row, col);
1860                     }
1861                 }
1862 
1863             image->convertTo (max_, max);
1864 
1865             if (max < 16384) {
1866                 max = 16384;
1867             }
1868 
1869             scaleForSave = 65535 * 8192 / max;
1870 
1871             // Correction and gamma to 8 Bit
1872             for (int i = 0; i < image->getHeight(); i++)
1873                 for (int j = (image->getWidth() - trim_width) / 2; j < trim_width + (image->getWidth() - trim_width) / 2; j++) {
1874                     unsigned short rtmp, gtmp, btmp;
1875                     image->convertTo (image->r (i, j), rtmp);
1876                     image->convertTo (image->g (i, j), gtmp);
1877                     image->convertTo (image->b (i, j), btmp);
1878                     int r = rtmp * scaleForSave >> 21;
1879                     int g = gtmp * scaleForSave >> 21;
1880                     int b = btmp * scaleForSave >> 21;
1881                     tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16;
1882                 }
1883         } else {
1884             printf ("getGrayscaleHistEQ #2: Unsupported image type \"%s\"!\n", thumbImg->getType());
1885         }
1886     }
1887 
1888     // histogram equalization
1889     unsigned int hist[256] = {0};
1890 
1891     for (int i = 0; i < ix; i++) {
1892         hist[tmpdata[i]]++;
1893     }
1894 
1895     int cdf = 0, cdf_min = -1;
1896 
1897     for (int i = 0; i < 256; i++) {
1898         cdf += hist[i];
1899 
1900         if (cdf > 0 && cdf_min == -1) {
1901             cdf_min = cdf;
1902         }
1903 
1904         if (cdf_min != -1) {
1905             hist[i] = (cdf - cdf_min) * 255 / ((thumbImg->getHeight() * trim_width) - cdf_min);
1906         }
1907     }
1908 
1909     for (int i = 0; i < ix; i++) {
1910         tmpdata[i] = hist[tmpdata[i]];
1911     }
1912 
1913     return tmpdata;
1914 }
1915 
writeImage(const Glib::ustring & fname)1916 bool Thumbnail::writeImage (const Glib::ustring& fname)
1917 {
1918 
1919     if (!thumbImg) {
1920         return false;
1921     }
1922 
1923     Glib::ustring fullFName = fname + ".rtti";
1924 
1925     FILE* f = g_fopen (fullFName.c_str (), "wb");
1926 
1927     if (!f) {
1928         return false;
1929     }
1930 
1931     fwrite (thumbImg->getType(), sizeof (char), strlen (thumbImg->getType()), f);
1932     fputc ('\n', f);
1933     guint32 w = guint32 (thumbImg->getWidth());
1934     guint32 h = guint32 (thumbImg->getHeight());
1935     fwrite (&w, sizeof (guint32), 1, f);
1936     fwrite (&h, sizeof (guint32), 1, f);
1937 
1938     if (thumbImg->getType() == sImage8) {
1939         Image8 *image = static_cast<Image8*> (thumbImg);
1940         image->writeData (f);
1941     } else if (thumbImg->getType() == sImage16) {
1942         Image16 *image = static_cast<Image16*> (thumbImg);
1943         image->writeData (f);
1944     } else if (thumbImg->getType() == sImagefloat) {
1945         Imagefloat *image = static_cast<Imagefloat*> (thumbImg);
1946         image->writeData (f);
1947     }
1948 
1949     //thumbImg->writeData(f);
1950     fclose (f);
1951     return true;
1952 }
1953 
readImage(const Glib::ustring & fname)1954 bool Thumbnail::readImage (const Glib::ustring& fname)
1955 {
1956 
1957     if (thumbImg) {
1958         delete thumbImg;
1959         thumbImg = nullptr;
1960     }
1961 
1962     Glib::ustring fullFName = fname + ".rtti";
1963 
1964     if (!Glib::file_test(fullFName, Glib::FILE_TEST_EXISTS)) {
1965         return false;
1966     }
1967 
1968     FILE* f = g_fopen(fullFName.c_str (), "rb");
1969 
1970     if (!f) {
1971         return false;
1972     }
1973 
1974     char imgType[31];  // 30 -> arbitrary size, but should be enough for all image type's name
1975     fgets(imgType, 30, f);
1976     imgType[strlen(imgType) - 1] = '\0'; // imgType has a \n trailing character, so we overwrite it by the \0 char
1977 
1978     guint32 width, height;
1979 
1980     if (fread(&width, 1, sizeof(guint32), f) < sizeof(guint32)) {
1981         width = 0;
1982     }
1983 
1984     if (fread(&height, 1, sizeof(guint32), f) < sizeof(guint32)) {
1985         height = 0;
1986     }
1987 
1988     bool success = false;
1989 
1990     if (std::min(width , height) > 0) {
1991         if (!strcmp(imgType, sImage8)) {
1992             Image8 *image = new Image8(width, height);
1993             image->readData(f);
1994             thumbImg = image;
1995             success = true;
1996         } else if (!strcmp(imgType, sImage16)) {
1997             Image16 *image = new Image16(width, height);
1998             image->readData(f);
1999             thumbImg = image;
2000             success = true;
2001         } else if (!strcmp(imgType, sImagefloat)) {
2002             Imagefloat *image = new Imagefloat(width, height);
2003             image->readData(f);
2004             thumbImg = image;
2005             success = true;
2006         } else {
2007             printf ("readImage: Unsupported image type \"%s\"!\n", imgType);
2008         }
2009     }
2010     fclose(f);
2011     return success;
2012 }
2013 
readData(const Glib::ustring & fname)2014 bool Thumbnail::readData  (const Glib::ustring& fname)
2015 {
2016     setlocale (LC_NUMERIC, "C"); // to set decimal point to "."
2017     Glib::KeyFile keyFile;
2018 
2019     try {
2020         MyMutex::MyLock thmbLock (thumbMutex);
2021 
2022         try {
2023             keyFile.load_from_file (fname);
2024         } catch (Glib::Error&) {
2025             return false;
2026         }
2027 
2028         if (keyFile.has_group ("LiveThumbData")) {
2029             if (keyFile.has_key ("LiveThumbData", "CamWBRed")) {
2030                 camwbRed            = keyFile.get_double ("LiveThumbData", "CamWBRed");
2031             }
2032 
2033             if (keyFile.has_key ("LiveThumbData", "CamWBGreen")) {
2034                 camwbGreen          = keyFile.get_double ("LiveThumbData", "CamWBGreen");
2035             }
2036 
2037             if (keyFile.has_key ("LiveThumbData", "CamWBBlue")) {
2038                 camwbBlue           = keyFile.get_double ("LiveThumbData", "CamWBBlue");
2039             }
2040 
2041             if (keyFile.has_key ("LiveThumbData", "RedAWBMul")) {
2042                 redAWBMul           = keyFile.get_double ("LiveThumbData", "RedAWBMul");
2043             }
2044 
2045             if (keyFile.has_key ("LiveThumbData", "GreenAWBMul")) {
2046                 greenAWBMul         = keyFile.get_double ("LiveThumbData", "GreenAWBMul");
2047             }
2048 
2049             if (keyFile.has_key ("LiveThumbData", "BlueAWBMul")) {
2050                 blueAWBMul          = keyFile.get_double ("LiveThumbData", "BlueAWBMul");
2051             }
2052 
2053             if (keyFile.has_key ("LiveThumbData", "AEHistCompression")) {
2054                 aeHistCompression   = keyFile.get_integer ("LiveThumbData", "AEHistCompression");
2055             }
2056 
2057             aeValid = true;
2058             if (keyFile.has_key ("LiveThumbData", "AEExposureCompensation")) {
2059                 aeExposureCompensation = keyFile.get_double ("LiveThumbData", "AEExposureCompensation");
2060             } else {
2061                 aeValid = false;
2062             }
2063             if (keyFile.has_key ("LiveThumbData", "AELightness")) {
2064                 aeLightness   = keyFile.get_integer ("LiveThumbData", "AELightness");
2065             } else {
2066                 aeValid = false;
2067             }
2068             if (keyFile.has_key ("LiveThumbData", "AEContrast")) {
2069                 aeContrast   = keyFile.get_integer ("LiveThumbData", "AEContrast");
2070             } else {
2071                 aeValid = false;
2072             }
2073             if (keyFile.has_key ("LiveThumbData", "AEBlack")) {
2074                 aeBlack   = keyFile.get_integer ("LiveThumbData", "AEBlack");
2075             } else {
2076                 aeValid = false;
2077             }
2078             if (keyFile.has_key ("LiveThumbData", "AEHighlightCompression")) {
2079                 aeHighlightCompression   = keyFile.get_integer ("LiveThumbData", "AEHighlightCompression");
2080             } else {
2081                 aeValid = false;
2082             }
2083             if (keyFile.has_key ("LiveThumbData", "AEHighlightCompressionThreshold")) {
2084                 aeHighlightCompressionThreshold   = keyFile.get_integer ("LiveThumbData", "AEHighlightCompressionThreshold");
2085             } else {
2086                 aeValid = false;
2087             }
2088 
2089             if (keyFile.has_key ("LiveThumbData", "RedMultiplier")) {
2090                 redMultiplier       = keyFile.get_double ("LiveThumbData", "RedMultiplier");
2091             }
2092 
2093             if (keyFile.has_key ("LiveThumbData", "GreenMultiplier")) {
2094                 greenMultiplier     = keyFile.get_double ("LiveThumbData", "GreenMultiplier");
2095             }
2096 
2097             if (keyFile.has_key ("LiveThumbData", "BlueMultiplier")) {
2098                 blueMultiplier      = keyFile.get_double ("LiveThumbData", "BlueMultiplier");
2099             }
2100 
2101             if (keyFile.has_key ("LiveThumbData", "Scale")) {
2102                 scale               = keyFile.get_double ("LiveThumbData", "Scale");
2103             }
2104 
2105             if (keyFile.has_key ("LiveThumbData", "DefaultGain")) {
2106                 defGain             = keyFile.get_double ("LiveThumbData", "DefaultGain");
2107             }
2108 
2109             if (keyFile.has_key ("LiveThumbData", "ScaleForSave")) {
2110                 scaleForSave        = keyFile.get_integer ("LiveThumbData", "ScaleForSave");
2111             }
2112 
2113             if (keyFile.has_key ("LiveThumbData", "GammaCorrected")) {
2114                 gammaCorrected      = keyFile.get_boolean ("LiveThumbData", "GammaCorrected");
2115             }
2116 
2117             if (keyFile.has_key ("LiveThumbData", "ColorMatrix")) {
2118                 std::vector<double> cm = keyFile.get_double_list ("LiveThumbData", "ColorMatrix");
2119                 int ix = 0;
2120 
2121                 for (int i = 0; i < 3; i++)
2122                     for (int j = 0; j < 3; j++) {
2123                         colorMatrix[i][j] = cm[ix++];
2124                     }
2125             }
2126         }
2127 
2128         return true;
2129     } catch (Glib::Error &err) {
2130         if (settings->verbose) {
2131             printf ("Thumbnail::readData / Error code %d while reading values from \"%s\":\n%s\n", err.code(), fname.c_str(), err.what().c_str());
2132         }
2133     } catch (...) {
2134         if (settings->verbose) {
2135             printf ("Thumbnail::readData / Unknown exception while trying to load \"%s\"!\n", fname.c_str());
2136         }
2137     }
2138 
2139     return false;
2140 }
2141 
writeData(const Glib::ustring & fname)2142 bool Thumbnail::writeData  (const Glib::ustring& fname)
2143 {
2144     MyMutex::MyLock thmbLock (thumbMutex);
2145 
2146     Glib::ustring keyData;
2147 
2148     try {
2149 
2150         Glib::KeyFile keyFile;
2151 
2152         try {
2153             keyFile.load_from_file (fname);
2154         } catch (Glib::Error&) {}
2155 
2156         keyFile.set_double  ("LiveThumbData", "CamWBRed", camwbRed);
2157         keyFile.set_double  ("LiveThumbData", "CamWBGreen", camwbGreen);
2158         keyFile.set_double  ("LiveThumbData", "CamWBBlue", camwbBlue);
2159         keyFile.set_double  ("LiveThumbData", "RedAWBMul", redAWBMul);
2160         keyFile.set_double  ("LiveThumbData", "GreenAWBMul", greenAWBMul);
2161         keyFile.set_double  ("LiveThumbData", "BlueAWBMul", blueAWBMul);
2162         keyFile.set_double  ("LiveThumbData", "AEExposureCompensation", aeExposureCompensation);
2163         keyFile.set_integer ("LiveThumbData", "AELightness", aeLightness);
2164         keyFile.set_integer ("LiveThumbData", "AEContrast", aeContrast);
2165         keyFile.set_integer ("LiveThumbData", "AEBlack", aeBlack);
2166         keyFile.set_integer ("LiveThumbData", "AEHighlightCompression", aeHighlightCompression);
2167         keyFile.set_integer ("LiveThumbData", "AEHighlightCompressionThreshold", aeHighlightCompressionThreshold);
2168         keyFile.set_double  ("LiveThumbData", "RedMultiplier", redMultiplier);
2169         keyFile.set_double  ("LiveThumbData", "GreenMultiplier", greenMultiplier);
2170         keyFile.set_double  ("LiveThumbData", "BlueMultiplier", blueMultiplier);
2171         keyFile.set_double  ("LiveThumbData", "Scale", scale);
2172         keyFile.set_double  ("LiveThumbData", "DefaultGain", defGain);
2173         keyFile.set_integer ("LiveThumbData", "ScaleForSave", scaleForSave);
2174         keyFile.set_boolean ("LiveThumbData", "GammaCorrected", gammaCorrected);
2175         Glib::ArrayHandle<double> cm ((double*)colorMatrix, 9, Glib::OWNERSHIP_NONE);
2176         keyFile.set_double_list ("LiveThumbData", "ColorMatrix", cm);
2177 
2178         keyData = keyFile.to_data ();
2179 
2180     } catch (Glib::Error& err) {
2181         if (settings->verbose) {
2182             printf ("Thumbnail::writeData / Error code %d while reading values from \"%s\":\n%s\n", err.code(), fname.c_str(), err.what().c_str());
2183         }
2184     } catch (...) {
2185         if (settings->verbose) {
2186             printf ("Thumbnail::writeData / Unknown exception while trying to save \"%s\"!\n", fname.c_str());
2187         }
2188     }
2189 
2190     if (keyData.empty ()) {
2191         return false;
2192     }
2193 
2194     FILE *f = g_fopen (fname.c_str (), "wt");
2195 
2196     if (!f) {
2197         if (settings->verbose) {
2198             printf ("Thumbnail::writeData / Error: unable to open file \"%s\" with write access!\n", fname.c_str());
2199         }
2200 
2201         return false;
2202     } else {
2203         fprintf (f, "%s", keyData.c_str ());
2204         fclose (f);
2205     }
2206 
2207     return true;
2208 }
2209 
readEmbProfile(const Glib::ustring & fname)2210 bool Thumbnail::readEmbProfile  (const Glib::ustring& fname)
2211 {
2212 
2213     embProfileData = nullptr;
2214     embProfile = nullptr;
2215     embProfileLength = 0;
2216 
2217     FILE* f = g_fopen (fname.c_str (), "rb");
2218 
2219     if (f) {
2220         if (!fseek (f, 0, SEEK_END)) {
2221             int profileLength = ftell (f);
2222 
2223             if (profileLength > 0) {
2224                 embProfileLength = profileLength;
2225 
2226                 if (!fseek (f, 0, SEEK_SET)) {
2227                     embProfileData = new unsigned char[embProfileLength];
2228                     embProfileLength = fread (embProfileData, 1, embProfileLength, f);
2229                     embProfile = cmsOpenProfileFromMem (embProfileData, embProfileLength);
2230                 }
2231             }
2232         }
2233 
2234         fclose (f);
2235         return embProfile != nullptr;
2236     }
2237 
2238     return false;
2239 }
2240 
writeEmbProfile(const Glib::ustring & fname)2241 bool Thumbnail::writeEmbProfile (const Glib::ustring& fname)
2242 {
2243 
2244     if (embProfileData) {
2245         FILE* f = g_fopen (fname.c_str (), "wb");
2246 
2247         if (f) {
2248             fwrite (embProfileData, 1, embProfileLength, f);
2249             fclose (f);
2250             return true;
2251         }
2252     }
2253 
2254     return false;
2255 }
2256 
readAEHistogram(const Glib::ustring & fname)2257 bool Thumbnail::readAEHistogram  (const Glib::ustring& fname)
2258 {
2259 
2260     FILE* f = g_fopen(fname.c_str(), "rb");
2261 
2262     if (!f) {
2263         aeHistogram.reset();
2264     } else {
2265         aeHistogram(65536 >> aeHistCompression);
2266         const size_t histoBytes = (65536 >> aeHistCompression) * sizeof(aeHistogram[0]);
2267         const size_t bytesRead = fread(&aeHistogram[0], 1, histoBytes, f);
2268         fclose (f);
2269         if (bytesRead != histoBytes) {
2270             aeHistogram.reset();
2271             return false;
2272         }
2273         return true;
2274     }
2275 
2276     return false;
2277 }
2278 
writeAEHistogram(const Glib::ustring & fname)2279 bool Thumbnail::writeAEHistogram (const Glib::ustring& fname)
2280 {
2281 
2282     if (aeHistogram) {
2283         FILE* f = g_fopen (fname.c_str (), "wb");
2284 
2285         if (f) {
2286             fwrite (&aeHistogram[0], 1, (65536 >> aeHistCompression)*sizeof (aeHistogram[0]), f);
2287             fclose (f);
2288             return true;
2289         }
2290     }
2291 
2292     return false;
2293 }
2294 
getImage8Data()2295 unsigned char* Thumbnail::getImage8Data()
2296 {
2297     if (thumbImg && thumbImg->getType() == rtengine::sImage8) {
2298         Image8* img8 = static_cast<Image8*> (thumbImg);
2299         return img8->data;
2300     }
2301 
2302     return nullptr;
2303 }
2304 
2305 
2306 
2307 }
2308