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