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