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

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :      qgsrenderchecker.cpp
       3                 :            :      --------------------------------------
       4                 :            :     Date                 : 18 Jan 2008
       5                 :            :     Copyright            : (C) 2008 by Tim Sutton
       6                 :            :     Email                : tim @ linfiniti.com
       7                 :            :  ***************************************************************************
       8                 :            :  *                                                                         *
       9                 :            :  *   This program is free software; you can redistribute it and/or modify  *
      10                 :            :  *   it under the terms of the GNU General Public License as published by  *
      11                 :            :  *   the Free Software Foundation; either version 2 of the License, or     *
      12                 :            :  *   (at your option) any later version.                                   *
      13                 :            :  *                                                                         *
      14                 :            :  ***************************************************************************/
      15                 :            : 
      16                 :            : #include "qgsrenderchecker.h"
      17                 :            : 
      18                 :            : #include "qgis.h"
      19                 :            : #include "qgsmaprenderersequentialjob.h"
      20                 :            : #include "qgsgeometry.h"
      21                 :            : 
      22                 :            : #include <QColor>
      23                 :            : #include <QPainter>
      24                 :            : #include <QImage>
      25                 :            : #include <QCryptographicHash>
      26                 :            : #include <QByteArray>
      27                 :            : #include <QDebug>
      28                 :            : #include <QBuffer>
      29                 :            : #include <QUuid>
      30                 :            : 
      31                 :         18 : QgsRenderChecker::QgsRenderChecker()
      32                 :         18 :   : mBasePath( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/control_images/" ) ) //defined in CmakeLists.txt
      33                 :            : {
      34                 :          6 : }
      35                 :            : 
      36                 :          6 : QString QgsRenderChecker::controlImagePath() const
      37                 :            : {
      38                 :          6 :   return mBasePath + ( mBasePath.endsWith( '/' ) ? QString() : QStringLiteral( "/" ) ) + mControlPathPrefix;
      39                 :          0 : }
      40                 :            : 
      41                 :          0 : void QgsRenderChecker::setControlImagePath( const QString &path )
      42                 :            : {
      43                 :          0 :   mBasePath = path;
      44                 :          0 : }
      45                 :            : 
      46                 :          6 : void QgsRenderChecker::setControlName( const QString &name )
      47                 :            : {
      48                 :          6 :   mControlName = name;
      49                 :          6 :   mExpectedImageFile = controlImagePath() + name + '/' + mControlPathSuffix + name + "." + mControlExtension;
      50                 :          6 : }
      51                 :            : 
      52                 :          0 : void QgsRenderChecker::setControlPathSuffix( const QString &name )
      53                 :            : {
      54                 :          0 :   if ( !name.isEmpty() )
      55                 :          0 :     mControlPathSuffix = name + '/';
      56                 :            :   else
      57                 :          0 :     mControlPathSuffix.clear();
      58                 :          0 : }
      59                 :            : 
      60                 :          0 : QString QgsRenderChecker::imageToHash( const QString &imageFile )
      61                 :            : {
      62                 :          0 :   QImage myImage;
      63                 :          0 :   myImage.load( imageFile );
      64                 :          0 :   QByteArray myByteArray;
      65                 :          0 :   QBuffer myBuffer( &myByteArray );
      66                 :          0 :   myImage.save( &myBuffer, "PNG" );
      67                 :          0 :   QString myImageString = QString::fromUtf8( myByteArray.toBase64().data() );
      68                 :          0 :   QCryptographicHash myHash( QCryptographicHash::Md5 );
      69                 :          0 :   myHash.addData( myImageString.toUtf8() );
      70                 :          0 :   return myHash.result().toHex().constData();
      71                 :          0 : }
      72                 :            : 
      73                 :          0 : void QgsRenderChecker::setMapSettings( const QgsMapSettings &mapSettings )
      74                 :            : {
      75                 :          0 :   mMapSettings = mapSettings;
      76                 :          0 : }
      77                 :            : 
      78                 :          0 : void QgsRenderChecker::drawBackground( QImage *image )
      79                 :            : {
      80                 :            :   // create a 2x2 checker-board image
      81                 :          0 :   uchar pixDataRGB[] = { 255, 255, 255, 255,
      82                 :            :                          127, 127, 127, 255,
      83                 :            :                          127, 127, 127, 255,
      84                 :            :                          255, 255, 255, 255
      85                 :            :                        };
      86                 :            : 
      87                 :          0 :   QImage img( pixDataRGB, 2, 2, 8, QImage::Format_ARGB32 );
      88                 :          0 :   QPixmap pix = QPixmap::fromImage( img.scaled( 20, 20 ) );
      89                 :            : 
      90                 :            :   // fill image with texture
      91                 :          0 :   QBrush brush;
      92                 :          0 :   brush.setTexture( pix );
      93                 :          0 :   QPainter p( image );
      94                 :          0 :   p.setRenderHint( QPainter::Antialiasing, false );
      95                 :          0 :   p.fillRect( QRect( 0, 0, image->width(), image->height() ), brush );
      96                 :          0 :   p.end();
      97                 :          0 : }
      98                 :            : 
      99                 :          0 : bool QgsRenderChecker::isKnownAnomaly( const QString &diffImageFile )
     100                 :            : {
     101                 :          0 :   QString myControlImageDir = controlImagePath() + mControlName + '/';
     102                 :          0 :   QDir myDirectory = QDir( myControlImageDir );
     103                 :          0 :   QStringList myList;
     104                 :          0 :   QString myFilename = QStringLiteral( "*" );
     105                 :          0 :   myList = myDirectory.entryList( QStringList( myFilename ),
     106                 :          0 :                                   QDir::Files | QDir::NoSymLinks );
     107                 :            :   //remove the control file from the list as the anomalies are
     108                 :            :   //all files except the control file
     109                 :          0 :   myList.removeAt( myList.indexOf( QFileInfo( mExpectedImageFile ).fileName() ) );
     110                 :            : 
     111                 :          0 :   QString myImageHash = imageToHash( diffImageFile );
     112                 :            : 
     113                 :            : 
     114                 :          0 :   for ( int i = 0; i < myList.size(); ++i )
     115                 :            :   {
     116                 :          0 :     QString myFile = myList.at( i );
     117                 :          0 :     mReport += "<tr><td colspan=3>"
     118                 :          0 :                "Checking if " + myFile + " is a known anomaly.";
     119                 :          0 :     mReport += QLatin1String( "</td></tr>" );
     120                 :          0 :     QString myAnomalyHash = imageToHash( controlImagePath() + mControlName + '/' + myFile );
     121                 :          0 :     QString myHashMessage = QStringLiteral(
     122                 :            :                               "Checking if anomaly %1 (hash %2)<br>" )
     123                 :          0 :                             .arg( myFile,
     124                 :            :                                   myAnomalyHash );
     125                 :          0 :     myHashMessage += QStringLiteral( "&nbsp; matches %1 (hash %2)" )
     126                 :          0 :                      .arg( diffImageFile,
     127                 :            :                            myImageHash );
     128                 :            :     //foo CDash
     129                 :          0 :     emitDashMessage( QStringLiteral( "Anomaly check" ), QgsDartMeasurement::Text, myHashMessage );
     130                 :            : 
     131                 :          0 :     mReport += "<tr><td colspan=3>" + myHashMessage + "</td></tr>";
     132                 :          0 :     if ( myImageHash == myAnomalyHash )
     133                 :            :     {
     134                 :          0 :       mReport += "<tr><td colspan=3>"
     135                 :          0 :                  "Anomaly found! " + myFile;
     136                 :          0 :       mReport += QLatin1String( "</td></tr>" );
     137                 :          0 :       return true;
     138                 :            :     }
     139                 :          0 :   }
     140                 :          0 :   mReport += "<tr><td colspan=3>"
     141                 :            :              "No anomaly found! ";
     142                 :          0 :   mReport += QLatin1String( "</td></tr>" );
     143                 :          0 :   return false;
     144                 :          0 : }
     145                 :            : 
     146                 :         24 : void QgsRenderChecker::emitDashMessage( const QgsDartMeasurement &dashMessage )
     147                 :            : {
     148                 :         24 :   if ( mBufferDashMessages )
     149                 :          0 :     mDashMessages << dashMessage;
     150                 :            :   else
     151                 :         24 :     dashMessage.send();
     152                 :         24 : }
     153                 :            : 
     154                 :         24 : void QgsRenderChecker::emitDashMessage( const QString &name, QgsDartMeasurement::Type type, const QString &value )
     155                 :            : {
     156                 :         24 :   emitDashMessage( QgsDartMeasurement( name, type, value ) );
     157                 :         24 : }
     158                 :            : 
     159                 :          0 : bool QgsRenderChecker::runTest( const QString &testName,
     160                 :            :                                 unsigned int mismatchCount )
     161                 :            : {
     162                 :          0 :   if ( mExpectedImageFile.isEmpty() )
     163                 :            :   {
     164                 :          0 :     qDebug( "QgsRenderChecker::runTest failed - Expected Image File not set." );
     165                 :          0 :     mReport = "<table>"
     166                 :            :               "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
     167                 :            :               "<tr><td>Nothing rendered</td>\n<td>Failed because Expected "
     168                 :            :               "Image File not set.</td></tr></table>\n";
     169                 :          0 :     return false;
     170                 :            :   }
     171                 :            :   //
     172                 :            :   // Load the expected result pixmap
     173                 :            :   //
     174                 :          0 :   QImage myExpectedImage( mExpectedImageFile );
     175                 :          0 :   if ( myExpectedImage.isNull() )
     176                 :            :   {
     177                 :          0 :     qDebug() << "QgsRenderChecker::runTest failed - Could not load expected image from " << mExpectedImageFile;
     178                 :          0 :     mReport = "<table>"
     179                 :            :               "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
     180                 :            :               "<tr><td>Nothing rendered</td>\n<td>Failed because Expected "
     181                 :            :               "Image File could not be loaded.</td></tr></table>\n";
     182                 :          0 :     return false;
     183                 :            :   }
     184                 :          0 :   mMatchTarget = myExpectedImage.width() * myExpectedImage.height();
     185                 :            :   //
     186                 :            :   // Now render our layers onto a pixmap
     187                 :            :   //
     188                 :          0 :   mMapSettings.setBackgroundColor( qRgb( 152, 219, 249 ) );
     189                 :          0 :   mMapSettings.setFlag( QgsMapSettings::Antialiasing );
     190                 :          0 :   mMapSettings.setOutputSize( QSize( myExpectedImage.width(), myExpectedImage.height() ) / mMapSettings.devicePixelRatio() );
     191                 :            : 
     192                 :          0 :   QElapsedTimer myTime;
     193                 :          0 :   myTime.start();
     194                 :            : 
     195                 :          0 :   QgsMapRendererSequentialJob job( mMapSettings );
     196                 :          0 :   job.start();
     197                 :          0 :   job.waitForFinished();
     198                 :            : 
     199                 :          0 :   mElapsedTime = myTime.elapsed();
     200                 :            : 
     201                 :          0 :   QImage myImage = job.renderedImage();
     202                 :            :   Q_ASSERT( myImage.devicePixelRatioF() == mMapSettings.devicePixelRatio() );
     203                 :            : 
     204                 :            :   //
     205                 :            :   // Save the pixmap to disk so the user can make a
     206                 :            :   // visual assessment if needed
     207                 :            :   //
     208                 :          0 :   mRenderedImageFile = QDir::tempPath() + '/' + testName + "_result.png";
     209                 :            : 
     210                 :          0 :   myImage.setDotsPerMeterX( myExpectedImage.dotsPerMeterX() );
     211                 :          0 :   myImage.setDotsPerMeterY( myExpectedImage.dotsPerMeterY() );
     212                 :          0 :   if ( ! myImage.save( mRenderedImageFile, "PNG", 100 ) )
     213                 :            :   {
     214                 :          0 :     qDebug() << "QgsRenderChecker::runTest failed - Could not save rendered image to " << mRenderedImageFile;
     215                 :          0 :     mReport = "<table>"
     216                 :            :               "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
     217                 :            :               "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
     218                 :            :               "Image File could not be saved.</td></tr></table>\n";
     219                 :          0 :     return false;
     220                 :            :   }
     221                 :            : 
     222                 :            :   //create a world file to go with the image...
     223                 :            : 
     224                 :          0 :   QFile wldFile( QDir::tempPath() + '/' + testName + "_result.wld" );
     225                 :          0 :   if ( wldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
     226                 :            :   {
     227                 :          0 :     QgsRectangle r = mMapSettings.extent();
     228                 :          6 : 
     229                 :          6 :     QTextStream stream( &wldFile );
     230                 :          0 :     stream << QStringLiteral( "%1\r\n0 \r\n0 \r\n%2\r\n%3\r\n%4\r\n" )
     231                 :          0 :            .arg( qgsDoubleToString( mMapSettings.mapUnitsPerPixel() ),
     232                 :          0 :                  qgsDoubleToString( -mMapSettings.mapUnitsPerPixel() ),
     233                 :          0 :                  qgsDoubleToString( r.xMinimum() + mMapSettings.mapUnitsPerPixel() / 2.0 ),
     234                 :          0 :                  qgsDoubleToString( r.yMaximum() - mMapSettings.mapUnitsPerPixel() / 2.0 ) );
     235                 :          0 :   }
     236                 :            : 
     237                 :          0 :   return compareImages( testName, mismatchCount );
     238                 :          0 : }
     239                 :            : 
     240                 :          6 : 
     241                 :         12 : bool QgsRenderChecker::compareImages( const QString &testName,
     242                 :          6 :                                       unsigned int mismatchCount,
     243                 :          6 :                                       const QString &renderedImageFile )
     244                 :          6 : {
     245                 :          6 :   if ( mExpectedImageFile.isEmpty() )
     246                 :          6 :   {
     247                 :          0 :     qDebug( "QgsRenderChecker::runTest failed - Expected Image (control) File not set." );
     248                 :          0 :     mReport = "<table>"
     249                 :            :               "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
     250                 :          6 :               "<tr><td>Nothing rendered</td>\n<td>Failed because Expected "
     251                 :            :               "Image File not set.</td></tr></table>\n";
     252                 :          0 :     return false;
     253                 :            :   }
     254                 :            : 
     255                 :          6 :   return compareImages( testName, mExpectedImageFile, renderedImageFile, mismatchCount );
     256                 :          6 : }
     257                 :            : 
     258                 :          6 : bool QgsRenderChecker::compareImages( const QString &testName, const QString &referenceImageFile, const QString &renderedImageFile, unsigned int mismatchCount )
     259                 :            : {
     260                 :          6 :   if ( ! renderedImageFile.isEmpty() )
     261                 :            :   {
     262                 :          0 :     mRenderedImageFile = renderedImageFile;
     263                 :            : #ifdef Q_OS_WIN
     264                 :            :     mRenderedImageFile = mRenderedImageFile.replace( '\\', '/' );
     265                 :            : #endif
     266                 :          0 :   }
     267                 :            : 
     268                 :          6 :   if ( mRenderedImageFile.isEmpty() )
     269                 :            :   {
     270                 :          0 :     qDebug( "QgsRenderChecker::runTest failed - Rendered Image File not set." );
     271                 :          0 :     mReport = "<table>"
     272                 :            :               "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
     273                 :            :               "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
     274                 :            :               "Image File not set.</td></tr></table>\n";
     275                 :          0 :     return false;
     276                 :            :   }
     277                 :            : 
     278                 :            :   //
     279                 :            :   // Load /create the images
     280                 :            :   //
     281                 :          6 :   QImage myExpectedImage( referenceImageFile );
     282                 :          6 :   QImage myResultImage( mRenderedImageFile );
     283                 :          6 :   if ( myResultImage.isNull() )
     284                 :            :   {
     285                 :          0 :     qDebug() << "QgsRenderChecker::runTest failed - Could not load rendered image from " << mRenderedImageFile;
     286                 :          0 :     mReport = "<table>"
     287                 :            :               "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
     288                 :            :               "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
     289                 :            :               "Image File could not be loaded.</td></tr></table>\n";
     290                 :          0 :     return false;
     291                 :            :   }
     292                 :         12 :   QImage myDifferenceImage( myExpectedImage.width(),
     293                 :          6 :                             myExpectedImage.height(),
     294                 :            :                             QImage::Format_RGB32 );
     295                 :          6 :   QString myDiffImageFile = QDir::tempPath() + '/' + testName + "_result_diff.png";
     296                 :          6 :   myDifferenceImage.fill( qRgb( 152, 219, 249 ) );
     297                 :            : 
     298                 :            :   //check for mask
     299                 :          6 :   QString maskImagePath = referenceImageFile;
     300                 :          6 :   maskImagePath.chop( 4 ); //remove .png extension
     301                 :          6 :   maskImagePath += QLatin1String( "_mask.png" );
     302                 :          6 :   const QImage maskImage( maskImagePath );
     303                 :          6 :   const bool hasMask = !maskImage.isNull();
     304                 :            : 
     305                 :            :   //
     306                 :            :   // Set pixel count score and target
     307                 :            :   //
     308                 :          6 :   mMatchTarget = myExpectedImage.width() * myExpectedImage.height();
     309                 :          6 :   unsigned int myPixelCount = myResultImage.width() * myResultImage.height();
     310                 :            :   //
     311                 :            :   // Set the report with the result
     312                 :            :   //
     313                 :         12 :   mReport = QStringLiteral( "<script src=\"file://%1/../renderchecker.js\"></script>\n" ).arg( TEST_DATA_DIR );
     314                 :          6 :   mReport += QLatin1String( "<table>" );
     315                 :          6 :   mReport += QLatin1String( "<tr><td colspan=2>" );
     316                 :         12 :   mReport += QString( "<tr><td colspan=2>"
     317                 :            :                       "Test image and result image for %1<br>"
     318                 :            :                       "Expected size: %2 w x %3 h (%4 pixels)<br>"
     319                 :            :                       "Actual   size: %5 w x %6 h (%7 pixels)"
     320                 :            :                       "</td></tr>" )
     321                 :          6 :              .arg( testName )
     322                 :          6 :              .arg( myExpectedImage.width() ).arg( myExpectedImage.height() ).arg( mMatchTarget )
     323                 :          6 :              .arg( myResultImage.width() ).arg( myResultImage.height() ).arg( myPixelCount );
     324                 :         12 :   mReport += QString( "<tr><td colspan=2>\n"
     325                 :            :                       "Expected Duration : <= %1 (0 indicates not specified)<br>"
     326                 :            :                       "Actual Duration : %2 ms<br></td></tr>" )
     327                 :          6 :              .arg( mElapsedTimeTarget )
     328                 :          6 :              .arg( mElapsedTime );
     329                 :            : 
     330                 :            :   // limit image size in page to something reasonable
     331                 :          6 :   int imgWidth = 420;
     332                 :          6 :   int imgHeight = 280;
     333                 :          6 :   if ( ! myExpectedImage.isNull() )
     334                 :            :   {
     335                 :          6 :     imgWidth = std::min( myExpectedImage.width(), imgWidth );
     336                 :          6 :     imgHeight = myExpectedImage.height() * imgWidth / myExpectedImage.width();
     337                 :          6 :   }
     338                 :            : 
     339                 :         12 :   QString myImagesString = QString(
     340                 :            :                              "<tr>"
     341                 :            :                              "<td colspan=2>Compare actual and expected result</td>"
     342                 :            :                              "<td>Difference (all blue is good, any red is bad)</td>"
     343                 :            :                              "</tr>\n<tr>"
     344                 :            :                              "<td colspan=2 id=\"td-%1-%7\"></td>\n"
     345                 :            :                              "<td align=center><img width=%5 height=%6 src=\"file://%2\"></td>\n"
     346                 :            :                              "</tr>"
     347                 :            :                              "</table>\n"
     348                 :            :                              "<script>\naddComparison(\"td-%1-%7\",\"file://%3\",\"file://%4\",%5,%6);\n</script>\n" )
     349                 :         12 :                            .arg( testName,
     350                 :            :                                  myDiffImageFile,
     351                 :          6 :                                  mRenderedImageFile,
     352                 :          6 :                                  referenceImageFile )
     353                 :          6 :                            .arg( imgWidth ).arg( imgHeight )
     354                 :          6 :                            .arg( QUuid::createUuid().toString().mid( 1, 6 ) );
     355                 :            : 
     356                 :          6 :   QString prefix;
     357                 :          6 :   if ( !mControlPathPrefix.isNull() )
     358                 :            :   {
     359                 :          0 :     prefix = QStringLiteral( " (prefix %1)" ).arg( mControlPathPrefix );
     360                 :          0 :   }
     361                 :            :   //
     362                 :            :   // To get the images into CDash
     363                 :            :   //
     364                 :          6 :   emitDashMessage( "Rendered Image " + testName + prefix, QgsDartMeasurement::ImagePng, mRenderedImageFile );
     365                 :          6 :   emitDashMessage( "Expected Image " + testName + prefix, QgsDartMeasurement::ImagePng, referenceImageFile );
     366                 :            : 
     367                 :            :   //
     368                 :            :   // Put the same info to debug too
     369                 :            :   //
     370                 :            : 
     371                 :          6 :   if ( myExpectedImage.width() != myResultImage.width() || myExpectedImage.height() != myResultImage.height() )
     372                 :            :   {
     373                 :          0 :     qDebug( "Expected size: %dw x %dh", myExpectedImage.width(), myExpectedImage.height() );
     374                 :          0 :     qDebug( "Actual   size: %dw x %dh", myResultImage.width(), myResultImage.height() );
     375                 :          0 :     if ( hasMask )
     376                 :          0 :       qDebug( "Mask size: %dw x %dh", maskImage.width(), maskImage.height() );
     377                 :          0 :   }
     378                 :            : 
     379                 :          6 :   if ( mMatchTarget != myPixelCount )
     380                 :            :   {
     381                 :          0 :     qDebug( "Test image and result image for %s are different dimensions", testName.toLocal8Bit().constData() );
     382                 :            : 
     383                 :          0 :     if ( std::abs( myExpectedImage.width() - myResultImage.width() ) > mMaxSizeDifferenceX ||
     384                 :          0 :          std::abs( myExpectedImage.height() - myResultImage.height() ) > mMaxSizeDifferenceY )
     385                 :            :     {
     386                 :          0 :       mReport += QLatin1String( "<tr><td colspan=3>" );
     387                 :          0 :       mReport += "<font color=red>Expected image and result image for " + testName + " are different dimensions - FAILING!</font>";
     388                 :          0 :       mReport += QLatin1String( "</td></tr>" );
     389                 :          0 :       mReport += myImagesString;
     390                 :          0 :       return false;
     391                 :            :     }
     392                 :            :     else
     393                 :            :     {
     394                 :          0 :       mReport += QLatin1String( "<tr><td colspan=3>" );
     395                 :          0 :       mReport += "Expected image and result image for " + testName + " are different dimensions, but within tolerance";
     396                 :          0 :       mReport += QLatin1String( "</td></tr>" );
     397                 :            :     }
     398                 :          0 :   }
     399                 :            : 
     400                 :          6 :   if ( myExpectedImage.format() == QImage::Format_Indexed8 )
     401                 :            :   {
     402                 :          0 :     if ( myResultImage.format() != QImage::Format_Indexed8 )
     403                 :            :     {
     404                 :          0 :       qDebug() << "Expected image and result image for " << testName << " have different formats (8bit format is expected) - FAILING!";
     405                 :            : 
     406                 :          0 :       mReport += QLatin1String( "<tr><td colspan=3>" );
     407                 :          0 :       mReport += "<font color=red>Expected image and result image for " + testName + " have different formats (8bit format is expected) - FAILING!</font>";
     408                 :          0 :       mReport += QLatin1String( "</td></tr>" );
     409                 :          0 :       mReport += myImagesString;
     410                 :          0 :       return false;
     411                 :            :     }
     412                 :            : 
     413                 :            :     // When we compute the diff between the 2 images, we use constScanLine expecting a QRgb color
     414                 :            :     // but this method returns color table index for 8 bit image, not color.
     415                 :            :     // So we convert the 2 images in 32 bits so the diff works correctly
     416                 :          0 :     myResultImage = myResultImage.convertToFormat( QImage::Format_ARGB32 );
     417                 :          0 :     myExpectedImage = myExpectedImage.convertToFormat( QImage::Format_ARGB32 );
     418                 :          0 :   }
     419                 :            : 
     420                 :            : 
     421                 :            :   //
     422                 :            :   // Now iterate through them counting how many
     423                 :            :   // dissimilar pixel values there are
     424                 :            :   //
     425                 :            : 
     426                 :          6 :   int maxHeight = std::min( myExpectedImage.height(), myResultImage.height() );
     427                 :          6 :   int maxWidth = std::min( myExpectedImage.width(), myResultImage.width() );
     428                 :            : 
     429                 :          6 :   mMismatchCount = 0;
     430                 :          6 :   int colorTolerance = static_cast< int >( mColorTolerance );
     431                 :       1506 :   for ( int y = 0; y < maxHeight; ++y )
     432                 :            :   {
     433                 :       1500 :     const QRgb *expectedScanline = reinterpret_cast< const QRgb * >( myExpectedImage.constScanLine( y ) );
     434                 :       1500 :     const QRgb *resultScanline = reinterpret_cast< const QRgb * >( myResultImage.constScanLine( y ) );
     435                 :       1500 :     const QRgb *maskScanline = ( hasMask && maskImage.height() > y ) ? reinterpret_cast< const QRgb * >( maskImage.constScanLine( y ) ) : nullptr;
     436                 :       1500 :     QRgb *diffScanline = reinterpret_cast< QRgb * >( myDifferenceImage.scanLine( y ) );
     437                 :            : 
     438                 :     376500 :     for ( int x = 0; x < maxWidth; ++x )
     439                 :            :     {
     440                 :     375000 :       int maskTolerance = ( maskScanline && maskImage.width() > x ) ? qRed( maskScanline[ x ] ) : 0;
     441                 :     375000 :       int pixelTolerance = std::max( colorTolerance, maskTolerance );
     442                 :     375000 :       if ( pixelTolerance == 255 )
     443                 :            :       {
     444                 :            :         //skip pixel
     445                 :          1 :         continue;
     446                 :            :       }
     447                 :            : 
     448                 :     374999 :       QRgb myExpectedPixel = expectedScanline[x];
     449                 :     374999 :       QRgb myActualPixel = resultScanline[x];
     450                 :     374999 :       if ( pixelTolerance == 0 )
     451                 :            :       {
     452                 :     374873 :         if ( myExpectedPixel != myActualPixel )
     453                 :            :         {
     454                 :          0 :           ++mMismatchCount;
     455                 :          0 :           diffScanline[ x ] = qRgb( 255, 0, 0 );
     456                 :          0 :         }
     457                 :     374873 :       }
     458                 :            :       else
     459                 :            :       {
     460                 :        252 :         if ( std::abs( qRed( myExpectedPixel ) - qRed( myActualPixel ) ) > pixelTolerance ||
     461                 :        126 :              std::abs( qGreen( myExpectedPixel ) - qGreen( myActualPixel ) ) > pixelTolerance ||
     462                 :        126 :              std::abs( qBlue( myExpectedPixel ) - qBlue( myActualPixel ) ) > pixelTolerance ||
     463                 :        126 :              std::abs( qAlpha( myExpectedPixel ) - qAlpha( myActualPixel ) ) > pixelTolerance )
     464                 :            :         {
     465                 :          0 :           ++mMismatchCount;
     466                 :          0 :           diffScanline[ x ] = qRgb( 255, 0, 0 );
     467                 :          0 :         }
     468                 :            :       }
     469                 :     374999 :     }
     470                 :       1500 :   }
     471                 :            :   //
     472                 :            :   //save the diff image to disk
     473                 :            :   //
     474                 :          6 :   myDifferenceImage.save( myDiffImageFile );
     475                 :          6 :   emitDashMessage( "Difference Image " + testName + prefix, QgsDartMeasurement::ImagePng, myDiffImageFile );
     476                 :            : 
     477                 :            :   //
     478                 :            :   // Send match result to debug
     479                 :            :   //
     480                 :          6 :   if ( mMismatchCount > mismatchCount )
     481                 :            :   {
     482                 :          0 :     qDebug( "%d/%d pixels mismatched (%d allowed)", mMismatchCount, mMatchTarget, mismatchCount );
     483                 :          0 :   }
     484                 :            : 
     485                 :            :   //
     486                 :            :   // Send match result to report
     487                 :            :   //
     488                 :         18 :   mReport += QStringLiteral( "<tr><td colspan=3>%1/%2 pixels mismatched (allowed threshold: %3, allowed color component tolerance: %4)</td></tr>" )
     489                 :          6 :              .arg( mMismatchCount ).arg( mMatchTarget ).arg( mismatchCount ).arg( mColorTolerance );
     490                 :            : 
     491                 :            :   //
     492                 :            :   // And send it to CDash
     493                 :            :   //
     494                 :         18 :   emitDashMessage( QStringLiteral( "Mismatch Count" ), QgsDartMeasurement::Integer, QStringLiteral( "%1/%2" ).arg( mMismatchCount ).arg( mMatchTarget ) );
     495                 :            : 
     496                 :          6 :   if ( mMismatchCount <= mismatchCount )
     497                 :            :   {
     498                 :          6 :     mReport += QLatin1String( "<tr><td colspan = 3>\n" );
     499                 :          6 :     mReport += "Test image and result image for " + testName + " are matched<br>";
     500                 :          6 :     mReport += QLatin1String( "</td></tr>" );
     501                 :          6 :     if ( mElapsedTimeTarget != 0 && mElapsedTimeTarget < mElapsedTime )
     502                 :            :     {
     503                 :            :       //test failed because it took too long...
     504                 :          0 :       qDebug( "Test failed because render step took too long" );
     505                 :          0 :       mReport += QLatin1String( "<tr><td colspan = 3>\n" );
     506                 :          0 :       mReport += QLatin1String( "<font color=red>Test failed because render step took too long</font>" );
     507                 :          0 :       mReport += QLatin1String( "</td></tr>" );
     508                 :          0 :       mReport += myImagesString;
     509                 :          0 :       return false;
     510                 :            :     }
     511                 :            :     else
     512                 :            :     {
     513                 :          6 :       mReport += myImagesString;
     514                 :          6 :       return true;
     515                 :            :     }
     516                 :            :   }
     517                 :            : 
     518                 :          0 :   bool myAnomalyMatchFlag = isKnownAnomaly( myDiffImageFile );
     519                 :          0 :   if ( myAnomalyMatchFlag )
     520                 :            :   {
     521                 :          0 :     mReport += "<tr><td colspan=3>"
     522                 :            :                "Difference image matched a known anomaly - passing test! "
     523                 :            :                "</td></tr>";
     524                 :          0 :     return true;
     525                 :            :   }
     526                 :            : 
     527                 :          0 :   mReport += QLatin1String( "<tr><td colspan=3></td></tr>" );
     528                 :          0 :   emitDashMessage( QStringLiteral( "Image mismatch" ), QgsDartMeasurement::Text, "Difference image did not match any known anomaly or mask."
     529                 :            :                    " If you feel the difference image should be considered an anomaly "
     530                 :            :                    "you can do something like this\n"
     531                 :          0 :                    "cp '" + myDiffImageFile + "' " + controlImagePath() + mControlName +
     532                 :            :                    "/\nIf it should be included in the mask run\n"
     533                 :          0 :                    "scripts/generate_test_mask_image.py '" + referenceImageFile + "' '" + mRenderedImageFile + "'\n" );
     534                 :            : 
     535                 :          0 :   mReport += QLatin1String( "<tr><td colspan = 3>\n" );
     536                 :          0 :   mReport += "<font color=red>Test image and result image for " + testName + " are mismatched</font><br>";
     537                 :          0 :   mReport += QLatin1String( "</td></tr>" );
     538                 :          0 :   mReport += myImagesString;
     539                 :          0 :   return false;
     540                 :          6 : }

Generated by: LCOV version 1.14