LCOV - code coverage report
Current view: top level - core/effects - qgsimageoperation.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 504 0.0 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

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

Generated by: LCOV version 1.14