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