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


概要

以前、こちらの記事オーバーレイ(風) にチャレンジしたのだけど。今回は ColorMatrix を利用して オーバーレイ に再チャレンジ。前回の (風) とは違い、今回はちゃんと望み動きが出来た。

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

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

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

カーラー オーバーレイを行うための行列

ColorMatrix行列(Matrix) を使って色味を調整する。

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

ジャグ配列... (´・ω・`)

通常

シンプルに指定色に置き換える。

color 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の時はで完全塗りつぶし。
color overlay + 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年齢制限の制限がついてしまっているので、本記事では一応何となく一部にモザイクをかけております (苦笑)。

サンプル プロジェクト

単色でのカラー オーバーレイ

単純に黒で塗りつぶし。
(人によっては背景が透明に見える)

+ α

色に透明度(α)も追加。
アルファ ブレンディング (加算) ではない。

おまけパターン

絵も背景も単色で塗りつぶし。
なんか、一昔の エロゲ ゲームの 立ち絵 + とアルファ チャンネルみたい (苦笑)

解説

行列 (Matrix) とは

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

こんな計算するやつ。

\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の正方行列を使う

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

\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}
\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}

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

単位行列 (何しない行列)

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

単位行列 = 
\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{align}
ベクトルV \times 単位行列 &= ベクトルV (値が変わらない) \\
行列M \times 単位行列 &= 行列M (値が変わらない) \\
\end{align}

一応確認。

\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}
\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)』 で任意の値に置換する。

今回の主役。

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

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

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

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

\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}
\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列 で十分だったりする。

\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}

W’無駄じゃね?

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

感想

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

参考