Mandelbrot Example: Use High DPI scaling

Create the pixmap with a device pixel ratio set.

Change-Id: I7f7e90aec4d117304852f050be70e14a0c6bf69d
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Friedemann Kleint 2020-01-14 13:36:39 +01:00
parent 2c390e85cf
commit a719c630f1
5 changed files with 47 additions and 21 deletions

View File

@ -187,6 +187,10 @@
generate more and more precise (and computationally expensive) generate more and more precise (and computationally expensive)
approximations of the fractal. approximations of the fractal.
We create a high resolution pixmap by applying the device
pixel ratio to the target size (see
\l{Drawing High Resolution Versions of Pixmaps and Images}).
If we discover inside the loop that \c restart has been set to \c If we discover inside the loop that \c restart has been set to \c
true (by \c render()), we break out of the loop immediately, so true (by \c render()), we break out of the loop immediately, so
that the control quickly returns to the very top of the outer that the control quickly returns to the very top of the outer
@ -273,12 +277,21 @@
\snippet threads/mandelbrot/mandelbrotwidget.cpp 8 \snippet threads/mandelbrot/mandelbrotwidget.cpp 8
If the pixmap has the right scale factor, we draw the pixmap directly onto If the pixmap has the right scale factor, we draw the pixmap directly onto
the widget. Otherwise, we scale and translate the \l{Coordinate the widget.
System}{coordinate system} before we draw the pixmap. By reverse mapping
the widget's rectangle using the scaled painter matrix, we also make sure Otherwise, we create a preview pixmap to be shown until the calculation
that only the exposed areas of the pixmap are drawn. The calls to finishes and translate the \l{Coordinate System}{coordinate system}
QPainter::save() and QPainter::restore() make sure that any painting accordingly.
performed afterwards uses the standard coordinate system.
Since we are going to use transformations on the painter
and use an overload of QPainter::drawPixmap() that does not support
high resolution pixmaps in that case, we create a pixmap with device pixel
ratio 1.
By reverse mapping the widget's rectangle using the scaled painter matrix,
we also make sure that only the exposed areas of the pixmap are drawn.
The calls to QPainter::save() and QPainter::restore() make sure that any
painting performed afterwards uses the standard coordinate system.
\snippet threads/mandelbrot/mandelbrotwidget.cpp 9 \snippet threads/mandelbrot/mandelbrotwidget.cpp 9

View File

@ -55,6 +55,8 @@
//! [0] //! [0]
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
QApplication app(argc, argv); QApplication app(argc, argv);
MandelbrotWidget widget; MandelbrotWidget widget;
widget.show(); widget.show();

View File

@ -107,18 +107,22 @@ void MandelbrotWidget::paintEvent(QPaintEvent * /* event */)
//! [6] //! [7] //! [6] //! [7]
} else { } else {
//! [7] //! [8] //! [7] //! [8]
auto previewPixmap = qFuzzyCompare(pixmap.devicePixelRatioF(), qreal(1))
? pixmap
: pixmap.scaled(pixmap.size() / pixmap.devicePixelRatioF(), Qt::KeepAspectRatio,
Qt::SmoothTransformation);
double scaleFactor = pixmapScale / curScale; double scaleFactor = pixmapScale / curScale;
int newWidth = int(pixmap.width() * scaleFactor); int newWidth = int(previewPixmap.width() * scaleFactor);
int newHeight = int(pixmap.height() * scaleFactor); int newHeight = int(previewPixmap.height() * scaleFactor);
int newX = pixmapOffset.x() + (pixmap.width() - newWidth) / 2; int newX = pixmapOffset.x() + (previewPixmap.width() - newWidth) / 2;
int newY = pixmapOffset.y() + (pixmap.height() - newHeight) / 2; int newY = pixmapOffset.y() + (previewPixmap.height() - newHeight) / 2;
painter.save(); painter.save();
painter.translate(newX, newY); painter.translate(newX, newY);
painter.scale(scaleFactor, scaleFactor); painter.scale(scaleFactor, scaleFactor);
QRectF exposed = painter.transform().inverted().mapRect(rect()).adjusted(-1, -1, 1, 1); QRectF exposed = painter.transform().inverted().mapRect(rect()).adjusted(-1, -1, 1, 1);
painter.drawPixmap(exposed, pixmap, exposed); painter.drawPixmap(exposed, previewPixmap, exposed);
painter.restore(); painter.restore();
} }
//! [8] //! [9] //! [8] //! [9]
@ -139,7 +143,7 @@ void MandelbrotWidget::paintEvent(QPaintEvent * /* event */)
//! [10] //! [10]
void MandelbrotWidget::resizeEvent(QResizeEvent * /* event */) void MandelbrotWidget::resizeEvent(QResizeEvent * /* event */)
{ {
thread.render(centerX, centerY, curScale, size()); thread.render(centerX, centerY, curScale, size(), devicePixelRatioF());
} }
//! [10] //! [10]
@ -208,8 +212,9 @@ void MandelbrotWidget::mouseReleaseEvent(QMouseEvent *event)
pixmapOffset += event->pos() - lastDragPos; pixmapOffset += event->pos() - lastDragPos;
lastDragPos = QPoint(); lastDragPos = QPoint();
int deltaX = (width() - pixmap.width()) / 2 - pixmapOffset.x(); const auto pixmapSize = pixmap.size() / pixmap.devicePixelRatioF();
int deltaY = (height() - pixmap.height()) / 2 - pixmapOffset.y(); int deltaX = (width() - pixmapSize.width()) / 2 - pixmapOffset.x();
int deltaY = (height() - pixmapSize.height()) / 2 - pixmapOffset.y();
scroll(deltaX, deltaY); scroll(deltaX, deltaY);
} }
} }
@ -234,7 +239,7 @@ void MandelbrotWidget::zoom(double zoomFactor)
{ {
curScale *= zoomFactor; curScale *= zoomFactor;
update(); update();
thread.render(centerX, centerY, curScale, size()); thread.render(centerX, centerY, curScale, size(), devicePixelRatioF());
} }
//! [17] //! [17]
@ -244,6 +249,6 @@ void MandelbrotWidget::scroll(int deltaX, int deltaY)
centerX += deltaX * curScale; centerX += deltaX * curScale;
centerY += deltaY * curScale; centerY += deltaY * curScale;
update(); update();
thread.render(centerX, centerY, curScale, size()); thread.render(centerX, centerY, curScale, size(), devicePixelRatioF());
} }
//! [18] //! [18]

View File

@ -76,13 +76,14 @@ RenderThread::~RenderThread()
//! [2] //! [2]
void RenderThread::render(double centerX, double centerY, double scaleFactor, void RenderThread::render(double centerX, double centerY, double scaleFactor,
QSize resultSize) QSize resultSize, double devicePixelRatio)
{ {
QMutexLocker locker(&mutex); QMutexLocker locker(&mutex);
this->centerX = centerX; this->centerX = centerX;
this->centerY = centerY; this->centerY = centerY;
this->scaleFactor = scaleFactor; this->scaleFactor = scaleFactor;
this->devicePixelRatio = devicePixelRatio;
this->resultSize = resultSize; this->resultSize = resultSize;
if (!isRunning()) { if (!isRunning()) {
@ -99,8 +100,10 @@ void RenderThread::run()
{ {
forever { forever {
mutex.lock(); mutex.lock();
const QSize resultSize = this->resultSize; const double devicePixelRatio = this->devicePixelRatio;
const double scaleFactor = this->scaleFactor; const QSize resultSize = this->resultSize * devicePixelRatio;
const double requestedScaleFactor = this->scaleFactor;
const double scaleFactor = requestedScaleFactor / devicePixelRatio;
const double centerX = this->centerX; const double centerX = this->centerX;
const double centerY = this->centerY; const double centerY = this->centerY;
mutex.unlock(); mutex.unlock();
@ -111,6 +114,7 @@ void RenderThread::run()
//! [4] //! [5] //! [4] //! [5]
int halfHeight = resultSize.height() / 2; int halfHeight = resultSize.height() / 2;
QImage image(resultSize, QImage::Format_RGB32); QImage image(resultSize, QImage::Format_RGB32);
image.setDevicePixelRatio(devicePixelRatio);
const int NumPasses = 8; const int NumPasses = 8;
int pass = 0; int pass = 0;
@ -162,7 +166,7 @@ void RenderThread::run()
pass = 4; pass = 4;
} else { } else {
if (!restart) if (!restart)
emit renderedImage(image, scaleFactor); emit renderedImage(image, requestedScaleFactor);
//! [5] //! [6] //! [5] //! [6]
++pass; ++pass;
} }

View File

@ -69,7 +69,8 @@ public:
RenderThread(QObject *parent = nullptr); RenderThread(QObject *parent = nullptr);
~RenderThread(); ~RenderThread();
void render(double centerX, double centerY, double scaleFactor, QSize resultSize); void render(double centerX, double centerY, double scaleFactor, QSize resultSize,
double devicePixelRatio);
signals: signals:
void renderedImage(const QImage &image, double scaleFactor); void renderedImage(const QImage &image, double scaleFactor);
@ -85,6 +86,7 @@ private:
double centerX; double centerX;
double centerY; double centerY;
double scaleFactor; double scaleFactor;
double devicePixelRatio;
QSize resultSize; QSize resultSize;
bool restart = false; bool restart = false;
bool abort = false; bool abort = false;