File 0003-HiDPI-Support-for-Gwenview.patch of Package gwenview5

From 1370552e49406d2869f56eea57a3ab8e1a17e1b3 Mon Sep 17 00:00:00 2001
From: Alexander Volkov <a.volkov@rusbitech.ru>
Date: Mon, 1 Apr 2019 16:40:35 +0300
Subject: [PATCH] HiDPI Support for Gwenview

Summary:
Initial support for HiDPI-scaling of documents in RasterImageView.

This patch scales up images to display them correctly on HiDPI-enabled screens.

TODO:
- SVG documents and videos
- Scaling of thumbnails

BUG: 373178

Reviewers: davidedmundson, rkflx, hetzenecker, ngraham, #gwenview

Reviewed By: ngraham, #gwenview

Subscribers: volkov, asturmlechner, fvogt, abalaji, rkflx, ngraham, anthonyfieroni, cfeck, asn

Tags: #gwenview

Differential Revision: https://phabricator.kde.org/D7581
---
 lib/documentview/abstractimageview.cpp | 36 +++++++++++++-------
 lib/documentview/abstractimageview.h   | 12 +++++++
 lib/documentview/birdeyeview.cpp       |  4 +--
 lib/documentview/documentview.cpp      |  7 +++-
 lib/documentview/rasterimageview.cpp   | 15 ++++++---
 lib/imagescaler.cpp                    | 60 +++++++++++++++++++++-------------
 6 files changed, 93 insertions(+), 41 deletions(-)

diff --git a/lib/documentview/abstractimageview.cpp b/lib/documentview/abstractimageview.cpp
index bcd77c21..25b9a35f 100644
--- a/lib/documentview/abstractimageview.cpp
+++ b/lib/documentview/abstractimageview.cpp
@@ -32,6 +32,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA
 #include <QGraphicsSceneMouseEvent>
 #include <QStandardPaths>
 #include <QPainter>
+#include <QApplication>
 
 namespace Gwenview
 {
@@ -63,7 +64,7 @@ struct AbstractImageViewPrivate
 
     void adjustImageOffset(Verbosity verbosity = Notify)
     {
-        QSizeF zoomedDocSize = q->documentSize() * mZoom;
+        QSizeF zoomedDocSize = q->dipDocumentSize() * mZoom;
         QSizeF viewSize = q->boundingRect().size();
         QPointF offset(
             qMax((viewSize.width() - zoomedDocSize.width()) / 2, qreal(0.)),
@@ -88,7 +89,7 @@ struct AbstractImageViewPrivate
             mScrollPos = _newPos;
             return;
         }
-        QSizeF zoomedDocSize = q->documentSize() * mZoom;
+        QSizeF zoomedDocSize = q->dipDocumentSize() * mZoom;
         QSizeF viewSize = q->boundingRect().size();
         QPointF newPos(
             qBound(qreal(0.), _newPos.x(), zoomedDocSize.width() - viewSize.width()),
@@ -123,6 +124,7 @@ struct AbstractImageViewPrivate
         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;
     }
 
@@ -185,6 +187,11 @@ QSizeF AbstractImageView::documentSize() const
     return d->mDocument ? d->mDocument->size() : QSizeF();
 }
 
+QSizeF AbstractImageView::dipDocumentSize() const
+{
+    return d->mDocument ? d->mDocument->size() / devicePixelRatio(): QSizeF();
+}
+
 qreal AbstractImageView::zoom() const
 {
     return d->mZoom;
@@ -335,7 +342,7 @@ void AbstractImageView::focusInEvent(QFocusEvent* event)
 
 qreal AbstractImageView::computeZoomToFit() const
 {
-    QSizeF docSize = documentSize();
+    QSizeF docSize = dipDocumentSize();
     if (docSize.isEmpty()) {
         return 1;
     }
@@ -351,7 +358,7 @@ qreal AbstractImageView::computeZoomToFit() const
 
 qreal AbstractImageView::computeZoomToFill() const
 {
-    QSizeF docSize = documentSize();
+    QSizeF docSize = dipDocumentSize();
     if (docSize.isEmpty()) {
         return 1;
     }
@@ -483,7 +490,7 @@ void AbstractImageView::keyPressEvent(QKeyEvent* event)
         d->setScrollPos(QPointF(d->mScrollPos.x(), 0));
         return;
     case Qt::Key_End:
-        d->setScrollPos(QPointF(d->mScrollPos.x(), documentSize().height() * zoom()));
+        d->setScrollPos(QPointF(d->mScrollPos.x(), dipDocumentSize().height() * zoom()));
         return;
     default:
         return;
@@ -523,9 +530,14 @@ void AbstractImageView::setScrollPos(const QPointF& pos)
     d->setScrollPos(pos);
 }
 
+qreal AbstractImageView::devicePixelRatio() const
+{
+    return qApp->devicePixelRatio();
+}
+
 QPointF AbstractImageView::mapToView(const QPointF& imagePos) const
 {
-    return imagePos * d->mZoom + d->mImageOffset - d->mScrollPos;
+    return imagePos / devicePixelRatio() * d->mZoom + d->mImageOffset - d->mScrollPos;
 }
 
 QPoint AbstractImageView::mapToView(const QPoint& imagePos) const
@@ -537,7 +549,7 @@ QRectF AbstractImageView::mapToView(const QRectF& imageRect) const
 {
     return QRectF(
                mapToView(imageRect.topLeft()),
-               imageRect.size() * zoom()
+               imageRect.size() * zoom() / devicePixelRatio()
            );
 }
 
@@ -545,13 +557,13 @@ QRect AbstractImageView::mapToView(const QRect& imageRect) const
 {
     return QRect(
                mapToView(imageRect.topLeft()),
-               imageRect.size() * zoom()
+               imageRect.size() * zoom() / devicePixelRatio()
            );
 }
 
 QPointF AbstractImageView::mapToImage(const QPointF& viewPos) const
 {
-    return (viewPos - d->mImageOffset + d->mScrollPos) / d->mZoom;
+    return (viewPos - d->mImageOffset + d->mScrollPos) / d->mZoom * devicePixelRatio();
 }
 
 QPoint AbstractImageView::mapToImage(const QPoint& viewPos) const
@@ -563,7 +575,7 @@ QRectF AbstractImageView::mapToImage(const QRectF& viewRect) const
 {
     return QRectF(
                mapToImage(viewRect.topLeft()),
-               viewRect.size() / zoom()
+               viewRect.size() / zoom() * devicePixelRatio()
            );
 }
 
@@ -571,7 +583,7 @@ QRect AbstractImageView::mapToImage(const QRect& viewRect) const
 {
     return QRect(
                mapToImage(viewRect.topLeft()),
-               viewRect.size() / zoom()
+               viewRect.size() / zoom() * devicePixelRatio()
            );
 }
 
@@ -601,7 +613,7 @@ QSizeF AbstractImageView::visibleImageSize() const
     if (!document()) {
         return QSizeF();
     }
-    QSizeF size = documentSize() * zoom();
+    QSizeF size = dipDocumentSize() * zoom();
     return size.boundedTo(boundingRect().size());
 }
 
diff --git a/lib/documentview/abstractimageview.h b/lib/documentview/abstractimageview.h
index f5a211e9..807c2aa2 100644
--- a/lib/documentview/abstractimageview.h
+++ b/lib/documentview/abstractimageview.h
@@ -75,12 +75,22 @@ public:
 
     QSizeF documentSize() const;
 
+    /**
+     * Returns the size of the loaded document in device independent pixels.
+    */
+    QSizeF dipDocumentSize() const;
+
+    /*
+     * The size of the image that is currently visible,
+     * in device independent pixels.
+     */
     QSizeF visibleImageSize() const;
 
     /**
      * If the image is smaller than the view, imageOffset is the distance from
      * the topleft corner of the view to the topleft corner of the image.
      * Neither x nor y can be negative.
+     * This is in device independent pixels.
      */
     QPointF imageOffset() const;
 
@@ -91,6 +101,8 @@ public:
     QPointF scrollPos() const;
     void setScrollPos(const QPointF& pos);
 
+    qreal devicePixelRatio() const;
+
     QPointF mapToView(const QPointF& imagePos) const;
     QPoint mapToView(const QPoint& imagePos) const;
     QRectF mapToView(const QRectF& imageRect) const;
diff --git a/lib/documentview/birdeyeview.cpp b/lib/documentview/birdeyeview.cpp
index 55dd5ba7..9f35a4b1 100644
--- a/lib/documentview/birdeyeview.cpp
+++ b/lib/documentview/birdeyeview.cpp
@@ -138,7 +138,7 @@ void BirdEyeView::adjustGeometry()
     if (!d->mDocView->canZoom() || d->mDocView->zoomToFit()) {
         return;
     }
-    QSizeF size = d->mDocView->document()->size();
+    QSizeF size = d->mDocView->document()->size() / qApp->devicePixelRatio();
     size.scale(MIN_SIZE, MIN_SIZE, Qt::KeepAspectRatioByExpanding);
     QRectF docViewRect = d->mDocView->boundingRect();
     int maxBevHeight = docViewRect.height() - 2 * VIEW_OFFSET;
@@ -163,7 +163,7 @@ void BirdEyeView::adjustGeometry()
 
 void BirdEyeView::adjustVisibleRect()
 {
-    QSizeF docSize = d->mDocView->document()->size();
+    QSizeF docSize = d->mDocView->document()->size() / qApp->devicePixelRatio();
     qreal viewZoom = d->mDocView->zoom();
     qreal bevZoom;
     if (docSize.height() > docSize.width()) {
diff --git a/lib/documentview/documentview.cpp b/lib/documentview/documentview.cpp
index 2035df28..f7a4359f 100644
--- a/lib/documentview/documentview.cpp
+++ b/lib/documentview/documentview.cpp
@@ -42,6 +42,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA
 #include <QDrag>
 #include <QMimeData>
 #include <QStyleHints>
+#include <QGestureEvent>
+#include <QLibraryInfo>
 
 // KDE
 #include <KLocalizedString>
@@ -455,7 +457,10 @@ DocumentView::DocumentView(QGraphicsScene* scene)
     // This is important for fade effects, where we don't want any background layers visible during the fade.
     d->mOpacityEffect = new QGraphicsOpacityEffect(this);
     d->mOpacityEffect->setOpacity(0);
-    setGraphicsEffect(d->mOpacityEffect);
+
+    // QTBUG-74963. QGraphicsOpacityEffect cause painting an image as non-highdpi.
+    if (qFuzzyCompare(qApp->devicePixelRatio(), 1.0) || QLibraryInfo::version() >= QVersionNumber(5, 12, 4))
+        setGraphicsEffect(d->mOpacityEffect);
 
     scene->addItem(this);
 
diff --git a/lib/documentview/rasterimageview.cpp b/lib/documentview/rasterimageview.cpp
index a0aa86ad..da2a28a1 100644
--- a/lib/documentview/rasterimageview.cpp
+++ b/lib/documentview/rasterimageview.cpp
@@ -26,6 +26,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA
 #include <lib/imagescaler.h>
 #include <lib/cms/cmsprofile.h>
 #include <lib/gvdebug.h>
+#include <lib/paintutils.h>
 
 // KDE
 
@@ -35,6 +36,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA
 #include <QTimer>
 #include <QPointer>
 #include <QDebug>
+#include <QApplication>
 
 
 namespace Gwenview
@@ -139,8 +141,9 @@ struct RasterImageViewPrivate
 
     void resizeBuffer()
     {
+        const auto dpr = q->devicePixelRatio();
         QSize size = q->visibleImageSize().toSize();
-        if (size == mCurrentBuffer.size()) {
+        if (size * dpr == mCurrentBuffer.size()) {
             return;
         }
         if (!size.isValid()) {
@@ -149,7 +152,8 @@ struct RasterImageViewPrivate
             return;
         }
 
-        mAlternateBuffer = QPixmap(size);
+        mAlternateBuffer = QPixmap(size * dpr);
+        mAlternateBuffer.setDevicePixelRatio(dpr);
         mAlternateBuffer.fill(Qt::transparent);
         {
             QPainter painter(&mAlternateBuffer);
@@ -384,6 +388,7 @@ void RasterImageView::onScrollPosChanged(const QPointF& oldPos)
     {
         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);
@@ -392,7 +397,7 @@ void RasterImageView::onScrollPosChanged(const QPointF& oldPos)
     qSwap(d->mCurrentBuffer, d->mAlternateBuffer);
 
     // Scale missing parts
-    QRegion bufferRegion = QRegion(d->mCurrentBuffer.rect().translated(scrollPos().toPoint()));
+    QRegion bufferRegion = QRect(scrollPos().toPoint(), d->mCurrentBuffer.size() / devicePixelRatio());
     QRegion updateRegion = bufferRegion - bufferRegion.translated(-delta.toPoint());
     updateBuffer(updateRegion);
     update();
@@ -400,13 +405,15 @@ void RasterImageView::onScrollPosChanged(const QPointF& oldPos)
 
 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(), (documentSize() * zoom()).toSize());
+        const QRect rect = QRect(topLeft.toPoint(), (dipDocumentSize() * zoom()).toSize());
         painter->drawPixmap(rect, d->mCurrentBuffer);
     } else {
         painter->drawPixmap(topLeft.toPoint(), d->mCurrentBuffer);
diff --git a/lib/imagescaler.cpp b/lib/imagescaler.cpp
index 75a38798..b6acb4c2 100644
--- a/lib/imagescaler.cpp
+++ b/lib/imagescaler.cpp
@@ -23,6 +23,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 #include <QImage>
 #include <QRegion>
 #include <QDebug>
+#include <QApplication>
 
 // KDE
 
@@ -45,6 +46,19 @@ 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;
@@ -126,9 +140,15 @@ void ImageScaler::doScale()
 
 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(rect);
+        QImage tmp = d->mDocument->image().copy(dpRect);
+        tmp.setDevicePixelRatio(dpr);
         emit scaledRect(rect.left(), rect.top(), tmp);
         return;
     }
@@ -144,14 +164,12 @@ void ImageScaler::scaleRect(const QRect& rect)
         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(
-        rect.left() / zoom,
-        rect.top() / zoom,
-        rect.width() / zoom,
-        rect.height() / zoom);
+    QRectF sourceRectF = Gwenview::scaledRect(QRectF(rect), 1.0 / zoom);
 
-    sourceRectF = sourceRectF.intersected(image.rect());
+    sourceRectF = sourceRectF.intersected(imageRect);
     QRect sourceRect = PaintUtils::containingRect(sourceRectF);
     if (sourceRect.isEmpty()) {
         return;
@@ -165,8 +183,8 @@ void ImageScaler::scaleRect(const QRect& rect)
     if (needsSmoothMargins) {
         sourceLeftMargin = qMin(sourceRect.left(), SMOOTH_MARGIN);
         sourceTopMargin = qMin(sourceRect.top(), SMOOTH_MARGIN);
-        sourceRightMargin = qMin(image.rect().right() - sourceRect.right(), SMOOTH_MARGIN);
-        sourceBottomMargin = qMin(image.rect().bottom() - sourceRect.bottom(), SMOOTH_MARGIN);
+        sourceRightMargin = qMin(imageRect.right() - sourceRect.right(), SMOOTH_MARGIN);
+        sourceBottomMargin = qMin(imageRect.bottom() - sourceRect.bottom(), SMOOTH_MARGIN);
         sourceRect.adjust(
             -sourceLeftMargin,
             -sourceTopMargin,
@@ -182,30 +200,28 @@ void ImageScaler::scaleRect(const QRect& rect)
     }
 
     // destRect is almost like rect, but it contains only "full" pixels
-    QRectF destRectF = QRectF(
-                           sourceRect.left() * zoom,
-                           sourceRect.top() * zoom,
-                           sourceRect.width() * zoom,
-                           sourceRect.height() * zoom
-                       );
-    QRect destRect = PaintUtils::containingRect(destRectF);
+    QRect destRect = Gwenview::scaledRect(sourceRect, zoom);
+
+    QRect dpSourceRect = Gwenview::scaledRect(sourceRect, dpr);
+    QRect dpDestRect = Gwenview::scaledRect(dpSourceRect, zoom);
 
     QImage tmp;
-    tmp = image.copy(sourceRect);
+    tmp = image.copy(dpSourceRect);
     tmp = tmp.scaled(
-              destRect.width(),
-              destRect.height(),
+              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, destTopMargin,
-                  destRect.width() - (destLeftMargin + destRightMargin),
-                  destRect.height() - (destTopMargin + destBottomMargin)
+                  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);
 }
 
-- 
2.16.4

openSUSE Build Service is sponsored by