【C#】 GDI+でカラー オーバーレイ (ColorMatrixでリベンジ)

# 概要
以前、[こちらの記事](https://qiita.com/libraplanet/items/a5c8a313a1f7e7bbc86a) で **オーバーレイ(風)** にチャレンジしたのだけど。今回は ``ColorMatrix`` を利用して ``オーバーレイ`` に再チャレンジ。前回の **(風)** とは違い、今回はちゃんと望み動きが出来た。

どちらかというと数学寄りの話。といっても私もにわかなので、そんなに難しくない話。

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


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

# カーラー オーバーレイを行うための行列
``ColorMatrix`` は ``行列(Matrix)`` を使って色味を調整する。

* ColorMatrixによる色の変換 - C# - hesperus.net  
  https://www.hesperus.net/csharp/color_matrix.aspx


まず早速、その ``行列`` のための配列。

ジャグ配列... (´・ω・｀)
## 通常
シンプルに指定色に置き換える。

```cs:color&#X20;overlay.cs
new ColorMatrix(new float[][]{
    new float[]{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
    new float[]{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
    new float[]{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
    new float[]{ 0.0f, 0.0f, 0.0f, 1.0f, 0.0f },
    new float[]{    r,    g,    b, 0.0f, 1.0f },
});
```
## 指定色のアルファを加味する場合
アルファによって、オーバーレイを加減をする。
(適応量を線形補間。)

* 0.0の時は塗りつぶしが行われない。
* 1.0の時はで完全塗りつぶし。

```cs:color&#X20;overlay&#X20;+&#X20;a.cs
new ColorMatrix(new float[][]{
    new float[]{ 1 - a,  0.0f,  0.0f, 0.0f, 0.0f },
    new float[]{  0.0f, 1 - a,  0.0f, 0.0f, 0.0f },
    new float[]{  0.0f,  0.0f, 1 - a, 0.0f, 0.0f },
    new float[]{  0.0f,  0.0f,  0.0f, 1.0f, 0.0f },
    new float[]{ r * a, g * a, b * a, 0.0f, 1.0f },
});
```

# 表示例
参考のイラストは、Pixivでは18年齢制限の制限がついてしまっているので、本記事では一応何となく一部にモザイクをかけております (苦笑)。

## サンプル プロジェクト
* libraplanet / coloroverlaycs — Bitbucket  
  https://bitbucket.org/libraplanet/coloroverlaycs/

## 単色でのカラー オーバーレイ
単純に黒で塗りつぶし。
(人によっては背景が透明に見える)
![SS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72257/0dc2e2dc-800f-899a-1573-5a838087340d.png)
![SS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72257/713df932-32cc-79e1-1153-da3be60e7b96.png)

## + α
色に透明度(α)も追加。
※ ``アルファ ブレンディング (加算)`` ではない。
![SS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72257/5429f5ec-e696-d7cc-70ab-0d7857cf70de.png)
![SS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72257/453a2108-26e3-e0ed-9cb4-640ed2ba05e1.png)

## おまけパターン
絵も背景も単色で塗りつぶし。
なんか、一昔の ~~エロゲ~~ ゲームの 立ち絵 + とアルファ チャンネルみたい (苦笑)
![SS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72257/4b48e6d8-cf0f-d7e0-51ed-a7aa45e0cb88.png)
![SS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72257/dccc31da-1847-6a41-8220-be59f34906d3.png)

# 解説
## 行列 (Matrix) とは
高校数学あたりでやる、あの行列。行列演算は3Dなどで大変活用されており、機械学習など様々に応用されているので、最早言わずもがな、というくらい。

こんな計算するやつ。

```math
\begin{bmatrix}
x & y \\
\end{bmatrix}
\times
\begin{bmatrix}
a & b & c\\
d & e & f\\
\end{bmatrix}
= 
\begin{bmatrix}
\Bigr((x \times a) + (y \times d)\Bigr) & \Bigr((x \times b) + (y \times e)\Bigr) & \Bigr((x \times c) + (y \times f)\Bigr)\\
\end{bmatrix}\\
```

例えば3Dの場合、行列1つの4x4の行列で、``回転情報 (Rotatioin)``、``拡縮情報 (Scale)``、``平行移動情報 (Translation)`` を持つことができ、座標情報(Vector) や 他の行列 と掛け合わせることで、``頂点座標を変換``したり、 ``回転情報を伝播`` したりなと、多様される。正直、当時は何となく習っただけの内容だけど、3Dで再会したときの衝撃はびっくりした。``CSS Transform 3D`` も基礎は全く同じなので、ほんと行列すごい。

今回の ``ColorMatrix`` では色情報を変換するのフィルターとして行列演算が使われている感じ。


## ColorMatrixでは5x5の正方行列を使う

* ColorMatrix クラス (System.Drawing.Imaging) | Microsoft Docs  
https://docs.microsoft.com/ja-jp/dotnet/api/system.drawing.imaging.colormatrix?view=netframework-4.5

より。入出力とフィルターとなるMatrixでの計算式はこんな感じ。

```math
\begin{bmatrix}
R & G & B & A & W \\
\end{bmatrix}
\times
\begin{bmatrix}
Rr & Gr & Br & Ar & Wr \\
Rg & Gg & Bg & Ag & Wg \\
Rb & Gb & Bb & Ab & Wb \\
Ra & Ga & Ba & Aa & Wa \\
Rw & Gw & Bw & Aw & Ww \\
\end{bmatrix}
= 
\begin{bmatrix}
R' & G' & B' & A' & W' \\
\end{bmatrix}
```
```math
\begin{align}
R' &= (R * Rr) + (G * Rg) + (B * Rb) + (A * Ra)  + (W * Rw) \\
G' &= (R * Gr) + (G * Gg) + (B * Gb) + (A * Ga)  + (W * Gw) \\
B' &= (R * Br) + (G * Bg) + (B * Bb) + (A * Ba)  + (W * Bw) \\
A' &= (R * Ar) + (G * Ag) + (B * Ab) + (A * Aa)  + (W * Aw) \\
W' &= (R * Wr) + (G * Wg) + (B * Wb) + (A * Wa)  + (W * Ww) \\
\end{align}
```

入力パラメータが **要素数5** の ```5次元ベクトル``` なので、行列は ``5x5の正方行列`` が利用される。これを使って色味の調整ができる。

### 単位行列 (何しない行列)
正方行列で主対角線上の要素が1で、他が0のベクトルは ``単位行列`` と呼ばれる。細かい条件はあると思うが、今回の様にベクトルの次元数(要素数)と同じ行数・列数を持つ ``正方行列`` の場合、 ``単位行列`` との積の場合、同じ値が得られる。

```math
単位行列 = 
\begin{bmatrix}
\color{red}{1} & 0 & 0 & 0 & 0 \\
0 & \color{red}{1} & 0 & 0 & 0 \\
0 & 0 & \color{red}{1} & 0 & 0 \\
0 & 0 & 0 & \color{red}{1} & 0 \\
0 & 0 & 0 & 0 & \color{red}{1} \\
\end{bmatrix}
```
```math
\begin{align}
ベクトルV \times 単位行列 &= ベクトルV (値が変わらない) \\
行列M \times 単位行列 &= 行列M (値が変わらない) \\
\end{align}
```

一応確認。

```math
\begin{bmatrix}
R & G & B & A & W \\
\end{bmatrix}
\times
\begin{bmatrix}
\color{red}{1} & 0 & 0 & 0 & 0 \\
0 & \color{red}{1} & 0 & 0 & 0 \\
0 & 0 & \color{red}{1} & 0 & 0 \\
0 & 0 & 0 & \color{red}{1} & 0 \\
0 & 0 & 0 & 0 & \color{red}{1} \\
\end{bmatrix}
= 
\begin{bmatrix}
R' & G' & B' & A' & W' \\
\end{bmatrix}
```
```math
\begin{align}
R' &= (R * \color{red}{1}) + (G * 0) + (B * 0) + (A * 0)  + (W * 0) = R \\
G' &= (R * 0) + (G * \color{red}{1}) + (B * 0) + (A * 0)  + (W * 0) = G \\
B' &= (R * 0) + (G * 0) + (B * \color{red}{1}) + (A * 0)  + (W * 0) = B \\
A' &= (R * 0) + (G * 0) + (B * 0) + (A * \color{red}{1})  + (W * 0) = A \\
W' &= (R * 0) + (G * 0) + (B * 0) + (A * 0)  + (W * \color{red}{1}) = W \\

\end{align}
```
ほんとだ！
だいたいここから値を変えながら色々と試行していく。

※ ちなみに、行列は ``交換法則`` は成り立たないが、単位行列との積については ``交換法則`` は成り立たつらしい。

### 入力値 『W(1)』 で任意の値に置換する。
今回の主役。

* ColorMatrix クラス (System.Drawing.Imaging) | Microsoft Docs  
  https://docs.microsoft.com/ja-jp/dotnet/api/system.drawing.imaging.colormatrix?view=netframework-4.5

> 行列係数は、ARGB 同種値の変換に使用される 5 x 5 線形変換を構成します。 たとえば、ARGB ベクターは、赤、緑、青、アルファ、w として表されます。 w は常に1です。

MSの説明をみて、なんのコッチャ？と思った人少なくないのでは、と思う。

行列の積では基本は乗算のため、ARGB値に係数を掛け合わせるだけでは、特定の値に置き換えることが出来ない。しかし、``Wには固定値 1`` がくるのでWで任意の値を作ることが可能。更に 　``他の係数を 0`` にすれば元の値を潰せるので、置き換えることができる。

 ``ColorMatrix`` は色情報を ``0.0～1.0`` で表現するため、 `rgb(229,153,76)` に置き換える場合。


```math
\begin{align}
R &= 229 \div 255 &= 0.8980... &\fallingdotseq \color{red}{0.9} \\
G &= 153 \div 255 &&= \color{red}{0.6} \\
B &= 76  \div 255 &= 0.2980... &\fallingdotseq \color{red}{0.3} \\
\end{align}
```

```math
\begin{bmatrix}
R & G & B & A & \color{red}{1} \\
\end{bmatrix}
\times
\begin{bmatrix}
0.0 & 0.0 & 0.0 & 0.0 & 0.0 \\
0.0 & 0.0 & 0.0 & 0.0 & 0.0 \\
0.0 & 0.0 & 0.0 & 0.0 & 0.0 \\
0.0 & 0.0 & 0.0 & \color{red}{1.0} & 0.0 \\
\color{red}{0.9} & \color{red}{0.6} & \color{red}{0.3} & 0.0 & 0.0 \\
\end{bmatrix}
= 
\begin{bmatrix}
\color{red}{0.9} & \color{red}{0.6} & \color{red}{0.3} & \color{red}{A} & W' \\
\end{bmatrix}
\\
\\
\begin{align}
R' &= (R * 0.0) + (G * 0.0) + (B * 0.0) + (A * 0.0)  + (\color{red}{W} * \color{red}{0.9}) = \color{red}{0.9} (置換) \\
G' &= (R * 0.0) + (G * 0.0) + (B * 0.0) + (A * 0.0)  + (\color{red}{W} * \color{red}{0.6}) = \color{red}{0.6} (置換) \\
B' &= (R * 0.0) + (G * 0.0) + (B * 0.0) + (A * 0.0)  + (\color{red}{W} * \color{red}{0.3}) = \color{red}{0.3} (置換) \\
A' &= (R * 0.0) + (G * 0.0) + (B * 0.0) + (A * \color{red}{1.0})  + (W * 0.0) = \color{red}{A} (通過) \\
W' &= \color{gray}{(捨てられるので省略)}
\end{align}
```

という計算式で置き換えることができる。
``W`` ありがてぇ。

### (おまけ) 正方行列の理由
前述で一部省略はしているが、 ``出力 W'`` は使われない。入力で``(R G B A W)``の **5次元ベクトル** のため、行列は **5行** 必要だが、出力は ``(R' G' B' A')`` の **4次元ベクトル** のため、行列は **4列** で十分だったりする。

```math
\begin{bmatrix}
R & G & B & A & W \\
\end{bmatrix}
\times
\begin{bmatrix}
Rr & Gr & Br & Ar & \color{red}{(Wr)} \\
Rg & Gg & Bg & Ag & \color{red}{(Wg)} \\
Rb & Gb & Bb & Ab & \color{red}{(Wb)} \\
Ra & Ga & Ba & Aa & \color{red}{(Wa)} \\
Rw & Gw & Bw & Aw & \color{red}{(Ww)} \\
\end{bmatrix}
= 
\begin{bmatrix}
R' & G' & B' & A' & \color{red}{(W')}\\
\end{bmatrix}
```
:thinking: W’無駄じゃね？

しかし、``正方行列`` であることに利点がある様で、**多少無駄な計算をさせても、作業のしやすさを取った** 形だと考えている。3Dでも ``4x4の正方行列`` が一般的に使われるけど、最後の列が不要らしい。``4x3の行列`` の3Dエンジンのゲーム機もあった。(今はどうしているかわからないけど。流石に正方行列の波に飲まれていると予想)。行列演算だと ``単位行列``、 ``逆行列``、 ``行列の積`` などで、沢山の行列同士を計算する上で、``正方行列`` のほうが確かに都合が良さそうなので。

# 感想
満足の行くカラー オーバーレイがGDI+で出来てよかった。``Matrix (行列)``を使うのは意外だったけど、やっぱすげーな、Matrix。しかし、Matrix より、サンプル プロジェクトを作る方が時間かかった(爆) 2つのコントロール間ので値を相互的な連動とか。今更ながら、DataBindingsにチャレンジ出来たのは良い機会だった。しかし、色の画面表示用にPanel使ってみたけどTabStop出来なかったので、そっちの方法とかもいずれ調べてみたい。

# 参考
* 【.NET】カラー行列(ColorMatrix)で画像のRGBの値を調整する - CommentOut?
  http://www.r-nakai.com/archives/30

* ColorMatrixによる色の変換 - C# - hesperus.net
  https://www.hesperus.net/csharp/color_matrix.aspx

* ColorMatrix クラス (System.Drawing.Imaging) | Microsoft Docs  
  https://docs.microsoft.com/ja-jp/dotnet/api/system.drawing.imaging.colormatrix?view=netframework-4.5

* ImageAttributes.SetColorMatrix メソッド (System.Drawing.Imaging) | Microsoft Docs
  https://docs.microsoft.com/ja-jp/dotnet/api/system.drawing.imaging.imageattributes.setcolormatrix?view=netframework-4.5

* コントロールのデータ連結機能(Control.DataBindings, Binding, データバインディング) - いろいろ備忘録日記
  https://devlights.hatenablog.com/entry/20070413/p1

* Data Binding With INotifyPropertyChanged Interface
  https://www.c-sharpcorner.com/UploadFile/020f8f/data-binding-with-inotifypropertychanged-interface/