| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the test suite of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | 
| 21 | ** included in the packaging of this file. Please review the following | 
| 22 | ** information to ensure the GNU General Public License requirements will | 
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. | 
| 24 | ** | 
| 25 | ** $QT_END_LICENSE$ | 
| 26 | ** | 
| 27 | ****************************************************************************/ | 
| 28 |  | 
| 29 | #include "paintcommands.h" | 
| 30 | #include <qbaselinetest.h> | 
| 31 | #include <QDir> | 
| 32 | #include <QPainter> | 
| 33 | #include <QPdfWriter> | 
| 34 | #include <QTemporaryFile> | 
| 35 | #include <QProcess> | 
| 36 |  | 
| 37 | #ifndef QT_NO_OPENGL | 
| 38 | #include <QOpenGLFramebufferObjectFormat> | 
| 39 | #include <QOpenGLContext> | 
| 40 | #include <QOpenGLPaintDevice> | 
| 41 | #endif | 
| 42 |  | 
| 43 | #include <algorithm> | 
| 44 |  | 
| 45 | #ifndef GL_RGB10 | 
| 46 | #define GL_RGB10                          0x8052 | 
| 47 | #endif | 
| 48 |  | 
| 49 | class tst_Lancelot : public QObject | 
| 50 | { | 
| 51 | Q_OBJECT | 
| 52 |  | 
| 53 | public: | 
| 54 |     tst_Lancelot(); | 
| 55 |  | 
| 56 | private: | 
| 57 |     enum GraphicsEngine { | 
| 58 |         Raster = 0, | 
| 59 |         OpenGL = 1, | 
| 60 |         Pdf = 2 | 
| 61 |     }; | 
| 62 |  | 
| 63 |     void setupTestSuite(const QStringList& blacklist = QStringList()); | 
| 64 |     void runTestSuite(GraphicsEngine engine, QImage::Format format, const QSurfaceFormat &contextFormat = QSurfaceFormat()); | 
| 65 |     void paint(QPaintDevice *device, GraphicsEngine engine, QImage::Format format, const QStringList &script, const QString &filePath); | 
| 66 |  | 
| 67 |     QStringList qpsFiles; | 
| 68 |     QHash<QString, QStringList> scripts; | 
| 69 |     QHash<QString, quint16> scriptChecksums; | 
| 70 |     QString scriptsDir; | 
| 71 |  | 
| 72 | private slots: | 
| 73 |     void initTestCase(); | 
| 74 |     void cleanupTestCase() {} | 
| 75 |  | 
| 76 |     void testRasterARGB32PM_data(); | 
| 77 |     void testRasterARGB32PM(); | 
| 78 |     void testRasterRGB32_data(); | 
| 79 |     void testRasterRGB32(); | 
| 80 |     void testRasterARGB32_data(); | 
| 81 |     void testRasterARGB32(); | 
| 82 |     void testRasterRGB16_data(); | 
| 83 |     void testRasterRGB16(); | 
| 84 |     void testRasterA2RGB30PM_data(); | 
| 85 |     void testRasterA2RGB30PM(); | 
| 86 |     void testRasterBGR30_data(); | 
| 87 |     void testRasterBGR30(); | 
| 88 |     void testRasterARGB8565PM_data(); | 
| 89 |     void testRasterARGB8565PM(); | 
| 90 |     void testRasterGrayscale8_data(); | 
| 91 |     void testRasterGrayscale8(); | 
| 92 |     void testRasterRGBA64PM_data(); | 
| 93 |     void testRasterRGBA64PM(); | 
| 94 |  | 
| 95 |     void testPdf_data(); | 
| 96 |     void testPdf(); | 
| 97 |  | 
| 98 | #ifndef QT_NO_OPENGL | 
| 99 |     void testOpenGL_data(); | 
| 100 |     void testOpenGL(); | 
| 101 |     void testOpenGLBGR30_data(); | 
| 102 |     void testOpenGLBGR30(); | 
| 103 |     void testCoreOpenGL_data(); | 
| 104 |     void testCoreOpenGL(); | 
| 105 | private: | 
| 106 |     bool checkSystemGLSupport(); | 
| 107 |     bool checkSystemCoreGLSupport(); | 
| 108 | #endif | 
| 109 | }; | 
| 110 |  | 
| 111 | tst_Lancelot::tst_Lancelot() | 
| 112 | { | 
| 113 | } | 
| 114 |  | 
| 115 | void tst_Lancelot::initTestCase() | 
| 116 | { | 
| 117 |     // Check and setup the environment. We treat failures because of test environment | 
| 118 |     // (e.g. script files not found) as just warnings, and not QFAILs, to avoid false negatives | 
| 119 |     // caused by environment or server instability | 
| 120 |  | 
| 121 |     QByteArray msg; | 
| 122 |     if (!QBaselineTest::connectToBaselineServer(msg: &msg)) | 
| 123 |         QSKIP(msg); | 
| 124 |  | 
| 125 |     QString baseDir = QFINDTESTDATA("scripts/text.qps" ); | 
| 126 |     scriptsDir = baseDir.left(n: baseDir.lastIndexOf(c: '/')) + '/'; | 
| 127 |     QDir qpsDir(scriptsDir); | 
| 128 |     qpsFiles = qpsDir.entryList(nameFilters: QStringList() << QLatin1String("*.qps" ), filters: QDir::Files | QDir::Readable); | 
| 129 |     if (qpsFiles.isEmpty()) { | 
| 130 |         QWARN("No qps script files found in "  + qpsDir.path().toLatin1()); | 
| 131 |         QSKIP("Aborted due to errors." ); | 
| 132 |     } | 
| 133 |  | 
| 134 |     std::sort(first: qpsFiles.begin(), last: qpsFiles.end()); | 
| 135 |     foreach (const QString& fileName, qpsFiles) { | 
| 136 |         QFile file(scriptsDir + fileName); | 
| 137 |         file.open(flags: QFile::ReadOnly); | 
| 138 |         QByteArray cont = file.readAll(); | 
| 139 |         scripts.insert(akey: fileName, avalue: QString::fromUtf8(str: cont).split(sep: QLatin1Char('\n'), behavior: Qt::SkipEmptyParts)); | 
| 140 |         scriptChecksums.insert(akey: fileName, avalue: qChecksum(s: cont.constData(), len: cont.size())); | 
| 141 |     } | 
| 142 | } | 
| 143 |  | 
| 144 |  | 
| 145 | void tst_Lancelot::testRasterARGB32PM_data() | 
| 146 | { | 
| 147 |     setupTestSuite(); | 
| 148 | } | 
| 149 |  | 
| 150 |  | 
| 151 | void tst_Lancelot::testRasterARGB32PM() | 
| 152 | { | 
| 153 |     runTestSuite(engine: Raster, format: QImage::Format_ARGB32_Premultiplied); | 
| 154 | } | 
| 155 |  | 
| 156 |  | 
| 157 | void tst_Lancelot::testRasterARGB32_data() | 
| 158 | { | 
| 159 |     setupTestSuite(); | 
| 160 | } | 
| 161 |  | 
| 162 | void tst_Lancelot::testRasterARGB32() | 
| 163 | { | 
| 164 |     runTestSuite(engine: Raster, format: QImage::Format_ARGB32); | 
| 165 | } | 
| 166 |  | 
| 167 |  | 
| 168 | void tst_Lancelot::testRasterRGB32_data() | 
| 169 | { | 
| 170 |     setupTestSuite(); | 
| 171 | } | 
| 172 |  | 
| 173 |  | 
| 174 | void tst_Lancelot::testRasterRGB32() | 
| 175 | { | 
| 176 |     runTestSuite(engine: Raster, format: QImage::Format_RGB32); | 
| 177 | } | 
| 178 |  | 
| 179 |  | 
| 180 | void tst_Lancelot::testRasterRGB16_data() | 
| 181 | { | 
| 182 |     setupTestSuite(); | 
| 183 | } | 
| 184 |  | 
| 185 |  | 
| 186 | void tst_Lancelot::testRasterRGB16() | 
| 187 | { | 
| 188 |     runTestSuite(engine: Raster, format: QImage::Format_RGB16); | 
| 189 | } | 
| 190 |  | 
| 191 |  | 
| 192 | void tst_Lancelot::testRasterA2RGB30PM_data() | 
| 193 | { | 
| 194 |     setupTestSuite(); | 
| 195 | } | 
| 196 |  | 
| 197 |  | 
| 198 | void tst_Lancelot::testRasterA2RGB30PM() | 
| 199 | { | 
| 200 |     runTestSuite(engine: Raster, format: QImage::Format_A2RGB30_Premultiplied); | 
| 201 | } | 
| 202 |  | 
| 203 |  | 
| 204 | void tst_Lancelot::testRasterBGR30_data() | 
| 205 | { | 
| 206 |     setupTestSuite(); | 
| 207 | } | 
| 208 |  | 
| 209 |  | 
| 210 | void tst_Lancelot::testRasterBGR30() | 
| 211 | { | 
| 212 |     runTestSuite(engine: Raster, format: QImage::Format_BGR30); | 
| 213 | } | 
| 214 |  | 
| 215 |  | 
| 216 | void tst_Lancelot::testRasterARGB8565PM_data() | 
| 217 | { | 
| 218 |     setupTestSuite(); | 
| 219 | } | 
| 220 |  | 
| 221 | void tst_Lancelot::testRasterARGB8565PM() | 
| 222 | { | 
| 223 |     runTestSuite(engine: Raster, format: QImage::Format_ARGB8565_Premultiplied); | 
| 224 | } | 
| 225 |  | 
| 226 |  | 
| 227 | void tst_Lancelot::testRasterGrayscale8_data() | 
| 228 | { | 
| 229 |     setupTestSuite(); | 
| 230 | } | 
| 231 |  | 
| 232 | void tst_Lancelot::testRasterGrayscale8() | 
| 233 | { | 
| 234 |     runTestSuite(engine: Raster, format: QImage::Format_Grayscale8); | 
| 235 | } | 
| 236 |  | 
| 237 |  | 
| 238 | void tst_Lancelot::testRasterRGBA64PM_data() | 
| 239 | { | 
| 240 |     setupTestSuite(); | 
| 241 | } | 
| 242 |  | 
| 243 | void tst_Lancelot::testRasterRGBA64PM() | 
| 244 | { | 
| 245 |     runTestSuite(engine: Raster, format: QImage::Format_RGBA64_Premultiplied); | 
| 246 | } | 
| 247 |  | 
| 248 |  | 
| 249 | void tst_Lancelot::testPdf_data() | 
| 250 | { | 
| 251 | #ifdef Q_OS_MACOS | 
| 252 |     setupTestSuite(); | 
| 253 | #else | 
| 254 |     QSKIP("Pdf testing only implemented for macOS" ); | 
| 255 | #endif | 
| 256 | } | 
| 257 |  | 
| 258 | void tst_Lancelot::testPdf() | 
| 259 | { | 
| 260 |     runTestSuite(engine: Pdf, format: QImage::Format_RGB32); | 
| 261 | } | 
| 262 |  | 
| 263 |  | 
| 264 | #ifndef QT_NO_OPENGL | 
| 265 | bool tst_Lancelot::checkSystemGLSupport() | 
| 266 | { | 
| 267 |     QWindow win; | 
| 268 |     win.setSurfaceType(QSurface::OpenGLSurface); | 
| 269 |     win.create(); | 
| 270 |     QOpenGLFramebufferObjectFormat fmt; | 
| 271 |     fmt.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); | 
| 272 |     fmt.setSamples(4); | 
| 273 |     QOpenGLContext ctx; | 
| 274 |     if (!ctx.create() || !ctx.makeCurrent(surface: &win)) | 
| 275 |         return false; | 
| 276 |     QOpenGLFramebufferObject fbo(800, 800, fmt); | 
| 277 |     if (!fbo.isValid() || !fbo.bind()) | 
| 278 |         return false; | 
| 279 |  | 
| 280 |     return true; | 
| 281 | } | 
| 282 |  | 
| 283 | bool tst_Lancelot::checkSystemCoreGLSupport() | 
| 284 | { | 
| 285 |     if (QOpenGLContext::openGLModuleType() != QOpenGLContext::LibGL) | 
| 286 |         return false; | 
| 287 |  | 
| 288 |     QSurfaceFormat coreFormat; | 
| 289 |     coreFormat.setVersion(major: 3, minor: 2); | 
| 290 |     coreFormat.setProfile(QSurfaceFormat::CoreProfile); | 
| 291 |     QWindow win; | 
| 292 |     win.setSurfaceType(QSurface::OpenGLSurface); | 
| 293 |     win.setFormat(coreFormat); | 
| 294 |     win.create(); | 
| 295 |     QOpenGLFramebufferObjectFormat fmt; | 
| 296 |     fmt.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); | 
| 297 |     fmt.setSamples(4); | 
| 298 |     QOpenGLContext ctx; | 
| 299 |     ctx.setFormat(coreFormat); | 
| 300 |     if (!ctx.create() || !ctx.makeCurrent(surface: &win)) | 
| 301 |         return false; | 
| 302 |     QOpenGLFramebufferObject fbo(800, 800, fmt); | 
| 303 |     if (!fbo.isValid() || !fbo.bind()) | 
| 304 |         return false; | 
| 305 |  | 
| 306 |     return true; | 
| 307 | } | 
| 308 |  | 
| 309 | void tst_Lancelot::testOpenGL_data() | 
| 310 | { | 
| 311 |     if (!checkSystemGLSupport()) | 
| 312 |         QSKIP("System under test does not meet preconditions for GL testing. Skipping." ); | 
| 313 |     QStringList localBlacklist = QStringList() << QLatin1String("rasterops.qps" ); | 
| 314 |     setupTestSuite(localBlacklist); | 
| 315 | } | 
| 316 |  | 
| 317 |  | 
| 318 | void tst_Lancelot::testOpenGL() | 
| 319 | { | 
| 320 |     runTestSuite(engine: OpenGL, format: QImage::Format_RGB32); | 
| 321 | } | 
| 322 |  | 
| 323 | void tst_Lancelot::testOpenGLBGR30_data() | 
| 324 | { | 
| 325 |     tst_Lancelot::testOpenGL_data(); | 
| 326 | } | 
| 327 |  | 
| 328 | void tst_Lancelot::testOpenGLBGR30() | 
| 329 | { | 
| 330 |     runTestSuite(engine: OpenGL, format: QImage::Format_BGR30); | 
| 331 | } | 
| 332 |  | 
| 333 | void tst_Lancelot::testCoreOpenGL_data() | 
| 334 | { | 
| 335 |     if (!checkSystemCoreGLSupport()) | 
| 336 |         QSKIP("System under test does not meet preconditions for Core Profile GL testing. Skipping." ); | 
| 337 |     QStringList localBlacklist = QStringList() << QLatin1String("rasterops.qps" ); | 
| 338 |     setupTestSuite(localBlacklist); | 
| 339 | } | 
| 340 |  | 
| 341 | void tst_Lancelot::testCoreOpenGL() | 
| 342 | { | 
| 343 |     QSurfaceFormat coreFormat; | 
| 344 |     coreFormat.setVersion(major: 3, minor: 2); | 
| 345 |     coreFormat.setProfile(QSurfaceFormat::CoreProfile); | 
| 346 |     runTestSuite(engine: OpenGL, format: QImage::Format_RGB32, contextFormat: coreFormat); | 
| 347 | } | 
| 348 | #endif | 
| 349 |  | 
| 350 |  | 
| 351 | void tst_Lancelot::setupTestSuite(const QStringList& blacklist) | 
| 352 | { | 
| 353 |     QTest::addColumn<QString>(name: "qpsFile" ); | 
| 354 |     foreach (const QString &fileName, qpsFiles) { | 
| 355 |         if (blacklist.contains(str: fileName)) | 
| 356 |             continue; | 
| 357 |         QBaselineTest::newRow(dataTag: fileName.toLatin1(), checksum: scriptChecksums.value(akey: fileName)) << fileName; | 
| 358 |     } | 
| 359 | } | 
| 360 |  | 
| 361 |  | 
| 362 | void tst_Lancelot::runTestSuite(GraphicsEngine engine, QImage::Format format, const QSurfaceFormat &contextFormat) | 
| 363 | { | 
| 364 |     QFETCH(QString, qpsFile); | 
| 365 |  | 
| 366 |     QString filePath = scriptsDir + qpsFile; | 
| 367 |     QStringList script = scripts.value(akey: qpsFile); | 
| 368 |     QImage rendered; | 
| 369 |  | 
| 370 |     if (engine == Raster) { | 
| 371 |         QImage img(800, 800, format); | 
| 372 |         paint(device: &img, engine, format, script, filePath: QFileInfo(filePath).absoluteFilePath()); | 
| 373 |         rendered = img; | 
| 374 | #ifndef QT_NO_OPENGL | 
| 375 |     } else if (engine == OpenGL) { | 
| 376 |         QWindow win; | 
| 377 |         win.setSurfaceType(QSurface::OpenGLSurface); | 
| 378 |         win.setFormat(contextFormat); | 
| 379 |         win.create(); | 
| 380 |         QOpenGLFramebufferObjectFormat fmt; | 
| 381 |         fmt.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); | 
| 382 |         fmt.setSamples(4); | 
| 383 |         if (format == QImage::Format_BGR30) | 
| 384 |             fmt.setInternalTextureFormat(GL_RGB10); | 
| 385 |         QOpenGLContext ctx; | 
| 386 |         ctx.setFormat(contextFormat); | 
| 387 |         QVERIFY(ctx.create()); | 
| 388 |         QVERIFY(ctx.makeCurrent(&win)); | 
| 389 |         QOpenGLFramebufferObject fbo(800, 800, fmt); | 
| 390 |         fbo.bind(); | 
| 391 |         QOpenGLPaintDevice pdv(800, 800); | 
| 392 |         paint(device: &pdv, engine, format, script, filePath: QFileInfo(filePath).absoluteFilePath()); | 
| 393 |         rendered = fbo.toImage().convertToFormat(f: format); | 
| 394 | #endif | 
| 395 |     } else if (engine == Pdf) { | 
| 396 |         QString tempStem(QDir::tempPath() + QLatin1String("/lancelot_XXXXXX_" ) + qpsFile.chopped(n: 4)); | 
| 397 |  | 
| 398 |         QTemporaryFile pdfFile(tempStem + QLatin1String(".pdf" )); | 
| 399 |         pdfFile.open(); | 
| 400 |         QPdfWriter writer(&pdfFile); | 
| 401 |         writer.setPdfVersion(QPdfWriter::PdfVersion_1_6); | 
| 402 |         writer.setResolution(150); | 
| 403 |         paint(device: &writer, engine, format, script, filePath: QFileInfo(filePath).absoluteFilePath()); | 
| 404 |         pdfFile.close(); | 
| 405 |  | 
| 406 |         // Convert pdf to something we can read into a QImage, using macOS' sips utility | 
| 407 |         QTemporaryFile pngFile(tempStem + QLatin1String(".png" )); | 
| 408 |         pngFile.open(); // Just create the file name | 
| 409 |         pngFile.close(); | 
| 410 |         QProcess proc; | 
| 411 |         const char *rawArgs = "-s format png --cropOffset 20 20 -c 800 800 -o" ; | 
| 412 |         QStringList argList = QString::fromLatin1(str: rawArgs).split(sep: QLatin1Char(' ')); | 
| 413 |         proc.start(program: QLatin1String("sips" ), arguments: argList << pngFile.fileName() << pdfFile.fileName()); | 
| 414 |         proc.waitForFinished(msecs: 2 * 60 * 1000); // May need some time | 
| 415 |  | 
| 416 |         rendered = QImage(pngFile.fileName()); | 
| 417 |     } | 
| 418 |  | 
| 419 |     QBASELINE_TEST(rendered); | 
| 420 | } | 
| 421 |  | 
| 422 | void tst_Lancelot::paint(QPaintDevice *device, GraphicsEngine engine, QImage::Format format, const QStringList &script, const QString &filePath) | 
| 423 | { | 
| 424 |     QPainter p(device); | 
| 425 |     PaintCommands pcmd(script, 800, 800, format); | 
| 426 |     //pcmd.setShouldDrawText(false); | 
| 427 |     switch (engine) { | 
| 428 |     case OpenGL: | 
| 429 |         pcmd.setType(OpenGLBufferType); // version/profile is communicated through the context's format() | 
| 430 |         break; | 
| 431 |     case Pdf: | 
| 432 |         pcmd.setType(PdfType); | 
| 433 |         break; | 
| 434 |     case Raster:  // fallthrough | 
| 435 |     default: | 
| 436 |         pcmd.setType(ImageType); | 
| 437 |         break; | 
| 438 |     } | 
| 439 |     pcmd.setPainter(&p); | 
| 440 |     pcmd.setFilePath(filePath); | 
| 441 |     pcmd.runCommands(); | 
| 442 |     p.end(); | 
| 443 | } | 
| 444 |  | 
| 445 | #define main _realmain | 
| 446 | QTEST_MAIN(tst_Lancelot) | 
| 447 | #undef main | 
| 448 |  | 
| 449 | int main(int argc, char *argv[]) | 
| 450 | { | 
| 451 |     qSetGlobalQHashSeed(newSeed: 0);   // Avoid rendering variations caused by QHash randomization | 
| 452 |  | 
| 453 |     QBaselineTest::handleCmdLineArgs(argcp: &argc, argvp: &argv); | 
| 454 |     return _realmain(argc, argv); | 
| 455 | } | 
| 456 |  | 
| 457 | #include "tst_lancelot.moc" | 
| 458 |  |