Developer's Blog

透過画像の表示の際にどんな処理がなされているのか

こんにちは、Sleipnir 3 for Windows 開発担当の森本です。

←透明度付き画像です

今回は透明度のある画像を扱うときの話をしたいと思います。

近頃ハードウェアが高性能化するに従って、CSS3 では rgba( 0,0,255,0.5 )  のように色が指定できるなど、透過処理が広く使われる様になってきました。
普段、何も考えずテキトーに使用している透過処理ですが、実際どのような計算がされているのか、を解説していきます。

 

まず、一般的なコンピュータで画像の扱いを説明します。

コンピュータではアナログな情報は扱いづらいので、画像を細かいマス目状に区分けし、
マス目( pixel と呼びます )ごとの色を、「」の三色の足し合わせとして表現しています。(詳細は この辺 からどうぞ)

それぞれの明るさと、透明度を C 言語風に表現してみます。

struct pixel {
	float b; // 青。範囲は 0.0~1.0
	float g; // 緑。範囲は 0.0~1.0
	float r; // 赤。範囲は 0.0~1.0
	float a; // 透明度。範囲は 0.0~1.0 で 1.0 が不透明
};

pixel src;  // 転送元。ユーザーから見て手前にある pixel
pixel dest; // 転送先。ユーザーから見て奥にある pixel

ちょっと解りにくくなりましたが、 dest.r が転送先の赤色成分、 src.a が転送元の透明度、程度が解ればOK です。

さて、ここで転送元と転送先の色を一定の透明度で混ぜるときの処理を考えてみます。

float alpha = 0.3f; // 透明度。範囲は 0.0~1.0 で 1.0 が不透明

dest.r = dest.r * ( 1.0 - alpha ) + src.r * alpha ;
dest.g = dest.g * ( 1.0 - alpha ) + src.g * alpha ;
dest.b = dest.b * ( 1.0 - alpha ) + src.b * alpha ;

これは簡単に書くことが出来ますね。単純に透明度で比例配分すれば OK です。

では、次に「ピクセルごとに透明度を持っている場合」のことを考えてみます。

dest.a = dest.a * ( 1.0 - src.a ) + src.a * src.a ;
dest.r = dest.r * ( 1.0 - src.a ) + src.r * src.a ;
dest.g = dest.g * ( 1.0 - src.a ) + src.g * src.a ;
dest.b = dest.b * ( 1.0 - src.a ) + src.b * src.a ;

“alpha” を転送元の透明度に置き換えるだけです。こんなのちょー簡単ですね!

…でも、一応、念のため、転送元を「50%透明な赤」、転送先を「90%透明な青」だったとして検算してみます。

a = 0.1 * ( 1.0 - 0.5 ) + 0.5 * 0.5  => 0.55
r = 0.0 * ( 1.0 - 0.5 ) + 1.0 * 0.5  => 0.5
g = 0.0 * ( 1.0 - 0.5 ) + 0.0 * 0.5  => 0.0
b = 1.0 * ( 1.0 - 0.5 ) + 0.0 * 0.5  => 0.5

これは!?45%透明な「紫色」になっています…

90%透明な青」なんてほぼ透明なのだから、もっと「50%透明な赤」に近い色になるはず……どこか間違っているようです。

というわけで、透明度 = 光が通り抜ける確率、と言う物理現象に基づいたモデルを作って検証してみます。

これを見ると、各色の計算時に、それぞれの透明度を考慮する必要が有りそうです。
ともかく、違う計算式を作る必要があるとわかったので、作ってみます。

dest.a = dest.a * ( 1.0 - src.a ) + src.a
dest.r = ( dest.r * dest.a * ( 1.0 - src.a ) + src.r * src.a )
       / ( dest.a * ( 1.0 - src.a ) + src.a ) ;
dest.g = ( dest.g * dest.a * ( 1.0 - src.a ) + src.g * src.a )
       / ( dest.a * ( 1.0 - src.a ) + src.a ) ;
dest.b = ( dest.b * dest.a * ( 1.0 - src.a ) + src.b * src.a )
       / ( dest.a * ( 1.0 - src.a ) + src.a ) ;

う…ややこしくなってしまいました… が、めげずに検算してみます。

a = 0.1 * ( 1.0 - 0.5 ) + 0.5 => 0.55
r = ( 0.0 * 0.1 * ( 1.0 - 0.5 ) + 1.0 * 0.5 ) / 0.55 => 0.91
g = ( 0.0 * 0.1 * ( 1.0 - 0.5 ) + 0.0 * 0.5 ) / 0.55 => 0.00
b = ( 1.0 * 0.1 * ( 1.0 - 0.5 ) + 0.0 * 0.5 ) / 0.55 => 0.09

無事、「45%透明で、少し青みがかった赤」という結果が得られました!

それにしても、こんな面倒な計算が本当に必要なんでしょうか?
このままの処理では、今の PC でも辛い計算量になりそうです。

というところで、長くなりそうなので続きは次回に書こうと思います。

次回は、この計算自体に誤解というか、勘違いが潜んでいる話とか、それが原因で各方面で混乱が発生していたりと言う話をしたいと思います。

Copyright © 2019 Fenrir Inc. All rights reserved.