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