GoogleでWindowsのカラーマネジメントについて検索してみても、アプリケーションの開発者の話とかプログラミングについての情報などは全くといっていいくらいに見つかりません。自分は開発者と言えるほどの立場ではありませんが、せめてとっかかりになる情報だけでもということで記事にしてみることにしました。

この記事をきっかけに、自分のアプリケーションをカラーマネジメント対応にしようと試みる人がひとりでも増えることを期待します。

基本的な描画処理にカラーマネジメントを適用する

WindowsにはGDI/GDI+というグラフィックスシステムが用意されていて、一般的に図形などの描画はこれらを用いて行われます(現在はDirect2Dというのが出てきてGDI/GDI+はレガシーとされていますが……たぶんまだ普通にみんなが使っているという状況ではないでしょう)。

GDI/GDI+は、WindowsのカラーマネジメントシステムであるICM2.0/WCSと連携できるように作られており、比較的容易にそれらを利用できるようになっています。簡単なサンプルで見ていくことにしましょう。

Visual C# 2010 ExpressでGDIICMTestという名称の新規Windowsフォームアプリケーション プロジェクトを作成し、フォームのコード部分(Form1.cs)に以下の内容を入力します(実際にはInitializedComponent();まで元のままですので、その先をコピペすればよいはずです)。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace GDIICMTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            Paint += (sender, e) =>
            {
                Graphics g = Graphics.FromHwnd(Handle);

                Brush rBrush = new SolidBrush(Color.FromArgb(255, 0, 0));
                Brush gBrush = new SolidBrush(Color.FromArgb(0, 255, 0));
                Brush bBrush = new SolidBrush(Color.FromArgb(0, 0, 255));
                g.FillRectangle(rBrush, 0, 0, 50, 50);
                g.FillRectangle(gBrush, 50, 0, 50, 50);
                g.FillRectangle(bBrush, 100, 0, 50, 50);

                g.Dispose();
            };
        }
    }
}
cm_prog_howto_1_fig1

実行するとRGBの3つの四角形が描かれたウインドウが現れます。現状ではただ指定のRGB値で塗りつぶしているだけですので、カラーマネジメントとは無縁です。

カラーマネジメントを有効にして描画するには、SetICMMode()関数を使用して、描画対象のデバイスコンテキストに対してICMをオンに設定します。書き忘れていましたがC#でGraphicsクラスを用いて描画する際にはGDI+が使われているため、描画結果はSetICMMode()関数の影響を受けます。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace GDIICMTest
{
    public partial class Form1 : Form
    {
        [DllImport("user32.dll")]
        private extern static IntPtr GetDC(IntPtr hWnd);
        [DllImport("user32.dll")]
        private extern static bool ReleaseDC(IntPtr hWnd, IntPtr hDC);

        /// <summary>
        /// デバイスコンテキストのカラーマネジメント有効・無効を指定
        /// </summary>
        /// <param name="hDC"<デバイスコンテキストのハンドル>/param<
        /// <param name="iEnableICM">ICM_ON, ICM_OFF, ICM_QUERY, ICM_DONE_OUTSIDEDC のいずれか</param>
        /// <returns>成功なら非ゼロ値、失敗なら0。iEnableICMがICM_QUERYの場合は現在のモードを示す値が返ってくる</returns>

        [DllImport("gdi32.dll")]
        private extern static int SetICMMode(IntPtr hDC, int iEnableICM);

        const int ICM_OFF = 1;
        const int ICM_ON  = 2;

        public Form1()
        {
            InitializeComponent();

            Paint += (sender, e) =>
            {
                // ウインドウのデバイスコンテキストを取得
                IntPtr hDC = GetDC(Handle);

                Graphics g = Graphics.FromHdc(hDC);

                // 描画色を決める
                Brush rBrush = new SolidBrush(Color.FromArgb(255, 0, 0));
                Brush gBrush = new SolidBrush(Color.FromArgb(0, 255, 0));
                Brush bBrush = new SolidBrush(Color.FromArgb(0, 0, 255));

                // カラーマネジメントOFFで描画する
                SetICMMode(hDC, ICM_OFF);
                g.FillRectangle(rBrush, 0, 0, 50, 50);
                g.FillRectangle(gBrush, 50, 0, 50, 50);
                g.FillRectangle(bBrush, 100, 0, 50, 50);

                // カラーマネジメントONで描画する
                SetICMMode(hDC, ICM_ON);
                g.FillRectangle(rBrush, 0, 60, 50, 50);
                g.FillRectangle(gBrush, 50, 60, 50, 50);
                g.FillRectangle(bBrush, 100, 60, 50, 50);

                // 後始末
                g.Dispose();
                ReleaseDC(Handle, hDC);
            };
        }
    }
}
cm_prog_howto_1_fig2 cm_prog_howto_1_fig3

実行すると2段目の四角形がカラーマネジメントされた状態で描画されます。モニターの既定のプロファイルを変えると2段目だけ描画結果が変わるのが確認できるはずです。塗りつぶすとき指定しているRGB値は1段目も2段目も一緒ですが、ICMが間に入って描画色を変換してから描画が行われるためこのような結果になるのです。