1 #include "imagePipeline.h"
2 #include "../database/exifFunctions.h"
3 #include "../database/camconst.h"
4 #include <QDir>
5 #include <QStandardPaths>
6 
ImagePipeline(Cache cacheIn,Histo histoIn,QuickQuality qualityIn)7 ImagePipeline::ImagePipeline(Cache cacheIn, Histo histoIn, QuickQuality qualityIn)
8 {
9     cache = cacheIn;
10     histo = histoIn;
11     quality = qualityIn;
12     valid = Valid::none;
13 
14     completionTimes.resize(Valid::count);
15     completionTimes[Valid::none] = 0;
16     completionTimes[Valid::load] = 5;
17     completionTimes[Valid::demosaic] = 50;
18     completionTimes[Valid::prefilmulation] = 5;
19     completionTimes[Valid::filmulation] = 50;
20     completionTimes[Valid::blackwhite] = 10;
21     completionTimes[Valid::colorcurve] = 10;
22     //completionTimes[Valid::filmlikecurve] = 10;
23 
24 }
25 
26 //int ImagePipeline::libraw_callback(void *data, LibRaw_progress p, int iteration, int expected)
27 //but we only need data.
libraw_callback(void * data,LibRaw_progress,int,int)28 int ImagePipeline::libraw_callback(void *data, LibRaw_progress, int, int)
29 {
30     AbortStatus abort;
31 
32     //Recover the param_manager from the data
33     ParameterManager * pManager = static_cast<ParameterManager*>(data);
34     //See whether to abort or not.
35     abort = pManager->claimDemosaicAbort();
36     if (abort == AbortStatus::restart)
37     {
38         return 1;//cancel processing
39     }
40     else
41     {
42         return 0;
43     }
44 }
45 
processImage(ParameterManager * paramManager,Interface * interface_in,Exiv2::ExifData & exifOutput,const QString fileHash)46 matrix<unsigned short>& ImagePipeline::processImage(ParameterManager * paramManager,
47                                                     Interface * interface_in,
48                                                     Exiv2::ExifData &exifOutput,
49                                                     const QString fileHash)//defaults to empty string
50 {
51     //Say that we've started processing to prevent cache status from changing..
52     hasStartedProcessing = true;
53     //Record when the function was requested. This is so that the function will not give up
54     // until a given short time has elapsed.
55     gettimeofday(&timeRequested, nullptr);
56     histoInterface = interface_in;
57 
58     //check that file requested matches the file associated with the parameter manager
59     if (fileHash != "")
60     {
61         QString paramIndex = paramManager->getImageIndex();
62         paramIndex.truncate(32);
63         if (fileHash != paramIndex)
64         {
65             cout << "processImage shuffle mismatch:  Requested: " << fileHash.toStdString() << endl;
66             cout << "processImage shuffle mismatch:  Parameter: " << paramIndex.toStdString() << endl;
67             cout << "processImage shuffle mismatch:  fullSize: " << (quality == HighQuality) << endl;
68             valid = none;
69         }
70     }
71 
72     valid = paramManager->getValid();
73     if (NoCache == cache || true == cacheEmpty)
74     {
75         valid = none;//we need to start fresh if nothing is going to be cached.
76     }
77 
78     LoadParams loadParam;
79     DemosaicParams demosaicParam;
80     PrefilmParams prefilmParam;
81     //FilmParams filmParam;
82     BlackWhiteParams blackWhiteParam;
83     FilmlikeCurvesParams curvesParam;
84 
85     isCR3 = false;
86 
87     cout << "ImagePipeline::processImage valid: " << valid << endl;
88 
89     updateProgress(valid, 0.0f);
90     switch (valid)
91     {
92     case partload: [[fallthrough]];
93     case none://Load image into buffer
94     {
95         AbortStatus abort;
96         //See whether to abort or not, while grabbing the latest parameters.
97         std::tie(valid, abort, loadParam) = paramManager->claimLoadParams();
98         if (abort == AbortStatus::restart)
99         {
100             cout << "ImagePipeline::processImage: aborted at the start" << endl;
101             return emptyMatrix();
102         }
103 
104         isCR3 = QString::fromStdString(loadParam.fullFilename).endsWith(".cr3", Qt::CaseInsensitive);
105         const bool isDNG = QString::fromStdString(loadParam.fullFilename).endsWith(".dng", Qt::CaseInsensitive);
106         if (isCR3)
107         {
108             cout << "processImage this is a CR3!" << endl;
109         }
110 
111         if (!loadParam.tiffIn && !loadParam.jpegIn && !((HighQuality == quality) && stealData))
112         {
113             std::unique_ptr<LibRaw> libraw = unique_ptr<LibRaw>(new LibRaw());
114 
115             //Open the file.
116             int libraw_error;
117 #if (defined(_WIN32) || defined(__WIN32__))
118             const QString tempFilename = QString::fromStdString(loadParam.fullFilename);
119             std::wstring wstr = tempFilename.toStdWString();
120             libraw_error = libraw->open_file(wstr.c_str());
121 #else
122             const char *cstr = loadParam.fullFilename.c_str();
123             libraw_error = libraw->open_file(cstr);
124 #endif
125             if (libraw_error)
126             {
127                 cout << "processImage: Could not read input file!" << endl;
128                 cout << "libraw error text: " << libraw_strerror(libraw_error) << endl;
129                 return emptyMatrix();
130             }
131 
132              //Make abbreviations for brevity in accessing data.
133 #define RSIZE libraw->imgdata.sizes
134 #define PARAM libraw->imgdata.params
135 #define IMAGE libraw->imgdata.image
136 #define RAW   libraw->imgdata.rawdata.raw_image
137 #define RAW3  libraw->imgdata.rawdata.color3_image
138 #define RAW4  libraw->imgdata.rawdata.color4_image
139 #define RAWF  libraw->imgdata.rawdata.float_image
140 #define IDATA libraw->imgdata.idata
141 #define LENS  libraw->imgdata.lens
142 #define MAKER libraw->imgdata.lens.makernotes
143 #define OTHER libraw->imgdata.other
144 #define SIZES libraw->imgdata.sizes
145 
146             if (libraw->is_floating_point())
147             {
148                 cout << "processImage: libraw cannot open a floating point raw" << endl;
149                 //LibRaw cannot process floating point images unless compiled with the DNG SDK.
150                 return emptyMatrix();
151             }
152             //This makes IMAGE contains the sensel value and 3 blank values at every
153             //location.
154             libraw_error = libraw->unpack();
155             if (libraw_error)
156             {
157                 cout << "processImage: Could not read input file, or was canceled" << endl;
158                 cout << "libraw error text: " << libraw_strerror(libraw_error) << endl;
159                 return emptyMatrix();
160             }
161 
162             //get dimensions
163             raw_width  = RSIZE.width;
164             raw_height = RSIZE.height;
165             cout << "raw width:  " << raw_width << endl;
166             cout << "raw height: " << raw_height << endl;
167 
168             int topmargin = RSIZE.top_margin;
169             int leftmargin = RSIZE.left_margin;
170             int full_width = RSIZE.raw_width;
171             //int full_height = RSIZE.raw_height;
172 
173             //get color matrix
174             for (int i = 0; i < 3; i++)
175             {
176                 //cout << "camToRGB: ";
177                 for (int j = 0; j < 3; j++)
178                 {
179                     camToRGB[i][j] = libraw->imgdata.color.rgb_cam[i][j];
180                     //cout << camToRGB[i][j] << " ";
181                 }
182                 //cout << endl;
183             }
184             for (int i = 0; i < 3; i++)
185             {
186                 //cout << "camToRGB4: ";
187                 for (int j = 0; j < 4; j++)
188                 {
189                     camToRGB4[i][j] = libraw->imgdata.color.rgb_cam[i][j];
190                     if (i==j)
191                     {
192                         camToRGB4[i][j] = 1;
193                     } else {
194                         camToRGB4[i][j] = 0;
195                     }
196                     if (j==3)
197                     {
198                         camToRGB4[i][j] = camToRGB4[i][1];
199                     }
200                     //cout << camToRGB4[i][j] << " ";
201                 }
202                 //cout << endl;
203             }
204             rCamMul = libraw->imgdata.color.cam_mul[0];
205             gCamMul = libraw->imgdata.color.cam_mul[1];
206             bCamMul = libraw->imgdata.color.cam_mul[2];
207             float minMult = min(min(rCamMul, gCamMul), bCamMul);
208             rCamMul /= minMult;
209             gCamMul /= minMult;
210             bCamMul /= minMult;
211             rPreMul = libraw->imgdata.color.pre_mul[0];
212             gPreMul = libraw->imgdata.color.pre_mul[1];
213             bPreMul = libraw->imgdata.color.pre_mul[2];
214             minMult = min(min(rPreMul, gPreMul), bPreMul);
215             rPreMul /= minMult;
216             gPreMul /= minMult;
217             bPreMul /= minMult;
218 
219             //get black subtraction values
220             //for everything
221             float blackpoint = libraw->imgdata.color.black;
222             //some cameras have individual color channel subtraction. This hasn't been implemented yet.
223             float rBlack = libraw->imgdata.color.cblack[0];
224             float gBlack = libraw->imgdata.color.cblack[1];
225             float bBlack = libraw->imgdata.color.cblack[2];
226             float g2Black = libraw->imgdata.color.cblack[3];
227             //Still others have a matrix to subtract.
228             int blackRow = int(libraw->imgdata.color.cblack[4]);
229             int blackCol = int(libraw->imgdata.color.cblack[5]);
230 
231             //cout << "BLACKPOINT" << endl;
232             //cout << blackpoint << endl;
233             //cout << "color channel blackpoints" << endl;
234             //cout << rBlack << endl;
235             //cout << gBlack << endl;
236             //cout << bBlack << endl;
237             //cout << g2Black << endl;
238             //cout << "block-based blackpoint dimensions:" << endl;
239             //cout << libraw->imgdata.color.cblack[4] << endl;
240             //cout << libraw->imgdata.color.cblack[5] << endl;
241             //cout << "block-based blackpoint: " << endl;
242             uint maxBlockBlackpoint = 0;
243             if (blackRow > 0 && blackCol > 0)
244             {
245                 for (int i = 0; i < blackRow; i++)
246                 {
247                     for (int j = 0; j < blackCol; j++)
248                     {
249                         maxBlockBlackpoint = max(maxBlockBlackpoint, libraw->imgdata.color.cblack[6 + i*blackCol + j]);
250                         //cout << libraw->imgdata.color.cblack[6 + i*blackCol + j] << "  ";
251                     }
252                     //cout << endl;
253                 }
254             }
255             //cout << "Max of block-based blackpoint: " << maxBlockBlackpoint << endl;
256 
257             //get white saturation values
258             cout << "WHITE SATURATION ===================================" << endl;
259             cout << "data_maximum: " << libraw->imgdata.color.data_maximum << endl;
260             cout << "maximum: " << libraw->imgdata.color.maximum << endl;
261 
262             //Calculate the white point based on the camera settings.
263             //This needs the black point subtracted, and a fudge factor to ensure clipping is hard and fast.
264             double whiteClippingPoint;
265             QString makeModel = IDATA.make;
266             makeModel.append(" ");
267             makeModel.append(IDATA.model);
268             camconst_status camconstStatus = camconst_read(makeModel, OTHER.iso_speed, OTHER.aperture, whiteClippingPoint);
269 
270             //Modern Nikons have camconst.json white levels specified as if they were 14-bit
271             // even if the raw files are 12-bit-only, like the entry level cams
272             //So we need to detect if it's 12-bit and if the camconst specifies as 14-bit.
273             if ((QString(IDATA.make) == "Nikon") && (libraw->imgdata.color.maximum < 4096) && (whiteClippingPoint >= 4096))
274             {
275                 whiteClippingPoint = whiteClippingPoint*4095/16383;
276                 cout << "Nikon 12-bit camconst white clipping point: " << whiteClippingPoint << endl;
277             }
278             cout << "is the file dng?: " << isDNG << endl;
279 
280             if (camconstStatus == CAMCONST_READ_OK && !isDNG) //dngs provide their own correct whitepoint
281             {
282                 maxValue = whiteClippingPoint - blackpoint - maxBlockBlackpoint;
283             } else {
284                 maxValue = libraw->imgdata.color.maximum - blackpoint - maxBlockBlackpoint;
285             }
286             cout << "black-subtracted maximum: " << maxValue << endl;
287             cout << "fmaximum: " << libraw->imgdata.color.fmaximum << endl;
288             cout << "fnorm: " << libraw->imgdata.color.fnorm << endl;
289 
290             //get color filter array
291             //if all the libraw.imgdata.idata.xtrans values are 0, it's bayer.
292             //bayer only for now
293             for (unsigned int i=0; i<2; i++)
294             {
295                 //cout << "bayer: ";
296                 for (unsigned int j=0; j<2; j++)
297                 {
298                     cfa[i][j] = unsigned(libraw->COLOR(int(i), int(j)));
299                     if (cfa[i][j] == 3) //Auto CA correct doesn't like 0123 for RGBG; we change it to 0121.
300                     {
301                         cfa[i][j] = 1;
302                     }
303                     //cout << cfa[i][j];
304                 }
305                 //cout << endl;
306             }
307 
308             //get xtrans color filter array
309             maxXtrans = 0;
310             for (int i=0; i<6; i++)
311             {
312                 //cout << "xtrans: ";
313                 for (int j=0; j<6; j++)
314                 {
315                     xtrans[i][j] = uint(libraw->imgdata.idata.xtrans[i][j]);
316                     maxXtrans = max(maxXtrans,int(libraw->imgdata.idata.xtrans[i][j]));
317                     //cout << xtrans[i][j];
318                 }
319                 //cout << endl;
320             }
321 
322             if (!isCR3)//we can't use exiv2 on CR3 yet
323             {
324                 cout << "processImage exiv filename: " << loadParam.fullFilename << endl;
325                 auto image = Exiv2::ImageFactory::open(loadParam.fullFilename);
326                 assert(image.get() != 0);
327                 image->readMetadata();
328                 exifData = image->exifData();
329             } else {
330                 //We need to fabricate fresh exif data from what libraw gives us
331                 Exiv2::ExifData basicExifData;
332 
333                 basicExifData["Exif.Image.Orientation"] = uint16_t(1);
334                 basicExifData["Exif.Image.ImageWidth"] = vibrance_saturation_image.nc()/3;
335                 basicExifData["Exif.Image.ImageLength"] = vibrance_saturation_image.nr();
336                 basicExifData["Exif.Image.Make"] = IDATA.make;
337                 basicExifData["Exif.Image.Model"] = IDATA.model;
338                 basicExifData["Exif.Image.DateTime"] = exifDateTimeString(OTHER.timestamp);
339                 basicExifData["Exif.Photo.DateTimeOriginal"] = exifDateTimeString(OTHER.timestamp);
340                 basicExifData["Exif.Photo.DateTimeDigitized"] = exifDateTimeString(OTHER.timestamp);
341                 basicExifData["Exif.Photo.ExposureTime"] = rationalTv(OTHER.shutter);
342                 basicExifData["Exif.Photo.FNumber"] = rationalAvFL(OTHER.aperture);
343                 basicExifData["Exif.Photo.ISOSpeed"] = int(round(OTHER.iso_speed));
344                 basicExifData["Exif.Photo.FocalLength"] = rationalAvFL(OTHER.focal_len);
345 
346                 exifData = basicExifData;
347             }
348 
349             raw_image.set_size(raw_height, raw_width);
350 
351             //copy raw data
352             float rawMin = std::numeric_limits<float>::max();
353             float rawMax = std::numeric_limits<float>::min();
354 
355             isSraw = libraw->is_sraw();
356 
357             //Iridient X-Transformer creates full-color files that aren't sraw
358             //They have 6666 as the cfa and all 0 for xtrans
359             //However, Leica M Monochrom files are exactly the same!
360             //So we have to check if the white balance tag exists.
361             bool isWeird = (cfa[0][0]==6 && cfa[0][1]==6 && cfa[1][0]==6 && cfa[1][1]==6);
362             //cout << "is weird: " << isWeird << endl;
363             bool noWB = false;
364             if (!isCR3)//we can't use exiv2 on CR3 yet and no CR3 cameras are monochrome
365             {
366                 noWB = exifData["Exif.Photo.WhiteBalance"].toString().length()==0;
367             }
368             //cout << "white balance: " << wb << endl;
369             isMonochrome = isWeird && noWB;
370             //cout << "is monochrome: " << isMonochrome << endl;
371             isSraw = isSraw || (isWeird && !isMonochrome);
372             //cout << "is full color raw: " << isSraw << endl;
373 
374 
375             isNikonSraw = libraw->is_nikon_sraw();
376             if (isSraw)
377             {
378                 raw_image.set_size(raw_height, raw_width*3);
379                 #pragma omp parallel for reduction (min:rawMin) reduction(max:rawMax)
380                 for (int row = 0; row < raw_height; row++)
381                 {
382                     //IMAGE is an (width*height) by 4 array, not width by height by 4.
383                     int rowoffset = (row + topmargin)*full_width;
384                     for (int col = 0; col < raw_width; col++)
385                     {
386                         float tempBlackpoint = blackpoint;
387                         if (blackRow > 0 && blackCol > 0)
388                         {
389                             tempBlackpoint = tempBlackpoint + libraw->imgdata.color.cblack[6 + (row%blackRow)*blackCol + col%blackCol];
390                         }
391                         //sraw comes from raw4 but only uses 3 channels
392                         raw_image[row][col*3   ] = RAW4[rowoffset + col + leftmargin][0] - tempBlackpoint;
393                         rawMin = std::min(rawMin, raw_image[row][col*3   ]);
394                         rawMax = std::max(rawMax, raw_image[row][col*3   ]);
395                         raw_image[row][col*3 +1] = RAW4[rowoffset + col + leftmargin][1] - tempBlackpoint;
396                         rawMin = std::min(rawMin, raw_image[row][col*3 +1]);
397                         rawMax = std::max(rawMax, raw_image[row][col*3 +1]);
398                         raw_image[row][col*3 +2] = RAW4[rowoffset + col + leftmargin][2] - tempBlackpoint;
399                         rawMin = std::min(rawMin, raw_image[row][col*3 +2]);
400                         rawMax = std::max(rawMax, raw_image[row][col*3 +2]);
401                     }
402                 }
403             } else if (libraw->is_floating_point()){//we can't even get here until libraw supports floating point raw
404                 #pragma omp parallel for reduction (min:rawMin) reduction(max:rawMax)
405                 for (int row = 0; row < raw_height; row++)
406                 {
407                     //IMAGE is an (width*height) by 4 array, not width by height by 4.
408                     int rowoffset = (row + topmargin)*full_width;
409                     for (int col = 0; col < raw_width; col++)
410                     {
411                         float tempBlackpoint = blackpoint;
412                         if (blackRow > 0 && blackCol > 0)
413                         {
414                             tempBlackpoint = tempBlackpoint + libraw->imgdata.color.cblack[6 + (row%blackRow)*blackCol + col%blackCol];
415                         }
416                         raw_image[row][col] = RAWF[rowoffset + col + leftmargin] - tempBlackpoint;
417                         rawMin = std::min(rawMin, raw_image[row][col]);
418                         rawMax = std::max(rawMax, raw_image[row][col]);
419                     }
420                 }
421             } else {
422                 #pragma omp parallel for reduction (min:rawMin) reduction(max:rawMax)
423                 for (int row = 0; row < raw_height; row++)
424                 {
425                     //IMAGE is an (width*height) by 4 array, not width by height by 4.
426                     int rowoffset = (row + topmargin)*full_width;
427                     for (int col = 0; col < raw_width; col++)
428                     {
429                         float tempBlackpoint = blackpoint;
430                         if (blackRow > 0 && blackCol > 0)
431                         {
432                             tempBlackpoint = tempBlackpoint + libraw->imgdata.color.cblack[6 + (row%blackRow)*blackCol + col%blackCol];
433                         }
434                         raw_image[row][col] = RAW[rowoffset + col + leftmargin] - tempBlackpoint;
435                         rawMin = std::min(rawMin, raw_image[row][col]);
436                         rawMax = std::max(rawMax, raw_image[row][col]);
437                     }
438                 }
439             }
440 
441             //generate raw histogram
442             if (WithHisto == histo)
443             {
444                 histoInterface->updateHistRaw(raw_image, maxValue, cfa, xtrans, maxXtrans, isSraw, isMonochrome);
445             }
446 
447             cout << "max of raw_image: " << rawMax << endl;
448             cout << "min of raw_image: " << rawMin << endl;
449         }
450         valid = paramManager->markLoadComplete();
451         updateProgress(valid, 0.0f);
452         [[fallthrough]];
453     }
454     case partdemosaic: [[fallthrough]];
455     case load://Do demosaic, or load non-raw images
456     {
457         AbortStatus abort;
458         std::tie(valid, abort, loadParam, demosaicParam) = paramManager->claimDemosaicParams();
459         if (abort == AbortStatus::restart)
460         {
461             cout << "imagePipeline.cpp: aborted at demosaic" << endl;
462             return emptyMatrix();
463         }
464 
465         cout << "imagePipeline.cpp: Opening " << loadParam.fullFilename << endl;
466 
467         //Reads in the photo.
468         cout << "load start:" << timeDiff (timeRequested) << endl;
469         struct timeval imload_time;
470         gettimeofday( &imload_time, nullptr );
471 
472         matrix<float>& scaled_image = recovered_image;
473         if ((HighQuality == quality) && stealData)//only full pipelines may steal data
474         {
475             scaled_image = stealVictim->input_image;
476             exifData = stealVictim->exifData;
477             rCamMul = stealVictim->rCamMul;
478             gCamMul = stealVictim->gCamMul;
479             bCamMul = stealVictim->bCamMul;
480             rPreMul = stealVictim->rPreMul;
481             gPreMul = stealVictim->gPreMul;
482             bPreMul = stealVictim->bPreMul;
483             maxValue = stealVictim->maxValue;
484             isSraw = stealVictim->isSraw;
485             isNikonSraw = stealVictim->isNikonSraw;
486             isMonochrome = stealVictim->isMonochrome;
487             isCR3 = stealVictim->isCR3;
488             raw_width = stealVictim->raw_width;
489             raw_height = stealVictim->raw_height;
490             //copy color matrix
491             //get color matrix
492             for (int i = 0; i < 3; i++)
493             {
494                 for (int j = 0; j < 3; j++)
495                 {
496                     camToRGB[i][j] = stealVictim->camToRGB[i][j];
497                 }
498             }
499             for (int i = 0; i < 3; i++)
500             {
501                 for (int j = 0; j < 4; j++)
502                 {
503                     camToRGB4[i][j] = stealVictim->camToRGB4[i][j];
504                 }
505             }
506         }
507         else if (loadParam.tiffIn)
508         {
509             if (imread_tiff(loadParam.fullFilename, input_image, exifData))
510             {
511                 cerr << "Could not open image " << loadParam.fullFilename << "; Exiting..." << endl;
512                 return emptyMatrix();
513             }
514         }
515         else if (loadParam.jpegIn)
516         {
517             if (imread_jpeg(loadParam.fullFilename, input_image, exifData))
518             {
519                 cerr << "Could not open image " << loadParam.fullFilename << "; Exiting..." << endl;
520                 return emptyMatrix();
521             }
522         }
523         else if (isSraw)//already demosaiced
524         {
525             //We just need to scale to 65535, and apply camera WB
526             float inputscale = maxValue;
527             float outputscale = 65535.0;
528             float scaleFactor = outputscale / inputscale;
529             input_image.set_size(raw_height, raw_width*3);
530             if (isNikonSraw)
531             {
532                 #pragma omp parallel for
533                 for (int row = 0; row < raw_height; row++)
534                 {
535                     for (int col = 0; col < raw_width*3; col++)
536                     {
537                         int color = col % 3;
538                         input_image(row, col) = raw_image(row, col) * scaleFactor;
539 
540                     }
541                 }
542             }
543             else
544             {
545                 #pragma omp parallel for
546                 for (int row = 0; row < raw_height; row++)
547                 {
548                     for (int col = 0; col < raw_width*3; col++)
549                     {
550                         int color = col % 3;
551                         input_image(row, col) = raw_image(row, col) * scaleFactor * ((color==0) ? rCamMul : (color == 1) ? gCamMul : bCamMul);
552 
553                     }
554                 }
555             }
556         }
557         else //raw
558         {
559             matrix<float>   red(raw_height, raw_width);
560             matrix<float> green(raw_height, raw_width);
561             matrix<float>  blue(raw_height, raw_width);
562 
563             double initialGain = 1.0;
564             float inputscale = maxValue;
565             float outputscale = 65535.0;
566             const int border = 4;//used for amaze
567             std::function<bool(double)> setProg = [](double) -> bool {return false;};
568 
569             cout << "raw width:  " << raw_width << endl;
570             cout << "raw height: " << raw_height << endl;
571 
572             //before demosaic, you want to apply raw white balance
573             //======================================================================
574             //TODO: If the camera white balance disagrees with some sort of AWB by a *lot*, use an awb instead
575             //======================================================================
576             matrix<float> premultiplied(raw_height, raw_width);
577 
578             cout << "demosaic start" << timeDiff(timeRequested) << endl;
579             struct timeval demosaic_time;
580             gettimeofday(&demosaic_time, nullptr);
581 
582             if (maxXtrans > 0)
583             {
584                 #pragma omp parallel for
585                 for (int row = 0; row < raw_height; row++)
586                 {
587                     for (int col = 0; col < raw_width; col++)
588                     {
589                         uint color = xtrans[uint(row) % 6][uint(col) % 6];
590                         premultiplied(row, col) = raw_image(row, col) * ((color==0) ? rCamMul : (color == 1) ? gCamMul : bCamMul);
591                     }
592                 }
593                 markesteijn_demosaic(raw_width, raw_height, premultiplied, red, green, blue, xtrans, camToRGB4, setProg, 3, true);
594                 //there's no inputscale for markesteijn so we need to scale
595                 float scaleFactor = outputscale / inputscale;
596                 #pragma omp parallel for
597                 for (int row = 0; row < red.nr(); row++)
598                 {
599                     for (int col = 0; col < red.nc(); col++)
600                     {
601                         red(row, col)   = red(row, col)   * scaleFactor;
602                         green(row, col) = green(row, col) * scaleFactor;
603                         blue(row, col)  = blue(row, col)  * scaleFactor;
604                     }
605                 }
606             }
607             else if (isMonochrome)
608             {
609                 float scaleFactor = outputscale / inputscale;
610                 for (int row = 0; row < raw_height; row++)
611                 {
612                     for (int col = 0; col < raw_width; col++)
613                     {
614                         red(row, col)   = raw_image(row, col) * scaleFactor;
615                         green(row, col) = raw_image(row, col) * scaleFactor;
616                         blue(row, col)  = raw_image(row, col) * scaleFactor;
617                     }
618                 }
619             }
620             else
621             {
622                 #pragma omp parallel for
623                 for (int row = 0; row < raw_height; row++)
624                 {
625                     for (int col = 0; col < raw_width; col++)
626                     {
627                         uint color = cfa[uint(row) & 1][uint(col) & 1];
628                         premultiplied(row, col) = raw_image(row, col) * ((color==0) ? rCamMul : (color == 1) ? gCamMul : bCamMul);
629                     }
630                 }
631                 if (demosaicParam.caEnabled > 0)
632                 {
633                     //we need to apply white balance and then remove it for Auto CA Correct to work properly
634                     double fitparams[2][2][16];
635                     CA_correct(0, 0, raw_width, raw_height, true, demosaicParam.caEnabled, 0.0, 0.0, true, premultiplied, premultiplied, cfa, setProg, fitparams, false);
636                 }
637                 amaze_demosaic(raw_width, raw_height, 0, 0, raw_width, raw_height, premultiplied, red, green, blue, cfa, setProg, initialGain, border, inputscale, outputscale);
638                 //matrix<float> normalized_image(raw_height, raw_width);
639                 //normalized_image = premultiplied * (outputscale/inputscale);
640                 //lmmse_demosaic(raw_width, raw_height, normalized_image, red, green, blue, cfa, setProg, 3);//needs inputscale and output scale to be implemented
641             }
642             premultiplied.set_size(0, 0);
643             cout << "demosaic end: " << timeDiff(demosaic_time) << endl;
644 
645             input_image.set_size(raw_height, raw_width*3);
646             #pragma omp parallel for
647             for (int row = 0; row < raw_height; row++)
648             {
649                 for (int col = 0; col < raw_width; col++)
650                 {
651                     input_image(row, col*3    ) =   red(row, col);
652                     input_image(row, col*3 + 1) = green(row, col);
653                     input_image(row, col*3 + 2) =  blue(row, col);
654                 }
655             }
656         }
657         cout << "load time: " << timeDiff(imload_time) << endl;
658 
659         cout << "ImagePipeline::processImage: Demosaic complete." << endl;
660 
661 
662         if (LowQuality == quality)
663         {
664             cout << "scale start:" << timeDiff (timeRequested) << endl;
665             struct timeval downscale_time;
666             gettimeofday( &downscale_time, nullptr );
667             downscale_and_crop(input_image,scaled_image, 0, 0, (input_image.nc()/3)-1,input_image.nr()-1, 600, 600);
668             cout << "scale end: " << timeDiff( downscale_time ) << endl;
669         }
670         else if (PreviewQuality == quality)
671         {
672             cout << "scale start:" << timeDiff (timeRequested) << endl;
673             struct timeval downscale_time;
674             gettimeofday( &downscale_time, nullptr );
675             downscale_and_crop(input_image,scaled_image, 0, 0, (input_image.nc()/3)-1,input_image.nr()-1, resolution, resolution);
676             cout << "scale end: " << timeDiff( downscale_time ) << endl;
677         }
678         else
679         {
680             if (!stealData) //If we had to compute the input image ourselves
681             {
682                 scaled_image = input_image;
683                 input_image.set_size(0,0);
684             }
685         }
686 
687         //Recover highlights now
688         cout << "hlrecovery start:" << timeDiff (timeRequested) << endl;
689         struct timeval hlrecovery_time;
690         gettimeofday(&hlrecovery_time, nullptr);
691 
692         int height = scaled_image.nr();
693         int width  = scaled_image.nc()/3;
694 
695         //Now, recover highlights.
696         std::function<bool(double)> setProg = [](double) -> bool {return false;};
697         //And return it back to a single layer
698         if (demosaicParam.highlights >= 2)
699         {
700             recovered_image.set_size(height, width*3);
701             //For highlight recovery, we need to split up the image into three separate layers.
702             matrix<float> rChannel(height, width), gChannel(height, width), bChannel(height, width);
703 
704             #pragma omp parallel for
705             for (int row = 0; row < height; row++)
706             {
707                 for (int col = 0; col < width; col++)
708                 {
709                     rChannel(row, col) = scaled_image(row, col*3    );
710                     gChannel(row, col) = scaled_image(row, col*3 + 1);
711                     bChannel(row, col) = scaled_image(row, col*3 + 2);
712                 }
713             }
714 
715             //We applied the camMul camera multipliers before applying white balance.
716             //Now we need to calculate the channel max and the raw clip levels.
717             //Channel max:
718             const float chmax[3] = {rChannel.max(), gChannel.max(), bChannel.max()};
719             //Max clip point:
720             const float clmax[3] = {65535.0f*rCamMul, 65535.0f*gCamMul, 65535.0f*bCamMul};
721 
722             HLRecovery_inpaint(width, height, rChannel, gChannel, bChannel, chmax, clmax, setProg);
723             #pragma omp parallel for
724             for (int row = 0; row < height; row++)
725             {
726                 for (int col = 0; col < width; col++)
727                 {
728                     recovered_image(row, col*3    ) = rChannel(row, col);
729                     recovered_image(row, col*3 + 1) = gChannel(row, col);
730                     recovered_image(row, col*3 + 2) = bChannel(row, col);
731                 }
732             }
733         } else if (demosaicParam.highlights == 0)
734         {
735             recovered_image.set_size(height, width*3);
736             #pragma omp parallel for
737             for (int row = 0; row < height; row++)
738             {
739                 for (int col = 0; col < width; col++)
740                 {
741                     recovered_image(row, col*3    ) = min(scaled_image(row, col*3    ), 65535.0f);
742                     recovered_image(row, col*3 + 1) = min(scaled_image(row, col*3 + 1), 65535.0f);
743                     recovered_image(row, col*3 + 2) = min(scaled_image(row, col*3 + 2), 65535.0f);
744                 }
745             }
746         } else {
747             recovered_image = std::move(scaled_image);
748         }
749 
750         //Lensfun processing
751         cout << "lensfun start" << endl;
752         lfDatabase *ldb = lf_db_create();
753         QDir dir = QDir::home();
754         QString dirstr = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
755         dirstr.append("/filmulator/version_2");
756         std::string stdstring = dirstr.toStdString();
757         ldb->Load(stdstring.c_str());
758 
759         std::string camName = demosaicParam.cameraName.toStdString();
760         const lfCamera * camera = NULL;
761         const lfCamera ** cameraList = ldb->FindCamerasExt(NULL,camName.c_str());
762 
763         //Set up stuff for rotation.
764         //We expect rotation to be from -45 to +45
765         //But -50 will be the signal from the UI to disable it.
766         float rotationAngle = demosaicParam.rotationAngle * 3.1415926535/180;//convert degrees to radians
767         if (demosaicParam.rotationAngle <= -49) {
768             rotationAngle = 0;
769         }
770         cout << "cos rotationangle: " << cos(rotationAngle) << endl;
771         cout << "sin rotationangle: " << sin(rotationAngle) << endl;
772         bool lensfunGeometryCorrectionApplied = false;
773 
774         if (cameraList)
775         {
776             const float cropFactor = cameraList[0]->CropFactor;
777 
778             QString tempLensName = demosaicParam.lensName;
779             if (tempLensName.length() > 0)
780             {
781                 if (tempLensName.front() == "\\")
782                 {
783                     //if the lens name starts with a backslash, don't filter by camera
784                     tempLensName.remove(0,1);
785                 } else {
786                     //if it doesn't start with a backslash, filter by camera
787                     camera = cameraList[0];
788                 }
789             }
790             std::string lensName = tempLensName.toStdString();
791             const lfLens * lens = NULL;
792             const lfLens ** lensList = NULL;
793             lensList = ldb->FindLenses(camera, NULL, lensName.c_str());
794             if (lensList)
795             {
796                 lens = lensList[0];
797 
798                 //Now we set up the modifier itself with the lens and processing flags
799 #ifdef LF_GIT
800                 lfModifier * mod = new lfModifier(lens, demosaicParam.focalLength, cropFactor, width, height, LF_PF_F32);
801 #else //lensfun v0.3.95
802                 lfModifier * mod = new lfModifier(cropFactor, width, height, LF_PF_F32);
803 #endif
804 
805                 int modflags = 0;
806                 if (demosaicParam.lensfunCA && !isMonochrome)
807                 {
808 #ifdef LF_GIT
809                     modflags |= mod->EnableTCACorrection();
810 #else //lensfun v0.3.95
811                     modflags |= mod->EnableTCACorrection(lens, demosaicParam.focalLength);
812 #endif
813                 }
814                 if (demosaicParam.lensfunVignetting)
815                 {
816 #ifdef LF_GIT
817                     modflags |= mod->EnableVignettingCorrection(demosaicParam.fnumber, 1000.0f);
818 #else //lensfun v0.3.95
819                     modflags |= mod->EnableVignettingCorrection(lens, demosaicParam.focalLength, demosaicParam.fnumber, 1000.0f);
820 #endif
821                 }
822                 if (demosaicParam.lensfunDistortion)
823                 {
824 #ifdef LF_GIT
825                     modflags |= mod->EnableDistortionCorrection();
826 #else //lensfun v0.3.95
827                     modflags |= mod->EnableDistortionCorrection(lens, demosaicParam.focalLength);
828 #endif
829                     modflags |= mod->EnableScaling(mod->GetAutoScale(false));
830                     cout << "Auto scale factor: " << mod->GetAutoScale(false) << endl;
831                 }
832 
833                 //Now we actually perform the required processing.
834                 //First is vignetting.
835                 if (demosaicParam.lensfunVignetting)
836                 {
837                     bool success = true;
838                     #pragma omp parallel for
839                     for (int row = 0; row < height; row++)
840                     {
841                         success = mod->ApplyColorModification(recovered_image[row], 0.0f, row, width, 1, LF_CR_3(RED, GREEN, BLUE), width);
842                     }
843                 }
844 
845                 //Next is CA, or distortion, or both.
846                 matrix<float> new_image;
847                 new_image.set_size(height, width*3);
848 
849                 if (demosaicParam.lensfunCA || demosaicParam.lensfunDistortion)
850                 {
851                     //ApplySubpixelGeometryDistortion
852                     lensfunGeometryCorrectionApplied = true;
853                     bool success = true;
854                     int listWidth = width * 2 * 3;
855 
856                     //Check how far out of bounds we go
857                     float maxOvershootDistance = 1.0f;
858                     float semiwidth = (width-1)/2.0f;
859                     float semiheight = (height-1)/2.0f;
860                     #pragma omp parallel for reduction(max:maxOvershootDistance)
861                     for (int row = 0; row < height; row++)
862                     {
863                         float positionList[listWidth];
864                         success = mod->ApplySubpixelGeometryDistortion(0.0f, row, width, 1, positionList);
865                         if (success)
866                         {
867                             for (int col = 0; col < width; col++)
868                             {
869                                 int listIndex = col * 2 * 3; //list index
870                                 for (int c = 0; c < 3; c++)
871                                 {
872                                     float coordX = positionList[listIndex+2*c] - semiwidth;
873                                     float coordY = positionList[listIndex+2*c+1] - semiheight;
874                                     float rotatedX = coordX * cos(rotationAngle) - coordY * sin(rotationAngle);
875                                     float rotatedY = coordX * sin(rotationAngle) + coordY * cos(rotationAngle);
876 
877                                     float overshoot = 1.0f;
878 
879                                     if (abs(rotatedX) > semiwidth)
880                                     {
881                                         overshoot = max(abs(rotatedX)/semiwidth,overshoot);
882                                     }
883                                     if (abs(rotatedY) > semiheight)
884                                     {
885                                         overshoot = max(abs(rotatedY)/semiheight,overshoot);
886                                     }
887 
888                                     if (overshoot > maxOvershootDistance)
889                                     {
890                                         maxOvershootDistance = overshoot;
891                                     }
892                                 }
893                             }
894                         }
895                     }
896 
897                     #pragma omp parallel for
898                     for (int row = 0; row < height; row++)
899                     {
900                         float positionList[listWidth];
901                         success = mod->ApplySubpixelGeometryDistortion(0.0f, row, width, 1, positionList);
902                         if (success)
903                         {
904                             for (int col = 0; col < width; col++)
905                             {
906                                 int listIndex = col * 2 * 3; //list index
907                                 for (int c = 0; c < 3; c++)
908                                 {
909                                     float coordX = positionList[listIndex+2*c] - semiwidth;
910                                     float coordY = positionList[listIndex+2*c+1] - semiheight;
911                                     float rotatedX = (coordX * cos(rotationAngle) - coordY * sin(rotationAngle)) / maxOvershootDistance + semiwidth;
912                                     float rotatedY = (coordX * sin(rotationAngle) + coordY * cos(rotationAngle)) / maxOvershootDistance + semiheight;
913                                     int sX = max(0, min(width-1,  int(floor(rotatedX))))*3 + c;//startX
914                                     int eX = max(0, min(width-1,  int(ceil(rotatedX))))*3 + c; //endX
915                                     int sY = max(0, min(height-1, int(floor(rotatedY))));      //startY
916                                     int eY = max(0, min(height-1, int(ceil(rotatedY))));       //endY
917                                     float notUsed;
918                                     float eWX = modf(rotatedX, &notUsed); //end weight X
919                                     float eWY = modf(rotatedY, &notUsed); //end weight Y;
920                                     float sWX = 1 - eWX;                //start weight X
921                                     float sWY = 1 - eWY;                //start weight Y;
922                                     new_image(row, col*3 + c) = recovered_image(sY, sX) * sWY * sWX +
923                                                                 recovered_image(eY, sX) * eWY * sWX +
924                                                                 recovered_image(sY, eX) * sWY * eWX +
925                                                                 recovered_image(eY, eX) * eWY * eWX;
926                                 }
927                             }
928                         }
929                     }
930                     recovered_image = std::move(new_image);
931                 }
932 
933                 if (mod != NULL)
934                 {
935                     delete mod;
936                 }
937             }
938             lf_free(lensList);
939         }
940         lf_free(cameraList);
941 
942         //cleanup lensfun
943         if (ldb != NULL)
944         {
945             lf_db_destroy(ldb);
946         }
947 
948         if (!lensfunGeometryCorrectionApplied)
949         {
950             //also do rotations on non-corrected images
951             float maxOvershootDistance = 1.0f;
952             float semiwidth = (width-1)/2.0f;
953             float semiheight = (height-1)/2.0f;
954 
955             //check the four corners
956             for (int row = 0; row < height; row += height-1)
957             {
958                 for (int col = 0; col < width; col += width-1)
959                 {
960                     float coordX = col - semiwidth;
961                     float coordY = row - semiheight;
962                     float rotatedX = coordX * cos(rotationAngle) - coordY * sin(rotationAngle);
963                     float rotatedY = coordX * sin(rotationAngle) + coordY * cos(rotationAngle);
964 
965                     float overshoot = 1.0f;
966 
967                     if (abs(rotatedX) > semiwidth)
968                     {
969                         overshoot = max(abs(rotatedX)/semiwidth,overshoot);
970                     }
971                     if (abs(rotatedY) > semiheight)
972                     {
973                         overshoot = max(abs(rotatedY)/semiheight,overshoot);
974                     }
975 
976                     if (overshoot > maxOvershootDistance)
977                     {
978                         maxOvershootDistance = overshoot;
979                     }
980                 }
981             }
982 
983             //Apply the rotation
984             matrix<float> new_image;
985             new_image.set_size(height, width*3);
986 
987             for (int row = 0; row < height; row++)
988             {
989                 for (int col = 0; col < width; col++)
990                 {
991                     float coordX = col - semiwidth;
992                     float coordY = row - semiheight;
993                     float rotatedX = (coordX * cos(rotationAngle) - coordY * sin(rotationAngle)) / maxOvershootDistance + semiwidth;
994                     float rotatedY = (coordX * sin(rotationAngle) + coordY * cos(rotationAngle)) / maxOvershootDistance + semiheight;
995                     int sX = max(0, min(width-1,  int(floor(rotatedX))))*3;//startX
996                     int eX = max(0, min(width-1,  int(ceil(rotatedX))))*3; //endX
997                     int sY = max(0, min(height-1, int(floor(rotatedY))));  //startY
998                     int eY = max(0, min(height-1, int(ceil(rotatedY))));   //endY
999                     float notUsed;
1000                     float eWX = modf(rotatedX, &notUsed); //end weight X
1001                     float eWY = modf(rotatedY, &notUsed); //end weight Y;
1002                     float sWX = 1 - eWX;                //start weight X
1003                     float sWY = 1 - eWY;                //start weight Y;
1004                     for (int c = 0; c < 3; c++)
1005                     {
1006                         new_image(row, col*3 + c) = recovered_image(sY, sX + c) * sWY * sWX +
1007                                                     recovered_image(eY, sX + c) * eWY * sWX +
1008                                                     recovered_image(sY, eX + c) * sWY * eWX +
1009                                                     recovered_image(eY, eX + c) * eWY * eWX;
1010                     }
1011                 }
1012             }
1013             recovered_image = std::move(new_image);
1014         }
1015 
1016         valid = paramManager->markDemosaicComplete();
1017         updateProgress(valid, 0.0f);
1018         [[fallthrough]];
1019     }
1020     case partprefilmulation: [[fallthrough]];
1021     case demosaic://Do pre-filmulation work.
1022     {
1023         AbortStatus abort;
1024         std::tie(valid, abort, prefilmParam) = paramManager->claimPrefilmParams();
1025         if (abort == AbortStatus::restart)
1026         {
1027             return emptyMatrix();
1028         }
1029 
1030 
1031         //Here we apply the exposure compensation and white balance and color conversion matrix.
1032         whiteBalance(recovered_image,
1033                      pre_film_image,
1034                      prefilmParam.temperature,
1035                      prefilmParam.tint,
1036                      camToRGB,
1037                      rCamMul, gCamMul, bCamMul,//needed as a reference but not actually applied
1038                      rPreMul, gPreMul, bPreMul,
1039                      65535.0f, pow(2, prefilmParam.exposureComp));
1040 
1041         if (NoCache == cache)
1042         {
1043             recovered_image.set_size( 0, 0 );
1044             cacheEmpty = true;
1045         }
1046         else
1047         {
1048             cacheEmpty = false;
1049         }
1050         if (WithHisto == histo)
1051         {
1052             //grab crop and rotation parameters
1053             CropParams cropParam = paramManager->claimCropParams();
1054             cropHeight = cropParam.cropHeight;
1055             cropAspect = cropParam.cropAspect;
1056             cropHoffset = cropParam.cropHoffset;
1057             cropVoffset = cropParam.cropVoffset;
1058             rotation = cropParam.rotation;
1059             histoInterface->updateHistPreFilm(pre_film_image, 65535,
1060                                               rotation,
1061                                               cropHeight, cropAspect,
1062                                               cropHoffset, cropVoffset);
1063         }
1064 
1065         cout << "ImagePipeline::processImage: Prefilmulation complete." << endl;
1066 
1067         valid = paramManager->markPrefilmComplete();
1068         updateProgress(valid, 0.0f);
1069         [[fallthrough]];
1070     }
1071     case partfilmulation: [[fallthrough]];
1072     case prefilmulation://Do filmulation
1073     {
1074         //We don't need to check abort status out here, because
1075         //the filmulate function will do so inside its loop.
1076         //We just check for it returning an empty matrix.
1077 
1078         //Here we do the film simulation on the image...
1079         //If filmulate detects an abort, it returns true.
1080         if (filmulate(pre_film_image,
1081                       filmulated_image,
1082                       paramManager,
1083                       this))
1084         {
1085             return emptyMatrix();
1086         }
1087 
1088         if (NoCache == cache)
1089         {
1090             pre_film_image.set_size(0, 0);
1091             cacheEmpty = true;
1092         }
1093         else
1094         {
1095             cacheEmpty = false;
1096         }
1097         if (WithHisto == histo)
1098         {
1099             //grab crop and rotation parameters
1100             CropParams cropParam = paramManager->claimCropParams();
1101             bool updatePreFilm = false;
1102             if (cropHeight != cropParam.cropHeight ||
1103                     cropAspect != cropParam.cropAspect ||
1104                     cropHoffset != cropParam.cropHoffset ||
1105                     cropVoffset != cropParam.cropVoffset ||
1106                     rotation != cropParam.rotation)
1107             {
1108                 updatePreFilm = true;
1109             }
1110             cropHeight = cropParam.cropHeight;
1111             cropAspect = cropParam.cropAspect;
1112             cropHoffset = cropParam.cropHoffset;
1113             cropVoffset = cropParam.cropVoffset;
1114             rotation = cropParam.rotation;
1115             if (updatePreFilm)
1116             {
1117                 histoInterface->updateHistPreFilm(pre_film_image, 65535,
1118                                                   rotation,
1119                                                   cropHeight, cropAspect,
1120                                                   cropHoffset, cropVoffset);
1121             }
1122             histoInterface->updateHistPostFilm(filmulated_image, .0025f,//TODO connect this magic number to the qml
1123                                                rotation,
1124                                                cropHeight, cropAspect,
1125                                                cropHoffset, cropVoffset);
1126         }
1127 
1128         cout << "ImagePipeline::processImage: Filmulation complete." << endl;
1129 
1130         valid = paramManager->markFilmComplete();
1131         updateProgress(valid, 0.0f);
1132         [[fallthrough]];
1133     }
1134     case partblackwhite: [[fallthrough]];
1135     case filmulation://Do whitepoint_blackpoint
1136     {
1137         AbortStatus abort;
1138         std::tie(valid, abort, blackWhiteParam) = paramManager->claimBlackWhiteParams();
1139         if (abort == AbortStatus::restart)
1140         {
1141             return emptyMatrix();
1142         }
1143 
1144         //Update histograms if necessary to correspond to crop
1145         if (WithHisto == histo)
1146         {
1147             //grab crop and rotation parameters
1148             bool updatePrePostFilm = false;
1149             if (cropHeight != blackWhiteParam.cropHeight ||
1150                     cropAspect != blackWhiteParam.cropAspect ||
1151                     cropHoffset != blackWhiteParam.cropHoffset ||
1152                     cropVoffset != blackWhiteParam.cropVoffset ||
1153                     rotation != blackWhiteParam.rotation)
1154             {
1155                 updatePrePostFilm = true;
1156             }
1157             cropHeight = blackWhiteParam.cropHeight;
1158             cropAspect = blackWhiteParam.cropAspect;
1159             cropHoffset = blackWhiteParam.cropHoffset;
1160             cropVoffset = blackWhiteParam.cropVoffset;
1161             rotation = blackWhiteParam.rotation;
1162             if (updatePrePostFilm)
1163             {
1164                 histoInterface->updateHistPreFilm(pre_film_image, 65535,
1165                                                   rotation,
1166                                                   cropHeight, cropAspect,
1167                                                   cropHoffset, cropVoffset);
1168                 histoInterface->updateHistPostFilm(filmulated_image, .0025f,//TODO connect this magic number to the qml
1169                                                    rotation,
1170                                                    cropHeight, cropAspect,
1171                                                    cropHoffset, cropVoffset);
1172             }
1173         }
1174         matrix<float> rotated_image;
1175 
1176         rotate_image(filmulated_image,
1177                      rotated_image,
1178                      blackWhiteParam.rotation);
1179 
1180         if (NoCache == cache)// clean up ram that's not needed anymore in order to reduce peak consumption
1181         {
1182             filmulated_image.set_size(0, 0);
1183             cacheEmpty = true;
1184         }
1185         else
1186         {
1187             cacheEmpty = false;
1188         }
1189 
1190 
1191         const int imWidth  = rotated_image.nc()/3;
1192         const int imHeight = rotated_image.nr();
1193 
1194         const float tempHeight = imHeight*max(min(1.0f,blackWhiteParam.cropHeight),0.0f);//restrict domain to 0:1
1195         const float tempAspect = max(min(10000.0f,blackWhiteParam.cropAspect),0.0001f);//restrict aspect ratio
1196         int width  = int(round(min(tempHeight*tempAspect,float(imWidth))));
1197         int height = int(round(min(tempHeight, imWidth/tempAspect)));
1198         const float maxHoffset = (1.0f-(float(width)  / float(imWidth) ))/2.0f;
1199         const float maxVoffset = (1.0f-(float(height) / float(imHeight)))/2.0f;
1200         const float oddH = (!(int(round((imWidth  - width )/2.0))*2 == (imWidth  - width )))*0.5f;//it's 0.5 if it's odd, 0 otherwise
1201         const float oddV = (!(int(round((imHeight - height)/2.0))*2 == (imHeight - height)))*0.5f;//it's 0.5 if it's odd, 0 otherwise
1202         const float hoffset = (round(max(min(blackWhiteParam.cropHoffset, maxHoffset), -maxHoffset) * imWidth  + oddH) - oddH)/imWidth;
1203         const float voffset = (round(max(min(blackWhiteParam.cropVoffset, maxVoffset), -maxVoffset) * imHeight + oddV) - oddV)/imHeight;
1204         int startX = int(round(0.5f*(imWidth  - width ) + hoffset*imWidth));
1205         int startY = int(round(0.5f*(imHeight - height) + voffset*imHeight));
1206         int endX = startX + width  - 1;
1207         int endY = startY + height - 1;
1208 
1209         if (blackWhiteParam.cropHeight <= 0)//it shall be turned off
1210         {
1211             startX = 0;
1212             startY = 0;
1213             endX = imWidth  - 1;
1214             endY = imHeight - 1;
1215             width  = imWidth;
1216             height = imHeight;
1217         }
1218 
1219 
1220         matrix<float> cropped_image;
1221         cout << "crop start:" << timeDiff (timeRequested) << endl;
1222         struct timeval crop_time;
1223         gettimeofday(&crop_time, nullptr);
1224 
1225         downscale_and_crop(rotated_image,
1226                            cropped_image,
1227                            startX,
1228                            startY,
1229                            endX,
1230                            endY,
1231                            width,
1232                            height);
1233 
1234         cout << "crop end: " << timeDiff(crop_time) << endl;
1235 
1236         rotated_image.set_size(0, 0);// clean up ram that's not needed anymore
1237 
1238 
1239         whitepoint_blackpoint(cropped_image,//filmulated_image,
1240                               contrast_image,
1241                               blackWhiteParam.whitepoint,
1242                               blackWhiteParam.blackpoint);
1243 
1244 
1245         valid = paramManager->markBlackWhiteComplete();
1246         updateProgress(valid, 0.0f);
1247         [[fallthrough]];
1248     }
1249     case partcolorcurve: [[fallthrough]];
1250     case blackwhite: // Do color_curve
1251     {
1252         //It's not gonna abort because we have no color curves yet..
1253         //Prepare LUT's for individual color processin.g
1254         lutR.setUnity();
1255         lutG.setUnity();
1256         lutB.setUnity();
1257         colorCurves(contrast_image,
1258                     color_curve_image,
1259                     lutR,
1260                     lutG,
1261                     lutB);
1262 
1263 
1264         if (NoCache == cache)
1265         {
1266             contrast_image.set_size(0, 0);
1267         }
1268         else
1269         {
1270             cacheEmpty = false;
1271         }
1272 
1273         valid = paramManager->markColorCurvesComplete();
1274         updateProgress(valid, 0.0f);
1275         [[fallthrough]];
1276     }
1277     case partfilmlikecurve: [[fallthrough]];
1278     case colorcurve://Do film-like curve
1279     {
1280         AbortStatus abort;
1281         std::tie(valid, abort, curvesParam) = paramManager->claimFilmlikeCurvesParams();
1282         if (abort == AbortStatus::restart)
1283         {
1284             return emptyMatrix();
1285         }
1286 
1287 
1288         filmLikeLUT.fill( [=](unsigned short in) -> unsigned short
1289             {
1290                 float shResult = shadows_highlights(float(in)/65535.0f,
1291                                                      curvesParam.shadowsX,
1292                                                      curvesParam.shadowsY,
1293                                                      curvesParam.highlightsX,
1294                                                      curvesParam.highlightsY);
1295                 return ushort(65535*default_tonecurve(shResult));
1296             }
1297         );
1298         matrix<unsigned short>& film_curve_image = vibrance_saturation_image;
1299         film_like_curve(color_curve_image,
1300                         film_curve_image,
1301                         filmLikeLUT);
1302 
1303         if (NoCache == cache)
1304         {
1305             color_curve_image.set_size(0, 0);
1306             cacheEmpty = true;
1307             //film_curve_image is going out of scope anyway.
1308         }
1309         else
1310         {
1311             cacheEmpty = false;
1312         }
1313 
1314         if (!curvesParam.monochrome)
1315         {
1316             vibrance_saturation(film_curve_image,
1317                                 vibrance_saturation_image,
1318                                 curvesParam.vibrance,
1319                                 curvesParam.saturation);
1320         } else {
1321             monochrome_convert(film_curve_image,
1322                                vibrance_saturation_image,
1323                                curvesParam.bwRmult,
1324                                curvesParam.bwGmult,
1325                                curvesParam.bwBmult);
1326         }
1327 
1328         updateProgress(valid, 0.0f);
1329         [[fallthrough]];
1330     }
1331     default://output
1332     {
1333         if (NoCache == cache)
1334         {
1335             //vibrance_saturation_image.set_size(0, 0);
1336             cacheEmpty = true;
1337         }
1338         else
1339         {
1340             cacheEmpty = false;
1341         }
1342         if (WithHisto == histo)
1343         {
1344             histoInterface->updateHistFinal(vibrance_saturation_image);
1345         }
1346         valid = paramManager->markFilmLikeCurvesComplete();
1347         updateProgress(valid, 0.0f);
1348 
1349         exifOutput = exifData;
1350         return vibrance_saturation_image;
1351     }
1352     }//End task switch
1353 
1354     return emptyMatrix();
1355 }
1356 
1357 //Saved for posterity: we may want to re-implement this 0.1 second continuation
1358 // inside of parameterManager.
1359 //bool ImagePipeline::checkAbort(bool aborted)
1360 //{
1361 //    if (aborted && timeDiff(timeRequested) > 0.1)
1362 //    {
1363 //        cout << "ImagePipeline::aborted. valid = " << valid << endl;
1364 //        return true;
1365 //    }
1366 //    else
1367 //    {
1368 //        return false;
1369 //    }
1370 //}
1371 
updateProgress(Valid valid,float stepProgress)1372 void ImagePipeline::updateProgress(Valid valid, float stepProgress)
1373 {
1374     double totalTime = numeric_limits<double>::epsilon();
1375     double totalCompletedTime = 0;
1376     for (ulong i = 0; i < completionTimes.size(); i++)
1377     {
1378         totalTime += completionTimes[i];
1379         float fractionCompleted = 0;
1380         if (i <= valid)
1381             fractionCompleted = 1;
1382         if (i == valid + 1)
1383             fractionCompleted = stepProgress;
1384         //if greater -> 0
1385         totalCompletedTime += completionTimes[i]*double(fractionCompleted);
1386     }
1387     histoInterface->setProgress(float(totalCompletedTime/totalTime));
1388 }
1389 
1390 //Do not call this on something that's already been used!
setCache(Cache cacheIn)1391 void ImagePipeline::setCache(Cache cacheIn)
1392 {
1393     if (false == hasStartedProcessing)
1394     {
1395         cache = cacheIn;
1396     }
1397 }
1398 
1399 //This swaps the data between pipelines.
1400 //The intended use is for preloading.
swapPipeline(ImagePipeline * swapTarget)1401 void ImagePipeline::swapPipeline(ImagePipeline * swapTarget)
1402 {
1403     std::swap(valid, swapTarget->valid);
1404     std::swap(progress, swapTarget->progress);
1405 
1406     raw_image.swap(swapTarget->raw_image);
1407 
1408     std::swap(cfa, swapTarget->cfa);
1409     std::swap(xtrans, swapTarget->xtrans);
1410     maxXtrans = swapTarget->maxXtrans;
1411 
1412     raw_width = swapTarget->raw_width;
1413     raw_height = swapTarget->raw_height;
1414 
1415     std::swap(camToRGB, swapTarget->camToRGB);
1416     std::swap(camToRGB4, swapTarget->camToRGB4);
1417 
1418     std::swap(rCamMul, swapTarget->rCamMul);
1419     std::swap(gCamMul, swapTarget->gCamMul);
1420     std::swap(bCamMul, swapTarget->bCamMul);
1421     std::swap(rPreMul, swapTarget->rPreMul);
1422     std::swap(gPreMul, swapTarget->gPreMul);
1423     std::swap(bPreMul, swapTarget->bPreMul);
1424 
1425     std::swap(maxValue, swapTarget->maxValue);
1426     std::swap(isSraw, swapTarget->isSraw);
1427     std::swap(isNikonSraw, swapTarget->isNikonSraw);
1428     std::swap(isMonochrome, swapTarget->isMonochrome);
1429     std::swap(isCR3, swapTarget->isCR3);
1430 
1431     std::swap(cropHeight, swapTarget->cropHeight);
1432     std::swap(cropAspect, swapTarget->cropAspect);
1433     std::swap(cropHoffset, swapTarget->cropHoffset);
1434     std::swap(cropVoffset, swapTarget->cropVoffset);
1435     std::swap(rotation, swapTarget->rotation);
1436 
1437     std::swap(exifData, swapTarget->exifData);
1438     std::swap(basicExifData, swapTarget->basicExifData);
1439 
1440     input_image.swap(swapTarget->input_image);
1441     recovered_image.swap(swapTarget->recovered_image);
1442     pre_film_image.swap(swapTarget->pre_film_image);
1443     filmulated_image.swap(swapTarget->filmulated_image);
1444     contrast_image.swap(swapTarget->contrast_image);
1445     color_curve_image.swap(swapTarget->color_curve_image);
1446     vibrance_saturation_image.swap(swapTarget->vibrance_saturation_image);
1447 }
1448 
1449 //This is used to copy only images from one pipeline to another,
1450 // but downsampling to the set resolution.
1451 //The intended use is for improving the quality of the quick preview
1452 // in the case of distortion correction or leveling.
copyAndDownsampleImages(ImagePipeline * copySource)1453 void ImagePipeline::copyAndDownsampleImages(ImagePipeline * copySource)
1454 {
1455     //We only want to copy stuff starting with recovered image.
1456     downscale_and_crop(copySource->recovered_image, recovered_image, 0, 0, ((copySource->recovered_image.nc())/3)-1, copySource->recovered_image.nr()-1, resolution, resolution);
1457     downscale_and_crop(copySource->pre_film_image, pre_film_image, 0, 0, ((copySource->pre_film_image.nc())/3)-1, copySource->pre_film_image.nr()-1, resolution, resolution);
1458     downscale_and_crop(copySource->filmulated_image, filmulated_image, 0, 0, ((copySource->filmulated_image.nc())/3)-1, copySource->filmulated_image.nr()-1, resolution, resolution);
1459     //The stuff after filmulated_image is type <unsigned short> and so
1460     // we don't have a routine to scale them. But that's okay, I think.
1461     //Anything except tweaking saturation will pull from the higher res
1462     // data.
1463 }
1464 
1465 //This is used to update the histograms once data is copied on an image change
rerunHistograms()1466 void ImagePipeline::rerunHistograms()
1467 {
1468     if (WithHisto == histo)
1469     {
1470         if (valid >= Valid::load)
1471         {
1472             histoInterface->updateHistRaw(raw_image, maxValue, cfa, xtrans, maxXtrans, isSraw, isMonochrome);
1473         }
1474         if (valid >= Valid::prefilmulation)
1475         {
1476             histoInterface->updateHistPreFilm(pre_film_image, 65535,
1477                                               rotation,
1478                                               cropHeight, cropAspect, cropHoffset, cropVoffset);
1479         }
1480         if (valid >= Valid::filmulation)
1481         {
1482             histoInterface->updateHistPostFilm(filmulated_image, .0025f,
1483                                                rotation,
1484                                                cropHeight, cropAspect, cropHoffset, cropVoffset);
1485         }
1486         if (valid >= Valid::filmlikecurve)
1487         {
1488             histoInterface->updateHistFinal(vibrance_saturation_image);
1489         }
1490     }
1491 }
1492 
1493 //Return the average level of each channel of the image sampled at a 21x21
1494 // square.
1495 //The square is positioned relative to the image dimensions of the cropped image.
sampleWB(const float xPos,const float yPos,const int rotation,const float cropHeight,const float cropAspect,const float cropVoffset,const float cropHoffset,float & red,float & green,float & blue)1496 void ImagePipeline::sampleWB(const float xPos, const float yPos,
1497                              const int rotation,
1498                              const float cropHeight, const float cropAspect,
1499                              const float cropVoffset, const float cropHoffset,
1500                              float &red, float &green, float &blue)
1501 {
1502     if (xPos < 0 || xPos > 1 || yPos < 0 || yPos > 1)
1503     {
1504         red = -1;
1505         green = -1;
1506         blue = -1;
1507         return;
1508     }
1509 
1510     //recovered_image is what we're looking to sample.
1511     //It already has the camera multipliers applied, so we have to divide by them later.
1512 
1513     //First we rotate it.
1514     matrix<float> rotated_image;
1515     rotate_image(recovered_image, rotated_image, rotation);
1516 
1517     //Then we crop the recovered image
1518     //This is copied from the actual image pipeline.
1519     const int imWidth  = rotated_image.nc()/3;
1520     const int imHeight = rotated_image.nr();
1521 
1522     const float tempHeight = imHeight*max(min(1.0f,cropHeight),0.0f);//restrict domain to 0:1
1523     const float tempAspect = max(min(10000.0f,cropAspect),0.0001f);//restrict aspect ratio
1524     int width  = int(round(min(tempHeight*tempAspect,float(imWidth))));
1525     int height = int(round(min(tempHeight, imWidth/tempAspect)));
1526     const float maxHoffset = (1.0f-(float(width)  / float(imWidth) ))/2.0f;
1527     const float maxVoffset = (1.0f-(float(height) / float(imHeight)))/2.0f;
1528     const float oddH = (!(int(round((imWidth  - width )/2.0))*2 == (imWidth  - width )))*0.5f;//it's 0.5 if it's odd, 0 otherwise
1529     const float oddV = (!(int(round((imHeight - height)/2.0))*2 == (imHeight - height)))*0.5f;//it's 0.5 if it's odd, 0 otherwise
1530     const float hoffset = (round(max(min(cropHoffset, maxHoffset), -maxHoffset) * imWidth  + oddH) - oddH)/imWidth;
1531     const float voffset = (round(max(min(cropVoffset, maxVoffset), -maxVoffset) * imHeight + oddV) - oddV)/imHeight;
1532     int startX = int(round(0.5f*(imWidth  - width ) + hoffset*imWidth));
1533     int startY = int(round(0.5f*(imHeight - height) + voffset*imHeight));
1534     int endX = startX + width  - 1;
1535     int endY = startY + height - 1;
1536 
1537     if (cropHeight <= 0)//it shall be turned off
1538     {
1539         startX = 0;
1540         startY = 0;
1541         endX = imWidth  - 1;
1542         endY = imHeight - 1;
1543         width  = imWidth;
1544         height = imHeight;
1545     }
1546 
1547 
1548     matrix<float> cropped_image;
1549 
1550     downscale_and_crop(rotated_image,
1551                        cropped_image,
1552                        startX,
1553                        startY,
1554                        endX,
1555                        endY,
1556                        width,
1557                        height);
1558 
1559 
1560     rotated_image.set_size(0, 0);
1561 
1562     //Now we compute the x position
1563     const int sampleX = round(xPos * (width-1));
1564     const int sampleY = round(yPos * (height-1));
1565     const int sampleStartX = max(0, sampleX - 10);
1566     const int sampleStartY = max(0, sampleY - 10);
1567     const int sampleEndX = min(width-1, sampleX + 10);
1568     const int sampleEndY = min(height-1, sampleY + 10);
1569 
1570     double rSum = 0;
1571     double gSum = 0;
1572     double bSum = 0;
1573     int count = 0;
1574     for (int row = sampleStartY; row <= sampleEndY; row++)
1575     {
1576         for (int col = sampleStartX; col <= sampleEndX; col++)
1577         {
1578             rSum += cropped_image(row, col*3    );
1579             gSum += cropped_image(row, col*3 + 1);
1580             bSum += cropped_image(row, col*3 + 2);
1581             count++;
1582         }
1583     }
1584     if (count < 1)//some sort of error occurs
1585     {
1586         red = -1;
1587         green = -1;
1588         blue = -1;
1589         return;
1590     }
1591 
1592     //Compute the average and also divide by the camera WB multipliers
1593     red   = rSum / (rCamMul*count);
1594     green = gSum / (gCamMul*count);
1595     blue  = bSum / (bCamMul*count);
1596 }
1597