【C#】 GDI+でカラー オーバーレイ(風)

# 概要
@Kyome さんのRunCat の記事

・Windowsのタスクバーでもネコ走らせてみた🐈 - Qiita
https://qiita.com/Kyome/items/47aac4979933dac12263

を見ていて、個人的に猫に自由に任意の色を付けたくなったので、WinForm (GDI+) で ``カラー オーバーレイ`` が出来ないか試してみた。

# ``カラー オーバーレイ`` とは？
Photoshopの ``レイヤー効果`` でおなじみ(？)のやつ。
レイヤーの描画ピクセルのに色情報を任意の色に上書きするやつ。
私は良くレイヤーの描画モードを ``スクリーン`` にした単色レイヤーを重ね合わせえて色味調整を行うことがよくあるのだけど。``カラー オーバーレイ`` だとカラーピッカーで選択中の色がリアルタイムで反映されるので、 ``スクリーン`` レイヤーの色味調整でよく使ってる。


![SS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72257/6995bf80-7404-2b0f-07a8-feda4d5fb8e2.png)

# 開発環境

プログラムは @Kyome22 さんの **[RunCat for windows](https://github.com/Kyome22/RunCat_for_windows)** を拝借させていただきました。ありがとうございます！

* Windows10 Pro v1809
* Visual Studio 20019 Community
* .NET Framework 4.7.2 (AnyCPU)
* ソース
   * RunCat for windows @Kyome22  
     https://github.com/Kyome22/RunCat_for_windows  
     ※forkさせて頂きました。
   * RunCat4WinColors (RunCat for windows をforkした私のリポジトリ)  
     https://bitbucket.org/libraplanet/runcat4wincolors/

# 概略
* ``Graphics.DrawImage`` のオプションで使える ``System.Drawing.Imaging.ImageAttributes`` で描画時に色を置換させる。
* 置換情報は ``ImageAttributes.SetRemapTable()`` に ``System.Drawing.Imaging.ColorMap`` をセットして行う。
* 画像ソースは 白1色 + α の32bit画像
* 白に対して、置換色表はα = 0～255の **256パターン** 用意する。
* 指定色のα値も反映させるため、置換後の色のα値にもかけ合わせる。

```cs:ColorMapはαの深度分.cs
ColorMap[] map = new ColorMap[256];
for (int j = 0; j < map.Length; j++)
{
    colorMap[j] = new ColorMap()
    {
        OldColor = Color.FromArgb(j, 255, 255, 255),
        NewColor = Color.FromArgb(j * newColor.A / 255, newColor.R, newColor.G, newColor.B),
    };
}
```

![ss_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72257/05df4445-682e-12af-e9ce-b9053e759b87.png)
![ss_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72257/e622fd12-d7c4-f9ae-18a9-b4b847ec7664.png)
![ss_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72257/0edf18e7-2d93-187c-217d-29a6c89ad721.png)


こんな塩梅。
``Color.Transparent`` もちゃんと効くのがありがたい。ただ、今回 ``カラー オーバーレイ(風)`` としているのは、オーバーレイ対象が描画ピクセルでなく白だけだから。白(+α)以外の色があるとダメなので、実は ``オーバーレイ`` としては若干用足らず(苦笑)。

# サンプル ソース
カラー オーバーレイを試すにあたり、 @Kyome22 さんの [RunCat for windows](https://github.com/Kyome22/RunCat_for_windows) を勝手に利用させていただきました。ありがとうございます！

* RunCat4WinColors  (RunCat for windows をforkしたプロジェクト)  
https://bitbucket.org/libraplanet/runcat4wincolors/

**バック バッファ** を ``Imageオブジェクト`` で白猫の画像の枚数分予め作っておき、下記メソッドが呼ばれたら **バックバッファ** の内容を更新し、``Iconオブジェクト`` を再生成する。 (**バック バッファ** はタスクトレイなので一応16x16。)


```cs:カラー&nbsp;オーバーレイ&nbsp;抜粋.cs
private void SetIconColor(Color catColor, Color bgColor)
{
    Bitmap[] images = new Bitmap[]
    {
        Resources.cat16_0,
        Resources.cat16_1,
        Resources.cat16_2,
        Resources.cat16_3,
        Resources.cat16_4,
    };
    ImageAttributes attr = new ImageAttributes();

    //　remap color table
    {
        ColorMap[] colorMap = new ColorMap[256];
        for (int j = 0; j < colorMap.Length; j++)
        {
            colorMap[j] = new ColorMap()
            {
                OldColor = Color.FromArgb(j, 255, 255, 255),
                NewColor = Color.FromArgb(j * catColor.A / 255, catColor.R, catColor.G, catColor.B),
            };
        }
        attr.SetRemapTable(colorMap);
    }

    //  recreate icons
    for (int i  = 0; i < imgIconBufs.Length; i++)
    {
        Bitmap imgBuf = imgIconBufs[i];
        Bitmap imgCat = images[i];
        Icon ico = icons[i];
        // bg
        {
            Graphics g = Graphics.FromImage(imgBuf);
            g.Clear(bgColor);
            g.DrawImage(imgCat, new Rectangle(new Point(0, 0), imgCat.Size), 0, 0, imgCat.Width, imgCat.Height, GraphicsUnit.Pixel, attr);
        }
        if(icons[i] != null)
        {
            // 再作成時、古いHICONは必ず廃棄する！
            DestroyIcon(icons[i].Handle);
            icons[i] = null;
        }
        icons[i] = Icon.FromHandle(imgBuf.GetHicon());
    }
}
```

# 感想
一応それっぽいのは出来たけど、``Bitmapクラス``から 生成した ``Iconクラス`` を明示的に破棄するためにWin32APIの ``DestroyIcon`` を呼ぶ必要があって ``プラットフォーム 呼び出し`` ( ``P/Invoke``) を使ってしまったのが若干つらい。とは言っても、**64bit モジュールがちゃんとがある普通のWin32API** だからそんなに気にしなくてもだけど。でもやっぱり.NETは可能な限りマネージド コードだけで行きたい。。。(びば！AnyCPU！)

ただ、最初はピクセル操作になると思っていたので ```Marshal.Copy``` するくらいならと ``Unsafe`` も覚悟していたけど、一応カラー オーバーレイ(？) 部分だけはマネージド コードだけなので、そこは助かった。今度は ``ColorMatrix`` も試してみたい。ColorMapで愚直に対応表を作るとすると。``2^32 - 1`` で **4,294,967,295 パターンにもなり、とても現実的ではないので。。。**

# 参考
* Kyome22/RunCat_for_windows: A cute running cat animation on your windows taskbar.
  https://github.com/Kyome22/RunCat_for_windows

* [C#]タスクトレイアイコンの画像を動的に変更する
  http://nanoappli.com/blog/archives/1975

* 色を入れ替えて画像を描画する - .NET Tips (VB.NET,C#...)
  https://dobon.net/vb/dotnet/graphics/setremaptable.html

* 色を反転させた画像（ネガティブイメージ）を表示する - .NET Tips (VB.NET,C#...)
　https://dobon.net/vb/dotnet/graphics/drawnegativeimage.html
* ColorMatrixによる色の変換 - C# - hesperus.net
  https://www.hesperus.net/csharp/color_matrix.aspx