350bool QgsRenderChecker::compareImages(
const QString &testName,
const QString &referenceImageFile,
const QString &renderedImageFile,
unsigned int mismatchCount, QgsRenderChecker::Flags flags )
353 if ( ! renderedImageFile.isEmpty() )
363 qDebug(
"QgsRenderChecker::runTest failed - Rendered Image File not set." );
365 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
366 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
367 "Image File not set.</td></tr></table>\n";
368 performPostTestActions( flags );
375 QImage expectedImage( referenceImageFile );
376 if ( expectedImage.isNull() )
378 qDebug() <<
"QgsRenderChecker::runTest failed - Could not load control image from " << referenceImageFile;
380 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
381 "<tr><td>Nothing rendered</td>\n<td>Failed because control "
382 "image file could not be loaded.</td></tr></table>\n";
383 performPostTestActions( flags );
388 if ( myResultImage.isNull() )
390 qDebug() <<
"QgsRenderChecker::runTest failed - Could not load rendered image from " <<
mRenderedImageFile;
392 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
393 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
394 "Image File could not be loaded.</td></tr></table>\n";
395 performPostTestActions( flags );
398 QImage myDifferenceImage( expectedImage.width(),
399 expectedImage.height(),
400 QImage::Format_RGB32 );
401 mDiffImageFile = QDir::tempPath() +
'/' + testName +
"_result_diff.png";
402 myDifferenceImage.fill( qRgb( 152, 219, 249 ) );
405 QString maskImagePath = referenceImageFile;
406 maskImagePath.chop( 4 );
407 maskImagePath += QLatin1String(
"_mask.png" );
408 const QImage maskImage( maskImagePath );
409 const bool hasMask = !maskImage.isNull();
414 mMatchTarget = expectedImage.width() * expectedImage.height();
415 const unsigned int myPixelCount = myResultImage.width() * myResultImage.height();
419 mReport = QStringLiteral(
"<script src=\"file://%1/../renderchecker.js\"></script>\n" ).arg( TEST_DATA_DIR );
420 mReport += QLatin1String(
"<table>" );
421 mReport += QLatin1String(
"<tr><td colspan=2>" );
422 mReport += QString(
"<tr><td colspan=2>"
423 "Test image and result image for %1<br>"
424 "Expected size: %2 w x %3 h (%4 pixels)<br>"
425 "Actual size: %5 w x %6 h (%7 pixels)"
428 .arg( expectedImage.width() ).arg( expectedImage.height() ).arg(
mMatchTarget )
429 .arg( myResultImage.width() ).arg( myResultImage.height() ).arg( myPixelCount );
430 mReport += QString(
"<tr><td colspan=2>\n"
431 "Expected Duration : <= %1 (0 indicates not specified)<br>"
432 "Actual Duration : %2 ms<br></td></tr>" )
433 .arg( mElapsedTimeTarget )
439 if ( ! expectedImage.isNull() )
441 imgWidth = std::min( expectedImage.width(), imgWidth );
442 imgHeight = expectedImage.height() * imgWidth / expectedImage.width();
446 const QString diffImageFileName = QFileInfo( mDiffImageFile ).fileName();
447 const QString myImagesString = QString(
449 "<td colspan=2>Compare actual and expected result</td>"
450 "<td>Difference (all blue is good, any red is bad)</td>"
452 "<td colspan=2 id=\"td-%1-%7\"></td>\n"
453 "<td align=center><img width=%5 height=%6 src=\"%2\"></td>\n"
456 "<script>\naddComparison(\"td-%1-%7\",\"%3\",\"file://%4\",%5,%6);\n</script>\n"
457 "<p>If the new image looks good, create or update a test mask with<br>"
458 "<code>scripts/generate_test_mask_image.py \"%8\" \"%9\"</code>" )
461 renderedImageFileName,
463 .arg( imgWidth ).arg( imgHeight )
464 .arg( QUuid::createUuid().toString().mid( 1, 6 ),
470 if ( !mControlPathPrefix.isNull() )
472 prefix = QStringLiteral(
" (prefix %1)" ).arg( mControlPathPrefix );
479 if ( expectedImage.width() != myResultImage.width() || expectedImage.height() != myResultImage.height() )
481 qDebug(
"Expected size: %dw x %dh", expectedImage.width(), expectedImage.height() );
482 qDebug(
"Actual size: %dw x %dh", myResultImage.width(), myResultImage.height() );
484 qDebug(
"Mask size: %dw x %dh", maskImage.width(), maskImage.height() );
489 qDebug(
"Test image and result image for %s are different dimensions", testName.toLocal8Bit().constData() );
491 if ( std::abs( expectedImage.width() - myResultImage.width() ) > mMaxSizeDifferenceX ||
492 std::abs( expectedImage.height() - myResultImage.height() ) > mMaxSizeDifferenceY )
497 mReport += QLatin1String(
"<tr><td colspan=3>" );
498 mReport +=
"<font color=red>Expected image and result image for " + testName +
" are different dimensions - FAILING!</font>";
499 mReport += QLatin1String(
"</td></tr>" );
501 const QString diffSizeImagesString = QString(
503 "<td colspan=3>Compare actual and expected result</td>"
505 "<td align=center><img src=\"%1\"></td>\n"
506 "<td align=center><img width=%3 height=%4 src=\"%2\"></td>\n"
510 renderedImageFileName,
512 .arg( imgWidth ).arg( imgHeight );
514 mReport += diffSizeImagesString;
515 performPostTestActions( flags );
520 mReport += QLatin1String(
"<tr><td colspan=3>" );
521 mReport +=
"Expected image and result image for " + testName +
" are different dimensions, but within tolerance";
522 mReport += QLatin1String(
"</td></tr>" );
526 if ( expectedImage.format() == QImage::Format_Indexed8 )
528 if ( myResultImage.format() != QImage::Format_Indexed8 )
533 qDebug() <<
"Expected image and result image for " << testName <<
" have different formats (8bit format is expected) - FAILING!";
535 mReport += QLatin1String(
"<tr><td colspan=3>" );
536 mReport +=
"<font color=red>Expected image and result image for " + testName +
" have different formats (8bit format is expected) - FAILING!</font>";
537 mReport += QLatin1String(
"</td></tr>" );
539 performPostTestActions( flags );
546 myResultImage = myResultImage.convertToFormat( QImage::Format_ARGB32 );
547 expectedImage = expectedImage.convertToFormat( QImage::Format_ARGB32 );
556 const int maxHeight = std::min( expectedImage.height(), myResultImage.height() );
557 const int maxWidth = std::min( expectedImage.width(), myResultImage.width() );
560 const int colorTolerance =
static_cast< int >( mColorTolerance );
561 for (
int y = 0; y < maxHeight; ++y )
563 const QRgb *expectedScanline =
reinterpret_cast< const QRgb *
>( expectedImage.constScanLine( y ) );
564 const QRgb *resultScanline =
reinterpret_cast< const QRgb *
>( myResultImage.constScanLine( y ) );
565 const QRgb *maskScanline = ( hasMask && maskImage.height() > y ) ?
reinterpret_cast< const QRgb *
>( maskImage.constScanLine( y ) ) :
nullptr;
566 QRgb *diffScanline =
reinterpret_cast< QRgb *
>( myDifferenceImage.scanLine( y ) );
568 for (
int x = 0; x < maxWidth; ++x )
570 const int maskTolerance = ( maskScanline && maskImage.width() > x ) ? qRed( maskScanline[ x ] ) : 0;
571 const int pixelTolerance = std::max( colorTolerance, maskTolerance );
572 if ( pixelTolerance == 255 )
578 const QRgb myExpectedPixel = expectedScanline[x];
579 const QRgb myActualPixel = resultScanline[x];
580 if ( pixelTolerance == 0 )
582 if ( myExpectedPixel != myActualPixel )
585 diffScanline[ x ] = qRgb( 255, 0, 0 );
590 if ( std::abs( qRed( myExpectedPixel ) - qRed( myActualPixel ) ) > pixelTolerance ||
591 std::abs( qGreen( myExpectedPixel ) - qGreen( myActualPixel ) ) > pixelTolerance ||
592 std::abs( qBlue( myExpectedPixel ) - qBlue( myActualPixel ) ) > pixelTolerance ||
593 std::abs( qAlpha( myExpectedPixel ) - qAlpha( myActualPixel ) ) > pixelTolerance )
596 diffScanline[ x ] = qRgb( 255, 0, 0 );
615 myDifferenceImage.save( mDiffImageFile );
622 mReport += QStringLiteral(
"<tr><td colspan=3>%1/%2 pixels mismatched (allowed threshold: %3, allowed color component tolerance: %4)</td></tr>" )
628 if ( mMismatchCount > 0 )
635 mReport += QLatin1String(
"<tr><td colspan = 3>\n" );
636 mReport +=
"Test image and result image for " + testName +
" are matched<br>";
637 mReport += QLatin1String(
"</td></tr>" );
638 if ( mElapsedTimeTarget != 0 && mElapsedTimeTarget <
mElapsedTime )
641 qDebug(
"Test failed because render step took too long" );
642 mReport += QLatin1String(
"<tr><td colspan = 3>\n" );
643 mReport += QLatin1String(
"<font color=red>Test failed because render step took too long</font>" );
644 mReport += QLatin1String(
"</td></tr>" );
646 performPostTestActions( flags );
653 performPostTestActions( flags );
658 mReport += QLatin1String(
"<tr><td colspan=3></td></tr>" );
659 emitDashMessage( QStringLiteral(
"Image mismatch" ),
QgsDartMeasurement::Text,
"Difference image did not match any known anomaly or mask."
660 " If you feel the difference image should be considered an anomaly "
661 "you can do something like this\n"
663 "/\nIf it should be included in the mask run\n"
664 "scripts/generate_test_mask_image.py '" + referenceImageFile +
"' '" +
mRenderedImageFile +
"'\n" );
666 mReport += QLatin1String(
"<tr><td colspan = 3>\n" );
667 mReport +=
"<font color=red>Test image and result image for " + testName +
" are mismatched</font><br>";
668 mReport += QLatin1String(
"</td></tr>" );
671 performPostTestActions( flags );