affineCopyを-0.5する意味
affineCopy のピクセルの特殊な扱いは、入力領域のみならず出力領域においても関係がある。
仕様の再確認
ひとまず affineCopy におけるピクセルの扱いを再確認しておこう。
整数座標上で (0, 0) のピクセルは、
実数座標上では (0.0, 0.0) – (1.0, 1.0) の範囲を覆っていると見なすのが普通である。
それに対して、affineCopyでは (0, 0) の位置にあるピクセルは
(-0.5, -0.5) – (0.5, 0.5) の範囲にあると見なされる。
出力領域の扱い
このピクセルの扱いは、出力領域においても適用される。
ソレがいったいどんな作用をもたらすのか、ちょっとわかりにくいかもしれない。
このことは
”ラスタライズ処理における、サンプリング位置”
に関係がある。
ラスタライズ
「ラスタライズ」とは、抽象的な描画情報を具体的なピクセルに落とし込む処理のことである。
ここまで述べてきたように整数空間において点であるピクセルは、
実数空間では特定の範囲を持つ矩形として扱われる。
画像のラスタライズ処理において最も気を遣わなければならないのが、
この「実数空間上の入力元ピクセルを、いかに整数空間上の出力先ピクセルに落とし込むか」
言い換えれば
「範囲を、いかに点に落とし込むか」
という部分だ。
サンプリング
ラスタライズ処理において、出力先ピクセルに出力する情報を
入力元から抽出するプロセスを「サンプリング」と呼ぶ。
ある出力先ピクセルに出力する情報は
出力先画像の上に入力元画像を重ね合わせた時、
出力先ピクセルの範囲と、範囲が被っている入力元ピクセルの中から決定する。
この時、出力先ピクセルの範囲内には
複数の入力元ピクセルの範囲が含まれる可能性がある。
そこで問題になってくるのが、範囲が被っている入力ピクセルのうち
どのピクセルの情報を採用するのかという問題だ。
サンプリング点による抽出
この決定は「サンプリング点」によって行う。
サンプリング点は実数空間上のピクセルの範囲の中のいずれかの点だ。
サンプリング点の位置を基準にサンプリングを行う。
最近傍法の場合、サンプリング点の真上にある入力元ピクセルが採用される。
サンプリング点にはピクセル範囲の中心位置を使うのが一般的だ。
一般的なピンセル範囲ではこれは (0.5, 0.5) の位置である。
affineCopy の場合、 ピクセルの範囲は (-0.5, -0.5) – (0.5, 0.5) なので
中心位置は (0.0, 0.0) ということになる。
実際 affineCopy では (0.0, 0.0) の位置がサンプリング点となっている。
(0.0, 0.0) の位置は一般的なピクセル範囲においては左上の位置を表す。
このことから、一般的なピクセル範囲の考え方において affineCopy は
サンプリング点がピクセルの左上になっている
と見なすことができる。
ズレの真の原因
サンプリング点がピクセルの左上だと……
最近傍法の場合、座標をわずかに動かしただけで
サンプリング対象が変化するという現象をひきおこす。
線形補間の場合、サンプリング位置が左上方向にズレることにより、描画が右下にズレる。
これまでに見てきた現象だ!
そう、すなわちズレの原因はサンプリング点が実質的に
ピクセルの左上になっていることにあるのである。
ただしこのズレは不具合ではなく、
ピクセル範囲の考え方の齟齬によって起こる現象
だと言える。
そしてそれこそがズレの真の原因である。
-0.5の意味
ここまで理解すると-0.5の意味はもうわかるだろう。
出力座標を-0.5すると、ピクセル範囲の考え方のズレを補正することができるのである。
またこのことは別の見方もできる。
出力座標を-0.5することは、出力座標上の入力座標を出力座標+0.5分だけずらすことと同義である。
これはサンプリング点の位置をピクセルの左上 (0.0, 0.0) から
ピクセルの中央 (0.5, 0.5) に移動させることと同じことだ。
よってズレが補正されるのである。
まとめ
回りくどく長々と説明してきたが…
結局のところ、描画のズレはピクセル範囲の考え方の齟齬に起因するものだということだ。
それさえ理解していれば affineCopy はなんら問題なく使うことができる。
またこの問題は operateAffine などでも同様である。
ちなみに stretchCopy は内部的に affineCopy を利用する形で実装されているが、
そこでは出力座標がしっかり -0.5 されている!
affineCopy を使う場合は -0.5 を忘れずに!
[追記]
Direct3D9 や OpenGL はピクセルの範囲が (-0.5, -0.5) – (0.5, 0.5) だったようだ。
affineCopy のピクセルの扱いはそれらに準拠したものかもしれない。
なお Direct3D10 からは ピクセルの範囲は (0.0, 0.0) – (1.0, 1.0) になっている。
参考:座標系 (Direct3D 10)
[追記]
上記のOpenGLは勘違いでした。