圓弧掃描算法
在平面解析幾何中,圓的方程可以描述為\((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)= 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 < 0\),則取\(P_1\)為下一個點,此時\(P_1\)的下一個點的判別式為:
展開后將\(d\)帶入可得到判別式的遞推關系:
若\(d > 0\),則取\(P_2\)為下一個點,此時\(P_2\)的下一個點的判別式為:
展開后將d帶入可得到判別式的遞推關系:
特別的,在第一個象限的第一個點\((0, R)\)時,可以推倒出判別式\(d\)的初始值\(d_0\):
根據上面的分析,可以寫出中點畫圓法的算法。考慮到圓心不在原點的情況,需要對計算出來的坐標進行了平移,下面就是通用的中點畫圓法的源代碼:
// 參數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()