Add QPaintDevice metric query to get precise fractional DPR value

For compatibility reasons, QPaintDevice needs to query subclasses for
device metrics as int values. To report fractional DPR values, they
have been multiplied with a large constant and divided back
afterwards. However, the loss of accuracy introduced by this, though
tiny, could still lead to rounding errors and painting artefacts when
the values where multiplied up for large coordinates.

Avoid this issue by adding a metric query that transports the full
floating point value encoded as two ints.

[ChangeLog][QtGui] Added new QPaintDevice metrics for querying
fractional device pixel ratios with high precision. Custom paintdevice
classes that support fractional DPRs are recommended to implement
support for these queries in metric(). Others can ignore them.

Fixes: QTBUG-124342
Change-Id: Ia6fa46e68e9fe981bdcbafb41daf080b4d1fb6d7
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
Eirik Aavitsland 2024-05-07 16:22:53 +02:00
parent 890c270b9b
commit a5953d20e2
11 changed files with 103 additions and 3 deletions

View File

@ -4323,6 +4323,12 @@ int QImage::metric(PaintDeviceMetric metric) const
return d->devicePixelRatio * QPaintDevice::devicePixelRatioFScale();
break;
case PdmDevicePixelRatioF_EncodedA:
Q_FALLTHROUGH();
case PdmDevicePixelRatioF_EncodedB:
return QPaintDevice::encodeMetricF(metric, d->devicePixelRatio);
break;
default:
qWarning("QImage::metric(): Unhandled metric type %d", metric);
break;

View File

@ -88,6 +88,10 @@ int QBlittablePlatformPixmap::metric(QPaintDevice::PaintDeviceMetric metric) con
return devicePixelRatio();
case QPaintDevice::PdmDevicePixelRatioScaled:
return devicePixelRatio() * QPaintDevice::devicePixelRatioFScale();
case QPaintDevice::PdmDevicePixelRatioF_EncodedA:
Q_FALLTHROUGH();
case QPaintDevice::PdmDevicePixelRatioF_EncodedB:
return QPaintDevice::encodeMetricF(metric, devicePixelRatio());
default:
qWarning("QRasterPlatformPixmap::metric(): Unhandled metric type %d", metric);
break;

View File

@ -253,6 +253,10 @@ int QRasterPlatformPixmap::metric(QPaintDevice::PaintDeviceMetric metric) const
return image.devicePixelRatio();
case QPaintDevice::PdmDevicePixelRatioScaled:
return image.devicePixelRatio() * QPaintDevice::devicePixelRatioFScale();
case QPaintDevice::PdmDevicePixelRatioF_EncodedA:
Q_FALLTHROUGH();
case QPaintDevice::PdmDevicePixelRatioF_EncodedB:
return QPaintDevice::encodeMetricF(metric, image.devicePixelRatio());
default:
qWarning("QRasterPlatformPixmap::metric(): Unhandled metric type %d", metric);

View File

@ -136,6 +136,11 @@ int QPaintDeviceWindow::metric(PaintDeviceMetric metric) const
case PdmDevicePixelRatioScaled:
return int(QWindow::devicePixelRatio() * devicePixelRatioFScale());
break;
case PdmDevicePixelRatioF_EncodedA:
Q_FALLTHROUGH();
case PdmDevicePixelRatioF_EncodedB:
return QPaintDevice::encodeMetricF(metric, QWindow::devicePixelRatio());
break;
default:
break;
}

View File

@ -17,6 +17,39 @@ QPaintDevice::~QPaintDevice()
"painted");
}
/*!
\internal
*/
// ### Qt 7: Replace this workaround mechanism: virtual devicePixelRatio() and virtual metricF()
inline double QPaintDevice::getDecodedMetricF(PaintDeviceMetric metricA, PaintDeviceMetric metricB) const
{
qint32 buf[2];
// The Encoded metric enum values come in pairs of one odd and one even value.
// We map those to the 0 and 1 indexes of buf by taking just the least significant bit.
// Same mapping here as in the encodeMetricF() function, to ensure correct order.
buf[metricA & 1] = metric(metricA);
buf[metricB & 1] = metric(metricB);
double res;
memcpy(&res, buf, sizeof(res));
return res;
}
qreal QPaintDevice::devicePixelRatio() const
{
Q_STATIC_ASSERT((PdmDevicePixelRatioF_EncodedA & 1) != (PdmDevicePixelRatioF_EncodedB & 1));
double res;
int scaledDpr = metric(PdmDevicePixelRatioScaled);
if (scaledDpr == int(devicePixelRatioFScale())) {
res = 1; // Shortcut for common case
} else if (scaledDpr == 2 * int(devicePixelRatioFScale())) {
res = 2; // Shortcut for common case
} else {
res = getDecodedMetricF(PdmDevicePixelRatioF_EncodedA, PdmDevicePixelRatioF_EncodedB);
if (res <= 0) // These metrics not implemented, fall back to PdmDevicePixelRatioScaled
res = scaledDpr / devicePixelRatioFScale();
}
return res;
}
/*!
\internal
@ -64,6 +97,8 @@ int QPaintDevice::metric(PaintDeviceMetric m) const
return 256;
} else if (m == PdmDevicePixelRatio) {
return 1;
} else if (m == PdmDevicePixelRatioF_EncodedA || m == PdmDevicePixelRatioF_EncodedB) {
return 0;
} else {
qDebug("Unrecognised metric %d!",m);
return 0;

View File

@ -29,7 +29,9 @@ public:
PdmPhysicalDpiX,
PdmPhysicalDpiY,
PdmDevicePixelRatio,
PdmDevicePixelRatioScaled
PdmDevicePixelRatioScaled,
PdmDevicePixelRatioF_EncodedA,
PdmDevicePixelRatioF_EncodedB
};
virtual ~QPaintDevice();
@ -46,18 +48,20 @@ public:
int logicalDpiY() const { return metric(PdmDpiY); }
int physicalDpiX() const { return metric(PdmPhysicalDpiX); }
int physicalDpiY() const { return metric(PdmPhysicalDpiY); }
qreal devicePixelRatio() const { return metric(PdmDevicePixelRatioScaled) / devicePixelRatioFScale(); }
qreal devicePixelRatio() const;
qreal devicePixelRatioF() const { return devicePixelRatio(); }
int colorCount() const { return metric(PdmNumColors); }
int depth() const { return metric(PdmDepth); }
static inline qreal devicePixelRatioFScale() { return 0x10000; }
static inline int encodeMetricF(PaintDeviceMetric metric, double value);
protected:
QPaintDevice() noexcept;
virtual int metric(PaintDeviceMetric metric) const;
virtual void initPainter(QPainter *painter) const;
virtual QPaintDevice *redirected(QPoint *offset) const;
virtual QPainter *sharedPainter() const;
double getDecodedMetricF(PaintDeviceMetric metricA, PaintDeviceMetric metricB) const;
ushort painters; // refcount
private:
@ -80,6 +84,14 @@ inline int QPaintDevice::devType() const
inline bool QPaintDevice::paintingActive() const
{ return painters != 0; }
inline int QPaintDevice::encodeMetricF(PaintDeviceMetric metric, double value)
{
qint32 buf[2];
Q_STATIC_ASSERT(sizeof(buf) == sizeof(double));
memcpy(buf, &value, sizeof(buf));
return buf[metric & 1];
}
QT_END_NAMESPACE
#endif // QPAINTDEVICE_H

View File

@ -99,6 +99,14 @@
The constant scaling factor used is devicePixelRatioFScale(). This enum value
has been introduced in Qt 5.6.
\value [since 6.8] PdmDevicePixelRatioF_EncodedA This enum item, together with the
corresponding \c B item, are used together for the device pixel ratio of the device, as an
encoded \c double floating point value. A QPaintDevice subclass that supports \e fractional DPR
values should implement support for these two enum items in its override of the metric()
function. The return value is expected to be the result of the encodeMetricF() function.
\value [since 6.8] PdmDevicePixelRatioF_EncodedB See PdmDevicePixelRatioF_EncodedA.
\sa metric(), devicePixelRatio()
*/
@ -288,3 +296,15 @@
\since 5.6
*/
/*!
\fn int QPaintDevice::encodeMetricF(PaintDeviceMetric metric, double value)
\internal
Returns \a value encoded for the metric \a metric. Subclasses implementing metric() should use
this function to encode \value as an integer return value when the query metric specifies an
encoded floating-point value.
\since 6.8
*/

View File

@ -247,6 +247,10 @@ int QOpenGLPaintDevice::metric(QPaintDevice::PaintDeviceMetric metric) const
return d_ptr->devicePixelRatio;
case PdmDevicePixelRatioScaled:
return d_ptr->devicePixelRatio * QPaintDevice::devicePixelRatioFScale();
case PdmDevicePixelRatioF_EncodedA:
Q_FALLTHROUGH();
case PdmDevicePixelRatioF_EncodedB:
return QPaintDevice::encodeMetricF(metric, d_ptr->devicePixelRatio);
default:
qWarning("QOpenGLPaintDevice::metric() - metric %d not known", metric);

View File

@ -1640,6 +1640,10 @@ int QOpenGLWidget::metric(QPaintDevice::PaintDeviceMetric metric) const
return QWidget::metric(metric);
case PdmDevicePixelRatioScaled:
return QWidget::metric(metric);
case PdmDevicePixelRatioF_EncodedA:
Q_FALLTHROUGH();
case PdmDevicePixelRatioF_EncodedB:
return QWidget::metric(metric);
default:
qWarning("QOpenGLWidget::metric(): unknown metric %d", metric);
return 0;

View File

@ -84,10 +84,12 @@ QT_WARNING_POP
case QPaintDevice::PdmDevicePixelRatio:
return 1;
case QPaintDevice::PdmDevicePixelRatioScaled:
return qRound(devicePixelRatioFScale());
return int(devicePixelRatioFScale());
case QPaintDevice::PdmWidthMM:
case QPaintDevice::PdmHeightMM:
break;
default:
break;
}
return -1;

View File

@ -12956,6 +12956,10 @@ int QWidget::metric(PaintDeviceMetric m) const
return resolveDevicePixelRatio();
case PdmDevicePixelRatioScaled:
return QPaintDevice::devicePixelRatioFScale() * resolveDevicePixelRatio();
case PdmDevicePixelRatioF_EncodedA:
Q_FALLTHROUGH();
case PdmDevicePixelRatioF_EncodedB:
return QPaintDevice::encodeMetricF(m, resolveDevicePixelRatio());
default:
break;
}