Seaside Laboratory

Post

Direct2D と DPI スケーリング

画面設定によって Direct2D で描画したもののサイズが変化してしまうことがあったので、もしやと思って調べてみると DPI スケーリングが影響していることが分かった。この挙動に関しては MSDN の「DPI と DIP (デバイス非依存ピクセル)」に書かれている。

Direct2D は DPI 設定と一致するように、自動でスケールを実行します。
Direct2D では、座標が DIP (デバイス非依存ピクセル) という単位で表されます。
DIP は "論理" インチの 1/96 と定義されています。
Direct2D ではすべての描画処理を DIP 数で指定した後、現在の DPI 設定に合わせてスケールを実行します。

DPI スケーリングにどう対応するかどうかも含め、Windows での DPI の扱いについて調べてみた。

DPI スケーリングが必要な理由

今まで物理ピクセルを基準としていた Windows に何故「高 DPI」なんていう概念が出てきたのか。それについては MSDN ブログ「アプリの高 DPI 対応について」に書かれている。

Surface Pro 3 をはじめ、最近発売される PC の解像度は高くなっています。
Surface Pro 3 の場合は、12 インチのディスプレイで解像度は 2160x1440 です。
Surface Pro 2 でも、10 インチのディスプレイで解像度は 1920x1080 です。
解像度がこれだけ高いと画像イメージは鮮明になりますが、
文字は拡大しないと小さく表示され、読みにくくなってしまいます。

要するに UI を物理ピクセル基準で作ると高解像度画面で小さくなってしまうのと、デバイスの多様化で環境によって画面の大きさが異なるケースが増えてきたので、高 DPI に対応したアプリケーションを作りましょう、という話。

DPI とは

まず、Wikipedia での DPI の定義をから。

dpi (ディーピーアイ、DPI とも表記) とは、dots per inch の略で、ドット密度の単位である。
1 インチ (1 平方インチではない) の幅の中にどれだけのドットを表現できるかを表す。

似たものとして PPI があるが、

ディスプレイでは、多くの場合、ピクセルとドットが 1 対 1 で対応するので、ppi は dpi に等しい。
そのため、ppi と dpi は同じ意味で使われる。

同じものとして考えて問題ない。

Windows における DPI の定義

ビットマップのヘッダー情報や各種 API のリファレンスには度々 "96dpi" という数値が登場するが、これは Windows が 96dpi 基準で設計されたことから来ている。この 96 という数値が選ばれた理由については MSDN ブログの「Where does 96 DPI come from in Windows?」に書かれている。

Most of the displays were of very low resolution.
The Macintosh display was 72 PPI while most of the "IBM" style displays were less than 72.
The VGA displays were around 70 to 74 PPI.

ディスプレイのほとんどは非常に低い解像度でした。
Macintosh のディスプレイは 72 PPI でしたが、ほとんどの「IBM」スタイルのディスプレイは 72 未満でした。
VGA ディスプレイは約 70 ~ 74 PPI でした。

When reading a book or other piece of paper, the text is usually held at arms length distance.
Computer monitors, on the other hand, tend to be farther away, somewhere around 1/3 the distance more than paper.
The size of the text on your retina is impacted by the distance from the text.
In order for the text to appear to be the same size, the text farther away must be larger.

本や他の紙を読むとき、テキストは通常、腕の長さの距離で保持されます。
一方、コンピュータモニタは、紙よりも距離の約 1/3 近く離れている傾向があります。
網膜上のテキストのサイズは、テキストからの距離の影響を受けます。
テキストが同じサイズに見えるようにするには、離れたテキストを大きくする必要があります。

The Windows solution was somewhat controversial.
The decision was made to report the resolution of displays on Windows as about 1/3 greater than actual resolution.
This roughly corresponds to the increased reading distance.
So, for displays around 72 PPI, Windows would indicate 96 PPI.

Windows の解決策は多少議論の余地がありました。
この決定は、Windows 上のディスプレイの解像度は実際の解像度より約 1/3 上回っていると報告されたためです。
これはおおよそ、読み上げ距離の増加に相当します。
したがって、約 72 PPI のディスプレイでは、Windows は 96 PPI を示します。

ページの最後では物理ピクセルを基準としたアプリケーションの問題について触れている。

many, many applications have been written for Windows with 96 PPI.
Unfortunately, many of these applications have made assumptions about the size of a pixel
and many dialog boxes and web pages have been designed around 96 PPI.

多くのアプリケーションが Windows 用に 96 PPI で書かれています。
残念なことに、これらのアプリケーションの多くはピクセルのサイズを前提にしており、
多くのダイアログボックスや Web ページは 96 PPI 前後に設計されています。

Unfortunately if one adjusts for this by using PPIs besides 96,
then there is a risk of some applications or web pages not working properly.

残念なことに、96 以外の PPI を使用してこれを調整すると、
一部のアプリケーションや Web ページが正しく動作しなくなるリスクがあります。

Win32 ベースのアプリケーションでの DPI スケーリング対策

物理ピクセル基準の Win32 API と、DPI スケーリングに対応した Direct2D を混在させて使う場合、アプリケーションをどちらの基準で作成するのか決めなければならない。

  • Win32 API とやり取りする際の入出力値に対して画面の拡大率を適用し、DPI スケーリングに対応する。
  • Direct2D を物理ピクセル基準にする。

マイクロソフトの方針としては前者のようなユーザービリティの高い設計が望ましいのだろうが、物理ピクセルを扱う API はたくさんあるので、それらすべてに変換処理を入れるのは現実的ではない。変換による誤差も発生するので全てのケースにおいて正常な動作を保証するのが難しいという問題もある。

MSDN にある Direct2D のサンプルプログラムは DPI スケーリングに対応しているので、ウィンドウの大きさを表すピクセル値に現在の DPI 数から求めた拡大率を適用する計算が入っている。

// to create its own windows.
m_pDirect2dFactory->GetDesktopDpi(&dpiX, &dpiY);

// Create the window.
m_hwnd = CreateWindow(
    L"D2DDemoApp",
    L"Direct2D Demo App",
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    static_cast<UINT>(ceil(640.f * dpiX / 96.f)),
    static_cast<UINT>(ceil(480.f * dpiY / 96.f)),
    NULL,
    NULL,
    HINST_THISCOMPONENT,
    this
    );

作っているものがゲームという特性上、ダイアログのような UI がないということと、アプリケーション側に画面を拡大する機能を持たせているので、Direct2D を物理ピクセル基準にすることにした。

Direct2D を物理ピクセル基準にする

物理ピクセル基準にするのは簡単で、レンダーターゲットを作成する際に固定の DPI 数を指定するだけ。

struct D2D1_RENDER_TARGET_PROPERTIES {
  D2D1_RENDER_TARGET_TYPE  type;
  D2D1_PIXEL_FORMAT        pixelFormat;
  FLOAT                    dpiX;
  FLOAT                    dpiY;
  D2D1_RENDER_TARGET_USAGE usage;
  D2D1_FEATURE_LEVEL       minLevel;
};

D2D1::RenderTargetProperties を引数なしで呼ぶと現在の DPI 数が使われてしまうので、dpiX と dpiY に Windows の基準 DPI 数である 96 を明示的に指定する。