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

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :                          qgshillshaderenderer.cpp
       3                 :            :                          ---------------------------------
       4                 :            :     begin                : May 2016
       5                 :            :     copyright            : (C) 2016 by Nathan Woodrow
       6                 :            :     email                : woodrow dot nathan at gmail dot 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 <QColor>
      19                 :            : 
      20                 :            : #include "qgshillshaderenderer.h"
      21                 :            : #include "qgsrastertransparency.h"
      22                 :            : #include "qgsrasterinterface.h"
      23                 :            : #include "qgsrasterblock.h"
      24                 :            : #include "qgsrectangle.h"
      25                 :            : #include "qgsmessagelog.h"
      26                 :            : #include <memory>
      27                 :            : 
      28                 :            : #ifdef HAVE_OPENCL
      29                 :            : #ifdef QGISDEBUG
      30                 :            : #include <chrono>
      31                 :            : #include "qgssettings.h"
      32                 :            : #endif
      33                 :            : #include "qgsexception.h"
      34                 :            : #include "qgsopenclutils.h"
      35                 :            : #endif
      36                 :            : 
      37                 :          0 : QgsHillshadeRenderer::QgsHillshadeRenderer( QgsRasterInterface *input, int band, double lightAzimuth, double lightAngle ):
      38                 :          0 :   QgsRasterRenderer( input, QStringLiteral( "hillshade" ) )
      39                 :          0 :   , mBand( band )
      40                 :          0 :   , mZFactor( 1 )
      41                 :          0 :   , mLightAngle( lightAngle )
      42                 :          0 :   , mLightAzimuth( lightAzimuth )
      43                 :          0 :   , mMultiDirectional( false )
      44                 :          0 : {
      45                 :            : 
      46                 :          0 : }
      47                 :            : 
      48                 :          0 : QgsHillshadeRenderer *QgsHillshadeRenderer::clone() const
      49                 :            : {
      50                 :          0 :   QgsHillshadeRenderer *r = new QgsHillshadeRenderer( nullptr, mBand, mLightAzimuth, mLightAngle );
      51                 :          0 :   r->copyCommonProperties( this );
      52                 :            : 
      53                 :          0 :   r->setZFactor( mZFactor );
      54                 :          0 :   r->setMultiDirectional( mMultiDirectional );
      55                 :          0 :   return r;
      56                 :          0 : }
      57                 :            : 
      58                 :          0 : QgsRasterRenderer *QgsHillshadeRenderer::create( const QDomElement &elem, QgsRasterInterface *input )
      59                 :            : {
      60                 :          0 :   if ( elem.isNull() )
      61                 :            :   {
      62                 :          0 :     return nullptr;
      63                 :            :   }
      64                 :            : 
      65                 :          0 :   int band = elem.attribute( QStringLiteral( "band" ), QStringLiteral( "0" ) ).toInt();
      66                 :          0 :   double azimuth = elem.attribute( QStringLiteral( "azimuth" ), QStringLiteral( "315" ) ).toDouble();
      67                 :          0 :   double angle = elem.attribute( QStringLiteral( "angle" ), QStringLiteral( "45" ) ).toDouble();
      68                 :          0 :   double zFactor = elem.attribute( QStringLiteral( "zfactor" ), QStringLiteral( "1" ) ).toDouble();
      69                 :          0 :   bool multiDirectional = elem.attribute( QStringLiteral( "multidirection" ), QStringLiteral( "0" ) ).toInt();
      70                 :          0 :   QgsHillshadeRenderer *r = new QgsHillshadeRenderer( input, band, azimuth, angle );
      71                 :          0 :   r->readXml( elem );
      72                 :            : 
      73                 :          0 :   r->setZFactor( zFactor );
      74                 :          0 :   r->setMultiDirectional( multiDirectional );
      75                 :          0 :   return r;
      76                 :          0 : }
      77                 :            : 
      78                 :          0 : void QgsHillshadeRenderer::writeXml( QDomDocument &doc, QDomElement &parentElem ) const
      79                 :            : {
      80                 :          0 :   if ( parentElem.isNull() )
      81                 :            :   {
      82                 :          0 :     return;
      83                 :            :   }
      84                 :            : 
      85                 :          0 :   QDomElement rasterRendererElem = doc.createElement( QStringLiteral( "rasterrenderer" ) );
      86                 :          0 :   _writeXml( doc, rasterRendererElem );
      87                 :            : 
      88                 :          0 :   rasterRendererElem.setAttribute( QStringLiteral( "band" ), mBand );
      89                 :          0 :   rasterRendererElem.setAttribute( QStringLiteral( "azimuth" ), QString::number( mLightAzimuth ) );
      90                 :          0 :   rasterRendererElem.setAttribute( QStringLiteral( "angle" ), QString::number( mLightAngle ) );
      91                 :          0 :   rasterRendererElem.setAttribute( QStringLiteral( "zfactor" ), QString::number( mZFactor ) );
      92                 :          0 :   rasterRendererElem.setAttribute( QStringLiteral( "multidirection" ), QString::number( mMultiDirectional ) );
      93                 :          0 :   parentElem.appendChild( rasterRendererElem );
      94                 :          0 : }
      95                 :            : 
      96                 :          0 : QgsRasterBlock *QgsHillshadeRenderer::block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback )
      97                 :            : {
      98                 :            :   Q_UNUSED( bandNo )
      99                 :          0 :   std::unique_ptr< QgsRasterBlock > outputBlock( new QgsRasterBlock() );
     100                 :          0 :   if ( !mInput )
     101                 :            :   {
     102                 :          0 :     QgsDebugMsg( QStringLiteral( "No input raster!" ) );
     103                 :          0 :     return outputBlock.release();
     104                 :            :   }
     105                 :            : 
     106                 :          0 :   std::shared_ptr< QgsRasterBlock > inputBlock( mInput->block( mBand, extent, width, height, feedback ) );
     107                 :            : 
     108                 :          0 :   if ( !inputBlock || inputBlock->isEmpty() )
     109                 :            :   {
     110                 :          0 :     QgsDebugMsg( QStringLiteral( "No raster data!" ) );
     111                 :          0 :     return outputBlock.release();
     112                 :            :   }
     113                 :            : 
     114                 :          0 :   std::shared_ptr< QgsRasterBlock > alphaBlock;
     115                 :            : 
     116                 :          0 :   if ( mAlphaBand > 0 && mBand != mAlphaBand )
     117                 :            :   {
     118                 :          0 :     alphaBlock.reset( mInput->block( mAlphaBand, extent, width, height, feedback ) );
     119                 :          0 :     if ( !alphaBlock || alphaBlock->isEmpty() )
     120                 :            :     {
     121                 :            :       // TODO: better to render without alpha
     122                 :          0 :       return outputBlock.release();
     123                 :            :     }
     124                 :          0 :   }
     125                 :          0 :   else if ( mAlphaBand > 0 )
     126                 :            :   {
     127                 :          0 :     alphaBlock = inputBlock;
     128                 :          0 :   }
     129                 :            : 
     130                 :          0 :   if ( !outputBlock->reset( Qgis::ARGB32_Premultiplied, width, height ) )
     131                 :            :   {
     132                 :          0 :     return outputBlock.release();
     133                 :            :   }
     134                 :            : 
     135                 :            :   // Starting the computation
     136                 :            : 
     137                 :            :   // Common pre-calculated values
     138                 :          0 :   float cellXSize = static_cast<float>( extent.width() ) / width;
     139                 :          0 :   float cellYSize = static_cast<float>( extent.height() ) / height;
     140                 :          0 :   float zenithRad = static_cast<float>( std::max( 0.0, 90 - mLightAngle ) * M_PI / 180.0 );
     141                 :          0 :   float azimuthRad = static_cast<float>( -1 * mLightAzimuth * M_PI / 180.0 );
     142                 :          0 :   float cosZenithRad = std::cos( zenithRad );
     143                 :          0 :   float sinZenithRad = std::sin( zenithRad );
     144                 :            : 
     145                 :            :   // For fast formula from GDAL DEM
     146                 :          0 :   float cos_alt_mul_z = cosZenithRad * static_cast<float>( mZFactor );
     147                 :          0 :   float cos_az_mul_cos_alt_mul_z = std::cos( azimuthRad ) * cos_alt_mul_z;
     148                 :          0 :   float sin_az_mul_cos_alt_mul_z = std::sin( azimuthRad ) * cos_alt_mul_z;
     149                 :          0 :   float cos_az_mul_cos_alt_mul_z_mul_254 = 254.0f * cos_az_mul_cos_alt_mul_z;
     150                 :          0 :   float sin_az_mul_cos_alt_mul_z_mul_254 = 254.0f * sin_az_mul_cos_alt_mul_z;
     151                 :          0 :   float square_z = static_cast<float>( mZFactor * mZFactor );
     152                 :          0 :   float sin_altRadians_mul_254 = 254.0f * sinZenithRad;
     153                 :            : 
     154                 :            :   // For multi directional
     155                 :          0 :   float sin_altRadians_mul_127 = 127.0f * sinZenithRad;
     156                 :            :   // 127.0 * std::cos(225.0 *  M_PI / 180.0) = -32.87001872802012
     157                 :          0 :   float cos225_az_mul_cos_alt_mul_z_mul_127 = -32.87001872802012f * cos_alt_mul_z;
     158                 :          0 :   float cos_alt_mul_z_mul_127 = 127.0f * cos_alt_mul_z;
     159                 :            : 
     160                 :          0 :   const QRgb defaultNodataColor = renderColorForNodataPixel();
     161                 :            : 
     162                 :            : #ifdef HAVE_OPENCL
     163                 :            : 
     164                 :            :   // Use OpenCL? For now OpenCL is enabled in the default configuration only
     165                 :            :   bool useOpenCL( QgsOpenClUtils::enabled()
     166                 :            :                   && QgsOpenClUtils::available()
     167                 :            :                   && ( ! mRasterTransparency || mRasterTransparency->isEmpty() )
     168                 :            :                   && mAlphaBand <= 0
     169                 :            :                   && inputBlock->dataTypeSize() <= 4 );
     170                 :            :   // Check for sources
     171                 :            :   QString source;
     172                 :            :   if ( useOpenCL )
     173                 :            :   {
     174                 :            :     source = QgsOpenClUtils::sourceFromBaseName( QStringLiteral( "hillshade_renderer" ) );
     175                 :            :     if ( source.isEmpty() )
     176                 :            :     {
     177                 :            :       useOpenCL = false;
     178                 :            :       QgsMessageLog::logMessage( QObject::tr( "Error loading OpenCL program source from path %1" ).arg( QgsOpenClUtils::sourcePath() ), QgsOpenClUtils::LOGMESSAGE_TAG, Qgis::Critical );
     179                 :            :     }
     180                 :            :   }
     181                 :            : 
     182                 :            : #ifdef QGISDEBUG
     183                 :            :   std::chrono::time_point<std::chrono::system_clock> startTime( std::chrono::system_clock::now() );
     184                 :            : #endif
     185                 :            : 
     186                 :            :   if ( useOpenCL )
     187                 :            :   {
     188                 :            : 
     189                 :            :     try
     190                 :            :     {
     191                 :            :       std::size_t inputDataTypeSize = inputBlock->dataTypeSize();
     192                 :            :       std::size_t outputDataTypeSize = outputBlock->dataTypeSize();
     193                 :            :       // Buffer scanline, 1px height, 2px wider
     194                 :            :       QString typeName;
     195                 :            :       switch ( inputBlock->dataType() )
     196                 :            :       {
     197                 :            :         case Qgis::DataType::Byte:
     198                 :            :           typeName = QStringLiteral( "unsigned char" );
     199                 :            :           break;
     200                 :            :         case Qgis::DataType::UInt16:
     201                 :            :           typeName = QStringLiteral( "unsigned int" );
     202                 :            :           break;
     203                 :            :         case Qgis::DataType::Int16:
     204                 :            :           typeName = QStringLiteral( "short" );
     205                 :            :           break;
     206                 :            :         case Qgis::DataType::UInt32:
     207                 :            :           typeName = QStringLiteral( "unsigned int" );
     208                 :            :           break;
     209                 :            :         case Qgis::DataType::Int32:
     210                 :            :           typeName = QStringLiteral( "int" );
     211                 :            :           break;
     212                 :            :         case Qgis::DataType::Float32:
     213                 :            :           typeName = QStringLiteral( "float" );
     214                 :            :           break;
     215                 :            :         default:
     216                 :            :           throw QgsException( QStringLiteral( "Unsupported data type for OpenCL processing." ) );
     217                 :            :       }
     218                 :            : 
     219                 :            :       if ( inputBlock->dataType() != Qgis::DataType::Float32 )
     220                 :            :       {
     221                 :            :         source.replace( QLatin1String( "__global float *scanLine" ), QStringLiteral( "__global %1 *scanLine" ).arg( typeName ) );
     222                 :            :       }
     223                 :            : 
     224                 :            :       // Data type for input is Float32 (4 bytes)
     225                 :            :       std::size_t scanLineWidth( inputBlock->width() + 2 );
     226                 :            :       std::size_t inputSize( inputDataTypeSize * inputBlock->width() );
     227                 :            : 
     228                 :            :       // CL buffers are also 2px wider for nodata initial and final columns
     229                 :            :       std::size_t bufferWidth( width + 2 );
     230                 :            :       std::size_t bufferSize( inputDataTypeSize * bufferWidth );
     231                 :            : 
     232                 :            :       // Buffer scanlines, 1px height, 2px wider
     233                 :            :       // Data type for input is Float32 (4 bytes)
     234                 :            :       // keep only three scanlines in memory at a time, make room for initial and final nodata
     235                 :            :       std::unique_ptr<QgsRasterBlock> scanLine = std::make_unique<QgsRasterBlock>( inputBlock->dataType(), scanLineWidth, 1 );
     236                 :            :       // Note: output block is not 2px wider and it is an image
     237                 :            :       // Prepare context and queue
     238                 :            :       cl::Context ctx = QgsOpenClUtils::context();
     239                 :            :       cl::CommandQueue queue = QgsOpenClUtils::commandQueue();
     240                 :            : 
     241                 :            :       // Cast to float (because double just crashes on some GPUs)
     242                 :            :       std::vector<float> rasterParams;
     243                 :            :       rasterParams.push_back( inputBlock->noDataValue() );
     244                 :            :       rasterParams.push_back( outputBlock->noDataValue() );
     245                 :            :       rasterParams.push_back( mZFactor );
     246                 :            :       rasterParams.push_back( cellXSize );
     247                 :            :       rasterParams.push_back( cellYSize );
     248                 :            :       rasterParams.push_back( static_cast<float>( mOpacity ) ); // 5
     249                 :            : 
     250                 :            :       // For fast formula from GDAL DEM
     251                 :            :       rasterParams.push_back( cos_az_mul_cos_alt_mul_z_mul_254 ); // 6
     252                 :            :       rasterParams.push_back( sin_az_mul_cos_alt_mul_z_mul_254 ); // 7
     253                 :            :       rasterParams.push_back( square_z ); // 8
     254                 :            :       rasterParams.push_back( sin_altRadians_mul_254 ); // 9
     255                 :            : 
     256                 :            :       // For multidirectional fast formula
     257                 :            :       rasterParams.push_back( sin_altRadians_mul_127 ); // 10
     258                 :            :       rasterParams.push_back( cos225_az_mul_cos_alt_mul_z_mul_127 ); // 11
     259                 :            :       rasterParams.push_back( cos_alt_mul_z_mul_127 ); // 12
     260                 :            : 
     261                 :            :       // Default color for nodata (BGR components)
     262                 :            :       rasterParams.push_back( static_cast<float>( qBlue( defaultNodataColor ) ) ); // 13
     263                 :            :       rasterParams.push_back( static_cast<float>( qGreen( defaultNodataColor ) ) ); // 14
     264                 :            :       rasterParams.push_back( static_cast<float>( qRed( defaultNodataColor ) ) ); // 15
     265                 :            :       rasterParams.push_back( static_cast<float>( qAlpha( defaultNodataColor ) ) / 255.0f ); // 16
     266                 :            : 
     267                 :            :       // Whether use multidirectional
     268                 :            :       rasterParams.push_back( static_cast<float>( mMultiDirectional ) ); // 17
     269                 :            : 
     270                 :            :       cl::Buffer rasterParamsBuffer( queue, rasterParams.begin(), rasterParams.end(), true, false, nullptr );
     271                 :            :       cl::Buffer scanLine1Buffer( ctx, CL_MEM_READ_ONLY, bufferSize, nullptr, nullptr );
     272                 :            :       cl::Buffer scanLine2Buffer( ctx, CL_MEM_READ_ONLY, bufferSize, nullptr, nullptr );
     273                 :            :       cl::Buffer scanLine3Buffer( ctx, CL_MEM_READ_ONLY, bufferSize, nullptr, nullptr );
     274                 :            :       cl::Buffer *scanLineBuffer[3] = {&scanLine1Buffer, &scanLine2Buffer, &scanLine3Buffer};
     275                 :            :       // Note that result buffer is an image
     276                 :            :       cl::Buffer resultLineBuffer( ctx, CL_MEM_WRITE_ONLY, outputDataTypeSize * width, nullptr, nullptr );
     277                 :            : 
     278                 :            :       static std::map<Qgis::DataType, cl::Program> programCache;
     279                 :            :       cl::Program program = programCache[inputBlock->dataType()];
     280                 :            :       if ( ! program.get() )
     281                 :            :       {
     282                 :            :         // Create a program from the kernel source
     283                 :            :         programCache[inputBlock->dataType()] = QgsOpenClUtils::buildProgram( source, QgsOpenClUtils::ExceptionBehavior::Throw );
     284                 :            :         program = programCache[inputBlock->dataType()];
     285                 :            :       }
     286                 :            : 
     287                 :            :       // Disable program cache when developing and testing cl program
     288                 :            :       // program = QgsOpenClUtils::buildProgram( ctx, source, QgsOpenClUtils::ExceptionBehavior::Throw );
     289                 :            : 
     290                 :            :       // Create the OpenCL kernel
     291                 :            :       auto kernel =  cl::KernelFunctor <
     292                 :            :                      cl::Buffer &,
     293                 :            :                      cl::Buffer &,
     294                 :            :                      cl::Buffer &,
     295                 :            :                      cl::Buffer &,
     296                 :            :                      cl::Buffer &
     297                 :            :                      > ( program, "processNineCellWindow" );
     298                 :            : 
     299                 :            : 
     300                 :            :       // Rotating buffer index
     301                 :            :       std::vector<int> rowIndex = {0, 1, 2};
     302                 :            : 
     303                 :            :       for ( int i = 0; i < height; i++ )
     304                 :            :       {
     305                 :            :         if ( feedback && feedback->isCanceled() )
     306                 :            :         {
     307                 :            :           break;
     308                 :            :         }
     309                 :            : 
     310                 :            :         if ( feedback )
     311                 :            :         {
     312                 :            :           feedback->setProgress( 100.0 * static_cast< double >( i ) / height );
     313                 :            :         }
     314                 :            : 
     315                 :            :         if ( i == 0 )
     316                 :            :         {
     317                 :            :           // Fill scanline 1 with (input) nodata for the values above the first row and feed scanline2 with the first row
     318                 :            :           scanLine->resetNoDataValue();
     319                 :            :           queue.enqueueWriteBuffer( scanLine1Buffer, CL_TRUE, 0, bufferSize, scanLine->bits( ) );
     320                 :            :           // Read first row
     321                 :            :           memcpy( scanLine->bits( 0, 1 ), inputBlock->bits( i, 0 ), inputSize );
     322                 :            :           queue.enqueueWriteBuffer( scanLine2Buffer, CL_TRUE, 0, bufferSize, scanLine->bits( ) ); // row 0
     323                 :            :           // Second row
     324                 :            :           memcpy( scanLine->bits( 0, 1 ), inputBlock->bits( i + 1, 0 ), inputSize );
     325                 :            :           queue.enqueueWriteBuffer( scanLine3Buffer, CL_TRUE, 0, bufferSize, scanLine->bits( ) ); //
     326                 :            :         }
     327                 :            :         else
     328                 :            :         {
     329                 :            :           // Normally fetch only scanLine3 and move forward one row
     330                 :            :           // Read scanline 3, fill the last row with nodata values if it's the last iteration
     331                 :            :           if ( i == inputBlock->height() - 1 )
     332                 :            :           {
     333                 :            :             scanLine->resetNoDataValue();
     334                 :            :             queue.enqueueWriteBuffer( *scanLineBuffer[rowIndex[2]], CL_TRUE, 0, bufferSize, scanLine->bits( ) );
     335                 :            :           }
     336                 :            :           else // Overwrite from input, skip first and last
     337                 :            :           {
     338                 :            :             queue.enqueueWriteBuffer( *scanLineBuffer[rowIndex[2]], CL_TRUE, inputDataTypeSize * 1 /* offset 1 */, inputSize, inputBlock->bits( i + 1, 0 ) );
     339                 :            :           }
     340                 :            :         }
     341                 :            : 
     342                 :            :         kernel( cl::EnqueueArgs(
     343                 :            :                   queue,
     344                 :            :                   cl::NDRange( width )
     345                 :            :                 ),
     346                 :            :                 *scanLineBuffer[rowIndex[0]],
     347                 :            :                 *scanLineBuffer[rowIndex[1]],
     348                 :            :                 *scanLineBuffer[rowIndex[2]],
     349                 :            :                 resultLineBuffer,
     350                 :            :                 rasterParamsBuffer
     351                 :            :               );
     352                 :            : 
     353                 :            :         queue.enqueueReadBuffer( resultLineBuffer, CL_TRUE, 0, outputDataTypeSize * outputBlock->width( ), outputBlock->bits( i, 0 ) );
     354                 :            :         std::rotate( rowIndex.begin(), rowIndex.begin() + 1, rowIndex.end() );
     355                 :            :       }
     356                 :            :     }
     357                 :            :     catch ( cl::Error &e )
     358                 :            :     {
     359                 :            :       QgsMessageLog::logMessage( QObject::tr( "Error running OpenCL program: %1 - %2" ).arg( e.what( ) ).arg( QgsOpenClUtils::errorText( e.err( ) ) ),
     360                 :            :                                  QgsOpenClUtils::LOGMESSAGE_TAG, Qgis::Critical );
     361                 :            :       QgsOpenClUtils::setEnabled( false );
     362                 :            :       QgsMessageLog::logMessage( QObject::tr( "OpenCL has been disabled, you can re-enable it in the options dialog." ),
     363                 :            :                                  QgsOpenClUtils::LOGMESSAGE_TAG, Qgis::Critical );
     364                 :            :     }
     365                 :            : 
     366                 :            :   } // End of OpenCL processing path
     367                 :            :   else  // Use the CPU and the original algorithm
     368                 :            :   {
     369                 :            : 
     370                 :            : #endif
     371                 :            : 
     372                 :          0 :     for ( qgssize i = 0; i < static_cast<qgssize>( height ); i++ )
     373                 :            :     {
     374                 :            : 
     375                 :          0 :       for ( qgssize j = 0; j < static_cast<qgssize>( width ); j++ )
     376                 :            :       {
     377                 :            : 
     378                 :          0 :         if ( inputBlock->isNoData( i,  j ) )
     379                 :            :         {
     380                 :          0 :           outputBlock->setColor( static_cast<int>( i ), static_cast<int>( j ), defaultNodataColor );
     381                 :          0 :           continue;
     382                 :            :         }
     383                 :            : 
     384                 :            :         qgssize iUp, iDown, jLeft, jRight;
     385                 :          0 :         if ( i == 0 )
     386                 :            :         {
     387                 :          0 :           iUp = i;
     388                 :          0 :           iDown = i + 1;
     389                 :          0 :         }
     390                 :          0 :         else if ( i < static_cast<qgssize>( height ) - 1 )
     391                 :            :         {
     392                 :          0 :           iUp = i - 1;
     393                 :          0 :           iDown = i + 1;
     394                 :          0 :         }
     395                 :            :         else
     396                 :            :         {
     397                 :          0 :           iUp = i - 1;
     398                 :          0 :           iDown = i;
     399                 :            :         }
     400                 :            : 
     401                 :          0 :         if ( j == 0 )
     402                 :            :         {
     403                 :          0 :           jLeft = j;
     404                 :          0 :           jRight = j + 1;
     405                 :          0 :         }
     406                 :          0 :         else if ( j <  static_cast<qgssize>( width ) - 1 )
     407                 :            :         {
     408                 :          0 :           jLeft = j - 1;
     409                 :          0 :           jRight = j + 1;
     410                 :          0 :         }
     411                 :            :         else
     412                 :            :         {
     413                 :          0 :           jLeft = j - 1;
     414                 :          0 :           jRight = j;
     415                 :            :         }
     416                 :            : 
     417                 :            :         double x11;
     418                 :            :         double x21;
     419                 :            :         double x31;
     420                 :            :         double x12;
     421                 :            :         double x22; // Working cell
     422                 :            :         double x32;
     423                 :            :         double x13;
     424                 :            :         double x23;
     425                 :            :         double x33;
     426                 :            : 
     427                 :            :         // This is center cell. It is not nodata. Use this in place of nodata neighbors
     428                 :          0 :         x22 = inputBlock->value( i, j );
     429                 :            : 
     430                 :          0 :         x11 = inputBlock->isNoData( iUp, jLeft )  ? x22 : inputBlock->value( iUp, jLeft );
     431                 :          0 :         x21 = inputBlock->isNoData( i, jLeft )     ? x22 : inputBlock->value( i, jLeft );
     432                 :          0 :         x31 = inputBlock->isNoData( iDown, jLeft ) ? x22 : inputBlock->value( iDown, jLeft );
     433                 :            : 
     434                 :          0 :         x12 = inputBlock->isNoData( iUp, j )       ? x22 : inputBlock->value( iUp, j );
     435                 :            :         // x22
     436                 :          0 :         x32 = inputBlock->isNoData( iDown, j )     ? x22 : inputBlock->value( iDown, j );
     437                 :            : 
     438                 :          0 :         x13 = inputBlock->isNoData( iUp, jRight )   ? x22 : inputBlock->value( iUp, jRight );
     439                 :          0 :         x23 = inputBlock->isNoData( i, jRight )     ? x22 : inputBlock->value( i, jRight );
     440                 :          0 :         x33 = inputBlock->isNoData( iDown, jRight ) ? x22 : inputBlock->value( iDown, jRight );
     441                 :            : 
     442                 :          0 :         double derX = calcFirstDerX( x11, x21, x31, x12, x22, x32, x13, x23, x33, cellXSize );
     443                 :          0 :         double derY = calcFirstDerY( x11, x21, x31, x12, x22, x32, x13, x23, x33, cellYSize );
     444                 :            : 
     445                 :            :         // Fast formula
     446                 :            : 
     447                 :            :         double grayValue;
     448                 :          0 :         if ( !mMultiDirectional )
     449                 :            :         {
     450                 :            :           // Standard single direction hillshade
     451                 :          0 :           grayValue = std::clamp( ( sin_altRadians_mul_254 -
     452                 :          0 :                                     ( derY * cos_az_mul_cos_alt_mul_z_mul_254 -
     453                 :          0 :                                       derX * sin_az_mul_cos_alt_mul_z_mul_254 ) ) /
     454                 :          0 :                                   std::sqrt( 1 + square_z * ( derX * derX + derY * derY ) ),
     455                 :          0 :                                   0.0, 255.0 );
     456                 :          0 :         }
     457                 :            :         else
     458                 :            :         {
     459                 :            :           // Weighted multi direction as in http://pubs.usgs.gov/of/1992/of92-422/of92-422.pdf
     460                 :            :           // Fast formula from GDAL DEM
     461                 :          0 :           const float xx = derX * derX;
     462                 :          0 :           const float yy = derY * derY;
     463                 :          0 :           const float xx_plus_yy = xx + yy;
     464                 :            :           // Flat?
     465                 :          0 :           if ( xx_plus_yy == 0.0 )
     466                 :            :           {
     467                 :          0 :             grayValue = std::clamp( static_cast<float>( 1.0 + sin_altRadians_mul_254 ), 0.0f, 255.0f );
     468                 :          0 :           }
     469                 :            :           else
     470                 :            :           {
     471                 :            :             // ... then the shade value from different azimuth
     472                 :          0 :             float val225_mul_127 = sin_altRadians_mul_127 +
     473                 :          0 :                                    ( derX - derY ) * cos225_az_mul_cos_alt_mul_z_mul_127;
     474                 :          0 :             val225_mul_127 = ( val225_mul_127 <= 0.0 ) ? 0.0 : val225_mul_127;
     475                 :          0 :             float val270_mul_127 = sin_altRadians_mul_127 -
     476                 :          0 :                                    derX * cos_alt_mul_z_mul_127;
     477                 :          0 :             val270_mul_127 = ( val270_mul_127 <= 0.0 ) ? 0.0 : val270_mul_127;
     478                 :          0 :             float val315_mul_127 = sin_altRadians_mul_127 +
     479                 :          0 :                                    ( derX + derY ) * cos225_az_mul_cos_alt_mul_z_mul_127;
     480                 :          0 :             val315_mul_127 = ( val315_mul_127 <= 0.0 ) ? 0.0 : val315_mul_127;
     481                 :          0 :             float val360_mul_127 = sin_altRadians_mul_127 -
     482                 :          0 :                                    derY * cos_alt_mul_z_mul_127;
     483                 :          0 :             val360_mul_127 = ( val360_mul_127 <= 0.0 ) ? 0.0 : val360_mul_127;
     484                 :            : 
     485                 :            :             // ... then the weighted shading
     486                 :          0 :             const float weight_225 = 0.5 * xx_plus_yy - derX * derY;
     487                 :          0 :             const float weight_270 = xx;
     488                 :          0 :             const float weight_315 = xx_plus_yy - weight_225;
     489                 :          0 :             const float weight_360 = yy;
     490                 :          0 :             const float cang_mul_127 = (
     491                 :          0 :                                          ( weight_225 * val225_mul_127 +
     492                 :          0 :                                            weight_270 * val270_mul_127 +
     493                 :          0 :                                            weight_315 * val315_mul_127 +
     494                 :          0 :                                            weight_360 * val360_mul_127 ) / xx_plus_yy ) /
     495                 :          0 :                                        ( 1 + square_z * xx_plus_yy );
     496                 :            : 
     497                 :          0 :             grayValue = std::clamp( 1.0f + cang_mul_127, 0.0f, 255.0f );
     498                 :            :           }
     499                 :            :         }
     500                 :            : 
     501                 :          0 :         double currentAlpha = mOpacity;
     502                 :          0 :         if ( mRasterTransparency )
     503                 :            :         {
     504                 :          0 :           currentAlpha = mRasterTransparency->alphaValue( x22, mOpacity * 255 ) / 255.0;
     505                 :          0 :         }
     506                 :          0 :         if ( mAlphaBand > 0 )
     507                 :            :         {
     508                 :          0 :           currentAlpha *= alphaBlock->value( i ) / 255.0;
     509                 :          0 :         }
     510                 :            : 
     511                 :          0 :         if ( qgsDoubleNear( currentAlpha, 1.0 ) )
     512                 :            :         {
     513                 :          0 :           outputBlock->setColor( i, j, qRgba( grayValue, grayValue, grayValue, 255 ) );
     514                 :          0 :         }
     515                 :            :         else
     516                 :            :         {
     517                 :          0 :           outputBlock->setColor( i, j, qRgba( currentAlpha * grayValue, currentAlpha * grayValue, currentAlpha * grayValue, currentAlpha * 255 ) );
     518                 :            :         }
     519                 :          0 :       }
     520                 :          0 :     }
     521                 :            : 
     522                 :            : #ifdef HAVE_OPENCL
     523                 :            :   } // End of switch in case OpenCL is not available or enabled
     524                 :            : 
     525                 :            : #ifdef QGISDEBUG
     526                 :            :   if ( QgsSettings().value( QStringLiteral( "Map/logCanvasRefreshEvent" ), false ).toBool() )
     527                 :            :   {
     528                 :            :     QgsMessageLog::logMessage( QStringLiteral( "%1 processing time for hillshade (%2 x %3 ): %4 ms" )
     529                 :            :                                .arg( useOpenCL ? QStringLiteral( "OpenCL" ) : QStringLiteral( "CPU" ) )
     530                 :            :                                .arg( width )
     531                 :            :                                .arg( height )
     532                 :            :                                .arg( std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::system_clock::now() - startTime ).count() ),
     533                 :            :                                tr( "Rendering" ) );
     534                 :            :   }
     535                 :            : #endif
     536                 :            : #endif
     537                 :            : 
     538                 :          0 :   return outputBlock.release();
     539                 :          0 : }
     540                 :            : 
     541                 :          0 : QList<int> QgsHillshadeRenderer::usesBands() const
     542                 :            : {
     543                 :          0 :   QList<int> bandList;
     544                 :          0 :   if ( mBand != -1 )
     545                 :            :   {
     546                 :          0 :     bandList << mBand;
     547                 :          0 :   }
     548                 :          0 :   return bandList;
     549                 :            : 
     550                 :          0 : }
     551                 :            : 
     552                 :          0 : void QgsHillshadeRenderer::setBand( int bandNo )
     553                 :            : {
     554                 :          0 :   if ( bandNo > mInput->bandCount() || bandNo <= 0 )
     555                 :            :   {
     556                 :          0 :     return;
     557                 :            :   }
     558                 :          0 :   mBand = bandNo;
     559                 :          0 : }
     560                 :            : 
     561                 :          0 : double QgsHillshadeRenderer::calcFirstDerX( double x11, double x21, double x31, double x12, double x22, double x32, double x13, double x23, double x33, double cellsize )
     562                 :            : {
     563                 :            :   Q_UNUSED( x12 )
     564                 :            :   Q_UNUSED( x22 )
     565                 :            :   Q_UNUSED( x32 )
     566                 :          0 :   return ( ( x13 + x23 + x23 + x33 ) - ( x11 + x21 + x21 + x31 ) ) / ( 8 * cellsize );
     567                 :            : }
     568                 :            : 
     569                 :          0 : double QgsHillshadeRenderer::calcFirstDerY( double x11, double x21, double x31, double x12, double x22, double x32, double x13, double x23, double x33, double cellsize )
     570                 :            : {
     571                 :            :   Q_UNUSED( x21 )
     572                 :            :   Q_UNUSED( x22 )
     573                 :            :   Q_UNUSED( x23 )
     574                 :          0 :   return ( ( x31 + x32 + x32 + x33 ) - ( x11 + x12 + x12 + x13 ) ) / ( 8 * -cellsize );
     575                 :            : }
     576                 :            : 
     577                 :          0 : void QgsHillshadeRenderer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
     578                 :            : {
     579                 :            :   // create base structure
     580                 :          0 :   QgsRasterRenderer::toSld( doc, element, props );
     581                 :            : 
     582                 :            :   // look for RasterSymbolizer tag
     583                 :          0 :   QDomNodeList elements = element.elementsByTagName( QStringLiteral( "sld:RasterSymbolizer" ) );
     584                 :          0 :   if ( elements.size() == 0 )
     585                 :          0 :     return;
     586                 :            : 
     587                 :            :   // there SHOULD be only one
     588                 :          0 :   QDomElement rasterSymbolizerElem = elements.at( 0 ).toElement();
     589                 :            : 
     590                 :            :   // add Channel Selection tags (if band is not default 1)
     591                 :            :   // Need to insert channelSelection in the correct sequence as in SLD standard e.g.
     592                 :            :   // after opacity or geometry or as first element after sld:RasterSymbolizer
     593                 :          0 :   if ( band() != 1 )
     594                 :            :   {
     595                 :          0 :     QDomElement channelSelectionElem = doc.createElement( QStringLiteral( "sld:ChannelSelection" ) );
     596                 :          0 :     elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Opacity" ) );
     597                 :          0 :     if ( elements.size() != 0 )
     598                 :            :     {
     599                 :          0 :       rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
     600                 :          0 :     }
     601                 :            :     else
     602                 :            :     {
     603                 :          0 :       elements = rasterSymbolizerElem.elementsByTagName( QStringLiteral( "sld:Geometry" ) );
     604                 :          0 :       if ( elements.size() != 0 )
     605                 :            :       {
     606                 :          0 :         rasterSymbolizerElem.insertAfter( channelSelectionElem, elements.at( 0 ) );
     607                 :          0 :       }
     608                 :            :       else
     609                 :            :       {
     610                 :          0 :         rasterSymbolizerElem.insertBefore( channelSelectionElem, rasterSymbolizerElem.firstChild() );
     611                 :            :       }
     612                 :            :     }
     613                 :            : 
     614                 :            :     // for gray band
     615                 :          0 :     QDomElement channelElem = doc.createElement( QStringLiteral( "sld:GrayChannel" ) );
     616                 :          0 :     channelSelectionElem.appendChild( channelElem );
     617                 :            : 
     618                 :            :     // set band
     619                 :          0 :     QDomElement sourceChannelNameElem = doc.createElement( QStringLiteral( "sld:SourceChannelName" ) );
     620                 :          0 :     sourceChannelNameElem.appendChild( doc.createTextNode( QString::number( band() ) ) );
     621                 :          0 :     channelElem.appendChild( sourceChannelNameElem );
     622                 :          0 :   }
     623                 :            : 
     624                 :            :   // add ShadedRelief tag
     625                 :          0 :   QDomElement shadedReliefElem = doc.createElement( QStringLiteral( "sld:ShadedRelief" ) );
     626                 :          0 :   rasterSymbolizerElem.appendChild( shadedReliefElem );
     627                 :            : 
     628                 :            :   // brightnessOnly tag
     629                 :          0 :   QDomElement brightnessOnlyElem = doc.createElement( QStringLiteral( "sld:BrightnessOnly" ) );
     630                 :          0 :   brightnessOnlyElem.appendChild( doc.createTextNode( QStringLiteral( "true" ) ) );
     631                 :          0 :   shadedReliefElem.appendChild( brightnessOnlyElem );
     632                 :            : 
     633                 :            :   // ReliefFactor tag
     634                 :          0 :   QDomElement reliefFactorElem = doc.createElement( QStringLiteral( "sld:ReliefFactor" ) );
     635                 :          0 :   reliefFactorElem.appendChild( doc.createTextNode( QString::number( zFactor() ) ) );
     636                 :          0 :   shadedReliefElem.appendChild( reliefFactorElem );
     637                 :            : 
     638                 :            :   // altitude VendorOption tag
     639                 :          0 :   QDomElement altitudeVendorOptionElem = doc.createElement( QStringLiteral( "sld:VendorOption" ) );
     640                 :          0 :   altitudeVendorOptionElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "altitude" ) );
     641                 :          0 :   altitudeVendorOptionElem.appendChild( doc.createTextNode( QString::number( altitude() ) ) );
     642                 :          0 :   shadedReliefElem.appendChild( altitudeVendorOptionElem );
     643                 :            : 
     644                 :            :   // azimuth VendorOption tag
     645                 :          0 :   QDomElement azimutVendorOptionElem = doc.createElement( QStringLiteral( "sld:VendorOption" ) );
     646                 :          0 :   azimutVendorOptionElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "azimuth" ) );
     647                 :          0 :   azimutVendorOptionElem.appendChild( doc.createTextNode( QString::number( azimuth() ) ) );
     648                 :          0 :   shadedReliefElem.appendChild( azimutVendorOptionElem );
     649                 :            : 
     650                 :            :   // multidirectional VendorOption tag
     651                 :          0 :   QDomElement multidirectionalVendorOptionElem = doc.createElement( QStringLiteral( "sld:VendorOption" ) );
     652                 :          0 :   multidirectionalVendorOptionElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "multidirectional" ) );
     653                 :          0 :   multidirectionalVendorOptionElem.appendChild( doc.createTextNode( QString::number( multiDirectional() ) ) );
     654                 :          0 :   shadedReliefElem.appendChild( multidirectionalVendorOptionElem );
     655                 :          0 : }

Generated by: LCOV version 1.14