25 #include <QCryptographicHash>
34 mRenderedImageFile(
"" ),
35 mExpectedImageFile(
"" ),
38 mElapsedTimeTarget( 0 ),
39 mBufferDashMessages( false )
45 QString myDataDir( TEST_DATA_DIR );
46 QString myControlImageDir = myDataDir + QDir::separator() +
"control_images" +
47 QDir::separator() + mControlPathPrefix;
48 return myControlImageDir;
53 mControlName = theName;
61 myImage.load( theImageFile );
62 QByteArray myByteArray;
63 QBuffer myBuffer( &myByteArray );
64 myImage.save( &myBuffer,
"PNG" );
65 QString myImageString = QString::fromUtf8( myByteArray.toBase64().data() );
66 QCryptographicHash myHash( QCryptographicHash::Md5 );
67 myHash.addData( myImageString.toUtf8() );
68 return myHash.result().toHex().constData();
78 mMapSettings = mapSettings;
84 uchar pixDataRGB[] = { 255, 255, 255, 255,
90 QImage img( pixDataRGB, 2, 2, 8, QImage::Format_ARGB32 );
91 QPixmap pix = QPixmap::fromImage( img.scaled( 20, 20 ) );
95 brush.setTexture( pix );
97 p.setRenderHint( QPainter::Antialiasing,
false );
98 p.fillRect( QRect( 0, 0, image->width(), image->height() ), brush );
106 QDir myDirectory = QDir( myControlImageDir );
108 QString myFilename =
"*";
109 myList = myDirectory.entryList( QStringList( myFilename ),
110 QDir::Files | QDir::NoSymLinks );
115 QString myImageHash =
imageToHash( theDiffImageFile );
118 for (
int i = 0; i < myList.size(); ++i )
120 QString myFile = myList.at( i );
121 mReport +=
"<tr><td colspan=3>"
122 "Checking if " + myFile +
" is a known anomaly.";
125 + QDir::separator() + myFile );
126 QString myHashMessage = QString(
127 "Checking if anomaly %1 (hash %2)<br>" )
129 .arg( myAnomalyHash );
130 myHashMessage += QString(
" matches %1 (hash %2)" )
131 .arg( theDiffImageFile )
136 mReport +=
"<tr><td colspan=3>" + myHashMessage +
"</td></tr>";
137 if ( myImageHash == myAnomalyHash )
139 mReport +=
"<tr><td colspan=3>"
140 "Anomaly found! " + myFile;
145 mReport +=
"<tr><td colspan=3>"
146 "No anomaly found! ";
153 if ( mBufferDashMessages )
154 mDashMessages << dashMessage;
165 unsigned int theMismatchCount )
169 qDebug(
"QgsRenderChecker::runTest failed - Expected Image File not set." );
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 not set.</td></tr></table>\n";
180 if ( myExpectedImage.isNull() )
182 qDebug() <<
"QgsRenderChecker::runTest failed - Could not load expected image from " <<
mExpectedImageFile;
184 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
185 "<tr><td>Nothing rendered</td>\n<td>Failed because Expected "
186 "Image File could not be loaded.</td></tr></table>\n";
189 mMatchTarget = myExpectedImage.width() * myExpectedImage.height();
195 mMapSettings.
setOutputSize( QSize( myExpectedImage.width(), myExpectedImage.height() ) );
213 theTestName +
"_result.png";
215 myImage.setDotsPerMeterX( myExpectedImage.dotsPerMeterX() );
216 myImage.setDotsPerMeterY( myExpectedImage.dotsPerMeterY() );
219 qDebug() <<
"QgsRenderChecker::runTest failed - Could not save rendered image to " <<
mRenderedImageFile;
221 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
222 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
223 "Image File could not be saved.</td></tr></table>\n";
229 QFile wldFile( QDir::tempPath() + QDir::separator() + theTestName +
"_result.wld" );
230 if ( wldFile.open( QIODevice::WriteOnly ) )
234 QTextStream stream( &wldFile );
235 stream << QString(
"%1\r\n0 \r\n0 \r\n%2\r\n%3\r\n%4\r\n" )
247 unsigned int theMismatchCount,
248 QString theRenderedImageFile )
252 qDebug(
"QgsRenderChecker::runTest failed - Expected Image (control) File not set." );
254 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
255 "<tr><td>Nothing rendered</td>\n<td>Failed because Expected "
256 "Image File not set.</td></tr></table>\n";
259 if ( ! theRenderedImageFile.isEmpty() )
265 qDebug(
"QgsRenderChecker::runTest failed - Rendered Image File not set." );
267 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
268 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
269 "Image File not set.</td></tr></table>\n";
277 if ( myResultImage.isNull() )
279 qDebug() <<
"QgsRenderChecker::runTest failed - Could not load rendered image from " <<
mRenderedImageFile;
281 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
282 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
283 "Image File could not be loaded.</td></tr></table>\n";
286 QImage myDifferenceImage( myExpectedImage.width(),
287 myExpectedImage.height(),
288 QImage::Format_RGB32 );
289 QString myDiffImageFile = QDir::tempPath() +
291 theTestName +
"_result_diff.png";
292 myDifferenceImage.fill( qRgb( 152, 219, 249 ) );
296 maskImagePath.chop( 4 );
297 maskImagePath +=
"_mask.png";
298 QImage* maskImage =
new QImage( maskImagePath );
299 bool hasMask = !maskImage->isNull();
302 qDebug(
"QgsRenderChecker using mask image" );
308 mMatchTarget = myExpectedImage.width() * myExpectedImage.height();
309 unsigned int myPixelCount = myResultImage.width() * myResultImage.height();
314 mReport +=
"<tr><td colspan=2>";
315 mReport +=
"Test image and result image for " + theTestName +
"<br>"
316 "Expected size: " + QString::number( myExpectedImage.width() ).toLocal8Bit() +
"w x " +
317 QString::number( myExpectedImage.height() ).toLocal8Bit() +
"h (" +
318 QString::number(
mMatchTarget ).toLocal8Bit() +
" pixels)<br>"
319 "Actual size: " + QString::number( myResultImage.width() ).toLocal8Bit() +
"w x " +
320 QString::number( myResultImage.height() ).toLocal8Bit() +
"h (" +
321 QString::number( myPixelCount ).toLocal8Bit() +
" pixels)";
323 mReport +=
"<tr><td colspan = 2>\n";
324 mReport +=
"Expected Duration : <= " + QString::number( mElapsedTimeTarget ) +
325 "ms (0 indicates not specified)<br>";
331 if ( ! myExpectedImage.isNull() )
333 imgWidth = qMin( myExpectedImage.width(), imgWidth );
334 imgHeight = myExpectedImage.height() * imgWidth / myExpectedImage.width();
336 QString myImagesString =
"</td></tr>"
337 "<tr><td>Test Result:</td><td>Expected Result:</td><td>Difference (all blue is good, any red is bad)</td></tr>\n"
338 "<tr><td><img width=" + QString::number( imgWidth ) +
339 " height=" + QString::number( imgHeight ) +
342 "\"></td>\n<td><img width=" + QString::number( imgWidth ) +
343 " height=" + QString::number( imgHeight ) +
346 "\"></td>\n<td><img width=" + QString::number( imgWidth ) +
347 " height=" + QString::number( imgHeight ) +
350 "\"></td>\n</tr>\n</table>";
353 if ( !mControlPathPrefix.isNull() )
355 prefix = QString(
" (prefix %1)" ).arg( mControlPathPrefix );
368 qDebug(
"Expected size: %dw x %dh", myExpectedImage.width(), myExpectedImage.height() );
369 qDebug(
"Actual size: %dw x %dh", myResultImage.width(), myResultImage.height() );
373 qDebug(
"Test image and result image for %s are different - FAILING!", theTestName.toLocal8Bit().constData() );
374 mReport +=
"<tr><td colspan=3>";
375 mReport +=
"<font color=red>Expected image and result image for " + theTestName +
" are different dimensions - FAILING!</font>";
376 mReport +=
"</td></tr>";
377 mReport += myImagesString;
388 int colorTolerance = ( int ) mColorTolerance;
389 for (
int y = 0; y < myExpectedImage.height(); ++y )
391 const QRgb* expectedScanline = (
const QRgb* )myExpectedImage.constScanLine( y );
392 const QRgb* resultScanline = (
const QRgb* )myResultImage.constScanLine( y );
393 const QRgb* maskScanline = hasMask ? (
const QRgb* )maskImage->constScanLine( y ) : 0;
394 QRgb* diffScanline = ( QRgb* )myDifferenceImage.scanLine( y );
396 for (
int x = 0; x < myExpectedImage.width(); ++x )
398 int maskTolerance = hasMask ? qRed( maskScanline[ x ] ) : 0;
399 int pixelTolerance = qMax( colorTolerance, maskTolerance );
400 if ( pixelTolerance == 255 )
406 QRgb myExpectedPixel = expectedScanline[x];
407 QRgb myActualPixel = resultScanline[x];
408 if ( pixelTolerance == 0 )
410 if ( myExpectedPixel != myActualPixel )
413 diffScanline[ x ] = qRgb( 255, 0, 0 );
418 if ( qAbs( qRed( myExpectedPixel ) - qRed( myActualPixel ) ) > pixelTolerance ||
419 qAbs( qGreen( myExpectedPixel ) - qGreen( myActualPixel ) ) > pixelTolerance ||
420 qAbs( qBlue( myExpectedPixel ) - qBlue( myActualPixel ) ) > pixelTolerance ||
421 qAbs( qAlpha( myExpectedPixel ) - qAlpha( myActualPixel ) ) > pixelTolerance )
424 diffScanline[ x ] = qRgb( 255, 0, 0 );
432 myDifferenceImage.save( myDiffImageFile );
438 qDebug(
"%d/%d pixels mismatched (%d allowed)", mMismatchCount,
mMatchTarget, theMismatchCount );
443 mReport +=
"<tr><td colspan=3>" +
444 QString::number( mMismatchCount ) +
"/" +
446 " pixels mismatched (allowed threshold: " +
447 QString::number( theMismatchCount ) +
448 ", allowed color component tolerance: " +
449 QString::number( mColorTolerance ) +
")";
459 if ( myAnomalyMatchFlag )
461 mReport +=
"<tr><td colspan=3>"
462 "Difference image matched a known anomaly - passing test! "
468 mReport +=
"<tr><td colspan=3>"
470 emitDashMessage(
"No Anomalies Match",
QgsDartMeasurement::Text,
"Difference image did not match any known anomaly."
471 " If you feel the difference image should be considered an anomaly "
472 "you can do something like this\n"
473 "cp " + myDiffImageFile +
" ../tests/testdata/control_images/" + theTestName +
474 "/<imagename>.{wld,png}" );
477 if ( mMismatchCount <= theMismatchCount )
479 mReport +=
"<tr><td colspan = 3>\n";
480 mReport +=
"Test image and result image for " + theTestName +
" are matched<br>";
482 if ( mElapsedTimeTarget != 0 && mElapsedTimeTarget <
mElapsedTime )
485 qDebug(
"Test failed because render step took too long" );
486 mReport +=
"<tr><td colspan = 3>\n";
487 mReport +=
"<font color=red>Test failed because render step took too long</font>";
500 mReport +=
"<tr><td colspan = 3>\n";
501 mReport +=
"<font color=red>Test image and result image for " + theTestName +
" are mismatched</font><br>";