高DPI環境におけるWindowsAPI/MFCのウィンドウ矩形・マウス座標、DPIの取れかたの違いなど

# はじめに

　マグネットウィンドウというウィンドウがくっつくアプリを自作してて、自分自身長年愛用しています。
　ただここ数年はほぼほぼノーメンテで。機能追加やこのウィンドウが動かないあのウィンドウが動かないーというお話を頂きますが全然お返事すら出来ていませんすみません。機能追加というよりむしろ削ってC#+C++で書き直すべく水面下でやってますがほとんど進んでません…（やる気が…）。
　話は脱線しましたが、私の開発環境もおそまきながら高DPI環境になり、マグネットウィンドウが高DPI未対応だったので重い腰を上げることにしました。
　ということで高DPIの調査をしていてある程度まとまったのでメモとして残します。
　なおここで取り扱うのは自作アプリ用調査なので主にウィンドウ座標系です。

## テスト環境・条件
* Windows 10 pro (1709)
* モニター 23inch FHD(1920x1080)
* 調査用のソフトは[Github](https://github.com/marksard/LogicalPhysicalCoordinateDisplaySample)にバイナリとソース置きました。
* 調査用ソフトを起動してウィンドウ左上座標を0, 0にする。
* ディスプレイ設定の拡大縮小で100%と150%にしたときの調査用ソフトのawareタイプとunawareタイプでそれぞれの矩形情報を読み取り、論理座標（拡大された時の仮想的な座標系）と物理座標（モニターの物理的な解像度をもとにした座標系）の値がどのように変化するかを観察する。

## ソフトの表示の説明

|名前|概要|
|:--|:--|
|Desktop|このアプリがあるデスクトップ矩形とサイズ|
|MDT_RAW_DPI|GetDpiForMonitorで取得。デバイスから取得した情報から計算された本当のDPI。sqrt(Width * Width + Height * Height) / inchで求まります|
|MDT_EFFECTIVE_DPI|GetDpiForMonitorで取得。WindowsUIで認識しているDPI。100% = 96dpiです。拡大率 = 表示DPI / Windows基準DPI(96) * 100で求まります|
|GetCursorPos|カーソル座標|
|->L2P|カーソル座標を物理座標系に変換（LogicalToPhysicalPointForPerMonitorDPI）|
|GetPhysicalCursorPos|カーソル座標|
|OnMovingArg|MFCのOnMoving(SDKでいうWM_MOVING)の引数の矩形座標。GetWindowRectと同じものが入っている|
|->L2P|上記の矩形座標を物理座標系に変換（LogicalToPhysicalPointForPerMonitorDPI）|
|DwmGetWindowAttribute|DWMWA_EXTENDED_FRAME_BOUNDSを指定して取得したこのウィンドウの矩形座標|
|->P2L|上記の矩形座標を論理座標に変換（PhysicalToLogicalPointForPerMonitorDPI）|


## 表示：100% / DPI設定：モニターごとの高いDPI認識

![100_aware.PNG](https://qiita-image-store.s3.amazonaws.com/0/149049/33f170de-305b-83e6-851a-c9afb1fd8636.png)

<dl>
  <dt>MDT_RAW_DPI</dt>
  <dd>自分で計算しても95DPIになりました</dd>
  <dt>GetCursorPos、->L2P、GetPhysicalCursorPos</dt>
  <dd>100%なので変換しても変化なし</dd>
  <dt>OnMovingArg、->L2P</dt>
  <dd>100%なので変換しても変化なし</dd>
  <dt>DwmGetWindowAttribute、->P2L</dt>
  <dd>Windows10系は見えない部分もウィンドウの矩形となっているようです（サイズ変更時の判定範囲を広げつつ枠自体は1ピクセルで表示するためのよう）</dd>
</dl>


## 表示：100% / DPI設定：なし

![100_unaware.PNG](https://qiita-image-store.s3.amazonaws.com/0/149049/e47c25d6-9ce0-1a8a-813f-9cc65e1cf08f.png)

マウス座標は違いますが「表示：100%、DPI設定：モニターごとの高いDPI認識」と同じ結果になりました。100%なので当然の結果ですね。

## 表示：150% / DPI設定：モニターごとの高いDPI認識

![150_aware.PNG](https://qiita-image-store.s3.amazonaws.com/0/149049/9230268e-9b77-440e-3f61-11ef4b643808.png)

<dl>
  <dt>Desktop</dt>
  <dd>150%ですが、解像度自体はもとのままです</dd>
  <dt>MDT_RAW_DPI</dt>
  <dd>100%と同じ</dd>
  <dt>MDT_EFFECTIVE_DPI</dt>
  <dd>144という数値になりました。150% = 144 / 96 * 100で辻褄が合ってますね</dd>
  <dt>GetCursorPos、->L2P、GetPhysicalCursorPos</dt>
  <dd>高DPI対応のため同じ数値</dd>
  <dt>OnMovingArg、->L2P</dt>
  <dd>高DPI対応のため同じ数値</dd>
  <dt>DwmGetWindowAttribute、->P2L</dt>
  <dd>高DPI対応のため同じ数値</dd>
</dl>

高DPI対応にすると論理座標と物理座標がイコールになるため矩形情報などはすべて100%と同じ情報が返りますね。 

## 表示：150% / DPI設定：なし

![150_unaware.PNG](https://qiita-image-store.s3.amazonaws.com/0/149049/722190c1-9ce7-8628-e3ef-b1067afb18ed.png)

<dl>
  <dt>Desktop</dt>
  <dd>デスクトップ自体が150%拡大したような座標になります。GetCursorPosやGetWindowRectなどもこの矩形領域で動作しています</dd>
  <dt>MDT_RAW_DPI</dt>
  <dd>てっきり物理的に表示可能なDPIを表示するものと思ってたのですが違いました。表示解像度を入れて計算したら63DPIになりました。</dd>
  <dt>MDT_EFFECTIVE_DPI</dt>
  <dd>てっきり150%になると思っていたのですが、Unawareの場合96DPIで表示していると思い込ませるために変化させないみたいですね</dd>
  <dt>GetCursorPos</dt>
  <dd>->L2P、GetPhysicalCursorPos：GetCursorPosが論理座標取得で、GetPhysicalCursorPosが物理座標取得と思っていたのですが同じでした。</dd>
  <dd>->L2Pを行うと物理座標と一致しました</dd>
  <dt>OnMovingArg、->L2P</dt>
  <dd>表示は拡大されてるけど矩形の大きさ自体は100%と同じですね。</dd>
  <dd>->L2Pを行うと物理座標と一致しました</dd>
  <dt>DwmGetWindowAttribute、->P2L</dt>
  <dd>DwmGetWindowAttributeはどうも物理座標系で取得出来るみたいですね</dd>
  <dd>->P2Lを行うと、DWM矩形の物理座標系を論理座標系に変換する。矩形は100%時のものと一致。</dd>
</dl>

# まとめ

|scale|aware per monitor|unaware|
|:--|:--|:--|
| 100% | ![100_aware.PNG](https://qiita-image-store.s3.amazonaws.com/0/149049/33f170de-305b-83e6-851a-c9afb1fd8636.png) | ![100_unaware.PNG](https://qiita-image-store.s3.amazonaws.com/0/149049/e47c25d6-9ce0-1a8a-813f-9cc65e1cf08f.png) |
| 150% | ![150_aware.PNG](https://qiita-image-store.s3.amazonaws.com/0/149049/9230268e-9b77-440e-3f61-11ef4b643808.png) | ![150_unaware.PNG](https://qiita-image-store.s3.amazonaws.com/0/149049/722190c1-9ce7-8628-e3ef-b1067afb18ed.png) |


* DPI設定なしで100%以外だとサイズが変わる（物理座標系を返す。）
* アプリのDPI設定にかかわらずOnMovingの引数は100%と同じ
* アプリのDPI設定にかかわらずDwmGetWindowAttributeのDWMWA_EXTENDED_FRAME_BOUNDSは物理座標系を返す。
* アプリのDPI設定にかかわらずGetCursorPosとGetPhysicalCursorPosは論理座標系を返す。

追記：
　マグネットウィンドウはアプリのウィンドウ同士をくっつけるためにウィンドウの座標やサイズが重要になってくるんですが、アプリによってDPI設定がまちまちな上に取得できる値がどうもおかしい、というのが調査のきっかけです。
　調査結果をもとに、GetProcessDpiAwareness APIでunaware（DPI設定なし）ならマウス座標やOnMovingその他の論理座標系で取れてしまうものを物理座標系に変換して内部処理し、一部は元の論理座標系に戻して返すなどをしています。

# 参考
* [High DPI Reference](https://msdn.microsoft.com/ja-jp/library/windows/desktop/hh447398(v=vs.85).aspx)
* [Visual Studio 2012 のような光るウィンドウを作る (再)、そして WPF での高 DPI 対応Add Star](
http://grabacr.net/archives/1105)
* [プログラマから見たWindows 10 #4 ～ 最新情報とプログラミングTips](http://www.webtech.co.jp/blog/os/win10/8445/)
* [高解像度厨が Windows 8.1 のスケーリングについてのお話を書いてみる。(2)](https://mntone.hateblo.jp/entry/2013/07/12/234722)