|
QGIS API Documentation
master-6227475
|
00001 /*************************************************************************** 00002 qgsrenderchecker.cpp 00003 -------------------------------------- 00004 Date : 18 Jan 2008 00005 Copyright : (C) 2008 by Tim Sutton 00006 Email : tim @ linfiniti.com 00007 *************************************************************************** 00008 * * 00009 * This program is free software; you can redistribute it and/or modify * 00010 * it under the terms of the GNU General Public License as published by * 00011 * the Free Software Foundation; either version 2 of the License, or * 00012 * (at your option) any later version. * 00013 * * 00014 ***************************************************************************/ 00015 00016 #include "qgsrenderchecker.h" 00017 00018 #include <QColor> 00019 #include <QPainter> 00020 #include <QImage> 00021 #include <QTime> 00022 #include <QCryptographicHash> 00023 #include <QByteArray> 00024 #include <QDebug> 00025 #include <QBuffer> 00026 00027 QgsRenderChecker::QgsRenderChecker( ) : 00028 mReport( "" ), 00029 mExpectedImageFile( "" ), 00030 mRenderedImageFile( "" ), 00031 mMismatchCount( 0 ), 00032 mMatchTarget( 0 ), 00033 mElapsedTime( 0 ), 00034 mElapsedTimeTarget( 0 ), 00035 mpMapRenderer( NULL ), 00036 mControlPathPrefix( "" ) 00037 { 00038 00039 } 00040 00041 QString QgsRenderChecker::controlImagePath() const 00042 { 00043 QString myDataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt 00044 QString myControlImageDir = myDataDir + QDir::separator() + "control_images" + 00045 QDir::separator() + mControlPathPrefix; 00046 return myControlImageDir; 00047 } 00048 00049 void QgsRenderChecker::setControlName( const QString theName ) 00050 { 00051 mControlName = theName; 00052 mExpectedImageFile = controlImagePath() + theName + QDir::separator() 00053 + theName + ".png"; 00054 } 00055 00056 QString QgsRenderChecker::imageToHash( QString theImageFile ) 00057 { 00058 QImage myImage; 00059 myImage.load( theImageFile ); 00060 QByteArray myByteArray; 00061 QBuffer myBuffer( &myByteArray ); 00062 myImage.save( &myBuffer, "PNG" ); 00063 QString myImageString = QString::fromUtf8( myByteArray.toBase64().data() ); 00064 QCryptographicHash myHash( QCryptographicHash::Md5 ); 00065 myHash.addData( myImageString.toUtf8() ); 00066 return myHash.result().toHex().constData(); 00067 } 00068 00069 bool QgsRenderChecker::isKnownAnomaly( QString theDiffImageFile ) 00070 { 00071 QString myControlImageDir = controlImagePath() + mControlName 00072 + QDir::separator(); 00073 QDir myDirectory = QDir( myControlImageDir ); 00074 QStringList myList; 00075 QString myFilename = "*"; 00076 myList = myDirectory.entryList( QStringList( myFilename ), 00077 QDir::Files | QDir::NoSymLinks ); 00078 //remove the control file from teh list as the anomalies are 00079 //all files except the control file 00080 myList.removeAt( myList.indexOf( mExpectedImageFile ) ); 00081 00082 QString myImageHash = imageToHash( theDiffImageFile ); 00083 00084 00085 for ( int i = 0; i < myList.size(); ++i ) 00086 { 00087 QString myFile = myList.at( i ); 00088 mReport += "<tr><td colspan=3>" 00089 "Checking if " + myFile + " is a known anomaly."; 00090 mReport += "</td></tr>"; 00091 QString myAnomalyHash = imageToHash( controlImagePath() + mControlName 00092 + QDir::separator() + myFile ); 00093 QString myHashMessage = QString( 00094 "Checking if anomaly %1 (hash %2)" ) 00095 .arg( myFile ) 00096 .arg( myAnomalyHash ); 00097 myHashMessage += QString( " matches %1 (hash %2)" ) 00098 .arg( theDiffImageFile ) 00099 .arg( myImageHash ); 00100 //foo CDash 00101 QString myMeasureMessage = "<DartMeasurement name=\"Anomaly check" 00102 "\" type=\"text/text\">" + myHashMessage + 00103 "</DartMeasurement>"; 00104 qDebug() << myMeasureMessage; 00105 mReport += "<tr><td colspan=3>" + myHashMessage + "</td></tr>"; 00106 if ( myImageHash == myAnomalyHash ) 00107 { 00108 mReport += "<tr><td colspan=3>" 00109 "Anomaly found! " + myFile; 00110 mReport += "</td></tr>"; 00111 return true; 00112 } 00113 } 00114 mReport += "<tr><td colspan=3>" 00115 "No anomaly found! "; 00116 mReport += "</td></tr>"; 00117 return false; 00118 } 00119 00120 bool QgsRenderChecker::runTest( QString theTestName, 00121 unsigned int theMismatchCount ) 00122 { 00123 if ( mExpectedImageFile.isEmpty() ) 00124 { 00125 qDebug( "QgsRenderChecker::runTest failed - Expected Image File not set." ); 00126 mReport = "<table>" 00127 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n" 00128 "<tr><td>Nothing rendered</td>\n<td>Failed because Expected " 00129 "Image File not set.</td></tr></table>\n"; 00130 return false; 00131 } 00132 // 00133 // Load the expected result pixmap 00134 // 00135 QImage myExpectedImage( mExpectedImageFile ); 00136 mMatchTarget = myExpectedImage.width() * myExpectedImage.height(); 00137 // 00138 // Now render our layers onto a pixmap 00139 // 00140 QImage myImage( myExpectedImage.width(), 00141 myExpectedImage.height(), 00142 QImage::Format_RGB32 ); 00143 myImage.setDotsPerMeterX( myExpectedImage.dotsPerMeterX() ); 00144 myImage.setDotsPerMeterY( myExpectedImage.dotsPerMeterY() ); 00145 myImage.fill( qRgb( 152, 219, 249 ) ); 00146 QPainter myPainter( &myImage ); 00147 myPainter.setRenderHint( QPainter::Antialiasing ); 00148 mpMapRenderer->setOutputSize( QSize( 00149 myExpectedImage.width(), 00150 myExpectedImage.height() ), 00151 myExpectedImage.logicalDpiX() ); 00152 QTime myTime; 00153 myTime.start(); 00154 mpMapRenderer->render( &myPainter ); 00155 mElapsedTime = myTime.elapsed(); 00156 myPainter.end(); 00157 // 00158 // Save the pixmap to disk so the user can make a 00159 // visual assessment if needed 00160 // 00161 mRenderedImageFile = QDir::tempPath() + QDir::separator() + 00162 theTestName + "_result.png"; 00163 myImage.save( mRenderedImageFile, "PNG", 100 ); 00164 return compareImages( theTestName, theMismatchCount ); 00165 00166 } 00167 00168 00169 bool QgsRenderChecker::compareImages( QString theTestName, 00170 unsigned int theMismatchCount, 00171 QString theRenderedImageFile ) 00172 { 00173 if ( mExpectedImageFile.isEmpty() ) 00174 { 00175 qDebug( "QgsRenderChecker::runTest failed - Expected Image (control) File not set." ); 00176 mReport = "<table>" 00177 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n" 00178 "<tr><td>Nothing rendered</td>\n<td>Failed because Expected " 00179 "Image File not set.</td></tr></table>\n"; 00180 return false; 00181 } 00182 if ( ! theRenderedImageFile.isEmpty() ) 00183 { 00184 mRenderedImageFile = theRenderedImageFile; 00185 } 00186 if ( mRenderedImageFile.isEmpty() ) 00187 { 00188 qDebug( "QgsRenderChecker::runTest failed - Rendered Image File not set." ); 00189 mReport = "<table>" 00190 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n" 00191 "<tr><td>Nothing rendered</td>\n<td>Failed because Expected " 00192 "Image File not set.</td></tr></table>\n"; 00193 return false; 00194 } 00195 // 00196 // Load /create the images 00197 // 00198 QImage myExpectedImage( mExpectedImageFile ); 00199 QImage myResultImage( mRenderedImageFile ); 00200 QImage myDifferenceImage( myExpectedImage.width(), 00201 myExpectedImage.height(), 00202 QImage::Format_RGB32 ); 00203 QString myDiffImageFile = QDir::tempPath() + QDir::separator() + 00204 QDir::separator() + 00205 theTestName + "_result_diff.png"; 00206 myDifferenceImage.fill( qRgb( 152, 219, 249 ) ); 00207 00208 // 00209 // Set pixel count score and target 00210 // 00211 mMatchTarget = myExpectedImage.width() * myExpectedImage.height(); 00212 unsigned int myPixelCount = myResultImage.width() * myResultImage.height(); 00213 // 00214 // Set the report with the result 00215 // 00216 mReport = "<table>"; 00217 mReport += "<tr><td colspan=2>"; 00218 mReport += "Test image and result image for " + theTestName + "<br>" 00219 "Expected size: " + QString::number( myExpectedImage.width() ).toLocal8Bit() + "w x " + 00220 QString::number( myExpectedImage.width() ).toLocal8Bit() + "h (" + 00221 QString::number( mMatchTarget ).toLocal8Bit() + " pixels)<br>" 00222 "Actual size: " + QString::number( myResultImage.width() ).toLocal8Bit() + "w x " + 00223 QString::number( myResultImage.width() ).toLocal8Bit() + "h (" + 00224 QString::number( myPixelCount ).toLocal8Bit() + " pixels)"; 00225 mReport += "</td></tr>"; 00226 mReport += "<tr><td colspan = 2>\n"; 00227 mReport += "Expected Duration : <= " + QString::number( mElapsedTimeTarget ) + 00228 "ms (0 indicates not specified)<br>"; 00229 mReport += "Actual Duration : " + QString::number( mElapsedTime ) + "ms<br>"; 00230 QString myImagesString = "</td></tr>" 00231 "<tr><td>Test Result:</td><td>Expected Result:</td><td>Difference (all blue is good, any red is bad)</td></tr>\n" 00232 "<tr><td><img src=\"file://" + 00233 mRenderedImageFile + 00234 "\"></td>\n<td><img src=\"file://" + 00235 mExpectedImageFile + 00236 "\"></td><td><img src=\"file://" + 00237 myDiffImageFile + 00238 "\"></td>\n</tr>\n</table>"; 00239 // 00240 // To get the images into CDash 00241 // 00242 QString myDashMessage = "<DartMeasurementFile name=\"Rendered Image " + theTestName + "\"" 00243 " type=\"image/png\">" + mRenderedImageFile + 00244 "</DartMeasurementFile>" 00245 "<DartMeasurementFile name=\"Expected Image " + theTestName + "\" type=\"image/png\">" + 00246 mExpectedImageFile + "</DartMeasurementFile>" 00247 "<DartMeasurementFile name=\"Difference Image " + theTestName + "\" type=\"image/png\">" + 00248 myDiffImageFile + "</DartMeasurementFile>"; 00249 qDebug( ) << myDashMessage; 00250 00251 // 00252 // Put the same info to debug too 00253 // 00254 00255 qDebug( "Expected size: %dw x %dh", myExpectedImage.width(), myExpectedImage.height() ); 00256 qDebug( "Actual size: %dw x %dh", myResultImage.width(), myResultImage.height() ); 00257 00258 if ( mMatchTarget != myPixelCount ) 00259 { 00260 qDebug( "Test image and result image for %s are different - FAILING!", theTestName.toLocal8Bit().constData() ); 00261 mReport += "<tr><td colspan=3>"; 00262 mReport += "<font color=red>Expected image and result image for " + theTestName + " are different dimensions - FAILING!</font>"; 00263 mReport += "</td></tr>"; 00264 mReport += myImagesString; 00265 return false; 00266 } 00267 00268 // 00269 // Now iterate through them counting how many 00270 // dissimilar pixel values there are 00271 // 00272 00273 mMismatchCount = 0; 00274 for ( int x = 0; x < myExpectedImage.width(); ++x ) 00275 { 00276 for ( int y = 0; y < myExpectedImage.height(); ++y ) 00277 { 00278 QRgb myExpectedPixel = myExpectedImage.pixel( x, y ); 00279 QRgb myActualPixel = myResultImage.pixel( x, y ); 00280 if ( myExpectedPixel != myActualPixel ) 00281 { 00282 ++mMismatchCount; 00283 myDifferenceImage.setPixel( x, y, qRgb( 255, 0, 0 ) ); 00284 } 00285 } 00286 } 00287 // 00288 //save the diff image to disk 00289 // 00290 myDifferenceImage.save( myDiffImageFile ); 00291 00292 // 00293 // Send match result to debug 00294 // 00295 qDebug( "%d/%d pixels mismatched", mMismatchCount, mMatchTarget ); 00296 00297 // 00298 // Send match result to report 00299 // 00300 mReport += "<tr><td colspan=3>" + 00301 QString::number( mMismatchCount ) + "/" + 00302 QString::number( mMatchTarget ) + 00303 " pixels mismatched (allowed threshold: " + 00304 QString::number( theMismatchCount ) + ")"; 00305 mReport += "</td></tr>"; 00306 00307 // 00308 // And send it to CDash 00309 // 00310 myDashMessage = "<DartMeasurement name=\"Mismatch Count " 00311 "\" type=\"numeric/integer\">" + 00312 QString::number( mMismatchCount ) + "/" + 00313 QString::number( mMatchTarget ) + 00314 "</DartMeasurement>"; 00315 qDebug( ) << myDashMessage; 00316 00317 bool myAnomalyMatchFlag = isKnownAnomaly( myDiffImageFile ); 00318 00319 if ( myAnomalyMatchFlag ) 00320 { 00321 mReport += "<tr><td colspan=3>" 00322 "Difference image matched a known anomaly - passing test! " 00323 "</td></tr>"; 00324 return true; 00325 } 00326 else 00327 { 00328 QString myMessage = "Difference image did not match any known anomaly."; 00329 mReport += "<tr><td colspan=3>" 00330 "</td></tr>"; 00331 QString myMeasureMessage = "<DartMeasurement name=\"No Anomalies Match" 00332 "\" type=\"text/text\">" + myMessage + 00333 " If you feel the difference image should be considered an anomaly " 00334 "you can do something like this\n" 00335 "cp " + myDiffImageFile + " ../tests/testdata/control_images/" + theTestName + 00336 "/<imagename>.png" 00337 "</DartMeasurement>"; 00338 qDebug() << myMeasureMessage; 00339 } 00340 00341 if ( mMismatchCount <= theMismatchCount ) 00342 { 00343 mReport += "<tr><td colspan = 3>\n"; 00344 mReport += "Test image and result image for " + theTestName + " are matched<br>"; 00345 mReport += "</td></tr>"; 00346 if ( mElapsedTimeTarget != 0 && mElapsedTimeTarget < mElapsedTime ) 00347 { 00348 //test failed because it took too long... 00349 qDebug( "Test failed because render step took too long" ); 00350 mReport += "<tr><td colspan = 3>\n"; 00351 mReport += "<font color=red>Test failed because render step took too long</font>"; 00352 mReport += "</td></tr>"; 00353 mReport += myImagesString; 00354 return false; 00355 } 00356 else 00357 { 00358 mReport += myImagesString; 00359 return true; 00360 } 00361 } 00362 else 00363 { 00364 mReport += "<tr><td colspan = 3>\n"; 00365 mReport += "<font color=red>Test image and result image for " + theTestName + " are mismatched</font><br>"; 00366 mReport += "</td></tr>"; 00367 mReport += myImagesString; 00368 return false; 00369 } 00370 }