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