QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
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 qBound( 0, static_cast< int >( ( ( ( ( ( colorComponent / 255.0 ) - 0.5 ) * contrastFactor ) + 0.5 ) * 255 ) + brightness ), 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 ), qBound( 0.0, std::round( mFactor * qAlpha( rgb ) ), 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 = qBound( 0, posy + ( i - mRadius ), 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 = qBound( 0, posx + ( i - mRadius ), 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 
QgsImageOperation::DistanceTransformProperties::ramp
QgsColorRamp * ramp
Color ramp to use for shading the distance transform.
Definition: qgsimageoperation.h:141
QgsImageOperation::multiplyOpacity
static void multiplyOpacity(QImage &image, double factor)
Multiplies opacity of image pixel values by a factor.
Definition: qgsimageoperation.cpp:322
qgis.h
QgsImageOperation::DistanceTransformProperties::useMaxDistance
bool useMaxDistance
Set to true to automatically calculate the maximum distance in the transform to use as the spread val...
Definition: qgsimageoperation.h:130
QgsImageOperation::GrayscaleOff
@ GrayscaleOff
No change.
Definition: qgsimageoperation.h:58
qgsimageoperation.h
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsImageOperation::cropTransparent
static QImage cropTransparent(const QImage &image, QSize minSize=QSize(), bool center=false)
Crop any transparent border from around an image.
Definition: qgsimageoperation.cpp:878
QgsImageOperation::FlipType
FlipType
Flip operation types.
Definition: qgsimageoperation.h:65
QgsImageOperation::convertToGrayscale
static void convertToGrayscale(QImage &image, GrayscaleMode mode=GrayscaleLuminosity)
Convert a QImage to a grayscale image.
Definition: qgsimageoperation.cpp:185
QgsImageOperation::flipImage
static void flipImage(QImage &image, FlipType type)
Flips an image horizontally or vertically.
Definition: qgsimageoperation.cpp:767
qgscolorramp.h
qgsDoubleNear
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:315
QgsImageOperation::adjustHueSaturation
static void adjustHueSaturation(QImage &image, double saturation, const QColor &colorizeColor=QColor(), double colorizeStrength=1.0)
Alter the hue or saturation of a QImage.
Definition: qgsimageoperation.cpp:268
QgsImageOperation::DistanceTransformProperties::shadeExterior
bool shadeExterior
Set to true to perform the distance transform on transparent pixels in the source image,...
Definition: qgsimageoperation.h:124
QgsImageOperation::distanceTransform
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.
Definition: qgsimageoperation.cpp:371
QgsImageOperation::gaussianBlur
static QImage * gaussianBlur(QImage &image, int radius)
Performs a gaussian blur on an image.
Definition: qgsimageoperation.cpp:603
BLOCK_THREADS
#define BLOCK_THREADS
Definition: qgsimageoperation.cpp:28
INF
#define INF
Definition: qgsimageoperation.cpp:30
QgsImageOperation::DistanceTransformProperties::spread
double spread
Maximum distance (in pixels) for the distance transform shading to spread.
Definition: qgsimageoperation.h:136
QgsImageOperation::DistanceTransformProperties
Struct for storing properties of a distance transform operation.
Definition: qgsimageoperation.h:117
QgsImageOperation::stackBlur
static void stackBlur(QImage &image, int radius, bool alphaOnly=false)
Performs a stack blur on an image.
Definition: qgsimageoperation.cpp:558
QgsImageOperation::FlipHorizontal
@ FlipHorizontal
Flip the image horizontally.
Definition: qgsimageoperation.h:66
QgsImageOperation::nonTransparentImageRect
static QRect nonTransparentImageRect(const QImage &image, QSize minSize=QSize(), bool center=false)
Calculates the non-transparent region of an image.
Definition: qgsimageoperation.cpp:773
QgsImageOperation::adjustBrightnessContrast
static void adjustBrightnessContrast(QImage &image, int brightness, double contrast)
Alter the brightness or contrast of a QImage.
Definition: qgsimageoperation.cpp:245
qgslogger.h
QgsImageOperation::overlayColor
static void overlayColor(QImage &image, const QColor &color)
Overlays a color onto an image.
Definition: qgsimageoperation.cpp:356
QgsImageOperation::GrayscaleMode
GrayscaleMode
Modes for converting a QImage to grayscale.
Definition: qgsimageoperation.h:54
qgssize
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:768