QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 void QgsImageOperation::StackBlurLineOperation::operator()( QRgb *startRef, const int lineLength, const int bytesPerLine )
602 {
603  unsigned char *p = reinterpret_cast< unsigned char * >( startRef );
604  int rgba[4];
605  int increment = ( mDirection == QgsImageOperation::ByRow ) ? 4 : bytesPerLine;
606  if ( !mForwardDirection )
607  {
608  p += ( lineLength - 1 ) * increment;
609  increment = -increment;
610  }
611 
612  for ( int i = mi1; i <= mi2; ++i )
613  {
614  rgba[i] = p[i] << 4;
615  }
616 
617  p += increment;
618  for ( int j = 1; j < lineLength; ++j, p += increment )
619  {
620  for ( int i = mi1; i <= mi2; ++i )
621  {
622  p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * mAlpha / 16 ) >> 4;
623  }
624  }
625 }
626 
627 //gaussian blur
628 
629 QImage *QgsImageOperation::gaussianBlur( QImage &image, const int radius )
630 {
631  int width = image.width();
632  int height = image.height();
633 
634  if ( radius <= 0 )
635  {
636  //just make an unchanged copy
637  QImage *copy = new QImage( image.copy() );
638  return copy;
639  }
640 
641  double *kernel = createGaussianKernel( radius );
642 
643  //ensure correct source format.
644  QImage::Format originalFormat = image.format();
645  QImage *pImage = &image;
646  if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
647  {
648  pImage = new QImage( image.convertToFormat( QImage::Format_ARGB32_Premultiplied ) );
649  }
650 
651  //blur along rows
652  QImage xBlurImage = QImage( width, height, QImage::Format_ARGB32_Premultiplied );
653  GaussianBlurOperation rowBlur( radius, QgsImageOperation::ByRow, &xBlurImage, kernel );
654  runRectOperation( *pImage, rowBlur );
655 
656  //blur along columns
657  QImage *yBlurImage = new QImage( width, height, QImage::Format_ARGB32_Premultiplied );
658  GaussianBlurOperation colBlur( radius, QgsImageOperation::ByColumn, yBlurImage, kernel );
659  runRectOperation( xBlurImage, colBlur );
660 
661  delete[] kernel;
662 
663  if ( originalFormat != QImage::Format_ARGB32_Premultiplied )
664  {
665  QImage *convertedImage = new QImage( yBlurImage->convertToFormat( originalFormat ) );
666  delete yBlurImage;
667  delete pImage;
668  return convertedImage;
669  }
670 
671  return yBlurImage;
672 }
673 
674 void QgsImageOperation::GaussianBlurOperation::operator()( QgsImageOperation::ImageBlock &block )
675 {
676  int width = block.image->width();
677  int height = block.image->height();
678  int sourceBpl = block.image->bytesPerLine();
679 
680  unsigned char *outputLineRef = mDestImage->scanLine( block.beginLine );
681  QRgb *destRef = nullptr;
682  if ( mDirection == ByRow )
683  {
684  unsigned char *sourceFirstLine = block.image->scanLine( 0 );
685  unsigned char *sourceRef;
686 
687  //blur along rows
688  for ( unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl )
689  {
690  sourceRef = sourceFirstLine;
691  destRef = reinterpret_cast< QRgb * >( outputLineRef );
692  for ( int x = 0; x < width; ++x, ++destRef, sourceRef += 4 )
693  {
694  *destRef = gaussianBlurVertical( y, sourceRef, sourceBpl, height );
695  }
696  }
697  }
698  else
699  {
700  unsigned char *sourceRef = block.image->scanLine( block.beginLine );
701  for ( unsigned int y = block.beginLine; y < block.endLine; ++y, outputLineRef += mDestImageBpl, sourceRef += sourceBpl )
702  {
703  destRef = reinterpret_cast< QRgb * >( outputLineRef );
704  for ( int x = 0; x < width; ++x, ++destRef )
705  {
706  *destRef = gaussianBlurHorizontal( x, sourceRef, width );
707  }
708  }
709  }
710 }
711 
712 inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurVertical( const int posy, unsigned char *sourceFirstLine, const int sourceBpl, const int height )
713 {
714  double r = 0;
715  double b = 0;
716  double g = 0;
717  double a = 0;
718  int y;
719  unsigned char *ref;
720 
721  for ( int i = 0; i <= mRadius * 2; ++i )
722  {
723  y = qBound( 0, posy + ( i - mRadius ), height - 1 );
724  ref = sourceFirstLine + sourceBpl * y;
725 
726  QRgb *refRgb = reinterpret_cast< QRgb * >( ref );
727  r += mKernel[i] * qRed( *refRgb );
728  g += mKernel[i] * qGreen( *refRgb );
729  b += mKernel[i] * qBlue( *refRgb );
730  a += mKernel[i] * qAlpha( *refRgb );
731  }
732 
733  return qRgba( r, g, b, a );
734 }
735 
736 inline QRgb QgsImageOperation::GaussianBlurOperation::gaussianBlurHorizontal( const int posx, unsigned char *sourceFirstLine, const int width )
737 {
738  double r = 0;
739  double b = 0;
740  double g = 0;
741  double a = 0;
742  int x;
743  unsigned char *ref;
744 
745  for ( int i = 0; i <= mRadius * 2; ++i )
746  {
747  x = qBound( 0, posx + ( i - mRadius ), width - 1 );
748  ref = sourceFirstLine + x * 4;
749 
750  QRgb *refRgb = reinterpret_cast< QRgb * >( ref );
751  r += mKernel[i] * qRed( *refRgb );
752  g += mKernel[i] * qGreen( *refRgb );
753  b += mKernel[i] * qBlue( *refRgb );
754  a += mKernel[i] * qAlpha( *refRgb );
755  }
756 
757  return qRgba( r, g, b, a );
758 }
759 
760 
761 double *QgsImageOperation::createGaussianKernel( const int radius )
762 {
763  double *kernel = new double[ radius * 2 + 1 ];
764  double sigma = radius / 3.0;
765  double twoSigmaSquared = 2 * sigma * sigma;
766  double coefficient = 1.0 / std::sqrt( M_PI * twoSigmaSquared );
767  double expCoefficient = -1.0 / twoSigmaSquared;
768 
769  double sum = 0;
770  double result;
771  for ( int i = 0; i <= radius; ++i )
772  {
773  result = coefficient * std::exp( i * i * expCoefficient );
774  kernel[ radius - i ] = result;
775  sum += result;
776  if ( i > 0 )
777  {
778  kernel[radius + i] = result;
779  sum += result;
780  }
781  }
782  //normalize
783  for ( int i = 0; i <= radius * 2; ++i )
784  {
785  kernel[i] /= sum;
786  }
787  return kernel;
788 }
789 
790 
791 // flip
792 
794 {
795  FlipLineOperation flipOperation( type == QgsImageOperation::FlipHorizontal ? QgsImageOperation::ByRow : QgsImageOperation::ByColumn );
796  runLineOperation( image, flipOperation );
797 }
798 
799 QRect QgsImageOperation::nonTransparentImageRect( const QImage &image, QSize minSize, bool center )
800 {
801  int width = image.width();
802  int height = image.height();
803  int xmin = width;
804  int xmax = 0;
805  int ymin = height;
806  int ymax = 0;
807 
808  // scan down till we hit something
809  for ( int y = 0; y < height; ++y )
810  {
811  bool found = false;
812  const QRgb *imgScanline = reinterpret_cast< const QRgb * >( image.constScanLine( y ) );
813  for ( int x = 0; x < width; ++x )
814  {
815  if ( qAlpha( imgScanline[x] ) )
816  {
817  ymin = y;
818  ymax = y;
819  xmin = x;
820  xmax = x;
821  found = true;
822  break;
823  }
824  }
825  if ( found )
826  break;
827  }
828 
829  //scan up till we hit something
830  for ( int y = height - 1; y >= ymin; --y )
831  {
832  bool found = false;
833  const QRgb *imgScanline = reinterpret_cast< const QRgb * >( image.constScanLine( y ) );
834  for ( int x = 0; x < width; ++x )
835  {
836  if ( qAlpha( imgScanline[x] ) )
837  {
838  ymax = y;
839  xmin = std::min( xmin, x );
840  xmax = std::max( xmax, x );
841  found = true;
842  break;
843  }
844  }
845  if ( found )
846  break;
847  }
848 
849  //scan left to right till we hit something, using a refined y region
850  for ( int y = ymin; y <= ymax; ++y )
851  {
852  const QRgb *imgScanline = reinterpret_cast< const QRgb * >( image.constScanLine( y ) );
853  for ( int x = 0; x < xmin; ++x )
854  {
855  if ( qAlpha( imgScanline[x] ) )
856  {
857  xmin = x;
858  }
859  }
860  }
861 
862  //scan right to left till we hit something, using the refined y region
863  for ( int y = ymin; y <= ymax; ++y )
864  {
865  const QRgb *imgScanline = reinterpret_cast< const QRgb * >( image.constScanLine( y ) );
866  for ( int x = width - 1; x > xmax; --x )
867  {
868  if ( qAlpha( imgScanline[x] ) )
869  {
870  xmax = x;
871  }
872  }
873  }
874 
875  if ( minSize.isValid() )
876  {
877  if ( xmax - xmin < minSize.width() ) // centers image on x
878  {
879  xmin = std::max( ( xmax + xmin ) / 2 - minSize.width() / 2, 0 );
880  xmax = xmin + minSize.width();
881  }
882  if ( ymax - ymin < minSize.height() ) // centers image on y
883  {
884  ymin = std::max( ( ymax + ymin ) / 2 - minSize.height() / 2, 0 );
885  ymax = ymin + minSize.height();
886  }
887  }
888  if ( center )
889  {
890  // recompute min and max to center image
891  const int dx = std::max( std::abs( xmax - width / 2 ), std::abs( xmin - width / 2 ) );
892  const int dy = std::max( std::abs( ymax - height / 2 ), std::abs( ymin - height / 2 ) );
893  xmin = std::max( 0, width / 2 - dx );
894  xmax = std::min( width, width / 2 + dx );
895  ymin = std::max( 0, height / 2 - dy );
896  ymax = std::min( height, height / 2 + dy );
897  }
898 
899  return QRect( xmin, ymin, xmax - xmin, ymax - ymin );
900 }
901 
902 QImage QgsImageOperation::cropTransparent( const QImage &image, QSize minSize, bool center )
903 {
904  return image.copy( QgsImageOperation::nonTransparentImageRect( image, minSize, center ) );
905 }
906 
907 void QgsImageOperation::FlipLineOperation::operator()( QRgb *startRef, const int lineLength, const int bytesPerLine )
908 {
909  int increment = ( mDirection == QgsImageOperation::ByRow ) ? 4 : bytesPerLine;
910 
911  //store temporary line
912  unsigned char *p = reinterpret_cast< unsigned char * >( startRef );
913  unsigned char *tempLine = new unsigned char[ lineLength * 4 ];
914  for ( int i = 0; i < lineLength * 4; ++i, p += increment )
915  {
916  tempLine[i++] = *( p++ );
917  tempLine[i++] = *( p++ );
918  tempLine[i++] = *( p++ );
919  tempLine[i] = *( p );
920  p -= 3;
921  }
922 
923  //write values back in reverse order
924  p = reinterpret_cast< unsigned char * >( startRef );
925  for ( int i = ( lineLength - 1 ) * 4; i >= 0; i -= 7, p += increment )
926  {
927  *( p++ ) = tempLine[i++];
928  *( p++ ) = tempLine[i++];
929  *( p++ ) = tempLine[i++];
930  *( p ) = tempLine[i];
931  p -= 3;
932  }
933 
934  delete[] tempLine;
935 }
936 
937 
938 
939 
static void multiplyOpacity(QImage &image, double factor)
Multiplies opacity of image pixel values by a factor.
static void overlayColor(QImage &image, const QColor &color)
Overlays a color onto an image.
Flip the image horizontally.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:278
Keep the lightness of the color, drops the saturation.
double spread
Maximum distance (in pixels) for the distance transform shading to spread.
static void convertToGrayscale(QImage &image, GrayscaleMode mode=GrayscaleLuminosity)
Convert a QImage to a grayscale image.
bool shadeExterior
Set to true to perform the distance transform on transparent pixels in the source image...
FlipType
Flip operation types.
Grayscale by perceptual luminosity (weighted sum of color RGB components)
Grayscale by taking average of color RGB components.
static QImage cropTransparent(const QImage &image, QSize minSize=QSize(), bool center=false)
Crop any transparent border from around an image.
QgsColorRamp * ramp
Color ramp to use for shading the distance transform.
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 flipImage(QImage &image, FlipType type)
Flips an image horizontally or vertically.
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...
bool useMaxDistance
Set to true to automatically calculate the maximum distance in the transform to use as the spread val...
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:586
static QImage * gaussianBlur(QImage &image, int radius)
Performs a gaussian blur on an image.
static void stackBlur(QImage &image, int radius, bool alphaOnly=false)
Performs a stack blur on an image.
static QRect nonTransparentImageRect(const QImage &image, QSize minSize=QSize(), bool center=false)
Calculates the non-transparent region of an image.
#define INF
#define BLOCK_THREADS
GrayscaleMode
Modes for converting a QImage to grayscale.
static void adjustBrightnessContrast(QImage &image, int brightness, double contrast)
Alter the brightness or contrast of a QImage.
Struct for storing properties of a distance transform operation.