25 #include <QCryptographicHash> 34 , mRenderedImageFile(
"" )
35 , mExpectedImageFile(
"" )
37 , mColorTolerance( 0 )
38 , mElapsedTimeTarget( 0 )
39 , mBufferDashMessages( false )
45 QString myDataDir( TEST_DATA_DIR );
46 QString myControlImageDir = myDataDir + QDir::separator() +
"control_images" +
47 QDir::separator() + mControlPathPrefix;
48 return myControlImageDir;
53 mControlName = theName;
61 myImage.load( theImageFile );
62 QByteArray myByteArray;
63 QBuffer myBuffer( &myByteArray );
64 myImage.save( &myBuffer,
"PNG" );
65 QString myImageString = QString::fromUtf8( myByteArray.toBase64().data() );
66 QCryptographicHash myHash( QCryptographicHash::Md5 );
67 myHash.addData( myImageString.toUtf8() );
68 return myHash.result().toHex().constData();
78 mMapSettings = mapSettings;
84 uchar pixDataRGB[] = { 255, 255, 255, 255,
90 QImage img( pixDataRGB, 2, 2, 8, QImage::Format_ARGB32 );
91 QPixmap pix = QPixmap::fromImage( img.scaled( 20, 20 ) );
95 brush.setTexture( pix );
97 p.setRenderHint( QPainter::Antialiasing,
false );
98 p.fillRect( QRect( 0, 0, image->width(), image->height() ), brush );
106 QDir myDirectory = QDir( myControlImageDir );
108 QString myFilename =
"*";
109 myList = myDirectory.entryList( QStringList( myFilename ),
110 QDir::Files | QDir::NoSymLinks );
115 QString myImageHash =
imageToHash( theDiffImageFile );
118 for (
int i = 0; i < myList.size(); ++i )
120 QString myFile = myList.at( i );
121 mReport +=
"<tr><td colspan=3>" 122 "Checking if " + myFile +
" is a known anomaly.";
125 + QDir::separator() + myFile );
126 QString myHashMessage = QString(
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";
189 mMatchTarget = myExpectedImage.width() * myExpectedImage.height();
195 mMapSettings.
setOutputSize( QSize( myExpectedImage.width(), myExpectedImage.height() ) );
213 theTestName +
"_result.png";
215 myImage.setDotsPerMeterX( myExpectedImage.dotsPerMeterX() );
216 myImage.setDotsPerMeterY( myExpectedImage.dotsPerMeterY() );
219 qDebug() <<
"QgsRenderChecker::runTest failed - Could not save rendered image to " <<
mRenderedImageFile;
221 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n" 222 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered " 223 "Image File could not be saved.</td></tr></table>\n";
229 QFile wldFile( QDir::tempPath() + QDir::separator() + theTestName +
"_result.wld" );
230 if ( wldFile.open( QIODevice::WriteOnly ) )
234 QTextStream stream( &wldFile );
235 stream << QString(
"%1\r\n0 \r\n0 \r\n%2\r\n%3\r\n%4\r\n" )
247 unsigned int theMismatchCount,
248 QString theRenderedImageFile )
252 qDebug(
"QgsRenderChecker::runTest failed - Expected Image (control) File not set." );
254 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n" 255 "<tr><td>Nothing rendered</td>\n<td>Failed because Expected " 256 "Image File not set.</td></tr></table>\n";
259 if ( ! theRenderedImageFile.isEmpty() )
265 qDebug(
"QgsRenderChecker::runTest failed - Rendered Image File not set." );
267 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n" 268 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered " 269 "Image File not set.</td></tr></table>\n";
277 if ( myResultImage.isNull() )
279 qDebug() <<
"QgsRenderChecker::runTest failed - Could not load rendered image from " <<
mRenderedImageFile;
281 "<tr><td>Test Result:</td><td>Expected Result:</td></tr>\n" 282 "<tr><td>Nothing rendered</td>\n<td>Failed because Rendered " 283 "Image File could not be loaded.</td></tr></table>\n";
286 QImage myDifferenceImage( myExpectedImage.width(),
287 myExpectedImage.height(),
288 QImage::Format_RGB32 );
289 QString myDiffImageFile = QDir::tempPath() +
291 theTestName +
"_result_diff.png";
292 myDifferenceImage.fill( qRgb( 152, 219, 249 ) );
296 maskImagePath.chop( 4 );
297 maskImagePath +=
"_mask.png";
298 QImage* maskImage =
new QImage( maskImagePath );
299 bool hasMask = !maskImage->isNull();
302 qDebug(
"QgsRenderChecker using mask image" );
308 mMatchTarget = myExpectedImage.width() * myExpectedImage.height();
309 unsigned int myPixelCount = myResultImage.width() * myResultImage.height();
314 mReport +=
"<tr><td colspan=2>";
315 mReport +=
"Test image and result image for " + theTestName +
"<br>" 316 "Expected size: " + QString::number( myExpectedImage.width() ).toLocal8Bit() +
"w x " +
317 QString::number( myExpectedImage.height() ).toLocal8Bit() +
"h (" +
318 QString::number(
mMatchTarget ).toLocal8Bit() +
" pixels)<br>" 319 "Actual size: " + QString::number( myResultImage.width() ).toLocal8Bit() +
"w x " +
320 QString::number( myResultImage.height() ).toLocal8Bit() +
"h (" +
321 QString::number( myPixelCount ).toLocal8Bit() +
" pixels)";
323 mReport +=
"<tr><td colspan = 2>\n";
324 mReport +=
"Expected Duration : <= " + QString::number( mElapsedTimeTarget ) +
325 "ms (0 indicates not specified)<br>";
331 if ( ! myExpectedImage.isNull() )
333 imgWidth = qMin( myExpectedImage.width(), imgWidth );
334 imgHeight = myExpectedImage.height() * imgWidth / myExpectedImage.width();
336 QString myImagesString =
"</td></tr>" 337 "<tr><td>Test Result:</td><td>Expected Result:</td><td>Difference (all blue is good, any red is bad)</td></tr>\n" 338 "<tr><td><img width=" + QString::number( imgWidth ) +
339 " height=" + QString::number( imgHeight ) +
342 "\"></td>\n<td><img width=" + QString::number( imgWidth ) +
343 " height=" + QString::number( imgHeight ) +
346 "\"></td>\n<td><img width=" + QString::number( imgWidth ) +
347 " height=" + QString::number( imgHeight ) +
350 "\"></td>\n</tr>\n</table>";
353 if ( !mControlPathPrefix.isNull() )
355 prefix = QString(
" (prefix %1)" ).arg( mControlPathPrefix );
367 qDebug(
"Expected size: %dw x %dh", myExpectedImage.width(), myExpectedImage.height() );
368 qDebug(
"Actual size: %dw x %dh", myResultImage.width(), myResultImage.height() );
372 qDebug(
"Test image and result image for %s are different - FAILING!", theTestName.toLocal8Bit().constData() );
373 mReport +=
"<tr><td colspan=3>";
374 mReport +=
"<font color=red>Expected image and result image for " + theTestName +
" are different dimensions - FAILING!</font>";
375 mReport +=
"</td></tr>";
376 mReport += myImagesString;
387 int colorTolerance = ( int ) mColorTolerance;
388 for (
int y = 0; y < myExpectedImage.height(); ++y )
390 const QRgb* expectedScanline = (
const QRgb* )myExpectedImage.constScanLine( y );
391 const QRgb* resultScanline = (
const QRgb* )myResultImage.constScanLine( y );
392 const QRgb* maskScanline = hasMask ? (
const QRgb* )maskImage->constScanLine( y ) : 0;
393 QRgb* diffScanline = ( QRgb* )myDifferenceImage.scanLine( y );
395 for (
int x = 0; x < myExpectedImage.width(); ++x )
397 int maskTolerance = hasMask ? qRed( maskScanline[ x ] ) : 0;
398 int pixelTolerance = qMax( colorTolerance, maskTolerance );
399 if ( pixelTolerance == 255 )
405 QRgb myExpectedPixel = expectedScanline[x];
406 QRgb myActualPixel = resultScanline[x];
407 if ( pixelTolerance == 0 )
409 if ( myExpectedPixel != myActualPixel )
412 diffScanline[ x ] = qRgb( 255, 0, 0 );
417 if ( qAbs( qRed( myExpectedPixel ) - qRed( myActualPixel ) ) > pixelTolerance ||
418 qAbs( qGreen( myExpectedPixel ) - qGreen( myActualPixel ) ) > pixelTolerance ||
419 qAbs( qBlue( myExpectedPixel ) - qBlue( myActualPixel ) ) > pixelTolerance ||
420 qAbs( qAlpha( myExpectedPixel ) - qAlpha( myActualPixel ) ) > pixelTolerance )
423 diffScanline[ x ] = qRgb( 255, 0, 0 );
431 myDifferenceImage.save( myDiffImageFile );
438 qDebug(
"%d/%d pixels mismatched (%d allowed)", mMismatchCount,
mMatchTarget, theMismatchCount );
443 mReport +=
"<tr><td colspan=3>" +
444 QString::number( mMismatchCount ) +
"/" +
446 " pixels mismatched (allowed threshold: " +
447 QString::number( theMismatchCount ) +
448 ", allowed color component tolerance: " +
449 QString::number( mColorTolerance ) +
")";
459 if ( myAnomalyMatchFlag )
461 mReport +=
"<tr><td colspan=3>" 462 "Difference image matched a known anomaly - passing test! " 468 mReport +=
"<tr><td colspan=3>" 470 emitDashMessage(
"No Anomalies Match",
QgsDartMeasurement::Text,
"Difference image did not match any known anomaly." 471 " If you feel the difference image should be considered an anomaly " 472 "you can do something like this\n" 473 "cp " + myDiffImageFile +
" ../tests/testdata/control_images/" + theTestName +
474 "/<imagename>.{wld,png}" );
477 if ( mMismatchCount <= theMismatchCount )
479 mReport +=
"<tr><td colspan = 3>\n";
480 mReport +=
"Test image and result image for " + theTestName +
" are matched<br>";
482 if ( mElapsedTimeTarget != 0 && mElapsedTimeTarget <
mElapsedTime )
485 qDebug(
"Test failed because render step took too long" );
486 mReport +=
"<tr><td colspan = 3>\n";
487 mReport +=
"<font color=red>Test failed because render step took too long</font>";
500 mReport +=
"<tr><td colspan = 3>\n";
501 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.
virtual void start() override
Start the rendering job and immediately return.
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)
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 controlImagePath() const
QString imageToHash(QString theImageFile)
Get an md5 hash that uniquely identifies an image.
Enable anti-aliasin for map rendering.
double mapUnitsPerPixel() const
Return the distance in geographical coordinates that equals to one pixel in the map.
unsigned int mMatchTarget
QString qgsDoubleToString(const double &a, const int &precision=17)
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 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.
QString mExpectedImageFile
QgsRectangle extent() const
Return geographical coordinates of the rectangle that should be rendered.
bool compareImages(QString theTestName, unsigned int theMismatchCount=0, QString theRenderedImageFile="")
Test using two arbitary images (map renderer will not be used)
virtual void waitForFinished() override
Block until the job has finished.
double xMinimum() const
Get the x minimum value (left side of rectangle)