documentviewer: Add simple image viewer

Since qtbase/b8f588bea74aae0a890e1af18b936b0bfbf8c237
removed the QtWidgets image viewer example, there
is no way to exercise the image plugins.
Re-add the code as documentviewer plugin.

Pick-to: 6.9
Change-Id: I0eb26d02a9c11cdb0fce315ffc6f7c662e98c924
Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
This commit is contained in:
Friedemann Kleint 2025-01-23 13:26:29 +01:00
parent c2c840647a
commit 09bc4a6861
6 changed files with 330 additions and 0 deletions

View File

@ -142,6 +142,23 @@
It supports editing text files, copy/cut and paste, printing, and
saving changes.
\section1 ImageViewer class
\c ImageViewer displays images as supported by QImageReader, using
a QLabel.
In the constructor, we increase the allocation limit of QImageReader to
allow for larger photos:
\snippet demos/documentviewer/plugins/imageviewer/imageviewer.cpp init
In the openFile() function, we load the image and determine its size.
If it is larger than the screen, we downscale it to screen size,
maintaining the aspect ratio. This calculation has to be done in native
pixels, and the device pixel ratio needs to be set on the resulting pixmap
for it to appear crisp:
\snippet demos/documentviewer/plugins/imageviewer/imageviewer.cpp open
\section1 JsonViewer class

View File

@ -1,6 +1,7 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
add_subdirectory(imageviewer)
add_subdirectory(jsonviewer)
add_subdirectory(txtviewer)

View File

@ -0,0 +1,41 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets
OPTIONAL_COMPONENTS PrintSupport)
qt_add_plugin(imageviewer
CLASS_NAME ImageViewer
imageviewer.cpp imageviewer.h
)
set_target_properties(imageviewer PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/app"
)
target_include_directories(imageviewer PRIVATE
../../app
)
target_link_libraries(imageviewer PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
abstractviewer
)
if(TARGET Qt6::PrintSupport)
target_link_libraries(imageviewer PRIVATE Qt6::PrintSupport)
endif()
if(WIN32)
set(install_destination "${CMAKE_INSTALL_BINDIR}/app")
elseif(APPLE)
set(install_destination ".")
else()
set(install_destination "${CMAKE_INSTALL_BINDIR}")
endif()
install(TARGETS imageviewer
RUNTIME DESTINATION "${install_destination}"
LIBRARY DESTINATION "${install_destination}"
)

View File

@ -0,0 +1,207 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "imageviewer.h"
#include <QFileDialog>
#include <QLabel>
#include <QMainWindow>
#include <QToolBar>
#include <QColorSpace>
#include <QGuiApplication>
#include <QIcon>
#include <QImageReader>
#include <QKeySequence>
#include <QPainter>
#include <QPixmap>
#include <QScreen>
#include <QDir>
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
# include <QPrinter>
#endif
using namespace Qt::StringLiterals;
static QStringList imageFormats()
{
QStringList result;
const QByteArrayList &allFormats = QImageReader::supportedImageFormats();
for (const auto &format : allFormats) {
if (format != "tif" && format != "cur") // duplicate/non-existent
result.append("image/"_L1 + QLatin1StringView(format));
}
return result;
}
static QString msgOpen(const QString &name, const QImage &image)
{
const QString description = image.colorSpace().isValid()
? image.colorSpace().description() : ImageViewer::tr("unknown");
return ImageViewer::tr("Opened \"%1\", %2x%3, Depth: %4 (%5)")
.arg(QDir::toNativeSeparators(name))
.arg(image.width()).arg(image.height())
.arg(image.depth()).arg(description);
}
//! [init]
ImageViewer::ImageViewer() : m_formats(imageFormats())
{
connect(this, &AbstractViewer::uiInitialized, this, &ImageViewer::setupImageUi);
QImageReader::setAllocationLimit(1024); // MB
}
//! [init]
ImageViewer::~ImageViewer() = default;
void ImageViewer::init(QFile *file, QWidget *parent, QMainWindow *mainWindow)
{
m_imageLabel = new QLabel(parent);
m_imageLabel->setFrameShape(QFrame::Box);
m_imageLabel->setAlignment(Qt::AlignCenter);
m_imageLabel->setScaledContents(true);
AbstractViewer::init(file, m_imageLabel, mainWindow);
m_toolBar = addToolBar(tr("Images"));
m_zoomInAct = m_toolBar->addAction(tr("Zoom &In"), this, &ImageViewer::zoomIn);
m_zoomInAct->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ZoomIn));
m_zoomInAct->setShortcut(QKeySequence::ZoomIn);
m_zoomOutAct = m_toolBar->addAction(tr("Zoom &Out"), this, &ImageViewer::zoomOut);
m_zoomOutAct->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ZoomOut));
m_zoomOutAct->setShortcut(QKeySequence::ZoomOut);
m_resetZoomAct = m_toolBar->addAction(tr("Reset Zoom"), this, &ImageViewer::resetZoom);
m_resetZoomAct->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ZoomFitBest));
m_resetZoomAct->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_0));
}
QStringList ImageViewer::supportedMimeTypes() const
{
return m_formats;
}
void ImageViewer::clear()
{
m_imageLabel->setPixmap({});
m_maxScaleFactor = m_minScaleFactor = m_initialScaleFactor = m_scaleFactor = 1;
}
void ImageViewer::setupImageUi()
{
openFile();
}
//! [open]
void ImageViewer::openFile()
{
#if QT_CONFIG(cursor)
QGuiApplication::setOverrideCursor(Qt::WaitCursor);
#endif
const QString name = m_file->fileName();
QImageReader reader(name);
const QImage origImage = reader.read();
if (origImage.isNull()) {
statusMessage(tr("Cannot read file %1:\n%2.")
.arg(QDir::toNativeSeparators(name),
reader.errorString()), tr("open"));
disablePrinting();
#if QT_CONFIG(cursor)
QGuiApplication::restoreOverrideCursor();
#endif
return;
}
clear();
QImage image = origImage.colorSpace().isValid()
? origImage.convertedToColorSpace(QColorSpace::SRgb)
: origImage;
const auto devicePixelRatio = m_imageLabel->devicePixelRatioF();
m_imageSize = QSizeF(image.size()) / devicePixelRatio;
QPixmap pixmap = QPixmap::fromImage(image);
pixmap.setDevicePixelRatio(devicePixelRatio);
m_imageLabel->setPixmap(pixmap);
const QSizeF targetSize = m_imageLabel->parentWidget()->size();
if (m_imageSize.width() > targetSize.width()
|| m_imageSize.height() > targetSize.height()) {
m_initialScaleFactor = qMin(targetSize.width() / m_imageSize.width(),
targetSize.height() / m_imageSize.height());
}
m_maxScaleFactor = 3 * m_initialScaleFactor;
m_minScaleFactor = m_initialScaleFactor / 3;
doSetScaleFactor(m_initialScaleFactor);
statusMessage(msgOpen(name, origImage));
#if QT_CONFIG(cursor)
QGuiApplication::restoreOverrideCursor();
#endif
maybeEnablePrinting();
}
//! [open]
void ImageViewer::setScaleFactor(qreal scaleFactor)
{
if (!qFuzzyCompare(m_scaleFactor, scaleFactor))
doSetScaleFactor(scaleFactor);
}
void ImageViewer::doSetScaleFactor(qreal scaleFactor)
{
m_scaleFactor = scaleFactor;
const QSize labelSize = (m_imageSize * m_scaleFactor).toSize();
m_imageLabel->setFixedSize(labelSize);
enableZoomActions();
}
void ImageViewer::zoomIn()
{
setScaleFactor(m_scaleFactor * 1.25);
}
void ImageViewer::zoomOut()
{
setScaleFactor(m_scaleFactor * 0.8);
}
void ImageViewer::resetZoom()
{
setScaleFactor(m_initialScaleFactor);
}
bool ImageViewer::hasContent() const
{
return !m_imageLabel->pixmap().isNull();
}
void ImageViewer::enableZoomActions()
{
m_resetZoomAct->setEnabled(!qFuzzyCompare(m_scaleFactor, m_initialScaleFactor));
m_zoomInAct->setEnabled(m_scaleFactor < m_maxScaleFactor);
m_zoomOutAct->setEnabled(m_scaleFactor > m_minScaleFactor);
}
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
void ImageViewer::printDocument(QPrinter *printer) const
{
if (!hasContent())
return;
QPainter painter(printer);
QPixmap pixmap = m_imageLabel->pixmap();
QRect rect = painter.viewport();
QSize size = pixmap.size();
size.scale(rect.size(), Qt::KeepAspectRatio);
painter.setViewport(rect.x(), rect.y(), size.width(), size.height());
painter.setWindow(pixmap.rect());
painter.drawPixmap(0, 0, pixmap);
}
#endif // QT_DOCUMENTVIEWER_PRINTSUPPORT

View File

@ -0,0 +1,63 @@
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef IMAGEVIEWER_H
#define IMAGEVIEWER_H
#include "viewerinterfaces.h"
#include <QSizeF>
QT_FORWARD_DECLARE_CLASS(QLabel)
class ImageViewer : public ViewerInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.DocumentViewer.ViewerInterface" FILE "imageviewer.json")
Q_INTERFACES(ViewerInterface)
public:
Q_DISABLE_COPY_MOVE(ImageViewer)
ImageViewer();
~ImageViewer() override;
void init(QFile *file, QWidget *parent, QMainWindow *mainWindow) override;
QString viewerName() const override { return QLatin1StringView(staticMetaObject.className()); };
QStringList supportedMimeTypes() const override;
bool hasContent() const override;
QByteArray saveState() const override { return {}; }
bool restoreState(QByteArray &) override { return true; }
bool supportsOverview() const override { return false; }
#ifdef QT_DOCUMENTVIEWER_PRINTSUPPORT
protected:
void printDocument(QPrinter *printer) const override;
#endif // QT_DOCUMENTVIEWER_PRINTSUPPORT
private slots:
void setupImageUi();
void clear();
void zoomIn();
void zoomOut();
void resetZoom();
private:
void openFile();
void setScaleFactor(qreal scaleFactor);
void doSetScaleFactor(qreal scaleFactor);
void enableZoomActions();
QLabel *m_imageLabel{};
QToolBar *m_toolBar{};
QAction *m_zoomInAct{};
QAction *m_zoomOutAct{};
QAction *m_resetZoomAct{};
QStringList m_formats;
qreal m_scaleFactor = 1;
qreal m_initialScaleFactor = 1;
qreal m_minScaleFactor = 1;
qreal m_maxScaleFactor = 1;
QSizeF m_imageSize;
};
#endif //IMAGEVIEWER_H

View File

@ -0,0 +1 @@
{ "Keys": [ "imageviewer" ] }