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