Skip to content

Commit

Permalink
Merge pull request #8242 from 3nids/final_dpi
Browse files Browse the repository at this point in the history
 [fix #17773] fix HiDPI in map canvas on mac
  • Loading branch information
3nids committed Oct 19, 2018
2 parents 345d25f + ff1face commit fb2883c
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 26 deletions.
35 changes: 31 additions & 4 deletions python/core/auto_generated/qgsmapsettings.sip.in
Expand Up @@ -21,10 +21,12 @@ The rendering itself is done by QgsMapRendererJob subclasses.
In order to set up QgsMapSettings instance, it is necessary to set at least
few members: extent, output size and layers.

QgsMapSettings and QgsMapRendererJob (+subclasses) are intended to replace
QgsMapRenderer class that existed before QGIS 2.4. The advantage of the new
classes is that they separate the settings from the rendering and provide
asynchronous API for map rendering.
Some systems use high DPI scaling that is an alternative to the traditional
DPI scaling. The operating system provides Qt with a scaling ratio and it
scales window, event, and desktop geometry. The Cocoa platform plugin sets
the scaling ratio as QWindow.devicePixelRatio().
To properly render the map on such systems, the map settings device pixel
ratio shall be set accordingly.

.. versionadded:: 2.4
%End
Expand Down Expand Up @@ -58,6 +60,31 @@ Returns the size of the resulting map image
void setOutputSize( QSize size );
%Docstring
Sets the size of the resulting map image
%End

float devicePixelRatio() const;
%Docstring
Returns device pixel ratio
Common values are 1 for normal-dpi displays and 2 for high-dpi "retina" displays.

.. versionadded:: 3.4
%End

void setDevicePixelRatio( float dpr );
%Docstring
Sets the device pixel ratio
Common values are 1 for normal-dpi displays and 2 for high-dpi "retina" displays.

.. versionadded:: 3.4
%End

QSize deviceOutputSize() const;
%Docstring
Returns the device output size of the map canvas
This is equivalent to the output size multiplicated
by the device pixel ratio.

.. versionadded:: 3.4
%End

double rotation() const;
Expand Down
9 changes: 6 additions & 3 deletions src/core/qgsmaprenderercustompainterjob.cpp
Expand Up @@ -60,14 +60,17 @@ void QgsMapRendererCustomPainterJob::start()
prepareTime.start();

// clear the background
mPainter->fillRect( 0, 0, mSettings.outputSize().width(), mSettings.outputSize().height(), mSettings.backgroundColor() );
mPainter->fillRect( 0, 0, mSettings.deviceOutputSize().width(), mSettings.deviceOutputSize().height(), mSettings.backgroundColor() );

mPainter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );

#ifndef QT_NO_DEBUG
QPaintDevice *paintDevice = mPainter->device();
QString errMsg = QStringLiteral( "pre-set DPI not equal to painter's DPI (%1 vs %2)" ).arg( paintDevice->logicalDpiX() ).arg( mSettings.outputDpi() );
Q_ASSERT_X( qgsDoubleNear( paintDevice->logicalDpiX(), mSettings.outputDpi() ), "Job::startRender()", errMsg.toLatin1().data() );
QString errMsg = QStringLiteral( "pre-set DPI not equal to painter's DPI (%1 vs %2)" )
.arg( paintDevice->logicalDpiX() )
.arg( mSettings.outputDpi() );
Q_ASSERT_X( qgsDoubleNear( paintDevice->logicalDpiX(), mSettings.outputDpi() ),
"Job::startRender()", errMsg.toLatin1().data() );
#endif

mLabelingEngineV2.reset();
Expand Down
16 changes: 9 additions & 7 deletions src/core/qgsmaprendererjob.cpp
Expand Up @@ -313,6 +313,7 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter *painter, QgsLabelingEn
job.cached = true;
job.imageInitialized = true;
job.img = new QImage( mCache->cacheImage( ml->id() ) );
job.img->setDevicePixelRatio( mSettings.devicePixelRatio() );
job.renderer = nullptr;
job.context.setPainter( nullptr );
continue;
Expand All @@ -324,10 +325,9 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter *painter, QgsLabelingEn
if ( mCache || !painter || needTemporaryImage( ml ) )
{
// Flattened image for drawing when a blending mode is set
QImage *mypFlattenedImage = nullptr;
mypFlattenedImage = new QImage( mSettings.outputSize().width(),
mSettings.outputSize().height(),
mSettings.outputImageFormat() );
QImage *mypFlattenedImage = new QImage( mSettings.deviceOutputSize(),
mSettings.outputImageFormat() );
mypFlattenedImage->setDevicePixelRatio( mSettings.devicePixelRatio() );
if ( mypFlattenedImage->isNull() )
{
mErrors.append( Error( ml->id(), tr( "Insufficient memory for image %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
Expand Down Expand Up @@ -366,6 +366,7 @@ LabelRenderJob QgsMapRendererJob::prepareLabelingJob( QPainter *painter, QgsLabe
job.cached = true;
job.complete = true;
job.img = new QImage( mCache->cacheImage( LABEL_CACHE_ID ) );
Q_ASSERT( job.img->devicePixelRatio() == mSettings.devicePixelRatio() );
job.context.setPainter( nullptr );
}
else
Expand All @@ -374,9 +375,9 @@ LabelRenderJob QgsMapRendererJob::prepareLabelingJob( QPainter *painter, QgsLabe
{
// Flattened image for drawing labels
QImage *mypFlattenedImage = nullptr;
mypFlattenedImage = new QImage( mSettings.outputSize().width(),
mSettings.outputSize().height(),
mypFlattenedImage = new QImage( mSettings.deviceOutputSize(),
mSettings.outputImageFormat() );
mypFlattenedImage->setDevicePixelRatio( mSettings.devicePixelRatio() );
if ( mypFlattenedImage->isNull() )
{
mErrors.append( Error( QStringLiteral( "labels" ), tr( "Insufficient memory for label image %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
Expand Down Expand Up @@ -447,7 +448,8 @@ void QgsMapRendererJob::cleanupLabelJob( LabelRenderJob &job )

QImage QgsMapRendererJob::composeImage( const QgsMapSettings &settings, const LayerRenderJobs &jobs, const LabelRenderJob &labelJob )
{
QImage image( settings.outputSize(), settings.outputImageFormat() );
QImage image( settings.deviceOutputSize(), settings.outputImageFormat() );
image.setDevicePixelRatio( settings.devicePixelRatio() );
image.fill( settings.backgroundColor().rgba() );

QPainter painter( &image );
Expand Down
3 changes: 2 additions & 1 deletion src/core/qgsmaprenderersequentialjob.cpp
Expand Up @@ -25,7 +25,8 @@ QgsMapRendererSequentialJob::QgsMapRendererSequentialJob( const QgsMapSettings &
{
QgsDebugMsgLevel( QStringLiteral( "SEQUENTIAL construct" ), 5 );

mImage = QImage( mSettings.outputSize(), mSettings.outputImageFormat() );
mImage = QImage( mSettings.deviceOutputSize(), mSettings.outputImageFormat() );
mImage.setDevicePixelRatio( mSettings.devicePixelRatio() );
mImage.setDotsPerMeterX( 1000 * settings.outputDpi() / 25.4 );
mImage.setDotsPerMeterY( 1000 * settings.outputDpi() / 25.4 );
mImage.fill( Qt::transparent );
Expand Down
19 changes: 17 additions & 2 deletions src/core/qgsmapsettings.cpp
Expand Up @@ -146,8 +146,8 @@ void QgsMapSettings::updateDerived()
}
}

double myHeight = mSize.height();
double myWidth = mSize.width();
double myHeight = mSize.height() * mDevicePixelRatio;
double myWidth = mSize.width() * mDevicePixelRatio;

if ( !myWidth || !myHeight )
{
Expand Down Expand Up @@ -230,6 +230,21 @@ void QgsMapSettings::setOutputSize( QSize size )
updateDerived();
}

float QgsMapSettings::devicePixelRatio() const
{
return mDevicePixelRatio;
}

void QgsMapSettings::setDevicePixelRatio( float dpr )
{
mDevicePixelRatio = dpr;
}

QSize QgsMapSettings::deviceOutputSize() const
{
return outputSize() * mDevicePixelRatio;
}

double QgsMapSettings::outputDpi() const
{
return mDpi;
Expand Down
33 changes: 29 additions & 4 deletions src/core/qgsmapsettings.h
Expand Up @@ -48,10 +48,12 @@ class QgsMapRendererJob;
* In order to set up QgsMapSettings instance, it is necessary to set at least
* few members: extent, output size and layers.
*
* QgsMapSettings and QgsMapRendererJob (+subclasses) are intended to replace
* QgsMapRenderer class that existed before QGIS 2.4. The advantage of the new
* classes is that they separate the settings from the rendering and provide
* asynchronous API for map rendering.
* Some systems use high DPI scaling that is an alternative to the traditional
* DPI scaling. The operating system provides Qt with a scaling ratio and it
* scales window, event, and desktop geometry. The Cocoa platform plugin sets
* the scaling ratio as QWindow::devicePixelRatio().
* To properly render the map on such systems, the map settings device pixel
* ratio shall be set accordingly.
*
* \since QGIS 2.4
*/
Expand Down Expand Up @@ -81,6 +83,28 @@ class CORE_EXPORT QgsMapSettings
//! Sets the size of the resulting map image
void setOutputSize( QSize size );

/**
* Returns device pixel ratio
* Common values are 1 for normal-dpi displays and 2 for high-dpi "retina" displays.
* \since QGIS 3.4
*/
float devicePixelRatio() const;

/**
* Sets the device pixel ratio
* Common values are 1 for normal-dpi displays and 2 for high-dpi "retina" displays.
* \since QGIS 3.4
*/
void setDevicePixelRatio( float dpr );

/**
* Returns the device output size of the map canvas
* This is equivalent to the output size multiplicated
* by the device pixel ratio.
* \since QGIS 3.4
*/
QSize deviceOutputSize() const;

/**
* Returns the rotation of the resulting map image, in degrees clockwise.
* \see setRotation()
Expand Down Expand Up @@ -403,6 +427,7 @@ class CORE_EXPORT QgsMapSettings
double mDpi;

QSize mSize;
float mDevicePixelRatio = 1.0;

QgsRectangle mExtent;

Expand Down
11 changes: 10 additions & 1 deletion src/gui/qgsmapcanvas.cpp
Expand Up @@ -31,9 +31,11 @@ email : sherman at mrcc.com
#include <QRect>
#include <QTextStream>
#include <QResizeEvent>
#include <QScreen>
#include <QString>
#include <QStringList>
#include <QWheelEvent>
#include <QWindow>

#include "qgis.h"
#include "qgssettings.h"
Expand Down Expand Up @@ -163,11 +165,17 @@ QgsMapCanvas::QgsMapCanvas( QWidget *parent )

QSize s = viewport()->size();
mSettings.setOutputSize( s );
mSettings.setDevicePixelRatio( devicePixelRatio() );
setSceneRect( 0, 0, s.width(), s.height() );
mScene->setSceneRect( QRectF( 0, 0, s.width(), s.height() ) );

moveCanvasContents( true );

// keep device pixel ratio up to date on screen or resolution change
connect( window()->windowHandle(), &QWindow::screenChanged, this, [ = ]( QScreen * ) {mSettings.setDevicePixelRatio( devicePixelRatio() );} );
if ( window()->windowHandle() )
connect( window()->windowHandle()->screen(), &QScreen::physicalDotsPerInchChanged, [ = ]( qreal ) {mSettings.setDevicePixelRatio( devicePixelRatio() );} );

connect( &mMapUpdateTimer, &QTimer::timeout, this, &QgsMapCanvas::mapUpdateTimeout );
mMapUpdateTimer.setInterval( 250 );

Expand Down Expand Up @@ -682,7 +690,8 @@ QgsRectangle QgsMapCanvas::imageRect( const QImage &img, const QgsMapSettings &m
// expects (encoding of position and size of the item)
const QgsMapToPixel &m2p = mapSettings.mapToPixel();
QgsPointXY topLeft = m2p.toMapCoordinates( 0, 0 );
double res = m2p.mapUnitsPerPixel();
Q_ASSERT( img.devicePixelRatio() == mapSettings.devicePixelRatio() );
double res = m2p.mapUnitsPerPixel() / img.devicePixelRatioF();
QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + img.width()*res, topLeft.y() - img.height()*res );
return rect;
}
Expand Down
19 changes: 15 additions & 4 deletions src/gui/qgsmapcanvasmap.cpp
Expand Up @@ -60,12 +60,20 @@ QRectF QgsMapCanvasMap::boundingRect() const

void QgsMapCanvasMap::paint( QPainter *painter )
{
int w = std::round( mItemSize.width() ) - 2, h = std::round( mItemSize.height() ) - 2; // setRect() makes the size +2 :-(
if ( mImage.size() != QSize( w, h ) )
// setRect() makes the size +2 :-(
int w = std::round( mItemSize.width() ) - 2;
int h = std::round( mItemSize.height() ) - 2;

bool scale = false;
if ( mImage.size() != QSize( w, h )*mImage.devicePixelRatioF() )
{
QgsDebugMsg( QStringLiteral( "map paint DIFFERENT SIZE: img %1,%2 item %3,%4" ).arg( mImage.width() ).arg( mImage.height() ).arg( w ).arg( h ) );
QgsDebugMsg( QStringLiteral( "map paint DIFFERENT SIZE: img %1,%2 item %3,%4" )
.arg( mImage.width() / mImage.devicePixelRatioF() )
.arg( mImage.height() / mImage.devicePixelRatioF() )
.arg( w ).arg( h ) );
// This happens on zoom events when ::paint is called before
// the renderer has completed
scale = true;
}

/*Offset between 0/0 and mRect.xMinimum/mRect.yMinimum.
Expand All @@ -83,7 +91,10 @@ void QgsMapCanvasMap::paint( QPainter *painter )
painter->drawImage( QRectF( ul.x(), ul.y(), lr.x() - ul.x(), lr.y() - ul.y() ), imIt->first, QRect( 0, 0, imIt->first.width(), imIt->first.height() ) );
}

painter->drawImage( QRect( 0, 0, w, h ), mImage );
if ( scale )
painter->drawImage( QRect( 0, 0, w, h ), mImage );
else
painter->drawImage( 0, 0, mImage );

// For debugging:
#if 0
Expand Down

0 comments on commit fb2883c

Please sign in to comment.