アンチエイリアスとの戦い
最近は、shadowコマンドのアンチエイリアス部分の処理を改善する方法がないか、四六時中考えていた。影とアンチエイリアス処理された部分の色が「混ざっちまっているんだからどうしようもないじゃないか」と最初は諦め気分だったのだが、考え続けているといろいろな作戦が思い浮かんでくるものである。
以下は影付き画像から、影の部分だけをいかに美しく取り除くことができるか、その試行錯誤の記録である。
ことの経過
- だらしなく無駄に広がるOSXのウィンドウのスクリーンショットの影。
- 影付きウィンドウのスクリーンショットをそのままブログに使うと、影の部分が大き過ぎて、肝心のウィンドウ内部の絵が小さくなってしまう。
- そうかといって、影なしの画像ではメリハリのない印象で、何か足りない気がする。
- 無駄に広い影をスリムな必要最小限の影にしたい!
- また、AppleScriptやAutomatorなどから手軽に利用できる仕組みがいい。
- shadowコマンドの開発はそこから始まった。
- スクリーンショットは、影なし撮影も可能だ。
- やってみると、影なし画像に影を付けるのは、とても簡単だった。
- NSShadowの影の設定をして、NSImageに元画像を再描画するだけでOK。
- ところが、影あり画像の影も調整できるようにしようと思い始めると、問題が山積した。
- 影なしのスクリーンショットが撮影できるのだから、本来不要な機能かもしれないが、
- 触発されたMiniShadow.appは、影付き画像の影まで調整できるのだ!
- この機能に純粋に感動したので、shadowコマンドにもぜひ実装したいと思っていた。
- 影つき画像の影を調整するには、いったん影の部分を取り除いた画像にする必要がある。
- 影なしの画像になれば、NSShadowに好みの設定をして、影を自由に調整できるのだ。
- 四角いウィンドウなら話は簡単。上下左右の影の部分を切り取ってしまえばいいのだ。
- ところが、OSXのウィンドウは角が丸い。25年以上も前のMacintoshの頃から角丸ウィンドウ(ジョブズのビルこだわり)だった。
- すると、単純に四角領域に影の部分をカットしただけでは、コーナー外周部分の影が黒く残ってしまうのである...。
- この黒く残った影を取り除くにはどうすれば良いのか?
- 効率的な方法は思いつかなくて、やってみたのは1ピクセルずつ色情報を調べて、
- もし影だったら透明色にしてしまう、という何の工夫もない地道な方法である。
- 当初、影か 影でないかの判定には、色情報のアルファ値(透明度の情報)に少しでも透明が入っていたら、そのピクセルは影と見なして透明色で塗りつぶしていた。
- するとコーナー外周部の黒い影はきれいさっぱり消えたのだが、今度は別の問題が発生。
- コーナー部分が予想したよりも大きく削り取られてしまうのである。
影を切り取ったコーナー(shadow) | 理想のコーナー(OSX標準) |
---|---|
拡大・縮小作戦
- まず思いついたのが、高精細な画像を用意しておいて、それを必要なサイズに縮小する方法。
- 高精細な画像が縮小される過程で、必要に応じてアンチエイリアスな画像処理になるのではないか?と考えてしまった。
- しかし、いったん撮影されたスクリーンショットの画像の解像度を高めることは困難である。
- でも昔、ビットマップ画像をベクター画像のように拡大できる画像処理エンジンをどこかで見た気がする。
- また、撮影時に高精細な画像にしておくくらいなら、最初から影なしで撮影すれば良い訳で、本末転倒。
- さらには1.5倍して、0.6666...倍すれば、端数処理の過程でアンチエイリアス処理が復活するかもしれない!
- と思ってやってみたが、無情にもまったく効果はなかった。(というよりさらに悪くなった感じ)
- 結局、拡大・縮小作戦では、あえなく撃沈されてしまった...。
- 一方、アンチエイリアスから美しいカーブを取り出すことはできなかったが、
- その過程で追加した拡大・縮小の機能は、-zオプションとして実装した。
-
-
- -zオプションの「z」はズームの「z」
-
$ shadow -z 500 test.png
$ shadow -z 0.7 test.png
- 画像test.pngを0.7倍のサイズに縮小して影付けを行う。
- -zオプションの値には、上記のようにピクセル指定と倍率指定の二つの意味がある。
- その判断の違いは、-zオプションの値の大きさ。
- 8を超える値は、ピクセル制限として処理する。
- 8以下の値は、倍率として処理する。
今やshadowコマンドは自由に拡大・縮小できるようになったのだ!
でも、肝心なアンチエイリアスから真実の境界を見つける方は、さっぱりダメ...。
分析
- 自分の勘やひらめきだけでは、なかなか解決できないと思った。
- そもそも、アンチエイリアスとは何なのか?影とは何なのか?
- まずは問題となっている対象の本質を探っておく必要がありそう。
- 以下は、四角領域にクリッピング後、透明度のあるピクセルだけを検出した、座標と色情報のダンプリストである。
- 情報の並びは以下の順
- (x, y) a透明度 r赤 g緑 b青
- ちょうど、この画像のコーナー部分を想像すると良いかもしれない。(左下原点で)
-
- a透明度が0に近いほど、透明になる。
- a透明度が1に近いほど、rgbの色が濃くなる。
影がコーナーに残った画像
2012-11-15 04:28:57.149 shadow[65140:707] (0, 0) 0.082353 0.000000 0.000000 0.000000 2012-11-15 04:28:57.150 shadow[65140:707] (1, 0) 0.101961 0.000000 0.000000 0.000000 2012-11-15 04:28:57.151 shadow[65140:707] (2, 0) 0.152941 0.000000 0.000000 0.000000 2012-11-15 04:28:57.151 shadow[65140:707] (3, 0) 0.211765 0.000000 0.000000 0.000000 2012-11-15 04:28:57.151 shadow[65140:707] (4, 0) 0.435294 0.603604 0.603604 0.603604 2012-11-15 04:28:57.152 shadow[65140:707] (5, 0) 0.650980 0.807229 0.807229 0.807229 2012-11-15 04:28:57.152 shadow[65140:707] (6, 0) 0.831373 0.886792 0.886792 0.886792 2012-11-15 04:28:57.156 shadow[65140:707] (0, 1) 0.101961 0.000000 0.000000 0.000000 2012-11-15 04:28:57.157 shadow[65140:707] (1, 1) 0.172549 0.000000 0.000000 0.000000 2012-11-15 04:28:57.157 shadow[65140:707] (2, 1) 0.317647 0.382716 0.382716 0.382716 2012-11-15 04:28:57.157 shadow[65140:707] (3, 1) 0.647059 0.806061 0.806061 0.806061 2012-11-15 04:28:57.160 shadow[65140:707] (0, 2) 0.156863 0.000000 0.000000 0.000000 2012-11-15 04:28:57.161 shadow[65140:707] (1, 2) 0.321569 0.365854 0.365854 0.365854 2012-11-15 04:28:57.161 shadow[65140:707] (2, 2) 0.721569 0.831522 0.831522 0.831522 2012-11-15 04:28:57.164 shadow[65140:707] (0, 3) 0.219608 0.000000 0.000000 0.000000 2012-11-15 04:28:57.165 shadow[65140:707] (1, 3) 0.650980 0.795181 0.795181 0.795181 2012-11-15 04:28:57.167 shadow[65140:707] (0, 4) 0.439216 0.580357 0.580357 0.580357 2012-11-15 04:28:57.170 shadow[65140:707] (0, 5) 0.654902 0.790419 0.790419 0.790419 2012-11-15 04:28:57.173 shadow[65140:707] (0, 6) 0.831373 0.872642 0.872642 0.872642 ...中略...
影なしウィンドウの画像(理想のコーナー)
2012-11-15 04:28:59.286 shadow[65140:707] (0, 0) 0.000000 0.000000 0.000000 0.000000 2012-11-15 04:28:59.286 shadow[65140:707] (1, 0) 0.000000 0.000000 0.000000 0.000000 2012-11-15 04:28:59.287 shadow[65140:707] (2, 0) 0.000000 0.000000 0.000000 0.000000 2012-11-15 04:28:59.287 shadow[65140:707] (3, 0) 0.000000 0.000000 0.000000 0.000000 2012-11-15 04:28:59.288 shadow[65140:707] (4, 0) 0.274510 0.942857 0.942857 0.942857 2012-11-15 04:28:59.288 shadow[65140:707] (5, 0) 0.552941 0.943262 0.943262 0.943262 2012-11-15 04:28:59.288 shadow[65140:707] (6, 0) 0.780392 0.944724 0.944724 0.944724 2012-11-15 04:28:59.292 shadow[65140:707] (0, 1) 0.000000 0.000000 0.000000 0.000000 2012-11-15 04:28:59.293 shadow[65140:707] (1, 1) 0.000000 0.000000 0.000000 0.000000 2012-11-15 04:28:59.293 shadow[65140:707] (2, 1) 0.125490 0.937500 0.937500 0.937500 2012-11-15 04:28:59.293 shadow[65140:707] (3, 1) 0.549020 0.942857 0.942857 0.942857 2012-11-15 04:28:59.297 shadow[65140:707] (0, 2) 0.000000 0.000000 0.000000 0.000000 2012-11-15 04:28:59.297 shadow[65140:707] (1, 2) 0.125490 0.937500 0.937500 0.937500 2012-11-15 04:28:59.297 shadow[65140:707] (2, 2) 0.639216 0.944785 0.944785 0.944785 2012-11-15 04:28:59.300 shadow[65140:707] (0, 3) 0.000000 0.000000 0.000000 0.000000 2012-11-15 04:28:59.301 shadow[65140:707] (1, 3) 0.549020 0.935714 0.935714 0.935714 2012-11-15 04:28:59.303 shadow[65140:707] (0, 4) 0.274510 0.928571 0.928571 0.928571 2012-11-15 04:28:59.306 shadow[65140:707] (0, 5) 0.552941 0.936170 0.936170 0.936170 2012-11-15 04:28:59.309 shadow[65140:707] (0, 6) 0.780392 0.924623 0.924623 0.924623 ...中略...
以上のダンプリストから分かること。
試行錯誤
- なるほど、今までは影・アンチエイリアス部分の両方を有無を言わさずa=r=g=b=0にしていたから、コーナー部分がひと回り大きく削ぎ落とされていた。
- ならば、明確に影しかない部分だけ0にしたらどうなるのだろう?
- 0にするのは透明度aの値だけ。rgbの値は現状を維持してみる。
すると...
おっ、格段に良くなった感じ!
- このとき思ったこと。「そうか、argbは割合の情報だから、かけ算するとうまくいくかもしれない!」
- そう考えて、意味のありそうなかけ算をいろいろ試してみた。ここからは想像力でひたすら試すのみ。
- そして、最終的に行き着いたのが、r=r/a、g=g/a、b=b/a、a=a * r * g * b。
- aにrgbをかけ算したのだから、今度はrgbからaを取り除いてみたらどうかと。そんな発想。
進化の過程
すると、どうだろう?今までの計算式の中で、見た目が最も理想のコーナーに近づいた感じがする!(4番)
切り抜きの様子 | 処理方法 | |
---|---|---|
1 | r=0、g=0、b=0、a=0 | |
2 | rgbは現状保持、a=a * r | |
3 | rgbは現状保持、a=a * r * g * b | |
4 | r=r/a、g=g/a、b=b/a、、a=a * r * g * b | |
5 | 影なし画像に対してshadowを実行 (理想のコーナー) |
*1:NSShadowの世界では、色情報を持った影も設定できる。