Freigeben über


DPI- und geräteunabhängige Pixel

Um effektiv mit Windows-Grafiken zu programmieren, müssen Sie zwei verwandte Konzepte verstehen:

  • Punkte pro Zoll (DPI)
  • Geräteunabhängiges Pixel (DIPs).

Beginnen wir mit DPI. Dies erfordert einen kurzen Umweg in die Typografie. In der Typografie wird die Größe des Typs in Einheiten gemessen, die Punkt. Ein Punkt entspricht 1/72 zoll.

1 Pt = 1/72 Zoll

Anmerkung

Dies ist die Desktopveröffentlichungsdefinition von Punkt. Historisch hat sich das genaue Maß eines Punkts abwechslungsreiche.

Beispielsweise ist eine 12-Punkt-Schriftart so konzipiert, dass sie in eine Textzeile (12/72) (12/72) passt. Natürlich bedeutet dies nicht, dass jedes Zeichen in der Schriftart genau 1/6 Zoll hoch ist. Tatsächlich sind einige Zeichen möglicherweise höher als 1/6". Beispielsweise ist das Zeichen Å in vielen Schriftarten höher als die Nominalhöhe der Schriftart. Um richtig anzuzeigen, benötigt die Schriftart zusätzlichen Platz zwischen dem Text. Dieser Bereich wird als führendebezeichnet.

Die folgende Abbildung zeigt eine Schriftart mit 72 Punkt. Die einfarbigen Linien zeigen ein 1"-hoch umgebendes Feld um den Text. Die gestrichelte Linie wird als Basisliniebezeichnet. Die meisten Zeichen in einer Schriftart ruhen auf der Basislinie. Die Höhe der Schriftart enthält den Teil oberhalb der Grundlinie (die Aufstieg) und den Teil unterhalb der Grundlinie (die Abstieg). In der hier gezeigten Schriftart beträgt der Aufstieg 56 Punkte und der Aufstieg beträgt 16 Punkte.

eine Abbildung, die eine 72-Punkt-Schriftart zeigt.

Bei einer Computeranzeige ist es jedoch problematisch, die Textgröße zu messen, da Pixel nicht alle die gleiche Größe aufweisen. Die Größe eines Pixels hängt von zwei Faktoren ab: der Anzeigeauflösung und der physischen Größe des Monitors. Daher sind physische Zoll kein nützliches Maß, da es keine feste Beziehung zwischen physischem Zoll und Pixel gibt. Stattdessen werden Schriftarten in logischen Einheiten gemessen. Eine 72-Punkt-Schriftart ist so definiert, dass sie ein logisches Zoll hoch ist. Logische Zoll werden dann in Pixel konvertiert. Seit vielen Jahren hat Windows die folgende Konvertierung verwendet: Ein logischer Zoll entspricht 96 Pixel. Bei Verwendung dieses Skalierungsfaktors wird eine Schriftart mit 72 Punkt als 96 Pixel hoch gerendert. Eine 12-Punkt-Schriftart ist 16 Pixel hoch.

12 Punkt = 12/72 logische Zoll = 1/6 logische Zoll = 96/6 Pixel = 16 Pixel

Dieser Skalierungsfaktor wird als 96 Punkte pro Zoll (DPI) beschrieben. Der Begriff Punkte wird vom Drucken abgeleitet, bei dem physische Druckpunkte auf Papier platziert werden. Bei Computeranzeigen wäre es genauer, 96 Pixel pro logischem Zoll zu sagen, aber der Begriff DPI ist hängen geblieben.

Da die tatsächlichen Pixelgrößen variieren, kann text, der auf einem Monitor lesbar ist, auf einem anderen Monitor zu klein sein. Außerdem haben Personen unterschiedliche Vorlieben – manche bevorzugen größeren Text. Aus diesem Grund ermöglicht Windows dem Benutzer, die DPI-Einstellung zu ändern. Wenn der Benutzer beispielsweise die Anzeige auf 144 DPI festlegt, beträgt eine Schriftart mit 72 Punkt 144 Pixel hoch. Die Standard-DPI-Einstellungen sind 100% (96 DPI), 125% (120 DPI) und 150% (144 DPI). Der Benutzer kann auch eine benutzerdefinierte Einstellung anwenden. Ab Windows 7 ist DPI eine Einstellung pro Benutzer.

DWM-Skalierung

Wenn ein Programm keine DPI-Werte berücksichtigt, können die folgenden Fehler bei einstellungen mit hohem DPI-Wert offensichtlich sein:

  • Beschnittene UI-Elemente.
  • Falsches Layout.
  • Pixelierte Bitmaps und Symbole.
  • Falsche Mauskoordinaten, die sich auf Treffertests, Ziehen und Ablegen auswirken können usw.

Um sicherzustellen, dass ältere Programme bei Einstellungen mit hohem DPI-Wert funktionieren, implementiert das DWM einen nützlichen Fallback. Wenn ein Programm nicht als DPI-Wert gekennzeichnet ist, skaliert der DWM die gesamte Benutzeroberfläche entsprechend der DPI-Einstellung. Bei 144 DPI wird die Benutzeroberfläche beispielsweise um 150%skaliert, einschließlich Text, Grafiken, Steuerelemente und Fenstergrößen. Wenn das Programm ein 500 × 500 Fenster erstellt, wird das Fenster tatsächlich als 750 × 750 Pixel angezeigt, und der Inhalt des Fensters wird entsprechend skaliert.

Dieses Verhalten bedeutet, dass ältere Programme bei Einstellungen mit hohem DPI-Wert "nur funktionieren". Die Skalierung führt jedoch auch zu einer etwas verschwommenen Darstellung, da die Skalierung nach dem Zeichnen des Fensters angewendet wird.

DPI-fähige Anwendungen

Um die DWM-Skalierung zu vermeiden, kann sich ein Programm selbst als DPI-fähig kennzeichnen. Dies weist die DWM an, keine automatische DPI-Skalierung durchzuführen. Alle neuen Anwendungen sollten darauf ausgelegt sein, dpi-fähig zu sein, da die DPI-Sensibilisierung das Erscheinungsbild der Benutzeroberfläche bei höheren DPI-Einstellungen verbessert.

Ein Programm deklariert sich durch sein Anwendungsmanifest selbst DPI-fähig. Ein Manifest ist eine einfache XML-Datei, die eine DLL oder Anwendung beschreibt. Das Manifest ist in der Regel in die ausführbare Datei eingebettet, obwohl es als separate Datei bereitgestellt werden kann. Ein Manifest enthält Informationen wie DLL-Abhängigkeiten, die angeforderte Berechtigungsstufe und welche Version von Windows das Programm entwickelt hat.

Um zu deklarieren, dass Ihr Programm DPI-fähig ist, fügen Sie die folgenden Informationen in das Manifest ein.

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" >
  <asmv3:application>
    <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
      <dpiAware>true</dpiAware>
    </asmv3:windowsSettings>
  </asmv3:application>
</assembly>

Die hier gezeigte Auflistung ist nur ein partielles Manifest, aber der Visual Studio-Linker generiert den Rest des Manifests automatisch. Führen Sie die folgenden Schritte in Visual Studio aus, um ein partielles Manifest in Ihr Projekt einzuschließen.

  1. Klicken Sie im Menü Project auf Eigenschaft.
  2. Erweitern Sie im linken Bereich Konfigurationseigenschaften, erweitern Sie Manifesttool-, und klicken Sie dann auf Eingabe- und Ausgabe-.
  3. Geben Sie im Textfeld zusätzliche Manifestdateien den Namen der Manifestdatei ein, und klicken Sie dann auf OK.

Indem Sie Ihr Programm als DPI-fähig markieren, teilen Sie dem DWM mit, das Anwendungsfenster nicht zu skalieren. Wenn Sie nun ein 500 × 500 Fenster erstellen, belegt das Fenster 500 × 500 Pixel, unabhängig von der DPI-Einstellung des Benutzers.

GDI und DPI

Die GDI-Zeichnung wird in Pixel gemessen. Das bedeutet, wenn Ihr Programm als DPI-fähig markiert ist und Sie GDI bitten, ein Rechteck mit 200 × 100 zu zeichnen, das resultierende Rechteck ist 200 Pixel breit und 100 Pixel hoch auf dem Bildschirm. GDI-Schriftgrade werden jedoch auf die aktuelle DPI-Einstellung skaliert. Anders ausgedrückt: Wenn Sie eine Schriftart mit 72 Punkt erstellen, beträgt der Schriftgrad 96 Pixel bei 96 DPI, aber 144 Pixel bei 144 DPI. Hier ist eine 72-Punkt-Schriftart, die mit GDI mit 144 DPI gerendert wird.

ein Diagramm, das die DPI-Schriftskalierung in gdi zeigt.

Wenn Ihre Anwendung DPI-fähig ist und Sie GDI zum Zeichnen verwenden, skalieren Sie alle Zeichnungskoordinaten so, dass sie mit dem DPI-Wert übereinstimmen.

Direct2D und DPI

Direct2D führt automatisch eine Skalierung aus, um der DPI-Einstellung zu entsprechen. In Direct2D werden Koordinaten in Einheiten gemessen, die geräteunabhängigen Pixeln (DIPs) genannt werden. Ein DIP wird als 1/96. eines logischen Zoll definiert. In Direct2D werden alle Zeichnungsvorgänge in DIPs angegeben und dann auf die aktuelle DPI-Einstellung skaliert.

DPI-Einstellung DIP-Größe
96 1 Pixel
120 1,25 Pixel
144 1,5 Pixel

Wenn die DPI-Einstellung des Benutzers beispielsweise 144 DPI beträgt und Sie Direct2D bitten, ein Rechteck mit 200 × 100 zu zeichnen, beträgt das Rechteck 300 × 150 physische Pixel. Darüber hinaus misst DirectWrite Schriftgrade in DIPs anstelle von Punkten. Um eine 12-Punkt-Schriftart zu erstellen, geben Sie 16 DIPs an (12 Punkt = 1/6 logische Zoll = 96/6 DIPs). Wenn der Text auf dem Bildschirm gezeichnet wird, konvertiert Direct2D die DIPs in physische Pixel. Der Vorteil dieses Systems besteht darin, dass die Maßeinheiten unabhängig von der aktuellen DPI-Einstellung für Text und Zeichnung konsistent sind.

Vorsicht: Maus- und Fensterkoordinaten werden weiterhin in physischen Pixeln und nicht in DIPs angegeben. Wenn Sie beispielsweise die WM_LBUTTONDOWN Nachricht verarbeiten, wird die Maus-Abwärtsposition in physischen Pixeln angegeben. Um einen Punkt an dieser Position zu zeichnen, müssen Sie die Pixelkoordinaten in DIPs konvertieren.

Konvertieren physischer Pixel in DIPs

Der Basiswert von DPI wird als USER_DEFAULT_SCREEN_DPI definiert, der auf 96 festgelegt ist. Um den Skalierungsfaktor zu ermitteln, nehmen Sie den DPI-Wert und dividieren Sie durch USER_DEFAULT_SCREEN_DPI.

Die Konvertierung von physischen Pixeln in DIPs verwendet die folgende Formel.

DIPs = pixels / (DPI / USER_DEFAULT_SCREEN_DPI)

Rufen Sie zum Abrufen der DPI-Einstellung die GetDpiForWindow-Funktion auf. Der DPI-Wert wird als Gleitkommawert zurückgegeben. Berechnen Sie den Skalierungsfaktor für beide Achsen.

float g_DPIScale = 1.0f;

void InitializeDPIScale(HWND hwnd)
{
    float dpi = GetDpiForWindow(hwnd);
    g_DPIScale = dpi / USER_DEFAULT_SCREEN_DPI;
}

template <typename T>
float PixelsToDipsX(T x)
{
    return static_cast<float>(x) / g_DPIScale;
}

template <typename T>
float PixelsToDipsY(T y)
{
    return static_cast<float>(y) / g_DPIScale;
}

Hier ist eine alternative Möglichkeit zum Abrufen der DPI-Einstellung, wenn Sie Direct2D nicht verwenden:

void InitializeDPIScale(HWND hwnd)
{
    HDC hdc = GetDC(hwnd);
    g_DPIScaleX = (float)GetDeviceCaps(hdc, LOGPIXELSX) / USER_DEFAULT_SCREEN_DPI;
    g_DPIScaleY = (float)GetDeviceCaps(hdc, LOGPIXELSY) / USER_DEFAULT_SCREEN_DPI;
    ReleaseDC(hwnd, hdc);
}

Anmerkung

Wir empfehlen, dass Sie für eine Desktop-App GetDpiForWindowverwenden; und verwenden Sie für eine UWP-App (Universelle Windows-Plattform) DisplayInformation::LogicalDpi. Es wird zwar nicht empfohlen, aber es ist möglich, den standardmäßigen DPI-Grad programmgesteuert mithilfe von SetProcessDpiAwarenessContextfestzulegen. Nachdem ein Fenster (ein HWND) in Ihrem Prozess erstellt wurde, wird das Ändern des DPI-Sensibilisierungsmodus nicht mehr unterstützt. Wenn Sie den prozessbasierten DPI-Sensibilisierungsmodus programmgesteuert festlegen, müssen Sie die entsprechende API aufrufen, bevor HWNDs erstellt wurden. Weitere Informationen finden Sie unter Festlegen des standardmäßigen DPI-Bewusstseins für einen Prozess.

Ändern der Größe des Renderziels

Wenn sich die Größe des Fensters ändert, müssen Sie die Größe des Renderziels entsprechend ändern. In den meisten Fällen müssen Sie auch das Layout aktualisieren und das Fenster neu erstellen. Der folgende Code zeigt diese Schritte.

void MainWindow::Resize()
{
    if (pRenderTarget != NULL)
    {
        RECT rc;
        GetClientRect(m_hwnd, &rc);

        D2D1_SIZE_U size = D2D1::SizeU(rc.right, rc.bottom);

        pRenderTarget->Resize(size);
        CalculateLayout();
        InvalidateRect(m_hwnd, NULL, FALSE);
    }
}

Die GetClientRect--Funktion ruft die neue Größe des Clientbereichs in physischen Pixeln (keine DIPs) ab. Die ID2D1HwndRenderTarget::Resize Methode aktualisiert die Größe des Renderziels, auch in Pixel angegeben. Die funktion InvalidateRect erzwingt eine Neuaktualisierung, indem der gesamte Clientbereich zum Aktualisierungsbereich des Fensters hinzugefügt wird. (Siehe Malen des Fenstersin Modul 1.)

Wenn das Fenster wächst oder verkleinern wird, müssen Sie in der Regel die Position der Objekte neu berechnen, die Sie zeichnen. Beispielsweise muss im Kreisprogramm der Radius und der Mittelpunkt aktualisiert werden:

void MainWindow::CalculateLayout()
{
    if (pRenderTarget != NULL)
    {
        D2D1_SIZE_F size = pRenderTarget->GetSize();
        const float x = size.width / 2;
        const float y = size.height / 2;
        const float radius = min(x, y);
        ellipse = D2D1::Ellipse(D2D1::Point2F(x, y), radius, radius);
    }
}

Die ID2D1RenderTarget::GetSize Methode gibt die Größe des Renderziels in DIPs (nicht Pixel) zurück, die für die Berechnung des Layouts geeignet ist. Es gibt eine eng verwandte Methode, ID2D1RenderTarget::GetPixelSize, die die Größe in physischen Pixeln zurückgibt. Bei einem HWND- Renderziel entspricht dieser Wert der von GetClientRectzurückgegebenen Größe. Denken Sie jedoch daran, dass die Zeichnung in DIPs und nicht in Pixeln ausgeführt wird.

Nächster

Verwenden von Farben in Direct2D-