第二章 二维图元的生成
-
掌握扫描转换直线段的DDA算法、中点算法
- 及中点算法在哪些方面对DDA算法做了改进
-
掌握扫描转换直线段的
Bresenham算法
-
掌握圆弧的八对称性, 扫描转换圆弧的中点算法
-
掌握生成圆弧的多边形迫近法
-
了解正负法,掌握怎样利用正负法生成圆弧
-
掌握扫描转换椭圆弧的中点算法
-
了解线画图元的属性(线型、线宽)控制方法
圆弧的扫描转换
处理对象为圆心在原点的圆弧, 如果不在就使用坐标变换
由于圆的 八对称性
, 只需要扫描转换八分之一圆弧就可以了
void CirclePoints(int x, int y, int color)
{
Putpixel(x, y, color);
Putpixel(y, x, color);
Putpixel(-x, y, color);
Putpixel(y, -x, color);
Putpixel(x, -y, color);
Putpixel(-y, x, color);
Putpixel(-x, -y, color);
Putpixel(-y, -x, color);
}
直接离散的圆弧转换方法
缺点
- 需要开根号/三角函数运算, 计算量大
圆弧的正负划分性
圆弧的隐函数
F ( x , y ) = X 2 + Y 2 − R 2 = 0 F(x,y)=X^2+Y^2-R^2=0 F(x,y)=X2+Y2−R2=0
对于圆内的点F(x,y) < 0
对于圆外的点
F(x,y) > 0
生成圆弧的中点算法
假设, 已知
- 半径
R
- 圆心在原点
只考虑第一象限上侧的 1/8 圆, 则切线的斜率 k
属于 [1, 0]
则点 (xi, yi)
的取整值 (xi, yi')
只有两个取值, 即图中的点 E
或 SE
把中点 M(X_i+1, y_i+1)
带入隐函数
F(M) < 0
, 则M
在圆内, 说明点E
更近, 取E
F(M) > 0
, 则M
在圆外, 取SE
算法步骤
构造判别式
d
=
F
(
M
)
=
F
(
X
i
+
1
,
Y
i
−
0.5
)
=
(
X
i
+
1
)
2
+
(
Y
i
−
0.5
)
2
+
R
2
d=F(M)=F(X_i+1,Y_i-0.5)=(X_i+1)^2+(Y_i-0.5)^2+R^2
d=F(M)=F(Xi+1,Yi−0.5)=(Xi+1)2+(Yi−0.5)2+R2
d < 0
, 取点E
, 下一个像素判别式为
d 1 = F ( X i + 2 , Y i − 0.5 ) = d + 2 X i + 3 d1=F(X_i+2,Y_i-0.5)=d+2X_i+3 d1=F(Xi+2,Yi−0.5)=d+2Xi+3
d1
的增量为
Δ
d
1
=
2
X
i
+
3
\Delta d1=2X_i+3
Δd1=2Xi+3
d >= 0
, 取点SE
, 下一个像素判别式为
d 2 = F ( X i + 2 , Y i − 1.5 ) = d + ( 2 X i + 3 ) + ( − 2 Y i + 2 ) d2=F(X_i+2,Y_i-1.5)=d+(2X_i+3)+(-2Y_i+2) d2=F(Xi+2,Yi−1.5)=d+(2Xi+3)+(−2Yi+2)
d
2 的增量为
Δ
d
2
=
2
(
X
i
−
Y
i
)
+
5
\Delta d2=2(X_i-Y_i)+5
Δd2=2(Xi−Yi)+5
d
的初值为 d0 = F(1,R-0.5) = 1.25-R
, 因为已知第一个像素为 (0,R)
优化浮点数
由于
d
是浮点数, 用e = d-0.25
代替初始值:
e0 = 1-R
判别式:
d < 0
对应e < 0.25
, 但由于初始值e0
和增量都为整数,e
始终为整数,所以e < 0.25
等价于e < 0
void MidPointCircle(int r, int color)
{
int x, y, d;
x = 0;
y = r;
d = 1 - r; // 初值 d = 1 - r
Circlepoints(x, y, color); // 画八分对称性的其他点
while (x <= y) // 画到直线 x = y 为止
{
if (d < 0)
d += 2 * x + 3; // d < 0,取右侧点,d 增
else
{
d += 2 * (x - y) + 5; // d >= 0,取右下点,d 增
y--;
}
x++;
Circlepoints(x, y, color); // 画八分对称性的其他点
}
}
生成圆弧的多边形逼近法
圆的正内接多边形迫近法
假设多边形的第一个顶点
(x0,y0)
位于圆周上的某一点,通常选择(R, 0)
从第一个顶点开始,通过旋转角度
α = 2π/n
,依次计算出后续的顶点坐标这个旋转变换可以看作是围绕原点旋转角度
α
的操作,使用标准的二维旋转矩阵即可:
( x i + 1 y i + 1 ) = ( cos α − sin α sin α cos α ) ( x i y i ) \begin{pmatrix} x_{i+1} \\ y_{i+1} \end{pmatrix}= \begin{pmatrix} \cos \alpha & -\sin \alpha \\ \sin \alpha & \cos \alpha \end{pmatrix} \begin{pmatrix} x_{i} \\ y_{i} \end{pmatrix} (xi+1yi+1)=(cosαsinα−sinαcosα)(xiyi)
因为 α
是个常数, 每个顶点只需要计算 4 次乘法, n边型就需要计算 4n
次乘法, 再加上直线段的中点算法的计算量
问题: 给定最大逼近误差(最大距离) Δ
,如何确定多边形的边数 n
或角度 α
即要求:
d
=
R
−
R
cos
(
α
2
)
≤
Δ
d = R - R \cos\left(\frac{\alpha}{2}\right) \leq \Delta
d=R−Rcos(2α)≤Δ
由此可得:
α
≤
2
arccos
(
R
−
Δ
R
)
\alpha \leq 2 \arccos\left(\frac{R - \Delta}{R}\right)
α≤2arccos(RR−Δ)
边数 n
满足
n ≥ 360 α n \geq\frac{360}{\alpha} n≥α360
圆的等面积正多边形迫近法
有多种方法, 这里介绍一种
- 根据给定的最大逼近误差求出多边形边的圆心角
α
- 根据下图示意, 求出边
OPi
的长度- 旋转变换得到其他顶点
求 OP
的过程
这里为了简化公式, 令
OP = r
, 圆的半径为R
A sector = 1 2 R 2 α = A triangle = 1 2 r 2 ⋅ sin ( α ) A_{\text{sector}} = \frac{1}{2} R^2 \alpha= A_{\text{triangle}} = \frac{1}{2} r^2 \cdot \sin(\alpha) Asector=21R2α=Atriangle=21r2⋅sin(α)r = R α sin ( α ) r = R \sqrt{\frac{\alpha}{\sin(\alpha)}} r=Rsin(α)α
椭圆的扫描转换(生成椭圆的中点算法)
类似于圆弧的扫描转换, 但由于椭圆只具有四对称性, 所以考虑将第一象限的圆弧分为上下两部分分别绘制, 以 k = -1
为分界线
椭圆的隐函数
F ( x , y ) = b 2 x 2 + a 2 y 2 − a 2 b 2 = 0 F(x,y)=b^2x^2+a^2y^2-a^2b^2=0 F(x,y)=b2x2+a2y2−a2b2=0
对于椭圆内的点F(x,y) < 0
对于椭圆外的点
F(x,y) > 0
椭圆上一点处的法向如下公式, 可以用椭圆的隐函数求偏导推出, 这里不做详细解释
N
(
x
,
y
)
=
2
b
2
x
i
+
2
a
2
y
i
N(x,y)=2b^2x_i+2a^2y_i
N(x,y)=2b2xi+2a2yi
如何判断从上段进入下段
在上半部分, 法向量的
x分量 < y分量
, 而下半部分则相反因此, 当不等式改变方向时, 椭圆就从上段进入了下段
算法步骤
上段部分
因为斜率在 [-1,0]
, 所以求 x
递增 1 时, y
递减多少
当前像素的下一个像素可能是 右
点 或 右下
点
构造判别式
d
=
F
(
x
i
+
1
,
y
i
−
0.5
)
=
b
2
(
x
i
+
1
)
2
+
a
2
(
y
i
−
0.5
)
2
−
a
2
b
2
d=F(x_i+1,y_i-0.5)= b^2(x_i+1)^2+a^2(y_i-0.5)^2-a^2b^2
d=F(xi+1,yi−0.5)=b2(xi+1)2+a2(yi−0.5)2−a2b2
d < 0
, 取右点, 下一个像素判别式为
d 1 = F ( X i + 2 , Y i − 0.5 ) = d + b 2 ( 2 X i + 3 ) d1=F(X_i+2,Y_i-0.5)=d+b^2(2X_i+3) d1=F(Xi+2,Yi−0.5)=d+b2(2Xi+3)
d1
的增量为
Δ
d
1
=
b
2
(
2
X
i
+
3
)
\Delta d1=b^2(2X_i+3)
Δd1=b2(2Xi+3)
d >= 0
, 取右下点, 下一个像素判别式为
d 2 = F ( X i + 2 , Y i − 1.5 ) = d + b 2 ( 2 X i + 3 ) + a 2 ( − 2 Y i + 2 ) d2=F(X_i+2,Y_i-1.5)=d+b^2(2X_i+3)+a^2(-2Y_i+2) d2=F(Xi+2,Yi−1.5)=d+b2(2Xi+3)+a2(−2Yi+2)
d
2 的增量为
Δ
d
2
=
b
2
(
2
X
i
+
3
)
+
a
2
(
−
2
Y
i
+
2
)
\Delta d2=b^2(2X_i+3)+a^2(-2Y_i+2)
Δd2=b2(2Xi+3)+a2(−2Yi+2)
d
的初值为
d
0
=
F
(
1
,
b
−
0.5
)
=
b
∗
b
+
a
∗
a
(
−
b
+
0.25
)
d0 = F(1,b-0.5) = b*b+a*a(-b+0.25)
d0=F(1,b−0.5)=b∗b+a∗a(−b+0.25)
下段部分
因为斜率在 [-∞,-1]
, 所以求 y
递减 1 时, x
递增多少
当前像素的下一个像素可能是 下
点 或 右下
点
构造判别式, 为防止混淆, 这里使用 e
e
=
F
(
x
i
+
0.5
,
y
i
−
1
)
=
b
2
(
x
i
+
0.5
)
2
+
a
2
(
y
i
−
1
)
2
−
a
2
b
2
e=F(x_i+0.5,y_i-1)= b^2(x_i+0.5)^2+a^2(y_i-1)^2-a^2b^2
e=F(xi+0.5,yi−1)=b2(xi+0.5)2+a2(yi−1)2−a2b2
e >= 0
, 中点在椭圆弧外, 取下点, 下一个像素判别式为
e 1 = F ( X i + 0.5 , Y i − 2 ) = d + a 2 ( − 2 Y i + 3 ) e1=F(X_i+0.5,Y_i-2)=d+a^2(-2Y_i+3) e1=F(Xi+0.5,Yi−2)=d+a2(−2Yi+3)
e1
的增量为
Δ
e
1
=
a
2
(
−
2
y
i
+
3
)
\Delta e1=a^2(-2y_i+3)
Δe1=a2(−2yi+3)
e < 0
, 中点在椭圆弧内, 取右下点, 下一个像素判别式为
e 2 = F ( X i + 1.5 , Y i − 2 ) = d + b 2 ( 2 X i + 2 ) + a 2 ( − 2 Y i + 3 ) e2=F(X_i+1.5,Y_i-2)=d+b^2(2X_i+2)+a^2(-2Y_i+3) e2=F(Xi+1.5,Yi−2)=d+b2(2Xi+2)+a2(−2Yi+3)
e
2 的增量为
Δ
e
2
=
b
2
(
2
X
i
+
2
)
+
a
2
(
−
2
Y
i
+
3
)
\Delta e2=b^2(2X_i+2)+a^2(-2Y_i+3)
Δe2=b2(2Xi+2)+a2(−2Yi+3)
e
的初值为
e
0
=
F
(
x
p
+
0.5
,
y
p
−
1
)
,
其中
(
x
p
,
y
p
)
是上半段结束时的坐标
e0 = F(x_p+0.5,y_p-1), 其中(x_p,y_p)是上半段结束时的坐标
e0=F(xp+0.5,yp−1),其中(xp,yp)是上半段结束时的坐标
当 y = 0
时, 算法终止
void MidpointEllipse(int a, int b, int color)
{
int x, y;
float d1, d2;
x = 0;
y = b;
d1 = b * b + a * a * (-b + 0.25); // d1初值
putpixel(x, y, color);
// 上半部分
while (b * b * (x + 1) < a * a * (y - 0.5)) // 直到上下分界点
{
if (d1 < 0) {
d1 += b * b * (2 * x + 3); // 取右点, d1增
x++;
} else {
d1 += (b * b * (2 * x + 3) + a * a * (-2 * y + 2)); // 取右下点,d1增
x++;
y--;
}
EllipsePoints(x, y, color); // 四对称画点
}
// 下半部分
d2 = b * b * (x + 0.5) * (x + 0.5) + a * a * (y - 1) * (y - 1) - a * a * b * b; // d2初值
while (y > 0) // 直到x坐标轴
{
if (d2 < 0) {
d2 += b * b * (2 * x + 2) + a * a * (-2 * y + 3); // 取右下点
x++;
y--;
} else {
d2 += a * a * (-2 * y + 3); // 取下点
y--;
}
EllipsePoints(x, y, color);
}
}