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