CSS3のブレンドモードとは?
Photoshopなどの画像編集ツールに搭載されている「ブレンドモード(描画モード)」機能ですが、CSS3でもmix-blend-mode
というプロパティで実装されています。
CSS3なので当然各ブラウザ(IE以外)で見た目を統一する必要があるので、W3Cの仕様に各ブレンドモードの計算式が載っています。
Compositing and Blending
Compositing and Blending 日本語訳
なお、策定にはAdobeの社員も関わっているらしく、おそらくですがPhotoshopなどのAdobe製品のブレンドモードと同じ計算式が使われているものと思われます。
意外と難しい"Non-separable blend"
W3Cの仕様を読み解いていくと、ブレンドモードは"Separable blend"と”Non-separable blend"の2つに分類されます。
”Non-separable blend"は、成分ごとに個別に扱わずに,すべての色成分の組合せを考慮するブレンドです。
"Separable blend"はscreen
やoverlay
など、単純な数式を使うものなので何の問題もありません。
問題は”Non-separable blend"に分類されるhue
, saturation
, color
, luminosity
の4つです。
例えばhue
モードですが、ソース画像の色相(Hue)を、背景画像の輝度を維持しつつ適用するというモードです。
下の2つのイメージをhue
モードで合成してみましょう。
合成結果は以下のとおりです。
「色相を移植するんだからHSV色空間に変換して、H成分を移植して・・・」という単純な話ではありません。
色相を移植しつつ、元画像の輝度(Luminance)を維持しなければなりません、つまり合成結果に対してモノクロ化すると以下のようにならなくてはいけないのです。
あひるが完全に消えましたね。
明度(Value)と明度(Lightness)と輝度(Luminance)の話
色相(Hue)を扱う色空間は、よく使われるのがHSV色空間とHSL色空間の2つです。
HSV色空間は色相(Hue)、彩度(Saturation)、明度(Value)の3で構成されます。
HSL色空間は色相(Hue)、彩度(Saturation)、輝度(Lightness)の3つで構成されています。
Wikipediaでは、HSL色空間の輝度(Lightness)と記載されていますが、これは輝度(Luminance)とは異なります。
ここらへんの単語の混乱は、いろいろなページで見られるので注意が必要です。
RGBから輝度(Luminance)へ変換する一般的な数式は以下のとおりです。
r * 0.298912 + g * 0.586611 + b * 0.114478
HSLのL(Lightness)への変換式は以下の通り。
(MAX(r, g, b) - MIN(r, g, b)) / 2
ハッキリいって全く違いますね。
Lightnessの訳は「明度」や「輝度」など資料によって訳され方がまちまちですので、混乱しないようにすることが重要です。
SetSatの解釈について
W3Cの計算式にSetSat
という擬似コードの関数が記載されています。
SetSat(C, s)
if(Cmax > Cmin)
Cmid = (((Cmid - Cmin) x s) / (Cmax - Cmin))
Cmax = s
else
Cmid = Cmax = 0
Cmin = 0
return C;
この擬似コードを最初に見た時は、意味がわかりませんでした。
Cmin
はRGB成分の中の最小値、Cmax
は最大値、Cmid
は中央値となります。
例を出すとC=RGB(0,8, 0.6, 0.3)とするなら、Cmin
はB要素(0.3)、Cmax
はR要素(0.8)、Cmid
はG要素(0.6)となります。
計算式は以下のとおりになります。
G = (((G - B) x s) / (R - B))
R = s
B = 0.0
という事になります。
ちなみにRGB(0.1, 0.1, 0.5)やRGB(0.5, 0.5, 0.1)のようにCmin
やCmax
が2つある場合はどうなるかは書いていません。
正解はどうなるかはわかりませんが、以下の条件さえ満たせば、値は何でも良いようです。
max(Cred, Cgreen, Cblue) - min(Cred, Cgreen, Cblue) == s
Python(pillow)での実装
計算式がわかったので、Pythonの画像ライブラリであるpillowで実装します。pillowは実質的にPython標準の画像ライブラリと言って良いでしょう。OpenCVのように高度な機能はありませんが、様々なライブラリへデータコンバートが可能で、コンパクトで高速な画像処理が特徴です。
PIL/Pillow チートシートで記載されているように、ブレンドモードの実装はImageMath
モジュールを使います。
例としてhard-light
の実装をしてみましょう。
from PIL import ImageMath
def _hard_light(a, b):
_cl = 2 * a * b / 255
_ch = 2.0 * (a + b - a * b / 255.0) - 255.0
return _cl * (b < 128) + _ch * (b >= 128)
bands = []
for cb, cs in zip(backdrop.split(), source.split()):
t = ImageMath.eval(
"func(float(a), float(b))",
func=_hard_light,
a=cb, b=cs
).convert("L")
bands += [t]
Image.merge("RGB", bands)
ImageMath
モジュールを使うと、要素の計算を数値のように計算できるので便利です。
"Separable blend"のような単純な処理に関しては、こんな感じで実装ができます。
問題は"Non-separable blend"です、意外と計算量も多くトリッキーな処理もあり結構難しいです。
実装に関しては後でコードを公開していますので、興味がる方は見てもらうとして、ImageMath
モジュールの非公開な関数を駆使して実装しています。
他言語で移植するのであれば、GLSLなどを使用すると思います、参考になれば幸いです。
「Image4Layer」モジュール
これらの処理を「Image4Layer」というモジュール名でパッケージ化しました。
画像処理系などでoverlay
の合成などはよく使うので、結構使いドコロはあると思います。
インストールはpipで簡単に行なえます、実行にはpillow(PIL)があらかじめインストールされている必要があります。
$pip install image4layer
使い方は簡単です、color-dodgeモードで合成する例です。
from PIL import Image
from image4layer import Image4Layer
source = Image.open("ducky.png")
backdrop = Image.open("backdrop.png")
Image4Layer.color_dodge(backdrop, source)
簡単ですね、以下、対応しているブレンドモード一覧です。
CSS3にはありませんが、Photoshopに搭載されているブレンドモードもついでに実装しました。
ブレンドモード | 画像 |
---|---|
Image4Layer.vivid_light | |
Image4Layer.pin_light | |
Image4Layer.linear_dodge | |
Image4Layer.subtract |
ライセンスはMITですので、商用、個人利用とわず無料で使えます。
追記
Version0.4にPython2で動作しないことと、RGBA同士の演算にバグがありました、0.43で修正されています。
あとがき
Compositing and Blending 日本語訳には、想定される描画と実際にブラウザの表示があるのですが、Chromeでアクセスすると以下のようになります。
あれ・・・?色味が結構ちがくない?
ここら辺の表示は各ブラウザによって色味がかなり違ってきそうですね。
追記
Chromeのバージョンによって色味は違いそうです、私の環境はUbuntu環境で、どうやらbackdrop.pngに設定されているアルファ値の合成にバグがあるようです。
ちなみにImage4Lyaerの表示結果はChromeの結果とほぼ一緒です、おそくらく同じ実装をしているものと思います。
何か情報があれば、随時記事にしていきます。