viewer_window.cc (4017B)
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/viewer/viewer_window.h" 7 8 #include <QElapsedTimer> 9 #include <QFileDialog> 10 #include <QFileInfo> 11 #include <QKeyEvent> 12 #include <QMessageBox> 13 #include <QSet> 14 15 #include "tools/icc_detect/icc_detect.h" 16 #include "tools/viewer/load_jxl.h" 17 18 namespace jpegxl { 19 namespace tools { 20 21 namespace { 22 23 template <typename Output> 24 void recursivelyAddSubEntries(const QFileInfo& info, 25 QSet<QString>* const visited, 26 Output* const output) { 27 if (visited->contains(info.absoluteFilePath())) return; 28 *visited << info.absoluteFilePath(); 29 if (info.isDir()) { 30 QDir dir(info.absoluteFilePath()); 31 for (const QFileInfo& entry : dir.entryInfoList( 32 QStringList() << "*.jxl", 33 QDir::Files | QDir::AllDirs | QDir::NoDotAndDotDot)) { 34 recursivelyAddSubEntries(entry, visited, output); 35 } 36 } else { 37 *output << info.absoluteFilePath(); 38 } 39 } 40 41 } // namespace 42 43 ViewerWindow::ViewerWindow(QWidget* const parent) 44 : QMainWindow(parent), monitorProfile_(GetMonitorIccProfile(this)) { 45 ui_.setupUi(this); 46 ui_.actionOpen->setShortcut(QKeySequence::Open); 47 ui_.actionExit->setShortcut(QKeySequence::Quit); 48 } 49 50 void ViewerWindow::loadFilesAndDirectories(QStringList entries) { 51 filenames_.clear(); 52 QSet<QString> visited; 53 for (const QString& entry : entries) { 54 recursivelyAddSubEntries(QFileInfo(entry), &visited, &filenames_); 55 } 56 57 const bool several = filenames_.size() > 1; 58 ui_.actionPreviousImage->setEnabled(several); 59 ui_.actionNextImage->setEnabled(several); 60 61 currentFileIndex_ = 0; 62 refreshImage(); 63 } 64 65 void ViewerWindow::on_actionOpen_triggered() { 66 QFileDialog dialog(this, tr("Select JPEG XL files to open…")); 67 dialog.setFileMode(QFileDialog::ExistingFiles); 68 dialog.setNameFilter(tr("JPEG XL images (*.jxl);;All files (*)")); 69 if (dialog.exec()) { 70 loadFilesAndDirectories(dialog.selectedFiles()); 71 } 72 } 73 74 void ViewerWindow::on_actionPreviousImage_triggered() { 75 currentFileIndex_ = 76 (currentFileIndex_ - 1 + filenames_.size()) % filenames_.size(); 77 refreshImage(); 78 } 79 80 void ViewerWindow::on_actionNextImage_triggered() { 81 currentFileIndex_ = (currentFileIndex_ + 1) % filenames_.size(); 82 refreshImage(); 83 } 84 85 void ViewerWindow::refreshImage() { 86 if (currentFileIndex_ < 0 || currentFileIndex_ >= filenames_.size()) { 87 return; 88 } 89 90 qint64 elapsed_ns; 91 bool usedRequestedProfile; 92 const QImage image = 93 loadJxlImage(filenames_[currentFileIndex_], monitorProfile_, &elapsed_ns, 94 &usedRequestedProfile); 95 if (image.isNull()) { 96 const QString message = 97 tr("Failed to load \"%1\".").arg(filenames_[currentFileIndex_]); 98 ui_.image->clear(); 99 ui_.statusBar->showMessage(message); 100 QMessageBox errorDialog(this); 101 errorDialog.setIcon(QMessageBox::Critical); 102 errorDialog.setWindowTitle(tr("Failed to load image")); 103 errorDialog.setText(message); 104 errorDialog.exec(); 105 return; 106 } 107 108 ui_.image->setPixmap(QPixmap::fromImage(image)); 109 ui_.statusBar->showMessage( 110 tr("Loaded image %L1/%L2 (%3, %4×%5) in %L6ms (%L7 fps)") 111 .arg(currentFileIndex_ + 1) 112 .arg(filenames_.size()) 113 .arg(filenames_[currentFileIndex_]) 114 .arg(image.width()) 115 .arg(image.height()) 116 .arg(elapsed_ns / 1e6) 117 .arg(1e9 / elapsed_ns)); 118 119 if (!usedRequestedProfile && !hasWarnedAboutMonitorProfile_) { 120 hasWarnedAboutMonitorProfile_ = true; 121 QMessageBox message(this); 122 message.setIcon(QMessageBox::Warning); 123 message.setWindowTitle(tr("No valid monitor profile found")); 124 message.setText( 125 tr("Failed to find a usable monitor profile. Images will be shown " 126 "assuming that the monitor's colorspace is sRGB.")); 127 message.exec(); 128 } 129 } 130 131 } // namespace tools 132 } // namespace jpegxl