前回の線分生成と同様な手法で, 円の走査変換をプログラミングしてみよう.
次のような設定のもとで, Fig.1 のような円周を描くことにする:
もっとも単純な 1/8 円弧の描画アルゴリズムは次の通り:
この方法では,実数演算(平方根)と乗算(自乗)があるので, 処理速度的に不利である.
なお,プログラムを実装する際には,前回の線分と同様, 簡単化・効率化のために, \( x_0 = 0 \) ,\( y_0 = 0 \) とみなして計算し, 画素 \( \P({x_0 + x,\ y_0 + y}) \) を点灯する. また,この場合には,円の対称性より, 次のような8個の画素を点灯すれば 0 〜 360度の円が完成することになる:
ちなみに,\( y \) 座標から \( x \) 座標を求めるための数式のC言語コードは:
x = (int)round(sqrt((double)(r*r - y*y))); // round() は四捨五入の関数
低速アルゴリズムでは,この式を利用することになる.
または:(低速版の多少の高速化)
x = (int)(sqrt((double)(r*r - y*y)) + 0.5);
両者は同じような計算なのに,なぜ処理速度が変わるのか? 計算方法に多少の違いがある他, 関数の呼出や戻り値の返却の処理にも時間を要するので... 一般に,ループ内での関数呼出は処理速度低下を招く.
ただし今回は,低速版と高速版とを比較したいので, 前者の関数 round() を使い,差を明確化しておきましょう.
また,本来なら関数 Plot() も(さらに,その実体である mvaddch() も) ループ内では使いたくないところだが, 描画できなくなるのも困るので, これは低速版・高速版ともに気にしないでおこう.
関数 Plot() の8回呼出のコストを軽減するには, こんな関数を利用するとよいだろう:
void Plot8(int x0, int y0, int x, int y, char c) { if (!target) return; mvaddch(y0-y, x0-x, c); mvaddch(y0-y, x0+x, c); mvaddch(y0+y, x0-x, c); mvaddch(y0+y, x0+x, c); mvaddch(y0-x, x0-y, c); mvaddch(y0-x, x0+y, c); mvaddch(y0+x, x0-y, c); mvaddch(y0+x, x0+y, c); }
前回紹介したブレゼンハムのアルゴリズムを応用すれば, 直線だけでなく,円についても, 整数のインクリメンタル計算による高速描画が可能である.
円の方程式より次の誤差関数が得られる: (定義式)
この関数の値は:
アルゴリズムについては,Fig.2 の説明図および 前回の高速アルゴリズム (ブレゼンハムの線分生成アルゴリズム) を参考にして考えてみよう.
なお,アルゴリズム中で利用される計算式は次の通り: (漸化式)
これで,元々は2乗を含んでいた誤差関数を, 簡単な整数の足し算とシフト演算( \( 2^n \) 倍)だけで計算できるようになった.
前回同様,コンピュータには,定義式では計算させない. 漸化式で計算させる.
右辺の関数値 \( f \) の部分には, 既に計算済みの変数値を使う. ループ内で関数 \( f \) を定義式通りに何度も再計算してしまうのは非効率.
なお,このアルゴリズムでも,実装上は, \( x_0 = y_0 = 0 \) として計算し, 画素 \( \P({x_0 + x,\ y_0 + y}) \) 等を点灯する方が簡単.
前回作成したプログラム line.cを元にして, ディジタル円を生成するための関数の低速版 SlowDrawCircle() および高速版 FastDrawCircle() を定義し, 実行速度を比較せよ:
void SlowDrawCircle(int x0, int y0, int r, char c) { ... } void FastDrawCircle(int x0, int y0, int r, char c) { ... }
高速版では,無駄な計算を極力排除するよう,工夫すること. 前回の注意事項も参考にしよう.
まずは,円を正しく描画できることを確認すること. その後で速度計測だ.
前回の線分描画では効果を実感できなかったかもしれないが, 今回はかなりの高速化が期待できるだろう.
(レポートに説明があれば加点対象.)
いろいろなアルゴリズムが考えられる:
どれが速い?
多分,1/8領域ではなく1/4領域を単位として, 高速アルゴリズムが考えられる:
どちらも,楕円の輪郭線が途切れて点線にならないようにするための措置ね. 隣接画素が離れてしまわないように注意しよう.
円と三角形の塗り潰しのアルゴリズムについて, 参考資料(2007年度の課題) に説明がある.