25 #include <QCryptographicHash>
33 QString myDataDir( TEST_DATA_DIR );
34 QString myControlImageDir = myDataDir +
"/control_images/" + mControlPathPrefix;
35 return myControlImageDir;
46 if ( !name.isEmpty() )
47 mControlPathSuffix = name +
'/';
49 mControlPathSuffix.clear();
55 myImage.load( imageFile );
56 QByteArray myByteArray;
57 QBuffer myBuffer( &myByteArray );
58 myImage.save( &myBuffer,
"PNG" );
59 QString myImageString = QString::fromUtf8( myByteArray.toBase64().data() );
60 QCryptographicHash myHash( QCryptographicHash::Md5 );
61 myHash.addData( myImageString.toUtf8() );
62 return myHash.result().toHex().constData();
67 mMapSettings = mapSettings;
73 uchar pixDataRGB[] = { 255, 255, 255, 255,
79 QImage img( pixDataRGB, 2, 2, 8, QImage::Format_ARGB32 );
80 QPixmap pix = QPixmap::fromImage( img.scaled( 20, 20 ) );
84 brush.setTexture( pix );
86 p.setRenderHint( QPainter::Antialiasing,
false );
87 p.fillRect( QRect( 0, 0, image->width(), image->height() ), brush );
94 QDir myDirectory = QDir( myControlImageDir );
96 QString myFilename = QStringLiteral(
"*" );
97 myList = myDirectory.entryList( QStringList( myFilename ),
98 QDir::Files | QDir::NoSymLinks );
103 QString myImageHash =
imageToHash( diffImageFile );
106 for (
int i = 0; i < myList.size(); ++i )
108 QString myFile = myList.at( i );
109 mReport +=
"<tr><td colspan=3>"
110 "Checking if " + myFile +
" is a known anomaly.";
111 mReport += QLatin1String(
"</td></tr>" );
113 QString myHashMessage = QStringLiteral(
114 "Checking if anomaly %1 (hash %2)<br>" )
117 myHashMessage += QStringLiteral(
" matches %1 (hash %2)" )
123 mReport +=
"<tr><td colspan=3>" + myHashMessage +
"</td></tr>";
124 if ( myImageHash == myAnomalyHash )
126 mReport +=
"<tr><td colspan=3>"
127 "Anomaly found! " + myFile;
128 mReport += QLatin1String(
"</td></tr>" );
132 mReport +=
"<tr><td colspan=3>"
133 "No anomaly found! ";
134 mReport += QLatin1String(
"</td></tr>" );
140 if ( mBufferDashMessages )
141 mDashMessages << dashMessage;
152 unsigned int mismatchCount )
156 qDebug(
"QgsRenderChecker::runTest failed - Expected Image File not set." );
158 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
159 "<tr><td>Nothing rendered</td>\n<td>Failed because Expected "
160 "Image File not set.</td></tr></table>\n";
167 if ( myExpectedImage.isNull() )
169 qDebug() <<
"QgsRenderChecker::runTest failed - Could not load expected image from " <<
mExpectedImageFile;
171 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
172 "<tr><td>Nothing rendered</td>\n<td>Failed because Expected "
173 "Image File could not be loaded.</td></tr></table>\n";
176 mMatchTarget = myExpectedImage.width() * myExpectedImage.height();
184 QElapsedTimer myTime;
202 myImage.setDotsPerMeterX( myExpectedImage.dotsPerMeterX() );
203 myImage.setDotsPerMeterY( myExpectedImage.dotsPerMeterY() );
206 qDebug() <<
"QgsRenderChecker::runTest failed - Could not save rendered image to " <<
mRenderedImageFile;
208 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
209 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
210 "Image File could not be saved.</td></tr></table>\n";
216 QFile wldFile( QDir::tempPath() +
'/' + testName +
"_result.wld" );
217 if ( wldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
221 QTextStream stream( &wldFile );
222 stream << QStringLiteral(
"%1\r\n0 \r\n0 \r\n%2\r\n%3\r\n%4\r\n" )
234 unsigned int mismatchCount,
235 const QString &renderedImageFile )
239 qDebug(
"QgsRenderChecker::runTest failed - Expected Image (control) File not set." );
241 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
242 "<tr><td>Nothing rendered</td>\n<td>Failed because Expected "
243 "Image File not set.</td></tr></table>\n";
246 if ( ! renderedImageFile.isEmpty() )
256 qDebug(
"QgsRenderChecker::runTest failed - Rendered Image File not set." );
258 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
259 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
260 "Image File not set.</td></tr></table>\n";
269 if ( myResultImage.isNull() )
271 qDebug() <<
"QgsRenderChecker::runTest failed - Could not load rendered image from " <<
mRenderedImageFile;
273 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
274 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
275 "Image File could not be loaded.</td></tr></table>\n";
278 QImage myDifferenceImage( myExpectedImage.width(),
279 myExpectedImage.height(),
280 QImage::Format_RGB32 );
281 QString myDiffImageFile = QDir::tempPath() +
'/' + testName +
"_result_diff.png";
282 myDifferenceImage.fill( qRgb( 152, 219, 249 ) );
286 maskImagePath.chop( 4 );
287 maskImagePath += QLatin1String(
"_mask.png" );
288 const QImage maskImage( maskImagePath );
289 const bool hasMask = !maskImage.isNull();
294 mMatchTarget = myExpectedImage.width() * myExpectedImage.height();
295 unsigned int myPixelCount = myResultImage.width() * myResultImage.height();
299 mReport = QStringLiteral(
"<script src=\"file://%1/../renderchecker.js\"></script>\n" ).arg( TEST_DATA_DIR );
300 mReport += QLatin1String(
"<table>" );
301 mReport += QLatin1String(
"<tr><td colspan=2>" );
302 mReport += QString(
"<tr><td colspan=2>"
303 "Test image and result image for %1<br>"
304 "Expected size: %2 w x %3 h (%4 pixels)<br>"
305 "Actual size: %5 w x %6 h (%7 pixels)"
308 .arg( myExpectedImage.width() ).arg( myExpectedImage.height() ).arg(
mMatchTarget )
309 .arg( myResultImage.width() ).arg( myResultImage.height() ).arg( myPixelCount );
310 mReport += QString(
"<tr><td colspan=2>\n"
311 "Expected Duration : <= %1 (0 indicates not specified)<br>"
312 "Actual Duration : %2 ms<br></td></tr>" )
313 .arg( mElapsedTimeTarget )
319 if ( ! myExpectedImage.isNull() )
321 imgWidth = std::min( myExpectedImage.width(), imgWidth );
322 imgHeight = myExpectedImage.height() * imgWidth / myExpectedImage.width();
325 QString myImagesString = QString(
327 "<td colspan=2>Compare actual and expected result</td>"
328 "<td>Difference (all blue is good, any red is bad)</td>"
330 "<td colspan=2 id=\"td-%1-%7\"></td>\n"
331 "<td align=center><img width=%5 height=%6 src=\"file://%2\"></td>\n"
334 "<script>\naddComparison(\"td-%1-%7\",\"file://%3\",\"file://%4\",%5,%6);\n</script>\n" )
339 .arg( imgWidth ).arg( imgHeight )
340 .arg( QUuid::createUuid().toString().mid( 1, 6 ) );
343 if ( !mControlPathPrefix.isNull() )
345 prefix = QStringLiteral(
" (prefix %1)" ).arg( mControlPathPrefix );
357 if ( myExpectedImage.width() != myResultImage.width() || myExpectedImage.height() != myResultImage.height() )
359 qDebug(
"Expected size: %dw x %dh", myExpectedImage.width(), myExpectedImage.height() );
360 qDebug(
"Actual size: %dw x %dh", myResultImage.width(), myResultImage.height() );
362 qDebug(
"Mask size: %dw x %dh", maskImage.width(), maskImage.height() );
367 qDebug(
"Test image and result image for %s are different dimensions", testName.toLocal8Bit().constData() );
369 if ( std::abs( myExpectedImage.width() - myResultImage.width() ) > mMaxSizeDifferenceX ||
370 std::abs( myExpectedImage.height() - myResultImage.height() ) > mMaxSizeDifferenceY )
372 mReport += QLatin1String(
"<tr><td colspan=3>" );
373 mReport +=
"<font color=red>Expected image and result image for " + testName +
" are different dimensions - FAILING!</font>";
374 mReport += QLatin1String(
"</td></tr>" );
380 mReport += QLatin1String(
"<tr><td colspan=3>" );
381 mReport +=
"Expected image and result image for " + testName +
" are different dimensions, but within tolerance";
382 mReport += QLatin1String(
"</td></tr>" );
386 if ( myExpectedImage.format() == QImage::Format_Indexed8 )
388 if ( myResultImage.format() != QImage::Format_Indexed8 )
390 qDebug() <<
"Expected image and result image for " << testName <<
" have different formats (8bit format is expected) - FAILING!";
392 mReport += QLatin1String(
"<tr><td colspan=3>" );
393 mReport +=
"<font color=red>Expected image and result image for " + testName +
" have different formats (8bit format is expected) - FAILING!</font>";
394 mReport += QLatin1String(
"</td></tr>" );
402 myResultImage = myResultImage.convertToFormat( QImage::Format_ARGB32 );
403 myExpectedImage = myExpectedImage.convertToFormat( QImage::Format_ARGB32 );
412 int maxHeight = std::min( myExpectedImage.height(), myResultImage.height() );
413 int maxWidth = std::min( myExpectedImage.width(), myResultImage.width() );
416 int colorTolerance =
static_cast< int >( mColorTolerance );
417 for (
int y = 0; y < maxHeight; ++y )
419 const QRgb *expectedScanline =
reinterpret_cast< const QRgb *
>( myExpectedImage.constScanLine( y ) );
420 const QRgb *resultScanline =
reinterpret_cast< const QRgb *
>( myResultImage.constScanLine( y ) );
421 const QRgb *maskScanline = ( hasMask && maskImage.height() > y ) ?
reinterpret_cast< const QRgb *
>( maskImage.constScanLine( y ) ) :
nullptr;
422 QRgb *diffScanline =
reinterpret_cast< QRgb *
>( myDifferenceImage.scanLine( y ) );
424 for (
int x = 0; x < maxWidth; ++x )
426 int maskTolerance = ( maskScanline && maskImage.width() > x ) ? qRed( maskScanline[ x ] ) : 0;
427 int pixelTolerance = std::max( colorTolerance, maskTolerance );
428 if ( pixelTolerance == 255 )
434 QRgb myExpectedPixel = expectedScanline[x];
435 QRgb myActualPixel = resultScanline[x];
436 if ( pixelTolerance == 0 )
438 if ( myExpectedPixel != myActualPixel )
441 diffScanline[ x ] = qRgb( 255, 0, 0 );
446 if ( std::abs( qRed( myExpectedPixel ) - qRed( myActualPixel ) ) > pixelTolerance ||
447 std::abs( qGreen( myExpectedPixel ) - qGreen( myActualPixel ) ) > pixelTolerance ||
448 std::abs( qBlue( myExpectedPixel ) - qBlue( myActualPixel ) ) > pixelTolerance ||
449 std::abs( qAlpha( myExpectedPixel ) - qAlpha( myActualPixel ) ) > pixelTolerance )
452 diffScanline[ x ] = qRgb( 255, 0, 0 );
460 myDifferenceImage.save( myDiffImageFile );
474 mReport += QStringLiteral(
"<tr><td colspan=3>%1/%2 pixels mismatched (allowed threshold: %3, allowed color component tolerance: %4)</td></tr>" )
484 mReport += QLatin1String(
"<tr><td colspan = 3>\n" );
485 mReport +=
"Test image and result image for " + testName +
" are matched<br>";
486 mReport += QLatin1String(
"</td></tr>" );
487 if ( mElapsedTimeTarget != 0 && mElapsedTimeTarget <
mElapsedTime )
490 qDebug(
"Test failed because render step took too long" );
491 mReport += QLatin1String(
"<tr><td colspan = 3>\n" );
492 mReport += QLatin1String(
"<font color=red>Test failed because render step took too long</font>" );
493 mReport += QLatin1String(
"</td></tr>" );
505 if ( myAnomalyMatchFlag )
507 mReport +=
"<tr><td colspan=3>"
508 "Difference image matched a known anomaly - passing test! "
513 mReport += QLatin1String(
"<tr><td colspan=3></td></tr>" );
514 emitDashMessage( QStringLiteral(
"Image mismatch" ),
QgsDartMeasurement::Text,
"Difference image did not match any known anomaly or mask."
515 " If you feel the difference image should be considered an anomaly "
516 "you can do something like this\n"
518 "/\nIf it should be included in the mask run\n"
521 mReport += QLatin1String(
"<tr><td colspan = 3>\n" );
522 mReport +=
"<font color=red>Test image and result image for " + testName +
" are mismatched</font><br>";
523 mReport += QLatin1String(
"</td></tr>" );