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