高 DPI アプリケーションの記述方法

Nick Kramer
Microsoft Corporation

March 2001
日本語版最終更新日 2001年8月20日

要約: この記事では、高密度ディスプレイ上で動作するアプリケーションの作成方法を説明します。特に、テキストとフォント、イメージ (グラフィックス、アイコン、カーソル)、レイアウト、およびペイントの 4 つの一般的な分野に焦点を当てます。

目次

はじめに
システム メトリックス
主な問題点
GDI+
Visual Basic と共通言語ランタイム
高 DPI アプリケーションのテスト方法

はじめに

あなたのアプリケーションは高密度ディスプレイ上で動作しますか? おそらく動作しないでしょう。今日の標準的なコンピュータ モニタは、約 96 DPI のディスプレイを使用しており、大部分のアプリケーションは 96 DPI のディスプレイ上で実行されることを想定しています。しかし、この前提は不確かなものになりつつあります。今日では、133 DPI のディスプレイを備えたラップトップを簡単に買うことができます。170 DPI のディスプレイはもうすぐ入手可能になりますし、今後 2、3 年の間には 200 DPI のディスプレイが大量に出荷されるようになるでしょう。業界紙の DisplaySearch は、2002 年末までに、販売されるラップトップの 40% が 100 DPI 以上の密度を持つようになると予測しています。事態がこれよりも速く進むと考えている関係者も少なくありません。

高 DPI に対応していないアプリケーションは、アプリケーションの種類と DPI によって、見栄えが悪くなったり、さらには使用不可能にさえなりかねません。大きいフォントをサポートしているアプリケーションの大部分は、比率がおかしくなるにせよ、130 DPI では使用可能です。しかし、200 DPI では同じアプリケーションが使用不可能になります。アプリケーションが一様に小さくなることもあるでしょうが、一般には、図 1 に示すように一部の要素が正しいサイズで描画され (たとえばアプリケーションが DPI に伴って大きくなる DEFAULT_GUI_FONT を使用している場合)、他の要素は縮小されます。

ms969894.highdpiapp1(ja-jp,MSDN.10).gif

図 1. 解像度を変更した結果

アプリケーションを高 DPI ディスプレイに対応させることの必要性は高まりつつありますが、この傾向にはいくつかの重要な利点があります。イメージの見栄えは良くなり、テキストはより鮮明に、読みやすいものになります。200 DPI モニタ上のテキストは、レーザー プリンタからのプリントアウトと同じほどクリアです (コンピュータ ディスプレイはよりシャープなピクセルを持ち、グレー スケールをサポートしているため、200 DPI のモニタは 600 DPI のプリンタに相当します)。PDA と eBook のメーカー各社は、紙との競争力を高めるために、高 DPI ディスプレイに目を向けつつあります。

全体的に見て、高 DPI アプリケーションの作成は簡単で、DPI に関する仮定を設けず、拡大縮小がうまく行かない要素 (ビットマップやビットマップ フォントなど) を避けるだけです。高 DPI アプリケーションの作成は、ときには面倒なこともありますが、難しいということはめったにありません。また、アプリケーションがアクセシビリティを考慮したものである場合には、作業のほとんどはすでに終わっていると言えます (ハイコントラスト モードは大きなフォントを使用しますが、これは多くの点で高 DPI における作業と同様のものです)。

システム メトリックス

Microsoft(R) Windows(R) は、ユーザー システムに関する仮定を考慮せずに済むように使用すべき多くのシステム メトリックスを用意しています。GetDeviceCaps() は、ディスプレイの DPI を取得するために使用できます (第 2 パラメータとして LOGPIXELSX または LOGPIXELSY を渡します)。GetSystemMetrics()SystemParametersInfo() は、3 次元境界の幅から小さいアイコンのサイズまで、Windows 内のほぼすべてのグラフィカル要素のサイズを提供します。比較的目立たないメトリックスの 1 つが、線の太さの最小値です (GetSystemMetrics に SM_CYBORDER または SM_CXBORDER を渡します)。きわめて高い DPI では、1 ピクセル幅の線はほとんど見えなくなります。

GetDeviceCaps からの情報をもとにスケーリング係数を適用する SCALEX および SCALEY マクロを定義することができます。



// 96 DPI ピクセルを想定しているデザインをどれだけスケーリングするか
double scaleX;
double scaleY;
#define SCALEX(argX) ((int) ((argX) * scaleX))
#define SCALEY(argY) ((int) ((argY) * scaleY))

void InitScaling() {
   HDC screen = GetDC(0);
   scaleX = GetDeviceCaps(screen, LOGPIXELSX) / 96.0;
   scaleY = GetDeviceCaps(screen, LOGPIXELSY) / 96.0;
   ReleaseDC(0, screen);
}

主な問題点

高 DPI アプリケーションでは、テキストとフォント、イメージ (グラフィックス、アイコン、カーソル)、レイアウト、およびペイントの 4 つの一般的な分野に注意を払う必要があります。

テキストとフォント

フォントにはビットマップ フォントと TrueType フォントの 2 つの種類があります。高 DPI アプリケーションは TrueType フォントのみを使用するべきです。ビットマップ フォントは 96 DPI のスクリーン用に作られており、スケーリングしません。一方、TrueType フォントはどの DPI でもきれいに見えます。Windows はすでに長い間 TrueType をサポートしてきたので、適当な TrueType フォントは簡単に見つかるでしょう (TrueType フォントのみを使用すべきもう 1 つの理由として、GDI+ のような一部の新しいテクノロジが TrueType フォントのみをサポートしているということがあります)。

ウィンドウ ハンドル (HWND) とグラフィックス デバイス コンテキスト (HDC) の既定フォントはビットマップ フォントなので、必ず別のフォントに変えるようにしてください。その名前にもかかわらず、DEFAULT_GUI_FONT は HWND と HDC の既定フォントではなく TrueType なので、これを使うことを検討してください。


HFONT font = (HFONT) GetStockObject(DEFAULT_GUI_FONT);
SendMessage(hwnd, WM_SETFONT, (WPARAM) font, 0);
SelectObject(hdc, font);

Windows でフォントを作成するときにはフォント サイズをピクセル単位で指定するので、DPI に合わせて調整する必要があります。


LOGFONT lf;
memset(&lf, 0, sizeof(lf));
lf.lfHeight = SCALEY(13);
HFONT font = CreateFontIndirect(&lf);

Windows の ChooseFont ダイアログ ボックスでは、フォント サイズの指定にピクセル数ではなくポイントを使用することができます。ChooseFont は、フォント サイズをピクセル単位に変換する際に、DPI を考慮に入れます。このダイアログ ボックスは TrueType フォントのみに制限した方がいいでしょう。このためには CF_TTONLY フラグを使用します。


CHOOSEFONT data;
memset(&data, 0, sizeof(data));
data.lStructSize = sizeof(data);
data.hwndOwner = form;
data.Flags = CF_TTONLY | CF_SCREENFONTS;
ChooseFont(&data);

一般に、高 DPI アプリケーションでサイズと位置を指定するときには、ページ上の要素を指定するための測定単位としてフォント サイズを使うといいでしょう。たとえば、ボタン間のスペースを既定のシステム フォントの高さと等しい値に設定します。フォントの高さは GetTextMetrics() を使って取得します。


TEXTMETRIC metrics;
GetTextMetrics(hdc, &metrics);
int height = metrics.tmHeight;

TEXTMETRIC.tmAveCharWidth は使わないようにしてください。この値は英語の文字しか考慮に入れていませんし、表示したい文字が平均的な文字であるとは限りません。その代わりに、GetTextExtent の関数ファミリを使って、表示したい文字列のサイズを測定します。次の例は、GetTextExtentPoint32() を使って、文字列の周囲に矩形を描画しています。


SIZE size;
GetTextExtentPoint32(hdc, string, strlen(string), &size);

int paddingX = SCALEX(8);
int paddingY = SCALEX(8);
Rectangle(hdc, x - paddingX, y - paddingY, x + size.cx 
+ paddingX, y + size.cy + paddingY);
TextOut(hdc, x, y, string, strlen(string));

最後に、TrueType はきれいにスケーリングしますが、線形的にスケーリングするわけではないことに注意してください。DPI を 10% 増やしても、一般に文字列の長さがちょうど 10% 増えるわけではありません (GDI+ にはこの問題はありません。 GDI+ のセクションを参照してください)。これは、どの文字もいくつかの特定のサイズでしか見栄えが良くならず、TrueType が見栄えの良い最も近いサイズを選択するからです。これもまた GetTextExtent 関数を使用すべき理由の 1 つです。

イメージ

“イメージ”には、すべてのラスタ ベースのイメージ ファイル (BMP、JPEG、GIF など)、アイコン、およびカーソルが含まれます。イメージは孤立したピクセルから構成されるので、フォントよりも扱いが難しくなります。ディスプレイの DPI が、そのイメージのデザイン時に念頭に置かれていた DPI と異なる場合、正しい物理的なサイズで表示するためにはイメージをスケーリングする必要があります。ビットマップのスケーリングは、BitBlt() の代わりに StretchBlt() を呼び出すことで行うことができます。一般には、イメージを描画する際にスケーリングするよりも、ロード時にスケーリングする方がアプリケーションの処理は簡単になります。StretchBlt はこの目的にも使用することができます。次の例は、96 DPI 用にデザインされたビットマップをスケーリングして描画します。


BITMAP info;
GetObject(bitmap, sizeof(info), (PTSTR) &info);

HDC hdcBitmap = CreateCompatibleDC(target);
SelectObject(hdcBitmap, bitmap);

StretchBlt(target, x, y, 
SCALEX(info.bmWidth), SCALEY(info.bmHeight), 
hdcBitmap, 0, 0, info.bmWidth, info.bmHeight, SRCCOPY);
DeleteDC(hdcBitmap);

スケーリングはイメージの品質を低下させます。これは特に DPI の小さい値から大きい値に拡大したときに顕著ですが、縮小にも問題はあります。スケーリング アルゴリズムが保存しようとする細部は、必ずしもプログラマが保存したいと思う細部と一致しません。既定の伸縮モード COLORONCOLOR は高速ですが、細部の情報を大量に失います。伸縮モード HALFTONE は低速ですが、品質ははるかに高くなります (GDI+ はこれ以外の伸縮オプションも用意しています。GDI+ のセクションを参照してください)。 伸縮モードの切り替えには SetStretchBltMode() を使用します。


SetStretchBltMode(hdc, HALFTONE);

スケーリングの代わりに使用できる手法として、それぞれ異なる DPI 用にデザインされた複数のイメージを使用するというものがあります。.ico および .cur 形式は 1 つのファイルに複数のイメージを格納することができます。アイコンまたはカーソルをロードするとき、アプリケーションは GetSystemMetrics() が提案したサイズを要求します。その後、システムは最も近いイメージを選択し、必要ならばスケーリングを行います。BMP やその他の大部分のイメージ ファイル形式は、1 つのファイルで複数のサイズをサポートしていませんが、複数のファイルを作成しておいて、ロード時に最適なものを選択するのは簡単です。


if (GetDeviceCaps(hdc, LOGPIXELSX) < 130)
   bitmap = LoadBitmap(hInstance, (char*) IDB_BITMAP1);
else
   bitmap = LoadBitmap(hInstance, (char*) IDB_BITMAP2);

特にアイコンとカーソルでは、追加のイメージを作成しておくことをお勧めします。現在では、アプリケーション アイコンとして 16x16 ピクセルのイメージと 32x32 ピクセルのイメージの両方を用意することが一般的です。高 DPI アプリケーションは、アプリケーション アイコンに少なくとも 64x64 ピクセルのイメージを追加するべきです。その他にも、主な解像度ごとに大きいアイコンと小さいアイコンの両方を用意しておくのが理想的です。

コモン コントロール バージョン 5 (comctl) でイメージ リスト (HIMAGELIST) を使用している場合には、イメージ リストに格納する前にイメージをスケーリングする必要があります。それよりも、Microsoft Windows XP オペレーティング システムに付属するコモン コントロール バージョン 6 に切り替えることをお勧めします。バージョン 6 はハーフトーンの StretchBlt を使って、イメージを自動的にスケーリングします (イメージを高 DPI 用に作成している場合には、この機能を無効にすることができます)。

レイアウト

レイアウトは、高 DPI システムで問題を引き起こす可能性のあるもう 1 つの分野です。大部分のダイアログ ボックスは、システム DPI に合わせてスケーリングされるダイアログ単位を使ってレイアウトされています。しかし、カスタム レイアウト ロジックは一般に再検討する必要があります。ほとんどのカスタム レイアウト ロジックは作業をピクセル単位で行っており、ピクセルの大きさに関してさまざまな仮定を立てています。プログラマは、ダイアログ単位などの他の単位を使用するようにレイアウト ロジックを修正することができます (ただし SetWindowPos を呼び出す前にピクセル単位に戻す必要があります)。または、ピクセルを使い続け、位置を他のコントロールの位置やフォントのサイズを基準にして表現するか、システム メトリックスを使用することで、DPI に関する仮定を取り除くという方法もあります。

ペイント

ペイントもこれに似ています。スクリーンにペイントするときには、つねに DPI の違いを念頭に置く必要があります。カスタム コントロールを作成する際には、ピクセル単位を使って作業を行うのがおそらく最も簡単でしょうが、DPI に関する仮定を避けるためにシステム メトリックスを使用する必要があります。複雑なグラフィックスを作成しているときには、SetMapMode を呼び出して、グラフィックス エンジンにスケーリングを行わせるといいでしょう。

(サンプルの SCALEX マクロのように) 手動でスケーリングを行う場合には、整数を使用するときの丸めの問題に注意してください。たとえば、丸めの問題のために、SCALEX (a + b) は SCALEX (a) + SCALEX (b) と等しくならないかもしれません。スケーリングの適用方法に一貫性を持たせるか、中間結果に浮動小数点を使用するようにしてください。

GDI+

GDI+ は、GDI に続く Microsoft の次世代の 2 次元グラフィックス ライブラリです。GDI+ は高 DPI アプリケーションにいくつもの利点をもたらし、テキストは線形に、かつスムーズにスケーリングします (GDI+ は文字と単語の間隔を微妙に操作します)。

イメージのスケーリングも大幅に改善されています。GDI+ は、速度と品質のトレードオフが異なるイメージ スケーリング アルゴリズムをいくつか備えていますが、そのどれもが GDI の StretchBlt よりも高品質のイメージを生成します。最も高速なのは InterpolationModeBilinear で、これは小さいイメージでは全体的に最適なアルゴリズムです。品質が重要となる大きなイメージには InterpolationModeHighQualityBicubic が適しています。

GDI+ のもう 1 つの特徴は、どれだけの DPI を前提としてデザインされたかという情報がイメージに付加されていることです (Image::GetPhysicalDimension や Bitmap::SetResolution など)。この情報を使って、イメージを手動でスケーリングすることもできますし、GDI+ にスケーリングを行わせることもできます。Graphics::DrawImage の呼び出しの際に高さと幅を指定しなければ、GDI+ はスクリーンの DPI とイメージの DPI に基づいて計算を行います。

Visual Basic と共通言語ランタイム

Microsoft Visual Basic(R) およびそれ以前では、フォームはダイアログ ボックス単位で指定されるので、コントロールとシェイプは一般に正しくスケーリングされます。テキストは線形にスケーリングしないので、依然としてアプリケーションをテストし、必要ならばテキストに合わせてコントロールを拡大しなくてはならないことがあります。

(Visual Basic .NET で使用される) Microsoft .NET Windows フォームでは、すべてがピクセル単位で指定されます。ただし、フォームを最初に作成するときには、コントロールはシステムの DPI に基づいてスケーリングされ、初期フォーム レイアウトも同じようにスケーリングされます (詳細については、Form.AutoScaleBaseSize を参照)。もちろん、プログラマはペインとレイアウトのカスタム ロジックにスケーラビリティを持たせることが可能です。Windows フォームと System.Drawing は、どちらもテキストとイメージのスケーリングを改善した GDI+ を使用します。

高 DPI アプリケーションのテスト方法

高 DPI アプリケーションのテストは、システムを高 DPI モードにし、不適切なサイズになっているものがないかどうかを調べることによって行います。ほとんどの問題は、高 DPI モニタがなくても発見することができます。

システムの DPI 設定を変更するには、次の操作を行います。

  1. Windows デスクトップを右クリックします。
  2. [プロパティ] をクリックします。
  3. [設定] タブを開き、[詳細] をクリックします。
  4. [全般] タブの [フォント サイズ] ボックスで、システム DPI を変更します。
  5. システムを再ブートして、新しい設定を有効にします。

特に以下の点に注意して、アプリケーションのすべてのビジュアル要素をチェックします。

  • テキストが与えられたスペースに収まり切らない。
  • テキストとコントロールが重なっている、または正しい間隔になっていない。
  • テキストとイメージが小さすぎる。
  • イメージは正しいサイズになっているが、スケーリングのせいで品質が落ちている。
  • 線が細すぎて見えにくい (200 DPI では、1 ピクセル幅の線はほとんど見えません)。
  • スケーリングの際の丸めの問題のために、端が正しく揃っていない。

アプリケーションは複数の DPI でテストするようにしてください。具体的な DPI の値はメーカーによって異なりますが、テストすべき値としては、96 (標準の CRT)、120 (標準の CRT で大きなフォントを設定した場合)、135、170、そして近い将来には 200 が適切でしょう。

表示: