DPI とデバイスに依存しないピクセル

Windows グラフィックスを効果的にプログラムするには、次の 2 つの関連概念を理解する必要があります。

  • DPI (1 インチあたりのドット数)
  • デバイスに依存しないピクセル (DIP)。

DPI から始めましょう。 これには、文字体裁への短い迂回が必要になります。 文字体裁では、型のサイズは ポイントと呼ばれる単位で測定されます。 1 ポイントは 1 インチの 1/72 に等しくなります。

1 pt = 1/72 インチ

Note

これは、ポイントのデスクトップ発行定義です。 歴史的に、ポイントの正確な測定値はさまざまです。

たとえば、12 ポイントのフォントは、1/6" (12/72) 行のテキスト内に収まるように設計されています。 明らかに、これはフォント内のすべての文字が正確に1/6"背が高いという意味ではありません。 実際、一部の文字は 1/6 より高い可能性があります。 たとえば、多くのフォントでは、文字 Å はフォントの標準の高さよりも高くなります。 正しく表示するには、フォントのテキスト間に追加のスペースが必要です。 このスペースは 、先頭と呼ばれます。

次の図は、72 ポイントのフォントを示しています。 実線には、テキストの周囲に高さ 1 インチの境界ボックスが表示されます。 破線は ベースラインと呼ばれます。 フォントのほとんどの文字は、ベースライン上の残りの部分です。 フォントの高さは、ベースライン ( 上昇) の上の部分とベースラインの下の部分 ( 降下) を含みます。 ここに示すフォントでは、上昇は 56 ポイント、降下は 16 ポイントです。

72 ポイントのフォントを示す図。

ただし、コンピューターディスプレイに関しては、ピクセルがすべて同じサイズではないので、テキストサイズの測定に問題があります。 ピクセルのサイズは、ディスプレイの解像度とモニターの物理サイズの 2 つの要因によって異なります。 したがって、物理的なインチとピクセルの間に固定の関係がないため、物理的なインチは有用な手段ではありません。 代わりに、フォントは 論理 単位で測定されます。 72 ポイントのフォントは、高さが 1 インチの論理インチとして定義されます。 その後、論理インチはピクセルに変換されます。 Windows では長年にわたり、次の変換が使用されています。1 つの論理インチは 96 ピクセルに相当します。 この拡大縮小率を使用すると、72 ポイントのフォントは 96 ピクセルの高さとしてレンダリングされます。 12 ポイントのフォントの高さは 16 ピクセルです。

12 ポイント = 12/72 論理インチ = 1/6 論理インチ = 96/6 ピクセル = 16 ピクセル

この拡大縮小率は、96 ドット/インチ (DPI) と記述されています。 ドットという用語は、インクの物理的なドットが紙の上に置かれる印刷から派生します。 コンピューターディスプレイの場合、論理インチあたり 96 ピクセルと言う方が正確ですが、DPI という用語はスタックしています。

実際のピクセル サイズは異なるため、あるモニターで読み取り可能なテキストが別のモニターでは小さすぎる可能性があります。 また、ユーザーの好みは異なります。一部のユーザーは、より大きなテキストを好みます。 このため、Windows を使用すると、ユーザーは DPI 設定を変更できます。 たとえば、ユーザーがディスプレイを 144 DPI に設定すると、72 ポイントのフォントの高さは 144 ピクセルになります。 標準 DPI 設定は、100% (96 DPI)、125% (120 DPI)、150% (144 DPI) です。 ユーザーはカスタム設定を適用することもできます。 Windows 7 以降では、DPI はユーザーごとの設定です。

DWM スケーリング

プログラムが DPI を考慮していない場合は、高 DPI 設定で次の欠陥が見られる可能性があります。

  • クリップされた UI 要素。
  • レイアウトが正しくありません。
  • ピクセル化されたビットマップとアイコン。
  • マウス座標が正しくありません。ヒット テスト、ドラッグ アンド ドロップなどに影響する可能性があります。

古いプログラムが高 DPI 設定で動作するように、DWM は便利なフォールバックを実装します。 プログラムが DPI 対応としてマークされていない場合、DWM は DPI 設定に合わせて UI 全体をスケーリングします。 たとえば、144 DPI では、UI はテキスト、グラフィックス、コントロール、ウィンドウ サイズなど、150% ずつスケーリングされます。 プログラムによって 500 × 500 ウィンドウが作成された場合、ウィンドウは実際には 750 × 750 ピクセルとして表示され、ウィンドウの内容はそれに応じてスケーリングされます。

この動作は、古いプログラムが高 DPI 設定で "動作する" ことを意味します。 ただし、ウィンドウの描画後にスケーリングが適用されるため、スケーリングではややぼやけた外観になります。

DPI 対応アプリケーション

DWM スケーリングを回避するために、プログラムはそれ自体を DPI 対応としてマークできます。 これにより、DWM は自動 DPI スケーリングを実行しないように指示します。 DPI 認識により、より高い DPI 設定で UI の外観が向上するため、すべての新しいアプリケーションは DPI 対応に設計する必要があります。

プログラムは、アプリケーション マニフェストを通じて DPI 対応を宣言します。 マニフェストは、DLL またはアプリケーションを記述する単なる XML ファイルです。 マニフェストは通常、実行可能ファイルに埋め込まれますが、別のファイルとして提供できます。 マニフェストには、DLL の依存関係、要求された特権レベル、プログラムが設計された Windows のバージョンなどの情報が含まれています。

プログラムが DPI 対応であることを宣言するには、マニフェストに次の情報を含めます。

<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>

ここに示す一覧は部分的なマニフェストのみですが、Visual Studio リンカーによってマニフェストの残りの部分が自動的に生成されます。 プロジェクトに部分的なマニフェストを含めるには、Visual Studio で次の手順を実行します。

  1. [ プロジェクト ] メニューの [ プロパティ] をクリックします。
  2. 左側のウィンドウで、[ 構成プロパティ] を展開し、[ マニフェスト ツール] を展開して、[ 入力と出力] をクリックします。
  3. [ 追加のマニフェスト ファイル ] テキスト ボックスにマニフェスト ファイルの名前を入力し、[OK] をクリック します

プログラムを DPI 対応としてマークすることで、DWM にアプリケーション ウィンドウをスケーリングしないように指示します。 500 × 500 ウィンドウを作成すると、ユーザーの DPI 設定に関係なく、ウィンドウは 500 × 500 ピクセルを占めます。

GDI と DPI

GDI 描画はピクセル単位で測定されます。 つまり、プログラムが DPI 対応としてマークされていて、GDI に 200 × 100 の四角形を描画するように求める場合、結果の四角形の幅は 200 ピクセル、画面の高さは 100 ピクセルになります。 ただし、GDI フォント サイズは現在の DPI 設定にスケーリングされます。 つまり、72 ポイントのフォントを作成すると、フォントのサイズは 96 DPI で 96 ピクセル、144 DPI では 144 ピクセルになります。 GDI を使用して 144 DPI でレンダリングされる 72 ポイントのフォントを次に示します。

gdi での dpi フォントのスケーリングを示す図。

アプリケーションが DPI 対応で、描画に GDI を使用する場合は、DPI に合わせてすべての描画座標をスケーリングします。

Direct2D と DPI

Direct2D では、DPI 設定に合わせて自動的にスケーリングが実行されます。 Direct2D では、座標は デバイスに依存しないピクセル (DIP) と呼ばれる単位で測定されます。 DIP は、 論理 インチの 1/96 分の 1 として定義されます。 Direct2D では、すべての描画操作が DIP で指定され、現在の DPI 設定にスケーリングされます。

DPI 設定 DIP サイズ
96 1 ピクセル
120 1.25 ピクセル
144 1.5 ピクセル

たとえば、ユーザーの DPI 設定が 144 DPI で、200 × 100 の四角形を描画するように Direct2D に依頼すると、四角形は 150 × 300 物理ピクセルになります。 さらに、DirectWriteはポイントではなく、DIP のフォント サイズを測定します。 12 ポイントのフォントを作成するには、16 個の DIP (12 ポイント = 1/6 論理インチ = 96/6 DIP) を指定します。 テキストが画面に描画されると、Direct2D は DIP を物理ピクセルに変換します。 このシステムの利点は、現在の DPI 設定に関係なく、測定単位がテキストと描画の両方で一貫していることです。

注意: マウスとウィンドウの座標は、DIP ではなく物理ピクセル単位で引き続き指定されます。 たとえば、 WM_LBUTTONDOWN メッセージを処理すると、マウスの下の位置は物理ピクセル単位で指定されます。 その位置にポイントを描画するには、ピクセル座標を DIP に変換する必要があります。

物理ピクセルを DIP に変換する

DPI の基本値は、96 に設定された として USER_DEFAULT_SCREEN_DPI 定義されます。 拡大縮小率を決定するには、DPI 値を取得し、 で除算します USER_DEFAULT_SCREEN_DPI

物理ピクセルから DIP への変換では、次の式を使用します。

DIPs = pixels / (DPI / USER_DEFAULT_SCREEN_DPI)

DPI 設定を取得するには、 GetDpiForWindow 関数を呼び出します。 DPI は浮動小数点値として返されます。 両方の軸の拡大縮小率を計算します。

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 PixelsToDips(T y)
{
    return static_cast<float>(y) / g_DPIScale;
}

Direct2D を使用していない場合は、DPI 設定を取得する別の方法を次に示します。

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);
}

Note

デスクトップ アプリの場合は、GetDpiForWindow を使用することをお勧めします。ユニバーサル Windows プラットフォーム (UWP) アプリの場合は、DisplayInformation::LogicalDpi を使用します。 お勧めしませんが、 SetProcessDpiAwarenessContext を使用して、既定の DPI 認識をプログラムで設定できます。 プロセスでウィンドウ (HWND) が作成されると、DPI 認識モードの変更はサポートされなくなりました。 プロセスの既定の DPI 認識モードをプログラムで設定する場合は、HWND が作成される前に、対応する API を呼び出す必要があります。 詳細については、「 プロセスの既定の DPI 認識の設定」を参照してください。

レンダー ターゲットのサイズを変更する

ウィンドウのサイズが変更された場合は、一致するようにレンダー ターゲットのサイズを変更する必要があります。 ほとんどの場合、レイアウトを更新してウィンドウを再描画する必要もあります。 次のコードは、これらの手順を示しています。

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);
    }
}

GetClientRect 関数は、クライアント領域の新しいサイズを物理ピクセル (DIP ではなく) で取得します。 ID2D1HwndRenderTarget::Resize メソッドは、レンダリング ターゲットのサイズ (ピクセル単位でも指定) を更新します。 InvalidateRect 関数は、クライアント領域全体をウィンドウの更新領域に追加して、強制的に再描画します。 (モジュール 1 の「ウィンドウの描画」を参照してください)。

ウィンドウが拡大または縮小されると、通常、描画するオブジェクトの位置を再計算する必要があります。 たとえば、円プログラムでは、半径と中心点を更新する必要があります。

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);
    }
}

ID2D1RenderTarget::GetSize メソッドは、レンダリング ターゲットのサイズをピクセル単位ではなく DIP 単位で返します。これは、レイアウトを計算するための適切な単位です。 物理ピクセル単位でサイズを返す、密接に関連するメソッド ID2D1RenderTarget::GetPixelSize があります。 HWND レンダー ターゲットの場合、この値は GetClientRect によって返されるサイズと一致します。 ただし、描画はピクセルではなく、DIP で実行されることを覚えておいてください。

次へ

Direct2D での色の使用