QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgsmultirenderchecker.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmultirenderchecker.cpp
3  --------------------------------------
4  Date : 6.11.2014
5  Copyright : (C) 2014 Matthias Kuhn
6  Email : matthias at opengis dot ch
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 "qgsmultirenderchecker.h"
17 #include "qgslayout.h"
18 #include <QDebug>
19 
21 {
22  if ( qgetenv( "QGIS_CONTINUOUS_INTEGRATION_RUN" ) == QStringLiteral( "true" ) )
23  mIsCiRun = true;
24 }
25 
26 void QgsMultiRenderChecker::setControlName( const QString &name )
27 {
28  mControlName = name;
29 }
30 
31 void QgsMultiRenderChecker::setControlPathPrefix( const QString &prefix )
32 {
33  mControlPathPrefix = prefix;
34 }
35 
37 {
38  mMapSettings = mapSettings;
39 }
40 
41 bool QgsMultiRenderChecker::runTest( const QString &testName, unsigned int mismatchCount )
42 {
43  mResult = false;
44 
45  mReport += "<h2>" + testName + "</h2>\n";
46 
47  const QString baseDir = controlImagePath();
48 
49  QStringList subDirs = QDir( baseDir ).entryList( QDir::Dirs | QDir::NoDotAndDotDot );
50 
51  if ( subDirs.isEmpty() )
52  {
53  subDirs << QString();
54  }
55 
56  QVector<QgsDartMeasurement> dartMeasurements;
57 
58  for ( const QString &suffix : std::as_const( subDirs ) )
59  {
60  if ( subDirs.count() > 1 )
61  {
62  qDebug() << "Checking subdir " << suffix;
63  }
64  bool result;
65  QgsRenderChecker checker;
66  checker.enableDashBuffering( true );
67  checker.setColorTolerance( mColorTolerance );
68  checker.setSizeTolerance( mMaxSizeDifferenceX, mMaxSizeDifferenceY );
69  checker.setControlPathPrefix( mControlPathPrefix );
70  checker.setControlPathSuffix( suffix );
71  checker.setControlName( mControlName );
72  checker.setMapSettings( mMapSettings );
73 
74  if ( !mRenderedImage.isNull() )
75  {
76  checker.setRenderedImage( mRenderedImage );
77  result = checker.compareImages( testName, mismatchCount, mRenderedImage, QgsRenderChecker::Flag::AvoidExportingRenderedImage );
78  }
79  else
80  {
81  result = checker.runTest( testName, mismatchCount, QgsRenderChecker::Flag::AvoidExportingRenderedImage );
82  mRenderedImage = checker.renderedImage();
83  }
84 
85  mResult |= result;
86 
87  dartMeasurements << checker.dartMeasurements();
88 
89  mReport += checker.report( false );
90  }
91 
92  if ( !mResult && !mExpectFail && mIsCiRun )
93  {
94  const auto constDartMeasurements = dartMeasurements;
95  for ( const QgsDartMeasurement &measurement : constDartMeasurements )
96  measurement.send();
97 
98  QgsDartMeasurement msg( QStringLiteral( "Image not accepted by test" ), QgsDartMeasurement::Text, "This may be caused because the test is supposed to fail or rendering inconsistencies."
99  "If this is a rendering inconsistency, please add another control image folder, add an anomaly image or increase the color tolerance." );
100  msg.send();
101 
102 #if DUMP_BASE64_IMAGES
103  QFile fileSource( mRenderedImage );
104  fileSource.open( QIODevice::ReadOnly );
105 
106  const QByteArray blob = fileSource.readAll();
107  const QByteArray encoded = blob.toBase64();
108  qDebug() << "Dumping rendered image " << mRenderedImage << " as base64\n";
109  qDebug() << "################################################################";
110  qDebug() << encoded;
111  qDebug() << "################################################################";
112  qDebug() << "End dump";
113 #endif
114  }
115 
116  if ( !mResult && !mExpectFail )
117  {
118  const QDir reportDir = QgsRenderChecker::testReportDir();
119  if ( !reportDir.exists() )
120  {
121  if ( !QDir().mkpath( reportDir.path() ) )
122  {
123  qDebug() << "!!!!! cannot create " << reportDir.path();
124  }
125  }
126  if ( QFile::exists( mRenderedImage ) )
127  {
128  QFileInfo fi( mRenderedImage );
129  const QString destPath = reportDir.filePath( fi.fileName() );
130  if ( !QFile::copy( mRenderedImage, destPath ) )
131  {
132  qDebug() << "!!!!! could not copy " << mRenderedImage << " to " << destPath;
133  }
134  }
135  }
136 
137  return mResult;
138 }
139 
141 {
142  return !mResult ? mReport : QString();
143 }
144 
146 {
147  QString myDataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
148  QString myControlImageDir = myDataDir + QDir::separator() + "control_images" +
149  QDir::separator() + mControlPathPrefix + QDir::separator() + mControlName + QDir::separator();
150  return myControlImageDir;
151 }
152 
153 //
154 // QgsLayoutChecker
155 //
156 
158 
159 QgsLayoutChecker::QgsLayoutChecker( const QString &testName, QgsLayout *layout )
160  : mTestName( testName )
161  , mLayout( layout )
162  , mSize( 1122, 794 )
163  , mDotsPerMeter( 96 / 25.4 * 1000 )
164 {
165  // Qt has some slight render inconsistencies on the whole image sometimes
166  setColorTolerance( 5 );
167 }
168 
169 bool QgsLayoutChecker::testLayout( QString &checkedReport, int page, int pixelDiff, bool createReferenceImage )
170 {
171 #ifdef QT_NO_PRINTER
172  return false;
173 #else
174  if ( !mLayout )
175  {
176  return false;
177  }
178 
179  setControlName( "expected_" + mTestName );
180 
181 
182  if ( createReferenceImage )
183  {
184  //fake mode to generate expected image
185  //assume 96 dpi
186 
187 
188  QImage _outputImage( mSize, QImage::Format_RGB32 );
189  _outputImage.setDotsPerMeterX( 96 / 25.4 * 1000 );
190  _outputImage.setDotsPerMeterY( 96 / 25.4 * 1000 );
191  QPainter _p( &_outputImage );
192  QgsLayoutExporter _exporter( mLayout );
193  _exporter.renderPage( &_p, page );
194  _p.end();
195 
196  if ( ! QDir( controlImagePath() ).exists() )
197  {
198  QDir().mkdir( controlImagePath() );
199  }
200  _outputImage.save( controlImagePath() + QDir::separator() + "expected_" + mTestName + ".png", "PNG" );
201  qDebug( ) << "Reference image saved to : " + controlImagePath() + QDir::separator() + "expected_" + mTestName + ".png";
202 
203  }
204 
205  QImage outputImage( mSize, QImage::Format_RGB32 );
206  outputImage.setDotsPerMeterX( mDotsPerMeter );
207  outputImage.setDotsPerMeterY( mDotsPerMeter );
208  drawBackground( &outputImage );
209  QPainter p( &outputImage );
210  QgsLayoutExporter exporter( mLayout );
211  exporter.renderPage( &p, page );
212  p.end();
213 
214  QString renderedFilePath = QDir::tempPath() + '/' + QFileInfo( mTestName ).baseName() + "_rendered.png";
215  outputImage.save( renderedFilePath, "PNG" );
216 
217  setRenderedImage( renderedFilePath );
218 
219  bool testResult = runTest( mTestName, pixelDiff );
220 
221  checkedReport += report();
222 
223  return testResult;
224 #endif // QT_NO_PRINTER
225 }
226 
227 
228 
QgsRenderChecker::setMapSettings
void setMapSettings(const QgsMapSettings &mapSettings)
Definition: qgsrenderchecker.cpp:92
QgsRenderChecker::setControlPathSuffix
void setControlPathSuffix(const QString &name)
Definition: qgsrenderchecker.cpp:71
QgsMultiRenderChecker::setColorTolerance
void setColorTolerance(unsigned int colorTolerance)
Set tolerance for color components used by runTest() Default value is 0.
Definition: qgsmultirenderchecker.h:105
QgsRenderChecker::runTest
bool runTest(const QString &testName, unsigned int mismatchCount=0, QgsRenderChecker::Flags flags=QgsRenderChecker::Flags())
Render checker flags.
Definition: qgsrenderchecker.cpp:239
QgsDartMeasurement::Text
@ Text
Definition: qgsdartmeasurement.h:32
QgsRenderChecker::report
QString report(bool ignoreSuccess=true) const
Returns the HTML report describing the results of the test run.
Definition: qgsrenderchecker.cpp:60
QgsMultiRenderChecker::QgsMultiRenderChecker
QgsMultiRenderChecker()
Constructor for QgsMultiRenderChecker.
Definition: qgsmultirenderchecker.cpp:20
QgsRenderChecker::Flag::AvoidExportingRenderedImage
@ AvoidExportingRenderedImage
Avoids exporting rendered images to reports.
QgsRenderChecker::setColorTolerance
void setColorTolerance(unsigned int colorTolerance)
Set tolerance for color components used by runTest() and compareImages().
Definition: qgsrenderchecker.h:174
QgsLayoutExporter
Handles rendering and exports of layouts to various formats.
Definition: qgslayoutexporter.h:46
QgsRenderChecker::setSizeTolerance
void setSizeTolerance(int xTolerance, int yTolerance)
Sets the largest allowable difference in size between the rendered and the expected image.
Definition: qgsrenderchecker.h:182
qgslayout.h
QgsMultiRenderChecker::runTest
bool runTest(const QString &testName, unsigned int mismatchCount=0)
Test using renderer to generate the image to be compared.
Definition: qgsmultirenderchecker.cpp:41
qgsmultirenderchecker.h
QgsRenderChecker::renderedImage
QString renderedImage() const
Returns the path of the rendered image generated by the test.
Definition: qgsrenderchecker.h:162
QgsDartMeasurement
Definition: qgsdartmeasurement.h:27
QgsLayout
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:50
QgsMultiRenderChecker::setControlName
void setControlName(const QString &name)
Base directory name for the control image (with control image path suffixed) the path to the image wi...
Definition: qgsmultirenderchecker.cpp:26
QgsMultiRenderChecker::report
QString report() const
Returns a report for this test.
Definition: qgsmultirenderchecker.cpp:140
QgsRenderChecker::enableDashBuffering
void enableDashBuffering(bool enable)
Call this to enable internal buffering of dash messages.
Definition: qgsrenderchecker.h:267
QgsMultiRenderChecker::controlImagePath
QString controlImagePath() const
Returns the path to the control images.
Definition: qgsmultirenderchecker.cpp:145
QgsRenderChecker::setControlPathPrefix
void setControlPathPrefix(const QString &name)
Sets the path prefix where the control images are kept.
Definition: qgsrenderchecker.h:138
QgsRenderChecker::setRenderedImage
void setRenderedImage(const QString &imageFileName)
Sets the file name of the rendered image generated by the test.
Definition: qgsrenderchecker.h:148
QgsMultiRenderChecker::setMapSettings
void setMapSettings(const QgsMapSettings &mapSettings)
Set the map settings to use to render the image.
Definition: qgsmultirenderchecker.cpp:36
QgsDartMeasurement::send
void send() const
Definition: qgsdartmeasurement.cpp:43
QgsRenderChecker
This is a helper class for unit tests that need to write an image and compare it to an expected resul...
Definition: qgsrenderchecker.h:41
QgsMapSettings
The QgsMapSettings class contains configuration for rendering of the map. The rendering itself is don...
Definition: qgsmapsettings.h:88
QgsMultiRenderChecker::setControlPathPrefix
void setControlPathPrefix(const QString &prefix)
Definition: qgsmultirenderchecker.cpp:31
QgsRenderChecker::dartMeasurements
QVector< QgsDartMeasurement > dartMeasurements() const
Gets access to buffered dash messages.
Definition: qgsrenderchecker.h:275
QgsRenderChecker::testReportDir
static QDir testReportDir()
Returns the directory to use for generating a test report.
Definition: qgsrenderchecker.cpp:37
QgsRenderChecker::compareImages
bool compareImages(const QString &testName, unsigned int mismatchCount=0, const QString &renderedImageFile=QString(), QgsRenderChecker::Flags flags=QgsRenderChecker::Flags())
Test using two arbitrary images (map renderer will not be used)
Definition: qgsrenderchecker.cpp:326
QgsRenderChecker::setControlName
void setControlName(const QString &name)
Sets the base directory name for the control image (with control image path suffixed).
Definition: qgsrenderchecker.cpp:65