OpenMotion の各クリップで指定可能な合成モード(blendMode)の数学的定義と視覚的効果についての詳細な仕様です。
共通の計算アルゴリズム
OpenMotion のレンダラーは、以下の計算手順でピクセルごとの合成を行います(BLEND_RGB 定義がある特殊モードを除く)。
1. 前処理
ソース色 (Cs) と背景色 (Cd) のアルファ値を分離(Unpremultiply)し、ソースの不透明度 (O) を適用します。
Cs=src.asrc.rgb,Cd=dst.adst.rgb
As=src.a×O,Ad=dst.a
const Cs = src.rgb / src.a;
const Cd = dst.rgb / dst.a;
const As = src.a * opacity;
const Ad = dst.a;
2. 重なりの領域計算 (Overlap)
OpenMotion は標準的な非相関オーバーラップを採用しています。
p0=As×Ad(ソース∩背景)
p1=As×(1−Ad)(ソース∖背景)
p2=Ad×(1−As)(背景∖ソース)
const p0 = As * Ad;
const p1 = As * (1.0 - Ad);
const p2 = Ad * (1.0 - As);
3. 最終色の出力
各モードごとに定義された関数 f(Cs,Cd) と、重み係数 k (coeff) を用いて算出します。
C∗res=f(Cs,Cd)⋅p0⋅kx+Cs⋅p1⋅ky+Cd⋅p2⋅kz
A∗res=p0⋅kx+p1⋅ky+p2⋅kz
const resRGB = f(Cs, Cd) * p0 * coeff.x + Cs * p1 * coeff.y + Cd * p2 * coeff.z;
const resA = p0 * coeff.x + p1 * coeff.y + p2 * coeff.z;
gl_FragColor = vec4(resRGB, resA);
Porter-Duff 演算(アルファ合成)
重ね合わせの論理的な領域を制御します。色成分の変化を伴わず、f(Cs,Cd) は Cs または Cd を返します。
| モード名 | k (coeff) | f(Cs,Cd) | 説明 |
|---|
zero | [0,0,0] | 0 | 何も描画しない。 |
src | [1,1,0] | Cs | ソースのみを表示。 |
dest | [1,0,1] | Cd | 背景のみを表示。 |
src-over | [1,1,1] | Cs | 標準。背景の上にソース。 |
dest-over | [1,1,1] | Cd | ソースの下に背景。 |
src-in | [1,0,0] | Cs | 背景がある所にのみソース。 |
dest-in | [1,0,0] | Cd | ソースがある所にのみ背景。 |
src-out | [0,1,0] | 0 | 背景がない所にのみソース。 |
dest-out | [0,0,1] | 0 | ソースがない所にのみ背景。 |
src-atop | [1,0,1] | Cs | 背景の範囲内で上にソース。 |
dest-atop | [1,1,0] | Cd | ソースの範囲内で上に背景。 |
xor | [0,1,1] | 0 | 重なっていない所。 |
ブレンディング演算
背景とソースの色を計算によって混合します。これらはすべて coeff = [1,1,1] を使用します。
multiply (乗算)
f(Cs,Cd)=Cs×Cd
screen (スクリーン)
f(Cs,Cd)=Cs+Cd−Cs×Cd
f(Cs, Cd) = Cs + Cd - Cs * Cd
overlay (オーバーレイ)
f(Cs,Cd)={2CsCd1−2(1−Cs)(1−Cd)(Cd≤0.5)(Cd>0.5)
f(Cs, Cd) = (Cd <= 0.5) ? 2.0*Cs*Cd : 1.0 - 2.0*(1.0-Cs)*(1.0-Cd)
darken (比較(暗))
f(Cs,Cd)=min(Cs,Cd)
f(Cs, Cd) = Math.min(Cs, Cd)
lighten (比較(明))
f(Cs,Cd)=max(Cs,Cd)
f(Cs, Cd) = Math.max(Cs, Cd)
color-dodge (覆い焼き)
f(Cs,Cd)=min(1.0,1.0−CsCd)
f(Cs, Cd) = Math.min(1.0, Cd / (1.0 - Cs))
color-burn (焼き込み)
f(Cs,Cd)=1.0−min(1.0,Cs1.0−Cd)
f(Cs, Cd) = 1.0 - Math.min(1.0, (1.0 - Cd) / Cs)
hard-light (ハードライト)
f(Cs,Cd)={2CsCd1−2(1−Cs)(1−Cd)(Cs≤0.5)(Cs>0.5)
f(Cs, Cd) = (Cs <= 0.5) ? 2.0*Cs*Cd : 1.0 - 2.0*(1.0-Cs)*(1.0-Cd)
soft-light (ソフトライト)
f(Cs,Cd)=⎩⎨⎧Cd−(1−2Cs)Cd(1−Cd)Cd+(2Cs−1)(Cd−Cd)Cd+(2Cs−1)Cd((16Cd−12)Cd+3)(Cs≤0.5)(Cs>0.5,Cd>0.25)(Cs>0.5,Cd≤0.25)
f(Cs, Cd) = (Cs <= 0.5) ?
Cd - (1.0 - 2.0*Cs) * Cd * (1.0 - Cd) :
(Cd > 0.25) ?
Cd + (2.0*Cs - 1.0) * (Math.sqrt(Cd) - Cd) :
Cd + (2.0*Cs - 1.0) * Cd * ((16.0*Cd - 12.0) * Cd + 3.0)
difference (差分)
f(Cs,Cd)=∣Cd−Cs∣
f(Cs, Cd) = Math.abs(Cd - Cs)
exclusion (除外)
f(Cs,Cd)=Cs+Cd−2CsCd
f(Cs, Cd) = Cs + Cd - 2.0 * Cs * Cd
invert (階調反転)
f(Cs,Cd)=1.0−Cd
invert-rgb
f(Cs,Cd)=Cs(1.0−Cd)
f(Cs, Cd) = Cs * (1.0 - Cd)
linear-dodge (線形覆い焼き)
f(Cs,Cd)=Cs+Cd
linear-burn (線形焼き込み)
f(Cs,Cd)=Cs+Cd−1.0
f(Cs, Cd) = Cs + Cd - 1.0
vivid-light (ビビットライト)
f(Cs,Cd)={1−min(1,2Cs1−Cd)min(1,2(1−Cs)Cd)(Cs≤0.5)(Cs>0.5)
f(Cs, Cd) = (Cs <= 0.5) ?
1.0 - Math.min(1.0, (1.0 - Cd) / (2.0 * Cs)) :
Math.min(1.0, Cd / (2.0 * (1.0 - Cs)))
linear-light (リニアライト)
f(Cs,Cd)=2Cs+Cd−1.0
f(Cs, Cd) = 2.0 * Cs + Cd - 1.0
pin-light (ピンライト)
c=2Cs−1
f(Cs,Cd)=min({cc+1(c≤Cd)(c>Cd),Cd)
const c = 2.0 * Cs - 1.0;
f(Cs, Cd) = Math.min((c <= Cd ? c : c + 1.0), Cd);
hard-mix (ハードミックス)
f(Cs,Cd)={01(Cs+Cd≤1.0)(Cs+Cd>1.0)
f(Cs, Cd) = (Cs + Cd <= 1.0) ? 0.0 : 1.0;
HSL演算
色相 (H), 彩度 (S), 輝度 (L) を用いた合成です。
hue (色相)
f(Cs,Cd)=SetLumSat(Cs,Sat(Cd),Lum(Cd))
f(Cs, Cd) = SetLumSat(Cs, Cd, Cd)
saturation (彩度)
f(Cs,Cd)=SetLumSat(Cd,Sat(Cs),Lum(Cd))
f(Cs, Cd) = SetLumSat(Cd, Cs, Cd)
color (カラー)
f(Cs,Cd)=SetLum(Cs,Lum(Cd))
f(Cs, Cd) = SetLum(Cs, Cd)
luminosity (輝度)
f(Cs,Cd)=SetLum(Cd,Lum(Cs))
f(Cs, Cd) = SetLum(Cd, Cs)
特殊演算 (RGBA 直接合成)
add (plus / 加算)
f(src,dst)=src+dst
subtract (減算)
f(src,dst)=dst−src
add-darker
アルファ値を考慮しつつ、各チャンネルの「暗さ」を合算して差し引く特殊な加算合成です。
A∗res=min(1.0,As+Ad)
R∗res=max(0.0,A∗res−((As−Rs)+(Ad−Rd)))
G∗res=max(0.0,A∗res−((As−Gs)+(Ad−Gd)))
B∗res=max(0.0,A_res−((As−Bs)+(Ad−Bd)))
const resA = min(1.0, src.a + dst.a);
const resR = max(0.0, resA - (src.a - src.r + (dst.a - dst.r)));
const resG = max(0.0, resA - (src.a - src.g + (dst.a - dst.g)));
const resB = max(0.0, resA - (src.a - src.b + (dst.a - dst.b)));
return vec4(resR, resG, resB, resA);
contrast (コントラスト)
背景のコントラストをソースを基準に強調します。
C_res=2Ad+2(Cd−2Ad)(Cs−2As)
f(src, dst).rgb =
dst.a * 0.5 + 2.0 * (dst.rgb - dst.a * 0.5) * (src.rgb - src.a * 0.5);
red / green / blue (チャンネル抽出)
f(src,dst)=(Sr,Dg,Db,Da)(red の場合)
f(src, dst) = vec4(src.r, dst.g, dst.b, dst.a)