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>" )
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" )
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 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...
double yMaximum() const
Get the y maximum value (top side of rectangle)
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)
The QgsMapSettings class contains configuration for rendering of the map.
QString fromUtf8(const char *str, int size)
QString controlImagePath() const
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
bool compareImages(const QString &theTestName, unsigned int theMismatchCount=0, QString theRenderedImageFile="")
Test using two arbitary images (map renderer will not be used)
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(const 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
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