opengl实现直线扫描算法和区域填充算法

总体介绍

1、   使用线性扫描算法画一条线,线性离散点

2、   利用区域填充算法画多边形区域,区域离散的点

开发环境VS2012+OpenGL

开发平台 Intel core i5,Intel HD Graphics Family

设计思路

一、直线扫描算法

1、数值微分法(DDA)

已知过端点P0 (x0, y0), P1(x1, y1)的直线段L:y = kx + b,easy得知直线斜率为:k = (y1-y0)/(x1-x0)。(如果x1≠x0)。

我们如果|k|≤1,这样x每添加1,y将添加k。而且保证x每添加1。y的增量不能大于1;如果|k| > 1,则应该将x和y互换。因为k是浮点数,因此算法中须要将y舍入为int型。并圆整到最接近的位置。

DDA算法在每次迭代中的x, y值是上一步的值加上一个增量获得的,因此它是一个增量算法。

可是这样的方法直观。但效率太低,由于每一步须要一次浮点乘法和一次舍入运算。

2、中点画线法

在直线斜率在0~1直接的情况下,设当前像素点为(x,y),那么它的下一个像素点就是p1(x+1,y)或者p2(x+1,y+1)。若称p1和p2的中点M(px+1,y+0.5),Q为理想直线与x+1垂线的交点,当Q在M的下方时。p1即为下一个像素点,否则p2即为下一个像素点。

3、Bresenham算法

过各行各列象素中心构造一组虚拟网格线。

按直线从起点到终点的顺序计算直线与各垂直网格线的交点,然后确定该列象素中与此交点近期的象素。该算法的巧妙之处在于採用增量计算,使得对于每一列,仅仅要检查一个误差项的符号,就能够确定该列的所求象素。

如图所看到的,设直线方程为yi+1=yi+k(xi+1-xi)+k。

如果列坐标象素已经确定为xi。其行坐标为yi。

那么下一个象素的列坐标为xi+1,而行坐标要么为yi,要么递增1为yi+1。是否增1取决于误差项d的值。误差项d的初值d0=0,x坐标每添加1。d的值对应递增直线的斜率值k。即d=d+k。一旦  d≥1,就把它减去1,这样保证d在0、1之间。当d≥0.5时。直线与垂线x=xi+1交点最接近于当前象素(xi,yi)的右上方象素(xi+1,yi+1)。而当d<0.5时,更接近于右方象素(xi+1,yi)。为方便计算,令e=d-0.5,e的初值为-0.5,增量为k。当e≥0时,取当前象素(xi,yi)的右上方象素(xi+1。yi+1);而当e<0时,取(xi,yi)右方象素(xi+1。yi)。

二、区域填充算法

1、递归算法

从指定的种子点開始。向各个方向上搜索。逐个像素进行处理,直到遇到边界,各种种子填充算法仅仅是在处理颜色和边界的方式上有所不同。

2、扫描线算法

扫描线种子填充算法的基本步骤例如以下:当给定种子点(x, y)时,首先分别向左和向右两个方向填充种子点所在扫描线上的位于给定区域的一个区段,同一时候记下这个区段的范围[xLeft, xRight],然后确定与这一区段相连通的上、下两条扫描线上位于给定区域内的区段,并依次保存下来。

重复这个过程,直到填充结束。

扫描线种子填充算法可由下列四个步骤实现:

(1) 初始化一个空的栈用于存放种子点,将种子点(x, y)入栈。

(2) 推断栈是否为空,假设栈为空则结束算法,否则取出栈顶元素作为当前扫描线的种子点(x, y),y是当前的扫描线。

(3) 从种子点(x,y)出发。沿当前扫描线向左、右两个方向填充。直到边界。分别标记区段的左、右端点坐标为xLeft和xRight;

(4) 分别检查与当前扫描线相邻的y - 1和y + 1两条扫描线在区间[xLeft,xRight]中的像素,从xLeft開始向xRight方向搜索。若存在非边界且未填充的像素点,则找出这些相邻的像素点中最右边的一个,并将其作为种子点压入栈中,然后返回第(2)步;

三、算法实现

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvempjY29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvempjY29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvempjY29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >

总结及学习感悟

在学习直线扫描算法时,一開始总是画不出来,后来发现这句glBegin(GL_POINTS);少了个S,没有S就仅仅能画一个点,细节非常重要。

学习区域填充算法时,主要的思路就是以一个点为起点。不断探索周围,假设在这个区域内,就填充颜色,假设遇到边界就停止。

扫描线算法也是,先以某点画一条直线,在区域内的线段部分就填充颜色。

我们就像被选中的一点一样。周围的一切对我们来说都是不可知的黑色,仅仅有不断探索,才知道哪里是边界。也可能也许没有边界,也许边界的那边又是一个更大的新世界······噗,我想多了。

源码

扫描线主要算法

void k1()   //0<k<1
{
	glClear(GL_COLOR_BUFFER_BIT); 

    glColor3f(0.0,0.0,0.0);
    glBegin(GL_POINTS);
    GLint x1=0,y1=0,x2=400,y2=200;
    GLint x=x1,y=y1;
    GLint dx=x2-x1,dy=y2-y1,dT=2*(dy-dx),dS=2*dy;
    GLint d=2*dy-dx;
    glVertex2i(x,y);
    while(x<x2)
 {
   x++;
   if(d<0)
   d=d+dS;
   else
   {
    y++;
    d=d+dT;
   }
 glVertex2i(x,y);
 }
     glEnd();
	 glFlush(); 

}

区域填充

#include "gl/glut.h"
#include "windows.h"
const int POINTNUM=7;      //多边形点数.

/******定义结构体用于活性边表AET和新边表NET***********************************/
 typedef struct XET
 {
  float x;
  float dx,ymax;
  XET* next;
 }AET,NET;

/******定义点结构体point******************************************************/
 struct point
 {
  float x;
  float y;
 }
 polypoint[POINTNUM]={250,50,550,150,550,400,250,250,100,350,100,100,120,30};//多边形顶点

 void PolyScan()
{
/******计算最高点的y坐标(扫描到此结束)****************************************/
 int MaxY=0;
 int i;
 for(i=0;i<POINTNUM;i++)
  if(polypoint[i].y>MaxY)
   MaxY=polypoint[i].y;

/*******初始化AET表***********************************************************/
  AET *pAET=new AET;
  pAET->next=NULL;

/******初始化NET表************************************************************/
  NET *pNET[1024];
  for(i=0;i<=MaxY;i++)
  {
   pNET[i]=new NET;
   pNET[i]->next=NULL;
  }
  glClear(GL_COLOR_BUFFER_BIT);        //赋值的窗体显示.
  glColor3f(0.0,0.0,0.0);             //设置直线的颜色红色
  glBegin(GL_POINTS);
/******扫描并建立NET表*********************************************************/
  for(i=0;i<=MaxY;i++)
  {
   for(int j=0;j<POINTNUM;j++)
    if(polypoint[j].y==i)
    {  //一个点跟前面的一个点形成一条线段。跟后面的点也形成线段
     if(polypoint[(j-1+POINTNUM)%POINTNUM].y>polypoint[j].y)
     {
      NET *p=new NET;
      p->x=polypoint[j].x;
      p->ymax=polypoint[(j-1+POINTNUM)%POINTNUM].y;
      p->dx=(polypoint[(j-1+POINTNUM)%POINTNUM].x-polypoint[j].x)/(polypoint[(j-1+POINTNUM)%POINTNUM].y-polypoint[j].y);
      p->next=pNET[i]->next;
      pNET[i]->next=p;

     }
     if(polypoint[(j+1+POINTNUM)%POINTNUM].y>polypoint[j].y)
     {
      NET *p=new NET;
      p->x=polypoint[j].x;
      p->ymax=polypoint[(j+1+POINTNUM)%POINTNUM].y;
      p->dx=(polypoint[(j+1+POINTNUM)%POINTNUM].x-polypoint[j].x)/(polypoint[(j+1+POINTNUM)%POINTNUM].y-polypoint[j].y);
      p->next=pNET[i]->next;
      pNET[i]->next=p;
     }
    }
  }
/******建立并更新活性边表AET*****************************************************/
for(i=0;i<=MaxY;i++)
  {
 //计算新的交点x,更新AET
   NET *p=pAET->next;
   while(p)
   {
    p->x=p->x + p->dx;
    p=p->next;
   }
 //更新后新AET先排序*************************************************************/
    //断表排序,不再开辟空间
   AET *tq=pAET;
   p=pAET->next;
   tq->next=NULL;
   while(p)
   {
    while(tq->next && p->x >= tq->next->x)
     tq=tq->next;
    NET *s=p->next;
    p->next=tq->next;
    tq->next=p;
    p=s;
    tq=pAET;
   }
 //(改进算法)先从AET表中删除ymax==i的结点****************************************/
   AET *q=pAET;
   p=q->next;
   while(p)
   {
    if(p->ymax==i)
    {
     q->next=p->next;
     delete p;
     p=q->next;
    }
    else
	{
     q=q->next;
     p=q->next;
    }
   }
 //将NET中的新点增加AET,并用插入法按X值递增排序**********************************/
   p=pNET[i]->next;
   q=pAET;
   while(p)
   {
    while(q->next && p->x >= q->next->x)
     q=q->next;
    NET *s=p->next;
    p->next=q->next;
    q->next=p;
    p=s;
    q=pAET;
   }
/******配对填充颜色***************************************************************/

             p=pAET->next;
   while(p && p->next)
   {
    for(float j=p->x;j<=p->next->x;j++)
     glVertex2i(static_cast<int>(j),i);
    p=p->next->next;//考虑端点情况
   }

  }
   glEnd();
glFlush();
}
void init(void)
{glClearColor(1.0,1.0,1.0,0.0);
//窗体的背景颜色设置为白色
glMatrixMode(GL_PROJECTION);
gluOrtho2D(0.0,600.0,0.0,450.0);
}

void main(int argc,char* argv)
{
    glutInit(&argc,&argv);                //I初始化 GLUT.
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);    //设置显示模式:单个缓存和使用RGB模型
    glutInitWindowPosition(50,100);        //设置窗体的顶部和左边位置
    glutInitWindowSize(400,300);        //设置窗体的高度和宽度
    glutCreateWindow("An Example OpenGL Program");    //创建显示窗体

    init();                                //调用初始化过程
    glutDisplayFunc(PolyScan);        //图形的定义传递给我window.
    glutMainLoop();                        //所有的图形和等待
	}

版权声明:本文博客原创文章,博客,未经同意,不得转载。

时间: 2024-08-25 09:53:09

opengl实现直线扫描算法和区域填充算法的相关文章

[计算机图形学] 基于C#窗口的Bresenham直线扫描算法、种子填充法、扫描线填充法模拟软件设计(一)

一.首先说明: 这是啥? —— 这是利用C#FORM写的一个用来演示计算机图形学中 ①Bresenham直线扫描算法(即:连点成线):②种子填充法(即:填充多边形):③扫描线填充法 有啥用? ——  无论是连点成线还是区域填充在高级编程中基本上都提供很高效的库函数来调用.这里拿出这些算法一方面有利于大家理解那些封装的函数底层是实现:另一方面是方便嵌入式TFT屏幕底层驱动开发时借鉴的. 是啥样? ——  如下面的操作,不言而喻. 二.进入正题: 2-1.直线的扫描转换 图形的扫描转换实质就是在光栅

多边形区域填充算法--递归种子填充算法

http://blog.csdn.net/orbit/article/details/7323090 平面区域填充算法是计算机图形学领域的一个很重要的算法,区域填充即给出一个区域的边界(也可以是没有边界,只是给出指定颜色),要求将边界范围内的所有象素单元都修改成指定的颜色(也可能是图案填充).区域填充中最常用的是多边形填色,本文中我们就讨论几种多边形区域填充算法. 一.种子填充算法(Seed Filling) 如果要填充的区域是以图像元数据方式给出的,通常使用种子填充算法(Seed Fillin

区域填充算法

三.区域填充算法-->[要求:区域是连通的] 区域:指已经表示成点阵形式的填充图形,是象素的集合.1.区域有两种表示形式: 1)内点表示:枚举出区域内部的所有象素,内部所有象素着同一个颜色,边界像素着与内部象素不同的颜色.2)边界表示:枚举出区域外部的所有象素,边界上的所有象素着同一个颜色,内部像素着与边界象素不同的颜色. 2.区域分为:1)四向连通区域:从区域上一点出发可通过[上.下.左.右]四个方向移动的组合,在不越出区域的前提下,到达区域内的任意象素.2)八向连通区域:从区域上一点出发可通

计算机图形学——区域填充算法

一.区域填充概念 区域:指已经表示成点阵形式的填充图形,是象素的集合. 区域填充:将区域内的一点(常称[种子点])赋予给定颜色,然后将这种颜色扩展到整个区域内的过程. 区域填充算法要求区域是连通的,因为只有在连通区域中,才可能将种子点的颜色扩展到区域内的其它点. 1.区域有两种表示形式 1)内点表示:枚举出区域内部的所有象素,内部所有象素着同一个颜色,边界像素着与内部象素不同的颜色.2)边界表示:枚举出区域外部的所有象素,边界上的所有象素着同一个颜色,内部像素着与边界象素不同的颜色. 2.区域连

最短路径算法之Dijkstra算法(java实现)

前言 Dijkstra算法是最短路径算法中为人熟知的一种,是单起点全路径算法.该算法被称为是“贪心算法”的成功典范.本文接下来将尝试以最通俗的语言来介绍这个伟大的算法,并赋予java实现代码. 一.知识准备: 1.表示图的数据结构 用于存储图的数据结构有多种,本算法中笔者使用的是邻接矩阵.  图的邻接矩阵存储方式是用两个数组来表示图.一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息. 设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为: 从上面可以看出,无向图的边

C++算法之——常用算法总结

http://blog.sina.com.cn/s/blog_61bebe480100v7c7.html 基本的C++算法分为三类:排序算法.树算法.图算法 算法思想有三种:递推.分治.动态规划 以及 贪心算法. 本文将简要介绍上面三类算法,介绍时穿插介绍算法思想. 一.排序算法 1.基本O(n^2)排序算法: (对基本排序算法的时间复杂度分析主要考虑  比较次数.数据交换次数) 冒泡排序:针对数组.本地排序.需要交换数据.O(1)额外空间 选择排序:一般针对数组.本地排序.需要交换数据.O(1

十种常见的排序算法,面试算法必考

1.冒泡排序 已知一组无序数据a[1].a[2].……a[n],需将其按升序排列.首先比较a[1]与a[2]的值,若a[1]大于a[2]则交换两者的值,否则不变.再比较a[2]与a[3]的值,若a[2]大于a[3]则交换两者的值,否则不变.再比较a[3]与a[4],以此类推,最后比较a[n-1]与a[n]的值.这样处理一轮后,a[n]的值一定是这组数据中最大的.再对a[1]~a[n-1]以相同方法处理一轮,则a[n-1]的值一定是a[1]~a[n-1]中最大的.再对a[1]~a[n-2]以相同方

Java虚拟机 垃圾收集算法及HotSpot算法实现

垃圾收集算法 一般来说,垃圾收集算法分为四类: 标记-清除算法 最基础的算法便是标记-清除算法(Mark-Sweep).算法分为“标记”和“清除”两个阶段:首先标记处需要收集的对象,在标记完成之后,再统一回收所有被标记的对象. 这是最简单的一种算法,但是缺点也是很明显的:一个是效率问题,标记和清除效率都不高.二是空间问题,清除之后会产生大量的空间碎片,导致之后分配大对象找不到足够的连续对象而不得不触发另一次垃圾收集动作.算法执行过程如下图. 复制算法 复制算法(Copying)将可用内存按照容量

联机算法和离线算法的区别

联机算法:联机算法是在任意时刻算法对要操作的数据只读入(扫描)一次,一旦被读入并处理,它就不需要在被记忆了.而在此处理过程中算法能对它已经读入的数据立即给出相应子序列问题的正确答案. 离线算法:算法设计策略都是基于在执行算法前输入数据已知的基本假设,也就是说,对于一个离线算法,在开始时就需要知道问题的所有输入数据,而且在解决一个问题后就要立即输出结果,通常将这类具有问题完全信息前提下设计出的算法称为离线算法( off line algorithms). 例子:装箱问题, 1.联机解决,那么箱子的