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, ¬Used); //end weight X
919 float eWY = modf(rotatedY, ¬Used); //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, ¬Used); //end weight X
1001 float eWY = modf(rotatedY, ¬Used); //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