Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsrenderchecker.cpp
3 : : --------------------------------------
4 : : Date : 18 Jan 2008
5 : : Copyright : (C) 2008 by Tim Sutton
6 : : Email : tim @ linfiniti.com
7 : : ***************************************************************************
8 : : * *
9 : : * This program is free software; you can redistribute it and/or modify *
10 : : * it under the terms of the GNU General Public License as published by *
11 : : * the Free Software Foundation; either version 2 of the License, or *
12 : : * (at your option) any later version. *
13 : : * *
14 : : ***************************************************************************/
15 : :
16 : : #include "qgsrenderchecker.h"
17 : :
18 : : #include "qgis.h"
19 : : #include "qgsmaprenderersequentialjob.h"
20 : : #include "qgsgeometry.h"
21 : :
22 : : #include <QColor>
23 : : #include <QPainter>
24 : : #include <QImage>
25 : : #include <QCryptographicHash>
26 : : #include <QByteArray>
27 : : #include <QDebug>
28 : : #include <QBuffer>
29 : : #include <QUuid>
30 : :
31 : 18 : QgsRenderChecker::QgsRenderChecker()
32 : 18 : : mBasePath( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/control_images/" ) ) //defined in CmakeLists.txt
33 : : {
34 : 6 : }
35 : :
36 : 6 : QString QgsRenderChecker::controlImagePath() const
37 : : {
38 : 6 : return mBasePath + ( mBasePath.endsWith( '/' ) ? QString() : QStringLiteral( "/" ) ) + mControlPathPrefix;
39 : 0 : }
40 : :
41 : 0 : void QgsRenderChecker::setControlImagePath( const QString &path )
42 : : {
43 : 0 : mBasePath = path;
44 : 0 : }
45 : :
46 : 6 : void QgsRenderChecker::setControlName( const QString &name )
47 : : {
48 : 6 : mControlName = name;
49 : 6 : mExpectedImageFile = controlImagePath() + name + '/' + mControlPathSuffix + name + "." + mControlExtension;
50 : 6 : }
51 : :
52 : 0 : void QgsRenderChecker::setControlPathSuffix( const QString &name )
53 : : {
54 : 0 : if ( !name.isEmpty() )
55 : 0 : mControlPathSuffix = name + '/';
56 : : else
57 : 0 : mControlPathSuffix.clear();
58 : 0 : }
59 : :
60 : 0 : QString QgsRenderChecker::imageToHash( const QString &imageFile )
61 : : {
62 : 0 : QImage myImage;
63 : 0 : myImage.load( imageFile );
64 : 0 : QByteArray myByteArray;
65 : 0 : QBuffer myBuffer( &myByteArray );
66 : 0 : myImage.save( &myBuffer, "PNG" );
67 : 0 : QString myImageString = QString::fromUtf8( myByteArray.toBase64().data() );
68 : 0 : QCryptographicHash myHash( QCryptographicHash::Md5 );
69 : 0 : myHash.addData( myImageString.toUtf8() );
70 : 0 : return myHash.result().toHex().constData();
71 : 0 : }
72 : :
73 : 0 : void QgsRenderChecker::setMapSettings( const QgsMapSettings &mapSettings )
74 : : {
75 : 0 : mMapSettings = mapSettings;
76 : 0 : }
77 : :
78 : 0 : void QgsRenderChecker::drawBackground( QImage *image )
79 : : {
80 : : // create a 2x2 checker-board image
81 : 0 : uchar pixDataRGB[] = { 255, 255, 255, 255,
82 : : 127, 127, 127, 255,
83 : : 127, 127, 127, 255,
84 : : 255, 255, 255, 255
85 : : };
86 : :
87 : 0 : QImage img( pixDataRGB, 2, 2, 8, QImage::Format_ARGB32 );
88 : 0 : QPixmap pix = QPixmap::fromImage( img.scaled( 20, 20 ) );
89 : :
90 : : // fill image with texture
91 : 0 : QBrush brush;
92 : 0 : brush.setTexture( pix );
93 : 0 : QPainter p( image );
94 : 0 : p.setRenderHint( QPainter::Antialiasing, false );
95 : 0 : p.fillRect( QRect( 0, 0, image->width(), image->height() ), brush );
96 : 0 : p.end();
97 : 0 : }
98 : :
99 : 0 : bool QgsRenderChecker::isKnownAnomaly( const QString &diffImageFile )
100 : : {
101 : 0 : QString myControlImageDir = controlImagePath() + mControlName + '/';
102 : 0 : QDir myDirectory = QDir( myControlImageDir );
103 : 0 : QStringList myList;
104 : 0 : QString myFilename = QStringLiteral( "*" );
105 : 0 : myList = myDirectory.entryList( QStringList( myFilename ),
106 : 0 : QDir::Files | QDir::NoSymLinks );
107 : : //remove the control file from the list as the anomalies are
108 : : //all files except the control file
109 : 0 : myList.removeAt( myList.indexOf( QFileInfo( mExpectedImageFile ).fileName() ) );
110 : :
111 : 0 : QString myImageHash = imageToHash( diffImageFile );
112 : :
113 : :
114 : 0 : for ( int i = 0; i < myList.size(); ++i )
115 : : {
116 : 0 : QString myFile = myList.at( i );
117 : 0 : mReport += "<tr><td colspan=3>"
118 : 0 : "Checking if " + myFile + " is a known anomaly.";
119 : 0 : mReport += QLatin1String( "</td></tr>" );
120 : 0 : QString myAnomalyHash = imageToHash( controlImagePath() + mControlName + '/' + myFile );
121 : 0 : QString myHashMessage = QStringLiteral(
122 : : "Checking if anomaly %1 (hash %2)<br>" )
123 : 0 : .arg( myFile,
124 : : myAnomalyHash );
125 : 0 : myHashMessage += QStringLiteral( " matches %1 (hash %2)" )
126 : 0 : .arg( diffImageFile,
127 : : myImageHash );
128 : : //foo CDash
129 : 0 : emitDashMessage( QStringLiteral( "Anomaly check" ), QgsDartMeasurement::Text, myHashMessage );
130 : :
131 : 0 : mReport += "<tr><td colspan=3>" + myHashMessage + "</td></tr>";
132 : 0 : if ( myImageHash == myAnomalyHash )
133 : : {
134 : 0 : mReport += "<tr><td colspan=3>"
135 : 0 : "Anomaly found! " + myFile;
136 : 0 : mReport += QLatin1String( "</td></tr>" );
137 : 0 : return true;
138 : : }
139 : 0 : }
140 : 0 : mReport += "<tr><td colspan=3>"
141 : : "No anomaly found! ";
142 : 0 : mReport += QLatin1String( "</td></tr>" );
143 : 0 : return false;
144 : 0 : }
145 : :
146 : 24 : void QgsRenderChecker::emitDashMessage( const QgsDartMeasurement &dashMessage )
147 : : {
148 : 24 : if ( mBufferDashMessages )
149 : 0 : mDashMessages << dashMessage;
150 : : else
151 : 24 : dashMessage.send();
152 : 24 : }
153 : :
154 : 24 : void QgsRenderChecker::emitDashMessage( const QString &name, QgsDartMeasurement::Type type, const QString &value )
155 : : {
156 : 24 : emitDashMessage( QgsDartMeasurement( name, type, value ) );
157 : 24 : }
158 : :
159 : 0 : bool QgsRenderChecker::runTest( const QString &testName,
160 : : unsigned int mismatchCount )
161 : : {
162 : 0 : if ( mExpectedImageFile.isEmpty() )
163 : : {
164 : 0 : qDebug( "QgsRenderChecker::runTest failed - Expected Image File not set." );
165 : 0 : mReport = "<table>"
166 : : "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
167 : : "<tr><td>Nothing rendered</td>\n<td>Failed because Expected "
168 : : "Image File not set.</td></tr></table>\n";
169 : 0 : return false;
170 : : }
171 : : //
172 : : // Load the expected result pixmap
173 : : //
174 : 0 : QImage myExpectedImage( mExpectedImageFile );
175 : 0 : if ( myExpectedImage.isNull() )
176 : : {
177 : 0 : qDebug() << "QgsRenderChecker::runTest failed - Could not load expected image from " << mExpectedImageFile;
178 : 0 : mReport = "<table>"
179 : : "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
180 : : "<tr><td>Nothing rendered</td>\n<td>Failed because Expected "
181 : : "Image File could not be loaded.</td></tr></table>\n";
182 : 0 : return false;
183 : : }
184 : 0 : mMatchTarget = myExpectedImage.width() * myExpectedImage.height();
185 : : //
186 : : // Now render our layers onto a pixmap
187 : : //
188 : 0 : mMapSettings.setBackgroundColor( qRgb( 152, 219, 249 ) );
189 : 0 : mMapSettings.setFlag( QgsMapSettings::Antialiasing );
190 : 0 : mMapSettings.setOutputSize( QSize( myExpectedImage.width(), myExpectedImage.height() ) / mMapSettings.devicePixelRatio() );
191 : :
192 : 0 : QElapsedTimer myTime;
193 : 0 : myTime.start();
194 : :
195 : 0 : QgsMapRendererSequentialJob job( mMapSettings );
196 : 0 : job.start();
197 : 0 : job.waitForFinished();
198 : :
199 : 0 : mElapsedTime = myTime.elapsed();
200 : :
201 : 0 : QImage myImage = job.renderedImage();
202 : : Q_ASSERT( myImage.devicePixelRatioF() == mMapSettings.devicePixelRatio() );
203 : :
204 : : //
205 : : // Save the pixmap to disk so the user can make a
206 : : // visual assessment if needed
207 : : //
208 : 0 : mRenderedImageFile = QDir::tempPath() + '/' + testName + "_result.png";
209 : :
210 : 0 : myImage.setDotsPerMeterX( myExpectedImage.dotsPerMeterX() );
211 : 0 : myImage.setDotsPerMeterY( myExpectedImage.dotsPerMeterY() );
212 : 0 : if ( ! myImage.save( mRenderedImageFile, "PNG", 100 ) )
213 : : {
214 : 0 : qDebug() << "QgsRenderChecker::runTest failed - Could not save rendered image to " << mRenderedImageFile;
215 : 0 : mReport = "<table>"
216 : : "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
217 : : "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
218 : : "Image File could not be saved.</td></tr></table>\n";
219 : 0 : return false;
220 : : }
221 : :
222 : : //create a world file to go with the image...
223 : :
224 : 0 : QFile wldFile( QDir::tempPath() + '/' + testName + "_result.wld" );
225 : 0 : if ( wldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
226 : : {
227 : 0 : QgsRectangle r = mMapSettings.extent();
228 : 6 :
229 : 6 : QTextStream stream( &wldFile );
230 : 0 : stream << QStringLiteral( "%1\r\n0 \r\n0 \r\n%2\r\n%3\r\n%4\r\n" )
231 : 0 : .arg( qgsDoubleToString( mMapSettings.mapUnitsPerPixel() ),
232 : 0 : qgsDoubleToString( -mMapSettings.mapUnitsPerPixel() ),
233 : 0 : qgsDoubleToString( r.xMinimum() + mMapSettings.mapUnitsPerPixel() / 2.0 ),
234 : 0 : qgsDoubleToString( r.yMaximum() - mMapSettings.mapUnitsPerPixel() / 2.0 ) );
235 : 0 : }
236 : :
237 : 0 : return compareImages( testName, mismatchCount );
238 : 0 : }
239 : :
240 : 6 :
241 : 12 : bool QgsRenderChecker::compareImages( const QString &testName,
242 : 6 : unsigned int mismatchCount,
243 : 6 : const QString &renderedImageFile )
244 : 6 : {
245 : 6 : if ( mExpectedImageFile.isEmpty() )
246 : 6 : {
247 : 0 : qDebug( "QgsRenderChecker::runTest failed - Expected Image (control) File not set." );
248 : 0 : mReport = "<table>"
249 : : "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
250 : 6 : "<tr><td>Nothing rendered</td>\n<td>Failed because Expected "
251 : : "Image File not set.</td></tr></table>\n";
252 : 0 : return false;
253 : : }
254 : :
255 : 6 : return compareImages( testName, mExpectedImageFile, renderedImageFile, mismatchCount );
256 : 6 : }
257 : :
258 : 6 : bool QgsRenderChecker::compareImages( const QString &testName, const QString &referenceImageFile, const QString &renderedImageFile, unsigned int mismatchCount )
259 : : {
260 : 6 : if ( ! renderedImageFile.isEmpty() )
261 : : {
262 : 0 : mRenderedImageFile = renderedImageFile;
263 : : #ifdef Q_OS_WIN
264 : : mRenderedImageFile = mRenderedImageFile.replace( '\\', '/' );
265 : : #endif
266 : 0 : }
267 : :
268 : 6 : if ( mRenderedImageFile.isEmpty() )
269 : : {
270 : 0 : qDebug( "QgsRenderChecker::runTest failed - Rendered Image File not set." );
271 : 0 : mReport = "<table>"
272 : : "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
273 : : "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
274 : : "Image File not set.</td></tr></table>\n";
275 : 0 : return false;
276 : : }
277 : :
278 : : //
279 : : // Load /create the images
280 : : //
281 : 6 : QImage myExpectedImage( referenceImageFile );
282 : 6 : QImage myResultImage( mRenderedImageFile );
283 : 6 : if ( myResultImage.isNull() )
284 : : {
285 : 0 : qDebug() << "QgsRenderChecker::runTest failed - Could not load rendered image from " << mRenderedImageFile;
286 : 0 : mReport = "<table>"
287 : : "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
288 : : "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
289 : : "Image File could not be loaded.</td></tr></table>\n";
290 : 0 : return false;
291 : : }
292 : 12 : QImage myDifferenceImage( myExpectedImage.width(),
293 : 6 : myExpectedImage.height(),
294 : : QImage::Format_RGB32 );
295 : 6 : QString myDiffImageFile = QDir::tempPath() + '/' + testName + "_result_diff.png";
296 : 6 : myDifferenceImage.fill( qRgb( 152, 219, 249 ) );
297 : :
298 : : //check for mask
299 : 6 : QString maskImagePath = referenceImageFile;
300 : 6 : maskImagePath.chop( 4 ); //remove .png extension
301 : 6 : maskImagePath += QLatin1String( "_mask.png" );
302 : 6 : const QImage maskImage( maskImagePath );
303 : 6 : const bool hasMask = !maskImage.isNull();
304 : :
305 : : //
306 : : // Set pixel count score and target
307 : : //
308 : 6 : mMatchTarget = myExpectedImage.width() * myExpectedImage.height();
309 : 6 : unsigned int myPixelCount = myResultImage.width() * myResultImage.height();
310 : : //
311 : : // Set the report with the result
312 : : //
313 : 12 : mReport = QStringLiteral( "<script src=\"file://%1/../renderchecker.js\"></script>\n" ).arg( TEST_DATA_DIR );
314 : 6 : mReport += QLatin1String( "<table>" );
315 : 6 : mReport += QLatin1String( "<tr><td colspan=2>" );
316 : 12 : mReport += QString( "<tr><td colspan=2>"
317 : : "Test image and result image for %1<br>"
318 : : "Expected size: %2 w x %3 h (%4 pixels)<br>"
319 : : "Actual size: %5 w x %6 h (%7 pixels)"
320 : : "</td></tr>" )
321 : 6 : .arg( testName )
322 : 6 : .arg( myExpectedImage.width() ).arg( myExpectedImage.height() ).arg( mMatchTarget )
323 : 6 : .arg( myResultImage.width() ).arg( myResultImage.height() ).arg( myPixelCount );
324 : 12 : mReport += QString( "<tr><td colspan=2>\n"
325 : : "Expected Duration : <= %1 (0 indicates not specified)<br>"
326 : : "Actual Duration : %2 ms<br></td></tr>" )
327 : 6 : .arg( mElapsedTimeTarget )
328 : 6 : .arg( mElapsedTime );
329 : :
330 : : // limit image size in page to something reasonable
331 : 6 : int imgWidth = 420;
332 : 6 : int imgHeight = 280;
333 : 6 : if ( ! myExpectedImage.isNull() )
334 : : {
335 : 6 : imgWidth = std::min( myExpectedImage.width(), imgWidth );
336 : 6 : imgHeight = myExpectedImage.height() * imgWidth / myExpectedImage.width();
337 : 6 : }
338 : :
339 : 12 : QString myImagesString = QString(
340 : : "<tr>"
341 : : "<td colspan=2>Compare actual and expected result</td>"
342 : : "<td>Difference (all blue is good, any red is bad)</td>"
343 : : "</tr>\n<tr>"
344 : : "<td colspan=2 id=\"td-%1-%7\"></td>\n"
345 : : "<td align=center><img width=%5 height=%6 src=\"file://%2\"></td>\n"
346 : : "</tr>"
347 : : "</table>\n"
348 : : "<script>\naddComparison(\"td-%1-%7\",\"file://%3\",\"file://%4\",%5,%6);\n</script>\n" )
349 : 12 : .arg( testName,
350 : : myDiffImageFile,
351 : 6 : mRenderedImageFile,
352 : 6 : referenceImageFile )
353 : 6 : .arg( imgWidth ).arg( imgHeight )
354 : 6 : .arg( QUuid::createUuid().toString().mid( 1, 6 ) );
355 : :
356 : 6 : QString prefix;
357 : 6 : if ( !mControlPathPrefix.isNull() )
358 : : {
359 : 0 : prefix = QStringLiteral( " (prefix %1)" ).arg( mControlPathPrefix );
360 : 0 : }
361 : : //
362 : : // To get the images into CDash
363 : : //
364 : 6 : emitDashMessage( "Rendered Image " + testName + prefix, QgsDartMeasurement::ImagePng, mRenderedImageFile );
365 : 6 : emitDashMessage( "Expected Image " + testName + prefix, QgsDartMeasurement::ImagePng, referenceImageFile );
366 : :
367 : : //
368 : : // Put the same info to debug too
369 : : //
370 : :
371 : 6 : if ( myExpectedImage.width() != myResultImage.width() || myExpectedImage.height() != myResultImage.height() )
372 : : {
373 : 0 : qDebug( "Expected size: %dw x %dh", myExpectedImage.width(), myExpectedImage.height() );
374 : 0 : qDebug( "Actual size: %dw x %dh", myResultImage.width(), myResultImage.height() );
375 : 0 : if ( hasMask )
376 : 0 : qDebug( "Mask size: %dw x %dh", maskImage.width(), maskImage.height() );
377 : 0 : }
378 : :
379 : 6 : if ( mMatchTarget != myPixelCount )
380 : : {
381 : 0 : qDebug( "Test image and result image for %s are different dimensions", testName.toLocal8Bit().constData() );
382 : :
383 : 0 : if ( std::abs( myExpectedImage.width() - myResultImage.width() ) > mMaxSizeDifferenceX ||
384 : 0 : std::abs( myExpectedImage.height() - myResultImage.height() ) > mMaxSizeDifferenceY )
385 : : {
386 : 0 : mReport += QLatin1String( "<tr><td colspan=3>" );
387 : 0 : mReport += "<font color=red>Expected image and result image for " + testName + " are different dimensions - FAILING!</font>";
388 : 0 : mReport += QLatin1String( "</td></tr>" );
389 : 0 : mReport += myImagesString;
390 : 0 : return false;
391 : : }
392 : : else
393 : : {
394 : 0 : mReport += QLatin1String( "<tr><td colspan=3>" );
395 : 0 : mReport += "Expected image and result image for " + testName + " are different dimensions, but within tolerance";
396 : 0 : mReport += QLatin1String( "</td></tr>" );
397 : : }
398 : 0 : }
399 : :
400 : 6 : if ( myExpectedImage.format() == QImage::Format_Indexed8 )
401 : : {
402 : 0 : if ( myResultImage.format() != QImage::Format_Indexed8 )
403 : : {
404 : 0 : qDebug() << "Expected image and result image for " << testName << " have different formats (8bit format is expected) - FAILING!";
405 : :
406 : 0 : mReport += QLatin1String( "<tr><td colspan=3>" );
407 : 0 : mReport += "<font color=red>Expected image and result image for " + testName + " have different formats (8bit format is expected) - FAILING!</font>";
408 : 0 : mReport += QLatin1String( "</td></tr>" );
409 : 0 : mReport += myImagesString;
410 : 0 : return false;
411 : : }
412 : :
413 : : // When we compute the diff between the 2 images, we use constScanLine expecting a QRgb color
414 : : // but this method returns color table index for 8 bit image, not color.
415 : : // So we convert the 2 images in 32 bits so the diff works correctly
416 : 0 : myResultImage = myResultImage.convertToFormat( QImage::Format_ARGB32 );
417 : 0 : myExpectedImage = myExpectedImage.convertToFormat( QImage::Format_ARGB32 );
418 : 0 : }
419 : :
420 : :
421 : : //
422 : : // Now iterate through them counting how many
423 : : // dissimilar pixel values there are
424 : : //
425 : :
426 : 6 : int maxHeight = std::min( myExpectedImage.height(), myResultImage.height() );
427 : 6 : int maxWidth = std::min( myExpectedImage.width(), myResultImage.width() );
428 : :
429 : 6 : mMismatchCount = 0;
430 : 6 : int colorTolerance = static_cast< int >( mColorTolerance );
431 : 1506 : for ( int y = 0; y < maxHeight; ++y )
432 : : {
433 : 1500 : const QRgb *expectedScanline = reinterpret_cast< const QRgb * >( myExpectedImage.constScanLine( y ) );
434 : 1500 : const QRgb *resultScanline = reinterpret_cast< const QRgb * >( myResultImage.constScanLine( y ) );
435 : 1500 : const QRgb *maskScanline = ( hasMask && maskImage.height() > y ) ? reinterpret_cast< const QRgb * >( maskImage.constScanLine( y ) ) : nullptr;
436 : 1500 : QRgb *diffScanline = reinterpret_cast< QRgb * >( myDifferenceImage.scanLine( y ) );
437 : :
438 : 376500 : for ( int x = 0; x < maxWidth; ++x )
439 : : {
440 : 375000 : int maskTolerance = ( maskScanline && maskImage.width() > x ) ? qRed( maskScanline[ x ] ) : 0;
441 : 375000 : int pixelTolerance = std::max( colorTolerance, maskTolerance );
442 : 375000 : if ( pixelTolerance == 255 )
443 : : {
444 : : //skip pixel
445 : 1 : continue;
446 : : }
447 : :
448 : 374999 : QRgb myExpectedPixel = expectedScanline[x];
449 : 374999 : QRgb myActualPixel = resultScanline[x];
450 : 374999 : if ( pixelTolerance == 0 )
451 : : {
452 : 374873 : if ( myExpectedPixel != myActualPixel )
453 : : {
454 : 0 : ++mMismatchCount;
455 : 0 : diffScanline[ x ] = qRgb( 255, 0, 0 );
456 : 0 : }
457 : 374873 : }
458 : : else
459 : : {
460 : 252 : if ( std::abs( qRed( myExpectedPixel ) - qRed( myActualPixel ) ) > pixelTolerance ||
461 : 126 : std::abs( qGreen( myExpectedPixel ) - qGreen( myActualPixel ) ) > pixelTolerance ||
462 : 126 : std::abs( qBlue( myExpectedPixel ) - qBlue( myActualPixel ) ) > pixelTolerance ||
463 : 126 : std::abs( qAlpha( myExpectedPixel ) - qAlpha( myActualPixel ) ) > pixelTolerance )
464 : : {
465 : 0 : ++mMismatchCount;
466 : 0 : diffScanline[ x ] = qRgb( 255, 0, 0 );
467 : 0 : }
468 : : }
469 : 374999 : }
470 : 1500 : }
471 : : //
472 : : //save the diff image to disk
473 : : //
474 : 6 : myDifferenceImage.save( myDiffImageFile );
475 : 6 : emitDashMessage( "Difference Image " + testName + prefix, QgsDartMeasurement::ImagePng, myDiffImageFile );
476 : :
477 : : //
478 : : // Send match result to debug
479 : : //
480 : 6 : if ( mMismatchCount > mismatchCount )
481 : : {
482 : 0 : qDebug( "%d/%d pixels mismatched (%d allowed)", mMismatchCount, mMatchTarget, mismatchCount );
483 : 0 : }
484 : :
485 : : //
486 : : // Send match result to report
487 : : //
488 : 18 : mReport += QStringLiteral( "<tr><td colspan=3>%1/%2 pixels mismatched (allowed threshold: %3, allowed color component tolerance: %4)</td></tr>" )
489 : 6 : .arg( mMismatchCount ).arg( mMatchTarget ).arg( mismatchCount ).arg( mColorTolerance );
490 : :
491 : : //
492 : : // And send it to CDash
493 : : //
494 : 18 : emitDashMessage( QStringLiteral( "Mismatch Count" ), QgsDartMeasurement::Integer, QStringLiteral( "%1/%2" ).arg( mMismatchCount ).arg( mMatchTarget ) );
495 : :
496 : 6 : if ( mMismatchCount <= mismatchCount )
497 : : {
498 : 6 : mReport += QLatin1String( "<tr><td colspan = 3>\n" );
499 : 6 : mReport += "Test image and result image for " + testName + " are matched<br>";
500 : 6 : mReport += QLatin1String( "</td></tr>" );
501 : 6 : if ( mElapsedTimeTarget != 0 && mElapsedTimeTarget < mElapsedTime )
502 : : {
503 : : //test failed because it took too long...
504 : 0 : qDebug( "Test failed because render step took too long" );
505 : 0 : mReport += QLatin1String( "<tr><td colspan = 3>\n" );
506 : 0 : mReport += QLatin1String( "<font color=red>Test failed because render step took too long</font>" );
507 : 0 : mReport += QLatin1String( "</td></tr>" );
508 : 0 : mReport += myImagesString;
509 : 0 : return false;
510 : : }
511 : : else
512 : : {
513 : 6 : mReport += myImagesString;
514 : 6 : return true;
515 : : }
516 : : }
517 : :
518 : 0 : bool myAnomalyMatchFlag = isKnownAnomaly( myDiffImageFile );
519 : 0 : if ( myAnomalyMatchFlag )
520 : : {
521 : 0 : mReport += "<tr><td colspan=3>"
522 : : "Difference image matched a known anomaly - passing test! "
523 : : "</td></tr>";
524 : 0 : return true;
525 : : }
526 : :
527 : 0 : mReport += QLatin1String( "<tr><td colspan=3></td></tr>" );
528 : 0 : emitDashMessage( QStringLiteral( "Image mismatch" ), QgsDartMeasurement::Text, "Difference image did not match any known anomaly or mask."
529 : : " If you feel the difference image should be considered an anomaly "
530 : : "you can do something like this\n"
531 : 0 : "cp '" + myDiffImageFile + "' " + controlImagePath() + mControlName +
532 : : "/\nIf it should be included in the mask run\n"
533 : 0 : "scripts/generate_test_mask_image.py '" + referenceImageFile + "' '" + mRenderedImageFile + "'\n" );
534 : :
535 : 0 : mReport += QLatin1String( "<tr><td colspan = 3>\n" );
536 : 0 : mReport += "<font color=red>Test image and result image for " + testName + " are mismatched</font><br>";
537 : 0 : mReport += QLatin1String( "</td></tr>" );
538 : 0 : mReport += myImagesString;
539 : 0 : return false;
540 : 6 : }
|