1

We have desktop app which uses Qt 4.8. We are trying to support different DPI screens like Mac retina, Surface Pro 4 devices. For Mac we can get the device pixel ratio by just single function call:

CGFloat devicePixelRatio = [[NSScreen mainScreen] backingScaleFactor];

Is there any utility function available in WinAPI to get device pixel ratio?

thanks.

mahesh
  • 135
  • 1
  • 2
  • 8
  • 1
    What's pixel ratio? I use WinAPI to get the resolution and the physical dimensions of the screen, from there I can calculate the DPI. Is that what you want? – Violet Giraffe Jan 06 '16 at 12:51
  • on MacOSX backingScaleFactor returns 2 for retina display and for non-retina it returns 1. https://msdn.microsoft.com/en-us/library/windows/desktop/dd756596(v=vs.85).aspx this shows sample code to get the ratio, GetDeviceCaps(hdc, LOGPIXELSX) / 96.0f; but some how its returning 1 only :( – mahesh Jan 06 '16 at 13:18
  • BTW, right now I am testing this code on win 8.1 installed on bootcamp on macbook pro retina. – mahesh Jan 06 '16 at 13:22

3 Answers3

2

Finally I found the solution use below code snippet to get the scaling factor,

FLOAT dpiX, dpiY;
HDC screen = GetDC(0);
dpiX = static_cast<FLOAT>(GetDeviceCaps(screen, LOGPIXELSX));
dpiY = static_cast<FLOAT>(GetDeviceCaps(screen, LOGPIXELSY));
ReleaseDC(0, screen);

FLOAT scaleFactor = dpiX / 96.0f; // this is same as devicePixelRatio

Now need to make your app DPI aware. For that set Enable DPI Awareness flag to Yes in Project Settings > Manifest Tool > Input and Output property page.

mahesh
  • 135
  • 1
  • 2
  • 8
0

Here's how I calculate the DPI. Do replace the assertions with actual error checking, as some of those situations actually occur sometimes.

#include <QString>

#include <Windows.h>
#include <SetupApi.h>
#include <cfgmgr32.h>

#include <assert.h>
#include <vector>
#include <stdint.h>

const GUID GUID_CLASS_MONITOR = { 0x4d36e96e, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 };

// Assumes hEDIDRegKey is valid
bool GetMonitorSizeFromEDID(const HKEY hEDIDRegKey, short& WidthMm, short& HeightMm)
{
    DWORD dwType, AcutalValueNameLength = 128;
    TCHAR valueName[128];

    BYTE EDIDdata[1024];
    DWORD edidsize = sizeof(EDIDdata);

    for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS; ++i)
    {
        retValue = RegEnumValue(hEDIDRegKey, i, &valueName[0],
            &AcutalValueNameLength, NULL, &dwType,
            EDIDdata, // buffer
            &edidsize); // buffer size

        if (retValue != ERROR_SUCCESS || QString(valueName) != "EDID")
            continue;

        WidthMm = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
        HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];

        return true; // valid EDID found
    }

    return false; // EDID not found
}

bool GetSizeForDevID(const QString& TargetDevID, short& WidthMm, short& HeightMm)
{
    HDEVINFO devInfo = SetupDiGetClassDevsExA(
        &GUID_CLASS_MONITOR, //class GUID
        NULL, //enumerator
        NULL, //HWND
        DIGCF_PRESENT | DIGCF_PROFILE, // Flags //DIGCF_ALLCLASSES|
        NULL, // device info, create a new one.
        NULL, // machine name, local machine
        NULL);// reserved

    if (NULL == devInfo)
        return false;

    bool success = false;

    for (ULONG i = 0; ERROR_NO_MORE_ITEMS != GetLastError(); ++i)
    {
        SP_DEVINFO_DATA devInfoData;
        memset(&devInfoData, 0, sizeof(devInfoData));
        devInfoData.cbSize = sizeof(devInfoData);

        if (SetupDiEnumDeviceInfo(devInfo, i, &devInfoData))
        {
            CHAR Instance[MAX_DEVICE_ID_LEN];
            SetupDiGetDeviceInstanceIdA(devInfo, &devInfoData, Instance, MAX_DEVICE_ID_LEN, NULL);

            if (!QString(Instance).contains(TargetDevID))
                continue;

            HKEY hEDIDRegKey = SetupDiOpenDevRegKey(devInfo, &devInfoData,
                DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);

            if (!hEDIDRegKey || (hEDIDRegKey == INVALID_HANDLE_VALUE))
                continue;

            success = GetMonitorSizeFromEDID(hEDIDRegKey, WidthMm, HeightMm);
            RegCloseKey(hEDIDRegKey);

            if (success)
                break;
        }
    }
    SetupDiDestroyDeviceInfoList(devInfo);
    return success;
}

static bool DisplayDeviceFromHMonitor(HMONITOR hMonitor, DISPLAY_DEVICE& ddMonOut)
{
    MONITORINFOEX mi;
    mi.cbSize = sizeof(MONITORINFOEX);
    GetMonitorInfoA(hMonitor, &mi);

    DISPLAY_DEVICE dd;
    dd.cb = sizeof(dd);

    for (DWORD devIdx = 0; EnumDisplayDevicesA(nullptr, devIdx, &dd, 0); ++devIdx)
    {
        if (QString(dd.DeviceName) != QString(mi.szDevice))
            continue;

        DISPLAY_DEVICE ddMon;
        memset(&ddMon, 0, sizeof(ddMon));
        ddMon.cb = sizeof(ddMon);
        if (EnumDisplayDevicesA(dd.DeviceName, 0, &ddMon, 0))
        {
            ddMonOut = ddMon;
            return true;
        }

        memset(&dd, 0, sizeof(dd));
        dd.cb = sizeof(dd);
    }

    return false;
}

BOOL CALLBACK MonitorEnumProc(
    _In_  HMONITOR hMonitor,
    _In_  HDC /*hdcMonitor*/,
    _In_  LPRECT /*lprcMonitor*/,
    _In_  LPARAM context
    )

{
    std::vector<HMONITOR> * monitors = (std::vector<HMONITOR>*)context;
    assert(monitors);
    monitors->push_back(hMonitor);

    return TRUE;
}

uint32_t CSystemMetrics::screenDpi(void* window, const CRect& rect)
{
    // Identify the HMONITOR of interest via the callback MyMonitorEnumProc
    HDC dc = GetWindowDC((HWND)window);
    assert(dc);

    RECT windowRect;
    windowRect.top = rect.top;
    windowRect.left = rect.left;
    windowRect.right = rect.right;
    windowRect.bottom = rect.bottom;

    std::vector<HMONITOR> monitors;
    EnumDisplayMonitors(dc, rect.size().area() > 0 ? (&windowRect) : nullptr, MonitorEnumProc, (LPARAM)&monitors);
    ReleaseDC((HWND)window, dc);

    assert(!monitors.empty());

    DISPLAY_DEVICE ddMon;
    assert(DisplayDeviceFromHMonitor(monitors.front(), ddMon));

    MONITORINFO monitorInfo;
    monitorInfo.cbSize = sizeof(MONITORINFO);
    assert(GetMonitorInfoA(monitors.front(), &monitorInfo));

    const auto deviceIdSections = QString(ddMon.DeviceID).split("\\");
    assert(deviceIdSections.size() > 1);

    short widthMm, heightMm;
    assert(GetSizeForDevID(deviceIdSections[1], widthMm, heightMm));

    const float hDPI = (monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left) * 25.4f / widthMm;
    const float vDPI = (monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top) * 25.4f / heightMm;

    const uint32_t dpi = uint32_t(((float)hDPI + (float)vDPI / 2.0f) + 0.5f);

    assert(dpi < 700 && widthMm > 100 && heightMm > 100);

    return dpi;
}
Violet Giraffe
  • 32,368
  • 48
  • 194
  • 335
0

Now there's a complicated issue camouflaged in a simple question :-) I cannot really answer that, but merely provide some minor hints:

Firstly, Windows does not have the device-pixel vs. logical-pixel paradigm known from OSX, so your question about the "device pixel ratio" may cause irritation among non-OSX-developers.

For Qt, there have been some substantial changes from versions 4.x to 5.x. In fact, you might actually want to consider "upgrading" to Qt 5.

http://doc.qt.io/qt-5/highdpi.html

To quote from this link:

"Qt 5.4 introduces experimental support for scaling by by device pixel ratio similar to OS X to the platform plugins for Windows and Unix (XCB)."

Qt 4 on the other hand offers only those tips for "scalable applications":

http://doc.qt.io/qt-4.8/scalability.html

Related SO question:

Automatic rescaling of an application on high-dpi Windows platform?

Community
  • 1
  • 1
ThorngardSO
  • 1,191
  • 7
  • 7
  • Yes upgrading to Qt 5 is perfect solution but it will take much more time than manually scaling up the UI items. I somewhat not agree that windows doesn't have device vs logical pixel paradigm because in there own documentation they have given example code where they are creating a window of size 640x480 but scaling it to system DPI. – mahesh Jan 06 '16 at 13:39
  • `FLOAT dpiX, dpiY; HDC screen = GetDC(0); dpiX = static_cast(GetDeviceCaps(screen, LOGPIXELSX)); dpiY = static_cast(GetDeviceCaps(screen, LOGPIXELSY)); ReleaseDC(0, screen); hWnd = CreateWindow( TEXT("DirectWriteApp"), TEXT("DirectWrite Demo App"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, static_cast(dpiX * 640.f / 96.f), static_cast(dpiY * 480.f / 96.f), NULL, NULL, hInstance, NULL );` – mahesh Jan 06 '16 at 13:39
  • If you cannot answer the question, then please post it as a comment. – Violet Giraffe Jan 06 '16 at 13:46
  • Looks like I found the problem. Some how app was not getting DPI aware after adding true in linker manifest file. Instead this manifest file we just need to turn ON "Enable DPI Awareness" option in project settings > Manifest Tool > Input and Output > Enable DPI Awareness. Many thanks for quick points.. – mahesh Jan 06 '16 at 13:56
  • @Violet Giraffe: I tried commenting instead of answering, but SO reputation policy wouldn't let me... – ThorngardSO Jan 06 '16 at 14:17