/*************************************************************************** qgsrenderchecker.cpp -------------------------------------- Date : 18 Jan 2008 Copyright : (C) 2008 by Tim Sutton Email : tim @ linfiniti.com *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "qgsrenderchecker.h" #include #include #include #include #include #include #include #include #include QgsRenderChecker::QgsRenderChecker( ) : mReport( "" ), mExpectedImageFile( "" ), mRenderedImageFile( "" ), mMismatchCount( 0 ), mMatchTarget( 0 ), mElapsedTime( 0 ), mElapsedTimeTarget( 0 ), mpMapRenderer( NULL ) { } QString QgsRenderChecker::controlImagePath() const { QString myDataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt QString myControlImageDir = myDataDir + QDir::separator() + "control_images" + QDir::separator() ; return myControlImageDir; } void QgsRenderChecker::setControlName(const QString theName) { mControlName = theName; mExpectedImageFile = controlImagePath() + theName + QDir::separator() + theName + ".png"; } QString QgsRenderChecker::imageToHash( QString theImageFile ) { QImage myImage; myImage.load( theImageFile ); QByteArray myByteArray; QBuffer myBuffer( &myByteArray ); myImage.save(&myBuffer, "PNG"); QString myImageString = QString::fromUtf8( myByteArray.toBase64().data() ); QCryptographicHash myHash( QCryptographicHash::Md5 ); myHash.addData(myImageString.toUtf8()); return myHash.result().toHex().constData(); } bool QgsRenderChecker::isKnownAnomaly( QString theDiffImageFile ) { QString myControlImageDir = controlImagePath() + mControlName + QDir::separator(); QDir myDirectory = QDir(myControlImageDir); QStringList myList; QString myFilename = "*"; myList = myDirectory.entryList(QStringList(myFilename), QDir::Files | QDir::NoSymLinks); //remove the control file from teh list as the anomalies are //all files except the control file myList.removeAt(myList.indexOf(mExpectedImageFile)); QString myImageHash = imageToHash( theDiffImageFile ); for (int i = 0; i < myList.size(); ++i) { QString myFile = myList.at(i); mReport += "" "Checking if " + myFile + " is a known anomaly."; mReport += ""; QString myAnomalyHash = imageToHash( controlImagePath() + mControlName + QDir::separator() + myFile ); QString myHashMessage = QString( "Checking if anomaly %1 (hash %2)") .arg( myFile ) .arg( myAnomalyHash ); myHashMessage += QString( " matches %1 (hash %2)" ) .arg( theDiffImageFile ) .arg( myImageHash ); //foo CDash QString myMeasureMessage = "" + myHashMessage + ""; qDebug() << myMeasureMessage; mReport += "" + myHashMessage + ""; if ( myImageHash == myAnomalyHash ) { mReport += "" "Anomaly found! " + myFile; mReport += ""; return true; } } mReport += "" "No anomaly found! "; mReport += ""; return false; } bool QgsRenderChecker::runTest( QString theTestName, unsigned int theMismatchCount ) { if ( mExpectedImageFile.isEmpty() ) { qDebug( "QgsRenderChecker::runTest failed - Expected Image File not set." ); mReport = "" "\n" "\n
Test Result:Expected Result:
Nothing renderedFailed because Expected " "Image File not set.
\n"; return false; } // // Load the expected result pixmap // QImage myExpectedImage( mExpectedImageFile ); mMatchTarget = myExpectedImage.width() * myExpectedImage.height(); // // Now render our layers onto a pixmap // QImage myImage( myExpectedImage.width(), myExpectedImage.height(), QImage::Format_RGB32 ); myImage.fill( qRgb( 152, 219, 249 ) ); QPainter myPainter( &myImage ); myPainter.setRenderHint( QPainter::Antialiasing ); mpMapRenderer->setOutputSize( QSize( myExpectedImage.width(), myExpectedImage.height() ), myExpectedImage.logicalDpiX()); QTime myTime; myTime.start(); mpMapRenderer->render( &myPainter ); mElapsedTime = myTime.elapsed(); myPainter.end(); // // Save the pixmap to disk so the user can make a // visual assessment if needed // mRenderedImageFile = QDir::tempPath() + QDir::separator() + theTestName + "_result.png"; myImage.save( mRenderedImageFile, "PNG", 100 ); return compareImages( theTestName, theMismatchCount ); } bool QgsRenderChecker::compareImages( QString theTestName, unsigned int theMismatchCount ) { if ( mExpectedImageFile.isEmpty() ) { qDebug( "QgsRenderChecker::runTest failed - Expected Image (control) File not set." ); mReport = "" "\n" "\n
Test Result:Expected Result:
Nothing renderedFailed because Expected " "Image File not set.
\n"; return false; } if ( mRenderedImageFile.isEmpty() ) { qDebug( "QgsRenderChecker::runTest failed - Rendered Image File not set." ); mReport = "" "\n" "\n
Test Result:Expected Result:
Nothing renderedFailed because Expected " "Image File not set.
\n"; return false; } // // Load /create the images // QImage myExpectedImage( mExpectedImageFile ); QImage myResultImage( mRenderedImageFile ); QImage myDifferenceImage( myExpectedImage.width(), myExpectedImage.height(), QImage::Format_RGB32 ); QString myDiffImageFile = QDir::tempPath() + QDir::separator() + QDir::separator() + theTestName + "_result_diff.png"; myDifferenceImage.fill( qRgb( 152, 219, 249 ) ); // // Set pixel count score and target // mMatchTarget = myExpectedImage.width() * myExpectedImage.height(); unsigned int myPixelCount = myResultImage.width() * myResultImage.height(); // // Set the report with the result // mReport = ""; mReport += ""; mReport += "" "\n" "\n\n\n
"; mReport += "Test image and result image for " + theTestName + "
" "Expected size: " + QString::number( myExpectedImage.width() ).toLocal8Bit() + "w x " + QString::number( myExpectedImage.width() ).toLocal8Bit() + "h (" + QString::number( mMatchTarget ).toLocal8Bit() + " pixels)
" "Actual size: " + QString::number( myResultImage.width() ).toLocal8Bit() + "w x " + QString::number( myResultImage.width() ).toLocal8Bit() + "h (" + QString::number( myPixelCount ).toLocal8Bit() + " pixels)"; mReport += "
\n"; mReport += "Expected Duration : <= " + QString::number( mElapsedTimeTarget ) + "ms (0 indicates not specified)
"; mReport += "Actual Duration : " + QString::number( mElapsedTime ) + "ms
"; QString myImagesString = "
Test Result:Expected Result:Difference (all blue is good, any red is bad)
"; // // To get the images into CDash // QString myDashMessage = "" + mRenderedImageFile + "" "" + mExpectedImageFile + "" "" + myDiffImageFile + ""; qDebug( ) << myDashMessage; // // Put the same info to debug too // qDebug( "Expected size: %dw x %dh", myExpectedImage.width(), myExpectedImage.height() ); qDebug( "Actual size: %dw x %dh", myResultImage.width(), myResultImage.height() ); if ( mMatchTarget != myPixelCount ) { qDebug( "Test image and result image for %s are different - FAILING!", theTestName.toLocal8Bit().constData() ); mReport += ""; mReport += "Expected image and result image for " + theTestName + " are different dimensions - FAILING!"; mReport += ""; mReport += myImagesString; return false; } // // Now iterate through them counting how many // dissimilar pixel values there are // mMismatchCount = 0; for ( int x = 0; x < myExpectedImage.width(); ++x ) { for ( int y = 0; y < myExpectedImage.height(); ++y ) { QRgb myExpectedPixel = myExpectedImage.pixel( x, y ); QRgb myActualPixel = myResultImage.pixel( x, y ); if ( myExpectedPixel != myActualPixel ) { ++mMismatchCount; myDifferenceImage.setPixel( x, y, qRgb( 255, 0, 0 ) ); } } } // //save the diff image to disk // myDifferenceImage.save( myDiffImageFile ); // // Send match result to debug // qDebug( "%d/%d pixels mismatched", mMismatchCount, mMatchTarget ); // // Send match result to report // mReport += "" + QString::number( mMismatchCount ) + "/" + QString::number( mMatchTarget ) + " pixels mismatched (allowed threshold: " + QString::number( theMismatchCount ) + ")"; mReport += ""; // // And send it to CDash // myDashMessage = "" + QString::number( mMismatchCount ) + "/" + QString::number( mMatchTarget ) + ""; qDebug( ) << myDashMessage; bool myAnomalyMatchFlag = isKnownAnomaly( myDiffImageFile ); if ( myAnomalyMatchFlag ) { mReport += "" "Difference image matched a known anomaly - passing test! " ""; return true; } else { mReport += "" "Difference image did not match any known anomaly." ""; } if ( mMismatchCount <= theMismatchCount) { mReport += "\n"; mReport += "Test image and result image for " + theTestName + " are matched
"; mReport += ""; if ( mElapsedTimeTarget != 0 && mElapsedTimeTarget < mElapsedTime ) { //test failed because it took too long... qDebug( "Test failed because render step took too long" ); mReport += "\n"; mReport += "Test failed because render step took too long"; mReport += ""; mReport += myImagesString; return false; } else { mReport += myImagesString; return true; } } else { mReport += "\n"; mReport += "Test image and result image for " + theTestName + " are mismatched
"; mReport += ""; mReport += myImagesString; return false; } }