SEARU.ORG
当前位置:SEARU.ORG > Linux 新闻 > 正文

OpenGL绘图实例:直线、颜色填充与鼠标响应

Bresenham直线生成算法

       在数学上,直线是没有宽度的、由无数个点构成的集合。对直线进行光栅化,就是在显示器所给定的有限个像素矩阵中,确定最佳逼近于该直线的一组像素。在本次实验中,我采用Bresenham算法来实现对直线的逼近显示。
       Bresenham算法于1965年提出,基本原理是借助于一个误差量(直线与当前实际绘制像素点的距离) 来确定下一个像素点的位置。算法的巧妙之处在于采用增量计算,使得对于每一列,只要检查误差量的符号,就可以确定该下一列的像素位置。
       作为最底层的光栅图形算法,在通常的CAD/图形系统中,会被大量应用,因此,哪怕节约一个加法或减法,也是很了不起的改进。

#ifndef _KEYVEN_DRAW_LINE_H
#define _KEYVEN_DRAW_LINE_H

namespace keyven
{
    void drawline(int x1, int y1, int x2, int y2);    // 暴露接口函数
}

#endif

#include "draw_line.h"

#include <gl/glut.h>
#include <math.h>
#include <algorithm>
using namespace std;

namespace keyven
{
    void line_1(int x1, int y1, int x2, int y2);
    void line_2(int x1, int y1, int x2, int y2);
    void line_3(int x1, int y1, int x2, int y2);
    void line_4(int x1, int y1, int x2, int y2);

    void drawline(int x1, int y1, int x2, int y2)
    {
        glBegin(GL_POINTS);
        glVertex2i(x1, y1);
        glVertex2i(x2, y2);
        glEnd();
        int dx = abs(x1 - x2);
        int dy = abs(y1 - y2);
        if (dx == 0)
        {
            line_4(x1, y1, x2, y2);
        }
        else if (dy == 0)
        {
            line_3(x1, y1, x2, y2);
        }
        else if ((double)dy / dx<1)
        {
            line_1(x1, y1, x2, y2);
        }
        else if ((double)dy / dx >= 1)
        {
            line_2(x1, y1, x2, y2);
        }
    }

    void line_1(int x1, int y1, int x2, int y2)
    {
        if (x1>x2)
        {
            swap(x1, x2);
            swap(y1, y2);
        }
        int dx = abs(x2 - x1) + 1;
        int dy = abs(y2 - y1) + 1;
        int p = 2 * dy - dx;
        int x = x1;
        int y = y1;
        bool f = true;
        if (y2<y1)
        {
            f = false;
        }
        for (int k = 1; k<dx; k++)
        {
            x++;
            if (p<0)
            {
                p = p + 2 * dy;
            }
            else
            {
                if (f)
                {
                    y++;
                }
                else
                {
                    y--;
                }
                p = p + 2 * dy - 2 * dx;
            }
            glBegin(GL_POINTS);
            glVertex2i(x, y);
            glEnd();
        }
    }

    void line_2(int x1, int y1, int x2, int y2)
    {
        if (y1>y2)
        {
            swap(x1, x2);
            swap(y1, y2);
        }
        int dx = abs(x2 - x1) + 1;
        int dy = abs(y2 - y1) + 1;
        int p = 2 * dx - dy;
        int x = x1;
        int y = y1;
        bool f = true;
        if (x2<x1)
        {
            f = false;
        }
        for (int k = 1; k<dy; k++)
        {
            y++;
            if (p<0)
            {
                p = p + 2 * dx;
            }
            else
            {
                if (f)
                {
                    x++;
                }
                else
                {
                    x--;
                }
                p = p + 2 * dx - 2 * dy;
            }
            glBegin(GL_POINTS);
            glVertex2i(x, y);
            glEnd();
        }
    }

    void line_3(int x1, int y1, int x2, int y2)
    {
        if (x1>x2)
        {
            swap(x1, x2);
            swap(y1, y2);
        }
        for (int k = x1; k <= x2; k++)
        {
            glBegin(GL_POINTS);
            glVertex2i(k, y1);
            glEnd();
        }
    }

    void line_4(int x1, int y1, int x2, int y2)
    {
        if (y1>y2)
        {
            swap(x1, x2);
            swap(y1, y2);
        }
        for (int k = y1; k <= y2; k++)
        {
            glBegin(GL_POINTS);
            glVertex2i(x1, k);
            glEnd();
        }
    }
}

区域填充算法

       在计算机图形学中,多边形区域有两种重要的表示方法:顶点表示和点阵表示。顶点表示,即用多边形的顶点序列来表示多边形,顶点表示几何意义强、占内存少,但没有明确指出哪些像素位于多边形内,故不能直接用与区域填充,算法实现较为复杂。点阵表示,即用位于多边形内的像素集合来刻画多边形,这种表示丢失了许多几何信息、占内存多,但便于填充,算法实现较为简单。
       根据区域的不同定义,可以采用不同的区域填充算法。对于顶点表示常使用线性扫描类算法,对于点阵表示常使用种子填充类算法。
       线性扫描类算法基本原理是,待填充区域按Y方向(X方向亦可)扫描线顺序扫描生成。具体实现时,首先按扫描线顺序,计算扫描线与多边形的相交区间;再用要求的颜色填充这些区间内的像素,即完成这一条扫描线的填充工作 。区间的端点可以通过计算扫描线与多边形边界线的交点获得。
       种子填充类算法,是指先将区域内的一点(通常称为种子点)赋予给定颜色,然后将这种颜色扩展到整个区域内的过程。通常使用图的广度优先搜索或深度优先搜索算法进行填充,区域填充可分为4向连通区域和8向连通区域。

       获取某点像素颜色的glut函数为

void glReadPixels(GLint x,GLint y,GLsizesi width,GLsizei height,
GLenum format,GLenum type,GLvoid *pixel);

函数说明如下:

该函数总共有七个参数。前四个参数可以得到一个矩形,该矩形所包括的像素都会被读取出来。(第一、二个参数表示了矩形的左下角横、纵坐标,坐标以窗口最左下角为零,最右上角为最大值;第三、四个参数表示了矩形的宽度和高度)
第五个参数表示读取的内容,例如:GL_RGB就会依次读取像素的红、绿、蓝三种数据,GL_RGBA则会依次读取像素的红、绿、蓝、alpha四种数据,GL_RED则只读取像素的红色数据(类似的还有GL_GREEN,GL_BLUE,以及GL_ALPHA)。如果采用的不是RGBA颜色模式,而是采用颜色索引模式,则也可以使用GL_COLOR_INDEX来读取像素的颜色索引。目前仅需要知道这些,但实际上还可以读取其它内容,例如深度缓冲区的深度数据等。
第六个参数表示读取的内容保存到内存时所使用的格式,例如:GL_UNSIGNED_BYTE会把各种数据保存为GLubyte,GL_FLOAT会把各种数据保存为GLfloat等。
第七个参数表示一个指针,像素数据被读取后,将被保存到这个指针所表示的地址。注意,需要保证该地址有足够的可以使用的空间,以容纳读取的像素数据。例如一幅大小为256*256的图象,如果读取其RGB数据,且每一数据被保存为GLubyte。

       而我在进行gluOrtho2D投影时,选取窗口中心为(0, 0),因此在调用glReadPixels函数时需要进行转换。

#ifndef _KEYVEN_DRAW_COLOR_H
#define _KEYVEN_DRAW_COLOR_H

namespace keyven
{
    class color
    {
    private:
        float iPixel[3];
    public:
        void SetPixels(int x, int y);
        void SetPixels(float r, float b, float g);
        bool operator ==(const color &other) const;
        bool operator !=(const color &other) const;
    };

    void drawcolor(int x, int y, const color &newColor);    // 暴露接口函数
}

#endif

#include "draw_color.h"

#include <queue>
using namespace std;
#include <gl/glut.h>
#include <string.h>
#include <math.h>

#include "variety.h"

namespace keyven
{
    void color::SetPixels(int x, int y)
    {
        memset(iPixel, 0, sizeof(iPixel));
        int tx = x + keyven::window_width / 2;
        int ty = y + keyven::window_height / 2;
        glReadPixels(x + keyven::window_width / 2, y + keyven::window_height / 2, 1, 1, GL_RGB, GL_FLOAT, &iPixel);
    }

    bool color::operator==(const color &other) const
    {
        bool v_equal = true;
        for (int i = 0; i < 3; i++)
        {
            if (fabs(iPixel[i] - other.iPixel[i]) > 0.1)    // 由于是float变量,有可能存在误差
            {
                v_equal = false;
            }
        }
        return v_equal;
    }

    void color::SetPixels(float r, float b, float g)
    {
        iPixel[0] = r;
        iPixel[1] = b;
        iPixel[2] = g;
    }

    bool color::operator!=(const color &other) const
    {
        bool v_equal = false;
        for (int i = 0; i < 3; i++)
        {
            if (fabs(iPixel[i] - other.iPixel[i]) > 0.1)
            {
                v_equal = true;
            }
        }
        return v_equal;
    }

    struct point
    {
        int x, y;
    };

    void fillcolor(int x, int y, const color &newColor);

    void drawcolor(int x, int y, const color &newColor)
    {
        fillcolor(x, y, newColor);
        glFlush();
    }

    void fillcolor(int x, int y, const color &newColor)
    {
        queue<point> plist;

        color oldColor;
        oldColor.SetPixels(x, y);
        glBegin(GL_POINTS);
        glVertex2i(x, y);
        glEnd();

        point p;
        p.x = x;
        p.y = y;
        plist.push(p);
        int count = 0;
        while (plist.size() > 0)
        {
            point p0 = plist.front();
            x = p0.x;
            y = p0.y;
            plist.pop();
            if (x < -window_width / 2 || x > window_width / 2
                || y < -window_height / 2 || y > window_height / 2)
            {
                continue;
            }
            for (int i = 0; i < 4; i++)
            {
                int tx = x + dir[i][0];
                int ty = y + dir[i][1];
                if (tx < -window_width / 2 || tx > window_width / 2
                    || ty < -window_height / 2 || ty > window_height / 2)
                {
                    continue;
                }
                color tempColor;
                tempColor.SetPixels(tx, ty);
                if (tempColor == oldColor && tempColor != newColor)
                {
                    glBegin(GL_POINTS);
                    glVertex2i(tx, ty);
                    glEnd();
                    count = (count + 1) % 100;
                    if (count % 100 == 1)
                    {
                        glFlush();
                    }
                    point p;
                    p.x = tx;
                    p.y = ty;
                    plist.push(p);
                }
            }
        }
    }
}

其余代码

       保存全局变量,类似JavaEE的context中的变量。

#ifndef _KEYVEN_VARIETY_H
#define _KEYVEN_VARIETY_H

namespace keyven
{
    extern int window_width, window_height;    // 全局变量:在.h文件中声明,在.cpp文件中定义
    extern int dir[4][2];
}

#endif

#include "variety.h"

namespace keyven
{
    int window_width, window_height;
    int dir[4][2] = { { 0, 1 }, { 1, 0 }, { -1, 0 }, { 0, -1 } };
}

// stdafx.h : 标准系统包含文件的包含文件,
// 或是经常使用但不常更改的
// 特定于项目的包含文件
//

#pragma once

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>
#include <iostream>
using namespace std;
#include <Windows.h>
#include <gl/glut.h>

// TODO:  在此处引用程序需要的其他头文件
#include "variety.h"
#include "draw_line.h"
#include "draw_color.h"

// OpenGL1.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

void display(void);
void init(void);
void mouseclick(int button, int state, int x, int y);    // 鼠标响应回调函数
// 这里注意鼠标坐标以窗口左上角为(0, 0)

int main(int argc, char* argv[])
{
    // 初始化glut
    glutInit(&argc, argv);
    // 设置窗口属性:使用RGB颜色类型,单缓存
    glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
    // 窗口大小600 x 300
    keyven::window_width = 600;
    keyven::window_height = 400;
    glutInitWindowSize(keyven::window_width, keyven::window_height);
    glutInitWindowPosition(0, 0);
    glutCreateWindow("Simple");
    init();
    glutDisplayFunc(display);
    glutMouseFunc(mouseclick);    // 注册鼠标响应回调函数

    /* glutMainLoop()作为main函数最后一条语句出现 */
    glutMainLoop();

    return 0;
}

void display()
{
    /* clear windows */
    glClear(GL_COLOR_BUFFER_BIT);
    /* 设置画笔颜色 */
    glColor3f(1.0, 0.5, 0.4);
    /* 设置画笔粗细 */
    glPointSize(1);

    keyven::drawline(-70, 40, 0, -50);
    keyven::drawline(0, -50, 70, 40);
    keyven::drawline(0, -130, -70, 40);
    keyven::drawline(70, 40, 0, -130);
    keyven::drawline(-70, 40, 70, 40);

    keyven::drawline(-200, 100, 200, 100);
    keyven::drawline(-200, -100, 200, -100);
    keyven::drawline(-200, 100, -200, -100);
    keyven::drawline(200, 100, 200, -100);

    glFlush();
}

void init()
{
    /* set clear color to black */
    glClearColor(1.0, 1.0, 1.0, 1.0);
    // 设置投影
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    // 左下角坐标(-300,-200),右上角坐标(300,200)
    gluOrtho2D(-keyven::window_width / 2, keyven::window_width / 2, -keyven::window_height / 2, keyven::window_height / 2);
}

void mouseclick(int button, int state, int x, int y)
{
    switch (button)
    {
    case GLUT_LEFT_BUTTON:    // 单机鼠标左键时
        if (state == GLUT_DOWN)    // 处于左键点击状态
        {
            keyven::color newColor;
            newColor.SetPixels(1.0, 0.5, 0.4);
            keyven::drawcolor(x - keyven::window_width / 2, keyven::window_height / 2 - y, newColor);
        }
        break;
    defalut:
        break;
    }
}

Reference

OpenGL绘图实例三之种子填充算法

未经允许不得转载:SEARU.ORG » OpenGL绘图实例:直线、颜色填充与鼠标响应

赞 (0)
分享到:更多 ()

评论 0