25 #include <QCryptographicHash>
36 , mRenderedImageFile(
"" )
37 , mExpectedImageFile(
"" )
39 , mColorTolerance( 0 )
40 , mMaxSizeDifferenceX( 0 )
41 , mMaxSizeDifferenceY( 0 )
42 , mElapsedTimeTarget( 0 )
43 , mBufferDashMessages( false )
49 QString myDataDir( TEST_DATA_DIR );
50 QString myControlImageDir = myDataDir +
"/control_images/" + mControlPathPrefix;
51 return myControlImageDir;
56 mControlName = theName;
63 myImage.
load( theImageFile );
65 QBuffer myBuffer( &myByteArray );
66 myImage.
save( &myBuffer,
"PNG" );
69 myHash.addData( myImageString.
toUtf8() );
70 return myHash.result().toHex().constData();
80 mMapSettings = mapSettings;
86 uchar pixDataRGB[] = { 255, 255, 255, 255,
92 QImage img( pixDataRGB, 2, 2, 8, QImage::Format_ARGB32 );
107 QDir myDirectory =
QDir( myControlImageDir );
111 QDir::Files | QDir::NoSymLinks );
119 for (
int i = 0; i < myList.
size(); ++i )
122 mReport +=
"<tr><td colspan=3>"
123 "Checking if " + myFile +
" is a known anomaly.";
127 "Checking if anomaly %1 (hash %2)<br>" )
129 .
arg( myAnomalyHash );
130 myHashMessage +=
QString(
" matches %1 (hash %2)" )
131 .
arg( theDiffImageFile )
136 mReport +=
"<tr><td colspan=3>" + myHashMessage +
"</td></tr>";
137 if ( myImageHash == myAnomalyHash )
139 mReport +=
"<tr><td colspan=3>"
140 "Anomaly found! " + myFile;
145 mReport +=
"<tr><td colspan=3>"
146 "No anomaly found! ";
153 if ( mBufferDashMessages )
154 mDashMessages << dashMessage;
165 unsigned int theMismatchCount )
169 qDebug(
"QgsRenderChecker::runTest failed - Expected Image File not set." );
171 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
172 "<tr><td>Nothing rendered</td>\n<td>Failed because Expected "
173 "Image File not set.</td></tr></table>\n";
180 if ( myExpectedImage.
isNull() )
182 qDebug() <<
"QgsRenderChecker::runTest failed - Could not load expected image from " <<
mExpectedImageFile;
184 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
185 "<tr><td>Nothing rendered</td>\n<td>Failed because Expected "
186 "Image File could not be loaded.</td></tr></table>\n";
218 qDebug() <<
"QgsRenderChecker::runTest failed - Could not save rendered image to " <<
mRenderedImageFile;
220 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
221 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
222 "Image File could not be saved.</td></tr></table>\n";
229 if ( wldFile.
open( QIODevice::WriteOnly ) )
234 stream <<
QString(
"%1\r\n0 \r\n0 \r\n%2\r\n%3\r\n%4\r\n" )
246 unsigned int theMismatchCount,
251 qDebug(
"QgsRenderChecker::runTest failed - Expected Image (control) File not set." );
253 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
254 "<tr><td>Nothing rendered</td>\n<td>Failed because Expected "
255 "Image File not set.</td></tr></table>\n";
258 if ( ! theRenderedImageFile.
isEmpty() )
269 qDebug(
"QgsRenderChecker::runTest failed - Rendered Image File not set." );
271 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
272 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
273 "Image File not set.</td></tr></table>\n";
282 if ( myResultImage.
isNull() )
284 qDebug() <<
"QgsRenderChecker::runTest failed - Could not load rendered image from " <<
mRenderedImageFile;
286 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n"
287 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered "
288 "Image File could not be loaded.</td></tr></table>\n";
293 QImage::Format_RGB32 );
295 myDifferenceImage.
fill( qRgb( 152, 219, 249 ) );
299 maskImagePath.
chop( 4 );
300 maskImagePath +=
"_mask.png";
302 bool hasMask = !maskImage->
isNull();
305 qDebug(
"QgsRenderChecker using mask image" );
312 unsigned int myPixelCount = myResultImage.
width() * myResultImage.
height();
316 mReport =
QString(
"<script src=\"file://%1/../renderchecker.js\"></script>\n" ).
arg( TEST_DATA_DIR );
318 mReport +=
"<tr><td colspan=2>";
320 "Test image and result image for %1<br>"
321 "Expected size: %2 w x %3 h (%4 pixels)<br>"
322 "Actual size: %5 w x %6 h (%7 pixels)"
326 .
arg( myResultImage.
width() ).arg( myResultImage.
height() ).arg( myPixelCount );
328 "Expected Duration : <= %1 (0 indicates not specified)<br>"
329 "Actual Duration : %2 ms<br></td></tr>" )
330 .
arg( mElapsedTimeTarget )
336 if ( ! myExpectedImage.
isNull() )
338 imgWidth = qMin( myExpectedImage.
width(), imgWidth );
339 imgHeight = myExpectedImage.
height() * imgWidth / myExpectedImage.
width();
344 "<td colspan=2>Compare actual and expected result</td>"
345 "<td>Difference (all blue is good, any red is bad)</td>"
347 "<td colspan=2 id=\"td-%1-%7\"></td>\n"
348 "<td align=center><img width=%5 height=%6 src=\"file://%2\"></td>\n"
351 "<script>\naddComparison(\"td-%1-%7\",\"file://%3\",\"file://%4\",%5,%6);\n</script>\n" )
353 .
arg( myDiffImageFile )
356 .
arg( imgWidth ).
arg( imgHeight )
360 if ( !mControlPathPrefix.
isNull() )
362 prefix =
QString(
" (prefix %1)" ).
arg( mControlPathPrefix );
374 qDebug(
"Expected size: %dw x %dh", myExpectedImage.
width(), myExpectedImage.
height() );
375 qDebug(
"Actual size: %dw x %dh", myResultImage.
width(), myResultImage.
height() );
379 qDebug(
"Test image and result image for %s are different dimensions", theTestName.
toLocal8Bit().
constData() );
381 if ( qAbs( myExpectedImage.
width() - myResultImage.
width() ) > mMaxSizeDifferenceX ||
382 qAbs( myExpectedImage.
height() - myResultImage.
height() ) > mMaxSizeDifferenceY )
384 mReport +=
"<tr><td colspan=3>";
385 mReport +=
"<font color=red>Expected image and result image for " + theTestName +
" are different dimensions - FAILING!</font>";
393 mReport +=
"<tr><td colspan=3>";
394 mReport +=
"Expected image and result image for " + theTestName +
" are different dimensions, but within tolerance";
404 int maxHeight = qMin( myExpectedImage.
height(), myResultImage.
height() );
405 int maxWidth = qMin( myExpectedImage.
width(), myResultImage.
width() );
408 int colorTolerance = ( int ) mColorTolerance;
409 for (
int y = 0; y < maxHeight; ++y )
411 const QRgb* expectedScanline = (
const QRgb* )myExpectedImage.
constScanLine( y );
412 const QRgb* resultScanline = (
const QRgb* )myResultImage.
constScanLine( y );
413 const QRgb* maskScanline = hasMask ? (
const QRgb* )maskImage->
constScanLine( y ) : 0;
414 QRgb* diffScanline = ( QRgb* )myDifferenceImage.scanLine( y );
416 for (
int x = 0; x < maxWidth; ++x )
418 int maskTolerance = hasMask ? qRed( maskScanline[ x ] ) : 0;
419 int pixelTolerance = qMax( colorTolerance, maskTolerance );
420 if ( pixelTolerance == 255 )
426 QRgb myExpectedPixel = expectedScanline[x];
427 QRgb myActualPixel = resultScanline[x];
428 if ( pixelTolerance == 0 )
430 if ( myExpectedPixel != myActualPixel )
433 diffScanline[ x ] = qRgb( 255, 0, 0 );
438 if ( qAbs( qRed( myExpectedPixel ) - qRed( myActualPixel ) ) > pixelTolerance ||
439 qAbs( qGreen( myExpectedPixel ) - qGreen( myActualPixel ) ) > pixelTolerance ||
440 qAbs( qBlue( myExpectedPixel ) - qBlue( myActualPixel ) ) > pixelTolerance ||
441 qAbs( qAlpha( myExpectedPixel ) - qAlpha( myActualPixel ) ) > pixelTolerance )
444 diffScanline[ x ] = qRgb( 255, 0, 0 );
452 myDifferenceImage.save( myDiffImageFile );
459 qDebug(
"%d/%d pixels mismatched (%d allowed)", mMismatchCount,
mMatchTarget, theMismatchCount );
464 mReport +=
QString(
"<tr><td colspan=3>%1/%2 pixels mismatched (allowed threshold: %3, allowed color component tolerance: %4)</td></tr>" )
472 if ( mMismatchCount <= theMismatchCount )
474 mReport +=
"<tr><td colspan = 3>\n";
475 mReport +=
"Test image and result image for " + theTestName +
" are matched<br>";
477 if ( mElapsedTimeTarget != 0 && mElapsedTimeTarget <
mElapsedTime )
480 qDebug(
"Test failed because render step took too long" );
481 mReport +=
"<tr><td colspan = 3>\n";
482 mReport +=
"<font color=red>Test failed because render step took too long</font>";
495 if ( myAnomalyMatchFlag )
497 mReport +=
"<tr><td colspan=3>"
498 "Difference image matched a known anomaly - passing test! "
503 mReport +=
"<tr><td colspan=3></td></tr>";
504 emitDashMessage(
"Image mismatch",
QgsDartMeasurement::Text,
"Difference image did not match any known anomaly or mask."
505 " If you feel the difference image should be considered an anomaly "
506 "you can do something like this\n"
508 "/\nIf it should be included in the mask run\n"
511 mReport +=
"<tr><td colspan = 3>\n";
512 mReport +=
"<font color=red>Test image and result image for " + theTestName +
" are mismatched</font><br>";
const QgsMapSettings & mapSettings()
bridge to QgsMapSettings
A rectangle specified with double values.
void setDotsPerMeterX(int x)
void setDotsPerMeterY(int y)
bool load(QIODevice *device, const char *format)
virtual void start() override
Start the rendering job and immediately return.
QString & fill(QChar ch, int size)
void fillRect(const QRectF &rectangle, const QBrush &brush)
const uchar * constScanLine(int i) const
void setRenderHint(RenderHint hint, bool on)
void setControlName(const QString &theName)
Base directory name for the control image (with control image path suffixed) the path to the image wi...
double yMaximum() const
Get the y maximum value (top side of rectangle)
bool save(const QString &fileName, const char *format, int quality) const
const T & at(int i) const
QPixmap fromImage(const QImage &image, QFlags< Qt::ImageConversionFlag > flags)
int dotsPerMeterX() const
int dotsPerMeterY() const
Q_DECL_DEPRECATED void setMapRenderer(QgsMapRenderer *thepMapRenderer)
bool runTest(QString theTestName, unsigned int theMismatchCount=0)
Test using renderer to generate the image to be compared.
A non GUI class for rendering a map layer set onto a QPainter.
void setMapSettings(const QgsMapSettings &mapSettings)
void setFlag(Flag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
The QgsMapSettings class contains configuration for rendering of the map.
QString fromUtf8(const char *str, int size)
QString controlImagePath() const
QString imageToHash(QString theImageFile)
Get an md5 hash that uniquely identifies an image.
Enable anti-aliasin for map rendering.
const char * constData() const
double mapUnitsPerPixel() const
Return the distance in geographical coordinates that equals to one pixel in the map.
unsigned int mMatchTarget
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
QByteArray toLocal8Bit() const
QString qgsDoubleToString(const double &a, const int &precision=17)
void setTexture(const QPixmap &pixmap)
static void drawBackground(QImage *image)
Draws a checkboard pattern for image backgrounds, so that transparency is visible without requiring a...
Job implementation that renders everything sequentially in one thread.
QString & replace(int position, int n, QChar after)
QString mRenderedImageFile
void setBackgroundColor(const QColor &color)
Set the background color of the map.
bool isKnownAnomaly(QString theDiffImageFile)
Get a list of all the anomalies.
void setOutputSize(const QSize &size)
Set the size of the resulting map image.
virtual QImage renderedImage() override
Get a preview/resulting image.
QStringList entryList(QFlags< QDir::Filter > filters, QFlags< QDir::SortFlag > sort) const
QString mExpectedImageFile
QgsRectangle extent() const
Return geographical coordinates of the rectangle that should be rendered.
int indexOf(const QRegExp &rx, int from) const
bool compareImages(QString theTestName, unsigned int theMismatchCount=0, QString theRenderedImageFile="")
Test using two arbitary images (map renderer will not be used)
QByteArray toBase64() const
virtual void waitForFinished() override
Block until the job has finished.
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
double xMinimum() const
Get the x minimum value (left side of rectangle)
QImage scaled(int width, int height, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const
QByteArray toUtf8() const