图形学Bresenham

1 Bresenham直线

1.1 起始条件

  • 终点坐标

  • 起始点坐标

    通过终点坐标和起始点坐标可以得到直线方程:

KaTeX parse error: Undefined control sequence: \label at position 8: y=kx+b\̲l̲a̲b̲e̲l̲{1}\tag{1}

​ 然后选定主位移方向:
{ X 轴 ∣ k ∣ < 1 Y 轴 ∣ k ∣ > 1 (2) \begin{cases} X\text{轴}& \left|k\right|<1\\\tag{2} Y\text{轴}& \left|k\right|>1\\ \end{cases} { XYk<1k>1(2)

1.2 构造中点误差项

​ 从 P i ( x i , y i ) P_i(\text{x}_i,\text{y}_i) Pi(xi,yi)点走第一步后为了选取下一个像素点,需将 P u P_u Pu P d P_d Pd的中点 M ( x i + 1 , y i + 0.5 ) M(\text{x}_i+1,\text{y}_i+0.5) M(xi+1,yi+0.5)带入直线的隐函数方程,构造中点误差项 d i d_i di
d i = F ( x M , y M ) = F ( x i + 1 , y i + 0.5 ) = y i + 0.5 − k ( x i + 1 ) − b (3) d_i=F(x_{_M},y_{_M})=F(x_i+1,y_i+0.5)=y_i+0.5-k(x_i+1)-b\tag{3} di=F(xM,yM)=F(xi+1,yi+0.5)=yi+0.5k(xi+1)b(3)
​ 根据 d i d_i di判断选择下一个点为 P u P_u Pu还是 P d P_d Pd
y i + 1 = { y i + 1 , d i < 0 y i , d i ⩾ 0 (4) y_{i+1}= \begin{cases} y_{i}+1,& d_i<0\\\tag{4} y_i,& d_i\geqslant0 \end{cases} yi+1={ yi+1,yi,di<0di0(4)

1.3 递推公式☆

1.3.1 中点误差项的递推公式

  1. d i < 0 d_i<0 di<0时,下一步进行判断的中点坐标为 M ( x i + 2 , y i + 1.5 ) M(x_i+2,y_i+1.5) M(xi+2,yi+1.5)。所以下一步中点误差项为:

d i + 1 = F ( x i + 2 , y i + 1.5 ) = y i + 1.5 − k ( x i + 2 ) − b = y i + 0.5 − k ( x i + 1 ) − b + 1 − k = d i + 1 − k (5) \begin{aligned} d_{i+1}&=F(x_i+2,y_i+1.5)=y_i+1.5-k(x_i+2)-b\\ &=y_i+0.5-k(x_i+1)-b+1-k\\ &=d_i+1-k \end{aligned}\tag{5} di+1=Fxi+2,yi+1.5=yi+1.5k(xi+2)b=yi+0.5k(xi+1)b+1k=di+1k(5)

  1. d i ⩾ 0 d_i\geqslant0 di0时,下一步的中点坐标为 M ( x i + 2 , y i + 0.5 ) M(x_i+2,y_i+0.5) M(xi+2,yi+0.5)。所以下一步中点误差项为:
    d i + 1 = F ( x i + 2 , y i + 0.5 ) = y i + 0.5 − k ( x i + 2 ) − b = y i + 0.5 − k ( x i + 1 ) − b − k = d i − k (6) \begin{aligned} d_{i+1}&=F(x_i+2,y_i+0.5)=y_i+0.5-k(x_i+2)-b\\ &=y_i+0.5-k(x_i+1)-b-k\\ &=d_i-k \end{aligned}\tag{6} di+1=Fxi+2,yi+0.5=yi+0.5k(xi+2)b=yi+0.5k(xi+1)bk=dik(6)

1.3.2 中点误差项的初始值

​ 假设以 x x x为主位移方向。因此,第一个中点坐标是 ( x 0 + 1 , y 0 + 0.5 ) (x_0+1,y_0+0.5) (x0+1,y0+0.5),相应的 d i d_i di的初始值为:
d 0 = F ( x 0 + 1 , y 0 + 0.5 ) = y 0 + 0.5 − k ( x 0 + 1 ) − b = y 0 − k x 0 − b − k + 0.5 (7) \begin{aligned} d_0&=F(x_0+1,y_0+0.5)=y_0+0.5-k(x_0+1)-b\\ &=y_0-kx_0-b-k+0.5 \end{aligned}\tag{7} d0=F(x0+1,y0+0.5)=y0+0.5k(x0+1)b=y0kx0bk+0.5(7)
​ 其中,因此 ( x 0 , y 0 ) (x0,y_0) (x0y0)在直线上,所以 y 0 − k x 0 − b = 0 y_0-kx_0-b=0 y0kx0b=0,则

d i = 0.5 − k (8) d_i=0.5-k\tag{8} di=0.5k(8)

1.4 算法流程图

Created with Raphaël 2.3.0 开始 x<=x2 输入x,y di>=0 di = di - k y=y x += 1 输出x,y SetPixel() di = di + 1 - k y += 1; 结束 yes no yes no

1.5 代码

void CMFCApplication1View::DrawLine(CDC* pDC, int x1, int y1, int x2, int y2)
{
	// TODO: 在此处添加实现代码.
	int dx = x2 - x1;
	int dy = y2 - y1;
	int deltaY = 0;
	int middle = dx;
	int y = y1;
	for (int i = x1;i < x2;i++) {
		pDC->SetPixel(i, y, RGB(30, 30, 30));
		deltaY += 2 * dy;
		if (deltaY >= middle) {
			y += 1;
			middle += 2 * dx;
		}
	}
	pDC->SetPixel(x2, y2, RGB(30, 30, 30));
}

1.6

void CMFCApplication1View::DrawLine(int x1, int y1, int x2, int y2, CDC* pDC){

	// TODO: 在此处添加实现代码.
	int dx = x2 - x1;
	int dy = y2 - y1;
	int di = 2*dx - 2*dy;
	int y = y1;
	for (int i = x1; i < x2; i++) {
		pDC->SetPixel(i, y, RGB(30, 30, 30));
		if (di < 0){
			di = di + 2 * (dx - dy);
			y++;
		}
		else{
			di = di - 2 * dy;

		}
	}
}

2 Bresenham圆

​ 圆只需要画 1 8 \frac{1}{8} 81的圆弧就行了,剩下的圆弧可以通过对称得到。

2.1 起始条件

  • 半径R
  • 起始点(x,y)

​ 主位移方向为X轴方向

2.2 构造中点误差项

​ 从 P i ( x i , y i ) P_i(\text{x}_i,\text{y}_i) Pi(xi,yi)点走第一步后为了选取下一个像素点,需将 P u P_u Pu P d P_d Pd的中点 M ( x i + 1 , y i + 0.5 ) M(\text{x}_i+1,\text{y}_i+0.5) M(xi+1,yi+0.5)带入圆的隐函数方程,构造中点误差项 d i d_i di
d i = F ( x M , y M ) = F ( x i + 1 , y i − 0.5 ) = ( x i + 1 ) 2 + ( y i − 0.5 ) 2 − R 2 (9) d_i=F(x_{_M},y_{_M})=F(x_i+1,y_i-0.5)=(x_i+1)^2+(y_i-0.5)^2-R^2\tag{9} di=F(xM,yM)=F(xi+1,yi0.5)=(xi+1)2+(yi0.5)2R2(9)
根据 d i d_i di判断选择下一个点为 P u P_u Pu还是 P d P_d Pd
y i + 1 = { y i , d i < 0 y i − 1 , d i ⩾ 0 (10) y_{i+1}= \begin{cases} y_{i},& d_i<0\\\tag{10} y_i-1,& d_i\geqslant0 \end{cases} yi+1={ yi,yi1,di<0di0(10)

2.3 递推公式☆

2.3.1 中点误差项的递推公式

  1. d i < 0 d_i<0 di<0时,下一步进行判断的中点坐标为 M ( x i + 2 , y i − 0.5 ) M(x_i+2,y_i-0.5) M(xi+2,yi0.5)。所以下一步中点误差项为:

d i + 1 = F ( x i + 2 , y i − 0.5 ) = ( x i + 2 ) 2 + ( y i − 0.5 ) 2 − R 2 = ( x i + 1 ) 2 + ( y i − 0.5 ) 2 − R 2 + 2 x i + 3 = d i + 2 x i + 3 (11) \begin{aligned} d_{i+1}&=F(x_i+2,y_i-0.5)=(x_i+2)^2+(y_i-0.5)^2-R^2\\ &=(x_i+1)^2+(y_i-0.5)^2-R^2+2x_i+3\\ &=d_i+2x_i+3 \end{aligned}\tag{11} di+1=Fxi+2,yi0.5=(xi+2)2+(yi0.5)2R2=(xi+1)2+(yi0.5)2R2+2xi+3=di+2xi+3(11)

  1. d i ⩾ 0 d_i\geqslant0 di0时,下一步的中点坐标为 M ( x i + 2 , y i − 1.5 ) M(x_i+2,y_i-1.5) M(xi+2,yi1.5)。所以下一步中点误差项为:
    d i + 1 = F ( x i + 2 , y i − 1.5 ) = ( x i + 2 ) 2 + ( y i − 1.5 ) 2 − R 2 = ( x i + 1 ) 2 + ( y i − 0.5 ) 2 − R 2 + 2 x i + 3 + ( − 2 y i + 2 ) = d i + 2 ( x i − y i ) + 5 (12) \begin{aligned} d_{i+1}&=F(x_i+2,y_i-1.5)=(x_i+2)^2+(y_i-1.5)^2-R^2\\ &=(x_i+1)^2+(y_i-0.5)^2-R^2+2x_i+3+(-2y_i+2)\\ &=d_i+2(x_i-y_i)+5 \end{aligned}\tag{12} di+1=Fxi+2,yi1.5=(xi+2)2+(yi1.5)2R2=(xi+1)2+(yi0.5)2R2+2xi+3+(2yi+2)=di+2(xiyi)+5(12)

2.3.2 中点误差项的初始值

​ 以 x x x为主位移方向。因此,第一个中点坐标是 ( 1 , R − 0.5 ) (1,R-0.5) (1,R0.5),相应的 d i d_i di的初始值为:
d 0 = F ( 1 , R − 0.5 ) = 1 + ( R − 0.5 ) 2 − R 2 = 1.25 − R (13) \begin{aligned} d_0&=F(1,R-0.5)=1+(R-0.5)^2-R^2=1.25-R \end{aligned}\tag{13} d0=F(1,R0.5)=1+(R0.5)2R2=1.25R(13)

2.4 算法流程图

Created with Raphaël 2.3.0 开始 x<=y 输入x,y SetPixel() di>=0 di = di + 2 * (x - y) + 5 y = y - 1 x += 1 输出x,y di = di + 2 * x + 3 y = y; 结束 yes no yes no

2.5 代码

void CMFCApplication1View::BreCircle(int x0, int y0, int R, CDC* pDC)
{
	// TODO: 在此处添加实现代码.
	int x = x0;
	int y = R;
	float di = 1.25 - R;
	while (x <= y) {
		CirclePoint(pDC, x, y);
		pDC->SetPixel(x, y, RGB(30, 30, 30));
		if (di <= 0) {
			di = di + 2 * x + 3;
			x += 1;
		}
		else {
			di = di + 2 * (x - y) + 5;
			x += 1;
			y -= 1;
		}
	}
}

3 Bresenham椭圆

3.1 起始条件

  • 长半轴a
  • 短半轴b

​ 椭圆画出 1 4 \frac{1}{4} 41的圆弧的就行了,其他三个圆弧可以通过对称得到。但是第一个圆弧的主位移方向会发生变化,需要分情况讨论。我们通过法矢量的两个分量进行讨论。

​ 椭圆上任意一点的处的法矢量:
KaTeX parse error: Undefined control sequence: \symbfit at position 80: …tial{y}}j=2b^2x\̲s̲y̲m̲b̲f̲i̲t̲{i}+2a^2y\symbf…
​ 确定主位移方向:
{ X 轴 , 2 b 2 x ≥ 2 a 2 y Y 轴 , 2 b 2 x < 2 a 2 y (15) \begin{cases} X轴,& 2b^2x≥2a^2y\\ Y轴,& 2b^2x<2a^2y\tag{15} \end{cases} { X,Y,2b2x2a2y2b2x2a2y(15)

3.2 构造中点误差项及递推式☆

3.2.1 上半部分的中点误差项初始值

​ 上半部分椭圆弧的起点坐标为 A ( 0 , b ) A(0,b) A(0,b),因此,第一个中点坐标为 ( 1 , b − 0.5 ) (1,b-0.5) (1,b0.5),对应的 d 1 i d_1i d1i的初始值为:
d 1 i = F ( 1 , b − 0.5 ) = b 2 + a 2 ( − b + 0.25 ) (16) d_1i=F(1,b-0.5)=b^2+a^2(-b+0.25)\tag{16} d1i=F(1,b0.5)=b2+a2(b+0.25)(16)

3.2.2 上半部分中点误差项递推式

KaTeX parse error: Undefined control sequence: \textless at position 100: …(2x_i+3)&d_{1i}\̲t̲e̲x̲t̲l̲e̲s̲s̲0\tag{17} \end{…

y i + 1 = { y i , d i < 0 y i − 1 , d i ⩾ 0 (18) y_{i+1}= \begin{cases} y_{i},& d_i<0\\\tag{18} y_i-1,& d_i\geqslant0 \end{cases} yi+1={ yi,yi1,di<0di0(18)

3.2.1 下半部分的中点误差项初始值

d 2 i = F ( x M , y M ) = b 2 ( x i + 0.5 ) 2 + a 2 ( y i − 1 ) 2 − a 2 b 2 (19) d_{2i}=F(x_{_M},y_{_M})=b^2(x_i+0.5)^2+a^2(y_i-1)^2-a^2b^2\tag{19} d2i=F(xM,yM)=b2(xi+0.5)2+a2(yi1)2a2b2(19)

3.2.2 下半部分中点误差项递推式

KaTeX parse error: Undefined control sequence: \textless at position 101: …-2y_i+3)&d_{2i}\̲t̲e̲x̲t̲l̲e̲s̲s̲0\tag{20} \end{…

y i + 1 = { y i , d i < 0 y i − 1 , d i ⩾ 0 (21) y_{i+1}= \begin{cases} y_{i},& d_i<0\\\tag{21} y_i-1,& d_i\geqslant0 \end{cases} yi+1={ yi,yi1,di<0di0(21)

3.3 算法流程图

yes
yes
no
no
yes
yes
no
no
开始
输入
dy >= dx?
di1<0?
计算下一个di
y=y
x=x+1
计算以一个di
y=y-1
计算dx,dy
描点
y>=0?
di2<0?
计算下一个di
x=x+1
y=y-1
计算下一个di
x=x
描点
结束

3.4 代码

void CMFCApplication1View::BreEllipse(CDC* pDC,int a,int b) {
	int x0 = 0;
	int y0 = b;
	float di1 = b * b + a * a * (-b + 0.25);
	int x = x0;
	int y = y0;
	int dx = 2 * b * b * x;
	int dy = 2 * a * a * y;

	while (dy>=dx)
	{
		if (di1 < 0) {
			di1 = di1 + b * b * (2 * x + 3);
			x = x + 1;
			y = y;
		}
		else {
			di1 = di1 + b * b * (2 * x + 3) + a * a * (2 - 2 * y);
			x = x + 1;
			y = y - 1;
		}
		dx = 2 * b * b * x;
		dy = 2 * a * a * y;
		EllipsePoint(pDC, x, y);
	}
	float di2 = b * b * (x + 0.5) * (x + 0.5) + a * a * (y - 1) * (y - 1) - a * a * b * b;
	while (y>=0) {
		if (di2 < 0) {
			di2 = di2 + b * b * (2 * x + 2) + a * a * (3 - 2 * y);
			x = x + 1;
			y = y - 1;
		}
		else
		{
			di2 = di2 + a * a * (3 - 2 * y);
			x = x;
			y = y - 1;
		}
		EllipsePoint(pDC, x, y);
	}
}

4 Wu反走样算法

4.1 原理

​ 对上下的两个点同时绘制,根据距离设置不同亮度
P u 亮 度 P d 亮 度 = D i s t a n c e P u F i D i s t a n c e P d F i \frac{P_u亮度}{P_d亮度}=\frac{Distance_{_{P_uF_i}}}{Distance_{_{P_dF_i}}} PdPu=DistancePdFiDistancePuFi

4.2 算法流程图

Created with Raphaël 2.3.0 开始 i<x2 delte<=0.5 y=y 上点SetPixel() 下点SetPixel() deltae=deltae+k deltae>1 deltae=deltae-1 y=y+1 上点SetPixel() 下点SetPixel() 结束 yes no yes no yes no

4.3 代码

void CMFCApplication1View::WuLine(CDC *pDC,int x1,int y1,int x2,int y2) {
	float k = (y2 - y1) * 1.0 / (x2 - x1);
	float deltae = k;
	int y = y1;
	pDC->SetPixel(x1, y1, RGB(0, 0, 0));
	for (int i = x1+1;i <= x2-1;i++) {
		if (deltae <= 0.5) {
			y = y;
			pDC->SetPixel(i, y, RGB(deltae * 255, deltae * 255, deltae * 255));
			pDC->SetPixel(i, y+1, RGB((1-deltae) * 255, (1 - deltae) * 255, (1 - deltae) * 255));

		}
		else {
			y = y + 1;
			pDC->SetPixel(i, y, RGB((1 - deltae) * 255, (1 - deltae) * 255, (1 - deltae) * 255));
			pDC->SetPixel(i, y - 1, RGB(deltae * 255, deltae * 255, deltae * 255));
		}
		deltae = deltae + k;
		if (deltae > 1) {
			deltae -= 1;
		}
	}
	pDC->SetPixel(x2, y2, RGB(0, 0, 0));

}

5 重建坐标系

//将坐标系移到中点
CRect rect;
GetClientRect(rect);
pDC->SetMapMode(MM_ANISOTROPIC);
pDC->SetWindowExt(rect.Width(), rect.Height());
pDC->SetViewportExt(rect.Width(), -rect.Height());
pDC->OffsetViewportOrg(rect.Width() / 2, rect.Height() / 2);

GetClientRect(rect);//获取指定窗口的客户区域大小

pDC->SetMapMode(MM_ANISOTROPIC);//设备视图和逻辑视图可以任意改变

pDC->SetWindowExt(rect.Width(), rect.Height());//逻辑范围

pDC->SetViewportExt(rect.Width(), -rect.Height());//设备范围,以及Y轴方向

pDC->OffsetViewportOrg(rect.Width() / 2, rect.Height() / 2);//视图中心偏移

//以物理设置坐标原点为基础(显示作用),以逻辑为单位(逻辑用于按比例设置单位)

5.1 参考资料

SetWindowExt和SetViewportExt

SetWindowOrg,SetViewportOrg,SetWindowExt,SetViewportExt

SetWindowExt和SetViewportExt

逻辑坐标,设备坐标,窗口,视口

关于映射方式MM_ANISOTROPIC的几个函数详解

GetClientRect用法

SetWindowExt() 与SetViewportExt()

mfc编程中SetViewportOrg与SetWindowOrg的理解

逻辑坐标与设备坐标——全窗口坐标、屏幕坐标、客户区坐标的总结

6 如何在MFC中添加自定义函数

  1. 方法一

    在类视图中,选择view,然后添加函数

  2. 方法二

    在view.h的头文件添加声明

    在view.cpp文件中添加函数体定义和调用

的几个函数详解](https://blog.csdn.net/zhandeen/article/details/8223747)

GetClientRect用法

SetWindowExt() 与SetViewportExt()

mfc编程中SetViewportOrg与SetWindowOrg的理解

逻辑坐标与设备坐标——全窗口坐标、屏幕坐标、客户区坐标的总结

6 如何在MFC中添加自定义函数

  1. 方法一

    在类视图中,选择view,然后添加函数

  2. 方法二

    在view.h的头文件添加声明

    在view.cpp文件中添加函数体定义和调用

猜你喜欢

转载自blog.csdn.net/m0_53438035/article/details/124871941