QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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 <QDebug>
19
21{
22 if ( qgetenv( "QGIS_CONTINUOUS_INTEGRATION_RUN" ) == QStringLiteral( "true" ) )
23 mIsCiRun = true;
24}
25
26void QgsMultiRenderChecker::setControlName( const QString &name )
27{
28 mControlName = name;
29}
30
32{
33 mControlPathPrefix = prefix;
34}
35
37{
38 mMapSettings = mapSettings;
39}
40
41bool 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 if ( !QFile::exists( baseDir ) )
49 {
50 qDebug() << "Control image path " << baseDir << " does not exist!";
51 return mResult;
52 }
53
54 QStringList subDirs = QDir( baseDir ).entryList( QDir::Dirs | QDir::NoDotAndDotDot );
55
56 if ( subDirs.isEmpty() )
57 {
58 subDirs << QString();
59 }
60
61 QVector<QgsDartMeasurement> dartMeasurements;
62
63 // we can only report one diff image, so just use the first
64 QString diffImageFile;
65
66 for ( const QString &suffix : std::as_const( subDirs ) )
67 {
68 if ( subDirs.count() > 1 )
69 {
70 qDebug() << "Checking subdir " << suffix;
71 }
72 bool result;
73 QgsRenderChecker checker;
74 checker.enableDashBuffering( true );
75 checker.setColorTolerance( mColorTolerance );
76 checker.setSizeTolerance( mMaxSizeDifferenceX, mMaxSizeDifferenceY );
77 checker.setControlPathPrefix( mControlPathPrefix );
78 checker.setControlPathSuffix( suffix );
79 checker.setControlName( mControlName );
80 checker.setMapSettings( mMapSettings );
81
82 if ( !mRenderedImage.isNull() )
83 {
84 checker.setRenderedImage( mRenderedImage );
85 result = checker.compareImages( testName, mismatchCount, mRenderedImage, QgsRenderChecker::Flag::AvoidExportingRenderedImage );
86 }
87 else
88 {
89 result = checker.runTest( testName, mismatchCount, QgsRenderChecker::Flag::AvoidExportingRenderedImage );
90 mRenderedImage = checker.renderedImage();
91 }
92
93 mResult |= result;
94
95 dartMeasurements << checker.dartMeasurements();
96
97 mReport += checker.report( false );
98
99 if ( !mResult && diffImageFile.isEmpty() )
100 {
101 diffImageFile = checker.mDiffImageFile;
102 }
103 }
104
105 if ( !mResult && !mExpectFail && mIsCiRun )
106 {
107 const auto constDartMeasurements = dartMeasurements;
108 for ( const QgsDartMeasurement &measurement : constDartMeasurements )
109 measurement.send();
110
111 QgsDartMeasurement msg( QStringLiteral( "Image not accepted by test" ), QgsDartMeasurement::Text, "This may be caused because the test is supposed to fail or rendering inconsistencies."
112 "If this is a rendering inconsistency, please add another control image folder, add an anomaly image or increase the color tolerance." );
113 msg.send();
114
115#if DUMP_BASE64_IMAGES
116 QFile fileSource( mRenderedImage );
117 fileSource.open( QIODevice::ReadOnly );
118
119 const QByteArray blob = fileSource.readAll();
120 const QByteArray encoded = blob.toBase64();
121 qDebug() << "Dumping rendered image " << mRenderedImage << " as base64\n";
122 qDebug() << "################################################################";
123 qDebug() << encoded;
124 qDebug() << "################################################################";
125 qDebug() << "End dump";
126#endif
127 }
128
129 if ( !mResult && !mExpectFail )
130 {
131 const QDir reportDir = QgsRenderChecker::testReportDir();
132 if ( !reportDir.exists() )
133 {
134 if ( !QDir().mkpath( reportDir.path() ) )
135 {
136 qDebug() << "!!!!! cannot create " << reportDir.path();
137 }
138 }
139 if ( QFile::exists( mRenderedImage ) )
140 {
141 QFileInfo fi( mRenderedImage );
142 const QString destPath = reportDir.filePath( fi.fileName() );
143 if ( QFile::exists( destPath ) )
144 QFile::remove( destPath );
145
146 if ( !QFile::copy( mRenderedImage, destPath ) )
147 {
148 qDebug() << "!!!!! could not copy " << mRenderedImage << " to " << destPath;
149 }
150 }
151
152 if ( !diffImageFile.isEmpty() && QFile::exists( diffImageFile ) )
153 {
154 QFileInfo fi( diffImageFile );
155 const QString destPath = reportDir.filePath( fi.fileName() );
156 if ( QFile::exists( destPath ) )
157 QFile::remove( destPath );
158
159 if ( !QFile::copy( diffImageFile, destPath ) )
160 {
161 qDebug() << "!!!!! could not copy " << diffImageFile << " to " << destPath;
162 }
163 }
164 }
165
166 return mResult;
167}
168
170{
171 return !mResult ? mReport : QString();
172}
173
175{
176 QString myDataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
177 QString myControlImageDir = myDataDir + QDir::separator() + "control_images" +
178 QDir::separator() + mControlPathPrefix + QDir::separator() + mControlName + QDir::separator();
179 return myControlImageDir;
180}
181
182//
183// QgsLayoutChecker
184//
185
187
188QgsLayoutChecker::QgsLayoutChecker( const QString &testName, QgsLayout *layout )
189 : mTestName( testName )
190 , mLayout( layout )
191 , mSize( 1122, 794 )
192 , mDotsPerMeter( 96 / 25.4 * 1000 )
193{
194 // Qt has some slight render inconsistencies on the whole image sometimes
196}
197
198bool QgsLayoutChecker::testLayout( QString &checkedReport, int page, int pixelDiff, bool createReferenceImage )
199{
200#ifdef QT_NO_PRINTER
201 return false;
202#else
203 if ( !mLayout )
204 {
205 return false;
206 }
207
208 setControlName( "expected_" + mTestName );
209
210
211 if ( createReferenceImage )
212 {
213 //fake mode to generate expected image
214 //assume 96 dpi
215
216
217 QImage _outputImage( mSize, QImage::Format_RGB32 );
218 _outputImage.setDotsPerMeterX( 96 / 25.4 * 1000 );
219 _outputImage.setDotsPerMeterY( 96 / 25.4 * 1000 );
220 QPainter _p( &_outputImage );
221 QgsLayoutExporter _exporter( mLayout );
222 _exporter.renderPage( &_p, page );
223 _p.end();
224
225 if ( ! QDir( controlImagePath() ).exists() )
226 {
227 QDir().mkdir( controlImagePath() );
228 }
229 _outputImage.save( controlImagePath() + QDir::separator() + "expected_" + mTestName + ".png", "PNG" );
230 qDebug( ) << "Reference image saved to : " + controlImagePath() + QDir::separator() + "expected_" + mTestName + ".png";
231
232 }
233
234 QImage outputImage( mSize, QImage::Format_RGB32 );
235 outputImage.setDotsPerMeterX( mDotsPerMeter );
236 outputImage.setDotsPerMeterY( mDotsPerMeter );
237 drawBackground( &outputImage );
238 QPainter p( &outputImage );
239 QgsLayoutExporter exporter( mLayout );
240 exporter.renderPage( &p, page );
241 p.end();
242
243 QString renderedFilePath = QDir::tempPath() + '/' + QFileInfo( mTestName ).baseName() + "_rendered.png";
244 outputImage.save( renderedFilePath, "PNG" );
245
246 setRenderedImage( renderedFilePath );
247
248 bool testResult = runTest( mTestName, pixelDiff );
249
250 checkedReport += report();
251
252 return testResult;
253#endif // QT_NO_PRINTER
254}
255
256
257
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:51
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().