Update Dpi and scale factor computation

Remove pixelScale() in favor of logicalBaseDpi(). Compute scale factor
based on logical DPI and logical base DPI, or optionally based on the
physical DPI.

Add policies for running the scale factor and adjusting the logical
DPI reported to the application. The policies are set via environment
variables:

  QT_SCALE_FACTOR_ROUNDING_POLICY=Round|Ceil|Floor|RoundPreferFloor|PassThrough
  QT_DPI_ADJUSTMENT_POLICY=AdjustDpi|DontAdjustDpi|AdjustUpOnly
  QT_USE_PHYSICAL_DPI=0|1

Done-with: Friedemann Kleint <Friedemann.Kleint@qt.io>
Task-number: QTBUG-53022
Change-Id: I4846f223186df665eb0a9c827eaef0a96d1f458f
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
This commit is contained in:
Morten Johan Sørvig 2016-04-25 11:31:34 +02:00
parent dd9da0441b
commit 900f2cb6f7
12 changed files with 264 additions and 53 deletions

View File

@ -46,6 +46,9 @@
#include <private/qguiapplication_p.h>
#include <QtCore/qdebug.h>
#include <QtCore/qmetaobject.h>
#include <algorithm>
QT_BEGIN_NAMESPACE
@ -56,6 +59,18 @@ static const char legacyDevicePixelEnvVar[] = "QT_DEVICE_PIXEL_RATIO";
static const char scaleFactorEnvVar[] = "QT_SCALE_FACTOR";
static const char autoScreenEnvVar[] = "QT_AUTO_SCREEN_SCALE_FACTOR";
static const char screenFactorsEnvVar[] = "QT_SCREEN_SCALE_FACTORS";
static const char scaleFactorRoundingPolicyEnvVar[] = "QT_SCALE_FACTOR_ROUNDING_POLICY";
static const char dpiAdjustmentPolicyEnvVar[] = "QT_DPI_ADJUSTMENT_POLICY";
static const char usePhysicalDpiEnvVar[] = "QT_USE_PHYSICAL_DPI";
// Reads and interprets the given environment variable as a bool,
// returns the default value if not set.
static bool qEnvironmentVariableAsBool(const char *name, bool defaultValue)
{
bool ok = false;
int value = qEnvironmentVariableIntValue(name, &ok);
return ok ? value > 0 : defaultValue;
}
static inline qreal initialGlobalScaleFactor()
{
@ -249,6 +264,191 @@ static inline bool usePixelDensity()
qgetenv(legacyDevicePixelEnvVar).compare("auto", Qt::CaseInsensitive) == 0);
}
qreal QHighDpiScaling::rawScaleFactor(const QPlatformScreen *screen)
{
// Determine if physical DPI should be used
static const bool usePhysicalDpi = qEnvironmentVariableAsBool(usePhysicalDpiEnvVar, false);
// Calculate scale factor beased on platform screen DPI values
qreal factor;
QDpi platformBaseDpi = screen->logicalBaseDpi();
if (usePhysicalDpi) {
qreal platformPhysicalDpi = screen->screen()->physicalDotsPerInch();
factor = qreal(platformPhysicalDpi) / qreal(platformBaseDpi.first);
} else {
QDpi platformLogicalDpi = screen->logicalDpi();
factor = qreal(platformLogicalDpi.first) / qreal(platformBaseDpi.first);
}
return factor;
}
template <class EnumType>
struct EnumLookup
{
const char *name;
EnumType value;
};
template <class EnumType>
static bool operator==(const EnumLookup<EnumType> &e1, const EnumLookup<EnumType> &e2)
{
return qstricmp(e1.name, e2.name) == 0;
}
template <class EnumType>
static QByteArray joinEnumValues(const EnumLookup<EnumType> *i1, const EnumLookup<EnumType> *i2)
{
QByteArray result;
for (; i1 < i2; ++i1) {
if (!result.isEmpty())
result += QByteArrayLiteral(", ");
result += i1->name;
}
return result;
}
using ScaleFactorRoundingPolicyLookup = EnumLookup<QHighDpiScaling::HighDpiScaleFactorRoundingPolicy>;
static const ScaleFactorRoundingPolicyLookup scaleFactorRoundingPolicyLookup[] =
{
{"Round", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::Round},
{"Ceil", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::Ceil},
{"Floor", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::Floor},
{"RoundPreferFloor", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor},
{"PassThrough", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::PassThrough}
};
static QHighDpiScaling::HighDpiScaleFactorRoundingPolicy
lookupScaleFactorRoundingPolicy(const QByteArray &v)
{
auto end = std::end(scaleFactorRoundingPolicyLookup);
auto it = std::find(std::begin(scaleFactorRoundingPolicyLookup), end,
ScaleFactorRoundingPolicyLookup{v.constData(), QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::Unset});
return it != end ? it->value : QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::Unset;
}
using DpiAdjustmentPolicyLookup = EnumLookup<QHighDpiScaling::DpiAdjustmentPolicy>;
static const DpiAdjustmentPolicyLookup dpiAdjustmentPolicyLookup[] =
{
{"AdjustDpi", QHighDpiScaling::DpiAdjustmentPolicy::Enabled},
{"DontAdjustDpi", QHighDpiScaling::DpiAdjustmentPolicy::Disabled},
{"AdjustUpOnly", QHighDpiScaling::DpiAdjustmentPolicy::UpOnly}
};
static QHighDpiScaling::DpiAdjustmentPolicy
lookupDpiAdjustmentPolicy(const QByteArray &v)
{
auto end = std::end(dpiAdjustmentPolicyLookup);
auto it = std::find(std::begin(dpiAdjustmentPolicyLookup), end,
DpiAdjustmentPolicyLookup{v.constData(), QHighDpiScaling::DpiAdjustmentPolicy::Unset});
return it != end ? it->value : QHighDpiScaling::DpiAdjustmentPolicy::Unset;
}
qreal QHighDpiScaling::roundScaleFactor(qreal rawFactor)
{
// Apply scale factor rounding policy. Using mathematically correct rounding
// may not give the most desirable visual results, especially for
// critical fractions like .5. In general, rounding down results in visual
// sizes that are smaller than the ideal size, and opposite for rounding up.
// Rounding down is then preferable since "small UI" is a more acceptable
// high-DPI experience than "large UI".
static auto scaleFactorRoundingPolicy = HighDpiScaleFactorRoundingPolicy::Unset;
// Determine rounding policy
if (scaleFactorRoundingPolicy == HighDpiScaleFactorRoundingPolicy::Unset) {
// Check environment
if (qEnvironmentVariableIsSet(scaleFactorRoundingPolicyEnvVar)) {
QByteArray policyText = qgetenv(scaleFactorRoundingPolicyEnvVar);
auto policyEnumValue = lookupScaleFactorRoundingPolicy(policyText);
if (policyEnumValue != HighDpiScaleFactorRoundingPolicy::Unset) {
scaleFactorRoundingPolicy = policyEnumValue;
} else {
auto values = joinEnumValues(std::begin(scaleFactorRoundingPolicyLookup),
std::end(scaleFactorRoundingPolicyLookup));
qWarning("Unknown scale factor rounding policy: %s. Supported values are: %s.",
policyText.constData(), values.constData());
}
} else {
// Set default policy if no environment variable is set.
scaleFactorRoundingPolicy = HighDpiScaleFactorRoundingPolicy::RoundPreferFloor;
}
}
// Apply rounding policy.
qreal roundedFactor = rawFactor;
switch (scaleFactorRoundingPolicy) {
case HighDpiScaleFactorRoundingPolicy::Round:
roundedFactor = qRound(rawFactor);
break;
case HighDpiScaleFactorRoundingPolicy::Ceil:
roundedFactor = qCeil(rawFactor);
break;
case HighDpiScaleFactorRoundingPolicy::Floor:
roundedFactor = qFloor(rawFactor);
break;
case HighDpiScaleFactorRoundingPolicy::RoundPreferFloor:
// Round up for .75 and higher. This favors "small UI" over "large UI".
roundedFactor = rawFactor - qFloor(rawFactor) < 0.75
? qFloor(rawFactor) : qCeil(rawFactor);
break;
case HighDpiScaleFactorRoundingPolicy::PassThrough:
case HighDpiScaleFactorRoundingPolicy::Unset:
break;
}
// Don't round down to to zero; clamp the minimum (rounded) factor to 1.
// This is not a common case but can happen if a display reports a very
// low DPI.
if (scaleFactorRoundingPolicy != HighDpiScaleFactorRoundingPolicy::PassThrough)
roundedFactor = qMax(roundedFactor, qreal(1));
return roundedFactor;
}
QDpi QHighDpiScaling::effectiveLogicalDpi(const QPlatformScreen *screen, qreal rawFactor, qreal roundedFactor)
{
// Apply DPI adjustment policy, if needed. If enabled this will change the
// reported logical DPI to account for the difference between the rounded
// scale factor and the actual scale factor. The effect is that text size
// will be correct for the screen dpi, but may be (slightly) out of sync
// with the rest of the UI. The amount of out-of-synch-ness depends on how
// well user code handles a non-standard DPI values, but since the
// adjustment is small (typically +/- 48 max) this might be OK.
static auto dpiAdjustmentPolicy = DpiAdjustmentPolicy::Unset;
// Determine adjustment policy.
if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::Unset) {
if (qEnvironmentVariableIsSet(dpiAdjustmentPolicyEnvVar)) {
QByteArray policyText = qgetenv(dpiAdjustmentPolicyEnvVar);
auto policyEnumValue = lookupDpiAdjustmentPolicy(policyText);
if (policyEnumValue != DpiAdjustmentPolicy::Unset) {
dpiAdjustmentPolicy = policyEnumValue;
} else {
auto values = joinEnumValues(std::begin(dpiAdjustmentPolicyLookup),
std::end(dpiAdjustmentPolicyLookup));
qWarning("Unknown DPI adjustment policy: %s. Supported values are: %s.",
policyText.constData(), values.constData());
}
}
if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::Unset)
dpiAdjustmentPolicy = DpiAdjustmentPolicy::UpOnly;
}
// Apply adjustment policy.
const QDpi baseDpi = screen->logicalBaseDpi();
const qreal dpiAdjustmentFactor = rawFactor / roundedFactor;
// Return the base DPI for cases where there is no adjustment
if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::Disabled)
return baseDpi;
if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::UpOnly && dpiAdjustmentFactor < 1)
return baseDpi;
return QDpi(baseDpi.first * dpiAdjustmentFactor, baseDpi.second * dpiAdjustmentFactor);
}
void QHighDpiScaling::initHighDpiScaling()
{
// Determine if there is a global scale factor set.
@ -259,8 +459,6 @@ void QHighDpiScaling::initHighDpiScaling()
m_pixelDensityScalingActive = false; //set in updateHighDpiScaling below
// we update m_active in updateHighDpiScaling, but while we create the
// screens, we have to assume that m_usePixelDensity implies scaling
m_active = m_globalScalingActive || m_usePixelDensity;
}
@ -312,7 +510,7 @@ void QHighDpiScaling::updateHighDpiScaling()
++i;
}
}
m_active = m_globalScalingActive || m_screenFactorSet || m_pixelDensityScalingActive;
m_active = m_globalScalingActive || m_usePixelDensity;
}
/*
@ -413,22 +611,8 @@ qreal QHighDpiScaling::screenSubfactor(const QPlatformScreen *screen)
{
qreal factor = qreal(1.0);
if (screen) {
if (m_usePixelDensity) {
qreal pixelDensity = screen->pixelDensity();
// Pixel density reported by the screen is sometimes not precise enough,
// so recalculate it: divide px (physical pixels) by dp (device-independent pixels)
// for both width and height, and then use the average if it is different from
// the one initially reported by the screen
QRect screenGeometry = screen->geometry();
qreal wFactor = qreal(screenGeometry.width()) / qRound(screenGeometry.width() / pixelDensity);
qreal hFactor = qreal(screenGeometry.height()) / qRound(screenGeometry.height() / pixelDensity);
qreal averageDensity = (wFactor + hFactor) / 2;
if (!qFuzzyCompare(pixelDensity, averageDensity))
pixelDensity = averageDensity;
factor *= pixelDensity;
}
if (m_usePixelDensity)
factor *= roundScaleFactor(rawScaleFactor(screen));
if (m_screenFactorSet) {
QVariant screenFactor = screen->screen()->property(scaleFactorProperty);
if (screenFactor.isValid())
@ -441,13 +625,15 @@ qreal QHighDpiScaling::screenSubfactor(const QPlatformScreen *screen)
QDpi QHighDpiScaling::logicalDpi(const QScreen *screen)
{
// (Note: m_active test is performed at call site.)
if (!screen)
if (!screen || !screen->handle())
return QDpi(96, 96);
qreal platformScreenfactor = screenSubfactor(screen->handle());
QDpi platformScreenDpi = screen->handle()->logicalDpi();
return QDpi(platformScreenDpi.first / platformScreenfactor,
platformScreenDpi.second / platformScreenfactor);
if (!m_usePixelDensity)
return screen->handle()->logicalDpi();
const qreal scaleFactor = rawScaleFactor(screen->handle());
const qreal roundedScaleFactor = roundScaleFactor(scaleFactor);
return effectiveLogicalDpi(screen->handle(), scaleFactor, roundedScaleFactor);
}
QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QPlatformScreen *platformScreen, QPoint *nativePosition)

View File

@ -72,7 +72,33 @@ typedef QPair<qreal, qreal> QDpi;
#ifndef QT_NO_HIGHDPISCALING
class Q_GUI_EXPORT QHighDpiScaling {
Q_GADGET
public:
enum class HighDpiScaleFactorRoundingPolicy {
Unset,
Round,
Ceil,
Floor,
RoundPreferFloor,
PassThrough
};
Q_ENUM(HighDpiScaleFactorRoundingPolicy)
enum class DpiAdjustmentPolicy {
Unset,
Enabled,
Disabled,
UpOnly
};
Q_ENUM(DpiAdjustmentPolicy)
QHighDpiScaling() = delete;
~QHighDpiScaling() = delete;
QHighDpiScaling(const QHighDpiScaling &) = delete;
QHighDpiScaling &operator=(const QHighDpiScaling &) = delete;
QHighDpiScaling(QHighDpiScaling &&) = delete;
QHighDpiScaling &operator=(QHighDpiScaling &&) = delete;
static void initHighDpiScaling();
static void updateHighDpiScaling();
static void setGlobalFactor(qreal factor);
@ -101,6 +127,9 @@ public:
static QDpi logicalDpi(const QScreen *screen);
private:
static qreal rawScaleFactor(const QPlatformScreen *screen);
static qreal roundScaleFactor(qreal rawFactor);
static QDpi effectiveLogicalDpi(const QPlatformScreen *screen, qreal rawFactor, qreal roundedFactor);
static qreal screenSubfactor(const QPlatformScreen *screen);
static qreal m_factor;

View File

@ -193,6 +193,20 @@ QDpi QPlatformScreen::logicalDpi() const
25.4 * s.height() / ps.height());
}
/*!
Reimplement to return the base logical DPI for the platform. This
DPI value should correspond to a standard-DPI (1x) display. The
default implementation returns 96.
QtGui will use this value (together with logicalDpi) to compute
the scale factor when high-DPI scaling is enabled:
factor = logicalDPI / baseDPI
*/
QDpi QPlatformScreen::logicalBaseDpi() const
{
return QDpi(96, 96);
}
/*!
Reimplement this function in subclass to return the device pixel ratio
for the screen. This is the ratio between physical pixels and the

View File

@ -115,6 +115,7 @@ public:
virtual QSizeF physicalSize() const;
virtual QDpi logicalDpi() const;
virtual QDpi logicalBaseDpi() const;
virtual qreal devicePixelRatio() const;
virtual qreal pixelDensity() const;

View File

@ -401,15 +401,17 @@ void QAndroidPlatformScreen::doRedraw()
m_dirtyRect = QRect();
}
static const int androidLogicalDpi = 72;
QDpi QAndroidPlatformScreen::logicalDpi() const
{
qreal lDpi = QtAndroid::scaledDensity() * 72;
qreal lDpi = QtAndroid::scaledDensity() * androidLogicalDpi;
return QDpi(lDpi, lDpi);
}
qreal QAndroidPlatformScreen::pixelDensity() const
QDpi QAndroidPlatformScreen::logicalBaseDpi() const
{
return QtAndroid::pixelDensity();
return QDpi(androidLogicalDpi, androidLogicalDpi);
}
Qt::ScreenOrientation QAndroidPlatformScreen::orientation() const

View File

@ -103,7 +103,7 @@ protected:
private:
QDpi logicalDpi() const override;
qreal pixelDensity() const override;
QDpi logicalBaseDpi() const override;
Qt::ScreenOrientation orientation() const override;
Qt::ScreenOrientation nativeOrientation() const override;
void surfaceChanged(JNIEnv *env, jobject surface, int w, int h) override;

View File

@ -68,6 +68,7 @@ public:
qreal devicePixelRatio() const override { return m_devicePixelRatio; }
QSizeF physicalSize() const override { return m_physicalSize; }
QDpi logicalDpi() const override { return m_logicalDpi; }
QDpi logicalBaseDpi() const override { return m_logicalDpi; }
qreal refreshRate() const override { return m_refreshRate; }
QString name() const override { return m_name; }
QPlatformCursor *cursor() const override { return m_cursor; }

View File

@ -256,15 +256,6 @@ QWindow *QWindowsScreen::windowAt(const QPoint &screenPoint, unsigned flags)
return result;
}
qreal QWindowsScreen::pixelDensity() const
{
// QTBUG-49195: Use logical DPI instead of physical DPI to calculate
// the pixel density since it is reflects the Windows UI scaling.
// High DPI auto scaling should be disabled when the user chooses
// small fonts on a High DPI monitor, resulting in lower logical DPI.
return qMax(1, qRound(logicalDpi().first / 96));
}
/*!
\brief Determine siblings in a virtual desktop system.

View File

@ -87,7 +87,7 @@ public:
QImage::Format format() const override { return m_data.format; }
QSizeF physicalSize() const override { return m_data.physicalSizeMM; }
QDpi logicalDpi() const override { return m_data.dpi; }
qreal pixelDensity() const override;
QDpi logicalBaseDpi() const override { return QDpi(96, 96); };
qreal devicePixelRatio() const override { return 1.0; }
qreal refreshRate() const override { return m_data.refreshRateHz; }
QString name() const override { return m_data.name; }

View File

@ -679,11 +679,6 @@ QDpi QXcbScreen::logicalDpi() const
return m_virtualDesktop->dpi();
}
qreal QXcbScreen::pixelDensity() const
{
return m_pixelDensity;
}
QPlatformCursor *QXcbScreen::cursor() const
{
return m_cursor;
@ -747,14 +742,6 @@ void QXcbScreen::updateGeometry(const QRect &geometry, uint8_t rotation)
if (m_sizeMillimeters.isEmpty())
m_sizeMillimeters = sizeInMillimeters(geometry.size(), m_virtualDesktop->dpi());
qreal dpi = forcedDpi();
if (dpi <= 0)
dpi = geometry.width() / physicalSize().width() * qreal(25.4);
// Use 128 as a reference DPI on small screens. This favors "small UI" over "large UI".
qreal referenceDpi = physicalSize().width() <= 320 ? 128 : 96;
m_pixelDensity = qMax(1, qRound(dpi/referenceDpi));
m_geometry = geometry;
m_availableGeometry = geometry & m_virtualDesktop->workArea();
QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), m_geometry, m_availableGeometry);

View File

@ -161,7 +161,7 @@ public:
QImage::Format format() const override;
QSizeF physicalSize() const override { return m_sizeMillimeters; }
QDpi logicalDpi() const override;
qreal pixelDensity() const override;
QDpi logicalBaseDpi() const override { return QDpi(96, 96); };
QPlatformCursor *cursor() const override;
qreal refreshRate() const override { return m_refreshRate; }
Qt::ScreenOrientation orientation() const override { return m_orientation; }
@ -227,7 +227,6 @@ private:
Qt::ScreenOrientation m_orientation = Qt::PrimaryOrientation;
QXcbCursor *m_cursor;
int m_refreshRate = 60;
int m_pixelDensity = 1;
QEdidParser m_edid;
};

View File

@ -15,3 +15,4 @@ HEADERS += \
RESOURCES += \
highdpi.qrc
DEFINES += HAVE_SCREEN_BASE_DPI