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