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