http://rararahp.cool.ne.jp/cgi-bin/lng/vc/vclng.cgi?print+201103/11030012.txt
アンチエイリアスのかかった線をスーパーサンプリングで描く場合、
長方形として描くのを思いついたのですが、もっといいアルゴリズムはないでしょうか?
思いついたのアルゴリズムは、
線を長方形とみなし、ドットが長方形内ならば点を打っていく方法です。
>もっといいアルゴリズム
かどうかはわかりませんが……
自分が「手動で」アンチエイリアスをするときの手順みたいなのを
そのまま実装するなら,↓のようになるかなぁ,と.
・ブレゼンハムのアルゴリズムか何かで線の通る
幅1のピクセル群■(アンチエイリアスしないときの線を構成する画素群)を得る
→この画素群はα=100%とする
・前記画素群■に2隣接する画素群□については■よりも小さいα値で描画する
・もっとやるなら,□と■に隣接する画素△ではより小さいα値で描画するとか
□に隣接する■のα値を少し下げるとか
■■■□△
△□■■■■□△
△□■■■
もっと真面目(?)にやるなら,描画したい線を数式で表して,
着目画素と線の距離に応じたα値で画素に描画する.
その場合,自分ならまともに”距離”を計算するのではなくて
描画したい線の式を f(x,y)=0 の形で表して,
画素位置(x,y)=(px,py)については
f(px,py)の絶対値か2乗値あたりを距離の代わりに使うと思います.
あとは,(スーパーサンプリングと同じような意味合いになるでしょうが)
等倍空間上でのぼかし処理をどうにかして使うとか…?
大昔に実装した、簡易な方法は以下の様な点Pnに対して
P0 P1 P2
P3 Pn P4
P5 P6 P7
Pn = f( V, P0~P7) ・・・f()は平滑化関数
V・・・評価方向ベクトル
というものでした。例外がけっこういっぱいあるので全てをここで
説明はできませんが、概念は上の様に至って簡単。
いかん、大事なことを言い忘れたorz
線分の場合の平滑化はLineDDA()を使いました。
ホウジョウウサギさん、ありがとうございます。
となるとぼかしのような方法ですね。
この場合線の太さは指定通りにできますか?
仲澤@失業者さん、LineDDA関数とは何ですか?
誤:LineDDA関数とは何ですか?
正:LineDDA関数はどうやって使いますか?
>この場合
がどの場合かにもよるとは思いますが,シンプルに
太い線 = 線の内側 + エッジ×2
と考えて
内側はべた塗りにして2つのエッジで個別にアンチ処理すれば
いいのではないでしょうか.
別の思いついた方法ですが,
描きたい線の式が用意できた時点で
その線上を(適当な離散間隔で)走査しながら
線と垂直な方向にガウス分布(σ<0.5[pixel]とかくらいで?)する濃度パターン
みたいなのをレンダリングしていいだけかも?
すみません.最後の語尾が変ですね
>レンダリングしていいだけかも?
→レンダリングしていけばいいだけかも?
ホウジョウウサギさん、ありがとうございます。
>内側はべた塗りにして2つのエッジで個別にアンチ処理すれば
>いいのではないでしょうか.
太くなってしまいませんか?その場合どういった補正をかけるべきでしょうか?
>太くなってしまいませんか?
太く見えないような結果となる方法を採用してください,としか……
例えば,1点の描画の場合で言えば…:
描画時の画素位置(x,y)は整数座標しかとれないのに
(x,y)=(0.25, 0.8) に(α=100%で)点を描画したい(したように見せたい)
→(0,0),(0,1),(1,0),(1,1)の4画素にそれぞれ
α<=100% かつ, 4画素のα値の合計=100% になるように描画する.
(この例なら,4か所の中では(0,1)が最もαが大きく,(1,0)が最もαが小さくなる
はず)
みたいな.
(要するに,本当に描画したい場所(0.25, 0.8)に直接描画することはできないので
その近隣の画素に 描画したい濃度(ここではα=100%)を分散させて疑似表現する )
で,この場合,
>α値の合計=100%
となっていれば,描画結果が 1画素よりも大きな面積を持った点 には見えないのでは
ないかと.
もし,αの合計が100を超えていれば 大きく,
100未満であれば 小さく見えることになるでしょう.
線を描く場合でも同じかと思います.
私が最初に書いたてきとーなやつ(■とか△とかで書いたやつ)では
最初の方のステップだけしか行わなければ
単に□や△の位置に追加描画を行うことになり,結果は太くなると思います.
(描画される濃度の総和が増加するから)
なので,最後のステップとして 「もっとやるなら」(=もう少しまじめにやるなら)と
して,
>□に隣接する■のα値を少し下げるとか
といったことを書いたわけです.
(□や△に追加された分を近隣の■から引いて,差し引き0にするということ)
ひょっとして,私は
>この場合線の太さは指定通りにできますか?
を「太い線を描画した場合にはどう処理するのか?」という内容に
誤読していたのではないだろうか……!
しかし,だとしたら
>線の太さを指定
とは一体何を指しているのか…?
ホウジョウウサギさん
>ひょっとして,私は
>>この場合線の太さは指定通りにできますか?
>を「太い線を描画した場合にはどう処理するのか?」という内容に
>誤読していたのではないだろうか……!
誤読ではありませんので大丈夫です。
線を書く場合一点一点の集合で表すのですね。
その場合一点の座標はどのようにして求められるのでしょうか?
隙間ができたりしないでしょうか?
>線を書く場合一点一点の集合で表すのですね
質問者様が最初に示したリンク先を見るに
>Win32APIというかアルゴリズムをお願いします。
>DIBセクションに対して自己で描画します。
とのことでしたので,1画素ずつ自前で何かしら計算しながら描画していく話かと思って
いましたが違うのでしょうか??
>その場合一点の座標はどのようにして求められるのでしょうか?
例えば,直線(線分)の話でいけば,
少なくともアンチエイリアスをかけない場合の線を構成するピクセル群の座標については
>ブレゼンハムのアルゴリズムか何かで
取得できるはずです.
(>LineDDA
というのもおそらくそのための関数ではないかと.詳細は知りませんが…)
で,それがわかれば,その周辺画素の座標は自明ではないかと.
曲線だとしても似たような方法でいけるのではないでしょうか?
というか最悪,全画素走査すれば処理は可能ですから,
処理範囲の絞り込みについての効率の良さを求めるのは後回しにして,そのへんは
・線を包括する十分なサイズの矩形領域内を走査する
とか
・太い線を実際にAPIで描画した結果の絵から座標をひっぱってくる
とかでてきとーに済ませておいて,
まずは アンチエイリアス処理自体 の内容を検討されるほうが質問者様の目的を達成する
近道となるのではないかと思います.
(処理すべき範囲が,最終的に採用するアルゴリズム次第ということもあると思います
し.)
>仲澤@失業者さん、LineDDA関数とは何ですか?
マニュアルを見ればわかる程度のことで板面を汚したくはありませんが
要は線を引く代わりにコールバックを呼んでくれるわけですね。
ホウジョウウサギさん、ありがとうございます。
点を打つ方法がよくわかりません。
LineDDA関数で導いた点をもとに点を打つとやはりジャギーが目立ちませんか?
仲澤@失業者さん、すみません。
つまりこの関数に渡したコールバック関数はなんども呼ばれるということですね?