QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
Loading...
Searching...
No Matches
qgsimageoperation.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsimageoperation.cpp
3 ----------------------
4 begin : January 2015
5 copyright : (C) 2015 by Nyall Dawson
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgsimageoperation.h"
19
20#include "qgis.h"
21#include "qgscolorramp.h"
22#include "qgsfeedback.h"
23#include "qgslogger.h"
24
25#include <QColor>
26#include <QPainter>
27#include <QString>
28#include <QtConcurrentMap>
29
30using namespace Qt::StringLiterals;
31
32//determined via trial-and-error. Could possibly be optimised, or varied
33//depending on the image size.
34#define BLOCK_THREADS 16
35
36#define INF 1E20
37
39
40template<typename PixelOperation> void QgsImageOperation::runPixelOperation( QImage &image, PixelOperation &operation, QgsFeedback *feedback )
41{
42 if ( static_cast< qgssize >( image.height() ) * image.width() < 100000 )
43 {
44 //small image, don't multithread
45 //this threshold was determined via testing various images
46 runPixelOperationOnWholeImage( image, operation, feedback );
47 }
48 else
49 {
50 //large image, multithread operation
51 QgsImageOperation::ProcessBlockUsingPixelOperation<PixelOperation> blockOp( operation, feedback );
52 runBlockOperationInThreads( image, blockOp, QgsImageOperation::ByRow );
53 }
54}
55
56template<typename PixelOperation> void QgsImageOperation::runPixelOperationOnWholeImage( QImage &image, PixelOperation &operation, QgsFeedback *feedback )
57{
58 int height = image.height();
59 int width = image.width();
60 for ( int y = 0; y < height; ++y )
61 {
62 if ( feedback && feedback->isCanceled() )
63 break;
64
65 QRgb *ref = reinterpret_cast< QRgb * >( image.scanLine( y ) );
66 for ( int x = 0; x < width; ++x )
67 {
68 operation( ref[x], x, y );
69 }
70 }
71}
72
73//rect operations
74
75template<typename RectOperation> void QgsImageOperation::runRectOperation( QImage &image, RectOperation &operation )
76{
77 //possibly could be tweaked for rect operations
78 if ( static_cast< qgssize >( image.height() ) * image.width() < 100000 )
79 {
80 //small image, don't multithread
81 //this threshold was determined via testing various images
82 runRectOperationOnWholeImage( image, operation );
83 }
84 else
85 {
86 //large image, multithread operation
87 runBlockOperationInThreads( image, operation, ByRow );
88 }
89}
90
91template<class RectOperation> void QgsImageOperation::runRectOperationOnWholeImage( QImage &image, RectOperation &operation )
92{
93 ImageBlock fullImage;
94 fullImage.beginLine = 0;
95 fullImage.endLine = image.height();
96 fullImage.lineLength = image.width();
97 fullImage.image = &image;
98
99 operation( fullImage );
100}
101
102//linear operations
103
104template<typename LineOperation> void QgsImageOperation::runLineOperation( QImage &image, LineOperation &operation, QgsFeedback *feedback )
105{
106 //possibly could be tweaked for rect operations
107 if ( static_cast< qgssize >( image.height() ) * image.width() < 100000 )
108 {
109 //small image, don't multithread
110 //this threshold was determined via testing various images
111 runLineOperationOnWholeImage( image, operation, feedback );
112 }
113 else
114 {
115 //large image, multithread operation
116 QgsImageOperation::ProcessBlockUsingLineOperation<LineOperation> blockOp( operation );
117 runBlockOperationInThreads( image, blockOp, operation.direction() );
118 }
119}
120
121template<class LineOperation> void QgsImageOperation::runLineOperationOnWholeImage( QImage &image, LineOperation &operation, QgsFeedback *feedback )
122{
123 int height = image.height();
124 int width = image.width();
125
126 //do something with whole lines
127 int bpl = image.bytesPerLine();
128 if ( operation.direction() == ByRow )
129 {
130 for ( int y = 0; y < height; ++y )
131 {
132 if ( feedback && feedback->isCanceled() )
133 break;
134
135 QRgb *ref = reinterpret_cast< QRgb * >( image.scanLine( y ) );
136 operation( ref, width, bpl );
137 }
138 }
139 else
140 {
141 //by column
142 unsigned char *ref = image.scanLine( 0 );
143 for ( int x = 0; x < width; ++x, ref += 4 )
144 {
145 if ( feedback && feedback->isCanceled() )
146 break;
147
148 operation( reinterpret_cast< QRgb * >( ref ), height, bpl );
149 }
150 }
151}
152
153
154//multithreaded block processing
155
156template<typename BlockOperation> void QgsImageOperation::runBlockOperationInThreads( QImage &image, BlockOperation &operation, LineOperationDirection direction )
157{
158 QList< ImageBlock > blocks;
159 unsigned int height = image.height();
160 unsigned int width = image.width();
161
162 unsigned int blockDimension1 = ( direction == QgsImageOperation::ByRow ) ? height : width;
163 unsigned int blockDimension2 = ( direction == QgsImageOperation::ByRow ) ? width : height;
164
165 //chunk image up into vertical blocks
166 blocks.reserve( BLOCK_THREADS );
167 unsigned int begin = 0;
168 unsigned int blockLen = blockDimension1 / BLOCK_THREADS;
169 for ( unsigned int block = 0; block < BLOCK_THREADS; ++block, begin += blockLen )
170 {
171 ImageBlock newBlock;
172 newBlock.beginLine = begin;
173 //make sure last block goes to end of image
174 newBlock.endLine = block < ( BLOCK_THREADS - 1 ) ? begin + blockLen : blockDimension1;
175 newBlock.lineLength = blockDimension2;
176 newBlock.image = &image;
177 blocks << newBlock;
178 }
179
180 //process blocks
181 QtConcurrent::blockingMap( blocks, operation );
182}
183
184
186
187//
188//operation specific code
189//
190
191//grayscale
192
193void QgsImageOperation::convertToGrayscale( QImage &image, const GrayscaleMode mode, QgsFeedback *feedback )
194{
195 if ( mode == GrayscaleOff )
196 {
197 return;
198 }
199
200 image.detach();
201 GrayscalePixelOperation operation( mode );
202 runPixelOperation( image, operation, feedback );
203}
204
205void QgsImageOperation::GrayscalePixelOperation::operator()( QRgb &rgb, const int x, const int y ) const
206{
207 Q_UNUSED( x )
208 Q_UNUSED( y )
209 switch ( mMode )
210 {
211 case GrayscaleOff:
212 return;
214 grayscaleLuminosityOp( rgb );
215 return;
216 case GrayscaleAverage:
217 grayscaleAverageOp( rgb );
218 return;
220 default:
221 grayscaleLightnessOp( rgb );
222 return;
223 }
224}
225
226void QgsImageOperation::grayscaleLightnessOp( QRgb &rgb )
227{
228 int red = qRed( rgb );
229 int green = qGreen( rgb );
230 int blue = qBlue( rgb );
231
232 int min = std::min( std::min( red, green ), blue );
233 int max = std::max( std::max( red, green ), blue );
234
235 int lightness = std::min( ( min + max ) / 2, 255 );
236 rgb = qRgba( lightness, lightness, lightness, qAlpha( rgb ) );
237}
238
239void QgsImageOperation::grayscaleLuminosityOp( QRgb &rgb )
240{
241 int luminosity = 0.21 * qRed( rgb ) + 0.72 * qGreen( rgb ) + 0.07 * qBlue( rgb );
242 rgb = qRgba( luminosity, luminosity, luminosity, qAlpha( rgb ) );
243}
244
245void QgsImageOperation::grayscaleAverageOp( QRgb &rgb )
246{
247 int average = ( qRed( rgb ) + qGreen( rgb ) + qBlue( rgb ) ) / 3;
248 rgb = qRgba( average, average, average, qAlpha( rgb ) );
249}
250
251
252//brightness/contrast
253
254void QgsImageOperation::adjustBrightnessContrast( QImage &image, const int brightness, const double contrast, QgsFeedback *feedback )
255{
256 image.detach();
257 BrightnessContrastPixelOperation operation( brightness, contrast );
258 runPixelOperation( image, operation, feedback );
259}
260
261void QgsImageOperation::BrightnessContrastPixelOperation::operator()( QRgb &rgb, const int x, const int y ) const
262{
263 Q_UNUSED( x )
264 Q_UNUSED( y )
265 int red = adjustColorComponent( qRed( rgb ), mBrightness, mContrast );
266 int blue = adjustColorComponent( qBlue( rgb ), mBrightness, mContrast );
267 int green = adjustColorComponent( qGreen( rgb ), mBrightness, mContrast );
268 rgb = qRgba( red, green, blue, qAlpha( rgb ) );
269}
270
271int QgsImageOperation::adjustColorComponent( int colorComponent, int brightness, double contrastFactor )
272{
273 return std::clamp( static_cast< int >( ( ( ( ( ( colorComponent / 255.0 ) - 0.5 ) * contrastFactor ) + 0.5 ) * 255 ) + brightness ), 0, 255 );
274}
275
276//hue/saturation
277
278void QgsImageOperation::adjustHueSaturation( QImage &image, const double saturation, const QColor &colorizeColor, const double colorizeStrength, QgsFeedback *feedback )
279{
280 image.detach();
281 HueSaturationPixelOperation operation( saturation, colorizeColor.isValid() && colorizeStrength > 0.0, colorizeColor.hue(), colorizeColor.saturation(), colorizeStrength );
282 runPixelOperation( image, operation, feedback );
283}
284
285void QgsImageOperation::HueSaturationPixelOperation::operator()( QRgb &rgb, const int x, const int y ) const
286{
287 Q_UNUSED( x )
288 Q_UNUSED( y )
289 QColor tmpColor( rgb );
290 int h, s, l;
291 tmpColor.getHsl( &h, &s, &l );
292
293 if ( mSaturation < 1.0 )
294 {
295 // Lowering the saturation. Use a simple linear relationship
296 s = std::min( static_cast< int >( s * mSaturation ), 255 );
297 }
298 else if ( mSaturation > 1.0 )
299 {
300 // Raising the saturation. Use a saturation curve to prevent
301 // clipping at maximum saturation with ugly results.
302 s = std::min( static_cast< int >( 255. * ( 1 - std::pow( 1 - ( s / 255. ), std::pow( mSaturation, 2 ) ) ) ), 255 );
303 }
304
305 if ( mColorize )
306 {
307 h = mColorizeHue;
308 s = mColorizeSaturation;
309 if ( mColorizeStrength < 1.0 )
310 {
311 //get rgb for colorized color
312 QColor colorizedColor = QColor::fromHsl( h, s, l );
313 int colorizedR, colorizedG, colorizedB;
314 colorizedColor.getRgb( &colorizedR, &colorizedG, &colorizedB );
315
316 // Now, linearly scale by colorize strength
317 int r = mColorizeStrength * colorizedR + ( 1 - mColorizeStrength ) * tmpColor.red();
318 int g = mColorizeStrength * colorizedG + ( 1 - mColorizeStrength ) * tmpColor.green();
319 int b = mColorizeStrength * colorizedB + ( 1 - mColorizeStrength ) * tmpColor.blue();
320
321 rgb = qRgba( r, g, b, qAlpha( rgb ) );
322 return;
323 }
324 }
325
326 tmpColor.setHsl( h, s, l, qAlpha( rgb ) );
327 rgb = tmpColor.rgba();
328}
329
330//multiply opacity
331
332void QgsImageOperation::multiplyOpacity( QImage &image, const double factor, QgsFeedback *feedback )
333{
334 if ( qgsDoubleNear( factor, 1.0 ) )
335 {
336 //no change
337 return;
338 }
339 else if ( factor < 1.0 )
340 {
341 //decreasing opacity - we can use the faster DestinationIn composition mode
342 //to reduce the alpha channel
343 QColor transparentFillColor = QColor( 0, 0, 0, 255 * factor );
344 if ( image.format() == QImage::Format_Indexed8 )
345 image = image.convertToFormat( QImage::Format_ARGB32 );
346 else
347 image.detach();
348
349 QPainter painter( &image );
350 painter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
351 painter.fillRect( 0, 0, image.width(), image.height(), transparentFillColor );
352 painter.end();
353 }
354 else
355 {
356 //increasing opacity - run this as a pixel operation for multithreading
357 image.detach();
358 MultiplyOpacityPixelOperation operation( factor );
359 runPixelOperation( image, operation, feedback );
360 }
361}
362
363void QgsImageOperation::MultiplyOpacityPixelOperation::operator()( QRgb &rgb, const int x, const int y ) const
364{
365 Q_UNUSED( x )
366 Q_UNUSED( y )
367 rgb = qRgba( qRed( rgb ), qGreen( rgb ), qBlue( rgb ), std::clamp( std::round( mFactor * qAlpha( rgb ) ), 0.0, 255.0 ) );
368}
369
370// overlay color
371
372void QgsImageOperation::overlayColor( QImage &image, const QColor &color )
373{
374 QColor opaqueColor = color;
375 opaqueColor.setAlpha( 255 );
376
377 //use QPainter SourceIn composition mode to overlay color (fast)
378 //this retains image's alpha channel but replaces color
379 image.detach();
380 QPainter painter( &image );
381 painter.setCompositionMode( QPainter::CompositionMode_SourceIn );
382 painter.fillRect( 0, 0, image.width(), image.height(), opaqueColor );
383 painter.end();
384}
385
386// distance transform
387
388void QgsImageOperation::distanceTransform( QImage &image, const DistanceTransformProperties &properties, QgsFeedback *feedback )
389{
390 if ( !properties.ramp )
391 {
392 QgsDebugError( u"no color ramp specified for distance transform"_s );
393 return;
394 }
395
396 //first convert to 1 bit alpha mask array
397 std::unique_ptr<double[]> array( new double[static_cast< qgssize >( image.width() ) * image.height()] );
398 if ( feedback && feedback->isCanceled() )
399 return;
400
401 image.detach();
402 ConvertToArrayPixelOperation convertToArray( image.width(), array.get(), properties.shadeExterior );
403 runPixelOperation( image, convertToArray, feedback );
404 if ( feedback && feedback->isCanceled() )
405 return;
406
407 //calculate distance transform (single threaded only)
408 distanceTransform2d( array.get(), image.width(), image.height(), feedback );
409 if ( feedback && feedback->isCanceled() )
410 return;
411
412 double spread;
413 if ( properties.useMaxDistance )
414 {
415 spread = std::sqrt( maxValueInDistanceTransformArray( array.get(), image.width() * image.height() ) );
416 }
417 else
418 {
419 spread = properties.spread;
420 }
421
422 if ( feedback && feedback->isCanceled() )
423 return;
424
425 //shade distance transform
426 ShadeFromArrayOperation shadeFromArray( image.width(), array.get(), spread, properties );
427 runPixelOperation( image, shadeFromArray, feedback );
428}
429
430void QgsImageOperation::ConvertToArrayPixelOperation::operator()( QRgb &rgb, const int x, const int y )
431{
432 qgssize idx = y * static_cast< qgssize >( mWidth ) + x;
433 if ( mExterior )
434 {
435 if ( qAlpha( rgb ) > 0 )
436 {
437 //opaque pixel, so zero distance
438 mArray[idx] = 1 - qAlpha( rgb ) / 255.0;
439 }
440 else
441 {
442 //transparent pixel, so initially set distance as infinite
443 mArray[idx] = INF;
444 }
445 }
446 else
447 {
448 //TODO - fix this for semi-transparent pixels
449 if ( qAlpha( rgb ) == 255 )
450 {
451 mArray[idx] = INF;
452 }
453 else
454 {
455 mArray[idx] = 0;
456 }
457 }
458}
459
460//fast distance transform code, adapted from http://cs.brown.edu/~pff/dt/
461
462/* distance transform of a 1d function using squared distance */
463void QgsImageOperation::distanceTransform1d( double *f, int n, int *v, double *z, double *d )
464{
465 int k = 0;
466 v[0] = 0;
467 z[0] = -INF;
468 z[1] = +INF;
469 for ( int q = 1; q <= n - 1; q++ )
470 {
471 double s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
472 while ( s <= z[k] )
473 {
474 k--;
475 s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
476 }
477 k++;
478 v[k] = q;
479 z[k] = s;
480 z[k + 1] = +INF;
481 }
482
483 k = 0;
484 for ( int q = 0; q <= n - 1; q++ )
485 {
486 while ( z[k + 1] < q )
487 k++;
488 d[q] = ( q - v[k] ) * ( q - v[k] ) + f[v[k]];
489 }
490}
491
492double QgsImageOperation::maxValueInDistanceTransformArray( const double *array, const unsigned int size )
493{
494 double dtMaxValue = array[0];
495 for ( unsigned int i = 1; i < size; ++i )
496 {
497 if ( array[i] > dtMaxValue )
498 {
499 dtMaxValue = array[i];
500 }
501 }
502 return dtMaxValue;
503}
504
505/* distance transform of 2d function using squared distance */
506void QgsImageOperation::distanceTransform2d( double *im, int width, int height, QgsFeedback *feedback )
507{
508 int maxDimension = std::max( width, height );
509
510 std::unique_ptr<double[]> f( new double[maxDimension] );
511 std::unique_ptr<int[]> v( new int[maxDimension] );
512 std::unique_ptr<double[]> z( new double[maxDimension + 1] );
513 std::unique_ptr<double[]> d( new double[maxDimension] );
514
515 // transform along columns
516 for ( int x = 0; x < width; x++ )
517 {
518 if ( feedback && feedback->isCanceled() )
519 break;
520
521 for ( int y = 0; y < height; y++ )
522 {
523 f[y] = im[x + y * width];
524 }
525 distanceTransform1d( f.get(), height, v.get(), z.get(), d.get() );
526 for ( int y = 0; y < height; y++ )
527 {
528 im[x + y * width] = d[y];
529 }
530 }
531
532 // transform along rows
533 for ( int y = 0; y < height; y++ )
534 {
535 if ( feedback && feedback->isCanceled() )
536 break;
537
538 for ( int x = 0; x < width; x++ )
539 {
540 f[x] = im[x + y * width];
541 }
542 distanceTransform1d( f.get(), width, v.get(), z.get(), d.get() );
543 for ( int x = 0; x < width; x++ )
544 {
545 im[x + y * width] = d[x];
546 }
547 }
548}
549
550void QgsImageOperation::ShadeFromArrayOperation::operator()( QRgb &rgb, const int x, const int y )
551{
552 if ( !mProperties.ramp )
553 return;
554
555 if ( qgsDoubleNear( mSpread, 0.0 ) )
556 {
557 rgb = mProperties.ramp->color( 1.0 ).rgba();
558 return;
559 }
560
561 int idx = y * mWidth + x;
562
563 //values are distance squared
564 double squaredVal = mArray[idx];
565 if ( squaredVal > mSpreadSquared )
566 {
567 rgb = Qt::transparent;
568 return;
569 }
570
571 double distance = std::sqrt( squaredVal );
572 double val = distance / mSpread;
573 QColor rampColor = mProperties.ramp->color( val );
574
575 if ( ( mProperties.shadeExterior && distance > mSpread - 1 ) )
576 {
577 //fade off final pixel to antialias edge
578 double alphaMultiplyFactor = mSpread - distance;
579 rampColor.setAlpha( rampColor.alpha() * alphaMultiplyFactor );
580 }
581 rgb = rampColor.rgba();
582}
583
584//stack blur
585
586void QgsImageOperation::stackBlur( QImage &image, const int radius, const bool alphaOnly, QgsFeedback *feedback )
587{
588 // culled from Qt's qpixmapfilter.cpp, see: http://www.qtcentre.org/archive/index.php/t-26534.html
589 int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 };
590 int alpha = ( radius < 1 ) ? 16 : ( radius > 17 ) ? 1 : tab[radius - 1];
591
592 int i1 = 0;
593 int i2 = 3;
594
595 //ensure correct source format.
596 QImage::Format originalFormat = image.format();
597 QImage *pImage = &image;
598 std::unique_ptr< QImage> convertedImage;
599 if ( !alphaOnly && originalFormat != QImage::Format_ARGB32_Premultiplied )
600 {
601 convertedImage = std::make_unique< QImage >( image.convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
602 pImage = convertedImage.get();
603 }
604 else if ( alphaOnly && originalFormat != QImage::Format_ARGB32 )
605 {
606 convertedImage = std::make_unique< QImage >( image.convertToFormat( QImage::Format_ARGB32 ) );
607 pImage = convertedImage.get();
608 }
609 else
610 {
611 image.detach();
612 }
613
614 if ( feedback && feedback->isCanceled() )
615 return;
616
617 if ( alphaOnly )
618 i1 = i2 = ( QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3 );
619
620 StackBlurLineOperation topToBottomBlur( alpha, QgsImageOperation::ByColumn, true, i1, i2, feedback );
621 runLineOperation( *pImage, topToBottomBlur, feedback );
622
623 if ( feedback && feedback->isCanceled() )
624 return;
625
626 StackBlurLineOperation leftToRightBlur( alpha, QgsImageOperation::ByRow, true, i1, i2, feedback );
627 runLineOperation( *pImage, leftToRightBlur, feedback );
628
629 if ( feedback && feedback->isCanceled() )
630 return;
631
632 StackBlurLineOperation bottomToTopBlur( alpha, QgsImageOperation::ByColumn, false, i1, i2, feedback );
633 runLineOperation( *pImage, bottomToTopBlur, feedback );
634
635 if ( feedback && feedback->isCanceled() )
636 return;
637
638 StackBlurLineOperation rightToLeftBlur( alpha, QgsImageOperation::ByRow, false, i1, i2, feedback );
639 runLineOperation( *pImage, rightToLeftBlur, feedback );
640
641 if ( feedback && feedback->isCanceled() )
642 return;
643
644 if ( pImage->format() != originalFormat )
645 {
646 image = pImage->convertToFormat( originalFormat );
647 }
648}
649
650//gaussian blur
651
652QImage *QgsImageOperation::gaussianBlur( QImage &image, const int radius, QgsFeedback *feedback )
653{
654 int width = image.width();
655 int height = image.height();
656
657 if ( radius <= 0 )
658 {
659 //just make an unchanged copy
660 QImage *copy = new QImage( image.copy() );
661 return copy;
662 }
663
664 std::unique_ptr<double[]> kernel( createGaussianKernel( radius ) );
665 if ( feedback && feedback->isCanceled() )
666 return new QImage();
667
668 //ensure correct source format.
669 QImage::Format originalFormat = image.format();
670 QImage *pImage = &image;
671 std::unique_ptr< QImage> convertedImage;
672 if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
673 {
674 convertedImage = std::make_unique< QImage >( image.convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
675 pImage = convertedImage.get();
676 }
677 else
678 {
679 image.detach();
680 }
681 if ( feedback && feedback->isCanceled() )
682 return new QImage();
683
684 //blur along rows
685 QImage xBlurImage = QImage( width, height, QImage::Format_ARGB32_Premultiplied );
686 GaussianBlurOperation rowBlur( radius, QgsImageOperation::ByRow, &xBlurImage, kernel.get(), feedback );
687 runRectOperation( *pImage, rowBlur );
688
689 if ( feedback && feedback->isCanceled() )
690 return new QImage();
691
692 //blur along columns
693 auto yBlurImage = std::make_unique< QImage >( width, height, QImage::Format_ARGB32_Premultiplied );
694 GaussianBlurOperation colBlur( radius, QgsImageOperation::ByColumn, yBlurImage.get(), kernel.get(), feedback );
695 runRectOperation( xBlurImage, colBlur );
696
697 if ( feedback && feedback->isCanceled() )
698 return new QImage();
699
700 kernel.reset();
701
702 if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
703 {
704 return new QImage( yBlurImage->convertToFormat( originalFormat ) );
705 }
706
707 return yBlurImage.release();
708}
709
710void QgsImageOperation::GaussianBlurOperation::operator()( QgsImageOperation::ImageBlock &block )
711{
712 if ( mFeedback && mFeedback->isCanceled() )
713 return;
714
715 int width = block.image->width();
716 int height = block.image->height();
717 int sourceBpl = block.image->bytesPerLine();
718
719 unsigned char *outputLineRef = mDestImage->scanLine( block.beginLine );
720 QRgb *destRef = nullptr;
721 if ( mDirection == ByRow )
722 {
723 unsigned char *sourceFirstLine = block.image->scanLine( 0 );
724 unsigned char *sourceRef;
725
726 //blur along rows
727 for ( unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl )
728 {
729 if ( mFeedback && mFeedback->isCanceled() )
730 break;
731
732 sourceRef = sourceFirstLine;
733 destRef = reinterpret_cast< QRgb * >( outputLineRef );
734 for ( int x = 0; x < width; ++x, ++destRef, sourceRef += 4 )
735 {
736 if ( mFeedback && mFeedback->isCanceled() )
737 break;
738
739 *destRef = gaussianBlurVertical( y, sourceRef, sourceBpl, height );
740 }
741 }
742 }
743 else
744 {
745 unsigned char *sourceRef = block.image->scanLine( block.beginLine );
746 for ( unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl, sourceRef += sourceBpl )
747 {
748 if ( mFeedback && mFeedback->isCanceled() )
749 break;
750
751 destRef = reinterpret_cast< QRgb * >( outputLineRef );
752 for ( int x = 0; x < width; ++x, ++destRef )
753 {
754 if ( mFeedback && mFeedback->isCanceled() )
755 break;
756
757 *destRef = gaussianBlurHorizontal( x, sourceRef, width );
758 }
759 }
760 }
761}
762
763inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurVertical( const int posy, unsigned char *sourceFirstLine, const int sourceBpl, const int height ) const
764{
765 double r = 0;
766 double b = 0;
767 double g = 0;
768 double a = 0;
769 int y;
770 unsigned char *ref;
771
772 for ( int i = 0; i <= mRadius * 2; ++i )
773 {
774 y = std::clamp( posy + ( i - mRadius ), 0, height - 1 );
775 ref = sourceFirstLine + static_cast< std::size_t >( sourceBpl ) * y;
776
777 QRgb *refRgb = reinterpret_cast< QRgb * >( ref );
778 r += mKernel[i] * qRed( *refRgb );
779 g += mKernel[i] * qGreen( *refRgb );
780 b += mKernel[i] * qBlue( *refRgb );
781 a += mKernel[i] * qAlpha( *refRgb );
782 }
783
784 return qRgba( r, g, b, a );
785}
786
787inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurHorizontal( const int posx, unsigned char *sourceFirstLine, const int width ) const
788{
789 double r = 0;
790 double b = 0;
791 double g = 0;
792 double a = 0;
793 int x;
794 unsigned char *ref;
795
796 for ( int i = 0; i <= mRadius * 2; ++i )
797 {
798 x = std::clamp( posx + ( i - mRadius ), 0, width - 1 );
799 ref = sourceFirstLine + x * 4;
800
801 QRgb *refRgb = reinterpret_cast< QRgb * >( ref );
802 r += mKernel[i] * qRed( *refRgb );
803 g += mKernel[i] * qGreen( *refRgb );
804 b += mKernel[i] * qBlue( *refRgb );
805 a += mKernel[i] * qAlpha( *refRgb );
806 }
807
808 return qRgba( r, g, b, a );
809}
810
811
812double *QgsImageOperation::createGaussianKernel( const int radius )
813{
814 double *kernel = new double[radius * 2 + 1];
815 double sigma = radius / 3.0;
816 double twoSigmaSquared = 2 * sigma * sigma;
817 double coefficient = 1.0 / std::sqrt( M_PI * twoSigmaSquared );
818 double expCoefficient = -1.0 / twoSigmaSquared;
819
820 double sum = 0;
821 double result;
822 for ( int i = 0; i <= radius; ++i )
823 {
824 result = coefficient * std::exp( i * i * expCoefficient );
825 kernel[radius - i] = result;
826 sum += result;
827 if ( i > 0 )
828 {
829 kernel[radius + i] = result;
830 sum += result;
831 }
832 }
833 //normalize
834 for ( int i = 0; i <= radius * 2; ++i )
835 {
836 kernel[i] /= sum;
837 }
838 return kernel;
839}
840
841
842// flip
843
845{
846 image.detach();
847 FlipLineOperation flipOperation( type == QgsImageOperation::FlipHorizontal ? QgsImageOperation::ByRow : QgsImageOperation::ByColumn );
848 runLineOperation( image, flipOperation );
849}
850
851QRect QgsImageOperation::nonTransparentImageRect( const QImage &image, QSize minSize, bool center )
852{
853 int width = image.width();
854 int height = image.height();
855 int xmin = width;
856 int xmax = 0;
857 int ymin = height;
858 int ymax = 0;
859
860 // scan down till we hit something
861 for ( int y = 0; y < height; ++y )
862 {
863 bool found = false;
864 const QRgb *imgScanline = reinterpret_cast< const QRgb * >( image.constScanLine( y ) );
865 for ( int x = 0; x < width; ++x )
866 {
867 if ( qAlpha( imgScanline[x] ) )
868 {
869 ymin = y;
870 ymax = y;
871 xmin = x;
872 xmax = x;
873 found = true;
874 break;
875 }
876 }
877 if ( found )
878 break;
879 }
880
881 //scan up till we hit something
882 for ( int y = height - 1; y >= ymin; --y )
883 {
884 bool found = false;
885 const QRgb *imgScanline = reinterpret_cast< const QRgb * >( image.constScanLine( y ) );
886 for ( int x = 0; x < width; ++x )
887 {
888 if ( qAlpha( imgScanline[x] ) )
889 {
890 ymax = y;
891 xmin = std::min( xmin, x );
892 xmax = std::max( xmax, x );
893 found = true;
894 break;
895 }
896 }
897 if ( found )
898 break;
899 }
900
901 //scan left to right till we hit something, using a refined y region
902 for ( int y = ymin; y <= ymax; ++y )
903 {
904 const QRgb *imgScanline = reinterpret_cast< const QRgb * >( image.constScanLine( y ) );
905 for ( int x = 0; x < xmin; ++x )
906 {
907 if ( qAlpha( imgScanline[x] ) )
908 {
909 xmin = x;
910 break;
911 }
912 }
913 }
914
915 //scan right to left till we hit something, using the refined y region
916 for ( int y = ymin; y <= ymax; ++y )
917 {
918 const QRgb *imgScanline = reinterpret_cast< const QRgb * >( image.constScanLine( y ) );
919 for ( int x = width - 1; x > xmax; --x )
920 {
921 if ( qAlpha( imgScanline[x] ) )
922 {
923 xmax = x;
924 break;
925 }
926 }
927 }
928
929 if ( minSize.isValid() )
930 {
931 if ( xmax - xmin < minSize.width() ) // centers image on x
932 {
933 xmin = std::max( ( xmax + xmin ) / 2 - minSize.width() / 2, 0 );
934 xmax = xmin + minSize.width();
935 }
936 if ( ymax - ymin < minSize.height() ) // centers image on y
937 {
938 ymin = std::max( ( ymax + ymin ) / 2 - minSize.height() / 2, 0 );
939 ymax = ymin + minSize.height();
940 }
941 }
942 if ( center )
943 {
944 // recompute min and max to center image
945 const int dx = std::max( std::abs( xmax - width / 2 ), std::abs( xmin - width / 2 ) );
946 const int dy = std::max( std::abs( ymax - height / 2 ), std::abs( ymin - height / 2 ) );
947 xmin = std::max( 0, width / 2 - dx );
948 xmax = std::min( width, width / 2 + dx );
949 ymin = std::max( 0, height / 2 - dy );
950 ymax = std::min( height, height / 2 + dy );
951 }
952
953 return QRect( xmin, ymin, xmax - xmin, ymax - ymin );
954}
955
956QImage QgsImageOperation::cropTransparent( const QImage &image, QSize minSize, bool center )
957{
958 return image.copy( QgsImageOperation::nonTransparentImageRect( image, minSize, center ) );
959}
960
961void QgsImageOperation::FlipLineOperation::operator()( QRgb *startRef, const int lineLength, const int bytesPerLine ) const
962{
963 int increment = ( mDirection == QgsImageOperation::ByRow ) ? 4 : bytesPerLine;
964
965 //store temporary line
966 unsigned char *p = reinterpret_cast< unsigned char * >( startRef );
967 unsigned char *tempLine = new unsigned char[lineLength * 4];
968 for ( int i = 0; i < lineLength * 4; ++i, p += increment )
969 {
970 tempLine[i++] = *( p++ );
971 tempLine[i++] = *( p++ );
972 tempLine[i++] = *( p++ );
973 tempLine[i] = *( p );
974 p -= 3;
975 }
976
977 //write values back in reverse order
978 p = reinterpret_cast< unsigned char * >( startRef );
979 for ( int i = ( lineLength - 1 ) * 4; i >= 0; i -= 7, p += increment )
980 {
981 *( p++ ) = tempLine[i++];
982 *( p++ ) = tempLine[i++];
983 *( p++ ) = tempLine[i++];
984 *( p ) = tempLine[i];
985 p -= 3;
986 }
987
988 delete[] tempLine;
989}
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
static void adjustHueSaturation(QImage &image, double saturation, const QColor &colorizeColor=QColor(), double colorizeStrength=1.0, QgsFeedback *feedback=nullptr)
Alter the hue or saturation of a QImage.
static void multiplyOpacity(QImage &image, double factor, QgsFeedback *feedback=nullptr)
Multiplies opacity of image pixel values by a factor.
static void distanceTransform(QImage &image, const QgsImageOperation::DistanceTransformProperties &properties, QgsFeedback *feedback=nullptr)
Performs a distance transform on the source image and shades the result using a color ramp.
FlipType
Flip operation types.
@ FlipHorizontal
Flip the image horizontally.
static void overlayColor(QImage &image, const QColor &color)
Overlays a color onto an image.
static void flipImage(QImage &image, FlipType type)
Flips an image horizontally or vertically.
static void adjustBrightnessContrast(QImage &image, int brightness, double contrast, QgsFeedback *feedback=nullptr)
Alter the brightness or contrast of a QImage.
static QImage * gaussianBlur(QImage &image, int radius, QgsFeedback *feedback=nullptr)
Performs a gaussian blur on an image.
static QRect nonTransparentImageRect(const QImage &image, QSize minSize=QSize(), bool center=false)
Calculates the non-transparent region of an image.
static void stackBlur(QImage &image, int radius, bool alphaOnly=false, QgsFeedback *feedback=nullptr)
Performs a stack blur on an image.
static QImage cropTransparent(const QImage &image, QSize minSize=QSize(), bool center=false)
Crop any transparent border from around an image.
static void convertToGrayscale(QImage &image, GrayscaleMode mode=GrayscaleLuminosity, QgsFeedback *feedback=nullptr)
Convert a QImage to a grayscale image.
GrayscaleMode
Modes for converting a QImage to grayscale.
@ GrayscaleLightness
Keep the lightness of the color, drops the saturation.
@ GrayscaleLuminosity
Grayscale by perceptual luminosity (weighted sum of color RGB components).
@ GrayscaleAverage
Grayscale by taking average of color RGB components.
unsigned long long qgssize
Qgssize is used instead of size_t, because size_t is stdlib type, unknown by SIP, and it would be har...
Definition qgis.h:7485
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6975
#define INF
#define BLOCK_THREADS
#define QgsDebugError(str)
Definition qgslogger.h:59
Struct for storing properties of a distance transform operation.
bool useMaxDistance
Set to true to automatically calculate the maximum distance in the transform to use as the spread val...
bool shadeExterior
Set to true to perform the distance transform on transparent pixels in the source image,...
double spread
Maximum distance (in pixels) for the distance transform shading to spread.
QgsColorRamp * ramp
Color ramp to use for shading the distance transform.