いろいろないろ、みんなちがってみんないい。…どういうことなの?

画像をいじくる時に、ある元画像の上にレイヤーを重ねて、上のレイヤーの「不透明度」を変えて画像を加工するというのは、よくある事だと思います。GIMPだと「標準」というモードの処理です。んで、数値的にはどんな処理をしてるのかと調べてみたら、意外な結果だったのでメモしておきます。
「不透明度」を変化させて画像を加工する時、元画像の色を I、上にかぶせる画像の色 M、合成された画像の色を E とします*1。M の不透明度を変化させていくと、不透明度 100% の時 E(結果) = M(マスク) で結果の画像は上にかぶせた画像と一致し、0% で E(結果) = I(元) となり元画像と一致します。と、いう事はその中間では、M と I の RGB 成分の差を取って、その差に不透明度の値を掛けたものを元画像の色に足してやる、というのが一番素直だよなと考えて、以下の関数を書いて試してみました。

#Ruby
#a, b は RGB を成分としたベクトルクラスのインスタンス。
#a は元画像の色、b はかぶせる画像の色を表す。n は不透明度。
def blend( a, b, n )
  n = n / 100.0
  ret = a + ((b - a) * n)
end

この関数の n を変化させて出て来た値と、実際に画像を重ね合わせた時に出て来る色の値を比べたところ、「だいたい合ってる」んだけど、微妙に違います。その違いは以下の通り。

不透明度floor()round()ceil()GIMPPictBearFirefoxGoogle Chrome
10%229.5, 235.8, 242.2229, 235, 242230, 236, 242230, 236, 243230, 236, 242229, 235, 242230, 236, 242231, 237, 243
20%204.0, 216.6, 229.4204, 216, 229204, 217, 229204, 217, 230204, 216, 229204, 216, 229204, 217, 229205, 217, 230
30%178.5, 197.4, 216.6178, 197, 216179, 197, 217179, 198, 217179, 197, 216178, 197, 216179, 198, 217180, 198, 217
40%153.0, 178.2, 203.8153, 178, 203153, 178, 204153, 179, 204153, 178, 203153, 178, 203153, 178, 204154, 179, 204
50%127.5, 159.0, 191.0127, 159, 191128, 159, 191128, 159, 191128, 159, 191127, 159, 191127, 159, 191129, 160, 192
60%102.0, 139.8, 178.2102, 139, 178102, 140, 178102, 140, 179102, 139, 178102, 139, 178102, 140, 178101, 138, 177
70%76.5, 120.6, 165.476, 120, 16577, 121, 16577, 121, 16676, 120, 16576, 120, 16576, 120, 16576, 120, 164
80%51.0, 101.4, 152.651, 101, 15251, 101, 15351, 102, 15351, 101, 15251, 101, 15251, 101, 15350, 100, 151
90%25.5, 82.2, 139.825, 82, 13926, 82, 14026, 83, 14025, 81, 13925, 82, 13925, 82, 14025, 81, 139

  • GIMP 2.6.2, PictBear 1.74, Firefox 3.0.10, Google Chrome 2.0.172.28
  • I(元画像)=#FFFFFF, M(マスク)=#003F7F, M の不透明度を 10% 刻みで変化。

表の内「生」は上の関数からの出力の値そのもの。次にその値を floor(切り下げ), round(四捨五入), ceil(切り上げ) の関数を使って整数に変換したもの。最後にそれぞれのアプリケーションで実際に変換された色の値です。floor:青, round:黄, ceil:赤 としてそれぞれの関数が出力した値と一致する場合、背景を一致する関数の色で塗り分けています(但し 50% の時 round と ceil の値は一致します)。
一見して分かるのは PictBear は floor の値と全てが一致しています。が、他のアプリケーションの場合見事なまでにばらばらです。内部の計算では RGB を使ってないのでしょうか。優勝は Google Chrome です。かすりもしません。
単純に不透明度を設定して画像を重ね合わせるってだけでも、こんなに違うもんなんですね。普段から画像や web サイトをあつかってる人にとっては常識なのかも知れませんが、ちょっと驚きました。

テストに使った html

<html> 
  <head>
    <style type="text/css">
      body {
        background: #fff}
	  div {
		margin: 0.5em 0;
        padding: 1em;
        text-indent: 1em;
        line-height: 1.5;
        background: #003f7f;}
      #s_1 {opacity: 0.1;}
      #s_2 {opacity: 0.2;}
      #s_3 {opacity: 0.3;}
      #s_4 {opacity: 0.4;}
      #s_5 {opacity: 0.5;}
      #s_6 {opacity: 0.6;}
      #s_7 {opacity: 0.7;}
      #s_8 {opacity: 0.8;}
      #s_9 {opacity: 0.9;}
    </style>
  </head>
        
  <body>
     <div id="s_1"></div>
     <div id="s_2"></div>
     <div id="s_3"></div>
     <div id="s_4"></div>
     <div id="s_5"></div>
     <div id="s_6"></div>
     <div id="s_7"></div>
     <div id="s_8"></div>
     <div id="s_9"></div>
  </body>
</html>

*1:2. Layer Modesの表記を拝借