高 DPI 対応アプリケーションの開発

  

Jan, 2004

Microsoft Corporation

適用対象

Microsoft® Windows Mobile™ 搭載の Pocket PC

Microsoft Windows Mobile 搭載の Smartphone

要約 : Microsoft Windows Mobile 搭載の Pocket PC およびスマートフォンは、将来より高 DPI 解像度で動作することになります。ここでは、アプリケーションがデバイスの DPI を判断し、高 DPI ディスプレイを利用するために必要となる変更点について説明します。

トピック

はじめに はじめに
主な問題点 : (1) レイアウト 主な問題点 : (1) レイアウト
主な問題点 : (2) テキストおよびフォント 主な問題点 : (2) テキストおよびフォント
主な問題点:(3)イメージ 主な問題点 : (3) イメージ
付録 A : UIHelper.H 付録 A : UIHelper.H
付録 B : UIHelper.CPP 付録 B : UIHelper.CPP

はじめに

ディスプレイ技術の向上によって、同じ領域により多くのピクセルを表示できるようになりつつあります。この高密度の解像度を使用して、画面により多くのアイテムを表示したり、同じアイテムでもより鮮明に表示することができます。後者の場合、ディスプレイの DPI (dots per inch) が増えたと言われます。DPI とは、ディスプレイ領域に表示できるピクセルの密度を表す論理値です。

印刷メディアでは、前々から高 DPI 技術を利用してきました。1200 dpi のプリンタで印刷された文書には、300 dpi のプリンタと同じ量のテキストが印刷されていますが、このテキストはより鮮明になっています。

Microsoft® Windows Mobile™ 搭載の Pocket PC およびスマートフォンでは、従来より 96 dpi のディスプレイが使用されていましたが、将来は高 DPI 解像度で動作するようになります。高 DPI に対応するようにアプリケーションをアップグレードすると、高 DPI デバイスで正しく表示できるようになるだけではなく、高 DPI によって、次のような利点がアプリケーションにもたらされます。

•より鮮明なテキスト : 最も顕著な改善点であり、ほとんど費用をかけずに実現します。高 DPI に対応し TrueType フォントを使用したアプリケーションならどれでも、この改善点を享受できます。

•より精緻なグラフィックス : 高解像度のビットマップを提供する手段が講じられれば、アプリケーションは増加した解像度を使用して、より精緻なアイコンやグラフィックスを表示できます。

この資料では、アプリケーションがデバイスの DPI を判断し、高 DPI ディスプレイを利用するために必要となる変更点について説明します。

主な問題点

Pocket PC およびSmartphone用の高 DPI 対応アプリケーションでは、次の 3 つの一般的な分野に注意を払う必要があります。

(1) レイアウト

(2) テキストおよびフォント
(3) イメージ

付録 A : UIHelper.H

付録 B : UIHelper.CPP

主な問題点 : (1) レイアウト

ユーザー インターフェイス要素は、96 dpi を想定したピクセル座標でその位置とサイズが指定されていると、高 DPI デバイスでは正しく表示されません。一般に、すべてのユーザー インターフェイス要素は、スケーリングした位置とサイズを使用するか、コントロール、フォント、システム メトリックスを基準としてレイアウトする必要があります。

Microsoft Windows® CE の GetDeviceCaps 関数は、LOGPIXELSX または LOGPIXELSY を第 2 パラメータとして渡すことにより、ディスプレイの DPI を取得するために使用できます。ピクセルを使用し続けることも可能ですが、次のテクニックを使用すれば DPI について想定する必要がなくなります。

• SCALEX および SCALEY マクロ (この記事の最後で説明) を使用して、96 dpi ピクセル座標をスケーリングするか、適用可能な場合は GetSystemMetrics で返されるメトリックスを使用する。

<li他のコントロールを基準にしてサイズまたは位置を表現する。

• フォントを基準にしてサイズまたは位置を表現する。

ダイアログ ボックスでは既に、フォント サイズを使用してそのレイアウトを決定しています。したがって、高 DPI デバイスで使用するために、特別な修正を行う必要はありません。

次に、ウィンドウの位置を、高 DPI に対応させて設定する例を示します。ただし、x、y、dx、および dy は 96 DPI のピクセル座標です。

SetWindowPos(hwnd, NULL, SCALEX(x), SCALEY(y), SCALEX(dx), SCALEY(dy), SWP_NOZORDER);

96 dpi のピクセル メトリックスをスケーリングする方法では、整数の使用時の丸めの問題に注意してください。たとえば、丸めの問題のために、SCALEX (a + b) は (SCALEX (a) + SCALEX (b)) と同じにならない場合があります。

システム メトリックス

DPI を意識した開発を行う場合、アイコン サイズや境界線の幅など、さまざまな画面要素のピクセル サイズを想定できません。Windows Mobile 搭載のスマートフォンおよび Pocket PC では、多数のシステム メトリックスを用意して、ユーザーのシステムに関する情報を提供しています。たとえば、Windows CE の GetSystemMetrics 関数を使用すると、次の情報を照会できます。

• 画面サイズの寸法は、GetSystemMetrics (SM_CXSCREEN) または GetSystemMetrics (SM_CXSCREEN) を使用して取得する。

• 境界線の寸法は、GetSystemMetrics (SM_CXBORDER) または GetSystemMetrics (SM_CYBORDER) を使用して取得する。

• 大きなアイコンと小さなアイコンのサイズは、GetSystemMetrics (SM_CXICON) または GetSystemMetrics (SM_CXSMICON) を使用して取得する。

高 DPI 対応の太ペンを使用した描画

DPI 値が高くなると、画像サイズの縦横比を保つために線を太くする必要があります。アプリケーションを高 DPI 対応にする場合、描画で使用するすべてのペンの幅をスケーリングしなければなりません。

Windows CE GDI (Graphics Device Interface) での、(1 ピクセル以上の) 太線の描画に関連する処理は、厳密で正確な位置決めが難しくなります。LineTo および Polyline の Windows CE 関数を呼び出すと、GDI は指定された起点をペン幅の中心と見なします。線の幅が奇数の場合、線の幅は起点の両側に均一に分けられますが、偶数の場合は上側または左側に片寄ります。

アプリケーションが、ペン幅を想定せずに線の位置を決めていた場合は、おそらく何も変更せずに、高 DPI に適した太さのペンに対応できるでしょう。しかし、1 ピクセルのように、一定のペン幅を想定していた場合は、GDI の処理によってレイアウトに及ぼされる影響に注意しなければなりません

次の図では、GDI の処理によって、どのように予想外のずれがレイアウトに生じるかを示しています。スケーリング後のペンを hDC として、次のコードを 96 dpi と 192 dpi のデバイス上で実行し、その結果を並べて表示します。

rgptEndpoints[0].x = SCALEX(1);
 
rgptEndpoints[0].y = SCALEY(1);
 
rgptEndpoints[1].x = SCALEX(6);
 
rgptEndpoints[1].y = SCALEY(1);
 
Polyline(hDC, rgptEndpoints, 2);
 

96 dpi での 1 ピクセル幅のペン ((1,1) から (6,1))

192 dpi での 2 ピクセル幅のペン ((2.2) から (12.2))

 

96 dpi での 1 ピクセル幅のペン

 

192 dpi での 2 ピクセル幅のペン

1 : GDI の処理によるレイアウトでの予想外のずれ

SCALEX および SCALEYマクロによって、起点は正しく配置され、スケーリング後のペンは正しい太さの線になりましたが、192 dpi での線は、96 dpi に比べて高い位置に描かれています。これは、GDI が、起点を中心に位置決めし、偶数幅の線のために上側に片寄らせたためです。

上の例では、次のようにすると、192 dpi のレイアウトと 96 dpi のレイアウトが一致するように、高 DPI 対応の水平線を描くことができます。

rgptEndpoints[0].x = SCALEX(1);

rgptEndpoints[0].y = SCALEY(1);

rgptEndpoints[1].x = SCALEX(6);

rgptEndpoints[1].y = SCALEY(1);

HIDPI_Polyline(hDC, rgptEndpoints, 2, PS_DOWNRIGHT);

HTML 要素

HTML レイアウトには、すべてのピクセル座標をスケーリングしなければならないというルールは該当しません。Windows Mobile 搭載の Pocket PC およびスマートフォンでは、Internet Explorer によって、HTML のピクセル座標は 96 dpi ピクセルと解釈されます。HTML では、相対座標を使用すること (表の列を表の幅の 50 % に定めるなど) が最も望ましいのですが、現在ピクセル メトリックスを使用している場合は、高 DPI でも同じレイアウトのままになります。

主な問題点 : (2) テキストおよびフォント

次のフォントは、Windows Mobile 搭載の Pocket PC およびスマートフォンで使用されています。

• ビットマップ フォント

• TrueType フォント

高 DPI 対応のアプリケーションでは、TrueType フォントだけを使用する必要があります。既存のビットマップ フォントは、96 DPI ディスプレイを想定しているため、きれいにスケーリングされません。TrueType フォントは、どの DPI でも美しく表示され、高 DPI での表示は特に優れています。

TrueType フォントは、きれいにスケーリングしますが、線形的にスケーリングするわけではありません。つまり、DPI を 10 % 増加しても、文字列の長さが必ずしもちょうど 10 % 増加するわけではありません。これは、どの特定の文字でもあるサイズでのみ美しく表示され、TrueType はきれいに見えるサイズに最も近いサイズを選んでいるからです。したがって、文字列にどの程度のスペースが必要なのかを想定するのではなく、Windows CE の CDC::GetTextExtent メソッドを使用することが重要になります。

高 DPI 対応フォントの作成

スマートフォンおよび Pocket PC のフォントを作成する場合、ポイントを使用してフォント サイズを指定します。ポイントとは、論理サイズ (論理インチの 1 / 72) のことでありピクセル高ではありません。したがって正しく使用すれば、そのまま高 DPI に対応します。次に、ポイント サイズを使用して、高 DPI 対応フォントを作成するサンプル コードを示します。

LOGFONT lf;
 
memset(&lf, 0, sizeof(lf));
 
lf.lfHeight = -MulDiv ( iPointSize, GetDeviceCaps(hdc, LOGPIXELSY), 72);
 
HFONT font = CreateFontIndirect(&lf);

主な問題点 : (3) イメージ

この資料では、イメージとは、すべてのラスタベースのイメージ ファイル (BMP / JPEG / GIF)、アイコン、およびカーソルを指します。その寸法はピクセル単位で固定されているため、高 DPI 対応アプリケーションには特有の問題が生じます。イメージのスケーリングによって、イメージを正しい論理サイズへ拡大できますが、高 DPI 画面を適切に利用し、ユーザーの使いやすさを追求するには、注目度の高いグラフィックスを手動で作り直すべきです。

イメージの拡大

ディスプレイの DPI が、イメージのデザイン時に対象とした DPI と異なっていると、正しい物理的なサイズで表示されるように、イメージを拡大する必要があります。ただし、イメージを描画ごとに拡大するのか、一度だけ拡大した上で新しいビットマップを作成するのかを決める必要があります。

ビットマップ描画コードを使用できる場合は、描画時に拡大するようにコードを変更すると、ロード時にビットマップを拡大するよりも、メモリの使用量が少なくなります。ただし、ビットマップのスケーリングに対応していない関数またはオブジェクト (イメージ リスト関数など) を使用する場合は、ロード時に拡大する必要があります。

描画時に拡大する場合は、BitBlt 関数ではなく、Windows CE の StretchBlt 関数を呼び出して、ビットマップをスケーリングします。次の例では、ビットマップが、96 DPI の寸法で作成されたと想定しています (したがって、元の幅と高さから、SCALEX (幅) と SCALEY (高さ) に拡大します)。

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

ロード時に拡大するには、この資料の最後に記載している HIDPI_StretchBitmap または HIDPI_ImageList_LoadImage の関数を使用できます。

イメージ リスト ロード関数を使用したビットマップのスケーリング

ImageList_LoadImage 関数ではスケーリングを想定していません。したがって cx という 1 つのパラメータしかありません。これは、ディスクの他、画面上でのビットマップの各イメージの幅を表します。HIDPI_ImageList_LoadImage も同様に 1 つの cx パラメータしかありません。これはディスク上での各イメージの幅を表します。それでは、HIDPI_ImageList_LoadImage は、各イメージが画面上でどれだけの幅で表示されるのかを、どのようにして判断するのでしょうか。それは、ビットマップの作成時に対象とされた DPI 解像度を識別する特別なフィールド、BITMAPINFO ヘッダーの biXPelsPerMeter および biYPelsPerMeter フィールドが、ビットマップにあるからです。HIDPI_ImageList_LoadImage は、この情報が画面の DPI 解像度と異なる場合に限り、これらのフィールドを調べてビットマップをスケーリングします。

ほとんどのグラフィクス編集ツールは、既定でこれらのフィールドを 96 DPI に設定しています。高解像度のイメージ リストでビットマップを正しく設定していることが、非常に重要になります。そうでなければ、ビットマップは、高 DPI ディスプレイで不適切にスケーリングされてしまいます。

複数のイメージ

別のスケーリング方法は、複数のイメージを、それぞれ別々の DPI に合わせて作成しておくというものです。このテクニックを使用すると、最も優れた結果が手に入ります。

既に .ico フォーマットは、複数のイメージを単一のファイルに保存できるようになっています。アイコンまたはカーソルをロードすると、アプリケーションは、GetSystemMetrics 関数で得られたサイズを要求し、続いてシステムが最も近いイメージを選択します。

次のサンプル コードでは、.ico フォーマットになっていない複数のイメージから選択する方法を示しています。アプリケーションを完全に高 DPI 対応にするには、ロードされたビットマップを、デバイスの実際の DPI に基づいて、さらにスケーリングする必要があります。なぜなら、どの DPI 解像度が利用できるのかを、想定していないからです。

ロードしたビットマップが、正しい DPI に合わせて既に作成されている場合、それをスケーリングすると、パフォーマンスまたはイメージの品質に逆効果を及ぼします。

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

静的コントロール

Windows CE では、静的コントロール内のビットマップは、そのコントロール以上に大きくならない限り、高 DPI ディスプレイ上で自動的にスケーリングします。この場合、Windows CE は、そのビットマップが高 DPI に合わせて作成されたものと見なします。したがって、静的コントロールが正しく機能するために、特別な変更を加える必要はないでしょう。

この自動スケーリングを無効にするには、静的コントロール内で、SS_REALSIZEIMAGE スタイルを使用します。このスタイルにより、イメージはスケーリングされなくなります。反対に、イメージをスケーリングしたいが、Windows CE が行わない場合は、静的コントロールへイメージを配置する前に STM_SETIMAGE を使用して、手動でイメージをスケーリングすることもできます。

付録 A : UIHelper.H

/*++
Copyright (c) 2003 Microsoft Corporation
 
Module Name:
 
uihelper.h
 
Abstract:
 
Include file for HIDPI / orientation / font change helper functions.
 
--*/
 
#ifndef __UIHELPER_H__
#define __UIHELPER_H__
 
#include 
#include 
 
////////////////////////////////////////////////////////////////////////////////
// HIDPI functions and constants.
////////////////////////////////////////////////////////////////////////////////
 
#ifndef ILC_COLORMASK
#define ILC_COLORMASK 0x00FE
#endif
 
//
// The two macros HIDPISIGN and HIDPIABS are there to ensure correct rounding
// for negative numbers passed into HIDPIMulDiv as x (we want -1.5 to round 
// to -1, 2.5 to round to 2, etc).So we use the absolute value of x, and then 
// multiply the result by the sign of x.Y and z should never be negative, as 
// y is the dpi of the device (presumably 192 or 96), and z is always 96, as 
// that is our original dpi we developed on.
//
 
#define HIDPISIGN(x) (((x)<0)?-1:1)
#define HIDPIABS(x) (((x)<0)?-(x):x)
#define HIDPIMulDiv(x,y,z) ((((HIDPIABS(x)*(y))+((z)>>1))/(z))*HIDPISIGN(x))
 
//
// Cached values of GetDeviceCaps(LOGPIXELSX/Y) for the screen DC.
//
EXTERN_C int g_HIDPI_LogPixelsX;
EXTERN_C int g_HIDPI_LogPixelsY;
 
//
// You need to define these somewhere in your .c files only if you make use of 
// the scaling macros.(Defined in UIHelper.cpp).
//
#define HIDPI_ENABLE \
int g_HIDPI_LogPixelsX; \
int g_HIDPI_LogPixelsY; 
 
//
// Scaling macros.
//
#define SCALEX(argX) (HIDPIMulDiv(argX,g_HIDPI_LogPixelsX,96))
#define SCALEY(argY) (HIDPIMulDiv(argY,g_HIDPI_LogPixelsY,96))
 
#define UNSCALEX(argX) (HIDPIMulDiv(argX,96,g_HIDPI_LogPixelsX))
#define UNSCALEY(argY) (HIDPIMulDiv(argY,96,g_HIDPI_LogPixelsY))
 
#define SCALERECT(rc) { rc.left = SCALEX(rc.left); rc.right = 
SCALEX(rc.right); rc.top = SCALEY(rc.top); rc.bottom = 
SCALEY(rc.bottom);}
#define SCALEPT(pt) { pt.x = SCALEX(pt.x); pt.y = SCALEY(pt.y);}
 
//////////////////////////////////////////////////////////////////////////////
// FUNCTION: HIDPI_InitScaling
//
// PURPOSE: Initializes g_HIDPI_LogPixelsX and g_HIDPI_LogPixelsY.This 
// should be called once at the beginning of any HIDPI-aware application.
//
__inline void HIDPI_InitScaling()
{
HDC screen;
 
if( g_HIDPI_LogPixelsX )
return;
 
screen = GetDC(NULL);
g_HIDPI_LogPixelsX = GetDeviceCaps(screen, LOGPIXELSX);
g_HIDPI_LogPixelsY = GetDeviceCaps(screen, LOGPIXELSY);
ReleaseDC(NULL, screen);
}
 
//////////////////////////////////////////////////////////////////////////////
// FUNCTION: HIDPI_StretchBitmap
//
// PURPOSE: Stretches a bitmap containing a grid of images.There are 
// cImagesX images per row and cImagesY rows per bitmap.Each image is 
// scaled individually, so that there are no artifacts with non-integral 
// scaling factors.If the bitmap contains only one image, set cImagesX
// and cImagesY to 1.
//
// ON ENTRY:
// HBITMAP* phbm: a pointer to the bitmap to be scaled.
// INT cxDstImg: the width of each image after scaling.
// INT cyDstImg: the height of each image after scaling.
// INT cImagesX: the number of images per row. This value should
// evenly divide the width of the bitmap.
// INT cImagesY: the number of rows in the bitmap. This value should 
// evenly divide the height of the bitmap.
//
// ON EXIT:
// Returns TRUE on success, FALSE on failure. 
//
// If any scaling has occured, the bitmap pointed to by phbm is deleted
// and is replaced by a new bitmap handle.
//
BOOL HIDPI_StretchBitmap(
HBITMAP* phbm,
int cxDstImg,
int cyDstImg,
int cImagesX,
int cImagesY
);
 
//////////////////////////////////////////////////////////////////////////////
// FUNCTION: HIDPI_ImageList_LoadImage
//
// PURPOSE: This function operates identically to ImageList_LoadImage, except
// that it first checks the DPI fields of the bitmap (using 
// HIDPI_GetBitmapLogPixels); compares it to the DPI of the screen
// (using g_HIDPI_LogPixelsX and g_HIDPI_LogPixelsY), and performs scaling
// (using HIDPI_StretchBitmap) if the values are different.
//
// ON ENTRY:
// See the MSDN documentation for ImageList_LoadImage.
//
// ON EXIT:
// See the MSDN documentation for ImageList_LoadImage.
//
HIMAGELIST HIDPI_ImageList_LoadImage(
HINSTANCE hinst,
LPCTSTR lpbmp,
int cx,
int cGrow,
COLORREF crMask,
UINT uType,
UINT uFlags
);
 
//////////////////////////////////////////////////////////////////////////////
// FUNCTION: HIDPI_Rectangle
//
// PURPOSE: Draws a rectangle using the currently selected pen. Drawing occurs
//completely within the drawing rectangle (the rectangle has an "inside 
//frame" drawing style).
//
// ON ENTRY:
// HDC hdc: the display context of the drawing surface.
// INT nLeft: left bound of rectangle
// INT nTop: top bound of rectangle
// INT nRight: right bound of rectangle plus one.
// INT nBottom: bottom bound of rectangle plus one.
//
// ON EXIT:
// Returns TRUE on success, FALSE on failure.
//
BOOL HIDPI_Rectangle(HDC hdc, int nLeft, int nTop, int nRight, int nBottom);
 
//////////////////////////////////////////////////////////////////////////////
// FUNCTION: HIDPI_Polyline
//
// PURPOSE: Draws a polyline using the currently selected pen. In addition,
// this function provides control over how the line will be drawn.
//
// ON ENTRY:
// HDC hdc: the display context of the drawing surface.
// const POINT* lppt: array of POINTS that specify line to draw.
// INT cPoints: number of points in array.
// INT nStyle: the style the pen should be drawn in.This may be an 
//existing pen style, such as PS_SOLID, or one of the following styles:
//
// PS_LEFTBIASPS_UPBIASPS_UPLEFT
// PS_RIGHTBIAS PS_DOWNBIASPS_DOWNRIGHT
//
//These styles indicate how the pen should "hang" from each line
//segment.By default, the pen is centered along the line, but with
//these line styles the developer can draw lines above, below, to the
//left or to the right of the line segment.
//
// ON EXIT:
// Returns TRUE on success, FALSE on failure.
//
 
#define PS_RIGHTBIAS0x10
#define PS_LEFTBIAS 0x20
#define PS_DOWNBIAS 0x40
#define PS_UPBIAS 0x80
#define PS_DOWNRIGHT(PS_DOWNBIAS | PS_RIGHTBIAS)
#define PS_UPLEFT (PS_UPBIAS | PS_LEFTBIAS)
#define PS_BIAS_MASK(PS_RIGHTBIAS | PS_LEFTBIAS | PS_DOWNBIAS | PS_UPBIAS)
 
BOOL HIDPI_Polyline(HDC hdc, const POINT *lppt, int cPoints, int nStyle);
 
#endif // __UIHELPER_H__

付録 B : UIHelper.CPP

/*++
 
Copyright (c) 2003 Microsoft Corporation
 
Module Name:
 
uihelper.cpp
 
Abstract:
 
Helper functions for HIDPI/Landscape support.
 
--*/
 
#include "uihelper.h"
HIDPI_ENABLE;
 
BOOL HIDPI_StretchBitmap(
HBITMAP* phbm,
int cxDstImg,
int cyDstImg,
int cImagesX,
int cImagesY
)
{
BOOL fRet = FALSE;
HBITMAP hbmNew;
BITMAP  bm;
HDC hdcSrc, hdcDst, hdcScreen;
HBITMAP hbmOldSrc, hbmOldDst;
int  cxSrcImg, cySrcImg;
int  i, j, xDest, yDest, xBmp, yBmp;
 
if (!phbm || !*phbm || (cxDstImg == 0 && cyDstImg == 0) || (cImagesX == 0 || cImagesY == 0))
goto donestretch;
 
if ((sizeof(bm) != GetObject(*phbm, sizeof(bm), &bm)))
goto donestretch;
 
// If you hit this ASSERT, that mean your passed in image count in row and
//   the column number of images is not correct.
ASSERT(((bm.bmWidth % cImagesX) == 0) && ((bm.bmHeight % cImagesY) == 0));
 
cxSrcImg = bm.bmWidth / cImagesX;
cySrcImg = bm.bmHeight / cImagesY;
 
if (cxSrcImg == cxDstImg && cySrcImg == cyDstImg)
{
fRet = TRUE;
goto donestretch;
}
 
if (cxDstImg == 0)
cxDstImg = HIDPIMulDiv(cyDstImg, cxSrcImg, cySrcImg);
else if (cyDstImg == 0)
cyDstImg = HIDPIMulDiv(cxDstImg, cySrcImg, cxSrcImg);
 
hdcSrc = CreateCompatibleDC(NULL);
hdcDst = CreateCompatibleDC(NULL);
hdcScreen = GetDC(NULL);
hbmOldSrc = (HBITMAP)SelectObject(hdcSrc, *phbm);
hbmNew = CreateCompatibleBitmap(hdcScreen, cxDstImg * cImagesX, cyDstImg * cImagesY);
hbmOldDst = (HBITMAP)SelectObject(hdcDst, hbmNew);
ReleaseDC(NULL, hdcScreen);
 
// BLAST!
for (j = 0, yDest = 0, yBmp = 0; j < cImagesY; j++, yDest += cyDstImg, yBmp += cySrcImg)
{
for (i = 0, xDest = 0, xBmp = 0; i < cImagesX; i++, xDest += cxDstImg, xBmp += cxSrcImg)
{
StretchBlt(hdcDst, xDest, yDest, cxDstImg, cyDstImg,
   hdcSrc, xBmp, yBmp, cxSrcImg, cySrcImg,
   SRCCOPY);
}
}
 
// Free allocated memory
SelectObject(hdcSrc, hbmOldSrc);
SelectObject(hdcDst, hbmOldDst);
DeleteDC(hdcSrc);
DeleteDC(hdcDst);
 
// Delete the passed in bitmap
DeleteObject(*phbm);
*phbm = hbmNew;
 
fRet = TRUE;
 
donestretch:
return fRet;
}
 
BOOL HIDPI_GetBitmapLogPixels(
HINSTANCE hinst,
LPCTSTR lpbmp,
int* pnLogPixelsX,
int* pnLogPixelsY
)
{
BOOL fRet = FALSE;
HRSRC hResource;
HGLOBAL hResourceBitmap = NULL;
BITMAPINFO* pBitmapInfo;
int PelsPerMeterX, PelsPerMeterY;
 
*pnLogPixelsX = 0;
*pnLogPixelsY = 0;
 
hResource = FindResource(hinst, lpbmp, RT_BITMAP);
if (!hResource)
{
goto error;
}
hResourceBitmap = LoadResource(hinst, hResource);
if (!hResourceBitmap)
{
goto error;
}
pBitmapInfo = (BITMAPINFO*)LockResource(hResourceBitmap);
if (!pBitmapInfo)
{
goto error;
}
 
// There are at least three kind value of PslsPerMeter used for 96 DPI bitmap:
//   0- the bitmap just simply doesn't set this value
//   2834 - 72 DPI
//   3780 - 96 DPI
// So any value of PslsPerMeter under 3780 should be treated as 96 DPI bitmap.
PelsPerMeterX = (pBitmapInfo->bmiHeader.biXPelsPerMeter < 3780) ? 
3780 : pBitmapInfo->bmiHeader.biXPelsPerMeter;
PelsPerMeterY = (pBitmapInfo->bmiHeader.biYPelsPerMeter < 3780) ? 
3780 : pBitmapInfo->bmiHeader.biYPelsPerMeter;
 
// The formula for converting PelsPerMeter to LogPixels(DPI) is:
//   LogPixels = PelsPerMeter / 39.37
//   ( PelsPerMeter : Pixels per meter )
//   ( LogPixels: Pixels per inch  )
// Note: We need to round up.
*pnLogPixelsX = (int)((PelsPerMeterX * 100 + 1968) / 3937);
*pnLogPixelsY = (int)((PelsPerMeterY * 100 + 1968) / 3937);
 
fRet = TRUE;
 
error:
return fRet;
}
 
HIMAGELIST HIDPI_ImageList_LoadImage(
HINSTANCE hinst,
LPCTSTR lpbmp,
int cx,
int cGrow,
COLORREF crMask,
UINT uType,
UINT uFlags
)
{
HBITMAP hbmImage = NULL;
HIMAGELIST piml = NULL;
BITMAP bm;
int cImages, cxImage, cy;
int BmpLogPixelsX, BmpLogPixelsY;
UINT flags;
 
if ((uType != IMAGE_BITMAP) ||  // Image type is not IMAGE_BITMAP
(cx == 0))  // Caller doesn't care about the 
dimensions of the image - assumes the ones in the file
{
piml = ImageList_LoadImage(hinst, lpbmp, cx, cGrow, crMask, uType, uFlags);
goto cleanup;
}
 
if (!HIDPI_GetBitmapLogPixels(hinst, lpbmp, &BmpLogPixelsX, &BmpLogPixelsY))
{
goto cleanup;
}
 
hbmImage = (HBITMAP)LoadImage(hinst, lpbmp, uType, 0, 0, uFlags);
if (!hbmImage || (sizeof(bm) != GetObject(hbmImage, sizeof(bm), &bm)))
{
goto cleanup;
}
 
// do we need to scale this image?
if (BmpLogPixelsX == g_HIDPI_LogPixelsX)
{
piml = ImageList_LoadImage(hinst, lpbmp, cx, cGrow, crMask, uType, uFlags);
goto cleanup;
}
 
cxImage = HIDPIMulDiv(cx, BmpLogPixelsX, g_HIDPI_LogPixelsX);
 
// Bitmap width should be multiple integral of image width.
// If not, that means either your bitmap is wrong or passed in cx is wrong.
ASSERT((bm.bmWidth % cxImage) == 0);
 
cImages = bm.bmWidth / cxImage;
 
cy = HIDPIMulDiv(bm.bmHeight, g_HIDPI_LogPixelsY, BmpLogPixelsY);
 
if ((g_HIDPI_LogPixelsX % BmpLogPixelsX) == 0)
{
HIDPI_StretchBitmap(&hbmImage, cx * cImages, cy, 1, 1);
}
else
{
// Here means the DPI is not integral multiple of standard DPI (96DPI).
// So if we stretch entire bitmap together, we are not sure each indivisual
//   image will be stretch to right place. It is controled by StretchBlt().
//   (for example, a 16 pixel icon, the first one might be stretch to 22 pixels
//and next one might be stretched to 20 pixels)
// What we have to do here is stretching indivisual image separately to make sure
//   every one is stretched properly.
HIDPI_StretchBitmap(&hbmImage, cx, cy, cImages, 1);
}
 
flags = 0;
// ILC_MASK is important for supporting CLR_DEFAULT
if (crMask != CLR_NONE)
{
flags |= ILC_MASK;
}
// ILC_COLORMASK bits are important if we ever want to Merge ImageLists
if (bm.bmBits)
{
flags |= (bm.bmBitsPixel & ILC_COLORMASK);
}
 
// bitmap MUST be de-selected from the DC
// create the image list of the size asked for.
piml = ImageList_Create(cx, cy, flags, cImages, cGrow);
 
if (piml)
{
int added;
 
if (crMask == CLR_NONE)
{
added = ImageList_Add(piml, hbmImage, NULL);
}
else
{
added = ImageList_AddMasked(piml, hbmImage, crMask);
}
 
if (added < 0)
{
ImageList_Destroy(piml);
piml = NULL;
}
}
 
cleanup:
DeleteObject(hbmImage);
return piml;
}
 
BOOL HIDPI_RectangleInternal(HDC hdc, int nLeft, int nTop, int nRight, 
int nBottom, int nThickness)
{
int nOff = nThickness/2;
 
nLeft   += nOff;
nTop+= nOff;
nRight  -= nOff;
nBottom -= nOff;
 
return Rectangle(hdc, nLeft, nTop, nRight, nBottom);
}
 
BOOL HIDPI_Rectangle(HDC hdc, int nLeft, int nTop, int nRight, int nBottom)
{
LOGPEN lpenSel;
HPEN hpenSel;
 
// Obtain current pen thickness
hpenSel = (HPEN)GetCurrentObject(hdc, OBJ_PEN);
GetObject(hpenSel, sizeof(lpenSel), &lpenSel);
 
return HIDPI_RectangleInternal(hdc, nLeft, nTop, nRight, nBottom, lpenSel.lopnWidth.x);
}
BOOL HIDPI_PolylineInternal(HDC hdc, const POINT *lppt, int cPoints, 
int nStyle, int nThickness)
{
int i;
int nHOff = 0, nVOff = 0;
BOOL bRet = TRUE;
POINT pts[2];
 
if (! (nStyle & PS_BIAS_MASK))
{
// No drawing bias. Draw normally
return Polyline(hdc, lppt, cPoints);
}
 
// Make sure caller didn't try to get both a left and a right bias 
or both a down and an up bias
ASSERT(!(nStyle & PS_LEFTBIAS) || !(nStyle & PS_RIGHTBIAS));
ASSERT(!(nStyle & PS_UPBIAS) || !(nStyle & PS_DOWNBIAS));
 
if (nStyle & PS_LEFTBIAS)
{
nHOff = -((nThickness-1)/2);
}
 
if (nStyle & PS_RIGHTBIAS)
{
nHOff = nThickness/2;
}
 
if (nStyle & PS_UPBIAS)
{
nVOff = -((nThickness-1)/2);
}
 
if (nStyle & PS_DOWNBIAS)
{
nVOff = nThickness/2;
}
 
for (i = 1; i < cPoints; i++)
{
// Use the two points that specify current line segment
memcpy(pts, &lppt[i-1], 2*sizeof(POINT));
if (abs(lppt[i].x - lppt[i-1].x) <= abs(lppt[i].y - lppt[i-1].y))
{
// Shift current line segment horizontally if abs(slope) >= 1
pts[0].x += nHOff;
pts[1].x += nHOff;
}
else
{
// Shift current line segment vertically if abs(slope) < 1
pts[0].y += nVOff;
pts[1].y += nVOff;
}
bRet = bRet && Polyline(hdc, pts, 2);
if (!bRet)
{
goto Error;
}
}
 
Error:
return bRet;
}
 
BOOL HIDPI_Polyline(HDC hdc, const POINT *lppt, int cPoints, int nStyle)
{
LOGPEN lpenSel;
HPEN hpenSel;
 
// Obtain current pen thickness
hpenSel = (HPEN)GetCurrentObject(hdc, OBJ_PEN);
GetObject(hpenSel, sizeof(lpenSel), &lpenSel);
 
return HIDPI_PolylineInternal(hdc, lppt, cPoints, nStyle, lpenSel.lopnWidth.x);
}