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