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 mControlPathSuffix = theName +
'/';
65 mControlPathSuffix.
clear();
71 myImage.
load( theImageFile );
73 QBuffer myBuffer( &myByteArray );
74 myImage.
save( &myBuffer,
"PNG" );
77 myHash.addData( myImageString.
toUtf8() );
78 return myHash.result().toHex().constData();
88 mMapSettings = mapSettings;
94 uchar pixDataRGB[] = { 255, 255, 255, 255,
100 QImage img( pixDataRGB, 2, 2, 8, QImage::Format_ARGB32 );
115 QDir myDirectory =
QDir( myControlImageDir );
119 QDir::Files | QDir::NoSymLinks );
127 for (
int i = 0; i < myList.
size(); ++i )
130 mReport +=
"<tr><td colspan=3>" 131 "Checking if " + myFile +
" is a known anomaly.";
135 "Checking if anomaly %1 (hash %2)<br>" )
138 myHashMessage +=
QString(
" matches %1 (hash %2)" )
139 .
arg( theDiffImageFile,
144 mReport +=
"<tr><td colspan=3>" + myHashMessage +
"</td></tr>";
145 if ( myImageHash == myAnomalyHash )
147 mReport +=
"<tr><td colspan=3>" 148 "Anomaly found! " + myFile;
153 mReport +=
"<tr><td colspan=3>" 154 "No anomaly found! ";
161 if ( mBufferDashMessages )
162 mDashMessages << dashMessage;
173 unsigned int theMismatchCount )
177 qDebug(
"QgsRenderChecker::runTest failed - Expected Image File not set." );
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";
188 if ( myExpectedImage.
isNull() )
190 qDebug() <<
"QgsRenderChecker::runTest failed - Could not load expected image from " <<
mExpectedImageFile;
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";
226 qDebug() <<
"QgsRenderChecker::runTest failed - Could not save rendered image to " <<
mRenderedImageFile;
228 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n" 229 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered " 230 "Image File could not be saved.</td></tr></table>\n";
237 if ( wldFile.
open( QIODevice::WriteOnly ) )
242 stream <<
QString(
"%1\r\n0 \r\n0 \r\n%2\r\n%3\r\n%4\r\n" )
254 unsigned int theMismatchCount,
255 const QString& theRenderedImageFile )
259 qDebug(
"QgsRenderChecker::runTest failed - Expected Image (control) File not set." );
261 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n" 262 "<tr><td>Nothing rendered</td>\n<td>Failed because Expected " 263 "Image File not set.</td></tr></table>\n";
266 if ( ! theRenderedImageFile.
isEmpty() )
276 qDebug(
"QgsRenderChecker::runTest failed - Rendered Image File not set." );
278 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n" 279 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered " 280 "Image File not set.</td></tr></table>\n";
289 if ( myResultImage.
isNull() )
291 qDebug() <<
"QgsRenderChecker::runTest failed - Could not load rendered image from " <<
mRenderedImageFile;
293 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n" 294 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered " 295 "Image File could not be loaded.</td></tr></table>\n";
300 QImage::Format_RGB32 );
302 myDifferenceImage.
fill( qRgb( 152, 219, 249 ) );
306 maskImagePath.
chop( 4 );
307 maskImagePath +=
"_mask.png";
309 bool hasMask = !maskImage->
isNull();
312 qDebug(
"QgsRenderChecker using mask image" );
319 unsigned int myPixelCount = myResultImage.
width() * myResultImage.
height();
323 mReport =
QString(
"<script src=\"file://%1/../renderchecker.js\"></script>\n" ).
arg( TEST_DATA_DIR );
325 mReport +=
"<tr><td colspan=2>";
327 "Test image and result image for %1<br>" 328 "Expected size: %2 w x %3 h (%4 pixels)<br>" 329 "Actual size: %5 w x %6 h (%7 pixels)" 333 .
arg( myResultImage.
width() ).arg( myResultImage.
height() ).arg( myPixelCount );
335 "Expected Duration : <= %1 (0 indicates not specified)<br>" 336 "Actual Duration : %2 ms<br></td></tr>" )
337 .
arg( mElapsedTimeTarget )
343 if ( ! myExpectedImage.
isNull() )
345 imgWidth = qMin( myExpectedImage.
width(), imgWidth );
346 imgHeight = myExpectedImage.
height() * imgWidth / myExpectedImage.
width();
351 "<td colspan=2>Compare actual and expected result</td>" 352 "<td>Difference (all blue is good, any red is bad)</td>" 354 "<td colspan=2 id=\"td-%1-%7\"></td>\n" 355 "<td align=center><img width=%5 height=%6 src=\"file://%2\"></td>\n" 358 "<script>\naddComparison(\"td-%1-%7\",\"file://%3\",\"file://%4\",%5,%6);\n</script>\n" )
363 .
arg( imgWidth ).
arg( imgHeight )
367 if ( !mControlPathPrefix.
isNull() )
369 prefix =
QString(
" (prefix %1)" ).
arg( mControlPathPrefix );
381 qDebug(
"Expected size: %dw x %dh", myExpectedImage.
width(), myExpectedImage.
height() );
382 qDebug(
"Actual size: %dw x %dh", myResultImage.
width(), myResultImage.
height() );
386 qDebug(
"Test image and result image for %s are different dimensions", theTestName.
toLocal8Bit().
constData() );
388 if ( qAbs( myExpectedImage.
width() - myResultImage.
width() ) > mMaxSizeDifferenceX ||
389 qAbs( myExpectedImage.
height() - myResultImage.
height() ) > mMaxSizeDifferenceY )
391 mReport +=
"<tr><td colspan=3>";
392 mReport +=
"<font color=red>Expected image and result image for " + theTestName +
" are different dimensions - FAILING!</font>";
400 mReport +=
"<tr><td colspan=3>";
401 mReport +=
"Expected image and result image for " + theTestName +
" are different dimensions, but within tolerance";
411 int maxHeight = qMin( myExpectedImage.
height(), myResultImage.
height() );
412 int maxWidth = qMin( myExpectedImage.
width(), myResultImage.
width() );
415 int colorTolerance =
static_cast< int >( mColorTolerance );
416 for (
int y = 0; y < maxHeight; ++y )
418 const QRgb* expectedScanline =
reinterpret_cast< const QRgb*
>( myExpectedImage.
constScanLine( y ) );
419 const QRgb* resultScanline =
reinterpret_cast< const QRgb*
>( myResultImage.
constScanLine( y ) );
420 const QRgb* maskScanline = hasMask ?
reinterpret_cast< const QRgb*
>( maskImage->
constScanLine( y ) ) :
nullptr;
421 QRgb* diffScanline =
reinterpret_cast< QRgb*
>( myDifferenceImage.scanLine( y ) );
423 for (
int x = 0; x < maxWidth; ++x )
425 int maskTolerance = hasMask ? qRed( maskScanline[ x ] ) : 0;
426 int pixelTolerance = qMax( colorTolerance, maskTolerance );
427 if ( pixelTolerance == 255 )
433 QRgb myExpectedPixel = expectedScanline[x];
434 QRgb myActualPixel = resultScanline[x];
435 if ( pixelTolerance == 0 )
437 if ( myExpectedPixel != myActualPixel )
440 diffScanline[ x ] = qRgb( 255, 0, 0 );
445 if ( qAbs( qRed( myExpectedPixel ) - qRed( myActualPixel ) ) > pixelTolerance ||
446 qAbs( qGreen( myExpectedPixel ) - qGreen( myActualPixel ) ) > pixelTolerance ||
447 qAbs( qBlue( myExpectedPixel ) - qBlue( myActualPixel ) ) > pixelTolerance ||
448 qAbs( qAlpha( myExpectedPixel ) - qAlpha( myActualPixel ) ) > pixelTolerance )
451 diffScanline[ x ] = qRgb( 255, 0, 0 );
459 myDifferenceImage.save( myDiffImageFile );
466 qDebug(
"%d/%d pixels mismatched (%d allowed)", mMismatchCount,
mMatchTarget, theMismatchCount );
471 mReport +=
QString(
"<tr><td colspan=3>%1/%2 pixels mismatched (allowed threshold: %3, allowed color component tolerance: %4)</td></tr>" )
479 if ( mMismatchCount <= theMismatchCount )
481 mReport +=
"<tr><td colspan = 3>\n";
482 mReport +=
"Test image and result image for " + theTestName +
" are matched<br>";
484 if ( mElapsedTimeTarget != 0 && mElapsedTimeTarget <
mElapsedTime )
487 qDebug(
"Test failed because render step took too long" );
488 mReport +=
"<tr><td colspan = 3>\n";
489 mReport +=
"<font color=red>Test failed because render step took too long</font>";
502 if ( myAnomalyMatchFlag )
504 mReport +=
"<tr><td colspan=3>" 505 "Difference image matched a known anomaly - passing test! " 510 mReport +=
"<tr><td colspan=3></td></tr>";
511 emitDashMessage(
"Image mismatch",
QgsDartMeasurement::Text,
"Difference image did not match any known anomaly or mask." 512 " If you feel the difference image should be considered an anomaly " 513 "you can do something like this\n" 515 "/\nIf it should be included in the mask run\n" 518 mReport +=
"<tr><td colspan = 3>\n";
519 mReport +=
"<font color=red>Test image and result image for " + theTestName +
" are mismatched</font><br>";
const QgsMapSettings & mapSettings()
bridge to QgsMapSettings
void setControlPathSuffix(const QString &theName)
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 imageToHash(const QString &theImageFile)
Get an md5 hash that uniquely identifies an image.
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...
bool save(const QString &fileName, const char *format, int quality) const
bool runTest(const QString &theTestName, unsigned int theMismatchCount=0)
Test using renderer to generate the image to be compared.
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)
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)
QString controlImagePath() const
The QgsMapSettings class contains configuration for rendering of the map.
bool compareImages(const QString &theTestName, unsigned int theMismatchCount=0, const QString &theRenderedImageFile="")
Test using two arbitary images (map renderer will not be used)
QgsRectangle extent() const
Return geographical coordinates of the rectangle that should be rendered.
void setOutputSize(QSize size)
Set the size of the resulting map image.
QString fromUtf8(const char *str, int size)
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
double mapUnitsPerPixel() const
Return the distance in geographical coordinates that equals to one pixel in the map.
Enable anti-aliasing for map rendering.
const char * constData() const
unsigned int mMatchTarget
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
QByteArray toLocal8Bit() const
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(const QString &theDiffImageFile)
Get a list of all the anomalies.
virtual QImage renderedImage() override
Get a preview/resulting image.
QStringList entryList(QFlags< QDir::Filter > filters, QFlags< QDir::SortFlag > sort) const
QString mExpectedImageFile
double xMinimum() const
Get the x minimum value (left side of rectangle)
double yMaximum() const
Get the y maximum value (top side of rectangle)
int indexOf(const QRegExp &rx, int from) const
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
QImage scaled(int width, int height, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const
QByteArray toUtf8() const