为了方便阅读算法代码的人,现在这贴上算法核心代码:
算法过程:
注意:本过程只针对,斜率绝对值小于1的情况。
void DDADrawLine::MPDrawLine(int x0, int y0, int x1, int y1)
{
int a, b, dt1, dt2, d, x, y;
a = y0 - y1;
b = x1 - x0;
d = a + a + b; //为了避免小数,这里取2倍 不写成2*a + b的原因是防止乘法
dt1 = a + b + a + b;
dt2 = a + a;
x = x1;
y = y1;
// 绘制起点
// glColor3f(1.0f, 0.0f, 0.0f);
glBegin(GL_POINTS);
glVertex2i(x, y);
// glEnd();
// 绘制整条线
while (x < x1)
{
if (d < 0)
{
x++;
y++;
d += dt1;
}
else
{
x++;
d += dt2;
}
glBegin(GL_POINTS);
glVertex2i(x, y);
glEnd();
}
}
如果针对全部斜率的情况,该算法改进结果如下:
void DDADrawLine::MPLineDraw(int x0, int y0, int x1, int y1)
{
int x = x0, y = y0;
int a = y0 - y1;
int b = x1 - x0;
int cx = (b >= 0 ? 1 : (b = -b, -1));
int cy = (a <= 0 ? 1 : (a = -a, -1));
// glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0f, 0.0f, 0.0f);
glBegin(GL_POINTS);
glVertex2i(x, y);
glEnd();
int d, d1, d2;
if (-a <= b) // 斜率绝对值 <= 1
{
d = a + a + b;
d1 = a + a;
d2 = a + a + b + b;
while (x != x1)
{
if (d < 0)
{
y += cy;
d += d2;
}
else
{
d += d1;
}
x += cx;
glBegin(GL_POINTS);
glVertex2i(x, y);
glEnd();
}
}
else // 斜率绝对值 > 1
{
d = a + b + b;
d1 = b + b;
d2 = a + a + b + b;
if (d < 0)
{
d += d1;
}
else
{
x += cx;
d += d2;
}
y += cy;
glBegin(GL_POINTS);
glVertex2i(x, y);
glEnd();
}
}
中点画线算法采用直线的一般式方程:
F ( x , y ) = 0 A x + B y + C = 0 A = − ( Δ y ) ; B = ( Δ x ) ; C = − B ( Δ x ) \begin {aligned} F(x, y) = 0 \\Ax + By + C = 0 \\A = -(\Delta y); B = (\Delta x); C = -B(\Delta x) \end {aligned} F(x,y)=0Ax+By+C=0A=−(Δy);B=(Δx);C=−B(Δx)
对于不同的点,我们可以利用其值和0进行比较来筛选。
所以,中点画线算法在每次最大位移方向上走一步,而另外一个方向是走步还是不走,需要利用中点误差项来进行判断。
我们假定: 0 ≤ ∣ k ∣ ≤ 1 0\leq|k|\leq1 0≤∣k∣≤1。 因此,每次x方向上加1, y 方向上是否加1取决于函数值与中点方位。
当M在Q的下方,则 P u P_u Pu离直线比较近,应为下一个像素点。
当M在Q的上方,则 P d P_d Pd为下一点。
那么我们如何判断M是在Q的上方还是下方呢?
我们就需要把M代入到理想直线方程中:
F ( x m , y m ) = A x m + B y m + C F(x_m, y_m) = Ax_m + By_m + C F(xm,ym)=Axm+Bym+C
KaTeX parse error: Expected & or \\ or \cr or \end at position 40: … F(x_m, y_m) \\\̲ ̲&=F(x_i + 1, y_…
当 d > 0的时候, M在Q的上方,取 P u P_u Pu
当 d < 0的时候, M在Q的下方,取 P d P_d Pd
当 d = 0的时候, 取 P d P_d Pd取 P u P_u Pu都可以
那么我们可以得到中点画线法的基本原理,公式如下:
y = { y + 1 d < 0 y d ≥ 0 y = \begin {cases} y + 1 & d < 0 \\ y & d\geq0 \end {cases} y={y+1yd<0d≥0
我们来分析一下该算法的计算量
y = { y + 1 d < 0 y d ≥ 0 d i = A ( x i + 1 ) + B ( y i + 0.5 ) + C y = \begin {cases} y + 1 & d < 0 \\ y & d\geq0 \end {cases} \\ d_i = A(x_i + 1) + B(y_i + 0.5) + C y={y+1yd<0d≥0di=A(xi+1)+B(yi+0.5)+C
我们发现,为了求出d的值,需要进行两个乘法,四个加法。似乎比DDA算法大了很多的计算量,那么我们该如何改进呢?
我们将利用求d的递推公式,将计算量简化为一个整数加法级别。
对于上面这种情况:
KaTeX parse error: Expected & or \\ or \cr or \end at position 49: …}, y_{m_0}) \\\̲ ̲&=F(x_i + 1, y_…
KaTeX parse error: Expected & or \\ or \cr or \end at position 49: …}, y_{m_1}) \\\̲ ̲&=F(x_i + 2, y_…
对于上面这种情况:
KaTeX parse error: Expected & or \\ or \cr or \end at position 49: …}, y_{m_1}) \\\̲ ̲&=F(x_i + 2, y_…
接下来我们计算d的初始值:
KaTeX parse error: Expected & or \\ or \cr or \end at position 52: …y——0 + 0.5) \\\̲ ̲&= A(x_0 + 1) +…
那么,可以得到:
d n e w = { d o l d + A + B d < 0 d o l d + A d ≥ 0 d 0 = A + 0.5 B d_{new} = \begin {cases} d_{old} + A + B & d < 0 \\ d_{old} + A & d \geq0 &d_0 = A + 0.5B \end {cases} dnew={dold+A+Bdold+Ad<0d≥0d0=A+0.5B
之后再利用2d代替d来摆脱浮点运算,写出仅仅包含整数的算法,这样中点画线算法就提高到了整数加法,优于DDA算法。
接下来来看中点画线算法的代码实现:
图形初始化过程:
// 实现DDA画线算法测试
// author: 赵天宇
// date : 2018/03/09
void DDADrawLine::MPTest(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
using namespace std;
// cout << "输入线段起始和终点坐标(范围 0 - 500, 0 - 500):";
// cin >> xs >> ys >> xe >> ye;
srand((unsigned)time(NULL));
xs = random(0, 500);
ys = random(0, 500);
xe = random(0, 500);
ye = random(0, 500);
glutInitWindowPosition(50, 100);
glutInitWindowSize(500, 500);
glutCreateWindow("Digital Differential Analyser Line");
glClearColor(1.0, 1.0, 1.0, 1.0);
glMatrixMode(GL_PROJECTION);
gluOrtho2D(0.0, 500, 0.0, 500.0);
glutDisplayFunc(DDADrawLine::display);
DDADrawLine::myInit();
// DDADrawLine::MPDrawLine(xs, ye, xe, ys);
glutMainLoop();
}
显示过程:
void DDADrawLine::display(void)
{
glClear(GL_COLOR_BUFFER_BIT); /*clear the window */
/*----------------------------------------*/
/* viewport stuff */
/*----------------------------------------*/
/* set up a viewport in the screen window */
/* args to glViewport are left, bottom, width, height */
glViewport(0, 0, 500, 500);
/* NB: default viewport has same coords as in myinit, */
/* so this could be omitted: */
// DDADrawLine::DDALine(xs, ys, xe, ye);
DDADrawLine::MPDrawLine(xs, ys, xe, ye);
/* and flush that buffer to the screen */
glFlush();
}
算法过程:
注意:本过程只针对,斜率绝对值小于1的情况。
void DDADrawLine::MPDrawLine(int x0, int y0, int x1, int y1)
{
int a, b, dt1, dt2, d, x, y;
a = y0 - y1;
b = x1 - x0;
d = a + a + b; //为了避免小数,这里取2倍 不写成2*a + b的原因是防止乘法
dt1 = a + b + a + b;
dt2 = a + a;
x = x1;
y = y1;
// 绘制起点
// glColor3f(1.0f, 0.0f, 0.0f);
glBegin(GL_POINTS);
glVertex2i(x, y);
// glEnd();
// 绘制整条线
while (x < x1)
{
if (d < 0)
{
x++;
y++;
d += dt1;
}
else
{
x++;
d += dt2;
}
glBegin(GL_POINTS);
glVertex2i(x, y);
glEnd();
}
}
如果针对全部斜率的情况,该算法改进结果如下:
void DDADrawLine::MPLineDraw(int x0, int y0, int x1, int y1)
{
int x = x0, y = y0;
int a = y0 - y1;
int b = x1 - x0;
int cx = (b >= 0 ? 1 : (b = -b, -1));
int cy = (a <= 0 ? 1 : (a = -a, -1));
// glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0f, 0.0f, 0.0f);
glBegin(GL_POINTS);
glVertex2i(x, y);
glEnd();
int d, d1, d2;
if (-a <= b) // 斜率绝对值 <= 1
{
d = a + a + b;
d1 = a + a;
d2 = a + a + b + b;
while (x != x1)
{
if (d < 0)
{
y += cy;
d += d2;
}
else
{
d += d1;
}
x += cx;
glBegin(GL_POINTS);
glVertex2i(x, y);
glEnd();
}
}
else // 斜率绝对值 > 1
{
d = a + b + b;
d1 = b + b;
d2 = a + a + b + b;
if (d < 0)
{
d += d1;
}
else
{
x += cx;
d += d2;
}
y += cy;
glBegin(GL_POINTS);
glVertex2i(x, y);
glEnd();
}
}
运行结果:
参考文献:
【1】 中国大学MOOC 中国农业大学 计算机图形学课程 计算机图形学
【2】 画线算法博客 - 只缘心高嫌地窄 画线算法
【3】 中点画线法(计算机图形学)- 时光足迹 中点画线法
声明:本文的主要内容来自[1], 如有违权将立即删除。