Index: lib/document/document.h =================================================================== --- lib/document/document.h +++ lib/document/document.h @@ -133,13 +133,35 @@ void waitUntilLoaded(); + /** + * Returns the size of the loaded document + * This value is NOT adjusted for HiDPI rendering - + * meaning that the real size of the image will be returned + */ QSize size() const; + /** + * Returns the size of the loaded document + * This value is adjusted for HiDPI rendering - + * meaning that the scaled size of the image will be returned + */ + QSize scaledSize() const; + + /** + * Returns the width of the loaded document + * This value is NOT adjusted for HiDPI rendering - + * meaning that the real size of the image will be returned + */ int width() const { return size().width(); } + /** + * Returns the height of the loaded document + * This value is NOT adjusted for HiDPI rendering - + * meaning that the real size of the image will be returned + */ int height() const { return size().height(); Index: lib/document/document.cpp =================================================================== --- lib/document/document.cpp +++ lib/document/document.cpp @@ -352,6 +352,11 @@ return d->mSize; } +QSize Document::scaledSize() const +{ + return d->mSize / qApp->devicePixelRatio(); +} + bool Document::hasAlphaChannel() const { if (d->mImage.isNull()) { Index: lib/document/documentloadedimpl.cpp =================================================================== --- lib/document/documentloadedimpl.cpp +++ lib/document/documentloadedimpl.cpp @@ -28,6 +28,7 @@ #include #include #include +#include // KDE @@ -35,6 +36,7 @@ #include "documentjob.h" #include "imageutils.h" #include "savejob.h" +#include "lib/paintutils.h" namespace Gwenview { @@ -63,7 +65,9 @@ void DocumentLoadedImpl::init() { if (!d->mQuietInit) { - emit imageRectUpdated(document()->image().rect()); + const qreal dpr = qApp->devicePixelRatio(); + QRect imageRect = PaintUtils::shrinkRect(document()->image().rect(), dpr); + emit imageRectUpdated(imageRect); emit loaded(); } } @@ -103,7 +107,9 @@ void DocumentLoadedImpl::setImage(const QImage& image) { setDocumentImage(image); - imageRectUpdated(image.rect()); + const qreal dpr = qApp->devicePixelRatio(); + QRect imageRect = PaintUtils::shrinkRect(image.rect(), dpr); + imageRectUpdated(imageRect); } void DocumentLoadedImpl::applyTransformation(Orientation orientation) @@ -112,7 +118,9 @@ QMatrix matrix = ImageUtils::transformMatrix(orientation); image = image.transformed(matrix); setDocumentImage(image); - imageRectUpdated(image.rect()); + const qreal dpr = qApp->devicePixelRatio(); + QRect imageRect = PaintUtils::shrinkRect(image.rect(), dpr); + imageRectUpdated(imageRect); } QByteArray DocumentLoadedImpl::rawData() const Index: lib/documentview/abstractimageview.h =================================================================== --- lib/documentview/abstractimageview.h +++ lib/documentview/abstractimageview.h @@ -67,14 +67,31 @@ qreal computeZoomToFitWidth() const; + /** + * Returns the size of the loaded document + * This value is NOT adjusted for HiDPI rendering - + * meaning that the real size of the image will be returned + */ QSizeF documentSize() const; + /** + * Returns the size of the loaded document + * This value is adjusted for HiDPI rendering - + * meaning that the scaled size of the image will be returned + */ + QSizeF scaledDocumentSize() const; + + /* + * The size of the image that is currently visible, + * in HiDPI-scaled coordinates + */ 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 image coordinates and NOT adjusted for HiDPI rendering. */ QPointF imageOffset() const; Index: lib/documentview/abstractimageview.cpp =================================================================== --- lib/documentview/abstractimageview.cpp +++ lib/documentview/abstractimageview.cpp @@ -30,6 +30,7 @@ #include #include #include +#include namespace Gwenview { @@ -58,7 +59,7 @@ void adjustImageOffset(Verbosity verbosity = Notify) { - QSizeF zoomedDocSize = q->documentSize() * mZoom; + QSizeF zoomedDocSize = q->scaledDocumentSize() * mZoom; QSizeF viewSize = q->boundingRect().size(); QPointF offset( qMax((viewSize.width() - zoomedDocSize.width()) / 2, qreal(0.)), @@ -83,7 +84,7 @@ mScrollPos = _newPos; return; } - QSizeF zoomedDocSize = q->documentSize() * mZoom; + QSizeF zoomedDocSize = q->scaledDocumentSize() * mZoom; QSizeF viewSize = q->boundingRect().size(); QPointF newPos( qBound(qreal(0.), _newPos.x(), zoomedDocSize.width() - viewSize.width()), @@ -157,6 +158,11 @@ return d->mDocument ? d->mDocument->size() : QSizeF(); } +QSizeF AbstractImageView::scaledDocumentSize() const +{ + return d->mDocument ? d->mDocument->scaledSize() : QSizeF(); +} + qreal AbstractImageView::zoom() const { return d->mZoom; @@ -213,7 +219,7 @@ of the new offset is not 0, we won't be able to center on a specific point in that direction. */ - QPointF oldScroll = scrollPos() - imageOffset(); + QPointF oldScroll = scrollPos() - (imageOffset() / qApp->devicePixelRatio()); QPointF scroll = (zoom / oldZoom) * (oldScroll + center) - center; @@ -285,7 +291,7 @@ qreal AbstractImageView::computeZoomToFit() const { - QSizeF docSize = documentSize(); + QSizeF docSize = scaledDocumentSize(); if (docSize.isEmpty()) { return 1; } @@ -301,7 +307,7 @@ qreal AbstractImageView::computeZoomToFitWidth() const { - QSizeF docSize = documentSize(); + QSizeF docSize = scaledDocumentSize(); if (docSize.isEmpty()) { return 1; } @@ -444,7 +450,7 @@ 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(), scaledDocumentSize().height() * zoom())); return; default: return; @@ -484,7 +490,7 @@ QPointF AbstractImageView::mapToView(const QPointF& imagePos) const { - return imagePos * d->mZoom + d->mImageOffset - d->mScrollPos; + return (imagePos / qApp->devicePixelRatio()) * d->mZoom + d->mImageOffset - d->mScrollPos; } QPoint AbstractImageView::mapToView(const QPoint& imagePos) const @@ -496,7 +502,7 @@ { return QRectF( mapToView(imageRect.topLeft()), - imageRect.size() * zoom() + imageRect.size() * zoom() / qApp->devicePixelRatio() ); } @@ -504,13 +510,13 @@ { return QRect( mapToView(imageRect.topLeft()), - imageRect.size() * zoom() + imageRect.size() * zoom() / qApp->devicePixelRatio() ); } QPointF AbstractImageView::mapToImage(const QPointF& viewPos) const { - return (viewPos - d->mImageOffset + d->mScrollPos) / d->mZoom; + return (viewPos - d->mImageOffset + d->mScrollPos) / d->mZoom * qApp->devicePixelRatio(); } QPoint AbstractImageView::mapToImage(const QPoint& viewPos) const @@ -522,7 +528,7 @@ { return QRectF( mapToImage(viewRect.topLeft()), - viewRect.size() / zoom() + viewRect.size() / zoom() * qApp->devicePixelRatio() ); } @@ -530,7 +536,7 @@ { return QRect( mapToImage(viewRect.topLeft()), - viewRect.size() / zoom() + viewRect.size() / zoom() * qApp->devicePixelRatio() ); } @@ -560,7 +566,7 @@ if (!document()) { return QSizeF(); } - QSizeF size = documentSize() * zoom(); + QSizeF size = scaledDocumentSize() * zoom(); return size.boundedTo(boundingRect().size()); } Index: lib/documentview/birdeyeview.cpp =================================================================== --- lib/documentview/birdeyeview.cpp +++ lib/documentview/birdeyeview.cpp @@ -138,7 +138,7 @@ if (!d->mDocView->canZoom() || d->mDocView->zoomToFit()) { return; } - QSizeF size = d->mDocView->document()->size(); + QSizeF size = d->mDocView->document()->scaledSize(); 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::adjustVisibleRect() { - QSizeF docSize = d->mDocView->document()->size(); + QSizeF docSize = d->mDocView->document()->scaledSize(); qreal viewZoom = d->mDocView->zoom(); qreal bevZoom; if (docSize.height() > docSize.width()) { Index: lib/documentview/rasterimageview.cpp =================================================================== --- lib/documentview/rasterimageview.cpp +++ lib/documentview/rasterimageview.cpp @@ -26,6 +26,7 @@ #include #include #include +#include // KDE @@ -35,6 +36,7 @@ #include #include #include +#include // LCMS2 #include @@ -140,7 +142,7 @@ QRectF mapViewportToZoomedImage(const QRectF& viewportRect) const { return QRectF( - viewportRect.topLeft() - q->imageOffset() + q->scrollPos(), + viewportRect.topLeft() - (q->imageOffset() / qApp->devicePixelRatio()) + q->scrollPos(), viewportRect.size() ); } @@ -163,7 +165,8 @@ return; } - mAlternateBuffer = QPixmap(size); + mAlternateBuffer = QPixmap(size * qApp->devicePixelRatio()); + mAlternateBuffer.setDevicePixelRatio(qApp->devicePixelRatio()); mAlternateBuffer.fill(Qt::transparent); { QPainter painter(&mAlternateBuffer); @@ -377,6 +380,7 @@ { if (d->mAlternateBuffer.size() != d->mCurrentBuffer.size()) { d->mAlternateBuffer = QPixmap(d->mCurrentBuffer.size()); + d->mAlternateBuffer.setDevicePixelRatio(d->mCurrentBuffer.devicePixelRatio()); } QPainter painter(&d->mAlternateBuffer); painter.drawPixmap(-delta, d->mCurrentBuffer); @@ -384,7 +388,9 @@ qSwap(d->mCurrentBuffer, d->mAlternateBuffer); // Scale missing parts - QRegion bufferRegion = QRegion(d->mCurrentBuffer.rect().translated(scrollPos().toPoint())); + const qreal dpr = qApp->devicePixelRatio(); + QRect scaledCurrentBufferRect = PaintUtils::shrinkRect(d->mCurrentBuffer.rect(), dpr); + QRegion bufferRegion = QRegion(scaledCurrentBufferRect.translated(scrollPos().toPoint())); QRegion updateRegion = bufferRegion - bufferRegion.translated(-delta.toPoint()); updateBuffer(updateRegion); update(); @@ -392,12 +398,14 @@ void RasterImageView::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) { + d->mCurrentBuffer.setDevicePixelRatio(qApp->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. - QSizeF size = documentSize() * zoom(); + QSizeF size = scaledDocumentSize() * zoom(); painter->drawPixmap(topLeft.x(), topLeft.y(), size.width(), size.height(), d->mCurrentBuffer); } else { painter->drawPixmap(topLeft, d->mCurrentBuffer); Index: lib/imagescaler.cpp =================================================================== --- lib/imagescaler.cpp +++ lib/imagescaler.cpp @@ -23,6 +23,7 @@ #include #include #include +#include // KDE @@ -126,9 +127,16 @@ void ImageScaler::scaleRect(const QRect& rect) { + const qreal dpr = qApp->devicePixelRatio(); + + /* variables prefixed with d are in the device pixels coordinate system, which translates to the rendered output - that means, + * multiplied with the device pixel ratio of the target PaintDevice */ + const QRect dRect = PaintUtils::enlargeRect(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(dRect); + tmp.setDevicePixelRatio(dpr); scaledRect(rect.left(), rect.top(), tmp); return; } @@ -144,14 +152,12 @@ image = d->mDocument->image(); zoom = d->mZoom; } + const QRect imageRect = PaintUtils::shrinkRect(image.rect(), 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 = PaintUtils::shrinkRectF(QRectF(rect), zoom); - sourceRectF = sourceRectF.intersected(image.rect()); + sourceRectF = sourceRectF.intersected(imageRect); QRect sourceRect = PaintUtils::containingRect(sourceRectF); if (sourceRect.isEmpty()) { return; @@ -165,8 +171,8 @@ 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 +188,28 @@ } // 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 = PaintUtils::enlargeRect(sourceRect, zoom); + + QRect dSourceRect = PaintUtils::enlargeRect(sourceRect, dpr); + QRect dDestRect = PaintUtils::enlargeRect(dSourceRect, zoom); QImage tmp; - tmp = image.copy(sourceRect); + tmp = image.copy(dSourceRect); tmp = tmp.scaled( - destRect.width(), - destRect.height(), + dDestRect.width(), + dDestRect.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, + dDestRect.width() - (destLeftMargin + destRightMargin)*dpr, + dDestRect.height() - (destTopMargin + destBottomMargin)*dpr ); } + tmp.setDevicePixelRatio(dpr); emit scaledRect(destRect.left() + destLeftMargin, destRect.top() + destTopMargin, tmp); } Index: lib/paintutils.h =================================================================== --- lib/paintutils.h +++ lib/paintutils.h @@ -67,6 +67,27 @@ */ GWENVIEWLIB_EXPORT QRect containingRect(const QRectF& rectF); +/** + * Enlarges all coordinates of QRectF @p rectF by factor @p factor + */ +GWENVIEWLIB_EXPORT QRectF enlargeRectF(const QRectF& rectF, qreal factor); + +/** + * Shrinks all coordinates of QRectF @p rectF by factor @p factor + */ +GWENVIEWLIB_EXPORT QRectF shrinkRectF(const QRectF& rectF, qreal factor); + +/** + * Enlarges all coordinates of QRect @p rect by factor @p factor + */ +GWENVIEWLIB_EXPORT QRect enlargeRect(const QRect& rect, qreal factor); + +/** + * Shrinks all coordinates of QRect @p rect by factor @p factor + */ +GWENVIEWLIB_EXPORT QRect shrinkRect(const QRect& rect, qreal factor); + + } // namespace } // namespace Index: lib/paintutils.cpp =================================================================== --- lib/paintutils.cpp +++ lib/paintutils.cpp @@ -152,5 +152,32 @@ // Note: QRect::right = left + width - 1, while QRectF::right = left + width } +QRectF enlargeRectF(const QRectF& rectF, qreal factor) +{ + return QRectF(rectF.x() * factor, + rectF.y() * factor, + rectF.width() * factor, + rectF.height() * factor); +} + +QRectF shrinkRectF(const QRectF& rectF, qreal factor) +{ + return QRectF(rectF.x() / factor, + rectF.y() / factor, + rectF.width() / factor, + rectF.height() / factor); +} + +QRect enlargeRect(const QRect& rect, qreal factor) +{ + // containingRect and QRectF::toAlignedRect() should be the same + return enlargeRectF(QRectF(rect), factor).toAlignedRect(); +} + +QRect shrinkRect(const QRect& rect, qreal factor) +{ + return shrinkRectF(QRectF(rect), factor).toAlignedRect(); +} + } // namespace } // namespace