Seaside Laboratory

Posts

Direct2D と DPI スケーリング

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

Direct2D automatically performs scaling to match the DPI setting. In Direct2D, coordinates are measured in units called device-independent pixels (DIPs). A DIP is defined as 1/96th of a logical inch. In Direct2D, all drawing operations are specified in DIPs and then scaled to the current DPI setting.

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 を示します。

DPI 仮想化

古いアプリケーションを DPI スケーリングされた環境で動かすために「DPI 仮想化」という機能が用意されている。PDC 2008 で発表された資料「DPI 対応の Win32 アプリケーションを記述する」では以下のような説明になっている。

Windows Vista には、DPI 仮想化と呼ばれる機能が導入されています。この機能は、DPI 対応でないアプリケーションに、ある程度の自動サイズ変更のサポートを提供します。この機能がなければ、高 DPI 設定では、DPI 対応でないアプリケーションのテキストや UI 要素のサイズがシステムの他の部分よりも小さくなることが多く、その結果、ユーザビリティや読みやすさの問題が生じる可能性があります。

この機能では、アプリケーションが 96 DPI で実行されているかのように、"仮想化された" システムメトリックと UI 要素をアプリケーションに提供することでサポートを提供しています。続いて、アプリケーションは 96 DPI で画面表示領域外にレンダリングされ、デスクトップウィンドウマネージャ (DWM) により、表示されるアプリケーションウィンドウのサイズが DPI 設定に合わせて変更されます。たとえば、ディスプレイの DPI 設定が 144 の場合は、DWM によってアプリケーションウィンドウのサイズは 150% (144/96) に拡大されます。DPI 仮想化で使用されるサイズ変更は、ピクセルの引き伸ばしに基づくタイプのものです。その結果、引き伸ばされたピクセルが原因でぼやけが発生します。

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 を明示的に指定する。