libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

codec_comparison_window.cc (11211B)


      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/comparison_viewer/codec_comparison_window.h"
      7 
      8 #include <stdlib.h>
      9 
     10 #include <QCollator>
     11 #include <QComboBox>
     12 #include <QDir>
     13 #include <QFileInfo>
     14 #include <QFlags>
     15 #include <QIcon>
     16 #include <QImage>
     17 #include <QImageReader>
     18 #include <QLabel>
     19 #include <QList>
     20 #include <QMap>
     21 #include <QString>
     22 #include <QStringList>
     23 #include <QtConcurrent>
     24 #include <algorithm>
     25 #include <climits>
     26 #include <functional>
     27 #include <utility>
     28 
     29 #include "lib/extras/codec.h"
     30 #include "tools/comparison_viewer/image_loading.h"
     31 #include "tools/comparison_viewer/split_image_view.h"
     32 #include "tools/icc_detect/icc_detect.h"
     33 
     34 namespace jpegxl {
     35 namespace tools {
     36 
     37 static constexpr char kPngSuffix[] = "png";
     38 
     39 namespace {
     40 
     41 QVector<QPair<QComboBox*, QString>> currentCodecSelection(
     42     const Ui::CodecComparisonWindow& ui) {
     43   QVector<QPair<QComboBox*, QString>> result;
     44   for (QComboBox* const comboBox :
     45        {ui.codec1ComboBox, ui.codec2ComboBox, ui.compressionLevel1ComboBox,
     46         ui.compressionLevel2ComboBox}) {
     47     result << qMakePair(comboBox, comboBox->currentText());
     48   }
     49   return result;
     50 }
     51 
     52 void restoreCodecSelection(
     53     const QVector<QPair<QComboBox*, QString>>& selection) {
     54   for (const auto& comboBox : selection) {
     55     const int index = comboBox.first->findText(comboBox.second);
     56     if (index != -1) {
     57       comboBox.first->setCurrentIndex(index);
     58     }
     59   }
     60 }
     61 
     62 }  // namespace
     63 
     64 CodecComparisonWindow::CodecComparisonWindow(const QString& directory,
     65                                              const float intensityTarget,
     66                                              QWidget* const parent)
     67     : QMainWindow(parent),
     68       intensityTarget_(intensityTarget),
     69       monitorIccProfile_(GetMonitorIccProfile(this)) {
     70   ui_.setupUi(this);
     71 
     72   connect(ui_.imageSetComboBox, &QComboBox::currentTextChanged, this,
     73           &CodecComparisonWindow::handleImageSetSelection);
     74   connect(ui_.imageComboBox, &QComboBox::currentTextChanged, this,
     75           &CodecComparisonWindow::handleImageSelection);
     76 
     77   connect(ui_.codec1ComboBox, &QComboBox::currentTextChanged,
     78           [this]() { handleCodecChange(Side::LEFT); });
     79   connect(ui_.codec2ComboBox, &QComboBox::currentTextChanged,
     80           [this]() { handleCodecChange(Side::RIGHT); });
     81 
     82   connect(ui_.compressionLevel1ComboBox, &QComboBox::currentTextChanged,
     83           [this]() { updateSideImage(Side::LEFT); });
     84   connect(ui_.compressionLevel2ComboBox, &QComboBox::currentTextChanged,
     85           [this]() { updateSideImage(Side::RIGHT); });
     86 
     87   connect(ui_.match1Label, &QLabel::linkActivated,
     88           [this]() { matchSize(Side::LEFT); });
     89   connect(ui_.match2Label, &QLabel::linkActivated,
     90           [this]() { matchSize(Side::RIGHT); });
     91 
     92   connect(
     93       ui_.splitImageView, &SplitImageView::renderingModeChanged,
     94       [this](const SplitImageRenderer::RenderingMode newMode) {
     95         switch (newMode) {
     96           case SplitImageRenderer::RenderingMode::LEFT:
     97           case SplitImageRenderer::RenderingMode::RIGHT: {
     98             QString codec, compressionLevel;
     99             if (newMode == SplitImageRenderer::RenderingMode::LEFT) {
    100               codec = ui_.codec1ComboBox->currentText();
    101               compressionLevel = ui_.compressionLevel1ComboBox->currentText();
    102             } else {
    103               codec = ui_.codec2ComboBox->currentText();
    104               compressionLevel = ui_.compressionLevel2ComboBox->currentText();
    105             }
    106             ui_.renderingModeLabel->setText(tr("Currently displaying: %1 @ %2")
    107                                                 .arg(codec)
    108                                                 .arg(compressionLevel));
    109             break;
    110           }
    111 
    112           case SplitImageRenderer::RenderingMode::MIDDLE:
    113             ui_.renderingModeLabel->setText(
    114                 tr("Currently displaying the original image."));
    115             break;
    116 
    117           default:
    118             ui_.renderingModeLabel->clear();
    119             break;
    120         }
    121       });
    122 
    123   loadDirectory(directory);
    124 }
    125 
    126 void CodecComparisonWindow::handleImageSetSelection(
    127     const QString& imageSetName) {
    128   const auto selection = currentCodecSelection(ui_);
    129   {
    130     const QSignalBlocker blocker(ui_.imageComboBox);
    131     ui_.imageComboBox->clear();
    132   }
    133   const QStringList imageNames = imageSets_.value(imageSetName).keys();
    134   const std::function<QIcon(const QString&)> loadIcon =
    135       [this, &imageSetName](const QString& imageName) {
    136         return QIcon(pathToOriginalImage(imageSetName, imageName));
    137       };
    138   const QFuture<QIcon> thumbnails = QtConcurrent::mapped(imageNames, loadIcon);
    139   int i = 0;
    140   for (const QString& imageName : imageNames) {
    141     ui_.imageComboBox->addItem(thumbnails.resultAt(i), imageName);
    142     ++i;
    143   }
    144   restoreCodecSelection(selection);
    145 }
    146 
    147 void CodecComparisonWindow::handleImageSelection(const QString& imageName) {
    148   const QString imageSetName = ui_.imageSetComboBox->currentText();
    149   ui_.splitImageView->setMiddleImage(
    150       loadImage(pathToOriginalImage(imageSetName, imageName),
    151                 monitorIccProfile_, intensityTarget_));
    152 
    153   const auto selection = currentCodecSelection(ui_);
    154   QStringList codecs = imageSets_.value(imageSetName).value(imageName).keys();
    155   for (QComboBox* const codecComboBox :
    156        {ui_.codec1ComboBox, ui_.codec2ComboBox}) {
    157     {
    158       const QSignalBlocker blocker(codecComboBox);
    159       codecComboBox->clear();
    160     }
    161     codecComboBox->addItems(codecs);
    162   }
    163   restoreCodecSelection(selection);
    164 }
    165 
    166 void CodecComparisonWindow::handleCodecChange(const Side side) {
    167   const QComboBox* const codecComboBox =
    168       side == Side::LEFT ? ui_.codec1ComboBox : ui_.codec2ComboBox;
    169   QComboBox* const compressionLevelComboBox =
    170       side == Side::LEFT ? ui_.compressionLevel1ComboBox
    171                          : ui_.compressionLevel2ComboBox;
    172 
    173   QStringList compressionLevels =
    174       imageSets_.value(ui_.imageSetComboBox->currentText())
    175           .value(ui_.imageComboBox->currentText())
    176           .value(codecComboBox->currentText())
    177           .keys();
    178   QCollator collator;
    179   collator.setNumericMode(true);
    180   std::sort(compressionLevels.begin(), compressionLevels.end(), collator);
    181 
    182   {
    183     const QSignalBlocker blocker(compressionLevelComboBox);
    184     compressionLevelComboBox->clear();
    185   }
    186   compressionLevelComboBox->addItems(compressionLevels);
    187   matchSize(side);
    188 }
    189 
    190 void CodecComparisonWindow::updateSideImage(const Side side) {
    191   const ComparableImage& imageInfo = currentlySelectedImage(side);
    192   if (imageInfo.decodedImagePath.isEmpty()) return;
    193   QImage image = loadImage(imageInfo.decodedImagePath, monitorIccProfile_,
    194                            intensityTarget_);
    195   const int pixels = image.width() * image.height();
    196   QLabel* const sizeInfoLabel =
    197       side == Side::LEFT ? ui_.size1Label : ui_.size2Label;
    198   if (pixels == 0) {
    199     sizeInfoLabel->setText(tr("Empty image."));
    200   } else {
    201     const double bpp =
    202         CHAR_BIT * static_cast<double>(imageInfo.byteSize) / pixels;
    203     sizeInfoLabel->setText(tr("%L1bpp").arg(bpp));
    204   }
    205 
    206   if (side == Side::LEFT) {
    207     ui_.splitImageView->setLeftImage(std::move(image));
    208   } else {
    209     ui_.splitImageView->setRightImage(std::move(image));
    210   }
    211 }
    212 
    213 QString CodecComparisonWindow::pathToOriginalImage(
    214     const QString& imageSetName, const QString& imageName) const {
    215   return baseDirectory_.absolutePath() + "/" + imageSetName + "/" + imageName +
    216          "/original.png";
    217 }
    218 
    219 CodecComparisonWindow::ComparableImage
    220 CodecComparisonWindow::currentlySelectedImage(const Side side) const {
    221   const QComboBox* const codecComboBox =
    222       side == Side::LEFT ? ui_.codec1ComboBox : ui_.codec2ComboBox;
    223   QComboBox* const compressionLevelComboBox =
    224       side == Side::LEFT ? ui_.compressionLevel1ComboBox
    225                          : ui_.compressionLevel2ComboBox;
    226 
    227   return imageSets_.value(ui_.imageSetComboBox->currentText())
    228       .value(ui_.imageComboBox->currentText())
    229       .value(codecComboBox->currentText())
    230       .value(compressionLevelComboBox->currentText());
    231 }
    232 
    233 void CodecComparisonWindow::matchSize(const Side side) {
    234   const Side otherSide = (side == Side::LEFT ? Side::RIGHT : Side::LEFT);
    235   const qint64 otherSideSize = currentlySelectedImage(otherSide).byteSize;
    236   if (otherSideSize == 0) return;
    237 
    238   const QComboBox* const codecComboBox =
    239       side == Side::LEFT ? ui_.codec1ComboBox : ui_.codec2ComboBox;
    240   QComboBox* const compressionLevelComboBox =
    241       side == Side::LEFT ? ui_.compressionLevel1ComboBox
    242                          : ui_.compressionLevel2ComboBox;
    243   const Codec codec = imageSets_.value(ui_.imageSetComboBox->currentText())
    244                           .value(ui_.imageComboBox->currentText())
    245                           .value(codecComboBox->currentText());
    246   if (codec.empty()) return;
    247   Codec::ConstIterator bestMatch = codec.begin();
    248   for (auto it = codec.begin(); it != codec.end(); ++it) {
    249     if (std::abs(it->byteSize - otherSideSize) <
    250         std::abs(bestMatch->byteSize - otherSideSize)) {
    251       bestMatch = it;
    252     }
    253   }
    254   compressionLevelComboBox->setCurrentText(bestMatch.key());
    255 }
    256 
    257 void CodecComparisonWindow::loadDirectory(const QString& directory) {
    258   baseDirectory_.setPath(directory);
    259   baseDirectory_.makeAbsolute();
    260   imageSets_.clear();
    261   visited_.clear();
    262 
    263   browseDirectory(directory);
    264 
    265   {
    266     const QSignalBlocker blocker(ui_.imageSetComboBox);
    267     ui_.imageSetComboBox->clear();
    268   }
    269   ui_.imageSetComboBox->addItems(imageSets_.keys());
    270 }
    271 
    272 void CodecComparisonWindow::browseDirectory(const QDir& directory, int depth) {
    273   for (const QFileInfo& subdirectory : directory.entryInfoList(
    274            QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks)) {
    275     if (visited_.contains(subdirectory.absoluteFilePath())) continue;
    276     visited_.insert(subdirectory.absoluteFilePath());
    277     browseDirectory(subdirectory.absoluteFilePath(), depth + 1);
    278   }
    279 
    280   // Need at least image_name/codec_name/file.
    281   if (depth < 2) return;
    282 
    283   for (const QFileInfo& file : directory.entryInfoList(QDir::Files)) {
    284     if (file.suffix() == kPngSuffix) continue;
    285     QString decodedImage;
    286     if (canLoadImageWithExtension(file.suffix())) {
    287       decodedImage = file.absoluteFilePath();
    288     } else {
    289       QFileInfo png(file.absolutePath() + "/" + file.completeBaseName() + "." +
    290                     kPngSuffix);
    291       if (png.exists()) {
    292         decodedImage = png.absoluteFilePath();
    293       }
    294     }
    295 
    296     if (decodedImage.isEmpty()) continue;
    297 
    298     const QString codec = file.absoluteDir().dirName();
    299     QDir imageDirectory = file.absoluteDir();
    300     if (!imageDirectory.cdUp()) return;
    301     const QString imageName = imageDirectory.dirName();
    302     QDir imageSetDirectory = imageDirectory;
    303     if (!imageSetDirectory.cdUp()) return;
    304     QString imageSetPath =
    305         baseDirectory_.relativeFilePath(imageSetDirectory.absolutePath());
    306     if (imageSetPath.isEmpty()) {
    307       imageSetPath = ".";
    308     }
    309 
    310     ComparableImage& image =
    311         imageSets_[imageSetPath][imageName][codec][file.completeBaseName()];
    312     image.decodedImagePath = decodedImage;
    313     image.byteSize = file.size();
    314   }
    315 }
    316 
    317 }  // namespace tools
    318 }  // namespace jpegxl