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 (¶msForAutoExp, 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 (¶msForAutoExp, 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, ¶ms) ;//perhaps not good!
1237
1238 ImProcFunctions ipf (¶ms, 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, ¶ms, 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 (¶ms, 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 (¶ms, 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