test_window.cc (6979B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #include "tools/flicker_test/test_window.h" 7 8 #include <QDir> 9 #include <QMessageBox> 10 #include <QSet> 11 #include <algorithm> 12 #include <random> 13 14 #include "tools/icc_detect/icc_detect.h" 15 16 namespace jpegxl { 17 namespace tools { 18 19 FlickerTestWindow::FlickerTestWindow(FlickerTestParameters parameters, 20 QWidget* const parent) 21 : QMainWindow(parent), 22 monitorProfile_(GetMonitorIccProfile(this)), 23 parameters_(std::move(parameters)), 24 originalFolder_(parameters_.originalFolder, "*.png"), 25 alteredFolder_(parameters_.alteredFolder, "*.png"), 26 outputFile_(parameters_.outputFile) { 27 ui_.setupUi(this); 28 ui_.splitView->setSpacing(parameters_.spacing); 29 ui_.endLabel->setText( 30 tr("The test is complete and the results have been saved to \"%1\".") 31 .arg(parameters_.outputFile)); 32 connect(ui_.startButton, &QAbstractButton::clicked, [&] { 33 ui_.stackedView->setCurrentWidget(ui_.splitView); 34 nextImage(); 35 }); 36 connect(ui_.splitView, &SplitView::testResult, this, 37 &FlickerTestWindow::processTestResult); 38 39 if (!outputFile_.open(QIODevice::WriteOnly)) { 40 QMessageBox messageBox; 41 messageBox.setIcon(QMessageBox::Critical); 42 messageBox.setStandardButtons(QMessageBox::Close); 43 messageBox.setWindowTitle(tr("Failed to open output file")); 44 messageBox.setInformativeText( 45 tr("Could not open \"%1\" for writing.").arg(outputFile_.fileName())); 46 messageBox.exec(); 47 proceed_ = false; 48 return; 49 } 50 outputStream_.setDevice(&outputFile_); 51 outputStream_ << "image name,original side,clicked side,click delay (ms)\n"; 52 53 if (monitorProfile_.isEmpty()) { 54 QMessageBox messageBox; 55 messageBox.setIcon(QMessageBox::Warning); 56 messageBox.setStandardButtons(QMessageBox::Ok); 57 messageBox.setWindowTitle(tr("No monitor profile found")); 58 messageBox.setText( 59 tr("No ICC profile appears to be associated with the display. It will " 60 "be assumed to match sRGB.")); 61 messageBox.exec(); 62 } 63 64 originalFolder_.setFilter(QDir::Files); 65 alteredFolder_.setFilter(QDir::Files); 66 67 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) 68 auto originalImages = QSet<QString>::fromList(originalFolder_.entryList()); 69 auto alteredImages = QSet<QString>::fromList(alteredFolder_.entryList()); 70 #else 71 const QStringList originalFolderEntries = originalFolder_.entryList(); 72 QSet<QString> originalImages(originalFolderEntries.begin(), 73 originalFolderEntries.end()); 74 const QStringList alteredFolderEntries = alteredFolder_.entryList(); 75 QSet<QString> alteredImages(alteredFolderEntries.begin(), 76 alteredFolderEntries.end()); 77 #endif 78 79 auto onlyOriginal = originalImages - alteredImages; 80 auto onlyAltered = alteredImages - originalImages; 81 if (!onlyOriginal.isEmpty() || !onlyAltered.isEmpty()) { 82 QMessageBox messageBox; 83 messageBox.setIcon(QMessageBox::Warning); 84 messageBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); 85 messageBox.setWindowTitle(tr("Image set mismatch")); 86 messageBox.setText( 87 tr("A mismatch has been detected between the original and altered " 88 "images.")); 89 messageBox.setInformativeText(tr("Proceed with the test?")); 90 QStringList detailedTextParagraphs; 91 const QString itemFormat = tr("— %1\n"); 92 if (!onlyOriginal.isEmpty()) { 93 QString originalList; 94 for (const QString& original : onlyOriginal) { 95 originalList += itemFormat.arg(original); 96 } 97 detailedTextParagraphs << tr("The following images were only found in " 98 "the originals folder:\n%1") 99 .arg(originalList); 100 } 101 if (!onlyAltered.isEmpty()) { 102 QString alteredList; 103 for (const QString& altered : onlyAltered) { 104 alteredList += itemFormat.arg(altered); 105 } 106 detailedTextParagraphs << tr("The following images were only found in " 107 "the altered images folder:\n%1") 108 .arg(alteredList); 109 } 110 messageBox.setDetailedText(detailedTextParagraphs.join("\n\n")); 111 if (messageBox.exec() == QMessageBox::Cancel) { 112 proceed_ = false; 113 return; 114 } 115 } 116 117 remainingImages_ = originalImages.intersect(alteredImages).values(); 118 std::random_device rd; 119 std::mt19937 g(rd()); 120 std::shuffle(remainingImages_.begin(), remainingImages_.end(), g); 121 } 122 123 void FlickerTestWindow::processTestResult(const QString& imageName, 124 const SplitView::Side originalSide, 125 const SplitView::Side clickedSide, 126 const int clickDelayMSecs) { 127 const auto sideToString = [](const SplitView::Side side) { 128 switch (side) { 129 case SplitView::Side::kLeft: 130 return "left"; 131 132 case SplitView::Side::kRight: 133 return "right"; 134 } 135 return "unknown"; 136 }; 137 outputStream_ << imageName << "," << sideToString(originalSide) << "," 138 << sideToString(clickedSide) << "," << clickDelayMSecs << "\n"; 139 140 nextImage(); 141 } 142 143 void FlickerTestWindow::nextImage() { 144 if (remainingImages_.empty()) { 145 outputStream_.flush(); 146 ui_.stackedView->setCurrentWidget(ui_.finalPage); 147 return; 148 } 149 const QString image = remainingImages_.takeFirst(); 150 retry: 151 QImage originalImage = 152 loadImage(originalFolder_.absoluteFilePath(image), monitorProfile_, 153 parameters_.intensityTarget); 154 QImage alteredImage = loadImage(alteredFolder_.absoluteFilePath(image), 155 monitorProfile_, parameters_.intensityTarget); 156 if (originalImage.isNull() || alteredImage.isNull()) { 157 QMessageBox messageBox(this); 158 messageBox.setIcon(QMessageBox::Warning); 159 messageBox.setStandardButtons(QMessageBox::Retry | QMessageBox::Ignore | 160 QMessageBox::Abort); 161 messageBox.setWindowTitle(tr("Failed to load image")); 162 messageBox.setText(tr("Could not load image \"%1\".").arg(image)); 163 switch (messageBox.exec()) { 164 case QMessageBox::Retry: 165 goto retry; 166 167 case QMessageBox::Ignore: 168 outputStream_ << image << ",,,\n"; 169 nextImage(); 170 return; 171 172 case QMessageBox::Abort: 173 ui_.stackedView->setCurrentWidget(ui_.finalPage); 174 return; 175 } 176 } 177 178 ui_.splitView->setOriginalImage(std::move(originalImage)); 179 ui_.splitView->setAlteredImage(std::move(alteredImage)); 180 ui_.splitView->startTest( 181 image, parameters_.blankingTimeMSecs, parameters_.viewingTimeSecs, 182 parameters_.advanceTimeMSecs, parameters_.gray, 183 parameters_.grayFadingTimeMSecs, parameters_.grayTimeMSecs); 184 } 185 186 } // namespace tools 187 } // namespace jpegxl