From 726c52998c50252fe2ce74c1ba63e52c3355a18a Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 6 May 2021 12:18:27 +0200 Subject: [PATCH 1/5] Use a simple QGraphicsItem subclass for drawing raster images RasterImageView was using a custom implementation that would use two QPixmaps to do double buffered rendering. However, since Qt already renders things double buffered, we are doing duplicate work. So drop that and use a simple QGraphicsItem subclass that paints the loaded image directly. This simplifies things a lot and removes a fair amount of difficult to follow custom code. BUG: 271671 --- lib/documentview/rasterimageview.cpp | 394 ++++++++++----------------- lib/documentview/rasterimageview.h | 6 +- 2 files changed, 148 insertions(+), 252 deletions(-) diff --git a/lib/documentview/rasterimageview.cpp b/lib/documentview/rasterimageview.cpp index 672b2f00..85806bfb 100644 --- a/lib/documentview/rasterimageview.cpp +++ b/lib/documentview/rasterimageview.cpp @@ -42,31 +42,97 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA namespace Gwenview { +/* + * This is effectively QGraphicsPixmapItem that draws the document's QImage + * directly rather than going through a QPixmap intermediary. This avoids + * duplicating the image contents and the extra memory usage that comes from + * that. + */ +class RasterImageItem : public QGraphicsItem +{ +public: + RasterImageItem(RasterImageView* parent) + : QGraphicsItem(parent) + , mParentView(parent) + { + + } + + void paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) override + { + auto document = mParentView->document(); + + // We want nearest neighbour when zooming in since that provides the most + // accurate representation of pixels, but when zooming out it will actually + // not look very nice, so use smoothing when zooming out. + painter->setRenderHint(QPainter::SmoothPixmapTransform, mParentView->zoom() < 1.0); + + painter->drawImage(QPointF{0, 0}, document->image()); + } + + QRectF boundingRect() const override + { + return QRectF{QPointF{0, 0}, mParentView->documentSize()}; + } + + RasterImageView* mParentView; +}; + +/* + * We need the tools to be painted on top of the image. However, since we are + * using RasterImageItem as a child of this item, the image gets painted on + * top of this item. To fix that, this custom item is stacked after the image + * item and will paint the tools, which will draw it on top of the image. + */ +class ToolPainter : public QGraphicsItem +{ +public: + ToolPainter(AbstractRasterImageViewTool* tool, QGraphicsItem* parent = nullptr) + : QGraphicsItem(parent) + , mTool(tool) + { + + } + + void paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) override + { + if (mTool) { + mTool->paint(painter); + } + } + + QRectF boundingRect() const override + { + return parentItem()->boundingRect(); + } + + QPointer mTool; +}; + struct RasterImageViewPrivate { + RasterImageViewPrivate(RasterImageView* qq) + : q(qq) + { + } + RasterImageView* q; - ImageScaler* mScaler; - bool mEmittedCompleted; - // Config - AbstractImageView::AlphaBackgroundMode mAlphaBackgroundMode; - QColor mAlphaBackgroundColor; - cmsUInt32Number mRenderingIntent; - // /Config + QGraphicsItem* mImageItem; + ToolPainter* mToolItem = nullptr; - bool mBufferIsEmpty; - QPixmap mCurrentBuffer; - // The alternate buffer is useful when scrolling: existing content is copied - // to mAlternateBuffer and buffers are swapped. This avoids allocating a new - // QPixmap every time the image is scrolled. - QPixmap mAlternateBuffer; + // Config + AbstractImageView::AlphaBackgroundMode mAlphaBackgroundMode = AbstractImageView::AlphaBackgroundNone; + QColor mAlphaBackgroundColor = Qt::black; - QTimer* mUpdateTimer; + cmsUInt32Number mRenderingIntent = INTENT_PERCEPTUAL; QPointer mTool; - bool mApplyDisplayTransform; // Defaults to true. Can be set to false if there is no need or no way to apply color profile - cmsHTRANSFORM mDisplayTransform; + bool mApplyDisplayTransform = true; // Can be set to false if there is no need or no way to apply color profile + cmsHTRANSFORM mDisplayTransform = nullptr; + + bool mLoaded = false; void updateDisplayTransform(QImage::Format format) { @@ -109,104 +175,50 @@ struct RasterImageViewPrivate mApplyDisplayTransform = true; } - void setupUpdateTimer() + void applyImageTransform() { - mUpdateTimer = new QTimer(q); - mUpdateTimer->setInterval(500); - mUpdateTimer->setSingleShot(true); - QObject::connect(mUpdateTimer, SIGNAL(timeout()), q, SLOT(updateBuffer())); - } - - void startAnimationIfNecessary() - { - if (q->document() && q->isVisible()) { - q->document()->startAnimation(); + if (!q->document()) { + return; } - } - QRectF mapViewportToZoomedImage(const QRectF& viewportRect) const - { - return QRectF( - viewportRect.topLeft() - q->imageOffset() + q->scrollPos(), - viewportRect.size() - ); - } + auto image = q->document()->image(); - void setScalerRegionToVisibleRect() - { - QRectF rect = mapViewportToZoomedImage(q->boundingRect()); - mScaler->setDestinationRegion(QRegion(rect.toRect())); + if (mApplyDisplayTransform) { + updateDisplayTransform(image.format()); + if (mDisplayTransform) { + quint8 *bytes = const_cast(image.bits()); + cmsDoTransform(mDisplayTransform, bytes, bytes, image.width() * image.height()); + } + } + + q->update(); } - void resizeBuffer() + void startAnimationIfNecessary() { - const auto dpr = q->devicePixelRatio(); - QSize size = q->visibleImageSize().toSize(); - if (size * dpr == mCurrentBuffer.size()) { - return; - } - if (!size.isValid()) { - mAlternateBuffer = QPixmap(); - mCurrentBuffer = QPixmap(); - return; - } - - mAlternateBuffer = QPixmap(size * dpr); - mAlternateBuffer.setDevicePixelRatio(dpr); - mAlternateBuffer.fill(Qt::transparent); - { - QPainter painter(&mAlternateBuffer); - painter.drawPixmap(0, 0, mCurrentBuffer); + if (q->document() && q->isVisible()) { + q->document()->startAnimation(); } - qSwap(mAlternateBuffer, mCurrentBuffer); - - mAlternateBuffer = QPixmap(); } - void drawAlphaBackground(QPainter* painter, const QRectF& viewportRect, const QPoint& zoomedImageTopLeft, const QPixmap &texture) + void adjustItemPosition() { - switch (mAlphaBackgroundMode) { - case AbstractImageView::AlphaBackgroundNone: - painter->fillRect(viewportRect, Qt::transparent); - break; - case AbstractImageView::AlphaBackgroundCheckBoard: - { - const QPoint textureOffset( - zoomedImageTopLeft.x() % texture.width(), - zoomedImageTopLeft.y() % texture.height()); - painter->drawTiledPixmap( - viewportRect, - texture, - textureOffset); - break; - } - case AbstractImageView::AlphaBackgroundSolid: - painter->fillRect(viewportRect, mAlphaBackgroundColor); - break; - default: - Q_ASSERT(0); - } + mImageItem->setPos((q->imageOffset() - q->scrollPos()).toPoint()); + q->update(); } }; RasterImageView::RasterImageView(QGraphicsItem* parent) : AbstractImageView(parent) -, d(new RasterImageViewPrivate) +, d(new RasterImageViewPrivate{this}) { - d->q = this; - d->mEmittedCompleted = false; - d->mApplyDisplayTransform = true; - d->mDisplayTransform = nullptr; + d->mImageItem = new RasterImageItem{this}; - d->mAlphaBackgroundMode = AlphaBackgroundNone; - d->mAlphaBackgroundColor = Qt::black; - d->mRenderingIntent = INTENT_PERCEPTUAL; + // Clip this item so we only render the visible part of the image when + // zoomed or when viewing a large image. + setFlag(QGraphicsItem::ItemClipsChildrenToShape); - d->mBufferIsEmpty = true; - d->mScaler = new ImageScaler(this); - connect(d->mScaler, &ImageScaler::scaledRect, this, &RasterImageView::updateFromScaler); - - d->setupUpdateTimer(); + setCacheMode(QGraphicsItem::DeviceCoordinateCache); } RasterImageView::~RasterImageView() @@ -217,39 +229,34 @@ RasterImageView::~RasterImageView() if (d->mDisplayTransform) { cmsDeleteTransform(d->mDisplayTransform); } + delete d; } void RasterImageView::setAlphaBackgroundMode(AlphaBackgroundMode mode) { d->mAlphaBackgroundMode = mode; - if (document() && document()->hasAlphaChannel()) { - d->mCurrentBuffer = QPixmap(); - updateBuffer(); - } + update(); } void RasterImageView::setAlphaBackgroundColor(const QColor& color) { d->mAlphaBackgroundColor = color; - if (document() && document()->hasAlphaChannel()) { - d->mCurrentBuffer = QPixmap(); - updateBuffer(); - } + update(); } void RasterImageView::setRenderingIntent(const RenderingIntent::Enum& renderingIntent) { if (d->mRenderingIntent != renderingIntent) { d->mRenderingIntent = renderingIntent; - updateBuffer(); + d->applyImageTransform(); } } void RasterImageView::resetMonitorICC() { d->mApplyDisplayTransform = true; - updateBuffer(); + d->applyImageTransform(); } void RasterImageView::loadFromDocument() @@ -272,7 +279,7 @@ void RasterImageView::loadFromDocument() void RasterImageView::slotDocumentMetaInfoLoaded() { - if (document()->size().isValid()) { + if (document()->size().isValid() && document()->image().format() != QImage::Format_Invalid) { QMetaObject::invokeMethod(this, &RasterImageView::finishSetDocument, Qt::QueuedConnection); } else { // Could not retrieve image size from meta info, we need to load the @@ -287,12 +294,7 @@ void RasterImageView::finishSetDocument() { GV_RETURN_IF_FAIL(document()->size().isValid()); - d->mScaler->setDocument(document()); - d->resizeBuffer(); - applyPendingScrollPos(); - - connect(document().data(), &Document::imageRectUpdated, - this, &RasterImageView::updateImageRect); + d->applyImageTransform(); if (zoomToFit()) { // Force the update otherwise if computeZoomToFit() returns 1, setZoom() @@ -301,33 +303,16 @@ void RasterImageView::finishSetDocument() } else if (zoomToFill()) { setZoom(computeZoomToFill(), QPointF(-1, -1), ForceUpdate); } else { - // Not only call updateBuffer, but also ensure the initial transformation mode - // of the image scaler is set correctly when zoom is unchanged (see Bug 396736). onZoomChanged(); } + applyPendingScrollPos(); d->startAnimationIfNecessary(); update(); -} -void RasterImageView::updateImageRect(const QRect& imageRect) -{ - QRectF viewRect = mapToView(imageRect); - if (!viewRect.intersects(boundingRect())) { - return; - } - - if (zoomToFit()) { - setZoom(computeZoomToFit()); - } else if (zoomToFill()) { - setZoom(computeZoomToFill()); - } else { - applyPendingScrollPos(); - } + d->mLoaded = true; - d->setScalerRegionToVisibleRect(); - update(); - emit imageRectUpdated(); + Q_EMIT completed(); } void RasterImageView::slotDocumentIsAnimatedUpdated() @@ -335,136 +320,27 @@ void RasterImageView::slotDocumentIsAnimatedUpdated() d->startAnimationIfNecessary(); } -void RasterImageView::updateFromScaler(int zoomedImageLeft, int zoomedImageTop, const QImage& image) -{ - if (d->mApplyDisplayTransform) { - d->updateDisplayTransform(image.format()); - if (d->mDisplayTransform) { - quint8 *bytes = const_cast(image.bits()); - cmsDoTransform(d->mDisplayTransform, bytes, bytes, image.width() * image.height()); - } - } - - d->resizeBuffer(); - int viewportLeft = zoomedImageLeft - scrollPos().x(); - int viewportTop = zoomedImageTop - scrollPos().y(); - d->mBufferIsEmpty = false; - { - QPainter painter(&d->mCurrentBuffer); - painter.setCompositionMode(QPainter::CompositionMode_Source); - if (document()->hasAlphaChannel()) { - const QRectF viewportRect(QPointF(viewportLeft, viewportTop), - QSizeF(image.size()) / devicePixelRatio()); - d->drawAlphaBackground(&painter, viewportRect, - QPoint(zoomedImageLeft, zoomedImageTop), - alphaBackgroundTexture()); - // This is required so transparent pixels don't replace our background - painter.setCompositionMode(QPainter::CompositionMode_SourceOver); - } - painter.drawImage(viewportLeft, viewportTop, image); - } - update(); - - if (!d->mEmittedCompleted) { - d->mEmittedCompleted = true; - emit completed(); - } -} - void RasterImageView::onZoomChanged() { - d->mScaler->setZoom(zoom()); - if (!d->mUpdateTimer->isActive()) { - updateBuffer(); - } + d->mImageItem->setScale(zoom() / devicePixelRatio()); + d->adjustItemPosition(); } void RasterImageView::onImageOffsetChanged() { - update(); + d->adjustItemPosition(); } void RasterImageView::onScrollPosChanged(const QPointF& oldPos) { - QPointF delta = scrollPos() - oldPos; - - // Scroll existing - { - if (d->mAlternateBuffer.size() != d->mCurrentBuffer.size()) { - d->mAlternateBuffer = QPixmap(d->mCurrentBuffer.size()); - d->mAlternateBuffer.setDevicePixelRatio(d->mCurrentBuffer.devicePixelRatio()); - } - d->mAlternateBuffer.fill(Qt::transparent); - QPainter painter(&d->mAlternateBuffer); - painter.drawPixmap(-delta, d->mCurrentBuffer); - } - qSwap(d->mCurrentBuffer, d->mAlternateBuffer); - - // Scale missing parts - QRegion bufferRegion = QRect(scrollPos().toPoint(), d->mCurrentBuffer.size() / devicePixelRatio()); - QRegion updateRegion = bufferRegion - bufferRegion.translated(-delta.toPoint()); - updateBuffer(updateRegion); - update(); + Q_UNUSED(oldPos); + d->adjustItemPosition(); } void RasterImageView::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) { - d->mCurrentBuffer.setDevicePixelRatio(devicePixelRatio()); - - QPointF topLeft = imageOffset(); - if (zoomToFit()) { - // In zoomToFit mode, scale crudely the buffer to fit the screen. This - // provide an approximate rendered which will be replaced when the scheduled - // proper scale is ready. - // Round point and size independently, to keep consistency with the below (non zoomToFit) painting - const QRect rect = QRect(topLeft.toPoint(), (dipDocumentSize() * zoom()).toSize()); - painter->drawPixmap(rect, d->mCurrentBuffer); - } else { - painter->drawPixmap(topLeft.toPoint(), d->mCurrentBuffer); - } - - if (d->mTool) { - d->mTool.data()->paint(painter); - } - - // Debug -#if 0 - QSizeF visibleSize = documentSize() * zoom(); - painter->setPen(Qt::red); - painter->drawRect(topLeft.x(), topLeft.y(), visibleSize.width() - 1, visibleSize.height() - 1); - - painter->setPen(Qt::blue); - painter->drawRect(topLeft.x(), topLeft.y(), d->mCurrentBuffer.width() - 1, d->mCurrentBuffer.height() - 1); -#endif -} - -void RasterImageView::resizeEvent(QGraphicsSceneResizeEvent* event) -{ - // If we are in zoomToFit mode and have something in our buffer, delay the - // update: paint() will paint a scaled version of the buffer until resizing - // is done. This is much faster than rescaling the whole image for each - // resize event we receive. - // mUpdateTimer must be started before calling AbstractImageView::resizeEvent() - // because AbstractImageView::resizeEvent() will call onZoomChanged(), which - // will trigger an immediate update unless the mUpdateTimer is active. - if ((zoomToFit() || zoomToFill()) && !d->mBufferIsEmpty) { - d->mUpdateTimer->start(); - } - AbstractImageView::resizeEvent(event); - if (!zoomToFit() || !zoomToFill()) { - // Only update buffer if we are not in zoomToFit mode: if we are - // onZoomChanged() will have already updated the buffer. - updateBuffer(); - } -} - -void RasterImageView::updateBuffer(const QRegion& region) -{ - d->mUpdateTimer->stop(); - if (region.isEmpty()) { - d->setScalerRegionToVisibleRect(); - } else { - d->mScaler->setDestinationRegion(region); + if (d->mLoaded) { + drawAlphaBackground(painter); } } @@ -473,6 +349,7 @@ void RasterImageView::setCurrentTool(AbstractRasterImageViewTool* tool) if (d->mTool) { d->mTool.data()->toolDeactivated(); d->mTool.data()->deleteLater(); + delete d->mToolItem; } // Go back to default cursor when tool is deactivated. We need to call this here and @@ -482,6 +359,7 @@ void RasterImageView::setCurrentTool(AbstractRasterImageViewTool* tool) d->mTool = tool; if (d->mTool) { d->mTool.data()->toolActivated(); + d->mToolItem = new ToolPainter{d->mTool, this}; } emit currentToolChanged(tool); update(); @@ -580,4 +458,24 @@ void RasterImageView::hoverMoveEvent(QGraphicsSceneHoverEvent* event) AbstractImageView::hoverMoveEvent(event); } +void RasterImageView::drawAlphaBackground(QPainter* painter) +{ + const QRect imageRect = QRect(imageOffset().toPoint(), visibleImageSize().toSize()); + + switch (d->mAlphaBackgroundMode) { + case AbstractImageView::AlphaBackgroundNone: + break; + case AbstractImageView::AlphaBackgroundCheckBoard: + { + painter->drawTiledPixmap(imageRect, alphaBackgroundTexture(), scrollPos()); + break; + } + case AbstractImageView::AlphaBackgroundSolid: + painter->fillRect(imageRect, d->mAlphaBackgroundColor); + break; + default: + Q_ASSERT(0); + } +} + } // namespace diff --git a/lib/documentview/rasterimageview.h b/lib/documentview/rasterimageview.h index ec437064..8ff1694b 100644 --- a/lib/documentview/rasterimageview.h +++ b/lib/documentview/rasterimageview.h @@ -63,7 +63,6 @@ protected: void onZoomChanged() override; void onImageOffsetChanged() override; void onScrollPosChanged(const QPointF& oldPos) override; - void resizeEvent(QGraphicsSceneResizeEvent* event) override; void mousePressEvent(QGraphicsSceneMouseEvent* event) override; void mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) override; void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override; @@ -77,11 +76,10 @@ private Q_SLOTS: void slotDocumentMetaInfoLoaded(); void slotDocumentIsAnimatedUpdated(); void finishSetDocument(); - void updateFromScaler(int, int, const QImage&); - void updateImageRect(const QRect& imageRect); - void updateBuffer(const QRegion& region = QRegion()); private: + void drawAlphaBackground(QPainter* painter); + RasterImageViewPrivate* const d; }; -- GitLab From 3173858c5d8f161cd9d0be56d98e2a55cad646e7 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 11 May 2021 15:34:59 +0200 Subject: [PATCH 2/5] Require C++14 so we can use things like std::make_unique Everything else should already be on the same or higher C++ version so this should be safe. --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 386ae7a8..661c887b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,6 +153,9 @@ function(JoinListAsString VALUES GLUE OUTPUT) set(${OUTPUT} "${_TMP_STR}" PARENT_SCOPE) endfunction() +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) + set(IMAGE_MIME_TYPES_LIST image/avif image/gif -- GitLab From aecc390d27b3478b0c959b68a9f98e34fb88447a Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 6 May 2021 12:55:46 +0200 Subject: [PATCH 3/5] Extract background painting into its own custom QGraphicsItem Rather than duplicating the background code everywhere, we can move it to its own QGraphicsItem which takes care of painting the right background. --- lib/CMakeLists.txt | 1 + lib/documentview/abstractimageview.cpp | 53 +++++++---- lib/documentview/abstractimageview.h | 10 +- lib/documentview/alphabackgrounditem.cpp | 100 ++++++++++++++++++++ lib/documentview/alphabackgrounditem.h | 52 ++++++++++ lib/documentview/rasterimageview.cpp | 48 +--------- lib/documentview/rasterimageview.h | 6 -- lib/documentview/rasterimageviewadapter.cpp | 6 +- lib/documentview/svgviewadapter.cpp | 50 +--------- lib/documentview/svgviewadapter.h | 8 -- 10 files changed, 202 insertions(+), 132 deletions(-) create mode 100644 lib/documentview/alphabackgrounditem.cpp create mode 100644 lib/documentview/alphabackgrounditem.h diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 8c2f9569..a918a861 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -90,6 +90,7 @@ set(gwenviewlib_SRCS documentview/abstractdocumentviewadapter.cpp documentview/abstractimageview.cpp documentview/abstractrasterimageviewtool.cpp + documentview/alphabackgrounditem.cpp documentview/birdeyeview.cpp documentview/documentview.cpp documentview/documentviewcontroller.cpp diff --git a/lib/documentview/abstractimageview.cpp b/lib/documentview/abstractimageview.cpp index 1542edc8..4693ffed 100644 --- a/lib/documentview/abstractimageview.cpp +++ b/lib/documentview/abstractimageview.cpp @@ -23,6 +23,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA // Local #include "gwenview_lib_debug.h" +#include "alphabackgrounditem.h" // KF @@ -60,7 +61,7 @@ struct AbstractImageViewPrivate QPointF mLastDragPos; QSizeF mDocumentSize; - const QPixmap mAlphaBackgroundTexture; + AlphaBackgroundItem* mBackgroundItem; void adjustImageOffset(Verbosity verbosity = Notify) { @@ -70,8 +71,11 @@ struct AbstractImageViewPrivate qMax((viewSize.width() - zoomedDocSize.width()) / 2, qreal(0.)), qMax((viewSize.height() - zoomedDocSize.height()) / 2, qreal(0.)) ); + if (offset != mImageOffset) { mImageOffset = offset; + + if (verbosity == Notify) { q->onImageOffsetChanged(); } @@ -98,6 +102,8 @@ struct AbstractImageViewPrivate if (newPos != mScrollPos) { QPointF oldPos = mScrollPos; mScrollPos = newPos; + + if (verbosity == Notify) { q->onScrollPosChanged(oldPos); } @@ -116,23 +122,13 @@ struct AbstractImageViewPrivate mZoomCursor = QCursor(cursorPixmap, 11, 11); } - QPixmap createAlphaBackgroundTexture() + AbstractImageViewPrivate(AbstractImageView *parent) + : q(parent) + , mBackgroundItem(new AlphaBackgroundItem{q}) { - QPixmap pix = QPixmap(32, 32); - QPainter painter(&pix); - painter.fillRect(pix.rect(), QColor(128, 128, 128)); - const QColor light = QColor(192, 192, 192); - painter.fillRect(0, 0, 16, 16, light); - painter.fillRect(16, 16, 16, 16, light); - pix.setDevicePixelRatio(q->devicePixelRatio()); - return pix; + mBackgroundItem->setVisible(false); } - AbstractImageViewPrivate(AbstractImageView *parent) : - q(parent), - mAlphaBackgroundTexture(createAlphaBackgroundTexture()) - { } - void checkAndRequestZoomAction(const QGraphicsSceneMouseEvent* event) { if (event->modifiers() & Qt::ControlModifier) { @@ -183,6 +179,10 @@ void AbstractImageView::setDocument(const Document::Ptr &doc) disconnect(d->mDocument.data(), nullptr, this, nullptr); } d->mDocument = doc; + if (d->mDocument) { + connect(d->mDocument.data(), &Document::imageRectUpdated, this, &AbstractImageView::onImageRectUpdated); + } + loadFromDocument(); } @@ -301,11 +301,6 @@ void AbstractImageView::setZoomToFill(bool on, const QPointF& center) emit zoomToFillChanged(d->mZoomToFill); } -const QPixmap& AbstractImageView::alphaBackgroundTexture() const -{ - return d->mAlphaBackgroundTexture; -} - void AbstractImageView::resizeEvent(QGraphicsSceneResizeEvent* event) { QGraphicsWidget::resizeEvent(event); @@ -633,4 +628,22 @@ void AbstractImageView::resetDragCursor() updateCursor(); } +AlphaBackgroundItem* AbstractImageView::backgroundItem() const +{ + return d->mBackgroundItem; +} + +void AbstractImageView::onImageRectUpdated() +{ + if (zoomToFit()) { + setZoom(computeZoomToFit()); + } else if (zoomToFill()) { + setZoom(computeZoomToFill()); + } else { + applyPendingScrollPos(); + } + + update(); +} + } // namespace diff --git a/lib/documentview/abstractimageview.h b/lib/documentview/abstractimageview.h index 4e3246f9..eca327c4 100644 --- a/lib/documentview/abstractimageview.h +++ b/lib/documentview/abstractimageview.h @@ -32,6 +32,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA namespace Gwenview { +class AlphaBackgroundItem; + struct AbstractImageViewPrivate; /** * @@ -119,6 +121,8 @@ public: void resetDragCursor(); + AlphaBackgroundItem* backgroundItem() const; + public Q_SLOTS: void updateCursor(); @@ -135,10 +139,6 @@ Q_SIGNALS: void toggleFullScreenRequested(); protected: - virtual void setAlphaBackgroundMode(AlphaBackgroundMode mode) = 0; - virtual void setAlphaBackgroundColor(const QColor& color) = 0; - const QPixmap& alphaBackgroundTexture() const; - virtual void loadFromDocument() = 0; virtual void onZoomChanged() = 0; /** @@ -152,6 +152,8 @@ protected: */ virtual void onScrollPosChanged(const QPointF& oldPos) = 0; + void onImageRectUpdated(); + void resizeEvent(QGraphicsSceneResizeEvent* event) override; void focusInEvent(QFocusEvent* event) override; diff --git a/lib/documentview/alphabackgrounditem.cpp b/lib/documentview/alphabackgrounditem.cpp new file mode 100644 index 00000000..4252ff72 --- /dev/null +++ b/lib/documentview/alphabackgrounditem.cpp @@ -0,0 +1,100 @@ +/* + * SPDX-FileCopyrightText: 2021 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + */ + +#include "alphabackgrounditem.h" + +#include +#include + +using namespace Gwenview; + +AlphaBackgroundItem::AlphaBackgroundItem(AbstractImageView* parent) + : QGraphicsItem(parent) + , mParent(parent) +{ +} + +AlphaBackgroundItem::~AlphaBackgroundItem() +{ +} + +AbstractImageView::AlphaBackgroundMode AlphaBackgroundItem::mode() const +{ + return mMode; +} + +void Gwenview::AlphaBackgroundItem::setMode(AbstractImageView::AlphaBackgroundMode mode) +{ + if (mode == mMode) { + return; + } + + mMode = mode; + update(); +} + +QColor AlphaBackgroundItem::color() +{ + return mColor; +} + +void AlphaBackgroundItem::setColor(const QColor& color) +{ + if (color == mColor) { + return; + } + + mColor = color; + update(); +} + +void AlphaBackgroundItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) +{ + // We need to floor the image size. Unfortunately, QPointF and QSizeF both + // _round_ when converting instead of flooring. This means that we need to + // manually do the flooring here, because we otherwise run into pixel + // alignment issues with the image that is drawn on top of the background. + const auto width = int((mParent->documentSize().width() / qApp->devicePixelRatio()) * mParent->zoom()); + const auto height = int((mParent->documentSize().height() / qApp->devicePixelRatio()) * mParent->zoom()); + const auto imageRect = QRectF{mParent->imageOffset().toPoint(), QSize{width, height}}; + + switch (mMode) { + case AbstractImageView::AlphaBackgroundNone: + // No background, do not paint anything. + break; + case AbstractImageView::AlphaBackgroundCheckBoard: + { + if (!mCheckBoardTexture) { + createCheckBoardTexture(); + } + + painter->drawTiledPixmap(imageRect, *mCheckBoardTexture, mParent->scrollPos()); + break; + } + case AbstractImageView::AlphaBackgroundSolid: + painter->fillRect(imageRect, mColor); + break; + default: + break; + } +} + +QRectF AlphaBackgroundItem::boundingRect() const +{ + return mParent->boundingRect(); +} + +void AlphaBackgroundItem::createCheckBoardTexture() +{ + mCheckBoardTexture = std::make_unique(32, 32); + QPainter painter(mCheckBoardTexture.get()); + painter.fillRect(mCheckBoardTexture->rect(), QColor(128, 128, 128)); + const QColor light = QColor(192, 192, 192); + painter.fillRect(0, 0, 16, 16, light); + painter.fillRect(16, 16, 16, 16, light); + // Don't set the pixmap's devicePixelRatio, just let Qt take care of scaling + // this, otherwise we get some ugly glitches. +} diff --git a/lib/documentview/alphabackgrounditem.h b/lib/documentview/alphabackgrounditem.h new file mode 100644 index 00000000..50d1f9e2 --- /dev/null +++ b/lib/documentview/alphabackgrounditem.h @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2021 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + */ + +#ifndef BACKGROUNDITEM_H +#define BACKGROUNDITEM_H + +#include + +#include + +#include "abstractimageview.h" + +namespace Gwenview +{ + +/** + * A QGraphicsItem subclass that draws the appropriate background for alpha images. + */ +class GWENVIEWLIB_EXPORT AlphaBackgroundItem : public QGraphicsItem +{ +public: + AlphaBackgroundItem(AbstractImageView* parent); + ~AlphaBackgroundItem() override; + + AbstractImageView::AlphaBackgroundMode mode() const; + void setMode(AbstractImageView::AlphaBackgroundMode mode); + + QColor color(); + void setColor(const QColor& color); + + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) override; + + virtual QRectF boundingRect() const override; + +private: + void createCheckBoardTexture(); + + AbstractImageView* mParent; + AbstractImageView::AlphaBackgroundMode mMode = AbstractImageView::AlphaBackgroundCheckBoard; + QColor mColor = Qt::black; + + // This pixmap will be used to fill the background when mMode is + // AlphaBackgroundCheckBoard. + std::unique_ptr mCheckBoardTexture; +}; + +} + +#endif // BACKGROUNDITEM_H diff --git a/lib/documentview/rasterimageview.cpp b/lib/documentview/rasterimageview.cpp index 85806bfb..3a495c80 100644 --- a/lib/documentview/rasterimageview.cpp +++ b/lib/documentview/rasterimageview.cpp @@ -28,6 +28,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA #include #include #include "gwenview_lib_debug.h" +#include "alphabackgrounditem.h" // KF @@ -121,10 +122,6 @@ struct RasterImageViewPrivate QGraphicsItem* mImageItem; ToolPainter* mToolItem = nullptr; - // Config - AbstractImageView::AlphaBackgroundMode mAlphaBackgroundMode = AbstractImageView::AlphaBackgroundNone; - QColor mAlphaBackgroundColor = Qt::black; - cmsUInt32Number mRenderingIntent = INTENT_PERCEPTUAL; QPointer mTool; @@ -132,8 +129,6 @@ struct RasterImageViewPrivate bool mApplyDisplayTransform = true; // Can be set to false if there is no need or no way to apply color profile cmsHTRANSFORM mDisplayTransform = nullptr; - bool mLoaded = false; - void updateDisplayTransform(QImage::Format format) { GV_RETURN_IF_FAIL(format != QImage::Format_Invalid); @@ -233,18 +228,6 @@ RasterImageView::~RasterImageView() delete d; } -void RasterImageView::setAlphaBackgroundMode(AlphaBackgroundMode mode) -{ - d->mAlphaBackgroundMode = mode; - update(); -} - -void RasterImageView::setAlphaBackgroundColor(const QColor& color) -{ - d->mAlphaBackgroundColor = color; - update(); -} - void RasterImageView::setRenderingIntent(const RenderingIntent::Enum& renderingIntent) { if (d->mRenderingIntent != renderingIntent) { @@ -310,7 +293,7 @@ void RasterImageView::finishSetDocument() d->startAnimationIfNecessary(); update(); - d->mLoaded = true; + backgroundItem()->setVisible(true); Q_EMIT completed(); } @@ -337,13 +320,6 @@ void RasterImageView::onScrollPosChanged(const QPointF& oldPos) d->adjustItemPosition(); } -void RasterImageView::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) -{ - if (d->mLoaded) { - drawAlphaBackground(painter); - } -} - void RasterImageView::setCurrentTool(AbstractRasterImageViewTool* tool) { if (d->mTool) { @@ -458,24 +434,4 @@ void RasterImageView::hoverMoveEvent(QGraphicsSceneHoverEvent* event) AbstractImageView::hoverMoveEvent(event); } -void RasterImageView::drawAlphaBackground(QPainter* painter) -{ - const QRect imageRect = QRect(imageOffset().toPoint(), visibleImageSize().toSize()); - - switch (d->mAlphaBackgroundMode) { - case AbstractImageView::AlphaBackgroundNone: - break; - case AbstractImageView::AlphaBackgroundCheckBoard: - { - painter->drawTiledPixmap(imageRect, alphaBackgroundTexture(), scrollPos()); - break; - } - case AbstractImageView::AlphaBackgroundSolid: - painter->fillRect(imageRect, d->mAlphaBackgroundColor); - break; - default: - Q_ASSERT(0); - } -} - } // namespace diff --git a/lib/documentview/rasterimageview.h b/lib/documentview/rasterimageview.h index 8ff1694b..d90150a8 100644 --- a/lib/documentview/rasterimageview.h +++ b/lib/documentview/rasterimageview.h @@ -44,13 +44,9 @@ public: explicit RasterImageView(QGraphicsItem* parent = nullptr); ~RasterImageView() override; - void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; - void setCurrentTool(AbstractRasterImageViewTool* tool); AbstractRasterImageViewTool* currentTool() const; - void setAlphaBackgroundMode(AlphaBackgroundMode mode) override; - void setAlphaBackgroundColor(const QColor& color) override; void setRenderingIntent(const RenderingIntent::Enum& renderingIntent); void resetMonitorICC(); @@ -78,8 +74,6 @@ private Q_SLOTS: void finishSetDocument(); private: - void drawAlphaBackground(QPainter* painter); - RasterImageViewPrivate* const d; }; diff --git a/lib/documentview/rasterimageviewadapter.cpp b/lib/documentview/rasterimageviewadapter.cpp index eee8150c..7be50e09 100644 --- a/lib/documentview/rasterimageviewadapter.cpp +++ b/lib/documentview/rasterimageviewadapter.cpp @@ -26,6 +26,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA #include #include +#include "alphabackgrounditem.h" + // KF // Qt @@ -136,8 +138,8 @@ void RasterImageViewAdapter::slotLoadingFailed() void RasterImageViewAdapter::loadConfig() { - d->mView->setAlphaBackgroundMode(GwenviewConfig::alphaBackgroundMode()); - d->mView->setAlphaBackgroundColor(GwenviewConfig::alphaBackgroundColor()); + d->mView->backgroundItem()->setMode(GwenviewConfig::alphaBackgroundMode()); + d->mView->backgroundItem()->setColor(GwenviewConfig::alphaBackgroundColor()); d->mView->setRenderingIntent(GwenviewConfig::renderingIntent()); d->mView->setEnlargeSmallerImages(GwenviewConfig::enlargeSmallerImages()); d->mView->resetMonitorICC(); diff --git a/lib/documentview/svgviewadapter.cpp b/lib/documentview/svgviewadapter.cpp index c50a7aa8..0e669318 100644 --- a/lib/documentview/svgviewadapter.cpp +++ b/lib/documentview/svgviewadapter.cpp @@ -36,6 +36,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA #include "document/documentfactory.h" #include #include +#include "alphabackgrounditem.h" namespace Gwenview { @@ -44,9 +45,6 @@ namespace Gwenview SvgImageView::SvgImageView(QGraphicsItem* parent) : AbstractImageView(parent) , mSvgItem(new QGraphicsSvgItem(this)) -, mAlphaBackgroundMode(AbstractImageView::AlphaBackgroundCheckBoard) -, mAlphaBackgroundColor(Qt::black) -, mImageFullyLoaded(false) { // At certain scales, the SVG can render outside its own bounds up to 1 pixel // This clips it so it isn't drawn outside the background or over the selection rect @@ -85,7 +83,7 @@ void SvgImageView::finishLoadFromDocument() } applyPendingScrollPos(); emit completed(); - mImageFullyLoaded = true; + backgroundItem()->setVisible(true); } void SvgImageView::onZoomChanged() @@ -110,46 +108,6 @@ void SvgImageView::adjustItemPos() update(); } -void SvgImageView::setAlphaBackgroundMode(AbstractImageView::AlphaBackgroundMode mode) -{ - mAlphaBackgroundMode = mode; - update(); -} - -void SvgImageView::setAlphaBackgroundColor(const QColor& color) -{ - mAlphaBackgroundColor = color; - update(); -} - -void SvgImageView::drawAlphaBackground(QPainter* painter) -{ - // The point and size must be rounded to integers independently, to keep consistency with RasterImageView - const QRect imageRect = QRect(imageOffset().toPoint(), visibleImageSize().toSize()); - - switch (mAlphaBackgroundMode) { - case AbstractImageView::AlphaBackgroundNone: - // Unlike RasterImageView, SVGs are rendered directly on the image view, - // therefore we can simply not draw a background - break; - case AbstractImageView::AlphaBackgroundCheckBoard: - painter->drawTiledPixmap(imageRect, alphaBackgroundTexture(), scrollPos()); - break; - case AbstractImageView::AlphaBackgroundSolid: - painter->fillRect(imageRect, mAlphaBackgroundColor); - break; - default: - Q_ASSERT(0); - } -} - -void SvgImageView::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) -{ - if (mImageFullyLoaded) { - drawAlphaBackground(painter); - } -} - //// SvgViewAdapter //// struct SvgViewAdapterPrivate { @@ -200,8 +158,8 @@ Document::Ptr SvgViewAdapter::document() const void SvgViewAdapter::loadConfig() { - d->mView->setAlphaBackgroundMode(GwenviewConfig::alphaBackgroundMode()); - d->mView->setAlphaBackgroundColor(GwenviewConfig::alphaBackgroundColor()); + d->mView->backgroundItem()->setMode(GwenviewConfig::alphaBackgroundMode()); + d->mView->backgroundItem()->setColor(GwenviewConfig::alphaBackgroundColor()); d->mView->setEnlargeSmallerImages(GwenviewConfig::enlargeSmallerImages()); } diff --git a/lib/documentview/svgviewadapter.h b/lib/documentview/svgviewadapter.h index 629efb29..2d6f3d13 100644 --- a/lib/documentview/svgviewadapter.h +++ b/lib/documentview/svgviewadapter.h @@ -42,8 +42,6 @@ class SvgImageView : public AbstractImageView Q_OBJECT public: explicit SvgImageView(QGraphicsItem* parent = nullptr); - void setAlphaBackgroundMode(AlphaBackgroundMode mode) override; - void setAlphaBackgroundColor(const QColor& color) override; protected: void loadFromDocument() override; @@ -56,13 +54,7 @@ private Q_SLOTS: private: QGraphicsSvgItem* mSvgItem; - AbstractImageView::AlphaBackgroundMode mAlphaBackgroundMode; - QColor mAlphaBackgroundColor; - bool mImageFullyLoaded; - void adjustItemPos(); - void drawAlphaBackground(QPainter* painter); - void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; }; struct SvgViewAdapterPrivate; -- GitLab From ab7ae24003725d606d608726108df2f38895af2a Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 6 May 2021 12:56:48 +0200 Subject: [PATCH 4/5] Use JPEG_VERSION for the libjpeg version Instead of trying to find it manually which is error prone. --- lib/CMakeLists.txt | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index a918a861..c920108d 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -4,41 +4,15 @@ add_definitions(-DTRANSLATION_DOMAIN="gwenview") set(LIBGWENVIEW_VERSION "4.97.0") -# Extract version of libjpeg so that we can use the appropriate dir -# See bug #227313 -message(STATUS "Looking for libjpeg version in ${JPEG_INCLUDE_DIR}/jpeglib.h") - -# Due to the large variety of different headers the version data might be -# found in (between libjpeg, libjpeg-turbo and various multilib header -# forwarding schemes seen in distros), have a simple program print out the -# right version. -set(JPEGLIB_VERSION_CHECK_PATH "${CMAKE_CURRENT_BINARY_DIR}/jpeglib-version-check.c") -file(WRITE ${JPEGLIB_VERSION_CHECK_PATH} " -#include -#include -#include - -int main(void) { printf(\"%d\\\n\", JPEG_LIB_VERSION); return 0; } -") - -try_run(JPEGLIB_RUN_RESULT JPEGLIB_COMPILE_RESULT - ${CMAKE_CURRENT_BINARY_DIR} ${JPEGLIB_VERSION_CHECK_PATH} - CMAKE_FLAGS -DINCLUDE_DIRECTORIES:PATH=${JPEG_INCLUDE_DIR} - RUN_OUTPUT_VARIABLE jpeglib_version) - -if ((${JPEGLIB_COMPILE_RESULT} EQUAL FALSE) OR ("${JPEGLIB_RUN_RESULT}" EQUAL FAILED_TO_RUN) OR "${jpeglib_version}" STREQUAL "") - message(FATAL_ERROR "Could not find jpeglib.h. This file comes with libjpeg.") -endif() - -if ("${jpeglib_version}" LESS 80) +if (${JPEG_VERSION} LESS 80) set(GV_JPEG_DIR libjpeg-62) endif() -if ("${jpeglib_version}" EQUAL 80) +if (${JPEG_VERSION} EQUAL 80) set(GV_JPEG_DIR libjpeg-80) endif() -if ("${jpeglib_version}" EQUAL 90) +if (${JPEG_VERSION} EQUAL 90) set(GV_JPEG_DIR libjpeg-90) endif() @@ -46,7 +20,7 @@ if ("${GV_JPEG_DIR}" STREQUAL "") message(FATAL_ERROR "Unknown libjpeg version: ${jpeglib_version}") endif() -message(STATUS "libjpeg version: ${jpeglib_version}") +message(STATUS "libjpeg version: ${JPEG_VERSION}") add_definitions(-Dlibjpeg_EXPORTS) include_directories( -- GitLab From 35b4656082720ef5334fd5fa7674aaff43f0895c Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Fri, 7 May 2021 19:59:10 +0200 Subject: [PATCH 5/5] Remove ImageScaler This seeems to have only been used by RasterImageView and now even that no longer uses is, so remove it. --- lib/CMakeLists.txt | 1 - lib/documentview/rasterimageview.cpp | 1 - lib/imagescaler.cpp | 228 --------------------------- lib/imagescaler.h | 65 -------- tests/auto/CMakeLists.txt | 1 - tests/auto/imagescalertest.cpp | 214 ------------------------- tests/auto/imagescalertest.h | 104 ------------ 7 files changed, 614 deletions(-) delete mode 100644 lib/imagescaler.cpp delete mode 100644 lib/imagescaler.h delete mode 100644 tests/auto/imagescalertest.cpp delete mode 100644 tests/auto/imagescalertest.h diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index c920108d..16ae00ea 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -99,7 +99,6 @@ set(gwenviewlib_SRCS hud/hudwidget.cpp graphicswidgetfloater.cpp imagemetainfomodel.cpp - imagescaler.cpp imageutils.cpp invisiblebuttongroup.cpp iodevicejpegsourcemanager.cpp diff --git a/lib/documentview/rasterimageview.cpp b/lib/documentview/rasterimageview.cpp index 3a495c80..19f86811 100644 --- a/lib/documentview/rasterimageview.cpp +++ b/lib/documentview/rasterimageview.cpp @@ -23,7 +23,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA // Local #include -#include #include #include #include diff --git a/lib/imagescaler.cpp b/lib/imagescaler.cpp deleted file mode 100644 index 8b838e50..00000000 --- a/lib/imagescaler.cpp +++ /dev/null @@ -1,228 +0,0 @@ -/* -Gwenview: an image viewer -Copyright 2007 Aurélien Gâteau - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -*/ -#include "imagescaler.h" - -// Qt -#include -#include -#include - -// KF - -// Local -#include "gwenview_lib_debug.h" -#include -#include - -#undef ENABLE_LOG -#undef LOG -//#define ENABLE_LOG -#ifdef ENABLE_LOG -#define LOG(x) qCDebug(GWENVIEW_LIB_LOG) << x -#else -#define LOG(x) ; -#endif - -namespace Gwenview -{ - -// Amount of pixels to keep so that smooth scale is correct -static const int SMOOTH_MARGIN = 3; - -static inline QRectF scaledRect(const QRectF& rect, qreal factor) -{ - return QRectF(rect.x() * factor, - rect.y() * factor, - rect.width() * factor, - rect.height() * factor); -} - -static inline QRect scaledRect(const QRect& rect, qreal factor) -{ - return scaledRect(QRectF(rect), factor).toAlignedRect(); -} - -struct ImageScalerPrivate -{ - Qt::TransformationMode mTransformationMode; - Document::Ptr mDocument; - qreal mZoom; - QRegion mRegion; -}; - -ImageScaler::ImageScaler(QObject* parent) -: QObject(parent) -, d(new ImageScalerPrivate) -{ - d->mTransformationMode = Qt::FastTransformation; - d->mZoom = 0; -} - -ImageScaler::~ImageScaler() -{ - delete d; -} - -void ImageScaler::setDocument(const Document::Ptr &document) -{ - if (d->mDocument) { - disconnect(d->mDocument.data(), nullptr, this, nullptr); - } - d->mDocument = document; - // Used when scaler asked for a down-sampled image - connect(d->mDocument.data(), &Document::downSampledImageReady, - this, &ImageScaler::doScale); - // Used when scaler asked for a full image - connect(d->mDocument.data(), &Document::loaded, - this, &ImageScaler::doScale); -} - -void ImageScaler::setZoom(qreal zoom) -{ - // If we zoom to 400% or more, then assume the user wants to see the real - // pixels, for example to fine tune a crop operation - d->mTransformationMode = zoom < 4. ? Qt::SmoothTransformation - : Qt::FastTransformation; - - d->mZoom = zoom; -} - -void ImageScaler::setDestinationRegion(const QRegion& region) -{ - LOG(region); - d->mRegion = region; - if (d->mRegion.isEmpty()) { - return; - } - - if (d->mDocument && d->mZoom > 0) { - doScale(); - } -} - -void ImageScaler::doScale() -{ - if (d->mZoom < Document::maxDownSampledZoom()) { - if (!d->mDocument->prepareDownSampledImageForZoom(d->mZoom)) { - LOG("Asked for a down sampled image"); - return; - } - } else if (d->mDocument->image().isNull()) { - LOG("Asked for the full image"); - d->mDocument->startLoadingFullImage(); - return; - } - - LOG("Starting"); - for (const QRect &rect : qAsConst(d->mRegion)) { - LOG(rect); - scaleRect(rect); - } - LOG("Done"); -} - -void ImageScaler::scaleRect(const QRect& rect) -{ - const qreal dpr = qApp->devicePixelRatio(); - - // variables prefixed with dp are in device pixels - const QRect dpRect = Gwenview::scaledRect(rect, dpr); - - const qreal REAL_DELTA = 0.001; - if (qAbs(d->mZoom - 1.0) < REAL_DELTA) { - QImage tmp = d->mDocument->image().copy(dpRect); - tmp.setDevicePixelRatio(dpr); - emit scaledRect(rect.left(), rect.top(), tmp); - return; - } - - QImage image; - qreal zoom; - if (d->mZoom < Document::maxDownSampledZoom()) { - image = d->mDocument->downSampledImageForZoom(d->mZoom); - Q_ASSERT(!image.isNull()); - qreal zoom1 = qreal(image.width()) / d->mDocument->width(); - zoom = d->mZoom / zoom1; - } else { - image = d->mDocument->image(); - zoom = d->mZoom; - } - const QRect imageRect = Gwenview::scaledRect(image.rect(), 1.0 / dpr); - - // If rect contains "half" pixels, make sure sourceRect includes them - QRectF sourceRectF = Gwenview::scaledRect(QRectF(rect), 1.0 / zoom); - - sourceRectF = sourceRectF.intersected(imageRect); - QRect sourceRect = sourceRectF.toAlignedRect(); - if (sourceRect.isEmpty()) { - return; - } - - // Compute smooth margin - bool needsSmoothMargins = d->mTransformationMode == Qt::SmoothTransformation; - - int sourceLeftMargin, sourceRightMargin, sourceTopMargin, sourceBottomMargin; - int destLeftMargin, destRightMargin, destTopMargin, destBottomMargin; - if (needsSmoothMargins) { - sourceLeftMargin = qMin(sourceRect.left(), SMOOTH_MARGIN); - sourceTopMargin = qMin(sourceRect.top(), SMOOTH_MARGIN); - sourceRightMargin = qMin(imageRect.right() - sourceRect.right(), SMOOTH_MARGIN); - sourceBottomMargin = qMin(imageRect.bottom() - sourceRect.bottom(), SMOOTH_MARGIN); - sourceRect.adjust( - -sourceLeftMargin, - -sourceTopMargin, - sourceRightMargin, - sourceBottomMargin); - destLeftMargin = int(sourceLeftMargin * zoom); - destTopMargin = int(sourceTopMargin * zoom); - destRightMargin = int(sourceRightMargin * zoom); - destBottomMargin = int(sourceBottomMargin * zoom); - } else { - sourceLeftMargin = sourceRightMargin = sourceTopMargin = sourceBottomMargin = 0; - destLeftMargin = destRightMargin = destTopMargin = destBottomMargin = 0; - } - - // destRect is almost like rect, but it contains only "full" pixels - QRect destRect = Gwenview::scaledRect(sourceRect, zoom); - - QRect dpSourceRect = Gwenview::scaledRect(sourceRect, dpr); - QRect dpDestRect = Gwenview::scaledRect(dpSourceRect, zoom); - - QImage tmp; - tmp = image.copy(dpSourceRect); - tmp = tmp.scaled( - dpDestRect.width(), - dpDestRect.height(), - Qt::IgnoreAspectRatio, // Do not use KeepAspectRatio, it can lead to skipped rows or columns - d->mTransformationMode); - - if (needsSmoothMargins) { - tmp = tmp.copy( - destLeftMargin * dpr, destTopMargin * dpr, - dpDestRect.width() - (destLeftMargin + destRightMargin) * dpr, - dpDestRect.height() - (destTopMargin + destBottomMargin) * dpr - ); - } - - tmp.setDevicePixelRatio(dpr); - emit scaledRect(destRect.left() + destLeftMargin, destRect.top() + destTopMargin, tmp); -} - -} // namespace diff --git a/lib/imagescaler.h b/lib/imagescaler.h deleted file mode 100644 index bfced9df..00000000 --- a/lib/imagescaler.h +++ /dev/null @@ -1,65 +0,0 @@ -/* -Gwenview: an image viewer -Copyright 2007 Aurélien Gâteau - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -*/ -#ifndef IMAGESCALER_H -#define IMAGESCALER_H - -// Qt -#include - -// KF - -// local -#include -#include - -class QImage; -class QRect; -class QRegion; - -namespace Gwenview -{ - -class Document; - -struct ImageScalerPrivate; -class GWENVIEWLIB_EXPORT ImageScaler : public QObject -{ - Q_OBJECT -public: - explicit ImageScaler(QObject* parent = nullptr); - ~ImageScaler() override; - void setDocument(const Document::Ptr&); - void setZoom(qreal); - void setDestinationRegion(const QRegion&); - -Q_SIGNALS: - void scaledRect(int left, int top, const QImage&); - -private: - ImageScalerPrivate * const d; - void scaleRect(const QRect&); - -private Q_SLOTS: - void doScale(); -}; - -} // namespace - -#endif /* IMAGESCALER_H */ diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index 33c5bc9b..a2c7dfb3 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -26,7 +26,6 @@ include_directories( set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) -gv_add_unit_test(imagescalertest testutils.cpp) if (KF5KDcraw_FOUND) gv_add_unit_test(documenttest testutils.cpp) endif() diff --git a/tests/auto/imagescalertest.cpp b/tests/auto/imagescalertest.cpp deleted file mode 100644 index 3a531455..00000000 --- a/tests/auto/imagescalertest.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/* -Gwenview: an image viewer -Copyright 2007 Aurélien Gâteau - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -*/ -#include - -#include "imagescalertest.h" - -#include "../lib/document/documentfactory.h" - -#include "testutils.h" - -QTEST_MAIN(ImageScalerTest) - -using namespace Gwenview; - -/** - * Scale whole image in one pass - */ -void ImageScalerTest::testScaleFullImage() -{ - const qreal zoom = 2; - QUrl url = urlForTestFile("test.png"); - Document::Ptr doc = DocumentFactory::instance()->load(url); - - // Wait for meta info because we need the document size - while (doc->loadingState() < Document::MetaInfoLoaded) { - QTest::qWait(500); - } - - ImageScaler scaler; - ImageScalerClient client(&scaler); - scaler.setDocument(doc); - scaler.setZoom(zoom); - - scaler.setDestinationRegion(QRect(QPoint(0, 0), doc->size() * zoom)); - - QSignalSpy spy(&scaler, SIGNAL(scaledRect(int,int,QImage))); - - bool ok = spy.wait(30); - QVERIFY2(ok, "ImageScaler did not emit scaledRect() signal in time"); - - // Document should be fully loaded by the time image scaler is done - QCOMPARE(doc->loadingState(), Document::Loaded); - - QImage scaledImage = client.createFullImage(); - - QImage expectedImage = doc->image().scaled(doc->size() * zoom, - Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - QVERIFY(TestUtils::imageCompare(scaledImage, expectedImage)); -} - -#if 0 -/** - * Scale parts of an image - * In this test, the result image should be missing its bottom-right corner - */ -void ImageScalerTest::testScalePartialImage() -{ - QImage image(10, 10, QImage::Format_ARGB32); - const int zoom = 2; - { - QPainter painter(&image); - painter.fillRect(image.rect(), Qt::white); - painter.drawText(0, image.height(), "X"); - } - - Gwenview::ImageScaler scaler; - ImageScalerClient client(&scaler); - scaler.setImage(&image); - scaler.setZoom(zoom); - QRegion region; - region |= QRect( - 0, 0, - image.width() * zoom / 2, image.height() * zoom); - - region |= QRect( - 0, 0, - image.width() * zoom, image.height() * zoom / 2); - scaler.setDestinationRegion(region); - - QImage expectedImage(image.size() * zoom, image.format()); - expectedImage.fill(0); - { - QPainter painter(&expectedImage); - QImage tmp; - tmp = image.copy(0, 0, - expectedImage.width() / zoom / 2, - expectedImage.height() / zoom); - painter.drawImage(0, 0, tmp.scaled(tmp.size() * zoom)); - tmp = image.copy(0, 0, - expectedImage.width() / zoom, - expectedImage.height() / zoom / 2); - painter.drawImage(0, 0, tmp.scaled(tmp.size() * zoom)); - } - - QImage scaledImage = client.createFullImage(); - - QCOMPARE(scaledImage, expectedImage); -} - -/** - * Scale whole image in two passes, not using exact pixel boundaries - */ -void ImageScalerTest::testScaleFullImageTwoPasses() -{ - QFETCH(qreal, zoom); - QImage image(10, 10, QImage::Format_ARGB32); - { - QPainter painter(&image); - painter.fillRect(image.rect(), Qt::white); - painter.drawLine(0, 0, image.width(), image.height()); - } - - Gwenview::ImageScaler scaler; - ImageScalerClient client(&scaler); - - scaler.setImage(&image); - scaler.setZoom(zoom); - int zWidth = int(image.width() * zoom); - int zHeight = int(image.width() * zoom); - int partialZWidth = zWidth / 3; - scaler.setDestinationRegion( - QRect( - 0, 0, - partialZWidth, zHeight) - ); - - scaler.setDestinationRegion( - QRect( - partialZWidth, 0, - zWidth - partialZWidth, zHeight) - ); - - QImage expectedImage = image.scaled(image.size() * zoom); - - QImage scaledImage = client.createFullImage(); - QCOMPARE(expectedImage, scaledImage); -} - -void ImageScalerTest::testScaleFullImageTwoPasses_data() -{ - QTest::addColumn("zoom"); - - QTest::newRow("0.5") << 0.5; - QTest::newRow("2.0") << 2.0; - QTest::newRow("4.0") << 4.0; -} - -/** - * When zooming out, make sure that we don't crash when scaling an area which - * have one dimension smaller than one pixel in the destination. - */ -void ImageScalerTest::testScaleThinArea() -{ - QImage image(10, 10, QImage::Format_ARGB32); - image.fill(0); - - Gwenview::ImageScaler scaler; - - const qreal zoom = 0.25; - scaler.setImage(&image); - scaler.setZoom(zoom); - scaler.setDestinationRegion(QRect(0, 0, image.width(), 2)); -} - -/** - * Test instantiating a scaler without setting an image won't crash - */ -void ImageScalerTest::testDontCrashWithoutImage() -{ - Gwenview::ImageScaler scaler; - scaler.setZoom(1.0); - scaler.setDestinationRegion(QRect(0, 0, 10, 10)); -} - -/** - * Test that scaling down a big image (==bigger than MAX_CHUNK_AREA) does not - * produce any gap - */ -void ImageScalerTest::testScaleDownBigImage() -{ - QImage image(1704, 2272, QImage::Format_RGB32); - image.fill(255); - - Gwenview::ImageScaler scaler; - ImageScalerClient client(&scaler); - - const qreal zoom = 0.28125; - scaler.setImage(&image); - scaler.setZoom(zoom); - scaler.setDestinationRegion(QRect(QPoint(0, 0), image.size() * zoom)); - - QImage scaledImage = client.createFullImage(); - - QImage expectedImage = image.scaled(scaledImage.size()); - QCOMPARE(expectedImage, scaledImage); -} -#endif diff --git a/tests/auto/imagescalertest.h b/tests/auto/imagescalertest.h deleted file mode 100644 index dd998693..00000000 --- a/tests/auto/imagescalertest.h +++ /dev/null @@ -1,104 +0,0 @@ -/* -Gwenview: an image viewer -Copyright 2007 Aurélien Gâteau - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -*/ -#ifndef IMAGESCALERTEST_H -#define IMAGESCALERTEST_H - -#include "../lib/imagescaler.h" - -// Qt -#include -#include -#include - -// KF - -class ImageScalerClient : public QObject -{ - Q_OBJECT -public: - ImageScalerClient(Gwenview::ImageScaler* scaler) - { - connect(scaler, &Gwenview::ImageScaler::scaledRect, - this, &ImageScalerClient::slotScaledRect); - } - - struct ImageInfo - { - int left; - int top; - QImage image; - }; - QVector mImageInfoList; - - QImage createFullImage() - { - Q_ASSERT(mImageInfoList.size() > 0); - QImage::Format format = mImageInfoList[0].image.format(); - - int imageWidth = 0; - int imageHeight = 0; - for (const ImageInfo & info : qAsConst(mImageInfoList)) { - int right = info.left + info.image.width(); - int bottom = info.top + info.image.height(); - imageWidth = qMax(imageWidth, right); - imageHeight = qMax(imageHeight, bottom); - } - - QImage image(imageWidth, imageHeight, format); - image.fill(0); - QPainter painter(&image); - for (const ImageInfo & info : qAsConst(mImageInfoList)) { - painter.drawImage(info.left, info.top, info.image); - } - return image; - } - -public Q_SLOTS: - void slotScaledRect(int left, int top, const QImage& image) - { - ImageInfo info; - info.left = left; - info.top = top; - info.image = image; - - mImageInfoList.append(info); - } -}; - -class ImageScalerTest : public QObject -{ - Q_OBJECT - -private Q_SLOTS: - void testScaleFullImage(); - - // FIXME Disabled for now, does not compile since ImageScaler::setImage() has - // been replaced with ImageScaler::setDocument() -#if 0 - void testScalePartialImage(); - void testScaleFullImageTwoPasses(); - void testScaleFullImageTwoPasses_data(); - void testScaleThinArea(); - void testDontCrashWithoutImage(); - void testScaleDownBigImage(); -#endif -}; - -#endif // IMAGESCALERTEST_H -- GitLab