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


概要

@Kyome さんのRunCat の記事

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

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

カラー オーバーレイ とは?

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

開発環境

プログラムは @Kyome22 さんの RunCat for windows を拝借させていただきました。ありがとうございます!

概略

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



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

サンプル ソース

カラー オーバーレイを試すにあたり、 @Kyome22 さんの RunCat for windows を勝手に利用させていただきました。ありがとうございます!

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

カラー オーバーレイ 抜粋.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 - 14,294,967,295 パターンにもなり、とても現実的ではないので。。。

参考