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