DPI とデバイスに依存しないピクセル
Windows グラフィックスを効果的にプログラムするには、次の 2 つの関連概念を理解する必要があります。
- ドット/インチ (DPI)
- デバイスに依存しないピクセル (DIP)。
DPI から始めましょう。 これには、タイポグラフィへの短い回り道が必要になります。 文字体裁では、型のサイズは ポイントと呼ばれる単位で測定されます。 1 ポイントは 1/72 インチです。
- 1 pt = 1/72 インチ
手記
これは、ポイントのデスクトップ公開定義です。 歴史的に、ポイントの正確な尺度はさまざまです。
たとえば、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 ピクセル
この倍率は、1 インチあたり 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 で次の手順を実行します。
- [プロジェクト ] メニューの [プロパティ クリックします。
- 左側のウィンドウで、[構成プロパティ] 展開し、[マニフェスト ツール] 展開し、[入力] と [出力] クリックします。
- [追加のマニフェスト ファイル ] テキスト ボックスにマニフェスト ファイルの名前を入力し、[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 に依頼すると、四角形は 300 × 150 物理ピクセルになります。 さらに、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 PixelsToDipsY(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);
}
手記
デスクトップ アプリでは、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 で色を使用する