图形学笔记: Bresenham画线算法

图形学课本, 按规矩介绍完矩阵行列式, 第一个算法肯定就是Bresenham画线算法了.

來我们來看看算法

Bresenham是用来画一些不反走样的线段的. 都说了线段肯定有起点和终点, 假设我们:

(xf, yf) ==[LINETO]==> (xt, yt)

按照一些初中(好像是初中吧忘了)的几何, 这条直线的方程是:

          yt - yf
y - yf = --------- (x - xf)
          xt - xf

好我们來变一下型. 首先恶心的除号就拿掉. 然后令dy = yt - yf, dx = xt - xf:

(y - yf)(xt - xf) = (x - xf)(yt - yf)
     y dx - yf dx = x dy - xf dy

到这里我们可以想想这个直线可以怎样画了. 对于一条斜率在1以下的直线, 相邻两点要么是同一行, 要么是斜角. 也就是说, 当前点是(x, y)的话, 下一个点要么是(x+1, y), 要么是(x+1, y+1).

^   ,------ This is the CURRENTLY drawn pixel.
|   V _
|   _|_| <----- Which is the next
|  |_|_| <----- pixel to draw ?
|
+-------->

设一个判别式 f(x, y) = x dy - y dx + yf dx - xf dy, 留意到 dx > 0dy > 0. 所以, 如果f(x, y) < 0, 说明y太大了, (x, y)在直线上方. 反之f(x, y) > 0, 说明x太大了.

^
|
|####/:::
|###/::::        #. Area where f(x, y) < 0
|##/:::::        :. Area where f(x, y) > 0
|#/::::::        /. Points on the line meet f(x, y) = 0
+/-------->
/

接下来的就耳熟能详了, 比较右边的点和右上的点的f(x, y), 画小的那个. 包含进上一次 image.h 那个头文件, 然后就写出第一个画线函数:

cppvoid bresenham_1(int xf, int yf, int xt, int yt, const color& c, image& img)
{
        auto f = [&](int x, int y) {
                int dx = xt - xf;
                int dy = yt - yf;
                return x*dy - y*dx + yf*dx - xf*dy; };

        for(int x = xf, y = yf; x <= xt; x++) {
                if(abs(f(x, y)) - abs(f(x, y+1)) < 0)
                        img(x, y) = c;
                else    img(x, ++y) = c;
        }
}

优化时间

事情说到这里, 算法已经能工作了, 接下来就是优化. 能在什么地方优化呢? Bresenham卖广告总是在说"全程只用加减法, 效率超高~". 但是一个看上去必须要用乘法的直线公式, 怎么都没办法优化到只用加减法吧...

其实乘法是在重复计算 :-)小学时候学乘法, 不都说4x3是四个3加起来吗, 所以乘法本身就是加法组成的啊. 而重复的部分, 就是你已经算了3x3, 你只需要再加上一个3就可以得到4x3了. 同样地, 当前判别式的结果, 我们只需要在上一个判别式的结果基础上加上一点什么就行了. 至于是什么, 就要看推导了:

For x = xf, y = yf: f(xf, yf) = 0

  f(x+1, y) = (x+1) dy - y dx + yf dx - xf dy
            = f(x, y) + dy                               * >= 0 *
f(x+1, y+1) = (x+1) dy - (y+1) dx + yf dx - xf dy
            = f(x, y) + dy - dx                          * <= 0 *

这个递推式就是我们需要的. 写下的代码是这样的:

cppvoid bresenham_2(int xf, int yf, int xt, int yt, const color& c, image& img)
{
        int f = 0;
        int dx = xt - xf;
        int dy = yt - yf;

        for(int x = xf, y = yf; x <= xt; x++) {
                img(x, y) = c;
                if((f + dy) + (f + dy - dx) < 0)
                        f += dy;
                else {
                        f += dy - dx;
                        y++;
                }
        }
}

注: 有的同学发现我这个结果跟Computer Graphics 2nd Edition, D. Hearn & M.P. Baker的公式有点不太一样. 其实仔细看一下是一样的, 只不过变了个型. 那本书的公式写出的程序是这个样子的:

cppvoid bresenham_3(int xf, int yf, int xt, int yt, const color& c, image& img)
{
        int dx = xt - xf;
        int dy = yt - yf;
        int f = dy + dy - dx;

        for(int x = xf, y = yf; x <= xt; x++) {
                img(x, y) = c;
                if(f < 0) f += dy + dy;
                else {
                        f += dy + dy - dx - dx;
                        y++;
                }
        }
}

补充完整

void bresenham_4(int xf, int yf, int xt, int yt, const color& c, image& img)
{
        if(xf > xt) {
                int t;
                t = xf; xf = xt; xt = t;
                t = yf; yf = yt; yt = t;
        }

        int dx = xt - xf;
        int dy = yt - yf;
        int f = 0;

        if(dy >= 0 && dy <= dx) {
                for(int x = xf, y = yf; x <= xt; x++) {
                        img(x, y) = c;

                        if((f + dy) + (f + dy - dx) < 0)
                                f += dy;
                        else { f += dy - dx; y++; }
                }
        } else if(dy >= 0 && dy > dx) {
                for(int x = xf, y = yf; y <= yt; y++) {
                        img(x, y) = c;

                        if((f - dx) + (f - dx + dy) > 0)
                                f += -dx;
                        else { f += -dx + dy; x++; }
                }
        } else if(dy <= 0 && -dy <= dx) {
                for(int x = xf, y = yf; x <= xt; x++) {
                        img(x, y) = c;

                        if((f + dy) + (f + dy + dx) > 0)
                                f += dy;
                        else { f += dy + dx; y--; }
                }
        } else if(dy <= 0 && -dy > dx) {
                for(int x = xf, y = yf; y >= yt; y--) {
                        img(x, y) = c;

                        if((f + dx) + (f + dy + dx) < 0)
                                f += dx;
                        else { f += dy + dx; x++; }
                }
        }
}

测试一下 :-)

cppint main()
{
        image img(800, 600);

        bresenham_1(20, 20,
                500, 50, color(uint32_t(0x00000000)), img);
        bresenham_2(20, 30,
                500, 60, color(uint32_t(0x00000000)), img);
        bresenham_3(20, 40,
                500, 70, color(uint32_t(0x00000000)), img);

        // completed line algorithm
        for(int i = 0; i < 50; i++) {

                bresenham_4(rand() % 800, rand() % 600, rand() % 800, rand() % 600,
                        color(uint32_t(0x00000000)), img);
        }


        ofstream f("3-1-line.ppm");
        f << img;
        f.close();
}

好了有人该警告我不要偷懒用來画图了, 老老实实贴上来吧 (压缩成jpg之后那种漂亮的Minecraft风格的直线变得很模糊糟透了)

你可能感兴趣的:(c++,图形学)