1 /*******************************************************************************************************
2  DkBasicLoader.cpp
3  Created on:	21.02.2014
4 
5  nomacs is a fast and small image viewer with the capability of synchronizing multiple instances
6 
7  Copyright (C) 2011-2014 Markus Diem <markus@nomacs.org>
8  Copyright (C) 2011-2014 Stefan Fiel <stefan@nomacs.org>
9  Copyright (C) 2011-2014 Florian Kleber <florian@nomacs.org>
10 
11  This file is part of nomacs.
12 
13  nomacs is free software: you can redistribute it and/or modify
14  it under the terms of the GNU General Public License as published by
15  the Free Software Foundation, either version 3 of the License, or
16  (at your option) any later version.
17 
18  nomacs is distributed in the hope that it will be useful,
19  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  GNU General Public License for more details.
22 
23  You should have received a copy of the GNU General Public License
24  along with this program.  If not, see <http://www.gnu.org/licenses/>.
25 
26  *******************************************************************************************************/
27 
28 #include "DkBasicLoader.h"
29 
30 #include "DkMetaData.h"
31 #include "DkImageContainer.h"
32 #include "DkImageStorage.h"
33 #include "DkSettings.h"
34 #include "DkTimer.h"
35 #include "DkMath.h"
36 #include "DkUtils.h"	// just needed for qInfo() #ifdef
37 
38 #pragma warning(push, 0)
39 #include <QObject>
40 #include <QFileInfo>
41 #include <QImage>
42 #include <QImageReader>
43 #include <QImageWriter>
44 #include <QNetworkReply>
45 #include <QBuffer>
46 #include <QNetworkProxyFactory>
47 #include <QPixmap>
48 #include <QIcon>
49 #include <QDebug>
50 #include <QtConcurrentRun>
51 
52 #include <qmath.h>
53 #include <assert.h>
54 
55 // quazip
56 #ifdef WITH_QUAZIP
57 #ifdef WITH_QUAZIP1
58 #include <quazip/JlCompress.h>
59 #else
60 #include <quazip5/JlCompress.h>
61 #endif
62 #endif
63 
64 // opencv
65 #ifdef WITH_OPENCV
66 
67 #ifdef Q_OS_WIN
68 #pragma warning(disable: 4996)
69 #endif
70 
71 #include "opencv2/core/core.hpp"
72 #include "opencv2/imgproc/imgproc.hpp"
73 
74 #ifdef WITH_LIBRAW
75 #include <libraw/libraw.h>
76 #endif
77 
78 #ifdef WITH_LIBTIFF
79 #ifdef Q_OS_WIN
80 #include <tif_config.h>
81 #endif
82 
83 //#if defined(Q_OS_MAC) || defined(Q_OS_OPENBSD)
84 // here we clash (typedef redefinition with different types ('long' vs 'int64_t' (aka 'long long')))
85 // so we simply define our own int64 before including tiffio
86 #define uint64 uint64_hack_
87 #define int64 int64_hack_
88 //#endif // defined(Q_OS_MAC) || defined(Q_OS_OPENBSD)
89 
90 #include <tiffio.h>
91 #include <tiffio.hxx>		// this is needed if you want to load tiffs from the buffer
92 
93 //#if defined(Q_OS_MAC) || defined(Q_OS_OPENBSD)
94 #undef uint64
95 #undef int64
96 //#endif // defined(Q_OS_MAC) || defined(Q_OS_OPENBSD)
97 
98 #endif //#ifdef WITH_LIBTIFF
99 
100 #endif //#ifdef WITH_OPENCV
101 
102 #ifdef Q_OS_WIN
103 #include <olectl.h>
104 #pragma comment(lib, "oleaut32.lib")
105 
106 #include <QtWin>
107 #endif //#ifdef Q_OS_WIN
108 
109 
110 #pragma warning(pop)
111 
112 namespace nmc {
113 
114 // DkEditImage --------------------------------------------------------------------
DkEditImage(const QImage & img,const QString & editName)115 DkEditImage::DkEditImage(const QImage& img, const QString& editName) {
116 	mImg = img;
117 	mEditName = editName;
118 }
119 
setImage(const QImage & img)120 void DkEditImage::setImage(const QImage& img) {
121 	mImg = img;
122 }
123 
image() const124 QImage DkEditImage::image() const {
125 	return mImg;
126 }
127 
editName() const128 QString DkEditImage::editName() const {
129 	return mEditName;
130 }
131 
size() const132 int DkEditImage::size() const {
133 
134 	return qRound(DkImage::getBufferSizeFloat(mImg.size(), mImg.depth()));
135 }
136 
137 // Basic loader and image edit class --------------------------------------------------------------------
DkBasicLoader(int mode)138 DkBasicLoader::DkBasicLoader(int mode) {
139 
140 	mMode = mode;
141 	mTraining = false;
142 	mPageIdxDirty = false;
143 	mNumPages = 1;
144 	mPageIdx = 1;
145 	mLoader = no_loader;
146 
147 	mMetaData = QSharedPointer<DkMetaDataT>(new DkMetaDataT());
148 }
149 
loadGeneral(const QString & filePath,bool loadMetaData,bool fast)150 bool DkBasicLoader::loadGeneral(const QString& filePath, bool loadMetaData, bool fast) {
151 
152 	return loadGeneral(filePath, QSharedPointer<QByteArray>(), loadMetaData, fast);
153 }
154 /**
155  * This function loads the images.
156  * @param file the image file that should be loaded.
157  * @return bool true if the image could be loaded.
158  **/
loadGeneral(const QString & filePath,QSharedPointer<QByteArray> ba,bool loadMetaData,bool fast)159 bool DkBasicLoader::loadGeneral(const QString& filePath, QSharedPointer<QByteArray> ba, bool loadMetaData, bool fast) {
160 
161 	DkTimer dt;
162 	bool imgLoaded = false;
163 
164 	mFile = DkUtils::resolveSymLink(filePath);
165 	QFileInfo fInfo(mFile);	// resolved lnk
166 	QString newSuffix = fInfo.suffix();
167 
168 	release();
169 
170 	if (mPageIdxDirty)
171 		imgLoaded = loadPage();
172 
173 	// identify raw images:
174 	//newSuffix.contains(QRegExp("(nef|crw|cr2|arw|rw2|mrw|dng)", Qt::CaseInsensitive)))
175 
176 	// this fixes an issue with the new jpg loader
177 	// Qt considers an orientation of 0 as wrong and fails to load these jpgs
178 	// however, the old nomacs wrote 0 if the orientation should be cleared
179 	// so we simply adopt the memory here
180 	if (loadMetaData && mMetaData) {
181 
182 		try {
183 			mMetaData->readMetaData(filePath, ba);
184 
185 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
186 			// this is a workaroung for old Qt5 versions where jpgs with 'illegal' orientation=0 were not loaded
187 			if (!DkSettingsManager::param().metaData().ignoreExifOrientation) {
188 				DkMetaDataT::ExifOrientationState orState = mMetaData->checkExifOrientation();
189 
190 				if (orState == DkMetaDataT::or_illegal) {
191 					mMetaData->clearOrientation();
192 					mMetaData->saveMetaData(ba);
193 					qWarning() << "deleting illegal EXIV orientation...";
194 				}
195 			}
196 #endif
197 		}
198 		catch (...) {}	// ignore if we cannot read the metadata
199 	}
200 	else if (!mMetaData) {
201 		qDebug() << "metaData is NULL!";
202 	}
203 
204 	QList<QByteArray> qtFormats = QImageReader::supportedImageFormats();
205 	qtFormats << "jpe";	// fixes #435 - thumbnail gets loaded in the RAW loader
206 	QString suf = fInfo.suffix().toLower();
207 
208 	QImage img;
209 
210     // load drif file
211     if (!imgLoaded && ("drif" == suf || "yuv" == suf || "raw" == suf))
212         imgLoaded = loadDrifFile(mFile, img, ba);
213 
214 	if (!imgLoaded && !fInfo.exists() && ba && !ba->isEmpty()) {
215 		imgLoaded = img.loadFromData(*ba.data());
216 
217 		if (imgLoaded)
218 			mLoader = qt_loader;
219 	}
220 
221 	// load large icons
222 	if (!imgLoaded && suf == "ico") {
223 
224 		QIcon icon(mFile);
225 
226 		if (!icon.isNull()) {
227 			img = icon.pixmap(QSize(256, 256)).toImage();
228 			imgLoaded = true;
229 		}
230 	}
231 
232 	// default Qt loader
233 	// here we just try those formats that are officially supported
234 	if (!imgLoaded && qtFormats.contains(suf.toStdString().c_str()) || suf.isEmpty()) {
235 
236 		// if image has Indexed8 + alpha channel -> we crash... sorry for that
237 		if (!ba || ba->isEmpty())
238 			imgLoaded = img.load(mFile, suf.toStdString().c_str());
239 		else
240 			imgLoaded = img.loadFromData(*ba.data(), suf.toStdString().c_str());	// toStdString() in order get 1 byte per char
241 
242 		if (imgLoaded) mLoader = qt_loader;
243 	}
244 
245 	// OpenCV Tiff loader - supports jpg compressed tiffs
246 	if (!imgLoaded && newSuffix.contains(QRegExp("(tif|tiff)", Qt::CaseInsensitive))) {
247 
248 		imgLoaded = loadTIFFile(mFile, img, ba);
249 
250 		if (imgLoaded)	mLoader = tif_loader;
251 	}
252 
253 	// PSD loader
254 	if (!imgLoaded) {
255 
256 		imgLoaded = loadPSDFile(mFile, img, ba);
257 		if (imgLoaded) mLoader = psd_loader;
258 	}
259 
260 #if QT_VERSION < 0x050000	// >DIR: qt5 ships with webp : ) [23.4.2015 markus]
261 	// WEBP loader
262 	if (!imgLoaded) {
263 
264 		imgLoaded = loadWebPFile(file, ba);
265 		if (imgLoaded) loader = webp_loader;
266 	}
267 #endif
268 
269 	// RAW loader
270 	if (!imgLoaded && !qtFormats.contains(suf.toStdString().c_str())) {
271 
272 		// TODO: sometimes (e.g. _DSC6289.tif) strange opencv errors are thrown - catch them!
273 		// load raw files
274 		imgLoaded = loadRawFile(mFile, img, ba, fast);
275 		if (imgLoaded) mLoader = raw_loader;
276 	}
277 
278 	// TGA loader
279 	if (!imgLoaded && newSuffix.contains(QRegExp("(tga)", Qt::CaseInsensitive))) {
280 
281 		imgLoaded = loadTgaFile(mFile, img, ba);
282 
283 		if (imgLoaded) mLoader = tga_loader;		// TODO: add tga loader
284 	}
285 
286 	QByteArray lba;
287 
288 	// default Qt loader
289 	if (!imgLoaded && !newSuffix.contains(QRegExp("(roh)", Qt::CaseInsensitive))) {
290 
291 		// if we first load files to buffers, we can additionally load images with wrong extensions (rainer bugfix : )
292 		// TODO: add warning here
293 		loadFileToBuffer(mFile, lba);
294 		imgLoaded = img.loadFromData(lba);
295 
296 		if (imgLoaded)
297 			qWarning() << "The image seems to have a wrong extension";
298 
299 		if (imgLoaded) mLoader = qt_loader;
300 	}
301 
302 	// add marker to fix broken panorama images from SAMSUNG
303 	// see: https://github.com/nomacs/nomacs/issues/254
304 	if (!imgLoaded && newSuffix.contains(QRegExp("(jpg|jpeg|jpe)", Qt::CaseInsensitive))) {
305 
306 		// prefer external buffer
307 		QByteArray baf = DkImage::fixSamsungPanorama(ba && !ba->isEmpty() ? *ba : lba);
308 
309 		if (!baf.isEmpty())
310 			imgLoaded = img.loadFromData(baf, suf.toStdString().c_str());
311 
312 		if (imgLoaded) mLoader = qt_loader;
313 	}
314 
315 	// this loader is a bit buggy -> be carefull
316 	if (!imgLoaded && newSuffix.contains(QRegExp("(roh)", Qt::CaseInsensitive))) {
317 
318 		imgLoaded = loadRohFile(mFile, img, ba);
319 		if (imgLoaded) mLoader = roh_loader;
320 	}
321 
322 	// this loader is for OpenCV cascade training files
323 	if (!imgLoaded && newSuffix.contains(QRegExp("(vec)", Qt::CaseInsensitive))) {
324 
325 		imgLoaded = loadOpenCVVecFile(mFile, img, ba);
326 		if (imgLoaded) mLoader = roh_loader;
327 	}
328 
329 	// tiff things
330 	if (imgLoaded && !mPageIdxDirty)
331 		indexPages(mFile, ba);
332 	mPageIdxDirty = false;
333 
334 	if (imgLoaded && loadMetaData && mMetaData) {
335 
336 		try {
337 			mMetaData->setQtValues(img);
338 			int orientation = mMetaData->getOrientationDegree();
339 
340 			if (orientation != -1 && !mMetaData->isTiff() && !DkSettingsManager::param().metaData().ignoreExifOrientation)
341 				img = rotate(img, orientation);
342 
343 		} catch(...) {}	// ignore if we cannot read the metadata
344 	}
345 	else if (!mMetaData) {
346 		qDebug() << "metaData is NULL!";
347 	}
348 
349 	if (imgLoaded)
350 		setEditImage(img, tr("Original Image"));
351 
352 	if (imgLoaded)
353 		qInfo() << "[Basic Loader]" << filePath << "loaded in" << dt;
354 	else
355 		qWarning() << "[Basic Loader] could not load" << filePath;
356 
357 	return imgLoaded;
358 }
359 
360 /**
361  * Loads special RAW files that are generated by the Hamamatsu camera.
362  * @param fileName the filename of the file to be loaded.
363  * @return bool true if the file could be loaded.
364  **/
loadRohFile(const QString & filePath,QImage & img,QSharedPointer<QByteArray> ba) const365 bool DkBasicLoader::loadRohFile(const QString& filePath, QImage& img, QSharedPointer<QByteArray> ba) const {
366 
367 	if (!ba)
368 		ba = loadFileToBuffer(filePath);
369 	if (!ba || ba->isEmpty())
370 		return false;
371 
372 	bool imgLoaded = false;
373 
374 	int rohW = 4000;
375 	int rohH = 2672;
376 	unsigned char fByte;	// first byte
377 	unsigned char sByte;	// second byte
378 
379 	try {
380 
381 		const unsigned char* pData = (const unsigned char*)ba->constData();
382 		unsigned char* buffer = new unsigned char[rohW*rohH];
383 
384 		if (!buffer)
385 			return imgLoaded;
386 
387 		for (long long i = 0; i < (rohW*rohH); i++){
388 
389 			fByte = pData[i*2];
390 			sByte = pData[i*2+1];
391 			fByte = fByte >> 4;
392 			fByte = fByte & 15;
393 			sByte = sByte << 4;
394 			sByte = sByte & 240;
395 
396 			buffer[i] = (fByte | sByte);
397 
398 		}
399 
400 		img = QImage(buffer, rohW, rohH, QImage::Format_Indexed8);
401 
402 		if (img.isNull())
403 			return imgLoaded;
404 		else
405 			imgLoaded = true;
406 
407 
408 		//img = img.copy();
409 		QVector<QRgb> colorTable;
410 
411 		for (int i = 0; i < 256; i++)
412 			colorTable.push_back(QColor(i, i, i).rgb());
413 
414 		img.setColorTable(colorTable);
415 
416 	} catch(...) {
417 		imgLoaded = false;
418 	}
419 
420 	//if (imgLoaded) {
421 	//	setEditImage(img, tr("Original Image"));
422 	//}
423 
424 	return imgLoaded;
425 }
426 
loadTgaFile(const QString & filePath,QImage & img,QSharedPointer<QByteArray> ba) const427 bool nmc::DkBasicLoader::loadTgaFile(const QString & filePath, QImage & img, QSharedPointer<QByteArray> ba) const {
428 
429 	if (!ba || ba->isEmpty())
430 		ba = loadFileToBuffer(filePath);
431 
432 	tga::DkTgaLoader tl = tga::DkTgaLoader(ba);
433 
434 	bool success = tl.load();
435 	img = tl.image();
436 
437 	return success;
438 }
439 
440 /**
441  * Loads the RAW file specified.
442  * Note: nomacs needs to be compiled with OpenCV and LibRaw in
443  * order to enable RAW file loading.
444  * @param ba the file loaded into a bytearray.
445  * @return bool true if the file could be loaded.
446  **/
loadRawFile(const QString & filePath,QImage & img,QSharedPointer<QByteArray> ba,bool fast) const447 bool DkBasicLoader::loadRawFile(const QString& filePath, QImage& img, QSharedPointer<QByteArray> ba, bool fast) const {
448 
449 	DkRawLoader rawLoader(filePath, mMetaData);
450 	rawLoader.setLoadFast(fast);
451 
452 	bool success = rawLoader.load(ba);
453 
454 	if (success)
455 		img = rawLoader.image();
456 
457 	return success;
458 }
459 
460 #ifdef Q_OS_WIN
loadPSDFile(const QString &,QImage &,QSharedPointer<QByteArray>) const461 bool DkBasicLoader::loadPSDFile(const QString&, QImage&, QSharedPointer<QByteArray>) const {
462 #else
463 bool DkBasicLoader::loadPSDFile(const QString& filePath, QImage& img, QSharedPointer<QByteArray> ba) const {
464 
465 	// load from file?
466 	if (!ba || ba->isEmpty()) {
467 		QFile file(filePath);
468 		file.open(QIODevice::ReadOnly);
469 
470 		QPsdHandler psdHandler;
471 		psdHandler.setDevice(&file);	// QFile is an IODevice
472 		//psdHandler.setFormat(fileInfo.suffix().toLocal8Bit());
473 
474 		if (psdHandler.canRead(&file)) {
475 			bool success = psdHandler.read(&img);
476 			//setEditImage(img, tr("Original Image"));
477 
478 			return success;
479 		}
480 	}
481 	else {
482 
483 		QBuffer buffer;
484 		buffer.setData(*ba.data());
485 		buffer.open(QIODevice::ReadOnly);
486 
487 		QPsdHandler psdHandler;
488 		psdHandler.setDevice(&buffer);	// QFile is an IODevice
489 		//psdHandler.setFormat(file.suffix().toLocal8Bit());
490 
491 		if (psdHandler.canRead(&buffer)) {
492 			bool success = psdHandler.read(&img);
493 			//setEditImage(img, tr("Original Image"));
494 
495 			return success;
496 		}
497 	}
498 
499 #endif // !Q_OS_WIN
500 	return false;
501 }
502 
503 #ifndef WITH_LIBTIFF
504 bool DkBasicLoader::loadTIFFile(const QString&, QImage&, QSharedPointer<QByteArray>) const {
505 #else
506 bool DkBasicLoader::loadTIFFile(const QString& filePath, QImage& img, QSharedPointer<QByteArray> ba) const {
507 
508 	bool success = false;
509 
510 	// first turn off nasty warning/error dialogs - (we do the GUI : )
511 	TIFFErrorHandler oldErrorHandler, oldWarningHandler;
512 	oldWarningHandler = TIFFSetWarningHandler(NULL);
513 	oldErrorHandler = TIFFSetErrorHandler(NULL);
514 
515 	DkTimer dt;
516 	TIFF* tiff = 0;
517 
518 
519 // TODO: currently TIFFStreamOpen can only be linked on Windows?!
520 #if QT_VERSION >= QT_VERSION_CHECK(5,5,0) && defined(Q_OS_WIN)
521 
522 	std::istringstream is(ba ? ba->toStdString() : "");
523 
524 	if (ba)
525 		tiff = TIFFStreamOpen("MemTIFF", &is);
526 
527 	// fallback to direct loading
528 	if (!tiff)
529 		tiff = TIFFOpen(filePath.toLatin1(), "r");
530 
531 	// loading from buffer allows us to load files with non-latin names
532 	QSharedPointer<QByteArray> bal;
533 	if (!tiff)
534 		bal = loadFileToBuffer(filePath);
535 
536 	std::istringstream isl(bal ? bal->toStdString() : "");
537 
538 	if (bal)
539 		tiff = TIFFStreamOpen("MemTIFF", &isl);
540 #else
541 	tiff = TIFFOpen(filePath.toLatin1(), "r");
542 #endif
543 
544 	if (!tiff)
545 		return success;
546 
547 	uint32 width = 0;
548 	uint32 height = 0;
549 
550 	TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width);
551 	TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height);
552 
553 	// init the qImage
554 	img = QImage(width, height, QImage::Format_ARGB32);
555 
556 	const int stopOnError = 1;
557 	success = TIFFReadRGBAImageOriented(tiff, width, height, reinterpret_cast<uint32 *>(img.bits()), ORIENTATION_TOPLEFT, stopOnError) != 0;
558 
559 	if (success) {
560 		for (uint32 y = 0; y<height; ++y)
561 			convert32BitOrder(img.scanLine(y), width);
562 	}
563 
564 	TIFFClose(tiff);
565 
566 	TIFFSetWarningHandler(oldWarningHandler);
567 	TIFFSetWarningHandler(oldErrorHandler);
568 
569 	return success;
570 
571 #endif // !WITH_LIBTIFF
572 	return false;
573 }
574 
575 #define DRIF_IMAGE_IMPL
576 #include "drif_image.h"
577 
578 bool isQtFmtCompatible(uint32_t f)
579 {
580     switch (f)
581     {
582     case DRIF_FMT_RGB888:
583     case DRIF_FMT_RGBA8888:
584     case DRIF_FMT_GRAY:
585         return true;
586     }
587 
588     return false;
589 }
590 
591 uint32_t drif2qtfmt(uint32_t f)
592 {
593     switch (f)
594     {
595     case DRIF_FMT_RGB888:  return QImage::Format_RGB888;
596     case DRIF_FMT_RGBA8888:  return QImage::Format_RGBA8888;
597 
598 	// grayscale 8 was added in Qt 5.4
599 #if QT_VERSION >= 0x050500
600 	case DRIF_FMT_GRAY:  return QImage::Format_Grayscale8;
601 #endif
602 
603     }
604 
605     return QImage::Format_Invalid;
606 }
607 
608 bool DkBasicLoader::loadDrifFile(const QString& filePath, QImage& img, QSharedPointer<QByteArray> ba) const {
609 
610     bool success = false;
611 
612     uint32_t w;
613     uint32_t h;
614     uint32_t f;
615 
616     uint8_t* imgBytes = drifLoadImg(filePath.toLatin1(), &w, &h, &f);
617 
618     if (!imgBytes)
619         return success;
620 
621     if (isQtFmtCompatible(f))
622     {
623         img = QImage((int)w, (int)h, (QImage::Format)drif2qtfmt(f));
624         memcpy(reinterpret_cast<void*>(img.bits()), imgBytes, drifGetSize(w, h, f));
625 
626         success = true;
627     }
628 #ifdef WITH_OPENCV
629     else
630     {
631         img = QImage((int)w, (int)h, QImage::Format_RGB888);
632 
633         switch (f)
634         {
635         case DRIF_FMT_BGR888:
636         {
637             cv::Mat imgMat = cv::Mat((int)h, (int)w, CV_8UC3, imgBytes);
638             cv::Mat rgbMat = cv::Mat((int)h, (int)w, CV_8UC3, reinterpret_cast<uint8_t*>(img.bits()));
639             cv::cvtColor(imgMat, rgbMat, CV_BGR2RGB);
640 
641             success = true;
642         }
643         break;
644 
645         case DRIF_FMT_RGB888P:
646         case DRIF_FMT_RGBA8888P:
647         {
648             cv::Mat imgMatR = cv::Mat((int)h, (int)w, CV_8UC1, imgBytes);
649             cv::Mat imgMatG = cv::Mat((int)h, (int)w, CV_8UC1, imgBytes + w * h);
650             cv::Mat imgMatB = cv::Mat((int)h, (int)w, CV_8UC1, imgBytes + 2 * w * h);
651             cv::Mat rgbMat  = cv::Mat((int)h, (int)w, CV_8UC3, reinterpret_cast<uint8_t*>(img.bits()));
652 
653             std::vector<cv::Mat> imgMat{ imgMatR, imgMatG, imgMatB };
654             cv::merge(imgMat, rgbMat);
655 
656             success = true;
657         }
658         break;
659 
660         case DRIF_FMT_BGR888P:
661         case DRIF_FMT_BGRA8888P:
662         {
663             cv::Mat imgMatB = cv::Mat((int)h, (int)w, CV_8UC1, imgBytes);
664             cv::Mat imgMatG = cv::Mat((int)h, (int)w, CV_8UC1, imgBytes + w * h);
665             cv::Mat imgMatR = cv::Mat((int)h, (int)w, CV_8UC1, imgBytes + 2 * w * h);
666             cv::Mat rgbMat  = cv::Mat((int)h, (int)w, CV_8UC3, reinterpret_cast<uint8_t*>(img.bits()));
667 
668             std::vector<cv::Mat> imgMat{ imgMatR, imgMatG, imgMatB };
669             cv::merge(imgMat, rgbMat);
670 
671             success = true;
672         }
673         break;
674 
675         case DRIF_FMT_BGRA8888:
676         {
677             cv::Mat imgMat = cv::Mat((int)h, (int)w, CV_8UC4, imgBytes);
678             cv::Mat rgbMat = cv::Mat((int)h, (int)w, CV_8UC3, reinterpret_cast<uint8_t*>(img.bits()));
679             cv::cvtColor(imgMat, rgbMat, CV_BGR2RGB, 3);
680 
681             success = true;
682         }
683         break;
684 
685         case DRIF_FMT_RGBA8888:
686         {
687             cv::Mat imgMat = cv::Mat((int)h, (int)w, CV_8UC4, imgBytes);
688             cv::Mat rgbMat = cv::Mat((int)h, (int)w, CV_8UC3, reinterpret_cast<uint8_t*>(img.bits()));
689             cv::cvtColor(imgMat, rgbMat, CV_RGBA2RGB, 3);
690 
691             success = true;
692         }
693         break;
694 
695         case DRIF_FMT_GRAY:
696         {
697             cv::Mat imgMat = cv::Mat((int)h, (int)w, CV_8UC1, imgBytes);
698             cv::Mat rgbMat = cv::Mat((int)h, (int)w, CV_8UC3, reinterpret_cast<uint8_t*>(img.bits()));
699             cv::cvtColor(imgMat, rgbMat, CV_GRAY2RGB);
700 
701             success = true;
702         }
703         break;
704 
705         case DRIF_FMT_YUV420P:
706         {
707             cv::Mat imgMat = cv::Mat((int)h + h / 2, (int)w, CV_8UC1, imgBytes);
708             cv::Mat rgbMat = cv::Mat((int)h, (int)w, CV_8UC3, reinterpret_cast<uint8_t*>(img.bits()));
709             cv::cvtColor(imgMat, rgbMat, CV_YUV2RGB_I420);
710 
711             success = true;
712         }
713         break;
714 
715         case DRIF_FMT_YVU420P:
716         {
717             cv::Mat imgMat = cv::Mat((int)h + h / 2, (int)w, CV_8UC1, imgBytes);
718             cv::Mat rgbMat = cv::Mat((int)h, (int)w, CV_8UC3, reinterpret_cast<uint8_t*>(img.bits()));
719             cv::cvtColor(imgMat, rgbMat, CV_YUV2RGB_YV12);
720 
721             success = true;
722         }
723         break;
724 
725         case DRIF_FMT_NV12:
726         {
727             cv::Mat imgMat = cv::Mat((int)h + h / 2, (int)w, CV_8UC1, imgBytes);
728             cv::Mat rgbMat = cv::Mat((int)h, (int)w, CV_8UC3, reinterpret_cast<uint8_t*>(img.bits()));
729             cv::cvtColor(imgMat, rgbMat, CV_YUV2RGB_NV12);
730 
731             success = true;
732         }
733         break;
734 
735         case DRIF_FMT_NV21:
736         {
737             cv::Mat imgMat = cv::Mat((int)h + h / 2, (int)w, CV_8UC1, imgBytes);
738             cv::Mat rgbMat = cv::Mat((int)h, (int)w, CV_8UC3, reinterpret_cast<uint8_t*>(img.bits()));
739             cv::cvtColor(imgMat, rgbMat, CV_YUV2RGB_NV21);
740 
741             success = true;
742         }
743         break;
744 
745         default:
746             success = false;
747             break;
748         }
749 
750     }
751 #endif
752 
753     drifFreeImg(imgBytes);
754 
755     return success;
756 }
757 
758 void DkBasicLoader::setImage(const QImage & img, const QString & editName, const QString & file) {
759 
760 	mFile = file;
761 	setEditImage(img, editName);
762 }
763 
764 void DkBasicLoader::setEditImage(const QImage& img, const QString& editName) {
765 
766 	if (img.isNull())
767 		return;
768 
769 	// delete all hidden edit states
770 	for (int idx = mImages.size() - 1; idx > mImageIndex; idx--) {
771 		mImages.pop_back();
772 	}
773 
774 	// compute new history size
775 	int historySize = 0;
776 	for (const DkEditImage& e : mImages) {
777 		historySize += e.size();
778 	}
779 
780 	DkEditImage newImg(img, editName);
781 
782 	if (historySize + newImg.size() > DkSettingsManager::param().resources().historyMemory && mImages.size() > mMinHistorySize) {
783 		mImages.removeAt(1);
784 		qDebug() << "removing history image because it's too large:" << historySize + newImg.size() << "MB";
785 	}
786 
787 	mImages.append(newImg);
788 	mImageIndex = mImages.size() - 1;	// set the index again to the last
789 }
790 
791 QImage DkBasicLoader::image() const {
792 
793 	if (mImages.empty())
794 		return QImage();
795 
796 	if (mImageIndex > mImages.size() || mImageIndex == -1) {
797 		qWarning() << "Illegal image index: " << mImageIndex;
798 		return mImages.last().image();
799 	}
800 
801 	return mImages[mImageIndex].image();
802 }
803 
804 void DkBasicLoader::undo() {
805 
806 	if (mImageIndex > 0)
807 		mImageIndex--;
808 }
809 
810 void DkBasicLoader::redo() {
811 
812 	if (mImageIndex < mImages.size()-1)
813 		mImageIndex++;
814 }
815 
816 QVector<DkEditImage>* DkBasicLoader::history() {
817 	return &mImages;
818 }
819 
820 DkEditImage DkBasicLoader::lastEdit() const {
821 
822 	assert(mImageIndex >= 0 && mImageIndex < mImages.size());
823 	return mImages[mImageIndex];
824 }
825 
826 int DkBasicLoader::historyIndex() const {
827 	return mImageIndex;
828 }
829 
830 void DkBasicLoader::setMinHistorySize(int size) {
831 	mMinHistorySize = size;
832 }
833 
834 void DkBasicLoader::setHistoryIndex(int idx) {
835 	mImageIndex = idx;
836 }
837 
838 void DkBasicLoader::loadFileToBuffer(const QString& filePath, QByteArray& ba) const {
839 
840 	QFileInfo fi(filePath);
841 
842 	if (!fi.exists())
843 		return;
844 
845 #ifdef WITH_QUAZIP
846 	if (fi.dir().path().contains(DkZipContainer::zipMarker()))
847 		DkZipContainer::extractImage(DkZipContainer::decodeZipFile(filePath), DkZipContainer::decodeImageFile(filePath), ba);
848 #endif
849 
850 	QFile file(filePath);
851 	file.open(QIODevice::ReadOnly);
852 
853 	ba = file.readAll();
854 }
855 
856 QSharedPointer<QByteArray> DkBasicLoader::loadFileToBuffer(const QString& filePath) const {
857 
858 	QFileInfo fi(filePath);
859 
860 #ifdef WITH_QUAZIP
861 	if (fi.dir().path().contains(DkZipContainer::zipMarker()))
862 		return DkZipContainer::extractImage(DkZipContainer::decodeZipFile(filePath), DkZipContainer::decodeImageFile(filePath));
863 #endif
864 
865 	QFile file(filePath);
866 	file.open(QIODevice::ReadOnly);
867 
868 	QSharedPointer<QByteArray> ba(new QByteArray(file.readAll()));
869 	file.close();
870 
871 	return ba;
872 }
873 
874 bool DkBasicLoader::writeBufferToFile(const QString& fileInfo, const QSharedPointer<QByteArray> ba) const {
875 
876 	if (!ba || ba->isEmpty())
877 		return false;
878 
879 	QFile file(fileInfo);
880 	file.open(QIODevice::WriteOnly);
881 	qint64 bytesWritten = file.write(*ba.data(), ba->size());
882 	file.close();
883 	qDebug() << "[DkBasicLoader] buffer saved, bytes written: " << bytesWritten;
884 
885 	if (!bytesWritten || bytesWritten == -1)
886 		return false;
887 
888 	return true;
889 }
890 
891 void DkBasicLoader::indexPages(const QString& filePath, const QSharedPointer<QByteArray> ba) {
892 
893 	// reset counters
894 	mNumPages = 1;
895 	mPageIdx = 1;
896 
897 #ifdef WITH_LIBTIFF
898 
899 	QFileInfo fInfo(filePath);
900 
901 	// for now we just support tiff's
902 	if (!fInfo.suffix().contains(QRegExp("(tif|tiff)", Qt::CaseInsensitive)))
903 		return;
904 
905 	// first turn off nasty warning/error dialogs - (we do the GUI : )
906 	TIFFErrorHandler oldErrorHandler, oldWarningHandler;
907 	oldWarningHandler = TIFFSetWarningHandler(NULL);
908 	oldErrorHandler = TIFFSetErrorHandler(NULL);
909 
910 	DkTimer dt;
911 	TIFF* tiff = 0;
912 
913 #if QT_VERSION >= QT_VERSION_CHECK(5,5,0) && defined(Q_OS_WIN)
914 	std::istringstream is(ba ? ba->toStdString() : "");
915 
916 	if (ba)
917 		tiff = TIFFStreamOpen("MemTIFF", &is);
918 
919 	// read from file
920 	if (!tiff)
921 		tiff = TIFFOpen(filePath.toLatin1(), "r");	// this->mFile was here before - not sure why
922 
923 	// loading from buffer allows us to load files with non-latin names
924 	QSharedPointer<QByteArray> bal;
925 	if (!tiff)
926 		bal = loadFileToBuffer(filePath);;
927 	std::istringstream isl(bal ? bal->toStdString() : "");
928 
929 	if (bal)
930 		tiff = TIFFStreamOpen("MemTIFF", &isl);
931 #else
932 	// read from file
933 	tiff = TIFFOpen(filePath.toLatin1(), "r");	// this->mFile was here before - not sure why
934 #endif
935 
936 	if (!tiff)
937 		return;
938 
939 	// libtiff example
940 	int dircount = 0;
941 
942 	do {
943 		dircount++;
944 
945 	} while (TIFFReadDirectory(tiff));
946 
947 	mNumPages = dircount;
948 
949 	if (mNumPages > 1)
950 		mPageIdx = 1;
951 
952 	qDebug() << dircount << " TIFF directories... " << dt;
953 	TIFFClose(tiff);
954 
955 	TIFFSetWarningHandler(oldWarningHandler);
956 	TIFFSetWarningHandler(oldErrorHandler);
957 #else
958 	Q_UNUSED(filePath);
959 #endif
960 
961 }
962 
963 bool DkBasicLoader::loadPage(int skipIdx) {
964 
965 	bool imgLoaded = false;
966 
967 	mPageIdx += skipIdx;
968 
969 	// <= 1 since first page is loaded using qt
970 	if (mPageIdx > mNumPages || mPageIdx <= 1)
971 		return imgLoaded;
972 
973 	return loadPageAt(mPageIdx);
974 }
975 
976 bool DkBasicLoader::loadPageAt(int pageIdx) {
977 
978 	bool imgLoaded = false;
979 
980 #ifdef WITH_LIBTIFF
981 
982 	// <= 1 since first page is loaded using qt
983 	if (pageIdx > mNumPages || pageIdx < 1)
984 		return imgLoaded;
985 
986 	// first turn off nasty warning/error dialogs - (we do the GUI : )
987 	TIFFErrorHandler oldErrorHandler, oldWarningHandler;
988 	oldWarningHandler = TIFFSetWarningHandler(NULL);
989 	oldErrorHandler = TIFFSetErrorHandler(NULL);
990 
991 	DkTimer dt;
992 	TIFF* tiff = TIFFOpen(mFile.toLatin1(), "r");
993 
994 #if QT_VERSION >= QT_VERSION_CHECK(5,5,0) && defined(Q_OS_WIN)
995 
996 	// loading from buffer allows us to load files with non-latin names
997 	QSharedPointer<QByteArray> ba;
998 	if (!tiff)
999 		ba = loadFileToBuffer(mFile);
1000 
1001 	std::istringstream is(ba ? ba->toStdString() : "");
1002 	if (ba)
1003 		tiff = TIFFStreamOpen("MemTIFF", &is);
1004 #endif
1005 
1006 	if (!tiff)
1007 		return imgLoaded;
1008 
1009 	uint32 width = 0;
1010 	uint32 height = 0;
1011 
1012 	// go to current directory
1013 	for (int idx = 1; idx < pageIdx; idx++) {
1014 
1015 		if (!TIFFReadDirectory(tiff))
1016 			return false;
1017 	}
1018 	TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width);
1019 	TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height);
1020 
1021 	// init the qImage
1022 	QImage img = QImage(width, height, QImage::Format_ARGB32);
1023 
1024 	const int stopOnError = 1;
1025 	imgLoaded = TIFFReadRGBAImageOriented(tiff, width, height, reinterpret_cast<uint32 *>(img.bits()), ORIENTATION_TOPLEFT, stopOnError) != 0;
1026 
1027 	if (imgLoaded) {
1028 		for (uint32 y=0; y<height; ++y)
1029 			convert32BitOrder(img.scanLine(y), width);
1030 	}
1031 
1032 	TIFFClose(tiff);
1033 
1034 	TIFFSetWarningHandler(oldWarningHandler);
1035 	TIFFSetWarningHandler(oldErrorHandler);
1036 
1037 	setEditImage(img, tr("Original Image"));
1038 #else
1039 	Q_UNUSED(pageIdx);
1040 #endif
1041 
1042 
1043 	return imgLoaded;
1044 }
1045 
1046 bool DkBasicLoader::setPageIdx(int skipIdx) {
1047 
1048 	// do nothing if we don't have tiff pages
1049 	if (mNumPages <= 1)
1050 		return false;
1051 
1052 	mPageIdxDirty = false;
1053 
1054 	int newPageIdx = mPageIdx + skipIdx;
1055 
1056 	if (newPageIdx > 0 && newPageIdx <= mNumPages) {
1057 		mPageIdxDirty = true;
1058 		mPageIdx = newPageIdx;
1059 	}
1060 
1061 	return mPageIdxDirty;
1062 }
1063 
1064 void DkBasicLoader::resetPageIdx() {
1065 
1066 	mPageIdxDirty = false;
1067 	mPageIdx = 1;
1068 }
1069 
1070 void DkBasicLoader::convert32BitOrder(void *buffer, int width) const {
1071 
1072 #ifdef WITH_LIBTIFF
1073 	// code from Qt QTiffHandler
1074 	uint32 *target = reinterpret_cast<uint32 *>(buffer);
1075 	for (int32 x=0; x<width; ++x) {
1076 		uint32 p = target[x];
1077 		// convert between ARGB and ABGR
1078 		target[x] = (p & 0xff000000)
1079 			| ((p & 0x00ff0000) >> 16)
1080 			| (p & 0x0000ff00)
1081 			| ((p & 0x000000ff) << 16);
1082 	}
1083 #else
1084 	Q_UNUSED(buffer);
1085 	Q_UNUSED(width);
1086 #endif
1087 }
1088 
1089 QString DkBasicLoader::save(const QString& filePath, const QImage& img, int compression) {
1090 
1091 	QSharedPointer<QByteArray> ba;
1092 
1093 	DkTimer dt;
1094 	if (saveToBuffer(filePath, img, ba, compression) && ba) {
1095 
1096 		if (writeBufferToFile(filePath, ba)) {
1097 			qDebug() << "saving to" << filePath << "in" << dt;
1098 			return filePath;
1099 		}
1100 	}
1101 
1102 	return QString();
1103 }
1104 
1105 bool DkBasicLoader::saveToBuffer(const QString& filePath, const QImage& img, QSharedPointer<QByteArray>& ba, int compression) const {
1106 
1107 	bool bufferCreated = false;
1108 
1109 	if (!ba) {
1110 		ba = QSharedPointer<QByteArray>(new QByteArray());
1111 		bufferCreated = true;
1112 	}
1113 
1114 	bool saved = false;
1115 
1116 	QFileInfo fInfo(filePath);
1117 
1118 	if (fInfo.suffix().contains("ico", Qt::CaseInsensitive)) {
1119 		saved = saveWindowsIcon(img, ba);
1120 	}
1121 #if QT_VERSION < 0x050000 // qt5 natively supports r/w webp
1122 	else if (fInfo.suffix().contains("webp", Qt::CaseInsensitive)) {
1123 		saved = saveWebPFile(img, ba, compression);
1124 	}
1125 #endif
1126 	else {
1127 
1128 		bool hasAlpha = DkImage::alphaChannelUsed(img);
1129 		QImage sImg = img;
1130 
1131 
1132 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1133 		// JPEG 2000 can only handle 32 or 8bit images
1134 		if (!hasAlpha && img.colorTable().empty() && !fInfo.suffix().contains(QRegExp("(j2k|jp2|jpf|jpx|png)"))) {
1135 			sImg = sImg.convertToFormat(QImage::Format_RGB888);
1136 		}
1137 		else if (fInfo.suffix().contains(QRegExp("(j2k|jp2|jpf|jpx)")) && sImg.depth() != 32 && sImg.depth() != 8)
1138 			sImg = sImg.convertToFormat(QImage::Format_RGB32);
1139 #endif
1140 
1141 		if (fInfo.suffix().contains(QRegExp("(png)")))
1142 			compression = -1;
1143 
1144 		QBuffer fileBuffer(ba.data());
1145 		//size_t s = fileBuffer.size();
1146 		fileBuffer.open(QIODevice::WriteOnly);
1147 		QImageWriter* imgWriter = new QImageWriter(&fileBuffer, fInfo.suffix().toStdString().c_str());
1148 
1149 		if (compression >= 0) {	// -1 -> use Qt's default
1150 			imgWriter->setCompression(compression);
1151 			imgWriter->setQuality(compression);
1152 		}
1153 		if (compression == -1 && imgWriter->format() == "jpg") {
1154 			imgWriter->setQuality(DkSettingsManager::instance().settings().app().defaultJpgQuality);
1155 		}
1156 
1157 #if QT_VERSION >= 0x050500
1158 		imgWriter->setOptimizedWrite(true);			// this saves space TODO: user option here?
1159 		imgWriter->setProgressiveScanWrite(true);
1160 #endif
1161 		saved = imgWriter->write(sImg);
1162 		delete imgWriter;
1163 	}
1164 
1165 	if (saved && mMetaData) {
1166 
1167 		if (!mMetaData->isLoaded() || !mMetaData->hasMetaData()) {
1168 
1169 			if (!bufferCreated)
1170 				mMetaData->readMetaData(filePath, ba);
1171 			else
1172 				// if we created the buffere here - force loading metadata from the file
1173 				mMetaData->readMetaData(filePath);
1174 			}
1175 
1176 			if (mMetaData->isLoaded()) {
1177 
1178 				try {
1179 					// be careful: here we actually lie about the constness
1180 					mMetaData->updateImageMetaData(img);
1181 					if (!mMetaData->saveMetaData(ba, true))
1182 						mMetaData->clearExifState();
1183 				}
1184 				catch (...) {
1185 					// is it still throwing anything?
1186 					qInfo() << "Sorry, I could not save the meta data...";
1187 					// clear exif state here -> the 'dirty' flag would otherwise edit the original image (see #514)
1188 					mMetaData->clearExifState();
1189 				}
1190 			}
1191 	}
1192 
1193 	if (!saved)
1194 		emit errorDialogSignal(tr("Sorry, I could not save: %1").arg(fInfo.fileName()));
1195 
1196 	return saved;
1197 }
1198 
1199 void DkBasicLoader::saveThumbToMetaData(const QString& filePath) {
1200 
1201 	QSharedPointer<QByteArray> ba;	// dummy
1202 	saveThumbToMetaData(filePath, ba);
1203 }
1204 
1205 void DkBasicLoader::saveMetaData(const QString& filePath) {
1206 
1207 	QSharedPointer<QByteArray> ba;	// dummy
1208 	saveMetaData(filePath, ba);
1209 }
1210 
1211 void DkBasicLoader::saveThumbToMetaData(const QString& filePath, QSharedPointer<QByteArray>& ba) {
1212 
1213 	if (!hasImage())
1214 		return;
1215 
1216 	mMetaData->setThumbnail(DkImage::createThumb(image()));
1217 	saveMetaData(filePath, ba);
1218 }
1219 
1220 void DkBasicLoader::saveMetaData(const QString& filePath, QSharedPointer<QByteArray>& ba) {
1221 
1222 	if (!ba)
1223 		ba = QSharedPointer<QByteArray>(new QByteArray());
1224 
1225 	if (ba->isEmpty() && mMetaData->isDirty()) {
1226 		ba = loadFileToBuffer(filePath);
1227 	}
1228 
1229 	bool saved = false;
1230 	try {
1231 		saved = mMetaData->saveMetaData(ba);
1232 	}
1233 	catch(...) {
1234 		qInfo() << "could not save metadata...";
1235 	}
1236 
1237 	if (saved)
1238 		writeBufferToFile(filePath, ba);
1239 
1240 }
1241 
1242 bool DkBasicLoader::isContainer(const QString& filePath) {
1243 
1244 	QFileInfo fInfo(filePath);
1245 	if (!fInfo.isFile() || !fInfo.exists())
1246 		return false;
1247 
1248 	QString suffix = fInfo.suffix();
1249 
1250 	if (suffix.isEmpty())
1251 		return false;
1252 
1253 	for (int idx = 0; idx < DkSettingsManager::param().app().containerFilters.size(); idx++) {
1254 
1255 		if (DkSettingsManager::param().app().containerFilters[idx].contains(suffix))
1256 			return true;
1257 	}
1258 
1259 	return false;
1260 }
1261 
1262 // image editing --------------------------------------------------------------------
1263 /**
1264  * This method rotates an image.
1265  * @param orientation the orientation in degree.
1266  **/
1267 QImage DkBasicLoader::rotate(const QImage& img, int orientation) {
1268 
1269 	if (orientation == 0 || orientation == -1)
1270 		return img;
1271 
1272 	QTransform rotationMatrix;
1273 	rotationMatrix.rotate((double)orientation);
1274 	QImage rImg = img.transformed(rotationMatrix);
1275 
1276 	return rImg;
1277 }
1278 
1279 /**
1280  * Releases the currently loaded images.
1281  **/
1282 void DkBasicLoader::release(bool clear) {
1283 
1284 	// TODO: auto save routines here?
1285 	//qDebug() << file.fileName() << " released...";
1286 	saveMetaData(mFile);
1287 
1288 	mImages.clear();
1289 	//metaData.clear();
1290 
1291 	// TODO: where should we clear the metadata?
1292 	if (clear || !mMetaData->isDirty())
1293 		mMetaData = QSharedPointer<DkMetaDataT>(new DkMetaDataT());
1294 
1295 }
1296 
1297 #ifdef Q_OS_WIN
1298 bool DkBasicLoader::saveWindowsIcon(const QString& filePath, const QImage& img) const {
1299 
1300 	QSharedPointer<QByteArray> ba;
1301 
1302 	if (saveWindowsIcon(img, ba) && ba && !ba->isEmpty()) {
1303 
1304 		writeBufferToFile(filePath, ba);
1305 		return true;
1306 	}
1307 
1308 	return false;
1309 }
1310 
1311 struct ICONDIRENTRY
1312 {
1313 	UCHAR nWidth;
1314 	UCHAR nHeight;
1315 	UCHAR nNumColorsInPalette; // 0 if no palette
1316 	UCHAR nReserved; // should be 0
1317 	WORD nNumColorPlanes; // 0 or 1
1318 	WORD nBitsPerPixel;
1319 	ULONG nDataLength; // length in bytes
1320 	ULONG nOffset; // offset of BMP or PNG data from beginning of file
1321 };
1322 
1323 bool DkBasicLoader::saveWindowsIcon(const QImage& img, QSharedPointer<QByteArray>& ba) const {
1324 
1325 	// this code is an adopted version of:
1326 	// http://stackoverflow.com/questions/2289894/how-can-i-save-hicon-to-an-ico-file
1327 
1328 	if (!ba)
1329 		ba = QSharedPointer<QByteArray>(new QByteArray());
1330 
1331 	HICON hIcon = QtWin::toHICON(QPixmap::fromImage(img));
1332 	int nColorBits = 32;
1333 
1334 	QBuffer buffer(ba.data());
1335 	buffer.open(QIODevice::WriteOnly);
1336 
1337 	if (!hIcon)
1338 		return false;
1339 
1340 	HDC screenDevice = GetDC(0);
1341 
1342 	// Write header:
1343 	UCHAR icoHeader[6] = { 0, 0, 1, 0, 1, 0 }; // ICO file with 1 image
1344 	buffer.write((const char*)(&icoHeader), sizeof(icoHeader));
1345 
1346 	// Get information about icon:
1347 	ICONINFO iconInfo;
1348 	GetIconInfo(hIcon, &iconInfo);
1349 	HGDIOBJ handle1(iconInfo.hbmColor); // free bitmaps when function ends
1350 	BITMAPINFO bmInfo = { 0 };
1351 	bmInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
1352 	bmInfo.bmiHeader.biBitCount = 0;    // don't get the color table
1353 	if (!GetDIBits(screenDevice, iconInfo.hbmColor, 0, 0, NULL, &bmInfo, DIB_RGB_COLORS))
1354 	{
1355 		return false;
1356 	}
1357 
1358 	// Allocate size of bitmap info header plus space for color table:
1359 	int nBmInfoSize = sizeof(BITMAPINFOHEADER);
1360 	if (nColorBits < 24)
1361 	{
1362 		nBmInfoSize += sizeof(RGBQUAD) * (int)(1 << nColorBits);
1363 	}
1364 
1365 	QSharedPointer<UCHAR> bitmapInfo(new UCHAR[nBmInfoSize]);
1366 	BITMAPINFO* pBmInfo = (BITMAPINFO*)bitmapInfo.data();
1367 	memcpy(pBmInfo, &bmInfo, sizeof(BITMAPINFOHEADER));
1368 
1369 	// Get bitmap data:
1370 	QSharedPointer<UCHAR> bits(new UCHAR[bmInfo.bmiHeader.biSizeImage]);
1371 	pBmInfo->bmiHeader.biBitCount = (WORD)nColorBits;
1372 	pBmInfo->bmiHeader.biCompression = BI_RGB;
1373 	if (!GetDIBits(screenDevice, iconInfo.hbmColor, 0, bmInfo.bmiHeader.biHeight, bits.data(), pBmInfo, DIB_RGB_COLORS))
1374 	{
1375 		return false;
1376 	}
1377 
1378 	// Get mask data:
1379 	BITMAPINFO maskInfo = { 0 };
1380 	maskInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
1381 	maskInfo.bmiHeader.biBitCount = 0;  // don't get the color table
1382 	if (!GetDIBits(screenDevice, iconInfo.hbmMask, 0, 0, NULL, &maskInfo, DIB_RGB_COLORS))
1383 	{
1384 		return false;
1385 	}
1386 
1387 	QSharedPointer<UCHAR> maskBits(new UCHAR[maskInfo.bmiHeader.biSizeImage]);
1388 	QSharedPointer<UCHAR> maskInfoBytes(new UCHAR[sizeof(BITMAPINFO) + 2 * sizeof(RGBQUAD)]);
1389 	BITMAPINFO* pMaskInfo = (BITMAPINFO*)maskInfoBytes.data();
1390 	memcpy(pMaskInfo, &maskInfo, sizeof(maskInfo));
1391 	if (!GetDIBits(screenDevice, iconInfo.hbmMask, 0, maskInfo.bmiHeader.biHeight, maskBits.data(), pMaskInfo, DIB_RGB_COLORS))
1392 	{
1393 		return false;
1394 	}
1395 
1396 	// Write directory entry:
1397 	ICONDIRENTRY dir;
1398 	dir.nWidth = (UCHAR)pBmInfo->bmiHeader.biWidth;
1399 	dir.nHeight = (UCHAR)pBmInfo->bmiHeader.biHeight;
1400 	dir.nNumColorsInPalette = (nColorBits == 4 ? 16 : 0);
1401 	dir.nReserved = 0;
1402 	dir.nNumColorPlanes = 0;
1403 	dir.nBitsPerPixel = pBmInfo->bmiHeader.biBitCount;
1404 	dir.nDataLength = pBmInfo->bmiHeader.biSizeImage + pMaskInfo->bmiHeader.biSizeImage + nBmInfoSize;
1405 	dir.nOffset = sizeof(dir) + sizeof(icoHeader);
1406 	buffer.write((const char*)&dir, sizeof(dir));
1407 
1408 	// Write DIB header (including color table):
1409 	int nBitsSize = pBmInfo->bmiHeader.biSizeImage;
1410 	pBmInfo->bmiHeader.biHeight *= 2; // because the header is for both image and mask
1411 	pBmInfo->bmiHeader.biCompression = 0;
1412 	pBmInfo->bmiHeader.biSizeImage += pMaskInfo->bmiHeader.biSizeImage; // because the header is for both image and mask
1413 	buffer.write((const char*)&pBmInfo->bmiHeader, nBmInfoSize);
1414 
1415 	// Write image data:
1416 	buffer.write((const char*)bits.data(), nBitsSize);
1417 
1418 	// Write mask data:
1419 	buffer.write((const char*)maskBits.data(), pMaskInfo->bmiHeader.biSizeImage);
1420 
1421 	buffer.close();
1422 
1423 	DeleteObject(handle1);
1424 
1425 	return true;
1426 }
1427 
1428 #endif // #ifdef Q_OS_WIN
1429 
1430 #ifdef WITH_OPENCV
1431 
1432 cv::Mat DkBasicLoader::getImageCv() {
1433 	return cv::Mat();
1434 }
1435 
1436 bool DkBasicLoader::loadOpenCVVecFile(const QString& filePath, QImage& img, QSharedPointer<QByteArray> ba, QSize s) const {
1437 
1438 	if (!ba)
1439 		ba = QSharedPointer<QByteArray>(new QByteArray());
1440 
1441 	// load from file?
1442 	if (ba->isEmpty())
1443 		ba = loadFileToBuffer(filePath);
1444 
1445 	if (ba->isEmpty())
1446 		return false;
1447 
1448 	// read header & get a pointer to the first image
1449 	int fileCount, vecSize;
1450 	const unsigned char* imgPtr = (const unsigned char*)ba->constData();
1451 	if (!readHeader(&imgPtr, fileCount, vecSize))
1452 		return false;
1453 
1454 	int guessedW = 0;
1455 	int guessedH = 0;
1456 
1457 	getPatchSizeFromFileName(QFileInfo(filePath).fileName(), guessedW, guessedH);
1458 
1459 	qDebug() << "patch size from filename: " << guessedW << " x " << guessedH;
1460 
1461 	if(vecSize > 0 && !guessedH && !guessedW) {
1462 		guessedW = qFloor(sqrt((float) vecSize));
1463 		if(guessedW > 0)
1464 			guessedH = vecSize/guessedW;
1465 	}
1466 
1467 	if(guessedW <= 0 || guessedH <= 0 || guessedW * guessedH != vecSize) {
1468 
1469 		// TODO: ask user
1470 		qDebug() << "dimensions do not match, patch size: " << guessedW << " x " << guessedH << " vecSize: " << vecSize;
1471 		return false;
1472 	}
1473 
1474 	int fSize = ba->size();
1475 	int numElements = 0;
1476 
1477 	// guess size
1478 	if (s.isEmpty()) {
1479 		double nEl = (fSize-64)/(vecSize*2);
1480 		nEl = (fSize-64-qCeil(nEl))/(vecSize*2)+1;	// opencv adds one byte per image - so we take care for this here
1481 
1482 		if (qFloor(nEl) != qCeil(nEl))
1483 			return false;
1484 		numElements = qRound(nEl);
1485 	}
1486 
1487 	double nRowsCols = sqrt(numElements);
1488 	int numCols = qCeil(nRowsCols);
1489 	int minusOneRow = (qFloor(nRowsCols) != qCeil(nRowsCols) && nRowsCols - qFloor(nRowsCols) < 0.5) ? 1 : 0;
1490 
1491 	cv::Mat allPatches((numCols-minusOneRow)*guessedH, numCols*guessedW, CV_8UC1, cv::Scalar(125));
1492 
1493 	for (int idx = 0; idx < numElements; idx++) {
1494 
1495 		if (*imgPtr != 0) {
1496 			qDebug() << "skipping non-empty byte - there is something seriously wrong here!";
1497 			//return false;	// stop if the byte is non-empty -> otherwise we might read wrong memory
1498 		}
1499 
1500 		imgPtr++;	// there is an empty byte between images
1501 		cv::Mat cPatch = getPatch(&imgPtr, QSize(guessedW, guessedH));
1502 		cv::Mat cPatchAll = allPatches(cv::Rect(idx%numCols*guessedW, qFloor(idx/numCols)*guessedH, guessedW, guessedH));
1503 
1504 		if (!cPatchAll.empty())
1505 			cPatch.copyTo(cPatchAll);
1506 	}
1507 
1508 	img = DkImage::mat2QImage(allPatches);
1509 	img = img.convertToFormat(QImage::Format_ARGB32);
1510 
1511 	//setEditImage(img, tr("Original Image"));
1512 
1513 	return true;
1514 }
1515 
1516 void DkBasicLoader::getPatchSizeFromFileName(const QString& fileName, int& width, int& height) const {
1517 
1518 	// parse patch size from file
1519 	QStringList sections = fileName.split(QRegExp("[-\\.]"));
1520 
1521 	for (int idx = 0; idx < sections.size(); idx++) {
1522 
1523 		QString tmpSec = sections[idx];
1524 		qDebug() << "section: " << tmpSec;
1525 
1526 		if (tmpSec.contains("w"))
1527 			width = tmpSec.remove("w").toInt();
1528 		else if (tmpSec.contains("h"))
1529 			height = tmpSec.remove("h").toInt();
1530 	}
1531 
1532 }
1533 
1534 bool DkBasicLoader::readHeader(const unsigned char** dataPtr, int& fileCount, int& vecSize) const {
1535 
1536 	const int* pData = (const int*)*dataPtr;
1537 	fileCount = *pData; pData++;	// read file count
1538 	vecSize = *pData;				// read vec size
1539 
1540 	qDebug() << "vec size: " << vecSize << " fileCount " << fileCount;
1541 
1542 	*dataPtr += 12;	// skip the first 12 (header) bytes
1543 
1544 	return true;
1545 }
1546 
1547 // the double pointer is here needed to additionally increase the pointer value
1548 cv::Mat DkBasicLoader::getPatch(const unsigned char** dataPtr, QSize patchSize) const {
1549 
1550 	cv::Mat img8U(patchSize.height(), patchSize.width(), CV_8UC1, cv::Scalar(0));
1551 
1552 	// ok, take just the second byte
1553 	for (int rIdx = 0; rIdx < img8U.rows; rIdx++) {
1554 
1555 		unsigned char* ptr8U = img8U.ptr<unsigned char>(rIdx);
1556 
1557 		for (int cIdx = 0; cIdx < img8U.cols; cIdx++) {
1558 			ptr8U[cIdx] = **dataPtr;
1559 			*dataPtr += 2;	// it is strange: opencv stores vec files as 16 bit but just use the 2nd byte
1560 		}
1561 	}
1562 
1563 	return img8U;
1564 }
1565 
1566 int DkBasicLoader::mergeVecFiles(const QStringList& vecFilePaths, QString& saveFilePath) const {
1567 
1568 	int lastVecSize = 0;
1569 	int totalFileCount = 0;
1570 	int vecCount = 0;
1571 	int pWidth = 0, pHeight = 0;
1572 	QByteArray vecBuffer;
1573 
1574 	for (const QString& filePath : vecFilePaths) {
1575 
1576 		QFileInfo fInfo(filePath);
1577 		QSharedPointer<QByteArray> ba = loadFileToBuffer(filePath);
1578 		if (ba->isEmpty()){
1579 			qDebug() << "could not load: " << fInfo.fileName();
1580 			continue;
1581 		}
1582 
1583 		int fileCount, vecSize;
1584 		const unsigned char* dataPtr = (const unsigned char*)ba->constData();
1585 		if (!readHeader(&dataPtr, fileCount, vecSize)) {
1586 			qDebug() << "could not read header, skipping: " << fInfo.fileName();
1587 			continue;
1588 		}
1589 
1590 		if (lastVecSize && vecSize != lastVecSize) {
1591 			qDebug() << "wrong vec size, skipping: " << fInfo.fileName();
1592 			continue;
1593 		}
1594 
1595 		vecBuffer.append((const char*)dataPtr, vecSize*fileCount*2+fileCount);	// +fileCount accounts for the '\0' bytes between the patches
1596 
1597 		getPatchSizeFromFileName(fInfo.fileName(), pWidth, pHeight);
1598 
1599 		totalFileCount += fileCount;
1600 		lastVecSize = vecSize;
1601 
1602 		vecCount++;
1603 	}
1604 
1605 	// don't save if we could not merge the files
1606 	if (!vecCount)
1607 		return vecCount;
1608 
1609 	unsigned int* header = new unsigned int[3];
1610 	header[0] = totalFileCount;
1611 	header[1] = lastVecSize;
1612 	header[2] = 0;
1613 
1614 	vecBuffer.prepend((const char*) header, 3*sizeof(int));
1615 
1616 	QFileInfo saveFileInfo(saveFilePath);
1617 
1618 	// append width, height if we don't know
1619 	if (pWidth && pHeight) {
1620 		QString whString = "-w" + QString::number(pWidth) + "-h" + QString::number(pHeight);
1621 		saveFileInfo = QFileInfo(saveFileInfo.absolutePath(), saveFileInfo.baseName() + whString + "." + saveFileInfo.suffix());
1622 	}
1623 
1624 	QFile file(saveFileInfo.absoluteFilePath());
1625 	file.open(QIODevice::WriteOnly);
1626 	file.write(vecBuffer);
1627 	file.close();
1628 
1629 	return vecCount;
1630 }
1631 
1632 #endif // #ifdef WITH_OPENCV
1633 
1634 // FileDownloader --------------------------------------------------------------------
1635 FileDownloader::FileDownloader(const QUrl& imageUrl, const QString& filePath, QObject *parent) : QObject(parent) {
1636 
1637 	mFilePath = filePath;
1638 
1639 	QNetworkProxyQuery npq(QUrl("https://google.com"));
1640 	QList<QNetworkProxy> listOfProxies = QNetworkProxyFactory::systemProxyForQuery(npq);
1641 	if (!listOfProxies.empty() && listOfProxies[0].hostName() != "") {
1642 		mWebCtrl.setProxy(listOfProxies[0]);
1643 	}
1644 
1645 	connect(&mWebCtrl, SIGNAL(finished(QNetworkReply*)),
1646 		SLOT(fileDownloaded(QNetworkReply*)));
1647 
1648 	downloadFile(imageUrl);
1649 }
1650 
1651 FileDownloader::~FileDownloader() {
1652 }
1653 
1654 void FileDownloader::downloadFile(const QUrl& url) {
1655 
1656 	QNetworkRequest request(url);
1657 	mWebCtrl.get(request);
1658 	mUrl = url;
1659 }
1660 
1661 void FileDownloader::saved() {
1662 
1663 	if (mSaveWatcher.result()) {
1664 		qInfo() << "downloaded image saved to" << mFilePath;
1665 		emit downloaded(mFilePath);
1666 	}
1667 	else {
1668 		qWarning() << "could not download file to " << mFilePath;
1669 	}
1670 }
1671 
1672 bool FileDownloader::save(const QString& filePath, const QSharedPointer<QByteArray> data) {
1673 
1674 	if (!data) {
1675 		qWarning() << "cannot save file if data is NULL";
1676 		return false;
1677 	}
1678 
1679 	QFileInfo fi(filePath);
1680 
1681 	if (!fi.absoluteDir().exists())
1682 		QDir().mkpath(fi.absolutePath());
1683 
1684 	QFile f(filePath);
1685 	f.open(QIODevice::WriteOnly);
1686 
1687 	return f.write(*data);
1688 }
1689 
1690 void FileDownloader::fileDownloaded(QNetworkReply* pReply) {
1691 
1692 	if (pReply->error() != QNetworkReply::NoError) {
1693 		qWarning() << "I could not download: " << mUrl;
1694 		qWarning() << pReply->errorString();
1695 	}
1696 
1697 	mDownloadedData = QSharedPointer<QByteArray>(new QByteArray(pReply->readAll()));
1698 	//emit a signal
1699 	pReply->deleteLater();
1700 
1701 	// data only requested
1702 	if (mFilePath.isEmpty()) {
1703 		emit downloaded();
1704 	}
1705 	// ok save it
1706 	else {
1707 		connect(&mSaveWatcher, SIGNAL(finished()), this, SLOT(saved()), Qt::UniqueConnection);
1708 		mSaveWatcher.setFuture(QtConcurrent::run(&nmc::FileDownloader::save, mFilePath, mDownloadedData));
1709 	}
1710 }
1711 
1712 QSharedPointer<QByteArray> FileDownloader::downloadedData() const {
1713 	return mDownloadedData;
1714 }
1715 
1716 QUrl FileDownloader::getUrl() const {
1717 	return mUrl;
1718 }
1719 
1720 #ifdef WITH_QUAZIP
1721 
1722 // DkZipContainer --------------------------------------------------------------------
1723 DkZipContainer::DkZipContainer(const QString& encodedFilePath) {
1724 
1725 	if (!encodedFilePath.isEmpty() &&
1726 		encodedFilePath.contains(mZipMarker)) {
1727 		mImageInZip = true;
1728 		mEncodedFilePath = encodedFilePath;
1729 		mZipFilePath = decodeZipFile(encodedFilePath);
1730 		mImageFileName = decodeImageFile(encodedFilePath);
1731 	}
1732 	else
1733 		mImageInZip = false;
1734 }
1735 
1736 QString DkZipContainer::encodeZipFile(const QString& zipFile, const QString& imageFile) {
1737 
1738 	// if you think this code is unreadable, take a look at the old line:
1739 	//return QFileInfo(QDir(zipFile.absoluteFilePath() + mZipMarker + imageFile.left(imageFile.lastIndexOf("/") + 1).replace("/", mZipMarker)),(imageFile.lastIndexOf("/") < 0) ? imageFile : imageFile.right(imageFile.size() - imageFile.lastIndexOf("/") - 1));
1740 
1741 	QDir dir = QDir(zipFile + mZipMarker + imageFile.left(imageFile.lastIndexOf("/") + 1).replace("/", mZipMarker));
1742 	QString fileName = (imageFile.lastIndexOf("/") < 0) ? imageFile : imageFile.right(imageFile.size() - imageFile.lastIndexOf("/") - 1);
1743 
1744 	return QFileInfo(dir, fileName).absoluteFilePath();
1745 }
1746 
1747 QString DkZipContainer::decodeZipFile(const QString& encodedFileInfo) {
1748 
1749 	QString encodedDir = QFileInfo(encodedFileInfo).absolutePath();
1750 
1751 	return encodedDir.left(encodedDir.indexOf(mZipMarker));
1752 }
1753 
1754 QString DkZipContainer::decodeImageFile(const QString& encodedFileInfo) {
1755 
1756 	// get relative zip path
1757 	QString tmp = encodedFileInfo.right(encodedFileInfo.size() - encodedFileInfo.indexOf(mZipMarker) - QString(mZipMarker).size());
1758 	tmp = tmp.replace(mZipMarker, "/");
1759 	tmp = tmp.replace("//", "/");
1760 
1761 	// diem: this fixes an issue with images that are in a zip's root folder
1762 	if (tmp.startsWith("/"))
1763 		tmp = tmp.right(tmp.length()-1);
1764 
1765 	return tmp;
1766 }
1767 
1768 QSharedPointer<QByteArray> DkZipContainer::extractImage(const QString& zipFile, const QString& imageFile) {
1769 
1770 	QuaZip zip(zipFile);
1771 	if(!zip.open(QuaZip::mdUnzip))
1772 		return QSharedPointer<QByteArray>(new QByteArray());
1773 
1774 	qDebug() << "DkZip::extractImage filePath: " << zipFile;
1775 	qDebug() << "3.0 image file" << imageFile;
1776 
1777 	zip.setCurrentFile(imageFile);
1778 	QuaZipFile extractedFile(&zip);
1779 	if(!extractedFile.open(QIODevice::ReadOnly) || extractedFile.getZipError() != UNZ_OK)
1780 		return QSharedPointer<QByteArray>(new QByteArray());
1781 
1782 	QSharedPointer<QByteArray> ba(new QByteArray(extractedFile.readAll()));
1783 	extractedFile.close();
1784 
1785 	zip.close();
1786 
1787 	return ba;
1788 }
1789 
1790 void DkZipContainer::extractImage(const QString& zipFile, const QString& imageFile, QByteArray& ba) {
1791 
1792 	QuaZip zip(zipFile);
1793 	if(!zip.open(QuaZip::mdUnzip))
1794 		return;
1795 
1796 	zip.setCurrentFile(imageFile);
1797 	QuaZipFile extractedFile(&zip);
1798 	if(!extractedFile.open(QIODevice::ReadOnly) || extractedFile.getZipError() != UNZ_OK)
1799 		return;
1800 
1801 	ba = QByteArray(extractedFile.readAll());
1802 	extractedFile.close();
1803 
1804 	zip.close();
1805 
1806 }
1807 
1808 bool DkZipContainer::isZip() const {
1809 
1810 	return mImageInZip;
1811 }
1812 
1813 QString DkZipContainer::getZipFilePath() const {
1814 
1815 	return mZipFilePath;
1816 }
1817 
1818 QString DkZipContainer::getImageFileName() const {
1819 
1820 	return mImageFileName;
1821 }
1822 
1823 QString DkZipContainer::getEncodedFilePath() const {
1824 
1825 	return mEncodedFilePath;
1826 }
1827 
1828 QString DkZipContainer::zipMarker() {
1829 
1830 	return mZipMarker;
1831 }
1832 
1833 #endif
1834 
1835 // DkRawLoader --------------------------------------------------------------------
1836 DkRawLoader::DkRawLoader(const QString & filePath, const QSharedPointer<DkMetaDataT>& metaData) {
1837 	mFilePath = filePath;
1838 	mMetaData = metaData;
1839 }
1840 
1841 bool DkRawLoader::isEmpty() const {
1842 	return mFilePath.isEmpty();
1843 }
1844 
1845 void DkRawLoader::setLoadFast(bool fast) {
1846 	mLoadFast = fast;
1847 }
1848 
1849 bool DkRawLoader::load(const QSharedPointer<QByteArray> ba) {
1850 
1851 	DkTimer dt;
1852 
1853 	// try fetching the preview
1854 	if (loadPreview(ba))
1855 		return true;
1856 
1857 #ifdef WITH_LIBRAW
1858 
1859 	try {
1860 
1861 		// open the buffer
1862 		LibRaw iProcessor;
1863 
1864 		if (!openBuffer(ba, iProcessor)) {
1865 			qDebug() << "could not open buffer for" << mFilePath;
1866 			return false;
1867 		}
1868 
1869 		// check camera models for specific hacks
1870 		detectSpecialCamera(iProcessor);
1871 
1872 		// try loading RAW preview
1873 		if (mLoadFast) {
1874 			mImg = loadPreviewRaw(iProcessor);
1875 
1876 			// are we done already?
1877 			if (!mImg.isNull())
1878 				return true;
1879 		}
1880 
1881 		//unpack the data
1882 		int error = iProcessor.unpack();
1883 		if (std::strcmp(iProcessor.version(), "0.13.5") != 0)	// fixes a bug specific to libraw 13 - version call is UNTESTED
1884 			iProcessor.raw2image();
1885 
1886 		if (error != LIBRAW_SUCCESS)
1887 			return false;
1888 
1889 		// develop using libraw
1890 		if (mCamType == camera_unknown) {
1891 			error = iProcessor.dcraw_process();
1892 
1893 			auto rimg = iProcessor.dcraw_make_mem_image();
1894 
1895 			if (rimg) {
1896 
1897 				mImg = QImage(rimg->data, rimg->width, rimg->height, rimg->width * 3, QImage::Format_RGB888);
1898 				mImg = mImg.copy();		// make a deep copy...
1899 				LibRaw::dcraw_clear_mem(rimg);
1900 
1901 				return true;
1902 			}
1903 		}
1904 
1905 		// demosaic image
1906 		cv::Mat rawMat;
1907 
1908 		if (iProcessor.imgdata.idata.filters)
1909 			rawMat = demosaic(iProcessor);
1910 		else
1911 			rawMat = prepareImg(iProcessor);
1912 
1913 		// color correction + white balance
1914 		if (mIsChromatic) {
1915 			whiteBalance(iProcessor, rawMat);
1916 		}
1917 
1918 		// gamma correction
1919 		gammaCorrection(iProcessor, rawMat);
1920 
1921 		// reduce color noise
1922 		if (DkSettingsManager::param().resources().filterRawImages && mIsChromatic)
1923 			reduceColorNoise(iProcessor, rawMat);
1924 
1925 		mImg = raw2Img(iProcessor, rawMat);
1926 
1927 		//qDebug() << "img size" << mImg.size();
1928 		//qDebug() << "raw mat size" << rawMat.rows << "x" << rawMat.cols;
1929 		iProcessor.recycle();
1930 		rawMat.release();
1931 	}
1932 	catch (...) {
1933 		qDebug() << "[RAW] error during processing...";
1934 		return false;
1935 	}
1936 
1937 	qInfo() << "[RAW] loaded in " << dt;
1938 
1939 #endif
1940 
1941 	return !mImg.isNull();
1942 }
1943 
1944 QImage DkRawLoader::image() const {
1945 	return mImg;
1946 }
1947 
1948 bool DkRawLoader::loadPreview(const QSharedPointer<QByteArray>& ba) {
1949 
1950 	try {
1951 
1952 		// try to get preview image from exiv2
1953 		if (mMetaData) {
1954 			if (mLoadFast || DkSettingsManager::param().resources().loadRawThumb == DkSettings::raw_thumb_always ||
1955 				DkSettingsManager::param().resources().loadRawThumb == DkSettings::raw_thumb_if_large) {
1956 
1957 				mMetaData->readMetaData(mFilePath, ba);
1958 
1959 				int minWidth = 0;
1960 
1961 #ifdef WITH_LIBRAW	// if nomacs has libraw - we can still hope for a fallback -> otherwise try whatever we have here
1962 				if (DkSettingsManager::param().resources().loadRawThumb == DkSettings::raw_thumb_if_large)
1963 					minWidth = 1920;
1964 #endif
1965 				mImg = mMetaData->getPreviewImage(minWidth);
1966 
1967 				if (!mImg.isNull()) {
1968 					qDebug() << "[RAW] loaded with exiv2";
1969 					return true;
1970 				}
1971 			}
1972 		}
1973 	}
1974 	catch (...) {
1975 		qWarning() << "Exception caught during fetching RAW from thumbnail...";
1976 	}
1977 
1978 	return false;
1979 }
1980 
1981 
1982 #ifdef WITH_LIBRAW
1983 
1984 // here are some hints from earlier days...
1985 //// (-w) Use camera white balance, if possible (otherwise, fallback to auto_wb)
1986 //iProcessor.imgdata.params.use_camera_wb = 1;
1987 //// (-a) Use automatic white balance obtained after averaging over the entire image
1988 //iProcessor.imgdata.params.use_auto_wb = 1;
1989 //// (-q 3) Adaptive homogeneity-directed de-mosaicing algorithm (AHD)
1990 //iProcessor.imgdata.params.user_qual = 3;
1991 //iProcessor.imgdata.params.output_tiff = 1;
1992 ////iProcessor.imgdata.params.four_color_rgb = 1;
1993 ////iProcessor.imgdata.params.output_color = 1; //sRGB  (0...raw)
1994 //// RAW data filtration mode during data unpacking and post-processing
1995 //iProcessor.imgdata.params.filtering_mode = LIBRAW_FILTERING_AUTOMATIC;
1996 
1997 
1998 QImage DkRawLoader::loadPreviewRaw(LibRaw & iProcessor) const {
1999 
2000 	int tW = iProcessor.imgdata.thumbnail.twidth;
2001 
2002 	if (DkSettingsManager::param().resources().loadRawThumb == DkSettings::raw_thumb_always ||
2003 		(DkSettingsManager::param().resources().loadRawThumb == DkSettings::raw_thumb_if_large && tW >= 1920)) {
2004 
2005 		// crashes here if image is broken
2006 		int err = iProcessor.unpack_thumb();
2007 		char* tPtr = iProcessor.imgdata.thumbnail.thumb;
2008 
2009 		if (!err && tPtr) {
2010 
2011 			QImage img;
2012 			img.loadFromData((const uchar*)tPtr, iProcessor.imgdata.thumbnail.tlength);
2013 
2014 			// we're good to go
2015 			if (!img.isNull()) {
2016 				qDebug() << "[RAW] I loaded the RAW's thumbnail";
2017 				return img;
2018 			}
2019 			else
2020 				qDebug() << "RAW could not load the thumb";
2021 		}
2022 		else
2023 			qDebug() << "error unpacking the thumb...";
2024 	}
2025 
2026 	// default: return nothing
2027 	return QImage();
2028 }
2029 
2030 bool DkRawLoader::openBuffer(const QSharedPointer<QByteArray>& ba, LibRaw& iProcessor) const {
2031 
2032 	int error = LIBRAW_DATA_ERROR;
2033 
2034 	QFileInfo fi(mFilePath);
2035 
2036 	//use iprocessor from libraw to read the data
2037 	// OK - so LibRaw 0.17 cannot identify iiq files in the buffer - so we load them from the file
2038 	if (fi.suffix().contains("iiq", Qt::CaseInsensitive) || !ba || ba->isEmpty()) {
2039 		error = iProcessor.open_file(mFilePath.toStdString().c_str());
2040 	}
2041 	else {
2042 		// the buffer check is because:
2043 		// libraw has an error when loading buffers if the first 4 bytes encode as 'RIFF'
2044 		// and no data follows at all
2045 		if (ba->isEmpty() || ba->size() < 100)
2046 			return false;
2047 
2048 		error = iProcessor.open_buffer((void*)ba->constData(), ba->size());
2049 	}
2050 
2051 	return (error == LIBRAW_SUCCESS);
2052 }
2053 
2054 void DkRawLoader::detectSpecialCamera(const LibRaw & iProcessor) {
2055 
2056 	if (QString(iProcessor.imgdata.idata.model) == "IQ260 Achromatic")
2057 		mIsChromatic = false;
2058 
2059 	if (QString(iProcessor.imgdata.idata.model).contains("IQ260"))
2060 		mCamType = camera_iiq;
2061 	else if (QString(iProcessor.imgdata.idata.make).compare("Canon", Qt::CaseInsensitive))
2062 		mCamType = camera_canon;
2063 
2064 	// add your camera flag (for hacks) here
2065 }
2066 
2067 cv::Mat DkRawLoader::demosaic(LibRaw & iProcessor) const {
2068 
2069 	cv::Mat rawMat = cv::Mat(iProcessor.imgdata.sizes.height, iProcessor.imgdata.sizes.width, CV_16UC1);
2070 	double dynamicRange = (double)(iProcessor.imgdata.color.maximum - iProcessor.imgdata.color.black);
2071 
2072 	// normalize all image values
2073 	for (int rIdx = 0; rIdx < rawMat.rows; rIdx++) {
2074 		unsigned short *ptrRaw = rawMat.ptr<unsigned short>(rIdx);
2075 
2076 		for (int cIdx = 0; cIdx < rawMat.cols; cIdx++) {
2077 
2078 			int colIdx = iProcessor.COLOR(rIdx, cIdx);
2079 			double val = (double)(iProcessor.imgdata.image[(rawMat.cols*rIdx) + cIdx][colIdx]);
2080 
2081 			// normalize the value w.r.t the black point defined
2082 			val = (val - iProcessor.imgdata.color.black) / dynamicRange;
2083 			ptrRaw[cIdx] = clip<unsigned short>(val * USHRT_MAX);  // for conversion to 16U
2084 		}
2085 	}
2086 
2087 	// no demosaicing
2088 	if (mIsChromatic) {
2089 
2090 		unsigned long type = (unsigned long)iProcessor.imgdata.idata.filters;
2091 		type = type & 255;
2092 
2093 		cv::Mat rgbImg;
2094 
2095 		//define bayer pattern
2096 		if (type == 180) {
2097 			cvtColor(rawMat, rgbImg, CV_BayerBG2RGB);		//bitmask  10 11 01 00  -> 3(G) 2(B) 1(G) 0(R) ->	RG RG RG
2098 															//													GB GB GB
2099 		}
2100 		else if (type == 30) {
2101 			cvtColor(rawMat, rgbImg, CV_BayerRG2RGB);		//bitmask  00 01 11 10	-> 0 1 3 2
2102 		}
2103 		else if (type == 225) {
2104 			cvtColor(rawMat, rgbImg, CV_BayerGB2RGB);		//bitmask  11 10 00 01
2105 		}
2106 		else if (type == 75) {
2107 			cvtColor(rawMat, rgbImg, CV_BayerGR2RGB);		//bitmask  01 00 10 11
2108 		}
2109 		else {
2110 			qWarning() << "Wrong Bayer Pattern (not BG, RG, GB, GR)\n";
2111 			return cv::Mat();
2112 		}
2113 
2114 		rawMat = rgbImg;
2115 	}
2116 
2117 	// 16U (1 or 3 channeled) Mat
2118 	return rawMat;
2119 }
2120 
2121 cv::Mat DkRawLoader::prepareImg(const LibRaw & iProcessor) const {
2122 
2123 	cv::Mat rawMat = cv::Mat(iProcessor.imgdata.sizes.height, iProcessor.imgdata.sizes.width, CV_16UC3, cv::Scalar(0));
2124 	double dynamicRange = (double)(iProcessor.imgdata.color.maximum - iProcessor.imgdata.color.black);
2125 
2126 	// normalization function
2127 	auto normalize = [&](double val) {
2128 
2129 		val = (val - iProcessor.imgdata.color.black) / dynamicRange;
2130 		return clip<unsigned short>(val * USHRT_MAX);
2131 	};
2132 
2133 	for (int rIdx = 0; rIdx < rawMat.rows; rIdx++) {
2134 		unsigned short *ptrI = rawMat.ptr<unsigned short>(rIdx);
2135 
2136 		for (int cIdx = 0; cIdx < rawMat.cols; cIdx++) {
2137 
2138 			*ptrI = normalize(iProcessor.imgdata.image[rawMat.cols*rIdx + cIdx][0]);
2139 			ptrI++;
2140 			*ptrI = normalize(iProcessor.imgdata.image[rawMat.cols*rIdx + cIdx][1]);
2141 			ptrI++;
2142 			*ptrI = normalize(iProcessor.imgdata.image[rawMat.cols*rIdx + cIdx][2]);
2143 			ptrI++;
2144 		}
2145 	}
2146 
2147 	return rawMat;
2148 }
2149 
2150 cv::Mat DkRawLoader::whiteMultipliers(const LibRaw & iProcessor) const {
2151 
2152 	// get camera white balance multipliers
2153 	cv::Mat wm(1, 4, CV_32FC1);
2154 
2155 	float* wmp = wm.ptr<float>();
2156 
2157 	for (int idx = 0; idx < wm.cols; idx++)
2158 		wmp[idx] = iProcessor.imgdata.color.cam_mul[idx];
2159 
2160 	if (wmp[3] == 0)
2161 		wmp[3] = wmp[1];	// take green (usually its RGBG)
2162 
2163 	// normalize white balance multipliers
2164 	float w = (float)cv::sum(wm)[0] / 4.0f;
2165 	float maxW = 1.0f;
2166 
2167 	//clipping according the camera model
2168 	//if w > 2.0 maxW is 256, otherwise 512
2169 	//tested empirically
2170 	//check if it can be defined by some metadata settings?
2171 	if (w > 2.0f)
2172 		maxW = 255.0f;
2173 	if (w > 2.0f && mCamType == camera_canon)
2174 		maxW = 511.0f;	// some cameras would even need ~800 - why?
2175 
2176 	//normalize white point
2177 	wm /= maxW;
2178 
2179 	// 1 x 4 32FC1 white balance vector
2180 	return wm;
2181 }
2182 
2183 cv::Mat DkRawLoader::gammaTable(const LibRaw & iProcessor) const {
2184 
2185 	// OK this is an instance of reverse engineering:
2186 	// we found out that the values of (at least) the PhaseOne's achromatic back have to be doubled
2187 	// our images are no close to what their software (Capture One does) - only the gamma correction
2188 	// seems to be slightly different... -> now we can load compressed IIQs that are not supported by PS : )
2189 	double cameraHackMlp = (QString(iProcessor.imgdata.idata.model) == "IQ260 Achromatic") ? 2.0 : 1.0;
2190 
2191 	//read gamma value and create gamma table
2192 	double gamma = (double)iProcessor.imgdata.params.gamm[0];
2193 
2194 	cv::Mat gmt(1, USHRT_MAX, CV_16UC1);
2195 	unsigned short* gmtp = gmt.ptr<unsigned short>();
2196 
2197 	for (int idx = 0; idx < gmt.cols; idx++) {
2198 		gmtp[idx] = clip<unsigned short>(qRound((1.099*std::pow((double)idx / USHRT_MAX, gamma) - 0.099) * 255 * cameraHackMlp));
2199 	}
2200 
2201 	// a 1 x 65535 U16 gamma table
2202 	return gmt;
2203 }
2204 
2205 void DkRawLoader::whiteBalance(const LibRaw & iProcessor, cv::Mat & img) const {
2206 
2207 	// white balance must not be empty at this point
2208 	cv::Mat wb = whiteMultipliers(iProcessor);
2209 	const float* wbp = wb.ptr<float>();
2210 	assert(wb.cols == 4);
2211 
2212 	for (int rIdx = 0; rIdx < img.rows; rIdx++) {
2213 
2214 		unsigned short *ptr = img.ptr<unsigned short>(rIdx);
2215 
2216 		for (int cIdx = 0; cIdx < img.cols; cIdx++) {
2217 
2218 			//apply white balance correction
2219 			unsigned short r = clip<unsigned short>(*ptr		* wbp[0]);
2220 			unsigned short g = clip<unsigned short>(*(ptr+1)	* wbp[1]);
2221 			unsigned short b = clip<unsigned short>(*(ptr+2)	* wbp[2]);
2222 
2223 			//apply color correction
2224 			int cr = qRound(iProcessor.imgdata.color.rgb_cam[0][0] * r +
2225 							iProcessor.imgdata.color.rgb_cam[0][1] * g +
2226 							iProcessor.imgdata.color.rgb_cam[0][2] * b);
2227 			int cg = qRound(iProcessor.imgdata.color.rgb_cam[1][0] * r +
2228 							iProcessor.imgdata.color.rgb_cam[1][1] * g +
2229 							iProcessor.imgdata.color.rgb_cam[1][2] * b);
2230 			int cb = qRound(iProcessor.imgdata.color.rgb_cam[2][0] * r +
2231 							iProcessor.imgdata.color.rgb_cam[2][1] * g +
2232 							iProcessor.imgdata.color.rgb_cam[2][2] * b);
2233 
2234 			// clip & save color corrected values
2235 			*ptr = clip<unsigned short>(cr);
2236 			ptr++;
2237 			*ptr = clip<unsigned short>(cg);
2238 			ptr++;
2239 			*ptr = clip<unsigned short>(cb);
2240 			ptr++;
2241 		}
2242 	}
2243 }
2244 
2245 void DkRawLoader::gammaCorrection(const LibRaw & iProcessor, cv::Mat& img) const {
2246 
2247 	// white balance must not be empty at this point
2248 	cv::Mat gt = gammaTable(iProcessor);
2249 	const unsigned short* gammaLookup = gt.ptr<unsigned short>();
2250 	assert(gt.cols == USHRT_MAX);
2251 
2252 	for (int rIdx = 0; rIdx < img.rows; rIdx++) {
2253 
2254 		unsigned short *ptr = img.ptr<unsigned short>(rIdx);
2255 
2256 		for (int cIdx = 0; cIdx < img.cols * img.channels(); cIdx++) {
2257 
2258 			// values close to 0 are treated linear
2259 			if (ptr[cIdx] <= 5)	// 0.018 * 255
2260 				ptr[cIdx] = (unsigned short)qRound(ptr[cIdx] * (double)iProcessor.imgdata.params.gamm[1] / 255.0);
2261 			else
2262 				ptr[cIdx] = gammaLookup[ptr[cIdx]];
2263 		}
2264 	}
2265 
2266 }
2267 
2268 void DkRawLoader::reduceColorNoise(const LibRaw & iProcessor, cv::Mat & img) const {
2269 
2270 	// filter color noise with a median filter
2271 	float isoSpeed = iProcessor.imgdata.other.iso_speed;
2272 
2273 	if (isoSpeed > 0) {
2274 
2275 		DkTimer dt;
2276 
2277 		int winSize;
2278 		if (isoSpeed > 6400)
2279 			winSize = 13;
2280 		else if (isoSpeed >= 3200)
2281 			winSize = 11;
2282 		else if (isoSpeed >= 2500)
2283 			winSize = 9;
2284 		else if (isoSpeed >= 400)
2285 			winSize = 7;
2286 		else
2287 			winSize = 5;
2288 
2289 		DkTimer dMed;
2290 
2291 		// revert back to 8-bit image
2292 		img.convertTo(img, CV_8U);
2293 
2294 		cv::cvtColor(img, img, CV_RGB2YCrCb);
2295 
2296 		std::vector<cv::Mat> imgCh;
2297 		cv::split(img, imgCh);
2298 		assert(imgCh.size() == 3);
2299 
2300 		cv::medianBlur(imgCh[1], imgCh[1], winSize);
2301 		cv::medianBlur(imgCh[2], imgCh[2], winSize);
2302 
2303 		cv::merge(imgCh, img);
2304 		cv::cvtColor(img, img, CV_YCrCb2RGB);
2305 		qDebug() << "median blur takes:" << dt;
2306 	}
2307 
2308 }
2309 
2310 QImage DkRawLoader::raw2Img(const LibRaw & iProcessor, cv::Mat & img) const {
2311 
2312 	//check the pixel aspect ratio of the raw image
2313 	if (iProcessor.imgdata.sizes.pixel_aspect != 1.0f)
2314 		cv::resize(img, img, cv::Size(), (double)iProcessor.imgdata.sizes.pixel_aspect, 1.0f);
2315 
2316 	// revert back to 8-bit image
2317 	img.convertTo(img, CV_8U);
2318 
2319 	// TODO: for now - fix this!
2320 	if (img.channels() == 1)
2321 		cv::cvtColor(img, img, CV_GRAY2RGB);
2322 
2323 
2324 	return DkImage::mat2QImage(img);
2325 }
2326 
2327 #endif
2328 
2329 // -------------------------------------------------------------------- DkTgaLoader
2330 namespace tga {
2331 
2332 	DkTgaLoader::DkTgaLoader(QSharedPointer<QByteArray> ba) {
2333 
2334 		mBa = ba;
2335 	}
2336 
2337 	QImage DkTgaLoader::image() const {
2338 		return mImg;
2339 	}
2340 
2341 	bool DkTgaLoader::load() {
2342 
2343 		if (!mBa || mBa->isEmpty())
2344 			return false;
2345 
2346 		return load(mBa);
2347 	}
2348 
2349 	bool DkTgaLoader::load(QSharedPointer<QByteArray> ba) {
2350 
2351 
2352 		// this code is from: http://www.paulbourke.net/dataformats/tga/
2353 		// thanks!
2354 		Header header;
2355 
2356 		const char* dataC = ba->data();
2357 
2358 		/* Display the header fields */
2359 		header.idlength = *dataC; dataC++;
2360 		header.colourmaptype = *dataC; dataC++;
2361 		header.datatypecode = *dataC; dataC++;
2362 
2363 		const short* dataS = (const short*)dataC;
2364 
2365 		header.colourmaporigin = *dataS; dataS++;
2366 		header.colourmaplength = *dataS; dataS++;
2367 		dataC = (const char*)dataS;
2368 		header.colourmapdepth = *dataC; dataC++;
2369 		dataS = (const short*)dataC;
2370 		header.x_origin = *dataS; dataS++;
2371 		header.y_origin = *dataS; dataS++;
2372 		header.width = *dataS; dataS++;
2373 		header.height = *dataS; dataS++;
2374 		dataC = (const char*)dataS;
2375 		header.bitsperpixel = *dataC; dataC++;
2376 		header.imagedescriptor = *dataC; dataC++;
2377 
2378 #ifdef _DEBUG
2379 		qDebug() << "TGA Header ------------------------------";
2380 		qDebug() << "ID length:         " << (int)header.idlength;
2381 		qDebug() << "Colourmap type:    " << (int)header.colourmaptype;
2382 		qDebug() << "Image type:        " << (int)header.datatypecode;
2383 		qDebug() << "Colour map offset: " << header.colourmaporigin;
2384 		qDebug() << "Colour map length: " << header.colourmaplength;
2385 		qDebug() << "Colour map depth:  " << (int)header.colourmapdepth;
2386 		qDebug() << "X origin:          " << header.x_origin;
2387 		qDebug() << "Y origin:          " << header.y_origin;
2388 		qDebug() << "Width:             " << header.width;
2389 		qDebug() << "Height:            " << header.height;
2390 		qDebug() << "Bits per pixel:    " << (int)header.bitsperpixel;
2391 		qDebug() << "Descriptor:        " << (int)header.imagedescriptor;
2392 #endif
2393 
2394 		/* What can we handle */
2395 		if (header.datatypecode != 2 && header.datatypecode != 10) {
2396 			qWarning() << "Can only handle image type 2 and 10";
2397 			return false;
2398 		}
2399 
2400 		if (header.bitsperpixel != 16 &&
2401 			header.bitsperpixel != 24 &&
2402 			header.bitsperpixel != 32) {
2403 			qWarning() << "Can only handle pixel depths of 16, 24, and 32";
2404 			return false;
2405 		}
2406 
2407 		if (header.colourmaptype != 0 && header.colourmaptype != 1) {
2408 			qWarning() << "Can only handle colour map types of 0 and 1";
2409 			return false;
2410 		}
2411 
2412 		Pixel *pixels = new Pixel[header.width*header.height * sizeof(Pixel)];
2413 
2414 		if (!pixels) {
2415 			qWarning() << "TGA: could not allocate" << header.width*header.height * sizeof(Pixel)/1024 << "KB";
2416 			return false;
2417 		}
2418 
2419 		///* Skip over unnecessary stuff */
2420 		int skipover = header.idlength;
2421 		skipover += header.colourmaptype * header.colourmaplength;
2422 		dataC += skipover;
2423 
2424 		/* Read the image */
2425 		int bytes2read = header.bitsperpixel / 8;	// save?
2426 		unsigned char p[5];
2427 
2428 		for (int n = 0; n < header.width * header.height;) {
2429 
2430 			if (header.datatypecode == 2) {                     /* Uncompressed */
2431 
2432 				// TODO: out-of-bounds not checked here...
2433 				for (int bi = 0; bi < bytes2read; bi++, dataC++)
2434 					p[bi] = *dataC;
2435 
2436 				mergeBytes(&(pixels[n]), p, bytes2read);
2437 				n++;
2438 			}
2439 			else if (header.datatypecode == 10) {             /* Compressed */
2440 
2441 				for (int bi = 0; bi < bytes2read+1; bi++, dataC++)
2442 					p[bi] = *dataC;
2443 
2444 				int j = p[0] & 0x7f;
2445 				mergeBytes(&(pixels[n]), &(p[1]), bytes2read);
2446 				n++;
2447 				if (p[0] & 0x80) {         /* RLE chunk */
2448 					for (int i = 0; i < j; i++) {
2449 						mergeBytes(&(pixels[n]), &(p[1]), bytes2read);
2450 						n++;
2451 					}
2452 				}
2453 				else {                   /* Normal chunk */
2454 					for (int i = 0; i < j; i++) {
2455 
2456 						for (int bi = 0; bi < bytes2read; bi++, dataC++)
2457 							p[bi] = *dataC;
2458 
2459 						mergeBytes(&(pixels[n]), p, bytes2read);
2460 						n++;
2461 					}
2462 				}
2463 			}
2464 		}
2465 
2466 		mImg = QImage((uchar*)pixels, header.width, header.height, QImage::Format_ARGB32);
2467 		mImg = mImg.copy();
2468 
2469 		// I somehow expected the 5th bit to be 0x10 -> but Paul seems to have a 0th bit : )
2470 		if (!(header.imagedescriptor & 0x20))
2471 			mImg = mImg.mirrored();
2472 
2473 		delete[] pixels;
2474 
2475 		return true;
2476 	}
2477 
2478 	void DkTgaLoader::mergeBytes(Pixel * pixel, unsigned char * p, int bytes) const {
2479 
2480 		if (bytes == 4) {
2481 			pixel->r = p[0];
2482 			pixel->g = p[1];
2483 			pixel->b = p[2];
2484 			pixel->a = p[3];
2485 		}
2486 		else if (bytes == 3) {
2487 			pixel->r = p[0];
2488 			pixel->g = p[1];
2489 			pixel->b = p[2];
2490 			pixel->a = 255;
2491 		}
2492 		else if (bytes == 2) {
2493 			pixel->r = (p[0] & 0x1f) << 3;
2494 			pixel->g = ((p[1] & 0x03) << 6) | ((p[0] & 0xe0) >> 2);
2495 			pixel->b = (p[1] & 0x7c) << 1;
2496 			pixel->a = 255;// (p[1] & 0x80);
2497 		}
2498 	}
2499 }
2500 
2501 }
2502