1 /*******************************************************************************************************
2 DkImageStorage.cpp
3 Created on: 12.07.2013
4
5 nomacs is a fast and small image viewer with the capability of synchronizing multiple instances
6
7 Copyright (C) 2011-2013 Markus Diem <markus@nomacs.org>
8 Copyright (C) 2011-2013 Stefan Fiel <stefan@nomacs.org>
9 Copyright (C) 2011-2013 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 "DkImageStorage.h"
29 #include "DkActionManager.h"
30 #include "DkSettings.h"
31 #include "DkTimer.h"
32 #include "DkMath.h"
33 #include "DkThumbs.h"
34
35 #pragma warning(push, 0) // no warnings from includes - begin
36 #include <QDebug>
37 #include <QtConcurrentRun>
38 #include <QPixmap>
39 #include <QPainter>
40 #include <QBitmap>
41 #include <qmath.h>
42 #include <QSvgRenderer>
43 #include <QTimer>
44 #pragma warning(pop) // no warnings from includes - end
45
46 #if defined(Q_OS_WIN) && !defined(SOCK_STREAM)
47 #include <winsock2.h> // needed since libraw 0.16
48 #endif
49 namespace nmc {
50
51 // DkImage --------------------------------------------------------------------
52
53 /**
54 * Returns a string with the buffer size of an image.
55 * @param img a QImage
56 * @return QString a human readable string containing the buffer size
57 **/
getBufferSize(const QImage & img)58 QString DkImage::getBufferSize(const QImage& img) {
59
60 return getBufferSize(img.size(), img.depth());
61 }
62
63 /**
64 * Returns a string with the buffer size of an image.
65 * @param imgSize the image size
66 * @param depth the image depth
67 * @return QString a human readable string containing the buffer size
68 **/
getBufferSize(const QSize & imgSize,const int depth)69 QString DkImage::getBufferSize(const QSize& imgSize, const int depth) {
70
71 double size = (double)imgSize.width() * (double)imgSize.height() * (double)(depth/8.0f);
72 QString sizeStr;
73 qDebug() << "dimension: " << size;
74
75 if (size >= 1024*1024*1024) {
76 return QString::number(size/(1024.0f*1024.0f*1024.0f), 'f', 2) + " GB";
77 }
78 else if (size >= 1024*1024) {
79 return QString::number(size/(1024.0f*1024.0f), 'f', 2) + " MB";
80 }
81 else if (size >= 1024) {
82 return QString::number(size/1024.0f, 'f', 2) + " KB";
83 }
84 else {
85 return QString::number(size, 'f', 2) + " B";
86 }
87 }
88
89 /**
90 * Returns a the buffer size of an image.
91 * @param imgSize the image size
92 * @param depth the image depth
93 * @return buffer size in MB
94 **/
getBufferSizeFloat(const QSize & imgSize,const int depth)95 float DkImage::getBufferSizeFloat(const QSize& imgSize, const int depth) {
96
97 double size = (double)imgSize.width() * (double)imgSize.height() * (double)(depth/8.0f);
98 QString sizeStr;
99
100 return (float)size/(1024.0f*1024.0f);
101 }
102
103 /**
104 * This function resizes an image according to the interpolation method specified.
105 * @param img the image to resize
106 * @param newSize the new size
107 * @param factor the resize factor
108 * @param interpolation the interpolation method
109 * @return QImage the resized image
110 **/
resizeImage(const QImage & img,const QSize & newSize,double factor,int interpolation,bool correctGamma)111 QImage DkImage::resizeImage(const QImage& img, const QSize& newSize, double factor /* = 1.0 */, int interpolation /* = ipl_cubic */, bool correctGamma /* = true */) {
112
113 QSize nSize = newSize;
114
115 // nothing to do
116 if (img.size() == nSize && factor == 1.0)
117 return img;
118
119 if (factor != 1.0)
120 nSize = QSize(qRound(img.width()*factor), qRound(img.height()*factor));
121
122 if (nSize.width() < 1 || nSize.height() < 1) {
123 return QImage();
124 }
125
126 Qt::TransformationMode iplQt = Qt::FastTransformation;
127 switch(interpolation) {
128 case ipl_nearest:
129 case ipl_area: iplQt = Qt::FastTransformation; break;
130 case ipl_linear:
131 case ipl_cubic:
132 case ipl_lanczos: iplQt = Qt::SmoothTransformation; break;
133 }
134 #ifdef WITH_OPENCV
135
136 int ipl = CV_INTER_CUBIC;
137 switch(interpolation) {
138 case ipl_nearest: ipl = CV_INTER_NN; break;
139 case ipl_area: ipl = CV_INTER_AREA; break;
140 case ipl_linear: ipl = CV_INTER_LINEAR; break;
141 case ipl_cubic: ipl = CV_INTER_CUBIC; break;
142 case ipl_lanczos: ipl = CV_INTER_LANCZOS4; break;
143 }
144
145 try {
146
147 QImage qImg;
148 cv::Mat resizeImage = DkImage::qImage2Mat(img);
149
150 if (correctGamma) {
151 resizeImage.convertTo(resizeImage, CV_16U, USHRT_MAX/255.0f);
152 DkImage::gammaToLinear(resizeImage);
153 }
154
155 // is the image convertible?
156 if (resizeImage.empty()) {
157 qImg = img.scaled(newSize, Qt::IgnoreAspectRatio, iplQt);
158 }
159 else {
160
161 cv::Mat tmp;
162 cv::resize(resizeImage, tmp, cv::Size(nSize.width(), nSize.height()), 0, 0, ipl);
163 resizeImage = tmp;
164
165 if (correctGamma) {
166 DkImage::linearToGamma(resizeImage);
167 resizeImage.convertTo(resizeImage, CV_8U, 255.0f/USHRT_MAX);
168 }
169
170 qImg = DkImage::mat2QImage(resizeImage);
171 }
172
173 if (!img.colorTable().isEmpty())
174 qImg.setColorTable(img.colorTable());
175
176 return qImg;
177
178 }
179 catch (std::exception se) {
180 return QImage();
181 }
182
183 #else
184
185 QImage qImg = img.copy();
186
187 if (correctGamma)
188 DkImage::gammaToLinear(qImg);
189 qImg.scaled(nSize, Qt::IgnoreAspectRatio, iplQt);
190
191 if (correctGamma)
192 DkImage::linearToGamma(qImg);
193 return qImg;
194 #endif
195 }
196
alphaChannelUsed(const QImage & img)197 bool DkImage::alphaChannelUsed(const QImage& img) {
198
199 if (img.format() != QImage::Format_ARGB32 && img.format() != QImage::Format_ARGB32)
200 return false;
201
202 // number of used bytes per line
203 int bpl = (img.width() * img.depth() + 7) / 8;
204 int pad = img.bytesPerLine() - bpl;
205 const uchar* ptr = img.bits();
206
207 for (int rIdx = 0; rIdx < img.height(); rIdx++) {
208
209 for (int cIdx = 0; cIdx < bpl; cIdx++, ptr++) {
210
211 if (cIdx % 4 == 3 && *ptr != 255)
212 return true;
213 }
214
215 ptr += pad;
216 }
217
218 return false;
219 }
220
thresholdImage(const QImage & img,double thr,bool color)221 QImage DkImage::thresholdImage(const QImage & img, double thr, bool color) {
222
223 if (img.isNull())
224 return img;
225
226 DkTimer dt;
227
228 QImage tImg = color ? img.copy() : grayscaleImage(img);
229
230 // number of bytes per line used
231 int bpl = (tImg.width() * tImg.depth() + 7) / 8;
232 int pad = tImg.bytesPerLine() - bpl;
233
234 uchar* mPtr = tImg.bits();
235
236 for (int rIdx = 0; rIdx < tImg.height(); rIdx++) {
237
238 for (int cIdx = 0; cIdx < bpl; cIdx++, mPtr++) {
239 *mPtr = *mPtr > thr ? 255 : 0;
240 }
241 mPtr += pad;
242 }
243
244 qDebug() << "thresholding takes: " << dt;
245
246 return tImg;
247 }
248
rotateImage(const QImage & img,double angle)249 QImage DkImage::rotateImage(const QImage & img, double angle) {
250
251 // compute new image size
252 DkVector nSl((float)img.width(), (float)img.height());
253 DkVector nSr = nSl;
254 double angleRad = angle*DK_DEG2RAD;
255
256 // size left
257 nSl.rotate(angleRad);
258 nSl.abs();
259
260 // size right
261 nSr.swap();
262 nSr.rotate(angleRad);
263 nSr.abs();
264 nSr.swap();
265
266 DkVector ns = nSl.maxVec(nSr);
267 QSize newSize((int)ns.width, (int)ns.height);
268
269 // create image
270 QImage imgR(newSize, QImage::Format_RGBA8888);
271 imgR.fill(Qt::transparent);
272
273 // create transformation
274 QTransform trans;
275 trans.translate(imgR.width()/2, imgR.height()/2);
276 trans.rotate(angle);
277 trans.translate(-img.width()/2, -img.height()/2);
278
279 // render
280 QPainter p(&imgR);
281 p.setRenderHint(QPainter::SmoothPixmapTransform);
282 p.setTransform(trans);
283 p.drawImage(QPoint(), img);
284
285 return imgR;
286 }
287
grayscaleImage(const QImage & img)288 QImage DkImage::grayscaleImage(const QImage & img) {
289
290 QImage imgR;
291
292 #ifdef WITH_OPENCV
293
294 cv::Mat cvImg = DkImage::qImage2Mat(img);
295 cv::cvtColor(cvImg, cvImg, CV_RGB2Lab);
296
297 std::vector<cv::Mat> imgs;
298 cv::split(cvImg, imgs);
299
300 // get the luminance channel
301 if (!imgs.empty())
302 cvImg = imgs[0];
303
304 // convert it back for the painter
305 cv::cvtColor(cvImg, cvImg, CV_GRAY2RGB);
306
307 imgR = DkImage::mat2QImage(cvImg);
308 #else
309
310 QVector<QRgb> table(256);
311 for(int i=0;i<256;++i)
312 table[i]=qRgb(i,i,i);
313
314 imgR = img.convertToFormat(QImage::Format_Indexed8,table);
315 #endif
316
317 return imgR;
318 }
319
320 template <typename numFmt>
getLinear2GammaTable(int maxVal)321 QVector<numFmt> DkImage::getLinear2GammaTable(int maxVal) {
322
323 QVector<numFmt> gammaTable;
324 double a = 0.055;
325
326 for (int idx = 0; idx <= maxVal; idx++) {
327
328 double i = idx/(double)maxVal;
329 if (i <= 0.0031308) {
330 gammaTable.append((numFmt)(qRound(i*12.92*(double)maxVal)));
331 }
332 else {
333 gammaTable.append((numFmt)(qRound(((1+a)*pow(i,1/2.4)-a)*(double)maxVal)));
334 }
335 }
336
337 return gammaTable;
338 }
339
340 template <typename numFmt>
getGamma2LinearTable(int maxVal)341 QVector<numFmt> DkImage::getGamma2LinearTable(int maxVal) {
342
343 // the formula should be:
344 // i = px/255
345 // i <= 0.04045 -> i/12.92
346 // i > 0.04045 -> (i+0.055)/(1+0.055)^2.4
347 QVector<numFmt> gammaTable;
348 double a = 0.055;
349
350 for (int idx = 0; idx <= maxVal; idx++) {
351
352 double i = idx/(double)maxVal;
353 if (i <= 0.04045) {
354 gammaTable.append((numFmt)(qRound(i/12.92*maxVal)));
355 }
356 else {
357 gammaTable.append(pow((i+a)/(1+a),2.4)*maxVal > 0 ? (numFmt)(pow((i+a)/(1+a),2.4)*maxVal) : 0);
358 }
359 }
360
361 return gammaTable;
362 }
363
gammaToLinear(QImage & img)364 void DkImage::gammaToLinear(QImage& img) {
365
366 QVector<uchar> gt = getGamma2LinearTable<uchar>(255);
367 mapGammaTable(img, gt);
368 }
369
linearToGamma(QImage & img)370 void DkImage::linearToGamma(QImage& img) {
371
372 QVector<uchar> gt = getLinear2GammaTable<uchar>(255);
373 mapGammaTable(img, gt);
374 }
375
mapGammaTable(QImage & img,const QVector<uchar> & gammaTable)376 void DkImage::mapGammaTable(QImage& img, const QVector<uchar>& gammaTable) {
377
378 DkTimer dt;
379
380 // number of bytes per line used
381 int bpl = (img.width() * img.depth() + 7) / 8;
382 int pad = img.bytesPerLine() - bpl;
383
384 //int channels = (img.hasAlphaChannel() || img.format() == QImage::Format_RGB32) ? 4 : 3;
385
386 uchar* mPtr = img.bits();
387
388 for (int rIdx = 0; rIdx < img.height(); rIdx++) {
389
390 for (int cIdx = 0; cIdx < bpl; cIdx++, mPtr++) {
391
392 if (*mPtr < 0 || *mPtr > gammaTable.size()) {
393 qDebug() << "WRONG VALUE: " << *mPtr;
394 continue;
395 }
396 if ((int)gammaTable[*mPtr] < 0 || (int)gammaTable[*mPtr] > USHRT_MAX) {
397 qDebug() << "WRONG VALUE: " << *mPtr;
398 continue;
399 }
400
401 *mPtr = gammaTable[*mPtr];
402 }
403 mPtr += pad;
404 }
405
406 qDebug() << "gamma computation takes: " << dt;
407 }
408
normImage(const QImage & img)409 QImage DkImage::normImage(const QImage& img) {
410
411 QImage imgN = img.copy();
412 normImage(imgN);
413
414 return imgN;
415 }
416
normImage(QImage & img)417 bool DkImage::normImage(QImage& img) {
418
419 uchar maxVal = 0;
420 uchar minVal = 255;
421
422 // number of used bytes per line
423 int bpl = (img.width() * img.depth() + 7) / 8;
424 int pad = img.bytesPerLine() - bpl;
425 uchar* mPtr = img.bits();
426 bool hasAlpha = img.hasAlphaChannel() || img.format() == QImage::Format_RGB32;
427
428 for (int rIdx = 0; rIdx < img.height(); rIdx++) {
429
430 for (int cIdx = 0; cIdx < bpl; cIdx++, mPtr++) {
431
432 if (hasAlpha && cIdx % 4 == 3)
433 continue;
434
435 if (*mPtr > maxVal)
436 maxVal = *mPtr;
437 if (*mPtr < minVal)
438 minVal = *mPtr;
439 }
440
441 mPtr += pad;
442 }
443
444 if ((minVal == 0 && maxVal == 255) || maxVal-minVal == 0)
445 return false;
446
447 uchar* ptr = img.bits();
448
449 for (int rIdx = 0; rIdx < img.height(); rIdx++) {
450
451 for (int cIdx = 0; cIdx < bpl; cIdx++, ptr++) {
452
453 if (hasAlpha && cIdx % 4 == 3)
454 continue;
455
456 *ptr = (uchar)qRound(255.0f*(*ptr-minVal)/(maxVal-minVal));
457 }
458
459 ptr += pad;
460 }
461
462 return true;
463
464 }
465
autoAdjustImage(const QImage & img)466 QImage DkImage::autoAdjustImage(const QImage& img) {
467
468 QImage imgA = img.copy();
469 autoAdjustImage(imgA);
470
471 return imgA;
472 }
473
autoAdjustImage(QImage & img)474 bool DkImage::autoAdjustImage(QImage& img) {
475
476 //return DkImage::unsharpMask(img, 30.0f, 1.5f);
477
478 DkTimer dt;
479 qDebug() << "[Auto Adjust] image format: " << img.format();
480
481 // for grayscale image - normalize is the same
482 if (img.format() <= QImage::Format_Indexed8) {
483 qDebug() << "[Auto Adjust] Grayscale - switching to Normalize: " << img.format();
484 return normImage(img);
485 }
486 else if (img.format() != QImage::Format_ARGB32 && img.format() != QImage::Format_ARGB32 &&
487 img.format() != QImage::Format_RGB32 && img.format() != QImage::Format_RGB888) {
488 qDebug() << "[Auto Adjust] Format not supported: " << img.format();
489 return false;
490 }
491
492 int channels = (img.hasAlphaChannel() || img.format() == QImage::Format_RGB32) ? 4 : 3;
493
494 uchar maxR = 0, maxG = 0, maxB = 0;
495 uchar minR = 255, minG = 255, minB = 255;
496
497 // number of bytes per line used
498 int bpl = (img.width() * img.depth() + 7) / 8;
499 int pad = img.bytesPerLine() - bpl;
500
501 uchar* mPtr = img.bits();
502 uchar r,g,b;
503
504 int histR[256] = {0};
505 int histG[256] = {0};
506 int histB[256] = {0};
507
508 for (int rIdx = 0; rIdx < img.height(); rIdx++) {
509
510 for (int cIdx = 0; cIdx < bpl; ) {
511
512 r = *mPtr; mPtr++;
513 g = *mPtr; mPtr++;
514 b = *mPtr; mPtr++;
515 cIdx += 3;
516
517 if (r > maxR) maxR = r;
518 if (r < minR) minR = r;
519
520 if (g > maxG) maxG = g;
521 if (g < minG) minG = g;
522
523 if (b > maxB) maxB = b;
524 if (b < minB) minB = b;
525
526 histR[r]++;
527 histG[g]++;
528 histB[b]++;
529
530
531 // ?? strange but I would expect the alpha channel to be the first (big endian?)
532 if (channels == 4) {
533 mPtr++;
534 cIdx++;
535 }
536
537 }
538 mPtr += pad;
539 }
540
541 QColor ignoreChannel;
542 bool ignoreR = maxR-minR == 0 || maxR-minR == 255;
543 bool ignoreG = maxR-minR == 0 || maxG-minG == 255;
544 bool ignoreB = maxR-minR == 0 || maxB-minB == 255;
545
546 uchar* ptr = img.bits();
547
548 if (ignoreR) {
549 maxR = findHistPeak(histR);
550 ignoreR = maxR-minR == 0 || maxR-minR == 255;
551 }
552 if (ignoreG) {
553 maxG = findHistPeak(histG);
554 ignoreG = maxG-minG == 0 || maxG-minG == 255;
555 }
556 if (ignoreB) {
557 maxB = findHistPeak(histB);
558 ignoreB = maxB-minB == 0 || maxB-minB == 255;
559 }
560
561 //qDebug() << "red max: " << maxR << " min: " << minR << " ignored: " << ignoreR;
562 //qDebug() << "green max: " << maxG << " min: " << minG << " ignored: " << ignoreG;
563 //qDebug() << "blue max: " << maxB << " min: " << minB << " ignored: " << ignoreB;
564 //qDebug() << "computed in: " << dt;
565
566 if (ignoreR && ignoreG && ignoreB) {
567 qDebug() << "[Auto Adjust] There is no need to adjust the image";
568 return false;
569 }
570
571 for (int rIdx = 0; rIdx < img.height(); rIdx++) {
572
573 for (int cIdx = 0; cIdx < bpl; ) {
574
575 // don't check values - speed (but you see under-/overflows anyway)
576 if (!ignoreR && *ptr < maxR)
577 *ptr = (uchar)qRound(255.0f*((float)*ptr-minR)/(maxR-minR));
578 else if (!ignoreR)
579 *ptr = 255;
580
581 ptr++;
582 cIdx++;
583
584 if (!ignoreG && *ptr < maxG)
585 *ptr = (uchar)qRound(255.0f*((float)*ptr-minG)/(maxG-minG));
586 else if (!ignoreG)
587 *ptr = 255;
588
589 ptr++;
590 cIdx++;
591
592 if (!ignoreB && *ptr < maxB)
593 *ptr = (uchar)qRound(255.0f*((float)*ptr-minB)/(maxB-minB));
594 else if (!ignoreB)
595 *ptr = 255;
596 ptr++;
597 cIdx++;
598
599 if (channels == 4) {
600 ptr++;
601 cIdx++;
602 }
603
604 }
605 ptr += pad;
606 }
607
608 qDebug() << "[Auto Adjust] image adjusted in: " << dt;
609
610 return true;
611 }
612
findHistPeak(const int * hist,float quantile)613 uchar DkImage::findHistPeak(const int* hist, float quantile) {
614
615 int histArea = 0;
616
617 for (int idx = 0; idx < 256; idx++)
618 histArea += hist[idx];
619
620 int sumBins = 0;
621
622 for (int idx = 255; idx >= 0; idx--) {
623
624 sumBins += hist[idx];
625
626 if (sumBins/(float)histArea > quantile) {
627 qDebug() << "max bin: " << idx;
628 return (uchar)idx;
629 }
630
631 }
632
633 qDebug() << "no max bin found... sum: " << sumBins;
634
635 return 255;
636 }
637
makeSquare(const QPixmap & pm)638 QPixmap DkImage::makeSquare(const QPixmap & pm) {
639
640 QRect r(QPoint(), pm.size());
641
642 if (r.width() > r.height()) {
643 r.setX(qFloor((r.width()-r.height())*0.5f));
644 r.setWidth(r.height());
645 }
646 else {
647 r.setY(qFloor((r.height()-r.width())*0.5f));
648 r.setHeight(r.width());
649 }
650
651 return pm.copy(r);
652 }
653
merge(const QVector<QImage> & imgs)654 QPixmap DkImage::merge(const QVector<QImage>& imgs) {
655
656 if (imgs.size() > 10) {
657 qWarning() << "DkImage::merge is built for a small amount of images, you gave me: " << imgs.size();
658 }
659
660 QPixmap pm;
661 int margin = 10;
662 int x = 0;
663 QPainter p;
664
665 for (const QImage& img : imgs) {
666
667 // init on first
668 if (pm.isNull()) {
669 pm = QPixmap(img.height()*imgs.size() + margin*(imgs.size()-1), img.height());
670 pm.fill(QColor(0,0,0,0));
671
672 p.begin(&pm);
673 }
674
675 QPixmap cpm = DkImage::makeSquare(QPixmap::fromImage(img));
676 QRect r(QPoint(x, 0), QSize(pm.height(), pm.height()));
677 p.drawPixmap(r, cpm);
678 x += r.width() + margin;
679 }
680
681 return pm;
682 }
683
cropToImage(const QImage & src,const DkRotatingRect & rect,const QColor & fillColor)684 QImage DkImage::cropToImage(const QImage & src, const DkRotatingRect & rect, const QColor & fillColor) {
685
686 QTransform tForm;
687 QPointF cImgSize;
688 rect.getTransform(tForm, cImgSize);
689
690 // illegal?
691 if (cImgSize.x() < 0.5f || cImgSize.y() < 0.5f)
692 return src;
693
694 double angle = DkMath::normAngleRad(rect.getAngle(), 0, CV_PI*0.5);
695 double minD = qMin(std::abs(angle), std::abs(angle-CV_PI*0.5));
696
697 QImage img = QImage(qRound(cImgSize.x()), qRound(cImgSize.y()), QImage::Format_ARGB32);
698 img.fill(fillColor.rgba());
699
700 // render the image into the new coordinate system
701 QPainter painter(&img);
702 painter.setWorldTransform(tForm);
703
704 // for rotated rects we want perfect anti-aliasing
705 if (minD > FLT_EPSILON)
706 painter.setRenderHints(QPainter::SmoothPixmapTransform | QPainter::Antialiasing);
707
708 painter.drawImage(QRect(QPoint(), src.size()), src, QRect(QPoint(), src.size()));
709 painter.end();
710
711 return img;
712 }
713
hueSaturation(const QImage & src,int hue,int sat,int brightness)714 QImage DkImage::hueSaturation(const QImage & src, int hue, int sat, int brightness) {
715
716 // nothing to do?
717 if (hue == 0 && sat == 0 && brightness == 0)
718 return src;
719
720 QImage imgR;
721
722 #ifdef WITH_OPENCV
723
724 // normalize brightness/saturation
725 int brightnessN = qRound(brightness / 100.0 * 255.0);
726 double satN = sat / 100.0 + 1.0;
727
728 cv::Mat hsvImg = DkImage::qImage2Mat(src);
729
730 if (hsvImg.channels() > 3)
731 cv::cvtColor(hsvImg, hsvImg, CV_RGBA2BGR);
732
733 cv::cvtColor(hsvImg, hsvImg, CV_BGR2HSV);
734
735 // apply hue/saturation changes
736 for (int rIdx = 0; rIdx < hsvImg.rows; rIdx++) {
737
738 unsigned char* iPtr = hsvImg.ptr<unsigned char>(rIdx);
739
740 for (int cIdx = 0; cIdx < hsvImg.cols*3; cIdx+=3) {
741
742 // adopt hue
743 int h = iPtr[cIdx] + hue;
744 if (h < 0) h += 180;
745 if (h >= 180) h -= 180;
746
747 iPtr[cIdx] = (unsigned char)h;
748
749 // adopt value
750 int v = iPtr[cIdx + 2] + brightnessN;
751 if (v < 0) v = 0;
752 if (v > 255) v = 255;
753 iPtr[cIdx + 2] = (unsigned char)v;
754
755 // adopt saturation
756 int s = qRound(iPtr[cIdx + 1] * satN);
757 if (s < 0) s = 0;
758 if (s > 255) s = 255;
759 iPtr[cIdx + 1] = (unsigned char)s;
760 }
761 }
762
763 cv::cvtColor(hsvImg, hsvImg, CV_HSV2BGR);
764 imgR = DkImage::mat2QImage(hsvImg);
765
766 #endif // WITH_OPENCV
767
768 return imgR;
769 }
770
exposure(const QImage & src,double exposure,double offset,double gamma)771 QImage DkImage::exposure(const QImage & src, double exposure, double offset, double gamma) {
772
773 if (exposure == 0.0 && offset == 0.0 && gamma == 1.0)
774 return src;
775
776 QImage imgR;
777 #ifdef WITH_OPENCV
778
779 cv::Mat rgbImg = DkImage::qImage2Mat(src);
780 rgbImg.convertTo(rgbImg, CV_16U, 256, offset*std::numeric_limits<unsigned short>::max());
781
782 if (rgbImg.channels() > 3)
783 cv::cvtColor(rgbImg, rgbImg, CV_RGBA2BGR);
784
785 if (exposure != 0.0)
786 rgbImg = exposureMat(rgbImg, exposure);
787
788 if (gamma != 1.0)
789 rgbImg = gammaMat(rgbImg, gamma);
790
791 rgbImg.convertTo(rgbImg, CV_8U, 1.0/256.0);
792 imgR = DkImage::mat2QImage(rgbImg);
793
794 #endif // WITH_OPENCV
795
796 return imgR;
797 }
798
bgColor(const QImage & src,const QColor & col)799 QImage DkImage::bgColor(const QImage & src, const QColor & col) {
800
801 QImage dst(src.size(), QImage::Format_RGB32);
802 dst.fill(col);
803
804 QPainter p(&dst);
805 p.drawImage(QPoint(0, 0), src);
806
807 return dst;
808 }
809
extractImageFromDataStream(const QByteArray & ba,const QByteArray & beginSignature,const QByteArray & endSignature,bool debugOutput)810 QByteArray DkImage::extractImageFromDataStream(const QByteArray & ba, const QByteArray & beginSignature, const QByteArray & endSignature, bool debugOutput) {
811
812
813 int bIdx = ba.indexOf(beginSignature);
814
815 if (bIdx == -1) {
816 qDebug() << "[ExtractImage] could not locate" << beginSignature;
817 return QByteArray();
818 }
819
820 int eIdx = ba.indexOf(endSignature, bIdx);
821
822 if (eIdx == -1) {
823 qDebug() << "[ExtractImage] could not locate" << endSignature;
824 return QByteArray();
825 }
826
827 QByteArray bac = ba.mid(bIdx, eIdx + endSignature.size() - bIdx);
828
829 if (debugOutput) {
830 qDebug() << "extracting image from stream...";
831 qDebug() << "cropping: [" << bIdx << eIdx << "]";
832 qDebug() << "original size: " << ba.size()/1024.0 << "KB" << "new size: " << bac.size()/1024.0 << "KB" << "difference:" << (ba.size()-bac.size())/1024 << "KB";
833 }
834
835 return bac;
836 }
837
fixSamsungPanorama(QByteArray & ba)838 QByteArray DkImage::fixSamsungPanorama(QByteArray & ba) {
839
840 // this code is based on python code from bcyrill
841 // see: https://gist.github.com/bcyrill/e59fda6c7ffe23c7c4b08a990804b269
842 // it fixes SAMSUNG panorama images that are not standard conformant by adding an EOI marker to the QByteArray
843 // see also: https://github.com/nomacs/nomacs/issues/254
844
845 if (ba.size() < 8)
846 return QByteArray();
847
848 QByteArray trailer = ba.right(4);
849
850 // is it a samsung panorama jpg?
851 if (trailer != QByteArray("SEFT"))
852 return QByteArray();
853
854 // TODO saveify:
855 int sefhPos = intFromByteArray(ba, ba.size() - 8) + 8;
856 trailer = ba.right(sefhPos);
857
858 // trailer starts with "SEFH"?
859 if (trailer.left(4) != QByteArray("SEFH"))
860 return QByteArray();
861
862 int endPos = ba.size();
863 int dirPos = endPos - sefhPos;
864
865 int count = intFromByteArray(trailer, 8);
866
867 int firstBlock = 0;
868 bool isPano = false;
869
870 for (int idx = 0; idx < count; idx++) {
871
872 int e = 12 + 12 * idx;
873
874 int noff = intFromByteArray(trailer, e + 4);
875 int size = intFromByteArray(trailer, e + 8);
876
877 if (firstBlock < noff)
878 firstBlock = noff;
879
880 QByteArray cdata = ba.mid(dirPos - noff, size);
881
882 int eoff = intFromByteArray(cdata, 4);
883 QString pi = cdata.mid(8, eoff);
884
885 if (pi == "Panorama_Shot_Info")
886 isPano = true;
887 }
888
889 if (!isPano)
890 return QByteArray();
891
892 int dataPos = dirPos - firstBlock;
893
894 // ok, append the missing marker
895 QByteArray nb;
896 nb.append(ba.left(dataPos));
897 nb.append(QByteArray("\xff\xd9"));
898 nb.append(ba.right(dataPos));
899 qDebug() << "SAMSUNG panorma fix: EOI marker injected";
900
901 return nb;
902
903 }
904
intFromByteArray(const QByteArray & ba,int pos)905 int DkImage::intFromByteArray(const QByteArray & ba, int pos) {
906
907 // TODO saveify:
908 QByteArray tmp = ba.mid(pos, 4);
909 const int* val = (const int*)(tmp.constData());
910
911 return *val;
912 }
913
914 #ifdef WITH_OPENCV
exposureMat(const cv::Mat & src,double exposure)915 cv::Mat DkImage::exposureMat(const cv::Mat& src, double exposure) {
916
917 int maxVal = std::numeric_limits<unsigned short>::max();
918 cv::Mat lut(1, maxVal+1, CV_16UC1);
919
920 double smooth = 0.5;
921 double cStops = std::log(exposure) / std::log(2.0);
922 double range = cStops*2.0;
923 double linRange = std::pow(2.0, range);
924 double x1 = (maxVal + 1.0) / linRange - 1.0;
925 double y1 = x1 * exposure;
926 double y2 = maxVal * (1.0 + (1.0 - smooth) * (exposure - 1.0));
927 double sq3x = std::pow(x1*x1 * maxVal, 1.0 / 3.0);
928 double B = (y2 - y1 + exposure * (3.0*x1 - 3.0*sq3x)) / (maxVal + 2.0*x1 - 3.0*sq3x);
929 double A = (exposure - B) * 3.0 * std::pow(x1*x1, 1.0 / 3.0);
930 double CC = y2 - A * std::pow(maxVal, 1.0 / 3.0) - B*maxVal;
931
932 for (int rIdx = 0; rIdx < lut.rows; rIdx++) {
933
934 unsigned short* ptrLut = lut.ptr<unsigned short>(rIdx);
935
936 for (int cIdx = 0; cIdx < lut.cols; cIdx++) {
937
938 double val = cIdx;
939 double valE = 0.0;
940
941 if (exposure < 1.0) {
942 valE = val * std::exp(exposure/10.0); // /10 - make it slower -> we go down till -20
943 }
944 else if (cIdx < x1) {
945 valE = val * exposure ;
946 }
947 else {
948 valE = A * std::pow(val, 1.0 / 3.0) + B*val + CC;
949 }
950
951 if (valE < 0)
952 ptrLut[cIdx] = 0;
953 else if (valE > maxVal)
954 ptrLut[cIdx] = (unsigned short)maxVal;
955 else
956 ptrLut[cIdx] = (unsigned short)qRound(valE);
957 }
958 }
959
960 return applyLUT(src, lut);
961 }
962
gammaMat(const cv::Mat & src,double gamma)963 cv::Mat DkImage::gammaMat(const cv::Mat& src, double gamma) {
964
965 int maxVal = std::numeric_limits<unsigned short>::max();
966 cv::Mat lut(1, maxVal+1, CV_16UC1);
967
968 for (int rIdx = 0; rIdx < lut.rows; rIdx++) {
969
970 unsigned short* ptrLut = lut.ptr<unsigned short>(rIdx);
971
972 for (int cIdx = 0; cIdx < lut.cols; cIdx++) {
973
974 double val = std::pow((double)cIdx / maxVal, 1.0 / gamma) * maxVal;
975 ptrLut[cIdx] = (unsigned short)qRound(val);
976 }
977 }
978
979 return applyLUT(src, lut);
980 }
981
applyLUT(const cv::Mat & src,const cv::Mat & lut)982 cv::Mat DkImage::applyLUT(const cv::Mat& src, const cv::Mat& lut) {
983
984 if (src.depth() != lut.depth()) {
985 qCritical() << "cannot apply LUT!";
986 return cv::Mat();
987 }
988
989 cv::Mat dst = src.clone();
990 const unsigned short* lutPtr = lut.ptr<unsigned short>();
991
992 for (int rIdx = 0; rIdx < src.rows; rIdx++) {
993
994 unsigned short* dPtr = dst.ptr<unsigned short>(rIdx);
995
996 for (int cIdx = 0; cIdx < src.cols*src.channels(); cIdx++) {
997 assert(dPtr[cIdx] >= 0 && dPtr[cIdx] < lut.cols);
998 dPtr[cIdx] = lutPtr[dPtr[cIdx]];
999 }
1000 }
1001
1002 return dst;
1003 }
1004 #endif // WITH_OPENCV
1005
colorizePixmap(const QPixmap & icon,const QColor & col,float opacity)1006 QPixmap DkImage::colorizePixmap(const QPixmap& icon, const QColor& col, float opacity) {
1007
1008 if (icon.isNull())
1009 return icon;
1010
1011 QPixmap glow = icon.copy();
1012 QPixmap sGlow = glow.copy();
1013 sGlow.fill(col);
1014
1015 QPainter painter(&glow);
1016 painter.setRenderHint(QPainter::SmoothPixmapTransform);
1017 painter.setCompositionMode(QPainter::CompositionMode_SourceIn); // check if this is the right composition mode
1018 painter.setOpacity(opacity);
1019 painter.drawPixmap(glow.rect(), sGlow);
1020
1021 return glow;
1022 }
1023
loadIcon(const QString & filePath,const QSize & size,const QColor & col)1024 QPixmap DkImage::loadIcon(const QString & filePath, const QSize& size, const QColor& col) {
1025
1026 if (filePath.isEmpty())
1027 return QPixmap();
1028
1029 QSize s = size * DkSettingsManager::param().dpiScaleFactor();
1030 if (size.isEmpty()) {
1031 int eis = DkSettingsManager::param().effectiveIconSize();
1032 s = QSize(eis, eis);
1033 }
1034
1035 QPixmap icon = loadFromSvg(filePath, s);
1036
1037 QColor c = (col.isValid()) ? col : DkSettingsManager::param().display().iconColor;
1038
1039 if (c.alpha() != 0)
1040 icon = colorizePixmap(icon, c);
1041
1042 return icon;
1043 }
1044
loadIcon(const QString & filePath,const QColor & col,const QSize & size)1045 QPixmap DkImage::loadIcon(const QString & filePath, const QColor& col, const QSize& size) {
1046
1047
1048 QSize is = size;
1049
1050 if (is.isNull()) {
1051 int s = DkSettingsManager::param().effectiveIconSize();
1052 is = QSize(s, s);
1053 }
1054
1055 QPixmap icon = loadFromSvg(filePath, is);
1056 icon = colorizePixmap(icon, col);
1057
1058 return icon;
1059 }
1060
loadFromSvg(const QString & filePath,const QSize & size)1061 QPixmap DkImage::loadFromSvg(const QString & filePath, const QSize & size) {
1062
1063 QSharedPointer<QSvgRenderer> svg(new QSvgRenderer(filePath));
1064
1065 QPixmap pm(size);
1066 pm.fill(QColor(0, 0, 0, 0)); // clear background
1067
1068 QPainter p(&pm);
1069 svg->render(&p);
1070
1071 return pm;
1072 }
1073
1074
1075 #ifdef WITH_OPENCV
1076
1077 /**
1078 * Converts a QImage to a Mat
1079 * @param img formats supported: ARGB32 | RGB32 | RGB888 | Indexed8
1080 * @return cv::Mat the corresponding Mat
1081 **/
qImage2Mat(const QImage & img)1082 cv::Mat DkImage::qImage2Mat(const QImage& img) {
1083
1084 cv::Mat mat2;
1085 QImage cImg; // must be initialized here! (otherwise the data is lost before clone())
1086
1087 try {
1088 //if (img.format() == QImage::Format_RGB32)
1089 // qDebug() << "we have an RGB32 in memory...";
1090
1091 if (img.format() == QImage::Format_ARGB32 || img.format() == QImage::Format_RGB32) {
1092 mat2 = cv::Mat(img.height(), img.width(), CV_8UC4, (uchar*)img.bits(), img.bytesPerLine());
1093 //qDebug() << "ARGB32 or RGB32";
1094 }
1095 else if (img.format() == QImage::Format_RGB888) {
1096 mat2 = cv::Mat(img.height(), img.width(), CV_8UC3, (uchar*)img.bits(), img.bytesPerLine());
1097 //qDebug() << "RGB888";
1098 }
1099 //// converting to indexed8 causes bugs in the qpainter
1100 //// see: http://qt-project.org/doc/qt-4.8/qimage.html
1101 //else if (img.format() == QImage::Format_Indexed8) {
1102 // mat2 = Mat(img.height(), img.width(), CV_8UC1, (uchar*)img.bits(), img.bytesPerLine());
1103 // //qDebug() << "indexed...";
1104 //}
1105 else {
1106 //qDebug() << "image flag: " << img.format();
1107 cImg = img.convertToFormat(QImage::Format_ARGB32);
1108 mat2 = cv::Mat(cImg.height(), cImg.width(), CV_8UC4, (uchar*)cImg.bits(), cImg.bytesPerLine());
1109 //qDebug() << "I need to convert the QImage to ARGB32";
1110 }
1111
1112 mat2 = mat2.clone(); // we need to own the pointer
1113 }
1114 catch (...) { // something went seriously wrong (e.g. out of memory)
1115 //DkNoMacs::dialog(QObject::tr("Sorry, could not convert image."));
1116 qDebug() << "[DkImage::qImage2Mat] could not convert image - something is seriously wrong down here...";
1117
1118 }
1119
1120 return mat2;
1121 }
1122
1123 /**
1124 * Converts a cv::Mat to a QImage.
1125 * @param img supported formats CV8UC1 | CV_8UC3 | CV_8UC4
1126 * @return QImage the corresponding QImage
1127 **/
mat2QImage(cv::Mat img)1128 QImage DkImage::mat2QImage(cv::Mat img) {
1129
1130 QImage qImg;
1131
1132 // since Mat header is copied, a new buffer should be allocated (check this!)
1133 if (img.depth() == CV_32F)
1134 img.convertTo(img, CV_8U, 255);
1135
1136 if (img.type() == CV_8UC1) {
1137 qImg = QImage(img.data, (int)img.cols, (int)img.rows, (int)img.step, QImage::Format_Indexed8); // opencv uses size_t for scaling in x64 applications
1138 //Mat tmp;
1139 //cvtColor(img, tmp, CV_GRAY2RGB); // Qt does not support writing to index8 images
1140 //img = tmp;
1141 }
1142 if (img.type() == CV_8UC3) {
1143
1144 //cv::cvtColor(img, img, CV_RGB2BGR);
1145 qImg = QImage(img.data, (int)img.cols, (int)img.rows, (int)img.step, QImage::Format_RGB888);
1146 }
1147 if (img.type() == CV_8UC4) {
1148 qImg = QImage(img.data, (int)img.cols, (int)img.rows, (int)img.step, QImage::Format_ARGB32);
1149 }
1150
1151 qImg = qImg.copy();
1152
1153 return qImg;
1154 }
1155
get1DGauss(double sigma)1156 cv::Mat DkImage::get1DGauss(double sigma) {
1157
1158 // correct -> checked with matlab reference
1159 int kernelsize = qCeil(sigma*3*2)+1;
1160 if (kernelsize < 3) kernelsize = 3;
1161 if ((kernelsize % 2) != 1) kernelsize+=1;
1162
1163 cv::Mat gKernel = cv::Mat(1, kernelsize, CV_32F);
1164 float* kernelPtr = gKernel.ptr<float>();
1165
1166 for (int idx = 0, x = -qFloor(kernelsize/2); idx < kernelsize; idx++,x++) {
1167
1168 kernelPtr[idx] = (float)(exp(-(x*x)/(2*sigma*sigma))); // 1/(sqrt(2pi)*sigma) -> discrete normalization
1169 }
1170
1171 if (sum(gKernel).val[0] != 0)
1172 gKernel *= 1.0f/sum(gKernel).val[0];
1173 else
1174 qWarning() << "The kernel sum is zero\n";
1175
1176 return gKernel;
1177 }
1178
linearToGamma(cv::Mat & img)1179 void DkImage::linearToGamma(cv::Mat& img) {
1180
1181 QVector<unsigned short> gt = getLinear2GammaTable<unsigned short>();
1182 mapGammaTable(img, gt);
1183 }
1184
gammaToLinear(cv::Mat & img)1185 void DkImage::gammaToLinear(cv::Mat& img) {
1186
1187 QVector<unsigned short> gt = getGamma2LinearTable<unsigned short>();
1188 mapGammaTable(img, gt);
1189 }
1190
mapGammaTable(cv::Mat & img,const QVector<unsigned short> & gammaTable)1191 void DkImage::mapGammaTable(cv::Mat& img, const QVector<unsigned short>& gammaTable) {
1192
1193 DkTimer dt;
1194
1195 for (int rIdx = 0; rIdx < img.rows; rIdx++) {
1196
1197 unsigned short* mPtr = img.ptr<unsigned short>(rIdx);
1198
1199 for (int cIdx = 0; cIdx < img.cols; cIdx++) {
1200
1201 for (int channelIdx = 0; channelIdx < img.channels(); channelIdx++, mPtr++) {
1202
1203 if (*mPtr < 0 || *mPtr > gammaTable.size()) {
1204 qDebug() << "WRONG VALUE: " << *mPtr;
1205 continue;
1206 }
1207 if ((int)gammaTable[*mPtr] < 0 || (int)gammaTable[*mPtr] > USHRT_MAX) {
1208 qDebug() << "WRONG VALUE: " << *mPtr;
1209 continue;
1210 }
1211
1212 *mPtr = gammaTable[*mPtr];
1213 }
1214 }
1215 }
1216
1217 qDebug() << "gamma computation takes: " << dt;
1218 }
1219
logPolar(const cv::Mat & src,cv::Mat & dst,cv::Point2d center,double scaleLog,double angle,double scale)1220 void DkImage::logPolar(const cv::Mat& src, cv::Mat& dst, cv::Point2d center, double scaleLog, double angle, double scale) {
1221
1222 cv::Mat mapx, mapy;
1223
1224 cv::Size ssize, dsize;
1225 ssize = src.size();
1226 dsize = dst.size();
1227
1228 mapx = cv::Mat(dsize.height, dsize.width, CV_32F);
1229 mapy = cv::Mat(dsize.height, dsize.width, CV_32F);
1230
1231 double xDist = dst.cols - center.x;
1232 double yDist = dst.rows - center.y;
1233
1234 double radius = std::sqrt(xDist*xDist + yDist*yDist);
1235
1236 scale *= src.cols / std::log(radius / scaleLog + 1.0);
1237
1238 int x, y;
1239 cv::Mat bufx, bufy, bufp, bufa;
1240 double ascale = ssize.height / (2 * CV_PI);
1241 cv::AutoBuffer<float> _buf(4 * dsize.width);
1242 float* buf = _buf;
1243
1244 bufx = cv::Mat(1, dsize.width, CV_32F, buf);
1245 bufy = cv::Mat(1, dsize.width, CV_32F, buf + dsize.width);
1246 bufp = cv::Mat(1, dsize.width, CV_32F, buf + dsize.width * 2);
1247 bufa = cv::Mat(1, dsize.width, CV_32F, buf + dsize.width * 3);
1248
1249 for (x = 0; x < dsize.width; x++)
1250 bufx.ptr<float>()[x] = (float)(x - center.x);
1251
1252 for (y = 0; y < dsize.height; y++) {
1253 float* mx = mapx.ptr<float>(y);
1254 float* my = mapy.ptr<float>(y);
1255
1256 for (x = 0; x < dsize.width; x++)
1257 bufy.ptr<float>()[x] = (float)(y - center.y);
1258
1259 cv::cartToPolar(bufx, bufy, bufp, bufa);
1260
1261 for (x = 0; x < dsize.width; x++) {
1262 bufp.ptr<float>()[x] /= (float)scaleLog;
1263 bufp.ptr<float>()[x] += 1.0f;
1264 }
1265
1266 cv::log(bufp, bufp);
1267
1268 for (x = 0; x < dsize.width; x++) {
1269 double rho = bufp.ptr<float>()[x] * scale;
1270 double phi = bufa.ptr<float>()[x] + angle;
1271
1272 if (phi < 0)
1273 phi += 2 * CV_PI;
1274 else if (phi > 2 * CV_PI)
1275 phi -= 2 * CV_PI;
1276
1277 phi *= ascale;
1278
1279 //qDebug() << "phi: " << bufa.data.fl[x];
1280
1281 mx[x] = (float)rho;
1282 my[x] = (float)phi;
1283 }
1284 }
1285
1286 cv::remap(src, dst, mapx, mapy, CV_INTER_AREA, IPL_BORDER_REPLICATE);
1287 }
1288
tinyPlanet(QImage & img,double scaleLog,double angle,QSize s,bool invert)1289 void DkImage::tinyPlanet(QImage& img, double scaleLog, double angle, QSize s, bool invert /* = false */) {
1290
1291 QTransform rotationMatrix;
1292 rotationMatrix.rotate((invert) ? (double)-90 : (double)90);
1293 img = img.transformed(rotationMatrix);
1294
1295 // make square
1296 img = img.scaled(s, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
1297
1298 cv::Mat mImg = DkImage::qImage2Mat(img);
1299
1300 qDebug() << "scale log: " << scaleLog << " inverted: " << invert;
1301 logPolar(mImg, mImg, cv::Point2d(mImg.cols*0.5, mImg.rows*0.5), scaleLog, angle);
1302
1303 img = DkImage::mat2QImage(mImg);
1304 }
1305
1306 #endif
1307
gaussianBlur(QImage & img,float sigma)1308 bool DkImage::gaussianBlur(QImage& img, float sigma) {
1309
1310 #ifdef WITH_OPENCV
1311 DkTimer dt;
1312 cv::Mat imgCv = DkImage::qImage2Mat(img);
1313
1314 cv::Mat imgG;
1315 cv::Mat gx = cv::getGaussianKernel(qRound(4 * sigma + 1), sigma);
1316 cv::Mat gy = gx.t();
1317 cv::sepFilter2D(imgCv, imgG, CV_8U, gx, gy);
1318 img = DkImage::mat2QImage(imgG);
1319
1320 qDebug() << "gaussian blur takes: " << dt;
1321 #else
1322 Q_UNUSED(img);
1323 Q_UNUSED(sigma);
1324 #endif
1325
1326 return true;
1327 }
1328
unsharpMask(QImage & img,float sigma,float weight)1329 bool DkImage::unsharpMask(QImage& img, float sigma, float weight) {
1330
1331 #ifdef WITH_OPENCV
1332 DkTimer dt;
1333 //DkImage::gammaToLinear(img);
1334 cv::Mat imgCv = DkImage::qImage2Mat(img);
1335
1336 cv::Mat imgG;
1337 cv::Mat gx = cv::getGaussianKernel(qRound(4*sigma+1), sigma);
1338 cv::Mat gy = gx.t();
1339 cv::sepFilter2D(imgCv, imgG, CV_8U, gx, gy);
1340 //cv::GaussianBlur(imgCv, imgG, cv::Size(4*sigma+1, 4*sigma+1), sigma); // this is awesomely slow
1341 cv::addWeighted(imgCv, weight, imgG, 1-weight, 0, imgCv);
1342 img = DkImage::mat2QImage(imgCv);
1343
1344 qDebug() << "unsharp mask takes: " << dt;
1345 //DkImage::linearToGamma(img);
1346 #else
1347 Q_UNUSED(img);
1348 Q_UNUSED(sigma);
1349 Q_UNUSED(weight);
1350 #endif
1351
1352 return true;
1353 }
1354
createThumb(const QImage & image,int maxSize)1355 QImage DkImage::createThumb(const QImage& image, int maxSize) {
1356
1357 if (image.isNull())
1358 return image;
1359
1360 int maxThumbSize = maxSize == -1 ? (int)(max_thumb_size * DkSettingsManager::param().dpiScaleFactor()) : maxSize;
1361 int imgW = image.width();
1362 int imgH = image.height();
1363
1364 if (imgW > maxThumbSize || imgH > maxThumbSize) {
1365 if (imgW > imgH) {
1366 imgH = qRound((float)maxThumbSize / imgW * imgH);
1367 imgW = maxThumbSize;
1368 }
1369 else if (imgW < imgH) {
1370 imgW = qRound((float)maxThumbSize / imgH * imgW);
1371 imgH = maxThumbSize;
1372 }
1373 else {
1374 imgW = maxThumbSize;
1375 imgH = maxThumbSize;
1376 }
1377 }
1378
1379 // fast downscaling
1380 QImage thumb = image.scaled(QSize(imgW*2, imgH*2), Qt::KeepAspectRatio, Qt::FastTransformation);
1381 thumb = thumb.scaled(QSize(imgW, imgH), Qt::KeepAspectRatio, Qt::SmoothTransformation);
1382
1383 //qDebug() << "thumb size in createThumb: " << thumb.size() << " format: " << thumb.format();
1384
1385 return thumb;
1386 }
1387
1388 // NOTE: this is just for fun (all images in the world : )
addToImage(QImage & img,unsigned char val)1389 bool DkImage::addToImage(QImage& img, unsigned char val) {
1390
1391 // number of bytes per line used
1392 int bpl = (img.width() * img.depth() + 7) / 8;
1393 int pad = img.bytesPerLine() - bpl;
1394 uchar* ptr = img.bits();
1395 bool done = false;
1396
1397 for (int rIdx = 0; rIdx < img.height(); rIdx++) {
1398
1399 for (int cIdx = 0; cIdx < bpl; cIdx++) {
1400
1401 // add it & we're done
1402 if (*ptr <= 255-val) {
1403 *ptr += val;
1404 done = true;
1405 break;
1406 }
1407
1408 int ov = *ptr+(int)val; // compute the overflow
1409 val = (char)(ov-255);
1410
1411 *ptr = val;
1412 ptr++;
1413 }
1414
1415 if (done)
1416 break;
1417
1418 ptr += pad;
1419 }
1420
1421 return done;
1422 }
1423
getMeanColor(const QImage & img)1424 QColor DkImage::getMeanColor(const QImage& img) {
1425
1426 // some speed-up params
1427 int nC = qRound(img.depth()/8.0f);
1428 int rStep = qRound(img.height()/100.0f)+1;
1429 int cStep = qRound(img.width()/100.0f)+1;
1430 int numCols = 42;
1431
1432 int offset = (nC > 1) ? 1 : 0; // no offset for grayscale images
1433 QMap<QRgb, int> colLookup;
1434 int maxColCount = 0;
1435 QRgb maxCol = 0;
1436
1437 for (int rIdx = 0; rIdx < img.height(); rIdx += rStep) {
1438
1439 const unsigned char* pixel = img.constScanLine(rIdx);
1440
1441 for (int cIdx = 0; cIdx < img.width()*nC; cIdx += cStep*nC) {
1442
1443 QColor cColC(qRound(pixel[cIdx+2*offset]/255.0f*numCols),
1444 qRound(pixel[cIdx+offset]/255.0f*numCols),
1445 qRound(pixel[cIdx]/255.0f*numCols));
1446 QRgb cCol = cColC.rgb();
1447
1448 //// skip black
1449 //if (cColC.saturation() < 10)
1450 // continue;
1451 if (qRed(cCol) < 3 && qGreen(cCol) < 3 && qBlue(cCol) < 3)
1452 continue;
1453 if (qRed(cCol) > numCols-3 && qGreen(cCol) > numCols-3 && qBlue(cCol) > numCols-3)
1454 continue;
1455
1456 if (colLookup.contains(cCol)) {
1457 colLookup[cCol]++;
1458 }
1459 else
1460 colLookup[cCol] = 1;
1461
1462 if (colLookup[cCol] > maxColCount) {
1463 maxCol = cCol;
1464 maxColCount = colLookup[cCol];
1465 }
1466 }
1467 }
1468
1469 if (maxColCount > 0)
1470 return QColor(qRound((float)qRed(maxCol)/numCols*255), qRound((float)qGreen(maxCol)/numCols*255), qRound((float)qBlue(maxCol)/numCols*255));
1471 else
1472 return DkSettingsManager::param().display().hudBgColor;
1473 }
1474
1475
1476 // DkImageStorage --------------------------------------------------------------------
DkImageStorage(const QImage & img)1477 DkImageStorage::DkImageStorage(const QImage& img) {
1478
1479 mImg = img;
1480
1481 mWaitTimer = new QTimer(this);
1482 mWaitTimer->setSingleShot(true);
1483 mWaitTimer->setInterval(100);
1484
1485 init();
1486
1487 connect(mWaitTimer, SIGNAL(timeout()), this, SLOT(compute()), Qt::UniqueConnection);
1488 connect(&mFutureWatcher, SIGNAL(finished()), this, SLOT(imageComputed()), Qt::UniqueConnection);
1489 connect(DkActionManager::instance().action(DkActionManager::menu_view_anti_aliasing), SIGNAL(toggled(bool)), this, SLOT(antiAliasingChanged(bool)), Qt::UniqueConnection);
1490 }
1491
init()1492 void DkImageStorage::init() {
1493
1494 mComputeState = l_not_computed;
1495 mScaledImg = QImage();
1496 mWaitTimer->stop();
1497 mScale = 1.0;
1498 }
1499
setImage(const QImage & img)1500 void DkImageStorage::setImage(const QImage& img) {
1501
1502 init();
1503 mImg = img;
1504
1505 mComputeState = l_cancelled;
1506 }
1507
antiAliasingChanged(bool antiAliasing)1508 void DkImageStorage::antiAliasingChanged(bool antiAliasing) {
1509
1510 DkSettingsManager::param().display().antiAliasing = antiAliasing;
1511
1512 if (!antiAliasing)
1513 init();
1514
1515 emit infoSignal((antiAliasing) ? tr("Anti Aliasing Enabled") : tr("Anti Aliasing Disabled"));
1516 emit imageUpdated();
1517
1518 }
1519
imageConst() const1520 QImage DkImageStorage::imageConst() const {
1521 return mImg;
1522 }
1523
image(double scale)1524 QImage DkImageStorage::image(double scale) {
1525
1526 if (scale >= 1.0 || mImg.isNull() || !DkSettingsManager::param().display().antiAliasing)
1527 return mImg;
1528
1529 QSize s = mImg.size() * scale;
1530
1531 if (s == mScaledImg.size())
1532 return mScaledImg;
1533
1534 if (mComputeState != l_computing) {
1535 // trigger a new computation
1536 init();
1537 mScale = scale;
1538 mWaitTimer->start();
1539 }
1540
1541 // currently no alternative is available
1542 return mImg;
1543 }
1544
cancel()1545 void DkImageStorage::cancel() {
1546 mComputeState = l_cancelled;
1547 }
1548
compute()1549 void DkImageStorage::compute() {
1550
1551 if (mComputeState == l_computed) {
1552 emit imageUpdated();
1553 qDebug() << "image is up-to-date in DkImageStorage::compute...";
1554 return;
1555 }
1556
1557 if (mComputeState == l_computing) // don't compute twice
1558 return;
1559
1560 mComputeState = l_computing;
1561
1562 mFutureWatcher.setFuture(QtConcurrent::run(this, &nmc::DkImageStorage::computeIntern, mImg, mScale));
1563 }
1564
computeIntern(const QImage & src,double scale)1565 QImage DkImageStorage::computeIntern(const QImage & src, double scale) {
1566
1567 if (scale >= 1.0)
1568 return src;
1569
1570 DkTimer dt;
1571 QImage resizedImg = src;
1572
1573 if (!DkSettingsManager::param().display().highQualityAntiAliasing) {
1574 QSize cs = src.size();
1575
1576 // fast down sampling until the image is twice times full HD
1577 while (qMin(cs.width(), cs.height()) > 2 * 4000) {
1578 cs *= 0.5;
1579 }
1580
1581 // for extreme panorama images the Qt scaling crashes (if we have a width > 30000) so we simply
1582 if (cs != mImg.size()) {
1583 resizedImg = resizedImg.scaled(cs, Qt::KeepAspectRatio, Qt::FastTransformation);
1584 }
1585 }
1586
1587 QSize s = mImg.size() * scale;
1588
1589 if (s.height() == 0)
1590 s.setHeight(1);
1591 if (s.width() == 0)
1592 s.setWidth(1);
1593
1594 #ifdef WITH_OPENCV
1595 cv::Mat rImgCv = DkImage::qImage2Mat(resizedImg);
1596 cv::Mat tmp;
1597 cv::resize(rImgCv, tmp, cv::Size(s.width(), s.height()), 0, 0, CV_INTER_AREA);
1598 resizedImg = DkImage::mat2QImage(tmp);
1599 #else
1600 resizedImg = resizedImg.scaled(s, Qt::KeepAspectRatio, Qt::SmoothTransformation);
1601 #endif
1602
1603 return resizedImg;
1604 }
1605
imageComputed()1606 void DkImageStorage::imageComputed() {
1607
1608 if (mComputeState == l_cancelled) {
1609 mComputeState = l_not_computed;
1610 return;
1611 }
1612
1613 mScaledImg = mFutureWatcher.result();
1614
1615 mComputeState = (mScaledImg.isNull()) ? l_empty : l_computed;
1616
1617 if (mComputeState == l_computed)
1618 emit imageUpdated();
1619 else
1620 qWarning() << "could not compute scale factor" << mScale;
1621 }
1622 }
1623