一、DDA算法
数字微分分析仪(digital differential analyzer, DDA)方法是一种线段扫描转换算法。
假设线段的起点坐标是(xstart, ystart),终点坐标是(xend, yend),线段的斜率是m,水平方向增量是dx,垂直方向增量是dy;
(1)、算法概括
1*、输入线段两个端点的像素位置,端点位置间的水平和垂直差值赋给参数dx,dy;
2*、判断是x方向、y方向上的增量,谁更大(该方向变化更快),dx,dy中绝对值大的确定参数steps(循环执行次数)值;
3*、确定沿线段生成下一个像素位置的每一步所需的偏移量,从像素位置(x0,y0)开始,循环该过程steps次。
每一步增量的确定规则:
如果dx绝对值大于dy的绝对值,且xstart小于xend: x、y方向上的增量值分别是1和m;
如果dx绝对值大于dy的绝对值,且xstart大于xend: x、y方向上的增量值分别是-1和-m;
如果dx绝对值小于dy的绝对值,且xstart小于xend: x、y方向上的增量值分别是1/m和1;
如果dx绝对值小于dy的绝对值,且xstart大于xend: x、y方向上的增量值分别是-1/m和-1;
(2)、算法描述
#include < stdlib.h> #include < math.h > inline int round(const float a){ return (int)(a+0.5f);}; void lineDDA( int x0, int y0, int xEnd, int yEnd) { int dx = xEnd - x0; int dy = yEnd - y0; int steps, k; float xIncrement, yIncrement, x = x0, y = y0; (int)fabs((float)dx) > (int)fabs((float)dy) ? steps = fabs((float)dx) : steps = fabs((float)dy); xIncrement = float(dx) / float(steps); yIncrement = float(dy) / float(steps); setPixel( round(x), round(y));//该函数用于设置像素点,不同的系统可能函数名称不同。 for( k=0; k<steps; k++) { x += xIncrement; y += yIncrement; setPixel(round(x), round(y)); } return ; }
(3)、优缺点
DDA方法计算像素位置要比直接使用直线方程计算的速度更快。但是在浮点增量的连续迭加中,取整误差的积累使得对于较长线段所计算的像素位置偏离实际线段,而且该过程中的取整操作和浮点运算仍然十分耗时。可以通过将增量m和1/m分离成整数和小数部分,从而使所有的计算都简化为整数操作来改善DDA算法的性能。
二、Bresenham画线算法
Bresenham画线算法是由Bresenham提出的一种精确而有效的光栅线生成算法,该算法仅仅使用增量整数计算。该算法也可以用于显示圆和其他曲线。
该算法在沿线路径方向的取样位置Xk+1,使用Dlower和Dupper来标识两个像素与数学上的线路径的偏差,并确定两个像素中哪一个更接近线路径,然后绘制更接近线路径的那个像素。
(1)、算法概括
|m|<1时的Bresenham画线算法:
1*、输入线段的两个端点,并将左端点存储在(x0,y0)中;
2*、将(x0,y0)装入帧缓存中,画出第一个点;
3*、计算常量dx、dy、2dy和2dy-2dx,并得到决策参数的第一个值:p0 = 2dy - dx;
4*、从k=0开始,在沿线路径的每个Xk处,进行一下检测:
如果Pk <0 ,下一个要绘制的点是(Xk+1, Yk),并且Pk+1= Pk +2dy;
否则,下一个要绘制的点是(Xk+1, Yk+1),并且Pk+1= Pk + 2dy - 2dx;
5*、重复步骤4,共dx-1次。
(2)、算法实现(斜率0<k<1.0)
#include <stdlib.h> #include <math.h> /* Bresenham line-drawing procedure for |m| < 1.0 */ void lineBres(int x0, int y0, int xEnd, int yEnd) { int dx = fabs(xEnd - x0), dy = fabs(yEnd - y0); int p = 2 * dy - dx ; int twoDy = 2 * dy, twoDyMinusDx = 2 * (dy - dx); int x, y; /* Determine which endpoint to use as start position .*/ if(x0 > xEnd) { x = xEnd; y = yEnd; xEnd = x0; } else { x = x0; y = y0; } setPixel(x, y); while( x <xEnd ) { x++; if( p<0 ) p += twoDy; else { y++; p += twoDyMinusDx; } setPixel(x, y); } }
Bresenham对于任意斜率具有通用性。对于斜率为正值且大于1.0的线段,只要交换x、y方向上的规则,即可使用上述算法。水平线、垂直线和对角线,它们都可以直接装入帧缓存而无需进行画线算法处理。
<圆生成算法>
三、中点画圆算法
(1)、背景
利用笛卡尔坐标系或极坐标系的圆方程以及利用圆的对称性来绘制圆,仍然是效率较差的,比较耗时的。因为使用笛卡尔坐标系的圆方程画圆,需要乘法和平方根运算,利用极坐标的圆方程来画圆,需要乘法和三角运算,这些运算都是非常耗时的。
将光栅系统的Bresenham画线算法移植为画圆算法,可以通过比较像素与圆的距离的平方而避免平方根运算;
然而,更加优秀的算法:中点画圆算法,可以不做平方运算而直接比较距离。该算法的基本思想是检验两个像素间的中间位置以确定该中心是在圆边界之内还是之外。这种方法更易应用于其他圆锥曲线,而且对于整数圆半径,中点方法生成与Bresenham画圆算法相同的像素位置。而且使用中点检验时,沿任何圆锥截面曲线所确定的像素位置,其误差限制在像素间隔的1/2以内。
(2)、概述
中点画圆算法的步骤:
1*、输入圆半径r和圆心(Xc,Yc),并得到圆周(圆心在原点)上的第一个点:(x0,y0)=( 0, r);
2*、计算决策参数的初始值:p0 = 5/4 - r;
3*、在每个Xk位置,从k=0开始,完成下列测试:假如Pk <0, 圆心在(0,0)的圆的下一点为(Xk+1, Yk),并且Pk+1= Pk + 2Xk+1+ 1,否则,圆的下一个点是(Xk + 1, Yk- 1),并且Pk+1 = Pk + 2Xk+1 + 1 - 2Yk+1 ,其中2Xk+1= 2Xk +2 且2Yk+1= 2Yk -2。
4*、确定在其他七个八分圆中的对称点。
5*、将每个计算出的像素位置(x,y)移动到圆心在(Xc,Yc)的圆路径上,并画坐标值:x = x+Xc, y = y + Yc;
6*、重复步骤3到步骤5,直至x >= y。
(3)、算法描述
#include <GL/glut.h> class screenPt { private: GLint x, y; public: screenPt() { x = y = 0; } void setCoords( GLint xCoordValue, GLint yCoordValue) { x = xCoordValue; y = yCoordValue; } GLint getx() const { return x; } GLint gety() const { return y; } void incrementx() { x++; } void decrementy() { y--; } }; void setPixel( GLint xCoord, GLint yCoord) { glBegin(GL_POINTS); glVertex2i(xCoord, yCoord); glEnd(); } void circleMidpoint(GLint xc, GLint yc, GLint radius) { screenPt circPt; GLint p = 1 - radius; circPt.setCoords(0, radius); void circlePlotPoints( GLint , GLint , screenPt); circlePlotPoints(xc, yc, circPt); while(circPt.getx() < circPt.gety() ) { circPt.incrementx(); if( p<0 ) p += 2 * circPt.getx() + 1; else { circPt.decrementy(); p += 2 * ( circPt.getx() - circPt.gety() ) +1; } circlePlotPoints(xc, yc, circPt); } }
<椭圆生成算法>
四、中点椭圆算法
(1)、算法概括
中点椭圆算法的步骤:
1* 、输入 椭圆的x轴半径rx, y轴半径ry 和椭圆中心(xc, yc),并得到椭圆(中心在原点)上的第一个点: (x0, y0)= (0, ry);
2*、计算区域1中决策参数的初始值:p10 = ry^2 - rx^2*ry + (rx^2)/4;
3*、在区域1的每个xk位置,从k =0开始,完成以下测试:假如p1k <0, 沿中心在(0,0)的椭圆的下一点为(xk+1, yk),并且P1k+1= p1k + 2*ry^2* xk+1+ ry^2,否则,沿椭圆的下一个点为(xk + 1, yk -1),并且p1k+1= p1k + 2*ry^2*xk+1 -2* rx^2*yk+1+ry^2, 其中2* ry^2*xk+1= 2* ry^2*xk + 2* ry^2, 2*rx^2 * yk+1= 2*rx^2 *yk - 2* rx^2,并且直到2*ry^2*x >= 2* ry^2*y。
4*、使用区域1中计算的最后点(x0, y0)来计算区域2中参数的初始值:p20 = ry^2(x0 + 1/2)^2 + rx^2 *(y0-1)^2 - rx^2* ry^2。
5*、在区域2的每个yk位置处,从 k=0开始,完成以下测试:假如p2k >0, 沿中心为(0,0)的椭圆的下一个点为(xk,yk-1),并p2k+1= p2k - 2*rx^2* yk+1+ rx^2, 否则,沿椭圆的下一个点为(xk+1, yk -1),并且p2k+1= p2k + 2* ry^2* xk+1- 2* rx^2*yk+1+ rx^2,使用与区域1中相同的x和y增量进行计算,直到y = 0;
6*、确定其他三个象限中的对称点。
7*、将计算出的每个像素位置(x,y)移动到中心在(xc,yc)的椭圆轨迹上,并按坐标值绘制点:x = x+xc, y = y+yc。
(2)、算法描述
inline int round( const float a){ return int (a + 0.5); } void ellipseMidpoint( int xCenter, int yCenter, int Rx, int Ry) { int Rx2 = Rx * Rx; int Ry2 = Ry * Ry; int twoRx2 = 2 * Rx2; int twoRy2 = 2 * Ry2; int p; int x = 0; int y = Ry; int px = 0; int py = twoRx2 * y; void ellipsePlotPoints( int, int, int, int); ellipsePlotPoints( xCenter, yCenter, x, y); /*Region 1*/ p = round(Ry2 - (Rx2 * Ry) + (0.25 * Rx2)); while(px <py) { x++; px += twoRy2; if( p<0 ) p += Ry2 +px; else { y--; py -= twoRx2; p += Ry2 +px - py; } ellipsePlotPoints(xCenter, yCenter, x, y); } /*Region 2*/ p = round(Ry2 * (x+0.5) * (x+0.5) + Rx2 * (y-1)*(y-1) - Rx2 * Ry2); while( y>0 ) { y--; py -= twoRx2; if( p>0 ) p += Rx2 - py; else { x++; px += twoRy2; p += Rx2 - py + px; } ellipsePlotPoints(xCenter, yCenter, x, y); } } void ellipsePlotPoints(int xCenter, int yCenter, int x, int y) { setPixel(xCenter + x, yCenter + y); setPixel(xCenter - x, yCenter + y); setPixel(xCenter + x, yCenter - y); setPixel(xCenter - x, yCenter - y); }