zoukankan      html  css  js  c++  java
  • 画直线算法 Line drawing algorithm

    画直线算法 Line drawing algorithm

    Line drawing algorithm 罗列了几种在屏幕像素上画直线的算法。

    自己想

    但我自己想的话可能就会变成这样:

    首先问自己几个问题:

    Q1: 算法的输入?

    两个实数点。

    Q2:算法的输出?

    若干个自然数点。

    Q3:怎么展示算法结果?

    屏幕上的像素可以抽象成一个二维数组,点作为坐标索引来定位,给二维数组赋值相当于给像素上色。

    然后思考过程:

    两个实数点算出来一个实数域上的实数直线方程。但显然此方程的解不能直接作为算法的解,它还需要再通过一个函数过滤成自然数的解。

    real_points = line_equation(p1, p2)
    natural_points = func(real_points)
    

    最直接的想法是每次迭代,得到实数解之后,我都有两个选择:

    1. 选上一个自然数解
    2. 选上一个自然数解 + 1

    那我选择的标准是什么?是离谁更近。因为我离这两个选择都不会超过 1 ,所以我离谁小于 0.5 ,就选谁。

    如下如图所示(注意为了方便画图像素中心点放在了左下角),画出最典型的情况:斜率大于零且两个点满足 x0 < x1, y0 < y1。红点代表实数解,绿点代表上一个自然数解,蓝点代表上一个自然数解 + 1 ,最后选择的像素标记为黄块块。起点随便选一个,每次看蓝点近选蓝点,绿点近选绿点。

    x0 < x1, y0 < y1 
    
    function line(x0, y0, x1, y1) 
        real slope := (y1 - y0 / x1 - x0)
        real realy:= y0
        int y := y0
        for x from x0 to x1 
            plot(x, y)
            realy := realy + slope
            if realy - y ≥ 0.5 then
                 y := y + 1
    

    边界和顺序问题就很多了。比如对直线分类讨论:

    • 水平的(斜率为零)
    • 垂直的(斜率为无穷大)
    • 斜的(其他可能的斜率)

    还有 x0 > x1 怎么处理等等。

    Bresenham's line algorithm

    计算的是每一像素点与该线之间的误差。误差应为每一点x中,其相对的像素点之y值与该线实际之y值的差距。每当x的值增加1,误差的值就会增加slope。每当误差的值超出0.5,线就会比较靠近下一个映像点,因此y的值便会加1,且误差减1。

    如上图,其实 error 算的就是红点和绿点之间的差距。比如看 x = 5 的时候,假设 error = 0.6 ,要选蓝点,所以 error - 1 = -0.4 。然后下次迭代 error = error + slope = -0.4 + 0.75 = 0.35 。

    x0 < x1, y0 < y1 
    
    function line(x0, y0, x1, y1)
        real slope := (y1 - y0 / x1 - x0)
        real error := 0.0 // No error at start
        int y := y0
        for x from x0 to x1 
            plot(x, y)
            error := error + slope
            if error ≥ 0.5 then
                y := y + 1
                error := error - 1.0
    

    【本质上跟我想的差不多】

    测试代码

    #include <iostream>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    const int width = 20;
    const int height = 20;
    const char clear_char = ' ';
    const char mark_char = '*';
    
    char screen[width][height];
    
    struct point 
    { 
        int x, y; 
        point(int _x, int _y) : x(_x), y(_y) {}
    };
    
    typedef void(*method)(point&, point&);
    
    struct testcase
    {
        point p0, p1;
        method func;
    
        testcase(point _p0, point _p1, method _func) : p0(_p0), p1(_p1), func(_func) {}
    
        void run() { func(p0, p1); }
    };
    
    void draw_pixel(int x, int y)
    {
        // 按照数学惯例,我们会认为数组的第一个索引是 y,也就是行数
        screen[y][x] = mark_char;
    }
    
    void show_screen()
    {
        cout << endl;
        // 同样按照数学惯例,行数需要做一个翻转,这样原点才在左下角
        for (int i = width - 1; i >= 0; --i)
        {
            for (int j = 0; j < height; ++j)
                cout << screen[i][j];
            cout << endl;
        }
        cout << 0;
        for (int i = 0; i < width; ++i) cout << '=';
    }
    
    
    void native(point& p0, point& p1)
    {
        memset(screen, clear_char, sizeof(screen));
    
        // 对边界情况的处理思路:
        // 1. 对直线分类讨论
        // 水平线
        if (p0.y == p1.y)
        {
            int dx = p0.x < p1.x ? 1 : -1;
            for (int x = p0.x; x != p1.x; x += dx)
                draw_pixel(x, p0.y);
        }
        // 垂直线
        else if (p0.x == p1.x)
        {
            int dy = p0.y < p1.y ? 1 : -1;
            for (int y = p0.y; y != p1.y; y += dy)
                draw_pixel(p0.x, y);
        }
        // 斜线
        else
        {
            // 2. 始终迭代 x
            // 3. 必须满足 p0.x <= p1.x, 如果不是则交换
            if (p0.x > p1.x) 
                swap(p0, p1);
    
            // 4. 那么还有两种情况
            // p0.y <= p1.y 斜率为正
            // p0.y > p1.y 斜率为负
            double slope = static_cast<double>(p1.y - p0.y) / (p1.x - p0.x);
            int dy = p0.y > p1.y ? -1 : 1; 
            float realy = p0.y;
            int y = p0.y;
            for (int x = p0.x; x <= p1.x; ++x)
            {
                draw_pixel(x, y);
                realy += slope;
                if (abs(realy - y) > 0.5)
                    y = y + dy;
            }
        }
        show_screen();
    }
    
    void bresenham(point& p0, point& p1)
    {
        memset(screen, clear_char, sizeof(screen));
    
        if (p0.y == p1.y)
        {
            int dx = p0.x < p1.x ? 1 : -1;
            for (int x = p0.x; x != p1.x; x += dx)
                draw_pixel(x, p0.y);
        }
        // 垂直线
        else if (p0.x == p1.x)
        {
            int dy = p0.y < p1.y ? 1 : -1;
            for (int y = p0.y; y != p1.y; y += dy)
                draw_pixel(p0.x, y);
        }
        // 斜线
        else
        {
            // 2. 始终迭代 x
            // 3. 必须满足 p0.x <= p1.x, 如果不是则交换
            if (p0.x > p1.x) 
                swap(p0, p1);
    
            // 4. 那么还有两种情况
            // p0.y <= p1.y 斜率为正
            // p0.y > p1.y 斜率为负
            double slope = static_cast<double>(p1.y - p0.y) / (p1.x - p0.x);
            int dy = p0.y > p1.y ? -1 : 1; 
            float error = 0.0;
            int y = p0.y;
            for (int x = p0.x; x <= p1.x; ++x)
            {
                draw_pixel(x, y);
                error += slope;
                if (abs(error) > 0.5)
                {
                    y = y + dy;
                    error -= 1.0;
                }
            }
        }
        show_screen();
    
    }
    
    int main()
    {
        testcase tcs[] = 
        {
            { point(2, 3), point(2, 10), native },
            { point(11, 5), point(1, 5), native },
            { point(1, 1), point(8, 6), bresenham },
            { point(1, 1), point(8, 6), native },
            { point(3, 1), point(15, 3), bresenham},
            { point(3, 1), point(15, 3), native},
            { point(10, 1), point(1, 17), bresenham},
            { point(10, 1), point(1, 17), native},
        };
    
        for (testcase& t : tcs) t.run();
    
        return 0;
    }
    
  • 相关阅读:
    jdk源码剖析三:锁Synchronized
    ASP.NET的session操作方法总结
    C#文件相同性判断
    C#的DataTable操作方法
    C#二进制流的序列化和反序列化
    C#常用的IO操作方法
    C#缓存操作
    CLR中的程序集加载
    Oracle数据库的SQL分页模板
    奇妙的NULL值,你知道多少
  • 原文地址:https://www.cnblogs.com/tandandan/p/14725697.html
Copyright © 2011-2022 走看看