1、DDA直线段生成绘制算法
DDA算法又称数值微分画线法。
根据直线方程y=m*x+b和微分方程dy=m*dx可知:
1)假设(x[i],y[i])是第i步计算生成像素,则第i+1步生成像素(x[i+1],y[i+1])为:x[i+1]=x[i]+dx,y[i+1]=y[i]+m*dx;
2)由于屏空间像素间最小位差|dx|、|dy|<=1,所以,步数steps=Max(|x2-x1|,|y2-y1|)
3)由步数可得每步x、y的增量:dx=(x2-x1)/steps;dy=(y2-y1)/steps;
4)第一个点取(x1,y1),即:x=x1,y=y1一定在直线上;
5)每一步增加一个增量:x=x+dx;y=y+dy,不是整数(即不在直线上的点)四舍五入转换为整数(int)(x+0.5)
void CcgDrawLineView::DDAline(int x1, int y1, int x2, int y2, CDC* pDC)
{
int steps;
float m,x,y,xp,yp,dx,dy,averageError;
CcgDrawLineDoc* pDoc = GetDocument();
x = x1 + 0.5f;//画点的时候四舍五入
y = y1 + 0.5f;
averageError = 0.0f;
steps = abs(x2-x1) > abs(y2-y1) ? abs(x2-x1) : abs(y2-y1);
dx = (float) (x2 - x1) / steps;
dy = (float) (y2 - y1) / steps;
m = (float) (y2 - y1) / (float) (x2 - x1);
for (int i = 0; i <= steps; i++) {
pDC->SetPixel((int)x+pDoc->m_wndWidth/2, (int)pDoc->m_wndHeight/2-y, RGB(255, 0, 0));
// 计算生成点到直线距离
xp = (y-y1+x/m+m*x1)/(m+1/m);
yp = -1/m*(xp-x)+y;
averageError +=sqrtf((x-xp)*(x-xp)+(y-yp)*(y-yp));
x += dx;
y += dy;
}
pDoc->m_DDALineAverageError += averageError/steps;
// 计算终点与直线终点误差
pDoc->m_DDALineEndPointError += sqrtf((x-x2)*(x-x2)+(y-y2)*(y-y2));
}
2、Bresenham直线段生成绘制算法
如果x1大于x2,则交换(x1,y1)和(x2,y2)的坐标,使x1始终小于x2。即将八种情况分成四种:
1)斜率>1; 2)0<斜率<1; 3)-1<斜率<0; 4)斜率<-1
1):斜率大于1时,每次描点y一定加一,通过计算(x,y+1)到直线的距离是否小于二分之一来判断x是加一还是不变。
2):0<斜率<1时,每次描点x一定加一,通过计算(x+1,y)到直线的距离是否小于二分之一来判断y是加一还是不变。
3):-1<斜率<0时,每次描点x一定加一,通过计算(x+1,y)到直线的距离是否小于二分之一来判断y是减一还是不变。
4):斜率<-1时,每次描点y一定减一,通过计算(x,y+1)到直线的距离是否小于二分之一来判断x是加一还是不变。
按照这种思想就有如下代码:
void CcgWXXDrawLineView::Bline(int x1, int y1, int x2, int y2, CDC* pDC)
{
int x, y;
if (x1 > x2)//令x2大于x1,即x只有+1,没有-1
{
x = x1;
x1 = x2;
x2 = x;
y = y1;
y1 = y2;
y2 = y;
}
int dx = x2 - x1, dy = y2 - y1;
float m = dy*1.0 / dx;
CcgWXXDrawLineDoc *pDoc = GetDocument(); // 获取Doc类指针
if (m>0&&m<1)//x++,y++或不变
{
int e = 2 * dy - dx;
x = x1;
y = y1;
while (x<x2)
{
pDC->SetPixel(x + pDoc->wm_width / 2, y + pDoc->wm_height/2, RGB(0, 255, 0));
if (e >= 0)
{
e = e - 2 * dx;
y = y + 1;
}
e = e + 2 * dy;
x = x + 1;
}
pDC->SetPixel(x + pDoc->wm_width / 2, y + pDoc->wm_height/2, RGB(0, 255, 0));
}
else if (m>-1&&m<0)//x++,y--或不变
{
int e = 2 * dy +dx;
x = x1;
y = y1;
while (x<x2)
{
pDC->SetPixel(x + pDoc->wm_width / 2, y + pDoc->wm_height/2, RGB(0, 255, 0));
if (e <= 0)
{
e = e + 2 * dx;
y = y - 1;
}
e = e + 2 * dy;
x = x + 1;
}
pDC->SetPixel(x + pDoc->wm_width / 2, y + pDoc->wm_height / 2, RGB(0, 255, 0));
}
else if (m > 1)//y++,x++或不变
{
int e = 2 * dx - dy;
x = x1;
y = y1;
while (y<y2)
{
pDC->SetPixel(x + pDoc->wm_width / 2, y + pDoc->wm_height/2, RGB(0, 255, 0));
if (e >= 0)
{
e = e - 2 * dy;
x = x + 1;
}
e = e + 2 * dx;
y = y + 1;
}
pDC->SetPixel(x + pDoc->wm_width / 2, y + pDoc->wm_height / 2, RGB(0, 255, 0));
}
else if(m<-1)//y--,x++或不变
{
int e = 2 * dx - dy;
x = x1;
y = y1;
while (y>y2)
{
pDC->SetPixel(x + pDoc->wm_width / 2, y + pDoc->wm_height / 2, RGB(0, 255, 0));
if (e >= 0)
{
e = e + dy; //理论上,这里应该是e=e+2*dy,可是e=e+2*dy画出来的线不对,e = e + dy才对,不明白为什么。大神们有空的话帮忙看看吧
x = x + 1;
}
e = e + dx;
y = y - 1;
}
pDC->SetPixel(x + pDoc->wm_width / 2, y + pDoc->wm_height / 2, RGB(0, 255, 0));
}
}
这样用把各种条件一一列出来太low了,能不能把各种可能再整合一下呢?将上述思想归纳一下,即有下述函数:
void CcgDrawLineView::Bline(int x1, int y1, int x2, int y2, CDC* pDC)
{
float m,xp,yp,averageError;
CcgDrawLineDoc* pDoc = GetDocument();
int x,y,dx,dy,e,xSign,ySign,interChange = 0;
averageError = 0.0f;
m = (float) (y2 - y1) / (float) (x2 - x1);
dx = abs(x2 - x1);
dy = abs(y2 - y1);
if (dx < dy) {
int temp;
interChange = 1;
temp = dx; dx = dy; dy = temp;
}
xSign = (x2 >= x1) ? 1 : -1;
ySign = (y2 >= y1) ? 1 : -1;
x = x1;
y = y1;
e = 2*dy - dx;
for (int i = 0; i <= dx; i++) {
pDC->SetPixel(x+pDoc->m_wndWidth/2, pDoc->m_wndHeight/2-y, RGB(0, 0, 255));
// 计算生成点到直线距离
xp = (y-y1+x/m+m*x1)/(m+1/m);
yp = -1/m*(xp-x)+y;
averageError +=sqrtf((x-xp)*(x-xp)+(y-yp)*(y-yp));
if (e > 0) {
e = e - 2*dx;
if (interChange) x += xSign;
else y += ySign;
}
if (interChange) y += ySign;
else x += xSign;
e = e + 2*dy;
}
pDoc->m_BLineAverageError += averageError/dx;
// 计算终点与直线终点误差
pDoc->m_BLineEndPointError += sqrtf((x-x2)*(x-x2)+(y-y2)*(y-y2));
}
3、中点分割直线段生成绘制算法
利用递归原理,将(x1,y1)和(x2,y2)两点间的线段不断二分,直到分成的子线段的两个端点的距离小于一个像素,然后对子线段进行描绘。
即:如果(x1,y1)和(x2,y2)的距离小于一个像素,则描点;
否则取(x1,y1)和(x2,y2)的中点(x,y),调用直线段生成算法
(此函数自身)描绘直线段(x1,y1)--(x,y)和(x,y)--(x2,y2)。
//中点分割直线段生成绘制算法
void CcgWXXDrawLineView::Midpointline(float x1, float y1, float x2, float y2, CDC* pDC)
{
float x = (x1 + x2) / 2, y = (y1 + y2) / 2;
if (fabs(x1 - x2) <= 1 && fabs(y1 - y2) <= 1)
{
CcgWXXDrawLineDoc *pDoc = GetDocument(); // 获取Doc类指针
pDC->SetPixel((int)(x + 0.5) + pDoc->wm_width / 2, (int)(y + 0.5) + pDoc->wm_height / 2, RGB(255, 0, 0));//输出绘制像素
return;
}
else
{
Midpointline(x1, y1, x, y, pDC);
Midpointline(x, y, x2, y2, pDC);
}
}