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 <http://www.gnu.org/licenses/>.
18 */
19 #include "rtengine.h"
20 #include "rtthumbnail.h"
21 #include "../rtgui/options.h"
22 #include "image8.h"
23 #include <lcms2.h>
24 #include "curves.h"
25 #include <glibmm.h>
26 #include "improcfun.h"
27 #include "colortemp.h"
28 #include "mytime.h"
29 #include "utils.h"
30 #include "iccstore.h"
31 #include "iccmatrices.h"
32 #include "rawimagesource.h"
33 #include "stdimagesource.h"
34 #include <glib/gstdio.h>
35 #include "rawimage.h"
36 #include "rtjpeg.h"
37 #include "../rtgui/ppversion.h"
38 #include "improccoordinator.h"
39 #include "settings.h"
40 #include <locale.h>
41 #include "median.h"
42 #define BENCHMARK
43 #include "StopWatch.h"
44
45 namespace {
46
checkRawImageThumb(const rtengine::RawImage & raw_image)47 bool checkRawImageThumb (const rtengine::RawImage& raw_image)
48 {
49 return raw_image.checkThumbOk();
50 }
51
52
scale_colors(rtengine::RawImage * ri,float scale_mul[4],float cblack[4],bool multiThread)53 void scale_colors (rtengine::RawImage *ri, float scale_mul[4], float cblack[4], bool multiThread)
54 {
55 rtengine::RawImage::ImageType image = ri->get_image();
56 const int height = ri->get_iheight();
57 const int width = ri->get_iwidth();
58 const int top_margin = ri->get_topmargin();
59 const int left_margin = ri->get_leftmargin();
60 const int raw_width = ri->get_rawwidth();
61 const bool isFloat = ri->isFloat();
62 const float * const float_raw_image = ri->get_FloatRawImage();
63
64 if (ri->isBayer()) {
65 #ifdef _OPENMP
66 #pragma omp parallel for if(multiThread)
67 #endif
68 for (int row = 0; row < height; ++row) {
69 unsigned c0 = ri->FC (row, 0);
70 unsigned c1 = ri->FC (row, 1);
71 int col = 0;
72
73 for (; col < width - 1; col += 2) {
74 float val0;
75 float val1;
76 if (isFloat) {
77 val0 = float_raw_image[(row + top_margin) * raw_width + col + left_margin];
78 val1 = float_raw_image[(row + top_margin) * raw_width + col + left_margin + 1];
79 } else {
80 val0 = image[row * width + col][c0];
81 val1 = image[row * width + col + 1][c1];
82 }
83 val0 -= cblack[c0];
84 val1 -= cblack[c1];
85 val0 *= scale_mul[c0];
86 val1 *= scale_mul[c1];
87 image[row * width + col][c0] = rtengine::CLIP (val0);
88 image[row * width + col + 1][c1] = rtengine::CLIP (val1);
89 }
90
91 if (col < width) { // in case width is odd
92 float val0;
93 if (isFloat) {
94 val0 = float_raw_image[(row + top_margin) * raw_width + col + left_margin];
95 } else {
96 val0 = image[row * width + col][c0];
97 }
98 val0 -= cblack[c0];
99 val0 *= scale_mul[c0];
100 image[row * width + col][c0] = rtengine::CLIP (val0);
101 }
102 }
103 } else if (ri->isXtrans()) {
104 #ifdef _OPENMP
105 #pragma omp parallel for if(multiThread)
106 #endif
107 for (int row = 0; row < height; ++row) {
108 unsigned c[6];
109 for (int i = 0; i < 6; ++i) {
110 c[i] = ri->XTRANSFC (row, i);
111 }
112
113 int col = 0;
114
115 for (; col < width - 5; col += 6) {
116 for (int i = 0; i < 6; ++i) {
117 const unsigned ccol = c[i];
118 float val;
119 if (isFloat) {
120 val = float_raw_image[(row + top_margin) * raw_width + col + i + left_margin];
121 } else {
122 val = image[row * width + col + i][ccol];
123 }
124 val -= cblack[ccol];
125 val *= scale_mul[ccol];
126 image[row * width + col + i][ccol] = rtengine::CLIP (val);
127 }
128 }
129
130 for (; col < width; ++col) { // remaining columns
131 const unsigned ccol = ri->XTRANSFC (row, col);
132 float val;
133 if (isFloat) {
134 val = float_raw_image[(row + top_margin) * raw_width + col + left_margin];
135 } else {
136 val = image[row * width + col][ccol];
137 }
138 val -= cblack[ccol];
139 val *= scale_mul[ccol];
140 image[row * width + col][ccol] = rtengine::CLIP (val);
141 }
142 }
143 } else if (isFloat) {
144 #ifdef _OPENMP
145 #pragma omp parallel for if(multiThread)
146 #endif
147 for (int row = 0; row < height; ++row) {
148 for (int col = 0; col < width; ++col) {
149 for (int i = 0; i < ri->get_colors(); ++i) {
150 float val = float_raw_image[(row + top_margin) * raw_width + col + left_margin + i];
151 val -= cblack[i];
152 val *= scale_mul[i];
153 image[row * width + col][i] = val;
154 }
155 }
156 }
157 } else {
158 const int size = ri->get_iheight() * ri->get_iwidth();
159
160 #ifdef _OPENMP
161 #pragma omp parallel for if(multiThread)
162 #endif
163 for (int i = 0; i < size; ++i) {
164 for (int j = 0; j < 4; ++j) {
165 float val = image[i][j];
166 val -= cblack[j];
167 val *= scale_mul[j];
168 image[i][j] = rtengine::CLIP (val);
169 }
170 }
171 }
172 }
173
174 } // namespace
175
176 extern Options options;
177
178 namespace rtengine {
179
180 extern const Settings *settings;
181
182 using namespace procparams;
183
loadFromImage(const Glib::ustring & fname,int & w,int & h,int fixwh,double wbEq)184 Thumbnail* Thumbnail::loadFromImage (const Glib::ustring& fname, int &w, int &h, int fixwh, double wbEq)
185 {
186
187 StdImageSource imgSrc;
188
189 if (imgSrc.load(fname, std::max(w, 0), std::max(h, 0))) {
190 return nullptr;
191 }
192
193 ImageIO* img = imgSrc.getImageIO();
194
195 Thumbnail* tpp = new Thumbnail ();
196
197 unsigned char* data;
198 img->getEmbeddedProfileData (tpp->embProfileLength, data);
199
200 if (data && tpp->embProfileLength) {
201 tpp->embProfileData = new unsigned char [tpp->embProfileLength];
202 memcpy (tpp->embProfileData, data, tpp->embProfileLength);
203 }
204
205 tpp->scaleForSave = 8192;
206 tpp->defGain = 1.0;
207 tpp->gammaCorrected = false;
208 tpp->isRaw = 0;
209 tpp->sensorType = ST_NONE;
210 memset (tpp->colorMatrix, 0, sizeof (tpp->colorMatrix));
211 tpp->colorMatrix[0][0] = 1.0;
212 tpp->colorMatrix[1][1] = 1.0;
213 tpp->colorMatrix[2][2] = 1.0;
214
215 if (fixwh < 0 && w > 0 && h > 0) {
216 int ww = h * img->getWidth() / img->getHeight();
217 int hh = w * img->getHeight() / img->getWidth();
218 if (ww <= w) {
219 w = ww;
220 tpp->scale = double(img->getHeight()) / h;
221 } else {
222 h = hh;
223 tpp->scale = double(img->getWidth()) / w;
224 }
225 } else if (fixwh == 1) {
226 w = h * img->getWidth() / img->getHeight();
227 tpp->scale = (double)img->getHeight() / h;
228 } else {
229 h = w * img->getHeight() / img->getWidth();
230 tpp->scale = (double)img->getWidth() / w;
231 }
232
233 h = std::max(h, 1);
234 w = std::max(w, 1);
235
236 // bilinear interpolation
237 if (tpp->thumbImg) {
238 delete tpp->thumbImg;
239 tpp->thumbImg = nullptr;
240 }
241
242 // we want the same image type than the source file
243 tpp->thumbImg = resizeToSameType (w, h, TI_Bilinear, img);
244
245 // histogram computation
246 tpp->aeHistCompression = 3;
247 tpp->aeHistogram (65536 >> tpp->aeHistCompression);
248
249 double avg_r = 0;
250 double avg_g = 0;
251 double avg_b = 0;
252 int n = 0;
253
254 if (img->getType() == rtengine::sImage8) {
255 Image8 *image = static_cast<Image8*> (img);
256 image->computeHistogramAutoWB (avg_r, avg_g, avg_b, n, tpp->aeHistogram, tpp->aeHistCompression);
257 } else if (img->getType() == sImage16) {
258 Image16 *image = static_cast<Image16*> (img);
259 image->computeHistogramAutoWB (avg_r, avg_g, avg_b, n, tpp->aeHistogram, tpp->aeHistCompression);
260 } else if (img->getType() == sImagefloat) {
261 Imagefloat *image = static_cast<Imagefloat*> (img);
262 image->computeHistogramAutoWB (avg_r, avg_g, avg_b, n, tpp->aeHistogram, tpp->aeHistCompression);
263 } else {
264 printf ("loadFromImage: Unsupported image type \"%s\"!\n", img->getType());
265 }
266
267 // ProcParams paramsForAutoExp; // Dummy for constructor
268 // ImProcFunctions ipf (¶msForAutoExp, false);
269 // ipf.getAutoExp (tpp->aeHistogram, tpp->aeHistCompression, 0.02, tpp->aeExposureCompensation, tpp->aeLightness, tpp->aeContrast, tpp->aeBlack, tpp->aeHighlightCompression, tpp->aeHighlightCompressionThreshold);
270 // tpp->aeValid = true;
271
272 if (n > 0) {
273 ColorTemp cTemp;
274
275 tpp->redAWBMul = avg_r / double (n);
276 tpp->greenAWBMul = avg_g / double (n);
277 tpp->blueAWBMul = avg_b / double (n);
278 tpp->wbEqual = wbEq;
279
280 cTemp.mul2temp (tpp->redAWBMul, tpp->greenAWBMul, tpp->blueAWBMul, tpp->wbEqual, tpp->autoWBTemp, tpp->autoWBGreen);
281 }
282
283 tpp->init ();
284
285 return tpp;
286 }
287
288
loadQuickFromRaw(const Glib::ustring & fname,eSensorType & sensorType,int & w,int & h,int fixwh,bool rotate,bool forHistogramMatching)289 Thumbnail* Thumbnail::loadQuickFromRaw (const Glib::ustring& fname, eSensorType &sensorType, int &w, int &h, int fixwh, bool rotate, bool forHistogramMatching)
290 {
291 Thumbnail* tpp = new Thumbnail ();
292 tpp->isRaw = 1;
293 tpp->sensorType = sensorType;
294 memset (tpp->colorMatrix, 0, sizeof (tpp->colorMatrix));
295 tpp->colorMatrix[0][0] = 1.0;
296 tpp->colorMatrix[1][1] = 1.0;
297 tpp->colorMatrix[2][2] = 1.0;
298
299 RawImage *ri = new RawImage (fname);
300 unsigned int imageNum = 0;
301 int r = ri->loadRaw (false, imageNum, false);
302
303 if ( r ) {
304 delete tpp;
305 delete ri;
306 sensorType = ST_NONE;
307 return nullptr;
308 }
309
310 sensorType = ri->getSensorType();
311
312 Image8* img = new Image8 ();
313 // No sample format detection occurred earlier, so we set them here,
314 // as they are mandatory for the setScanline method
315 img->setSampleFormat (IIOSF_UNSIGNED_CHAR);
316 img->setSampleArrangement (IIOSA_CHUNKY);
317
318 int err = 1;
319
320 // See if it is something we support
321 if (checkRawImageThumb (*ri)) {
322 const char* data ((const char*)fdata (ri->get_thumbOffset(), ri->get_file()));
323
324 if ( (unsigned char)data[1] == 0xd8 ) {
325 err = img->loadJPEGFromMemory (data, ri->get_thumbLength());
326 } else if (ri->is_ppmThumb()) {
327 err = img->loadPPMFromMemory (data, ri->get_thumbWidth(), ri->get_thumbHeight(), ri->get_thumbSwap(), ri->get_thumbBPS());
328 }
329 }
330
331 // did we succeed?
332 if ( err ) {
333 if (options.rtSettings.verbose) {
334 std::cout << "Could not extract thumb from " << fname.c_str() << std::endl;
335 }
336 delete tpp;
337 delete img;
338 delete ri;
339 return nullptr;
340 }
341
342 if (forHistogramMatching) {
343 // Special case, meaning that we want a full sized thumbnail image
344 w = img->getWidth();
345 h = img->getHeight();
346 tpp->scale = 1.;
347 } else {
348 if (fixwh == 1) {
349 w = h * img->getWidth() / img->getHeight();
350 tpp->scale = (double)img->getHeight() / h;
351 } else {
352 h = w * img->getHeight() / img->getWidth();
353 tpp->scale = (double)img->getWidth() / w;
354 }
355 }
356
357 if (tpp->thumbImg) {
358 delete tpp->thumbImg;
359 tpp->thumbImg = nullptr;
360 }
361
362 if (forHistogramMatching) {
363 tpp->thumbImg = img;
364 } else {
365 tpp->thumbImg = resizeTo<Image8> (w, h, TI_Nearest, img);
366 delete img;
367 }
368
369 if (rotate && ri->get_rotateDegree() > 0 && ri->thumbNeedsRotation()) {
370 tpp->thumbImg->rotate(ri->get_rotateDegree());
371 // width/height may have changed after rotating
372 w = tpp->thumbImg->getWidth();
373 h = tpp->thumbImg->getHeight();
374 }
375
376 if (!forHistogramMatching) {
377 tpp->init ();
378 }
379
380 delete ri;
381
382 return tpp;
383 }
384
385 #define FISRED(filter,row,col) \
386 ((filter >> ((((row) << 1 & 14) + ((col) & 1)) << 1) & 3)==0 || !filter)
387 #define FISGREEN(filter,row,col) \
388 ((filter >> ((((row) << 1 & 14) + ((col) & 1)) << 1) & 3)==1 || !filter)
389 #define FISBLUE(filter,row,col) \
390 ((filter >> ((((row) << 1 & 14) + ((col) & 1)) << 1) & 3)==2 || !filter)
391
loadFromRaw(const Glib::ustring & fname,eSensorType & sensorType,int & w,int & h,int fixwh,double wbEq,bool rotate,bool forHistogramMatching)392 Thumbnail* Thumbnail::loadFromRaw (const Glib::ustring& fname, eSensorType &sensorType, int &w, int &h, int fixwh, double wbEq, bool rotate, bool forHistogramMatching)
393 {
394 RawImage *ri = new RawImage (fname);
395 unsigned int tempImageNum = 0;
396
397 int r = ri->loadRaw(true, tempImageNum, false, nullptr, 1.0, false);
398
399 if ( r ) {
400 delete ri;
401 sensorType = ST_NONE;
402 return nullptr;
403 }
404
405 if (ri->getFrameCount() == 7) {
406 // special case for Hasselblad H6D-100cMS pixelshift files
407 // first frame is not bayer, load second frame
408 int r = ri->loadRaw(true, 1, false, nullptr, 1.0, false);
409
410 if ( r ) {
411 delete ri;
412 sensorType = ST_NONE;
413 return nullptr;
414 }
415 }
416 sensorType = ri->getSensorType();
417
418 int width = ri->get_width();
419 int height = ri->get_height();
420 rtengine::Thumbnail* tpp = new rtengine::Thumbnail;
421
422 tpp->isRaw = true;
423 tpp->sensorType = sensorType;
424 tpp->embProfile = nullptr;
425 tpp->embProfileData = nullptr;
426 tpp->embProfileLength = ri->get_profileLen();
427
428 if (ri->get_profileLen())
429 tpp->embProfile = cmsOpenProfileFromMem (ri->get_profile(),
430 ri->get_profileLen()); //\ TODO check if mutex is needed
431
432 tpp->redMultiplier = ri->get_pre_mul (0);
433 tpp->greenMultiplier = ri->get_pre_mul (1);
434 tpp->blueMultiplier = ri->get_pre_mul (2);
435
436 float pre_mul[4], scale_mul[4], cblack[4];
437 ri->get_colorsCoeff (pre_mul, scale_mul, cblack, false);
438 scale_colors (ri, scale_mul, cblack, forHistogramMatching); // enable multithreading when forHistogramMatching is true
439
440 ri->pre_interpolate();
441
442 tpp->camwbRed = tpp->redMultiplier / pre_mul[0]; //ri->get_pre_mul(0);
443 tpp->camwbGreen = tpp->greenMultiplier / pre_mul[1]; //ri->get_pre_mul(1);
444 tpp->camwbBlue = tpp->blueMultiplier / pre_mul[2]; //ri->get_pre_mul(2);
445 //tpp->defGain = 1.0 / min(ri->get_pre_mul(0), ri->get_pre_mul(1), ri->get_pre_mul(2));
446 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]);
447 tpp->defGain *= std::pow(2, ri->getBaselineExposure());
448 tpp->scaleGain = scale_mul[0] / pre_mul[0]; // used to reconstruct scale_mul from filmnegativethumb.cc
449
450 tpp->gammaCorrected = true;
451
452 unsigned filter = ri->get_filters();
453 int firstgreen = 1;
454
455 // locate first green location in the first row
456 if (ri->getSensorType() == ST_BAYER)
457 while (!FISGREEN (filter, 1, firstgreen) && firstgreen < 3) {
458 firstgreen++;
459 }
460
461 int skip = 1;
462
463 if (ri->get_FujiWidth() != 0) {
464 if (fixwh == 1) { // fix height, scale width
465 skip = ((ri->get_height() - ri->get_FujiWidth()) / sqrt (0.5) - firstgreen - 1) / h;
466 } else {
467 skip = (ri->get_FujiWidth() / sqrt (0.5) - firstgreen - 1) / w;
468 }
469 } else {
470 if (fixwh == 1) { // fix height, scale width
471 skip = (ri->get_height() - firstgreen - 1) / h;
472 } else {
473 skip = (ri->get_width() - firstgreen - 1) / w;
474 }
475 }
476
477 if (skip % 2) {
478 skip--;
479 }
480
481 if (skip < 2) {
482 skip = 2;
483 }
484
485 int hskip = skip, vskip = skip;
486
487 if (!ri->get_model().compare ("D1X")) {
488 hskip *= 2;
489 }
490
491 int rofs = 0;
492 int tmpw = (width - 2) / hskip;
493 int tmph = (height - 2) / vskip;
494
495 rtengine::RawImage::ImageType image = ri->get_image();
496
497 Imagefloat* tmpImg = new Imagefloat (tmpw, tmph);
498
499 if (ri->getSensorType() == ST_BAYER) {
500 // demosaicing! (sort of)
501 for (int row = 1, y = 0; row < height - 1 && y < tmph; row += vskip, y++) {
502 rofs = row * width;
503
504 for (int col = firstgreen, x = 0; col < width - 1 && x < tmpw; col += hskip, x++) {
505 int ofs = rofs + col;
506 int g = image[ofs][1];
507 int r, b;
508
509 if (FISRED (filter, row, col + 1)) {
510 r = (image[ofs + 1 ][0] + image[ofs - 1 ][0]) >> 1;
511 b = (image[ofs + width][2] + image[ofs - width][2]) >> 1;
512 } else {
513 b = (image[ofs + 1 ][2] + image[ofs - 1 ][2]) >> 1;
514 r = (image[ofs + width][0] + image[ofs - width][0]) >> 1;
515 }
516
517 tmpImg->r (y, x) = r;
518 tmpImg->g (y, x) = g;
519 tmpImg->b (y, x) = b;
520 }
521 }
522 } else if (ri->get_colors() == 1) {
523 for (int row = 1, y = 0; row < height - 1 && y < tmph; row += vskip, y++) {
524 rofs = row * width;
525
526 for (int col = firstgreen, x = 0; col < width - 1 && x < tmpw; col
527 += hskip, x++) {
528 int ofs = rofs + col;
529 tmpImg->r (y, x) = tmpImg->g (y, x) = tmpImg->b (y, x) = image[ofs][0];
530 }
531 }
532 } else {
533 if (ri->getSensorType() == ST_FUJI_XTRANS) {
534 for ( int row = 1, y = 0; row < height - 1 && y < tmph; row += vskip, y++) {
535 rofs = row * width;
536
537 for ( int col = 1, x = 0; col < width - 1 && x < tmpw; col += hskip, x++ ) {
538 int ofs = rofs + col;
539 float sum[3] = {};
540 int c;
541
542 for (int v = -1; v <= 1; v++) {
543 for (int h = -1; h <= 1; h++) {
544 c = ri->XTRANSFC (row + v, col + h);
545 sum[c] += image[ofs + v * width + h][c];
546 }
547 }
548
549 c = ri->XTRANSFC (row, col);
550
551 switch (c) {
552 case 0:
553 tmpImg->r (y, x) = image[ofs][0];
554 tmpImg->g (y, x) = sum[1] / 5.f;
555 tmpImg->b (y, x) = sum[2] / 3.f;
556 break;
557
558 case 1:
559 tmpImg->r (y, x) = sum[0] / 2.f;
560 tmpImg->g (y, x) = image[ofs][1];
561 tmpImg->b (y, x) = sum[2] / 2.f;
562 break;
563
564 case 2:
565 tmpImg->r (y, x) = sum[0] / 3.f;
566 tmpImg->g (y, x) = sum[1] / 5.f;
567 tmpImg->b (y, x) = image[ofs][2];
568 break;
569 }
570 }
571 }
572 } else {
573 int iwidth = ri->get_iwidth();
574 int iheight = ri->get_iheight();
575 int left_margin = ri->get_leftmargin();
576 firstgreen += left_margin;
577 int top_margin = ri->get_topmargin();
578 int wmax = tmpw;
579 int hmax = tmph;
580
581 if ((ri->get_maker() == "Sigma" || ri->get_maker() == "Pentax" || ri->get_maker() == "Sony") && ri->DNGVERSION()) { // Hack to prevent sigma dng files from crashing
582 wmax = (width - 2 - left_margin) / hskip;
583 hmax = (height - 2 - top_margin) / vskip;
584 }
585
586 int y = 0;
587
588 for (int row = 1 + top_margin; row < iheight + top_margin - 1 && y < hmax; row += vskip, y++) {
589 rofs = row * iwidth;
590
591 int x = 0;
592
593 for (int col = firstgreen; col < iwidth + left_margin - 1 && x < wmax; col += hskip, x++) {
594 int ofs = rofs + col;
595 tmpImg->r (y, x) = image[ofs][0];
596 tmpImg->g (y, x) = image[ofs][1];
597 tmpImg->b (y, x) = image[ofs][2];
598 }
599
600 for (; x < tmpw; ++x) {
601 tmpImg->r (y, x) = tmpImg->g (y, x) = tmpImg->b (y, x) = 0;
602 }
603 }
604
605 for (; y < tmph; ++y) {
606 for (int x = 0; x < tmpw; ++x) {
607 tmpImg->r (y, x) = tmpImg->g (y, x) = tmpImg->b (y, x) = 0;
608 }
609 }
610 }
611 }
612
613 if (ri->get_FujiWidth() != 0) {
614 int fw = ri->get_FujiWidth() / hskip;
615 double step = sqrt (0.5);
616 int wide = fw / step;
617 int high = (tmph - fw) / step;
618 Imagefloat* fImg = new Imagefloat (wide, high);
619 float r, c;
620
621 for (int row = 0; row < high; row++)
622 for (int col = 0; col < wide; col++) {
623 int ur = r = fw + (row - col) * step;
624 int uc = c = (row + col) * step;
625
626 if (ur > tmph - 2 || uc > tmpw - 2) {
627 continue;
628 }
629
630 double fr = r - ur;
631 double fc = c - uc;
632 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;
633 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;
634 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;
635 }
636
637 delete tmpImg;
638 tmpImg = fImg;
639 tmpw = wide;
640 tmph = high;
641 }
642
643 const bool rotate_90 =
644 rotate
645 && (
646 ri->get_rotateDegree() == 90
647 || ri->get_rotateDegree() == 270
648 );
649
650 if (rotate_90) {
651 std::swap (tmpw, tmph);
652 }
653
654 if (fixwh == 1) { // fix height, scale width
655 w = tmpw * h / tmph;
656 } else {
657 h = tmph * w / tmpw;
658 }
659
660 if (tpp->thumbImg) {
661 delete tpp->thumbImg;
662 }
663
664 if (rotate_90) {
665 tpp->thumbImg = resizeTo<Image16> (h, w, TI_Bilinear, tmpImg);
666 } else {
667 tpp->thumbImg = resizeTo<Image16> (w, h, TI_Bilinear, tmpImg);
668 }
669
670 delete tmpImg;
671
672
673 if (ri->get_FujiWidth() != 0) {
674 tpp->scale = (double) (height - ri->get_FujiWidth()) * 2.0 / (rotate_90 ? w : h);
675 } else {
676 tpp->scale = (double) height / (rotate_90 ? w : h);
677 }
678 if(!forHistogramMatching) { // we don't need this for histogram matching
679
680 // generate histogram for auto exposure, also calculate autoWB
681 tpp->aeHistCompression = 3;
682 tpp->aeHistogram(65536 >> tpp->aeHistCompression);
683 tpp->aeHistogram.clear();
684
685 const unsigned int add = filter ? 1 : 4 / ri->get_colors();
686
687 double pixSum[3] = {0.0};
688 unsigned int n[3] = {0};
689 const double compression = pow(2.0, tpp->aeHistCompression);
690 const double camWb[3] = {tpp->camwbRed / compression, tpp->camwbGreen / compression, tpp->camwbBlue / compression};
691 const double clipval = 64000.0 / tpp->defGain;
692
693 for (int i = 32; i < height - 32; i++) {
694 int start, end;
695
696 if (ri->get_FujiWidth() != 0) {
697 int fw = ri->get_FujiWidth();
698 start = ABS (fw - i) + 32;
699 end = min (height + width - fw - i, fw + i) - 32;
700 } else {
701 start = 32;
702 end = width - 32;
703 }
704
705 if (ri->get_colors() == 1) {
706 for (int j = start; j < end; j++) {
707 tpp->aeHistogram[image[i * width + j][0] >> tpp->aeHistCompression]++;
708 }
709 } else if (ri->getSensorType() == ST_BAYER) {
710 int c0 = ri->FC(i, start);
711 int c1 = ri->FC(i, start + 1);
712 int j = start;
713 int n0 = 0;
714 int n1 = 0;
715 double pixSum0 = 0.0;
716 double pixSum1 = 0.0;
717 for (; j < end - 1; j+=2) {
718 double v0 = image[i * width + j][c0];
719 tpp->aeHistogram[(int)(camWb[c0] * v0)]++;
720 if (v0 <= clipval) {
721 pixSum0 += v0;
722 n0++;
723 }
724 double v1 = image[i * width + j + 1][c1];
725 tpp->aeHistogram[(int)(camWb[c1] * v1)]++;
726 if (v1 <= clipval) {
727 pixSum1 += v1;
728 n1++;
729 }
730 }
731 if (j < end) {
732 double v0 = image[i * width + j][c0];
733 tpp->aeHistogram[(int)(camWb[c0] * v0)]++;
734 if (v0 <= clipval) {
735 pixSum0 += v0;
736 n0++;
737 }
738 }
739 n[c0] += n0;
740 n[c1] += n1;
741 pixSum[c0] += pixSum0;
742 pixSum[c1] += pixSum1;
743 } else if (ri->getSensorType() == ST_FUJI_XTRANS) {
744 int c[6];
745 for(int cc = 0; cc < 6; ++cc) {
746 c[cc] = ri->XTRANSFC(i, start + cc);
747 }
748 int j = start;
749 for (; j < end - 5; j += 6) {
750 for(int cc = 0; cc < 6; ++cc) {
751 double d = image[i * width + j + cc][c[cc]];
752 tpp->aeHistogram[(int)(camWb[c[cc]] * d)]++;
753 if (d <= clipval) {
754 pixSum[c[cc]] += d;
755 n[c[cc]]++;
756 }
757 }
758 }
759 for (; j < end; j++) {
760 if (ri->ISXTRANSGREEN (i, j)) {
761 double d = image[i * width + j][1];
762 tpp->aeHistogram[(int)(camWb[1] * d)]++;
763 if (d <= clipval) {
764 pixSum[1] += d;
765 n[1]++;
766 }
767 } else if (ri->ISXTRANSRED (i, j)) {
768 double d = image[i * width + j][0];
769 tpp->aeHistogram[(int)(camWb[0] * d)]++;
770 if (d <= clipval) {
771 pixSum[0] += d;
772 n[0]++;
773 }
774 } else if (ri->ISXTRANSBLUE (i, j)) {
775 double d = image[i * width + j][2];
776 tpp->aeHistogram[(int)(camWb[2] * d)]++;
777 if (d <= clipval) {
778 pixSum[2] += d;
779 n[2]++;
780 }
781 }
782 }
783 } else { /* if(ri->getSensorType()==ST_FOVEON) */
784 for (int j = start; j < end; j++) {
785 double r = image[i * width + j][0];
786 if (r <= clipval) {
787 pixSum[0] += r;
788 n[0]++;
789 }
790 double g = image[i * width + j][1];
791 if (g <= clipval) {
792 pixSum[1] += g;
793 n[1]++;
794 }
795 tpp->aeHistogram[((int)g) >> tpp->aeHistCompression] += add;
796 double b = image[i * width + j][2];
797 if (b <= clipval) {
798 pixSum[2] += b;
799 n[2]++;
800 }
801 tpp->aeHistogram[((int) (b * 0.5f)) >> tpp->aeHistCompression] += add;
802 }
803 }
804 }
805 // ProcParams paramsForAutoExp; // Dummy for constructor
806 // ImProcFunctions ipf (¶msForAutoExp, false);
807 // ipf.getAutoExp (tpp->aeHistogram, tpp->aeHistCompression, 0.02, tpp->aeExposureCompensation, tpp->aeLightness, tpp->aeContrast, tpp->aeBlack, tpp->aeHighlightCompression, tpp->aeHighlightCompressionThreshold);
808 // tpp->aeValid = true;
809
810 if (ri->get_colors() == 1) {
811 pixSum[0] = pixSum[1] = pixSum[2] = 1.;
812 n[0] = n[1] = n[2] = 1;
813 }
814 pixSum[0] *= tpp->defGain;
815 pixSum[1] *= tpp->defGain;
816 pixSum[2] *= tpp->defGain;
817
818 double reds = pixSum[0] / std::max(n[0], 1u) * tpp->camwbRed;
819 double greens = pixSum[1] / std::max(n[1], 1u) * tpp->camwbGreen;
820 double blues = pixSum[2] / std::max(n[2], 1u) * tpp->camwbBlue;
821
822 tpp->redAWBMul = ri->get_rgb_cam (0, 0) * reds + ri->get_rgb_cam (0, 1) * greens + ri->get_rgb_cam (0, 2) * blues;
823 tpp->greenAWBMul = ri->get_rgb_cam (1, 0) * reds + ri->get_rgb_cam (1, 1) * greens + ri->get_rgb_cam (1, 2) * blues;
824 tpp->blueAWBMul = ri->get_rgb_cam (2, 0) * reds + ri->get_rgb_cam (2, 1) * greens + ri->get_rgb_cam (2, 2) * blues;
825 tpp->wbEqual = wbEq;
826
827 ColorTemp cTemp;
828 cTemp.mul2temp (tpp->redAWBMul, tpp->greenAWBMul, tpp->blueAWBMul, tpp->wbEqual, tpp->autoWBTemp, tpp->autoWBGreen);
829 }
830
831 if (rotate && ri->get_rotateDegree() > 0) {
832 tpp->thumbImg->rotate (ri->get_rotateDegree());
833 }
834
835 for (int a = 0; a < 3; a++)
836 for (int b = 0; b < 3; b++) {
837 tpp->colorMatrix[a][b] = ri->get_rgb_cam (a, b);
838 }
839
840 tpp->init();
841
842 RawImageSource::computeFullSize(ri, TR_NONE, tpp->full_width, tpp->full_height);
843
844 delete ri;
845 return tpp;
846 }
847 #undef FISRED
848 #undef FISGREEN
849 #undef FISBLUE
850
init()851 void Thumbnail::init ()
852 {
853 RawImageSource::inverse33 (colorMatrix, iColorMatrix);
854 //colorMatrix is rgb_cam
855 memset (cam2xyz, 0, sizeof (cam2xyz));
856
857 for (int i = 0; i < 3; i++)
858 for (int j = 0; j < 3; j++)
859 for (int k = 0; k < 3; k++) {
860 cam2xyz[i][j] += xyz_sRGB[i][k] * colorMatrix[k][j];
861 }
862
863 camProfile = ICCStore::getInstance()->createFromMatrix (cam2xyz, false, "Camera");
864 }
865
Thumbnail()866 Thumbnail::Thumbnail () :
867 camProfile (nullptr),
868 iColorMatrix{},
869 cam2xyz{},
870 thumbImg (nullptr),
871 camwbRed (1.0),
872 camwbGreen (1.0),
873 camwbBlue (1.0),
874 redAWBMul (-1.0),
875 greenAWBMul (-1.0),
876 blueAWBMul (-1.0),
877 autoWBTemp (2700),
878 autoWBGreen (1.0),
879 wbEqual (-1.0),
880 aeHistCompression (3),
881 aeValid(false),
882 aeExposureCompensation(0.0),
883 aeLightness(0),
884 aeContrast(0),
885 aeBlack(0),
886 aeHighlightCompression(0),
887 aeHighlightCompressionThreshold(0),
888 embProfileLength (0),
889 embProfileData (nullptr),
890 embProfile (nullptr),
891 redMultiplier (1.0),
892 greenMultiplier (1.0),
893 blueMultiplier (1.0),
894 scale (1.0),
895 defGain (1.0),
896 scaleForSave (8192),
897 gammaCorrected (false),
898 colorMatrix{},
899 scaleGain(1.0),
900 sensorType(ST_NONE),
901 isRaw(false),
902 full_width(-1),
903 full_height(-1)
904 {
905 }
906
~Thumbnail()907 Thumbnail::~Thumbnail ()
908 {
909
910 delete thumbImg;
911 //delete [] aeHistogram;
912 delete [] embProfileData;
913
914 if (embProfile) {
915 cmsCloseProfile (embProfile);
916 }
917
918 if (camProfile) {
919 cmsCloseProfile (camProfile);
920 }
921 }
922
923 // Simple processing of RAW internal JPGs
quickProcessImage(const procparams::ProcParams & params,int rheight,rtengine::TypeInterpolation interp)924 IImage8* Thumbnail::quickProcessImage (const procparams::ProcParams& params, int rheight, rtengine::TypeInterpolation interp)
925 {
926
927 int rwidth;
928
929 if (params.coarse.rotate == 90 || params.coarse.rotate == 270) {
930 rwidth = rheight;
931 rheight = thumbImg->getHeight() * rwidth / thumbImg->getWidth();
932 } else {
933 rwidth = thumbImg->getWidth() * rheight / thumbImg->getHeight();
934 }
935
936 Image8* baseImg = resizeTo<Image8> (rwidth, rheight, interp, thumbImg);
937
938 if (params.coarse.rotate) {
939 baseImg->rotate (params.coarse.rotate);
940 }
941
942 if (params.coarse.hflip) {
943 baseImg->hflip ();
944 }
945
946 if (params.coarse.vflip) {
947 baseImg->vflip ();
948 }
949
950 return baseImg;
951 }
952
953 // 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)954 IImage8* Thumbnail::processImage (const procparams::ProcParams& params, eSensorType sensorType, int rheight, TypeInterpolation interp, const FramesMetaData *metadata, double& myscale, bool forMonitor, bool forHistogramMatching)
955 {
956 std::string camName = metadata->getCamera();
957
958 // check if the WB's equalizer value has changed
959 if (wbEqual < (params.wb.equal - 5e-4) || wbEqual > (params.wb.equal + 5e-4)) {
960 wbEqual = params.wb.equal;
961 // recompute the autoWB
962 ColorTemp cTemp;
963 cTemp.mul2temp (redAWBMul, greenAWBMul, blueAWBMul, wbEqual, autoWBTemp, autoWBGreen);
964 }
965
966 // compute WB multipliers
967 ColorTemp currWB;
968
969 if (!params.wb.enabled) {
970 currWB = ColorTemp();
971 } else if (params.wb.method == WBParams::CAMERA) {
972 //recall colorMatrix is rgb_cam
973 double cam_r = colorMatrix[0][0] * camwbRed + colorMatrix[0][1] * camwbGreen + colorMatrix[0][2] * camwbBlue;
974 double cam_g = colorMatrix[1][0] * camwbRed + colorMatrix[1][1] * camwbGreen + colorMatrix[1][2] * camwbBlue;
975 double cam_b = colorMatrix[2][0] * camwbRed + colorMatrix[2][1] * camwbGreen + colorMatrix[2][2] * camwbBlue;
976 currWB = ColorTemp (cam_r, cam_g, cam_b, params.wb.equal);
977 } else if (params.wb.method == WBParams::AUTO) {
978 currWB = ColorTemp (autoWBTemp, autoWBGreen, wbEqual, "Custom");
979 } else if (params.wb.method == WBParams::CUSTOM_TEMP) {
980 currWB = ColorTemp(params.wb.temperature, params.wb.green, params.wb.equal, "Custom");
981 } else if (params.wb.method == WBParams::CUSTOM_MULT) {
982 currWB = ColorTemp(params.wb.mult[0], params.wb.mult[1], params.wb.mult[2], 1.0);
983 }
984 double rm, gm, bm;
985 if (currWB.getTemp() < 0) {
986 rm = redMultiplier;
987 gm = greenMultiplier;
988 bm = blueMultiplier;
989 } else {
990 double r, g, b;
991 currWB.getMultipliers (r, g, b);
992 //iColorMatrix is cam_rgb
993 rm = iColorMatrix[0][0] * r + iColorMatrix[0][1] * g + iColorMatrix[0][2] * b;
994 gm = iColorMatrix[1][0] * r + iColorMatrix[1][1] * g + iColorMatrix[1][2] * b;
995 bm = iColorMatrix[2][0] * r + iColorMatrix[2][1] * g + iColorMatrix[2][2] * b;
996 }
997 rm = camwbRed / rm;
998 gm = camwbGreen / gm;
999 bm = camwbBlue / bm;
1000 double mul_lum = 0.299 * rm + 0.587 * gm + 0.114 * bm;
1001 float rmi, gmi, bmi;
1002
1003 rmi = rm * defGain / mul_lum;
1004 gmi = gm * defGain / mul_lum;
1005 bmi = bm * defGain / mul_lum;
1006
1007 // The RAW exposure is not reflected since it's done in preprocessing. If we only have e.g. the cached thumb,
1008 // that is already preprocessed. So we simulate the effect here roughly my modifying the exposure accordingly
1009 if (isRaw) {
1010 rmi *= params.raw.expos;
1011 gmi *= params.raw.expos;
1012 bmi *= params.raw.expos;
1013 }
1014
1015 // resize to requested width and perform coarse transformation
1016 int rwidth;
1017
1018 if (params.coarse.rotate == 90 || params.coarse.rotate == 270) {
1019 rwidth = rheight;
1020 rheight = int (size_t (thumbImg->getHeight()) * size_t (rwidth) / size_t (thumbImg->getWidth()));
1021 } else {
1022 rwidth = int (size_t (thumbImg->getWidth()) * size_t (rheight) / size_t (thumbImg->getHeight()));
1023 }
1024
1025 rwidth = std::max(rwidth, 1);
1026 rheight = std::max(rheight, 1);
1027
1028 Imagefloat* baseImg = resizeTo<Imagefloat> (rwidth, rheight, interp, thumbImg);
1029 processFilmNegative(params, baseImg, rwidth, rheight);
1030 baseImg->assignColorSpace(params.icm.workingProfile);
1031
1032 if (params.coarse.rotate) {
1033 baseImg->rotate (params.coarse.rotate);
1034 rwidth = baseImg->getWidth();
1035 rheight = baseImg->getHeight();
1036 }
1037
1038 if (params.coarse.hflip) {
1039 baseImg->hflip ();
1040 }
1041
1042 if (params.coarse.vflip) {
1043 baseImg->vflip ();
1044 }
1045
1046 const bool hrenabled = params.exposure.enabled && params.exposure.hrmode != procparams::ExposureParams::HR_OFF;
1047
1048 // apply white balance and raw white point (simulated)
1049 for (int i = 0; i < rheight; i++) {
1050 #ifdef _OPENMP
1051 #pragma omp simd
1052 #endif
1053 for (int j = 0; j < rwidth; j++) {
1054 float red = baseImg->r (i, j) * rmi;
1055 float green = baseImg->g (i, j) * gmi;
1056 float blue = baseImg->b (i, j) * bmi;
1057
1058 // avoid magenta highlights if highlight recovery is enabled
1059 if (hrenabled && red > MAXVALF && blue > MAXVALF) {
1060 baseImg->r(i, j) = baseImg->g(i, j) = baseImg->b(i, j) = CLIP((red + green + blue) / 3.f);
1061 } else {
1062 baseImg->r(i, j) = CLIP(red);
1063 baseImg->g(i, j) = CLIP(green);
1064 baseImg->b(i, j) = CLIP(blue);
1065 }
1066 }
1067 }
1068
1069 // if luma denoise has to be done for thumbnails, it should be right here
1070
1071 // perform color space transformation
1072
1073 if (isRaw) {
1074 double pre_mul[3] = { redMultiplier, greenMultiplier, blueMultiplier };
1075 RawImageSource::colorSpaceConversion (baseImg, params.icm, currWB, pre_mul, embProfile, camProfile, cam2xyz, camName, metadata->getFileName());
1076 } else {
1077 StdImageSource::colorSpaceConversion (baseImg, params.icm, embProfile, thumbImg->getSampleFormat());
1078 }
1079
1080 int fw = baseImg->getWidth();
1081 int fh = baseImg->getHeight();
1082
1083 ImProcFunctions ipf (¶ms, forHistogramMatching); // enable multithreading when forHistogramMatching is true
1084 int origFW;
1085 int origFH;
1086 double tscale = 0.0;
1087 getDimensions (origFW, origFH, tscale);
1088 ipf.setScale((origFW * tscale) / rwidth);
1089 //ipf.updateColorProfiles (ICCStore::getInstance()->getDefaultMonitorProfileName(), options.rtSettings.monitorIntent, false, false);
1090 ipf.setMonitorTransform(ICCStore::getInstance()->getThumbnailMonitorTransform());
1091
1092 LUTu hist16 (65536);
1093
1094 ipf.firstAnalysis (baseImg, params, hist16);
1095
1096 bool stop = ipf.process(ImProcFunctions::Pipeline::THUMBNAIL, ImProcFunctions::Stage::STAGE_0, baseImg);
1097
1098 // perform transform
1099 if (ipf.needsTransform()) {
1100 Imagefloat* trImg = new Imagefloat(fw, fh, baseImg);
1101 ipf.transform (baseImg, trImg, 0, 0, 0, 0, fw, fh, origFW * tscale + 0.5, origFH * tscale + 0.5, metadata, 0, false); // Raw rotate degree not detectable here
1102 delete baseImg;
1103 baseImg = trImg;
1104 }
1105
1106 DCPProfile *dcpProf = nullptr;
1107 DCPProfile::ApplyState as;
1108
1109 if (isRaw) {
1110 cmsHPROFILE dummy;
1111 RawImageSource::findInputProfile (params.icm.inputProfile, nullptr, camName, metadata->getFileName(), &dcpProf, dummy);
1112
1113 if (dcpProf) {
1114 dcpProf->setStep2ApplyState (params.icm.workingProfile, params.icm.toneCurve, params.icm.applyLookTable, params.icm.applyBaselineExposureOffset, as);
1115 }
1116 }
1117 ipf.setDCPProfile(dcpProf, as);
1118
1119 stop = stop || ipf.process(ImProcFunctions::Pipeline::THUMBNAIL, ImProcFunctions::Stage::STAGE_1, baseImg);
1120 stop = stop || ipf.process(ImProcFunctions::Pipeline::THUMBNAIL, ImProcFunctions::Stage::STAGE_2, baseImg);
1121 stop = stop || ipf.process(ImProcFunctions::Pipeline::THUMBNAIL, ImProcFunctions::Stage::STAGE_3, baseImg);
1122
1123 // obtain final image
1124 Image8 *readyImg = nullptr;
1125 if (forMonitor) {
1126 readyImg = new Image8 (fw, fh);
1127 ipf.rgb2monitor(baseImg, readyImg);
1128 } else {
1129 readyImg = ipf.rgb2out(baseImg, 0, 0, fw, fh, params.icm, false);
1130 }
1131 delete baseImg;
1132
1133 // calculate scale
1134 if (params.coarse.rotate == 90 || params.coarse.rotate == 270) {
1135 myscale = scale * thumbImg->getWidth() / fh;
1136 } else {
1137 myscale = scale * thumbImg->getHeight() / fh;
1138 }
1139
1140 myscale = 1.0 / myscale;
1141
1142 ipf.setMonitorTransform(nullptr);
1143
1144 return readyImg;
1145 }
1146
getImageWidth(const procparams::ProcParams & params,int rheight,float & ratio)1147 int Thumbnail::getImageWidth (const procparams::ProcParams& params, int rheight, float &ratio)
1148 {
1149 if (!thumbImg) {
1150 return 0; // Can happen if thumb is just building and GUI comes in with resize wishes
1151 }
1152
1153 int rwidth;
1154
1155 if (params.coarse.rotate == 90 || params.coarse.rotate == 270) {
1156 ratio = (float) (thumbImg->getHeight()) / (float) (thumbImg->getWidth());
1157 } else {
1158 ratio = (float) (thumbImg->getWidth()) / (float) (thumbImg->getHeight());
1159 }
1160
1161 rwidth = (int) (ratio * (float)rheight);
1162
1163 return rwidth;
1164 }
1165
getDimensions(int & w,int & h,double & scaleFac)1166 void Thumbnail::getDimensions (int& w, int& h, double& scaleFac)
1167 {
1168 if (thumbImg) {
1169 w = thumbImg->getWidth();
1170 h = thumbImg->getHeight();
1171 scaleFac = scale;
1172 } else {
1173 w = 0;
1174 h = 0;
1175 scale = 1;
1176 }
1177 }
1178
getCamWB(double & temp,double & green)1179 void Thumbnail::getCamWB (double& temp, double& green)
1180 {
1181
1182 double cam_r = colorMatrix[0][0] * camwbRed + colorMatrix[0][1] * camwbGreen + colorMatrix[0][2] * camwbBlue;
1183 double cam_g = colorMatrix[1][0] * camwbRed + colorMatrix[1][1] * camwbGreen + colorMatrix[1][2] * camwbBlue;
1184 double cam_b = colorMatrix[2][0] * camwbRed + colorMatrix[2][1] * camwbGreen + colorMatrix[2][2] * camwbBlue;
1185 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
1186 temp = currWB.getTemp ();
1187 green = currWB.getGreen ();
1188 }
1189
getAutoWB(double & temp,double & green,double equal)1190 void Thumbnail::getAutoWB (double& temp, double& green, double equal)
1191 {
1192
1193 if (equal != wbEqual) {
1194 // compute the values depending on equal
1195 ColorTemp cTemp;
1196 wbEqual = equal;
1197 // compute autoWBTemp and autoWBGreen
1198 cTemp.mul2temp (redAWBMul, greenAWBMul, blueAWBMul, wbEqual, autoWBTemp, autoWBGreen);
1199 }
1200
1201 temp = autoWBTemp;
1202 green = autoWBGreen;
1203 }
1204
getAutoWBMultipliers(double & rm,double & gm,double & bm)1205 void Thumbnail::getAutoWBMultipliers (double& rm, double& gm, double& bm)
1206 {
1207 rm = redAWBMul;
1208 gm = greenAWBMul;
1209 bm = blueAWBMul;
1210 }
1211
1212
getSpotWB(const procparams::ProcParams & params,int xp,int yp,int rect,double & rtemp,double & rgreen)1213 void Thumbnail::getSpotWB (const procparams::ProcParams& params, int xp, int yp, int rect, double& rtemp, double& rgreen)
1214 {
1215
1216 std::vector<Coord2D> points, red, green, blue;
1217
1218 for (int i = yp - rect; i <= yp + rect; i++)
1219 for (int j = xp - rect; j <= xp + rect; j++) {
1220 points.push_back (Coord2D (j, i));
1221 }
1222
1223 int fw = thumbImg->getWidth(), fh = thumbImg->getHeight();
1224
1225 if (params.coarse.rotate == 90 || params.coarse.rotate == 270) {
1226 fw = thumbImg->getHeight();
1227 fh = thumbImg->getWidth();
1228 }
1229
1230 ImProcFunctions ipf (¶ms, false);
1231 ipf.transCoord (fw, fh, points, red, green, blue);
1232 int tr = getCoarseBitMask (params.coarse);
1233 // calculate spot wb (copy & pasted from stdimagesource)
1234 double reds = 0, greens = 0, blues = 0;
1235 int rn = 0, gn = 0, bn = 0;
1236 thumbImg->getSpotWBData (reds, greens, blues, rn, gn, bn, red, green, blue, tr);
1237 reds = reds / rn * camwbRed;
1238 greens = greens / gn * camwbGreen;
1239 blues = blues / bn * camwbBlue;
1240
1241 double rm = colorMatrix[0][0] * reds + colorMatrix[0][1] * greens + colorMatrix[0][2] * blues;
1242 double gm = colorMatrix[1][0] * reds + colorMatrix[1][1] * greens + colorMatrix[1][2] * blues;
1243 double bm = colorMatrix[2][0] * reds + colorMatrix[2][1] * greens + colorMatrix[2][2] * blues;
1244
1245 ColorTemp ct (rm, gm, bm, params.wb.equal);
1246 rtemp = ct.getTemp ();
1247 rgreen = ct.getGreen ();
1248 }
1249
1250
transformPixel(int x,int y,int tran,int & tx,int & ty)1251 void Thumbnail::transformPixel (int x, int y, int tran, int& tx, int& ty)
1252 {
1253
1254 int W = thumbImg->getWidth();
1255 int H = thumbImg->getHeight();
1256 int sw = W, sh = H;
1257
1258 if ((tran & TR_ROT) == TR_R90 || (tran & TR_ROT) == TR_R270) {
1259 sw = H;
1260 sh = W;
1261 }
1262
1263 int ppx = x, ppy = y;
1264
1265 if (tran & TR_HFLIP) {
1266 ppx = sw - 1 - x ;
1267 }
1268
1269 if (tran & TR_VFLIP) {
1270 ppy = sh - 1 - y;
1271 }
1272
1273 tx = ppx;
1274 ty = ppy;
1275
1276 if ((tran & TR_ROT) == TR_R180) {
1277 tx = W - 1 - ppx;
1278 ty = H - 1 - ppy;
1279 } else if ((tran & TR_ROT) == TR_R90) {
1280 tx = ppy;
1281 ty = H - 1 - ppx;
1282 } else if ((tran & TR_ROT) == TR_R270) {
1283 tx = W - 1 - ppy;
1284 ty = ppx;
1285 }
1286
1287 tx /= scale;
1288 ty /= scale;
1289 }
1290
getGrayscaleHistEQ(int trim_width)1291 unsigned char* Thumbnail::getGrayscaleHistEQ (int trim_width)
1292 {
1293 if (!thumbImg) {
1294 return nullptr;
1295 }
1296
1297 if (thumbImg->getWidth() < trim_width) {
1298 return nullptr;
1299 }
1300
1301 // to utilize the 8 bit color range of the thumbnail we brighten it and apply gamma correction
1302 unsigned char* tmpdata = new unsigned char[thumbImg->getHeight() * trim_width];
1303 int ix = 0;
1304
1305 if (gammaCorrected) {
1306 // 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.
1307 // Strategy is limit a certain percent of pixels so the overall picture quality when scaling to 8 bit is way better
1308 const double BurnOffPct = 0.03; // *100 = percent pixels that may be clipped
1309
1310 // Calc the histogram
1311 unsigned int* hist16 = new unsigned int [65536];
1312 memset (hist16, 0, sizeof (int) * 65536);
1313
1314 if (thumbImg->getType() == sImage8) {
1315 Image8 *image = static_cast<Image8*> (thumbImg);
1316 image->calcGrayscaleHist (hist16);
1317 } else if (thumbImg->getType() == sImage16) {
1318 Image16 *image = static_cast<Image16*> (thumbImg);
1319 image->calcGrayscaleHist (hist16);
1320 } else if (thumbImg->getType() == sImagefloat) {
1321 Imagefloat *image = static_cast<Imagefloat*> (thumbImg);
1322 image->calcGrayscaleHist (hist16);
1323 } else {
1324 printf ("getGrayscaleHistEQ #1: Unsupported image type \"%s\"!\n", thumbImg->getType());
1325 }
1326
1327 // Go down till we cut off that many pixels
1328 unsigned long cutoff = thumbImg->getHeight() * thumbImg->getHeight() * 4 * BurnOffPct;
1329
1330 int max_;
1331 unsigned long sum = 0;
1332
1333 for (max_ = 65535; max_ > 16384 && sum < cutoff; max_--) {
1334 sum += hist16[max_];
1335 }
1336
1337 delete[] hist16;
1338
1339 scaleForSave = 65535 * 8192 / max_;
1340
1341 // Correction and gamma to 8 Bit
1342 if (thumbImg->getType() == sImage8) {
1343 Image8 *image = static_cast<Image8*> (thumbImg);
1344
1345 for (int i = 0; i < thumbImg->getHeight(); i++)
1346 for (int j = (thumbImg->getWidth() - trim_width) / 2; j < trim_width + (thumbImg->getWidth() - trim_width) / 2; j++) {
1347 unsigned short r_, g_, b_;
1348 image->convertTo (image->r (i, j), r_);
1349 image->convertTo (image->g (i, j), g_);
1350 image->convertTo (image->b (i, j), b_);
1351 int r = Color::gammatabThumb[min (r_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1352 int g = Color::gammatabThumb[min (g_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1353 int b = Color::gammatabThumb[min (b_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1354 tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16;
1355 }
1356 } else if (thumbImg->getType() == sImage16) {
1357 Image16 *image = static_cast<Image16*> (thumbImg);
1358
1359 for (int i = 0; i < thumbImg->getHeight(); i++)
1360 for (int j = (thumbImg->getWidth() - trim_width) / 2; j < trim_width + (thumbImg->getWidth() - trim_width) / 2; j++) {
1361 unsigned short r_, g_, b_;
1362 image->convertTo (image->r (i, j), r_);
1363 image->convertTo (image->g (i, j), g_);
1364 image->convertTo (image->b (i, j), b_);
1365 int r = Color::gammatabThumb[min (r_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1366 int g = Color::gammatabThumb[min (g_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1367 int b = Color::gammatabThumb[min (b_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1368 tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16;
1369 }
1370 } else if (thumbImg->getType() == sImagefloat) {
1371 Imagefloat *image = static_cast<Imagefloat*> (thumbImg);
1372
1373 for (int i = 0; i < thumbImg->getHeight(); i++)
1374 for (int j = (thumbImg->getWidth() - trim_width) / 2; j < trim_width + (thumbImg->getWidth() - trim_width) / 2; j++) {
1375 unsigned short r_, g_, b_;
1376 image->convertTo (image->r (i, j), r_);
1377 image->convertTo (image->g (i, j), g_);
1378 image->convertTo (image->b (i, j), b_);
1379 int r = Color::gammatabThumb[min (r_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1380 int g = Color::gammatabThumb[min (g_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1381 int b = Color::gammatabThumb[min (b_, static_cast<unsigned short> (max_)) * scaleForSave >> 13];
1382 tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16;
1383 }
1384 }
1385 } else {
1386 // If it's not gamma corrected (usually a JPG) we take the normal maximum
1387 int max = 0;
1388
1389 if (thumbImg->getType() == sImage8) {
1390 Image8 *image = static_cast<Image8*> (thumbImg);
1391 unsigned char max_ = 0;
1392
1393 for (int row = 0; row < image->getHeight(); row++)
1394 for (int col = 0; col < image->getWidth(); col++) {
1395 if (image->r (row, col) > max_) {
1396 max_ = image->r (row, col);
1397 }
1398
1399 if (image->g (row, col) > max_) {
1400 max_ = image->g (row, col);
1401 }
1402
1403 if (image->b (row, col) > max_) {
1404 max_ = image->b (row, col);
1405 }
1406 }
1407
1408 image->convertTo (max_, max);
1409
1410 if (max < 16384) {
1411 max = 16384;
1412 }
1413
1414 scaleForSave = 65535 * 8192 / max;
1415
1416 // Correction and gamma to 8 Bit
1417 for (int i = 0; i < image->getHeight(); i++)
1418 for (int j = (image->getWidth() - trim_width) / 2; j < trim_width + (image->getWidth() - trim_width) / 2; j++) {
1419 unsigned short rtmp, gtmp, btmp;
1420 image->convertTo (image->r (i, j), rtmp);
1421 image->convertTo (image->g (i, j), gtmp);
1422 image->convertTo (image->b (i, j), btmp);
1423 int r = rtmp * scaleForSave >> 21;
1424 int g = gtmp * scaleForSave >> 21;
1425 int b = btmp * scaleForSave >> 21;
1426 tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16;
1427 }
1428 } else if (thumbImg->getType() == sImage16) {
1429 Image16 *image = static_cast<Image16*> (thumbImg);
1430 unsigned short max_ = 0;
1431
1432 for (int row = 0; row < image->getHeight(); row++)
1433 for (int col = 0; col < image->getWidth(); col++) {
1434 if (image->r (row, col) > max_) {
1435 max_ = image->r (row, col);
1436 }
1437
1438 if (image->g (row, col) > max_) {
1439 max_ = image->g (row, col);
1440 }
1441
1442 if (image->b (row, col) > max_) {
1443 max_ = image->b (row, col);
1444 }
1445 }
1446
1447 image->convertTo (max_, max);
1448
1449 if (max < 16384) {
1450 max = 16384;
1451 }
1452
1453 scaleForSave = 65535 * 8192 / max;
1454
1455 // Correction and gamma to 8 Bit
1456 for (int i = 0; i < image->getHeight(); i++)
1457 for (int j = (image->getWidth() - trim_width) / 2; j < trim_width + (image->getWidth() - trim_width) / 2; j++) {
1458 unsigned short rtmp, gtmp, btmp;
1459 image->convertTo (image->r (i, j), rtmp);
1460 image->convertTo (image->g (i, j), gtmp);
1461 image->convertTo (image->b (i, j), btmp);
1462 int r = rtmp * scaleForSave >> 21;
1463 int g = gtmp * scaleForSave >> 21;
1464 int b = btmp * scaleForSave >> 21;
1465 tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16;
1466 }
1467 } else if (thumbImg->getType() == sImagefloat) {
1468 Imagefloat *image = static_cast<Imagefloat*> (thumbImg);
1469 float max_ = 0.f;
1470
1471 for (int row = 0; row < image->getHeight(); row++)
1472 for (int col = 0; col < image->getWidth(); col++) {
1473 if (image->r (row, col) > max_) {
1474 max_ = image->r (row, col);
1475 }
1476
1477 if (image->g (row, col) > max_) {
1478 max_ = image->g (row, col);
1479 }
1480
1481 if (image->b (row, col) > max_) {
1482 max_ = image->b (row, col);
1483 }
1484 }
1485
1486 image->convertTo (max_, max);
1487
1488 if (max < 16384) {
1489 max = 16384;
1490 }
1491
1492 scaleForSave = 65535 * 8192 / max;
1493
1494 // Correction and gamma to 8 Bit
1495 for (int i = 0; i < image->getHeight(); i++)
1496 for (int j = (image->getWidth() - trim_width) / 2; j < trim_width + (image->getWidth() - trim_width) / 2; j++) {
1497 unsigned short rtmp, gtmp, btmp;
1498 image->convertTo (image->r (i, j), rtmp);
1499 image->convertTo (image->g (i, j), gtmp);
1500 image->convertTo (image->b (i, j), btmp);
1501 int r = rtmp * scaleForSave >> 21;
1502 int g = gtmp * scaleForSave >> 21;
1503 int b = btmp * scaleForSave >> 21;
1504 tmpdata[ix++] = (r * 19595 + g * 38469 + b * 7472) >> 16;
1505 }
1506 } else {
1507 printf ("getGrayscaleHistEQ #2: Unsupported image type \"%s\"!\n", thumbImg->getType());
1508 }
1509 }
1510
1511 // histogram equalization
1512 unsigned int hist[256] = {0};
1513
1514 for (int i = 0; i < ix; i++) {
1515 hist[tmpdata[i]]++;
1516 }
1517
1518 int cdf = 0, cdf_min = -1;
1519
1520 for (int i = 0; i < 256; i++) {
1521 cdf += hist[i];
1522
1523 if (cdf > 0 && cdf_min == -1) {
1524 cdf_min = cdf;
1525 }
1526
1527 if (cdf_min != -1) {
1528 hist[i] = (cdf - cdf_min) * 255 / ((thumbImg->getHeight() * trim_width) - cdf_min);
1529 }
1530 }
1531
1532 for (int i = 0; i < ix; i++) {
1533 tmpdata[i] = hist[tmpdata[i]];
1534 }
1535
1536 return tmpdata;
1537 }
1538
writeImage(const Glib::ustring & fname)1539 bool Thumbnail::writeImage (const Glib::ustring& fname)
1540 {
1541
1542 if (!thumbImg) {
1543 return false;
1544 }
1545
1546 Glib::ustring fullFName = fname + ".rtti";
1547
1548 FILE* f = g_fopen (fullFName.c_str (), "wb");
1549
1550 if (!f) {
1551 return false;
1552 }
1553
1554 fwrite (thumbImg->getType(), sizeof (char), strlen (thumbImg->getType()), f);
1555 fputc ('\n', f);
1556 guint32 w = guint32 (thumbImg->getWidth());
1557 guint32 h = guint32 (thumbImg->getHeight());
1558 fwrite (&w, sizeof (guint32), 1, f);
1559 fwrite (&h, sizeof (guint32), 1, f);
1560
1561 if (thumbImg->getType() == sImage8) {
1562 Image8 *image = static_cast<Image8*> (thumbImg);
1563 image->writeData (f);
1564 } else if (thumbImg->getType() == sImage16) {
1565 Image16 *image = static_cast<Image16*> (thumbImg);
1566 image->writeData (f);
1567 } else if (thumbImg->getType() == sImagefloat) {
1568 Imagefloat *image = static_cast<Imagefloat*> (thumbImg);
1569 image->writeData (f);
1570 }
1571
1572 //thumbImg->writeData(f);
1573 fclose (f);
1574 return true;
1575 }
1576
readImage(const Glib::ustring & fname)1577 bool Thumbnail::readImage (const Glib::ustring& fname)
1578 {
1579
1580 if (thumbImg) {
1581 delete thumbImg;
1582 thumbImg = nullptr;
1583 }
1584
1585 Glib::ustring fullFName = fname + ".rtti";
1586
1587 if (!Glib::file_test(fullFName, Glib::FILE_TEST_EXISTS)) {
1588 return false;
1589 }
1590
1591 FILE* f = g_fopen(fullFName.c_str (), "rb");
1592
1593 if (!f) {
1594 return false;
1595 }
1596
1597 char imgType[31]; // 30 -> arbitrary size, but should be enough for all image type's name
1598 fgets(imgType, 30, f);
1599 imgType[strlen(imgType) - 1] = '\0'; // imgType has a \n trailing character, so we overwrite it by the \0 char
1600
1601 guint32 width, height;
1602
1603 if (fread(&width, 1, sizeof(guint32), f) < sizeof(guint32)) {
1604 width = 0;
1605 }
1606
1607 if (fread(&height, 1, sizeof(guint32), f) < sizeof(guint32)) {
1608 height = 0;
1609 }
1610
1611 bool success = false;
1612
1613 if (std::min(width , height) > 0) {
1614 if (!strcmp(imgType, sImage8)) {
1615 Image8 *image = new Image8(width, height);
1616 image->readData(f);
1617 thumbImg = image;
1618 success = true;
1619 } else if (!strcmp(imgType, sImage16)) {
1620 Image16 *image = new Image16(width, height);
1621 image->readData(f);
1622 thumbImg = image;
1623 success = true;
1624 } else if (!strcmp(imgType, sImagefloat)) {
1625 Imagefloat *image = new Imagefloat(width, height);
1626 image->readData(f);
1627 thumbImg = image;
1628 success = true;
1629 } else {
1630 printf ("readImage: Unsupported image type \"%s\"!\n", imgType);
1631 }
1632 }
1633 fclose(f);
1634 return success;
1635 }
1636
readData(const Glib::ustring & fname)1637 bool Thumbnail::readData (const Glib::ustring& fname)
1638 {
1639 setlocale (LC_NUMERIC, "C"); // to set decimal point to "."
1640 Glib::KeyFile keyFile;
1641
1642 try {
1643 MyMutex::MyLock thmbLock (thumbMutex);
1644
1645 try {
1646 keyFile.load_from_file (fname);
1647 } catch (Glib::Error&) {
1648 return false;
1649 }
1650
1651 if (keyFile.has_group ("LiveThumbData")) {
1652 if (keyFile.has_key ("LiveThumbData", "CamWBRed")) {
1653 camwbRed = keyFile.get_double ("LiveThumbData", "CamWBRed");
1654 }
1655
1656 if (keyFile.has_key ("LiveThumbData", "CamWBGreen")) {
1657 camwbGreen = keyFile.get_double ("LiveThumbData", "CamWBGreen");
1658 }
1659
1660 if (keyFile.has_key ("LiveThumbData", "CamWBBlue")) {
1661 camwbBlue = keyFile.get_double ("LiveThumbData", "CamWBBlue");
1662 }
1663
1664 if (keyFile.has_key ("LiveThumbData", "RedAWBMul")) {
1665 redAWBMul = keyFile.get_double ("LiveThumbData", "RedAWBMul");
1666 }
1667
1668 if (keyFile.has_key ("LiveThumbData", "GreenAWBMul")) {
1669 greenAWBMul = keyFile.get_double ("LiveThumbData", "GreenAWBMul");
1670 }
1671
1672 if (keyFile.has_key ("LiveThumbData", "BlueAWBMul")) {
1673 blueAWBMul = keyFile.get_double ("LiveThumbData", "BlueAWBMul");
1674 }
1675
1676 if (keyFile.has_key ("LiveThumbData", "AEHistCompression")) {
1677 aeHistCompression = keyFile.get_integer ("LiveThumbData", "AEHistCompression");
1678 }
1679
1680 aeValid = true;
1681 if (keyFile.has_key ("LiveThumbData", "AEExposureCompensation")) {
1682 aeExposureCompensation = keyFile.get_double ("LiveThumbData", "AEExposureCompensation");
1683 } else {
1684 aeValid = false;
1685 }
1686 if (keyFile.has_key ("LiveThumbData", "AELightness")) {
1687 aeLightness = keyFile.get_integer ("LiveThumbData", "AELightness");
1688 } else {
1689 aeValid = false;
1690 }
1691 if (keyFile.has_key ("LiveThumbData", "AEContrast")) {
1692 aeContrast = keyFile.get_integer ("LiveThumbData", "AEContrast");
1693 } else {
1694 aeValid = false;
1695 }
1696 if (keyFile.has_key ("LiveThumbData", "AEBlack")) {
1697 aeBlack = keyFile.get_integer ("LiveThumbData", "AEBlack");
1698 } else {
1699 aeValid = false;
1700 }
1701 if (keyFile.has_key ("LiveThumbData", "AEHighlightCompression")) {
1702 aeHighlightCompression = keyFile.get_integer ("LiveThumbData", "AEHighlightCompression");
1703 } else {
1704 aeValid = false;
1705 }
1706 if (keyFile.has_key ("LiveThumbData", "AEHighlightCompressionThreshold")) {
1707 aeHighlightCompressionThreshold = keyFile.get_integer ("LiveThumbData", "AEHighlightCompressionThreshold");
1708 } else {
1709 aeValid = false;
1710 }
1711
1712 if (keyFile.has_key ("LiveThumbData", "RedMultiplier")) {
1713 redMultiplier = keyFile.get_double ("LiveThumbData", "RedMultiplier");
1714 }
1715
1716 if (keyFile.has_key ("LiveThumbData", "GreenMultiplier")) {
1717 greenMultiplier = keyFile.get_double ("LiveThumbData", "GreenMultiplier");
1718 }
1719
1720 if (keyFile.has_key ("LiveThumbData", "BlueMultiplier")) {
1721 blueMultiplier = keyFile.get_double ("LiveThumbData", "BlueMultiplier");
1722 }
1723
1724 if (keyFile.has_key ("LiveThumbData", "Scale")) {
1725 scale = keyFile.get_double ("LiveThumbData", "Scale");
1726 }
1727
1728 if (keyFile.has_key ("LiveThumbData", "DefaultGain")) {
1729 defGain = keyFile.get_double ("LiveThumbData", "DefaultGain");
1730 }
1731
1732 if (keyFile.has_key ("LiveThumbData", "ScaleForSave")) {
1733 scaleForSave = keyFile.get_integer ("LiveThumbData", "ScaleForSave");
1734 }
1735
1736 if (keyFile.has_key ("LiveThumbData", "GammaCorrected")) {
1737 gammaCorrected = keyFile.get_boolean ("LiveThumbData", "GammaCorrected");
1738 }
1739
1740 if (keyFile.has_key ("LiveThumbData", "ColorMatrix")) {
1741 std::vector<double> cm = keyFile.get_double_list ("LiveThumbData", "ColorMatrix");
1742 int ix = 0;
1743
1744 for (int i = 0; i < 3; i++)
1745 for (int j = 0; j < 3; j++) {
1746 colorMatrix[i][j] = cm[ix++];
1747 }
1748 }
1749
1750 if (keyFile.has_key("LiveThumbData", "ScaleGain")) {
1751 scaleGain = keyFile.get_double("LiveThumbData", "ScaleGain");
1752 }
1753 }
1754
1755 return true;
1756 } catch (Glib::Error &err) {
1757 if (options.rtSettings.verbose) {
1758 printf ("Thumbnail::readData / Error code %d while reading values from \"%s\":\n%s\n", err.code(), fname.c_str(), err.what().c_str());
1759 }
1760 } catch (...) {
1761 if (options.rtSettings.verbose) {
1762 printf ("Thumbnail::readData / Unknown exception while trying to load \"%s\"!\n", fname.c_str());
1763 }
1764 }
1765
1766 return false;
1767 }
1768
writeData(const Glib::ustring & fname)1769 bool Thumbnail::writeData (const Glib::ustring& fname)
1770 {
1771 MyMutex::MyLock thmbLock (thumbMutex);
1772
1773 Glib::ustring keyData;
1774
1775 try {
1776
1777 Glib::KeyFile keyFile;
1778
1779 try {
1780 keyFile.load_from_file (fname);
1781 } catch (Glib::Error&) {}
1782
1783 keyFile.set_double ("LiveThumbData", "CamWBRed", camwbRed);
1784 keyFile.set_double ("LiveThumbData", "CamWBGreen", camwbGreen);
1785 keyFile.set_double ("LiveThumbData", "CamWBBlue", camwbBlue);
1786 keyFile.set_double ("LiveThumbData", "RedAWBMul", redAWBMul);
1787 keyFile.set_double ("LiveThumbData", "GreenAWBMul", greenAWBMul);
1788 keyFile.set_double ("LiveThumbData", "BlueAWBMul", blueAWBMul);
1789 keyFile.set_double ("LiveThumbData", "AEExposureCompensation", aeExposureCompensation);
1790 keyFile.set_integer ("LiveThumbData", "AELightness", aeLightness);
1791 keyFile.set_integer ("LiveThumbData", "AEContrast", aeContrast);
1792 keyFile.set_integer ("LiveThumbData", "AEBlack", aeBlack);
1793 keyFile.set_integer ("LiveThumbData", "AEHighlightCompression", aeHighlightCompression);
1794 keyFile.set_integer ("LiveThumbData", "AEHighlightCompressionThreshold", aeHighlightCompressionThreshold);
1795 keyFile.set_double ("LiveThumbData", "RedMultiplier", redMultiplier);
1796 keyFile.set_double ("LiveThumbData", "GreenMultiplier", greenMultiplier);
1797 keyFile.set_double ("LiveThumbData", "BlueMultiplier", blueMultiplier);
1798 keyFile.set_double ("LiveThumbData", "Scale", scale);
1799 keyFile.set_double ("LiveThumbData", "DefaultGain", defGain);
1800 keyFile.set_integer ("LiveThumbData", "ScaleForSave", scaleForSave);
1801 keyFile.set_boolean ("LiveThumbData", "GammaCorrected", gammaCorrected);
1802 Glib::ArrayHandle<double> cm ((double*)colorMatrix, 9, Glib::OWNERSHIP_NONE);
1803 keyFile.set_double_list("LiveThumbData", "ColorMatrix", cm);
1804 keyFile.set_double("LiveThumbData", "ScaleGain", scaleGain);
1805
1806 keyData = keyFile.to_data ();
1807
1808 } catch (Glib::Error& err) {
1809 if (options.rtSettings.verbose) {
1810 printf ("Thumbnail::writeData / Error code %d while reading values from \"%s\":\n%s\n", err.code(), fname.c_str(), err.what().c_str());
1811 }
1812 } catch (...) {
1813 if (options.rtSettings.verbose) {
1814 printf ("Thumbnail::writeData / Unknown exception while trying to save \"%s\"!\n", fname.c_str());
1815 }
1816 }
1817
1818 if (keyData.empty ()) {
1819 return false;
1820 }
1821
1822 FILE *f = g_fopen (fname.c_str (), "wt");
1823
1824 if (!f) {
1825 if (options.rtSettings.verbose) {
1826 printf ("Thumbnail::writeData / Error: unable to open file \"%s\" with write access!\n", fname.c_str());
1827 }
1828
1829 return false;
1830 } else {
1831 fprintf (f, "%s", keyData.c_str ());
1832 fclose (f);
1833 }
1834
1835 return true;
1836 }
1837
readEmbProfile(const Glib::ustring & fname)1838 bool Thumbnail::readEmbProfile (const Glib::ustring& fname)
1839 {
1840
1841 embProfileData = nullptr;
1842 embProfile = nullptr;
1843 embProfileLength = 0;
1844
1845 FILE* f = g_fopen (fname.c_str (), "rb");
1846
1847 if (f) {
1848 if (!fseek (f, 0, SEEK_END)) {
1849 int profileLength = ftell (f);
1850
1851 if (profileLength > 0) {
1852 embProfileLength = profileLength;
1853
1854 if (!fseek (f, 0, SEEK_SET)) {
1855 embProfileData = new unsigned char[embProfileLength];
1856 embProfileLength = fread (embProfileData, 1, embProfileLength, f);
1857 embProfile = cmsOpenProfileFromMem (embProfileData, embProfileLength);
1858 }
1859 }
1860 }
1861
1862 fclose (f);
1863 return embProfile != nullptr;
1864 }
1865
1866 return false;
1867 }
1868
writeEmbProfile(const Glib::ustring & fname)1869 bool Thumbnail::writeEmbProfile (const Glib::ustring& fname)
1870 {
1871
1872 if (embProfileData) {
1873 FILE* f = g_fopen (fname.c_str (), "wb");
1874
1875 if (f) {
1876 fwrite (embProfileData, 1, embProfileLength, f);
1877 fclose (f);
1878 return true;
1879 }
1880 }
1881
1882 return false;
1883 }
1884
1885
getImage8Data()1886 unsigned char* Thumbnail::getImage8Data()
1887 {
1888 if (thumbImg && thumbImg->getType() == rtengine::sImage8) {
1889 Image8* img8 = static_cast<Image8*> (thumbImg);
1890 return img8->data;
1891 }
1892
1893 return nullptr;
1894 }
1895
1896
loadInfoFromRaw(const Glib::ustring & fname,eSensorType & sensorType,int & w,int & h,int fixwh)1897 Thumbnail* Thumbnail::loadInfoFromRaw(const Glib::ustring &fname, eSensorType &sensorType, int &w, int &h, int fixwh)
1898 {
1899 RawImage *ri = new RawImage(fname);
1900 unsigned int tempImageNum = 0;
1901
1902 int r = ri->loadRaw(false, tempImageNum, false, nullptr, 1.0, false);
1903
1904 if (r) {
1905 delete ri;
1906 sensorType = ST_NONE;
1907 return nullptr;
1908 }
1909
1910 if (ri->getFrameCount() == 7) {
1911 // special case for Hasselblad H6D-100cMS pixelshift files
1912 // first frame is not bayer, load second frame
1913 int r = ri->loadRaw(false, 1, false, nullptr, 1.0, false);
1914
1915 if (r) {
1916 delete ri;
1917 sensorType = ST_NONE;
1918 return nullptr;
1919 }
1920 }
1921 sensorType = ri->getSensorType();
1922
1923 int width = ri->get_width();
1924 int height = ri->get_height();
1925 rtengine::Thumbnail *tpp = new rtengine::Thumbnail;
1926
1927 tpp->isRaw = true;
1928 tpp->sensorType = sensorType;
1929 tpp->embProfile = nullptr;
1930 tpp->embProfileData = nullptr;
1931 tpp->embProfileLength = 0;
1932
1933 tpp->redMultiplier = ri->get_pre_mul (0);
1934 tpp->greenMultiplier = ri->get_pre_mul (1);
1935 tpp->blueMultiplier = ri->get_pre_mul (2);
1936
1937 tpp->camwbRed = 1.0;
1938 tpp->camwbGreen = 1.0;
1939 tpp->camwbBlue = 1.0;
1940 tpp->defGain = 1.0;
1941
1942 float pre_mul[4], scale_mul[4], cblack[4];
1943 ri->get_colorsCoeff (pre_mul, scale_mul, cblack, false);
1944 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]);
1945 tpp->defGain *= std::pow(2, ri->getBaselineExposure());
1946
1947 int skip = 1;
1948 int firstgreen = 1;
1949
1950 if (ri->get_FujiWidth() != 0) {
1951 if (fixwh == 1) { // fix height, scale width
1952 skip = ((ri->get_height() - ri->get_FujiWidth()) / sqrt (0.5) - firstgreen - 1) / h;
1953 } else {
1954 skip = (ri->get_FujiWidth() / sqrt (0.5) - firstgreen - 1) / w;
1955 }
1956 } else {
1957 if (fixwh == 1) { // fix height, scale width
1958 skip = (ri->get_height() - firstgreen - 1) / h;
1959 } else {
1960 skip = (ri->get_width() - firstgreen - 1) / w;
1961 }
1962 }
1963
1964 if (skip % 2) {
1965 skip--;
1966 }
1967
1968 if (skip < 2) {
1969 skip = 2;
1970 }
1971
1972 int hskip = skip, vskip = skip;
1973
1974 if (!ri->get_model().compare ("D1X")) {
1975 hskip *= 2;
1976 }
1977
1978 int tmpw = (width - 2) / hskip;
1979 int tmph = (height - 2) / vskip;
1980
1981
1982 if (ri->get_FujiWidth() != 0) {
1983 int fw = ri->get_FujiWidth() / hskip;
1984 double step = sqrt (0.5);
1985 int wide = fw / step;
1986 int high = (tmph - fw) / step;
1987 tmpw = wide;
1988 tmph = high;
1989 }
1990
1991 const bool rotate_90 =
1992 true
1993 && (
1994 ri->get_rotateDegree() == 90
1995 || ri->get_rotateDegree() == 270
1996 );
1997
1998 if (rotate_90) {
1999 std::swap (tmpw, tmph);
2000 }
2001
2002 if (fixwh == 1) { // fix height, scale width
2003 w = tmpw * h / tmph;
2004 } else {
2005 h = tmph * w / tmpw;
2006 }
2007
2008
2009 if (ri->get_FujiWidth() != 0) {
2010 tpp->scale = (double) (height - ri->get_FujiWidth()) * 2.0 / (rotate_90 ? w : h);
2011 } else {
2012 tpp->scale = (double) height / (rotate_90 ? w : h);
2013 }
2014
2015 RawImageSource::computeFullSize(ri, TR_NONE, tpp->full_width, tpp->full_height);
2016
2017 delete ri;
2018 return tpp;
2019 }
2020
2021
2022 } // namespace rtengine
2023