圓弧掃描算法


圓弧掃描算法

在平面解析幾何中,圓的方程可以描述為\((x – x_0)^2 + (y – y_0)^2 = R^2\),其中\((x_0, y_0)\)是圓心坐標,\(R\)是圓的半徑,特別的,當\((x_0, y_0)\)就是坐標中心點時,圓方程可以簡化為\(x^2 + y^2 = R^2\)。在計算機圖形學中,圓和直線一樣,也存在在點陣輸出設備上顯示或輸出的問題,因此也需要一套光柵掃描轉換算法。為了簡化,我們先考慮圓心在原點的圓的生成,對於中心不是原點的圓,可以通過坐標的平移變換獲得相應位置的圓。

在進行掃描轉換之前,需要了解一個圓的特性,就是圓的八分對成性。如圖(1)所示:

圖(1)圓的八分對成性

圓心位於原點的圓有四條對稱軸\(x = 0\)\(y = 0\)\(x = y\)\(x = -y\),若已知圓弧上一點\(P(x,y)\),就可以得到其關於四條對稱軸的七個對稱點:\((x, -y)\)\((-x, y)\)\((-x, -y)\)\((y, x)\)\((y, -x)\)\((-y, x)\)\((-y, -x)\),這種性質稱為八分對稱性。因此只要能畫出八分之一的圓弧,就可以利用對稱性的原理得到整個圓。

中點畫圓法

原理

考慮圓心在原點,半徑為\(R\)的圓在第一象限內的八分之一圓弧,從點\((0, R)\)到點\((R' , R')\)順時針方向確定這段圓弧。假定某點\(P_i(x_i, y_i)\)已經是該圓弧上最接近實際圓弧的點,那么\(P_i\)的下一個點只可能是正右方\(P_1\)右下方\(P_2\)兩者之一,如圖(2)所示:

圖(2)中點划線法示例

構造判別函數:

\[F(x,y)=x^2+y^2-R^2 \]

\(F(x, y)= 0\),表示點在圓上,當\(F(x, y)> 0\),表示點在圓外,當\(F(x, y)< 0\),表示點在圓內。如果\(M\)\(P_1\)\(P_2\)的中點,則\(M\)的坐標是\((x_i + 1, y_i – 0.5)\),當\(F(x_i + 1, y_i – 0.5)< 0\)時,\(M\)在圓內,說明\(P_1\)點離實際圓弧更近,應該\(P_1\)作為圓的下一個點。同理分析,當\(F(x_i + 1, y_i – 0.5)> 0\)時,\(P_2\)離實際圓弧更近,應取\(P2\)作為下一個點。當\(F(x_i + 1, y_i – 0.5)= 0\)時,\(P_1\)\(P_2\)都可以作為圓的下一個點,算法約定取\(P_2\)作為下一個點。

現在將\(M\)點坐標\((x_i + 1, y_i – 0.5)\)帶入判別函數\(F(x, y)\),得到判別式\(d\)

\[d = F(x_i + 1, y_i – 0.5)= (x_i + 1)^2 + (y_i – 0.5)^2 – R^2 \]

\(d < 0\),則取\(P_1\)為下一個點,此時\(P_1\)的下一個點的判別式為:

\[d’ = F(x_i + 2, y_i – 0.5)= (x_i + 2)^2 + (y_i – 0.5)^2 – R^2 \]

展開后將\(d\)帶入可得到判別式的遞推關系

\[d’ = d + 2x_i + 3 \]

\(d > 0\),則取\(P_2\)為下一個點,此時\(P_2\)的下一個點的判別式為:

\[d’ = F(x_i + 2, y_i – 1.5)= (x_i + 2)^2 + (y_i – 1.5)^2 – R^2 \]

展開后將d帶入可得到判別式的遞推關系:

\[d’ = d + 2(x_i - y_i) + 5 \]

特別的,在第一個象限的第一個點\((0, R)\)時,可以推倒出判別式\(d\)的初始值\(d_0\)

\[d_0 = F(1, R – 0.5) = 1 – (R – 0.5)^2 – R^2 = 1.25 - R \]

根據上面的分析,可以寫出中點畫圓法的算法。考慮到圓心不在原點的情況,需要對計算出來的坐標進行了平移,下面就是通用的中點畫圓法的源代碼:

// 參數xc和yc是圓心坐標,r是半徑,CirclePlot()函數是參照圓的八分對稱性完成八個點的位置計算的輔助函數。
void MP_Circle(int xc , int yc , int r) {
	int x, y;
	double d;
	x = 0;
	y = r;
	d = 1.25 - r;
	CirclePlot(xc , yc , x , y);
	while(x < y) {
		if(d < 0) {
			d = d + 2 * x + 3;
		} else {
			d = d + 2 * ( x - y ) + 5;
			y--;
		}
		x++;
		CirclePlot(xc , yc , x , y);
	}
}

代碼實現

#include<stdio.h>
#include<graphics.h>
#include<conio.h>
#define x0 400
#define y0 300                    //定義全局變量x0,y0:坐標軸中心(x0,y0)
void Middle_point_draw_circle(int x1, int y1, int r) {
	int d0, x = 0, y = r;//d0是判別式的值
	d0 = 1.25 - r;   //判別式的初始值,1.25可以改為1
	while (x < y) {
		if (d0 >= 0) {
			d0 = d0 + 2 * (x - y) + 5;            //d0一定要先比x,y更新
			x += 1;                //因為d0表達式中的x,y是上一個點
			y -= 1;
			putpixel(((x + x1) + x0), (y0 - (y + y1)), RED);         //(x,y)
			putpixel(((-x + x1) + x0), (y0 - (y + y1)), RED);        //(-x,y)
			putpixel(((y + x1) + x0), (y0 - (x + y1)), RED);         //(y,x)
			putpixel(((-y + x1) + x0), (y0 - (x + y1)), RED);        //(-y,x)
			putpixel(((x + x1) + x0), (y0 - (-y + y1)), RED);        //(x,-y)
			putpixel(((-x + x1) + x0), (y0 - (-y + y1)), RED);       //(-x,-y)
			putpixel(((y + x1) + x0), (y0 - (-x + y1)), RED);        //(y,-y)
			putpixel(((-y + x1) + x0), (y0 - (-x + y1)), RED);       //(-y,-x)
			Sleep(50);
		} else {
			d0 = d0 + 2 * x + 3;
			x += 1;
			y = y;
			putpixel(((x + x1) + x0), (y0 - (y + y1)), RED);         //(x,y)
			putpixel(((-x + x1) + x0), (y0 - (y + y1)), RED);        //(-x,y)
			putpixel(((y + x1) + x0), (y0 - (x + y1)), RED);         //(y,x)
			putpixel(((-y + x1) + x0), (y0 - (x + y1)), RED);        //(-y,x)
			putpixel(((x + x1) + x0), (y0 - (-y + y1)), RED);        //(x,-y)
			putpixel(((-x + x1) + x0), (y0 - (-y + y1)), RED);       //(-x,-y)
			putpixel(((y + x1) + x0), (y0 - (-x + y1)), RED);        //(y,-y)
			putpixel(((-y + x1) + x0), (y0 - (-x + y1)), RED);       //(-y,-x)
			Sleep(50);
		}
	}
}
int main() {
	int x1, y1, r;
	printf("請輸入中點畫圓算法圓心坐標(x1,y1)和圓的半徑r:\n");
	scanf("%d %d %d", &x1, &y1, &r);
	initgraph(x0 * 2, y0 * 2);		    //初始化圖形窗口大小
	setbkcolor(WHITE);
	cleardevice();
	setcolor(BLACK);
	line(x0, 0, x0, y0 * 2);			//坐標軸X
	line(0, y0, x0 * 2, y0);			 //坐標軸Y
	Middle_point_draw_circle(x1, y1, r);             //中點畫圓算法
	getch();                                        //等待一個任意輸入結束
	closegraph();                                    //關閉圖形窗口
}

改進的中點畫圓法——Bresenham算法

中點畫圓法中,計算判別式\(d\)使用了浮點運算,影響了圓的生成效率。如果能將判別式規約到整數運算,則可以簡化計算,提高效率。於是人們針對中點畫圓法進行了多種改進,其中一種方式是將\(d\)的初始值由\(1.25 – R\)改成\(1 – R\),考慮到圓的半徑\(R\)總是大於\(2\),因此這個修改不會影響d的初始值的符號,同時可以避免浮點運算。還有一種方法是將\(d\)的計算放大兩倍,同時將初始值改成\(3 – 2R\),這樣避免了浮點運算,乘二運算也可以用移位快速代替,采用\(3 – 2R\)為初始值的改進算法,又稱為Bresenham算法

void Bresenham_Circle(int xc , int yc , int r) {
	int x, y, d;
	x = 0;
	y = r;
	d = 3 - 2 * r;
	CirclcPlot(xc , yc , x , y);
	while(x < y) {
		if(d < 0) {
			d = d + 4 * x + 6;
		} else {
			d = d + 4 * ( x - y ) + 10;
			y--;
		}
		x++;
		CirclePlot(xc , yc , x , y);
	}
}

代碼實現

# Bresenham畫圓
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
import random as rd
# ==========================================
# 圓的基本信息
# 1.圓半徑
r = 6.0
# 2.圓心坐標
a, b = (0., 0.)
Ary = []
Aryd = []
# ==========================================
# Bresenham計算點的方法
# 畫點方向 只有右下L點或者正右H點
# 計算D(H) D(L) 計算公式為D(p)=px*px+py*py-r*r
# 計算di=D(H)+D(L) di>0則畫L,di<=0則畫H
# 考慮圓的對稱性根據L點坐標依次畫出(x,y),(-x,y),(-x,-y),(x,-y),(y,x),(-y,x),(-y,-x),(y,-x)八個點
# 起止位置(0,r)到(r-1,y),因為終點沒有好的方法可以判斷到底划分到那里
# 為了適合大多數半徑還是使用上面的終點,雖然會多畫幾個點,不過消耗很少
# def init():
# plt.set_xlim(Ary[


def getPoint():
    R = int(r)
    st = [0, R]
    Ary.append(st)
    plt.scatter(0, R, color='b')
    plt.scatter(0, -R, color='b')
    plt.scatter(-R, 0, color='b')
    plt.scatter(R, 0, color='b')
    for i in range(0, R-1):
        H = [st[0]+1, st[1]]
        L = [st[0]+1, st[1]-1]
        DH = H[0]*H[0]+H[1]*H[1]-R*R
        DL = L[0]*L[0]+L[1]*L[1]-R*R
        di = DH+DL
        Aryd.append(di)
        if(di > 0):
            H = L
        st = H
        Ary.append(st)
        DrawPoint(st[0], st[1], 'b')


def DrawPoint(x, y, cr):
    plt.scatter(x, y, color=cr)
    plt.scatter(-x, y, color=cr)
    plt.scatter(-x, -y, color=cr)
    plt.scatter(x, -y, color=cr)
    plt.scatter(y, x, color=cr)
    plt.scatter(-y, x, color=cr)
    plt.scatter(-y, -x, color=cr)
    plt.scatter(y, -x, color=cr)


def setAxis():
    lent = range(-15, 15, 1)
    plt.xticks(lent)
    plt.yticks(lent)
    plt.plot([-18, 18], [0, 0], 'k')
    plt.plot([0, 0], [-18, 18], 'k')
# 參數方程畫圓


def drawCle():
    theta = np.arange(0, 2*np.pi, 0.01)
    x = a + r * np.cos(theta)
    y = b + r * np.sin(theta)
    plt.plot(x, y, 'r')
    plt.axis('equal')

# plt.show()


if __name__ == "__main__":
    # r=float(input("r:"))
    r = int(rd.uniform(3, 15)+0.5)
# r=8
    setAxis()
    getPoint()
    drawCle()
    print(Ary)
    print(Aryd)
    plt.show()


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM