本件は、2022年1月26日より配信されたKB5008353の更新を適用することで改善します。以下の内容は記録としてそのまま残します。

Windows11が10月5日に正式リリースされました。お仕事でPCを使われている方は基本的に様子見かと思いますが、種々の検証のためアップグレード、あるいは運悪くこのタイミングでPCが故障してエイヤ!とプリインストールモデルを購入……といった方もいらっしゃることでしょう。

当記事では、現在確認されているカラーマネジメント関連の不具合についてまとめます。

不具合の詳細

カラープロファイル(ICCプロファイル)に対応したアプリケーションは、ドキュメント・コンテンツのプロファイルとモニタのプロファイルを使用して適正なカラーで画面表示を行いますが、Windows11ではアプリ側からモニタに関連付けられたプロファイルを取得することができず、不適切なカラーで表示されてしまう可能性があります。

win11_monitorprofile_issue

Windows11でAdobe Photoshopを実行しているスクリーンショット。モニタの既定のプロファイルが正しく取得できずsRGBのプロファイルがあてがわれてしまっているのが分かります。

モニタへのプロファイルの関連付けは「色の管理」コントロールパネル(colorcpl.exe)から確認・追加・削除が行えます。「既定」と括弧書きで付いているプロファイルが実際に使われるプロファイルとなります。近年はHDR対応にともない、HDRモード時のプロファイルを別途設定できるようになっています(高度なカラー)。

colorcpl_win10

(複数のプロファイルが関連付けされている環境で)既定のプロファイルを切り替える操作は、設定アプリでも行うことができます。

i1シリーズ・Spiderシリーズなどを使用してモニタのキャリブレーションを行っている場合はプロファイルが作成された時点で既定のプロファイルに設定されるため、この辺はあまり意識されていない方もいるかもしれませんね。

話を戻しますが、今回は「モニタにプロファイルを関連付けられない」のではなく「関連付けられたプロファイルがアプリに正しく渡らない」問題となります。アプリ側からプロファイル取得用のAPI関数を呼び出しても、あたかも全くプロファイルが関連付けられていないような結果が返されます。

影響を受けるアプリ

GDIのデバイスコンテキストに対してGetICMProfile()関数やEnumICMProfiles()関数によりプロファイルを取得するアプリが影響を受けます。

UWPアプリはカラープロファイルに対応していてもGDIのAPIを使用しない(できない)ため、「フォト」アプリなどは影響ありません。一方、(カラープロファイルに対応した)デスクトップアプリの大多数はGetICMProfile()関数を使用しているため影響があります。

Note:GDIはWindows95よりさらに昔から存在する2DグラフィックスAPIです。またデバイスコンテキストは描画処理に関わる情報・状態(何に対して描画を行うのか、とか描画色、線の太さなど)を保持するために使われます。

GetICMProfile()はデバイスコンテキストの出力プロファイルを取得する関数、EnumICMProfiles()はデバイスコンテキストの作成元となっているデバイスに関連付けられたプロファイルの一覧を取得する関数です。いずれもWindows98 / 2000で実装されました。

まだ少数ではありますが、この件に対策済みのアプリも出てきています。Windows 11カラーマネジメント問題 アプリの対策状況もご覧ください。

Microsoftの対応

この件に関ししばらく情報がない状況でしたが、12月25日、Windows 11 の既知の問題と通知に「色をレンダリングするために Win32 API を使用するアプリケーションが期待通り動作しない可能性がある」という項目が掲載されました。

11 をインストールWindows、画像編集プログラムによっては、特定の HDR ディスプレイで色が正しくレンダリングされない場合があります。 これは、明るい黄色または他の色で表示される可能性がある白い色で頻繁に観察されます。

この問題は、特定の色レンダリング Win32 API が特定の条件下で予期しない情報またはエラーを返す場合に発生します。 すべてのカラー プロファイル管理プログラムが影響を受ける場合は、Microsoft のカラー コントロール パネルを含む Windows 11 設定 ページで使用できるカラー プロファイル オプションが正しく機能するはずです。

次の手順: 解決策に取り組み、1 月下旬にソリューションを利用できると見積もっています。

「この問題は、特定の色レンダリング Win32 API が特定の条件下で予期しない情報またはエラーを返す場合に発生します」という要件について、予期しない情報またはエラーを返すというあたりは当記事の件を連想させる記述となっていますが、それ以外の部分、またこの問題により発生する事象の説明については食い違いもみられ(機械翻訳によりおかしくなっている部分も見られますが、原文も内容的には大差ありません)、現時点ではGetICMProfile()EnumICMProfiles()の問題を指しているのか、それとも別のAPIの問題なのかは不透明です。

特定のAPIが、というところまで分かっているのであれば、そのAPIを早急に公表すべきと考えます。

12月31日追記:以前(11月?)よりDev版では問題が修正されている、というコメントが何カ所かで見られました。ただ、伝聞調だったり修正済みであることが確認できるスクリーンショットがなかったりで、本当にGetICMProfile()EnumICMProfilesの件なのかはっきりしない状況でした。

そのため、手持ちのraytrektabにDev版(ビルド22523)をインストールして実際に検証してみた結果以下のようになりました。

これをもって1月下旬に提供されるだろう更新がGetICMProfile()の件だと断言できないのは歯がゆいところではありますが……繰り返しますがMicrosoftには早急に詳細を公表していただきたいです。

ユーザー向けの回避策

IrfanViewやFirefoxなど、アプリ側でモニタのプロファイルを直接指定できるアプリがあります。それらをWindows11で使用する場合はお使いのモニタのプロファイルを設定しておきましょう。

GetICMProfile()を使用するアプリでは、モニタにひとつもプロファイルが関連付けられていない場合は「Windows色システムの既定値」として設定されているプロファイルが代用で渡されますので、これをお使いのモニタのプロファイルに変更することで改善が期待できます。

cm_prog_howto_2_fig1

ただし、ここはモニタのプロファイルを取得する文脈以外にも関わるためアプリによっては予期しない動作になる可能性があります。変更する際には十分テストを行い、不要になりしだい元に戻すようにしてください

開発者向けの回避策

デバイスのプロファイル取得関数の比較
関数要件Win11対象の識別取得対象フルパスシステム・ユーザー高度なカラー
GetICMProfile2000
98
×GDIデバイス名モニタ
プリンタ
区別不可×
WcsGetDefaultColorProfileVistaモニタ
プリンタ
スキャナ
×区別あり×
ColorProfileGetDisplayDefault10 1809アダプタ・モニタのIDモニタ×区別あり
※モニタならEnumDisplayDevices()で取得したdeviceKeyが使える

Windows10 1809(RS5)で実装されたColorProfileGetDisplayDefault()関数(mscms.dll)を使用すると、Windows11においても問題なくモニタの既定のプロファイルを取得することができます。

HRESULT WINAPI ColorProfileGetDisplayDefault(
  _In_  WCS_PROFILE_MANAGEMENT_SCOPE scope, 
  _In_  LUID   targetAdapterID,
  _In_  UINT32 sourceID,
  _In_  COLORPROFILETYPE profileType,
  _In_  COLORPROFILESUBTYPE profileSubType, 
  _Outptr_ LPWSTR* profileName
);

scopeはシステム規定(全ユーザー共有)の設定を参照するか、あるいは個別のユーザー設定を参照するかを指定します。通常はColorProfileGetDisplayUserScope()で取得した値を渡します。

targetAdapterIDsourceIDで対象となるアダプタ(GPU)とモニタを明示します。

profileTypeは取得するプロファイルのタイプを指定します。現状CPT_ICC(ICCプロファイル)のみ有効のようですが、他の値を使う場面はそもそも皆無でしょう。

profileSubTypeは取得するプロファイルのサブタイプを指定します。通常はCPST_NONECPST_STANDARD_DISPLAY_COLOR_MODEを渡しておけばよいと思います。HDR対応アプリなど「高度なカラー」として設定されたプロファイルを取得したい場合はCPST_EXTENDED_DISPLAY_COLOR_MODEを指定します。

profileNameにはファイル名を受け取るためのポインタを渡します。受け取った文字列が不要になったらLocalFree()で解放する必要があります。ファイル名はフルパスではありませんので、必要であればGetColorDirectory()でプロファイルのインストール場所を取得し、フルパスを作成してください。

HRESULT WINAPI ColorProfileGetDisplayUserScope(
  _In_  LUID   targetAdapterID,
  _In_  UINT32 sourceID,
  _Out_ WCS_PROFILE_MANAGEMENT_SCOPE *scope
);

こちらは特に説明する必要はないでしょう。「色の管理」コントロールパネルの「このデバイスに自分の設定を使用する」のチェックに応じた値がscopeに格納されます。

モニタのハンドル(HMONITOR)やGDIデバイス名("\\.\DISPLAY1"のような文字列)からtargetAdapterIDsourceIDを取得する方法は「Example code for displaying an app on a portrait device」のGetPathInfo()を参考にしてください。

10月23日追記:

WcsGetDefaultColorProfile()関数(mscms.dll)でも正しいプロファイルを取得することができます。こちらはWindows Vista以降で使用できますのでWindowsのバージョン判定やエントリポイントの有無を確認しなくてもよくなります。

BOOL WINAPI WcsGetDefaultColorProfile(
  _In_ WCS_PROFILE_MANAGEMENT_SCOPE scope,
  _In_opt_ PCWSTR pDeviceName,
  _In_ COLORPROFILETYPE cptColorProfileType,
  _In_ COLORPROFILESUBTYPE cpstColorProfileSubType,
  _In_ DWORD dwProfileID,
  _In_ DWORD cbProfileName,
  _Out_writes_bytes_(cbProfileName) LPWSTR pProfileName
 );

pDeviceNameにはEnumDisplayDevices()で取得したDISPLAY_DEVICE構造体のDeviceKeyメンバを与えます。

ProfileIDについては詳しい情報がなくどこから引っ張ってきた値を使用するのか不明ですが、とりあえず0のままでも支障はないようです。

pProfileNameはあらかじめWcsGetDefaultColorProfileSize()で取得したサイズのメモリを確保してそのポインタを渡します。

WINUSERAPI BOOL WINAPI EnumDisplayDevicesW(
  _In_opt_ LPCWSTR lpDevice,
  _In_ DWORD iDevNum,
  _Inout_ PDISPLAY_DEVICEW lpDisplayDevice,
  _In_ DWORD dwFlags
);

lpDeviceには(今回の文脈では)モニタのGDIデバイス名を指定します。

iDevNumは(今回の文脈では)0を指定します(モニタのリストを作成したい場合はlpDeviceにNULLを入れ、iDevNumの数値を増やしながら繰り返し呼び出す)。

lpDisplayDeviceは取得したい情報を格納するためのDISPLAY_DEVICE構造体のポインタを指定します。構造体のcbメンバには構造体のサイズを入れておく必要があります。

dwFlagsは0で構いません。

BOOL WINAPI WcsGetDefaultColorProfileSize(
  _In_ WCS_PROFILE_MANAGEMENT_SCOPE scope,
  _In_opt_ PCWSTR pDeviceName,
  _In_ COLORPROFILETYPE cptColorProfileType,
  _In_ COLORPROFILESUBTYPE cpstColorProfileSubType,
  _In_ DWORD dwProfileID,
  _Out_ PDWORD pcbProfileName
);

WcsGetDefaultColorProfile()とほぼ同じパラメータなので説明は省略します。pcbProfileNameに必要なサイズが格納されます。

サンプル

MonitorProfileSample

C#(WinForms)のサンプルです。新旧の方法で挙動の比較が可能です。10月23日追記:WcsGetDefaultColorProfile()の使用サンプルを実装しました。