Psychtoolboxでアルファブレンド（色の混ぜ合わせ）

Psychtoolbox（PTB）は、Matlabで心理物理学実験を行うためのツールボックスです。公式サイトは[こちら](http://psychtoolbox.org/)。ちなみに私は[Psychtoolboxをがんばる](https://sites.google.com/site/ptbganba/)の管理人ですが、解説記事を書くのにQiitaのほうが使い勝手がよいので、これからはこちらで情報を共有していこうかと考え中です。
どうぞよろしくお願いいたします。

# アルファブレンドをマスターしたい

アルファブレンド（アルファブレンディング）は２つの色を混ぜ合わせるときの方法を指定したもので、基本的な考え方は難しくはありませんが、いろいろな組み合わせがあるため分かりにくいです。私も毎度混乱するので、備忘録として残しておきます。
内容に誤りがあればご指摘ください。なお、PTBでのアルファブレンドはOpenGLのそれとほぼ同じです。

#PTBでの色表現

まずはPTBでの色表現から。基本的には次の３通りです。

```matlab:Pattern1
% スカラーを指定
black = 0;
grey = 0.5;
white = 1;
```

```matlab:Pattern2
% RGBチャンネルを指定
red = [1 0 0];
green = [0 1 0];
blue = [0 0 1];
```

```matlab:Pattern3
% RGBに加えて、アルファチャンネル（透明度）を指定
red_with_transparency = [1 0 0 0.2]; % alpha = 0.2
green_with_transparency = [0 1 0 0.4]; % alpha = 0.4
```

PTBでは、Pattern1や2のように透明度（アルファ、A）を明記しない場合は、透明度=1.0となります。この場合、描画先を完全に不透明な色で描画（上書き）します。描画先の色が、描画元の色で入れ替わると思ってもよいです。

従来は0から255の数値を使って色を指定する方法が主流でしたが、最近は0から1に正規化された数値を使うことが推奨されています。`PsychDefaultSetup(2);`を呼び出すことで、0から1の数値表現が有効になります。

#アルファブレンドとは？

模式図を示します。

![alphablend.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495823/ee7ebb43-9fcc-92f0-0423-290c163be1fe.jpeg)

画面になにかを描画しようとしたとき、そこには描画元の色情報と描画先の色情報があります。
ここでは便宜的に、描画元の色情報を (R1, G1, B1, A1)、描画先の色情報を (R2, G2, B2, A2)とします。RGBは赤緑青を表し、Aは透明度（アルファ）を表します。RGBAはチャンネルとも呼ばれます。

英語のマニュアルを読むときの参考までに、描画元はソース（SOURCE, SRC）、描画先はデスティネーション（DESTINATION, DST, あるいはTARGET）と表されることが多いです。上の模式図で**ソースアルファ**と書いていますが、それは描画元のアルファを意味します。

画面に表示される最終的な色は、次の式で計算されます。

> 最終的な色 = 描画元の色 x a1 + 描画先の色 x a2

つまり、描画元の色を何倍かして、同様に描画先の色も何倍かして、それらを足し合わせるだけです。
**ここで分かりにくいのは、a1=A1（描画元のアルファ）、あるいは a2=A2（描画先のアルファ）とは限らないという点です。** a1やa2の組み合わせがいろいろあるので分かりにくいんです。

しかし逆に言えば、**アルファブレンドとは、a1とa2をどのように指定するか**、ということに過ぎません。a1とa2の組み合わせによって、画面に提示される画像の見た目が劇的に変化します。

# よく使われるアルファブレンド
よく使われる、一番人気のブレンディングは次の通りです。

> 最終的な色 = 描画元の色 x A1 + 描画先の色 x (1 - A1)
> 　# A1は描画元のアルファ

PTBでこれを実現するには、

```matlab
Screen('BlendFunction', windowPtr, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
```

です。引数の３番目（GL_SRC_ALPHA）が描画元に適用される係数（上の説明でのa1）で、引数の４番目（GL_ONE_MINUS_SRC_ALPHA）が描画先に適用される係数（a2）です。

例えば描画元のアルファが0.3であれば、描画元のRGBのそれぞれに0.3をかけて、描画先のRGBのそれぞれに0.7(=1-0.3)をかけて足し合わせます。

このとき、描画先のアルファ（A2）はまったく使われない点に注意をしてください。つまりA2=0.5だろうが、A2=1.0だろうが、BlendFunctionが上のように設定されているかぎり、ブレンディングの結果は変わりません。

では、具体的な数値で見てみましょう。

> 描画元の色情報 RGBA = [1 1 0 0.5]
> 描画先の色情報 RGBA = [0.5 0.2 0.4 1]

とします。最終的な色は、

> 最終的な R = 1 x 0.5 + 0.5 x (1 - 0.5) = 0.75
> 最終的な G = 1 x 0.5 + 0.2 x (1 - 0.5) = 0.6
> 最終的な B = 0 x 0.5 + 0.4 x (1 - 0.5) = 0.2
> 最終的な A = 0.5 x 0.5 + 1 x (1 - 0.5) = 0.75

RGBだけでなく、A（アルファ、透明度）にも計算式が適用される点に注意してください。なお、この最終的な色情報は、描画先の新しい色情報と言い換えることもできます。

# 実際に確認

[チュートリアルで公開されているコード](http://peterscarfe.com/totallyminimaldemo.html)を使って説明します。

次のコードは、画面の左上に白い正方形を提示します。ブレンディングの機能はまだ使っていません。グレーの背景に、白い正方形を提示するだけです。

```matlab
% Clear the workspace and the screen
sca;
close all;
clearvars;

% Here we call some default settings for setting up Psychtoolbox
PsychDefaultSetup(2);

% Get the screen numbers. This gives us a number for each of the screens
% attached to our computer.
screens = Screen('Screens');

% To draw we select the maximum of these numbers. So in a situation where we
% have two screens attached to our monitor we will draw to the external
% screen.
screenNumber = max(screens);

% Define black and white (white will be 1 and black 0). This is because
% in general luminace values are defined between 0 and 1 with 255 steps in
% between. All values in Psychtoolbox are defined between 0 and 1
white = WhiteIndex(screenNumber);
black = BlackIndex(screenNumber);

% Do a simply calculation to calculate the luminance value for grey. This
% will be half the luminace values for white
grey = white / 2;
 
% Open an on screen window using PsychImaging and color it grey.
[windowPtr, windowRect] = PsychImaging('OpenWindow', screenNumber, grey, [10 10 800 800]);

imageMatrix = ones(80); % 輝度の強度のみ。80 x 80のすべてのピクセルに1が入っているイメージです。
textureIndex=Screen('MakeTexture', windowPtr, imageMatrix); % 描画元となるテクスチャーを作成
dstRect = Screen('Rect', textureIndex); % テクスチャーの四角領域

% 正方形1
Screen('DrawTexture', windowPtr, textureIndex, [], CenterRectOnPoint(dstRect, 100, 150));

% ここにコードを加えていく 

Screen('Flip', windowPtr); % 画面を更新

% Now we have drawn to the screen we wait for a keyboard button press (any
% key) to terminate the demo.
KbStrokeWait;

% Clear the screen.
sca;
```

描画元と描画先は以下の図のようになります。

![white_rect_alpha1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495823/103b50b2-e918-ce84-9883-2daddb6b006c.jpeg)

補足説明です。

描画元について

```matlab
imageMatrix = ones(80);
```
上述の Pattern1のように、80x80のすべてのピクセルのRGBAチャンネルに1（白色）が与えられていると思ってください。

```matlab
textureIndex=Screen('MakeTexture', windowPtr, imageMatrix);
```

imageMatrixからテクスチャーを作成します。多少語弊がありますが、数値の情報から画像（視覚刺激）を作成する作業です。このテクスチャーはアルファブレンドされない限り白色で、以下の例で何度も（正方形10まで）使い回されます。

描画先について

描画先となる背景色は`grey = 0.5`です。`RGBA = [0.5 0.5 0.5 1.0]`と同じです。透明度アルファは特に指定していないのでデフォルトの1.0です。

DrawTextureを使っていますが、このヘルプは、Matlabのコマンドウィンドウで、

```matlab
Screen DrawTexture?
```

とすると表示されます。[こちらの説明](https://sites.google.com/site/ptbganba/ptbno-komando-no-setsumei/miao-hua-guan-xi/screen-drawtexture-)も役に立つかもしれません。


```matlab
CenterRectOnPoint(dstRect, 100, 150)
```

で正方形を描画する位置を指定しています。座標(100, 150) を中心として正方形が描画されます。正方形の幅と高さは80ピクセルです。CenterRectOnPointの詳細は、Matlabのコマンドウィンドウで`help CenterRectOnPoint`とすると確認できます。

```matlab
% ここにコードを加えていく 
```
という箇所があるかと思いますが、以下で説明することはすべてここに書き加えていってください。

# アルファブレンドをしてみよう

```matlab
% 正方形2
Screen('BlendFunction', windowPtr, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Screen('DrawTexture', windowPtr, textureIndex, [], CenterRectOnPoint(dstRect, 200, 150));
```

この２行を加えてみてください。ブレンドの方法は一番人気の、

> 最終的な色 = 描画元の色 x A1 + 描画先の色 x (1 - A1)

ですね。

BlendFunctionを指定したので、ブレンディングされるかな・・・と思いきや、白い正方形がふたつ並んで表示されると思います。（左側の正方形1はアルファブレンドを指定していない正方形。右側の正方形2はアルファブレンドを指定した正方形）

ただし、ブレンドされていないわけではありません。

正方形2については、描画元の色情報に描画元のアルファ（ソースアルファ）をかける設定（GL_SRC_ALPHA）になっていますが、描画元のアルファは具体的にいくつでしょう？　

答えは1.0です。

さらに描画先の係数は、GL_ONE_MINUS_SRC_ALPHAなので、1から1を引いてゼロ。つまり描画先の色情報にゼロがかけられます。

> 最終的な色 = 描画元の色 x A1 + 描画先の色 x (1 - A1)

で、A1=1.0ということですから、

> 最終的な色 = 描画元の色 

となりますね。その結果、ブレンドされていないように見えるのです。

# globalAlpha

ブレンドの結果が見えるように正方形3を表示してみましょう。

imageMatrixに適切なアルファチャンネルを指定してもよいのですが、ここでは、DrawTextureのglobalAlphaを使ってみます。すでに設定されているアルファ値を、globalAlphaで更新することができます。globalAlphaはDrawTextureの8番目の引数です。

```matlab
% 正方形3 globalAlphaを使って、ソースアルファを0.5にしてみる。
Screen('BlendFunction', windowPtr, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
Screen('DrawTexture', windowPtr, textureIndex, [], CenterRectOnPoint(dstRect, 300, 150), [], [], 0.5);
```

上のコードを加えると３つ目の正方形が表示され、その色が明るいグレーになっていると思います。これは描画元の白色と描画先（背景色）のgreyがブレンドされた結果です。どのようにブレンドされるかというと、

> 描画元（白色 = 1.0) x 0.5 + 描画先（グレー = 0.5) x 0.5 = 0.75

です。アルファチャンネルもブレンドされることに注意してください。`RGBA = [0.75 0.75 0.75 0.75]`です。やっとブレンド結果を見ることができました！

# modulateColor

globalAlphaではなく、modulateColorを使う方法もあります。modulateColorはDrawTextureの9番目の引数です。ちなみに、globalAlphaとmodulateColorの両方を指定すると、globalAlphaが無視されます。

modulateColorを指定すると、描画元のRGBAチャンネルの数値をmodulate（変調）、分かりやすく言えば拡大・縮小することができます。例えば、

```matlab
SRC_RGBA = [0.8 0.6 0.4 1]; % 描画元の色情報
modulateColor = [1 0.5 0.25 0.5];
```

の場合、

> R = 0.8 x 1 = 0.8
> G = 0.6 x 0.5 = 0.3
> B = 0.4 x 0.25 = 0.1
> A = 1 x 0.5 = 0.5

となります。またこれらの数値はあくまで描画元の色情報であり、この色情報に対してさらにアルファブレンドがほどこされます。

また、以下の説明では`Screen('BlendFunction')`を記述していませんが、記述していないときは直前のブレンド設定が引き継がれます。いまのところ、

```matlab
Screen('BlendFunction', windowPtr, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
```
の設定が引き継がれます。

modulateColorの動作確認をしてみましょう。次のコードを追加して正方形4を表示します。

```matlab
% 正方形4
Screen('DrawTexture', windowPtr, textureIndex, [], CenterRectOnPoint(dstRect, 400, 150), [], [], [], [1 1 1 0.5]); 

```

正方形４の見た目は正方形３と同じになるはずです。上の例では、

```matlab
modulateColor = [1 1 1 0.5]
```
となっています。描画元の色情報はRGBA = [1 1 1 1]です。これに対してmodulateColorが適用されます。RGBチャンネルに変化はありません。アルファだけが0.5倍されて、`1 x 0.5 = 0.5` になります。

では、次のコードはどんな正方形を描画するでしょうか。

```matlab
% 正方形5
Screen('DrawTexture', windowPtr, textureIndex, [], CenterRectOnPoint(dstRect, 500, 150), [], [], [], [1 0 0 1]);
```

`modulateColor = [1 0 0 1]`となっています。RとAについては描画元の情報をそのまま使いますが、GとBについてはゼロになります。

> 最終的なRとA = 描画元の色 x A1 + 描画先の色 x (1 - A1) = 1 x 1 + 0.5 x 0 = 1
> 最終的なGとB = 描画先の色 x (1 - A1) = 0

正方形５は真っ赤な正方形になります。

---

正方形6は正方形５とほぼ同じですが、ソースアルファ（描画元のアルファ）が0.5倍されます。描画元のGとBについてはmodulateColorによってゼロになります。

```matlab
% 正方形6
Screen('DrawTexture', windowPtr, textureIndex, [], CenterRectOnPoint(dstRect, 600, 150), [], [], [], [1 0 0 0.5]);
```

> 最終的なR = 描画元（1） x 0.5 + 描画先(0.5) x (1 - 0.5) = 0.75
> 最終的なG = 描画先(0.5) x (1 - 0.5) = 0.25
> 最終的なB = 描画先(0.5) x (1 - 0.5) = 0.25
> 最終的なA = 描画元(0.5) x 0.5 + 描画先(1) x (1 - 0.5) = 0.75

となり、ややくすんだ感じの赤い正方形が表示されます。

# GL_ONE と GL_ZERO

ブレンドの方法を変えてみましょう。

```matlab
% 正方形7
Screen('BlendFunction', windowPtr, GL_SRC_ALPHA, GL_ONE);
Screen('DrawTexture', windowPtr, textureIndex, [], CenterRectOnPoint(dstRect, 100, 300), [], [], [], [1 0 0 0.5]);
```

まずは

> 描画元の色情報 RGBA = [1 1 1 1]

であることを思い出してください。

`modulateColor = [1 0 0 0.5]`ですから、描画元の色情報が次のようにmodulateされます。

> 描画元の色情報 RGBA = [1 0 0 0.5]

さらに、描画元の係数がGL_SRC_ALPHA、描画先の係数が`GL_ONE`になっていることから、次のような結果になります。

> 最終的なR = 描画元（1.0) x 0.5 + 描画先（0.5） x GL_ONE（1.0) = 1.0
> 最終的なG = 描画先（0.5) x GL_ONE（1.0) = 0.5
> 最終的なG = 描画先（0.5) x GL_ONE（1.0) = 0.5
> 最終的なA = 描画元（0.5) x 0.5 + 描画先（1.0） x GL_ONE（1.0) = 1.0（1.0以上にはならない）

次に、GL_ZEROを使ってみます。

```matlab
% 正方形8
Screen('BlendFunction', windowPtr, GL_SRC_ALPHA, GL_ZERO);
Screen('DrawTexture', windowPtr, textureIndex, [], CenterRectOnPoint(dstRect, 200, 300), [], [], [], [1 0 0 0.5]);
```

正方形7とほぼ同じですが、描画先の係数が`GL_ZERO`であるため、描画先の色情報はすべて失われます。その結果、

> 最終的なR = 描画元（1.0) x ソースアルファ（0.5） = 0.5
> 最終的なG = 0
> 最終的なG = 0
> 最終的なA = 描画元（0.5) x ソースアルファ（0.5） = 0.25

となります。

# colorMaskNew

`Screen('BlendFunction')`の５番目の引数で、colorMaskNewを指定することができます。詳しい説明は[こちら](https://sites.google.com/site/ptbganba/ptbno-komando-no-setsumei/miao-hua-guan-xi/screen-blendfunction-)

簡単に言えば、RGBAのそれぞれにおいて、ブレンドを有効にしたり無効にしたりできます。例えば、

```matlab
colorMaskNew = [1 0 0 1]
```

の場合は、RとAチャンネルについてのみブレンドを実行します。基本的に0または1を指定します。0.5などを指定しても意味がありません。0が指定されたときは、描画先の色情報がそのまま使われることを意味します。

例を見てみましょう。

```matlab
% 正方形9 Blueチャンネルだけをブレンドする。その他のチャンネルは描画先のものを維持
Screen('BlendFunction', windowPtr, GL_SRC_ALPHA, GL_ZERO, [0 0 1 0]);
Screen('DrawTexture', windowPtr, textureIndex, [], CenterRectOnPoint(dstRect, 300, 300));
```

`colorMaskNew = [0 0 1 0]`なので、B以外のRGAの３つのチャンネルについては描画先の色情報（グレー）がそのまま使われます。

Bについてのみ、次のようなブレンドがなされます。

> 最終的なB = 描画元 x GL_SRC_ALPHA + 描画先 x GL_ZERO = 1.0

描画元 = 1.0、GL_SRC_ALPHA = 1.0であることに注意してください。

**またこれも大切な点ですが、colorMaskNewはいったん設定すると、その後もずっと引き継がれます。**

例えば、正方形9の下に以下のコードを加えてみます。

```matlab
% 正方形10 colorMaskNewが引き継がれることに注意
Screen('BlendFunction', windowPtr, GL_SRC_ALPHA, GL_ZERO);
Screen('DrawTexture', windowPtr, textureIndex, [], CenterRectOnPoint(dstRect, 400, 300), [], [], [], [0, 0, 1, 0.7]);
```

まず、`modulateColor = [0, 0, 1, 0.7]`によって描画元の色情報が次のようになります。

> 描画元のRGBA = [0 0 1 0.7]

そして、colorMaskNewの設定が引き継がれているため、Bチャンネルのみにブレンドが適用されます。

> 最終的なB = 描画元（1.0) x GL_SRC_ALPHA（0.7） + 描画先 x GL_ZERO = 0.7

となります。RGAについては描画先の色情報がそのまま使われます。

# アルファチャンネルを設定する

いままでの例では、透明度（アルファ）はglobalAlphaやmodulateColorを使って指定していました。この方法以外にも、単純にアルファチャンネルを設定する方法があります。

```matlab
imageMatrix = ones(80, 80, 3); % RGBチャンネル
imageMatrix(:,:,4) = 0.5; % A（alpha）チャンネル
textureIndex=Screen('MakeTexture', windowPtr, imageMatrix);
```

このように設定して、次の正方形11は何色になるでしょうか？

```matlab
% 正方形11
Screen('BlendFunction', windowPtr, GL_ONE, GL_ZERO, [1 1 1 1]);
Screen('DrawTexture', windowPtr, textureIndex, [], CenterRectOnPoint(dstRect, 100, 450));
```

これはちょっと引っかけ問題みたいになっていますが、白色の正方形が表示されます。ブレンドされていないように見えますが、

```matlab
Screen('BlendFunction', windowPtr, GL_ONE, GL_ZERO, [1 1 1 1]);
```

となっていて、係数にアルファがまったく含まれていないですね。描画元の係数にGL_ONE、描画先の係数にGL_ZEROを指定すると、描画先の色情報が描画元の色情報と単純に入れ替わります。

気を取り直して、次のようにしてみましょう。

```matlab
% 正方形12
Screen('BlendFunction', windowPtr, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Screen('DrawTexture', windowPtr, textureIndex, [], CenterRectOnPoint(dstRect, 200, 450));
```

globalAlphaもmodulateColorも指定していませんが、ブレンドされた正方形が表示されたと思います。
正方形の色は、RGBAのいずれも次のようになります。

> RGBA = 描画元（1.0)x0.5 + 描画先（0.5)x0.5 = 0.75

# GL_DST_ALPHA
ここまでの例では描画先（デスティネーション, DST）のアルファをまったく使っていませんでした。
最後は描画先のアルファを使ってみましょう。正方形13と14のふたつの正方形を描画しますが、14は13の右下に描画され、重複する部分が存在します。

```matlab
% 正方形13
imageMatrix = ones(80, 80, 3); % RGBチャンネル
imageMatrix(:,:,4) = 0.4; % A（alpha）チャンネル
textureIndex=Screen('MakeTexture', windowPtr, imageMatrix);
Screen('BlendFunction', windowPtr, GL_SRC_ALPHA, GL_ZERO, [1 0 0 1]);
Screen('DrawTexture', windowPtr, textureIndex, [], CenterRectOnPoint(dstRect, 300, 450));
% 正方形14
Screen('BlendFunction', windowPtr, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, [1 1 1 1]);
Screen('DrawTexture', windowPtr, textureIndex, [], CenterRectOnPoint(dstRect, 320, 470));
```

やや複雑な例ですが、順番に考えてみましょう。

> 正方形13の描画元のRGBA = [1 1 1 0.4]
> 正方形13の描画先のRGBA = [0.5 0.5 0.5 1]

正方形13については、`colorMaskNew = [1 0 0 1]`としているため、RとAチャンネルのみがブレンドされます。描画元の係数はGL_SRC_ALPHAで、描画先の係数はGL_ZEROです。

> グレーの背景とブレンドされた正方形13・・・①
> R = 描画元(1) x 0.4 = 0.4
> G（ブレンドされない) = 0.5
> B（ブレンドされない) = 0.5
> A = 描画元(0.4) x 0.4 = 0.16

続いて正方形14についてですが、14の一部は13の上に描画されます。
まずは**重複しない**部分について考えてみましょう。

> 正方形14の描画元のRGBA = [1 1 1 0.4]
> 重複しない描画先のRGBA = [0.5 0.5 0.5 1]

正方形14については、`colorMaskNew = [1 1 1 1]`としているため、すべてのチャンネルがブレンドされます。描画元の係数はGL_DST_ALPHAで、描画先の係数はGL_ONE_MINUS_DST_ALPHAです。いずれも初めての係数ですね。でも考え方は同じです。描画先のアルファが1であることに注意して計算します。

> 重複していない部分について
> 正方形14のRGB = 描画元(1) x 1 + 0.5 x (1 - 1) = 1
> 正方形14のA = 描画元(0.4) x 1 + 0.5 x (1 - 1) = 0.4

つまり、重複していない部分は白色になります。

そして重複部分については、描画先が**背景とブレンドされた後の正方形13**であることに留意します。

> 正方形14の描画元のRGBA = [1 1 1 0.4]
> 重複している描画先のRGBA = [0.4 0.5 0.5 0.16]・・・上の①

GL_DST_ALPHA = 0.16ですから、

> 最終的なR = 描画元(1) x 0.16 + 0.4 x (1 - 0.16) = 0.496
> 最終的なG = 描画元(1) x 0.16 + 0.5 x (1 - 0.16) = 0.58
> 最終的なB = 描画元(1) x 0.16 + 0.5 x (1 - 0.16) = 0.58
> 最終的なA = 描画元(0.4) x 0.16 + 0.16 x (1 - 0.16) = 0.1984

# まとめ
- 描画元の色情報と描画先の色情報を確認
- 描画元にも、描画先にも、色情報のRGBと透明度アルファAが存在
- 色情報にかけられる係数はなにか（描画元のアルファ？　描画先のアルファ？　あるいは１からアルファを引いたもの？）
- 描画元の色情報と描画先の色情報のそれぞれに係数をかけて、足し合わせたものが最終的な色