-
2021-08-03 01:15:16
计算机图形学-- 多边形填充算法
第4章 基本光栅图形生成算法 4.3 多边形的填充 4.3.1 多边形的表示方法 4.3.2 多边形填充的扫描线算法 算法特点: 奇点的处理 如果把每一奇点简单地计为一个交点,则交点个数可能出现奇数。若将每一奇点都简单地计为两个交点,同样会导致反常的结果 扫描线算法的数据结构和实现步骤 扫描线算法的数据结构 [P0P1P2P3P4P5 P6] =[(2,5) (2,10) (9,6) (16,11) (16,4) (12,2) (7,2)] [P0P1P2P3P4P5 P6] =[(2,5) (2,10) (9,6) (16,11) (16,4) (12,2) (7,2)] 扫描线算法实现步骤 步骤1:(AEL初始化)将边的活化链表AEL设置为空。 边的Y筒ET 边的活化链表 4.3.3 边缘填充算法 采用对图像进行逐位求反的方法,免去对边排序 的工作量 边缘填充算法的实现 对多边形P的每一非水平边(i=0,1,…,n)上的各像素 做向右求反运算即可 边界标志算法实例 第4章 基本光栅图形生成算法 4.4 区域填充 4.4.1 区域的基本概念 是指已经表示成点阵形式的像素集合。 4连通的区域: 取区域内任意两点,在该区域内若从其中一点出发通过上、下、左、右四种运动可到达另一点。 4.4.2 简单的种子填充算法 4.4.3 扫描线种子填充算法 从给定的种子点开始,填充当前扫描线上种子点所在的区间 算法步骤: 具体实现过程 第4章 简单光栅图形生成算法 4.5 光栅图形的反走样算法 4.5.1 光栅图形的走样现象 图形的边界呈阶梯形, 图形的细节失真, 狭小图形遗失 4.5.2 提高分辨率的反走样方法 提高分辨率的方法: 第4章 简单光栅图形生成算法 4.6 线画图元的属性控制 线宽控制 线宽控制 像素复制方法:产生具有宽度的线状图形,可以顺着扫描所生成的单像素线条轨迹,通过像素复制法来获得 优点: 实现简单 缺点: 线段两端要么为水平的,要么是竖直的 折线顶点处有缺口 图元的宽度不均匀 移动刷子 线型控制 线型控制 造成走样的原因:用离散量表示连续量引起的 因为直线、多边形、色彩边界等是连续的,而光栅图形则是由离散的像素点组成 常见走样形式 不光滑(阶梯状)的图形边界 图形细节失真 狭小图形的遗失 采用硬件: 采用高分辨率的光栅图形显示器,花费的代价大。 为了提高图形质量,需要克服或减少走样现象,减少这种 现象的算法,称为光栅图形的反走样算法 反走样算法: 采用软件: 花费的代价小,也容易实现。 1.从硬件角度提高分辨率 高分辨率显示器 显示器点距减少一倍 帧缓存容量增加到原来的4倍 输带宽提高4倍 扫描转换花4倍时间 代价高 2.从软件角度提高分辨率 高分辨率计算,低分辨率显示 像素细分技术,相当于后置滤波 1 1 1 1 算术平均 1 2 2 1 4 2 1 2 1 加权平均 只能减轻,不能消除 低分辨率显示:将一象素内的各个子象素的颜色值或灰度值的平均值作为该象素的颜色值或灰度值。求平均值时可取算术平均,也可取加权平均。 用软件提高分辨率的方法是: 高分辨率计算,低分辨率显示 高分辨率计算:将低分辨的图形显示象素划分为许多子象素, 如2×2划分,3×3划分等,然后按通常的算法计算出各个子 象素的颜色值或灰度值。 3.区域采样技术(多边形反走样) 改变边或直线的外观,模糊淡化阶梯 相当于图像的前置滤波 点 有限区域 直线有宽度 根据相交的面积值决定像素显示的亮度级别 8级灰度 0≤面积≤1/8 7/8≤面积≤1 (a) (b) (c) 0 1 2 3 4 1 2 2 3 3 4 3 4 0 边缘填充算法分析 优点: 最适合于有帧缓存的显示器 可按任意顺序处理多边形的边 仅访问与该边有交点的扫描线上右方的像素,算法简单 缺点: 对复杂图形,每一像素可能被访问多次,输入/输出量大 图形输出不能与扫描同步进行,只有全部画完才能打印 4.3.4 栅栏填充算法 此算法是为了减少边缘算法访问像素的次数而提出的 栅栏: 是一条与扫描线垂直的直线,栅栏的位置通常取过多边形顶点,能把多边形分为左右两半 栅栏填充的基本思想: 对于每个扫描线与多边形边的交点,就将交点与栅栏之间的像素取补. 若交点位于栅栏左边,则将交点之右,栅栏之左的所有像素取补 若交点位于栅栏右边,则将交点之左,栅栏之右的所有像素取补 栅栏填充的具体实现: 0 1 2 3 4 栅栏线 1 2 栅栏线 3 4 栅栏线 2 3 栅栏线 4 栅栏线 0 边界标志法: 先画边界后填色,使对帧缓冲器中的每个元素的赋值次数不超过2次。 基本思想是:先用一种特殊的颜色在帧缓冲器中将多边形的边界(水平边的部分边界除外)勾画出来。
更多相关内容 -
基于MFC实现多边形填充算法完整代码
2020-05-05 19:19:00使用VS 2017实现多边形填充中的种子填充算法,此资源包括完整的项目文件,可以直接使用。此代码仅供学习交流使用。 -
QT实现多边形填充算法
2020-06-27 17:15:41利用QT实现多边形的填充算法,在网格下坐标系下,双击两下鼠标显示起点,之后点击依次连线,一共七条线,首尾要在同一个坐标才能实现功能。 -
图形学实验报告四多边形填充算法.docx
2020-07-28 19:30:38一 吴厶 成绩 实验项目名称 多边形填充算法 实 验 使学生掌握光栅显示系统中多边形的扫描转换和区域填充算法掌握 4连通区域的扩展性 目 实 验 要 求实现多边形的扫描转换算法和区域填充算法 实 验 要 求 实现多边形... -
有效边多边形填充算法
2016-04-19 15:58:38根据计算机图形学描算的有效边填充算法,来实现,使用vs2013编写。 关键函数用纯C实现,附加本人对此算法的一些理解 -
c#实现多边形填充
2021-03-16 00:37:43c#多边形填充源码实例,其中LCDEmulator_SE目录内的是程序要用到的一个控件的源码。 本C#多边形图形填充程序分为矢量填充和位图填充(区域填充)。矢量填充用的是扫描线算法,区域填充也是一种扫描线算法(而不是... -
多边形填充算法
2018-12-10 19:51:10具体代码实现在conversionview.cpp中,实现多边形填充算法 -
计算机图形学OpenGL多边形填充算法源码
2013-11-29 19:55:52代码是基于OpenGL的控制台程序,用C++编写,下载后直接复制到工程项目中即可运行,适合初学者学习。 -
计算机图形学:多边形填充算法(算法原理及代码实现)
2022-02-22 16:14:06依照此原理可以对图形进行扫描线算法扫描转换多边形,其中在判断上述交点时,还会出现扫描线与边重合、扫描线与边的交点为顶点等现象,具体实现步骤如下。 实现步骤: 1、找到多边形的最大Ymax和最小Ymin,在此...一、实现方案
扫描线算法:
实现原理:
把图形的填充转换为扫描线从上往下扫描填充,这时我们只需要判断每一条扫描线与图形的交点,而我们可以根据扫描线的连贯性,对交点进行排序,第1个点与第2个点之间,第3个点与第4个点之间.....
依照此原理可以对图形进行扫描线算法扫描转换多边形,其中在判断上述交点时,还会出现扫描线与边重合、扫描线与边的交点为顶点等现象,具体实现步骤如下。实现步骤:
1、找到多边形的最大Ymax和最小Ymin,在此范围内扫描多边形逐条;
2、一条扫描线y,求与多边形的交点(通过与每一条边的求交,步骤如下),首先定义一个交点表
(1)如果该边是水平的,并且边的Y值=y,直接将该线画出来
(2)如果该边与y无交点,跳过
(3)该边与y有交点,求出交点(qx,qy)
i.如果该点是顶点①如果该点极值点,将该点x加入表两次
①否则将该x加入表一次<通过判断顶点相交的两点是否在一侧>
Ii.如果不是顶点,则直接将该x加入表中
(4)对交点的x进行排序
(5)按照扫描线的连贯性进行填充
(6)重复上述操作注:设计多边形时,需要按顺序导入点
种子填充算法:
实现原理:
通过区域的4连通和8连通(区域内每一个像素可以通过四个方向(上、下、左、右)组合到达。),从四个方向寻找下一像素者来完成整个区域的填充。
实现步骤:
- 设(x,y)为内点表示的4连通区域内的一点,oldColor为区域的原色,newColor为填充后的颜色。
- 确定多边形的边界,并初始化区域内部原色
- 以(x,y)开始,每次向(上、下、左、右)四个点延伸,判断该点颜色,如果为newColor,递归终止。否则填充该点颜色为newColor,并以该点开始递归开始步骤3
- 所有点都被点亮,递归终止
注:实现代码只能完成矩形的填充
二、代码实现
任务A(扫描线算法填充):
实现代码:
源代码:
class MyPolygon { public: int m_VerticeNumber; CPoint m_Vertex[50]; COLORREF m_LineColor; }; //-------------------------------- int getMiny(MyPolygon ThePolygon) { int min = ThePolygon.m_Vertex[0].y; for (int i = 1; i < ThePolygon.m_VerticeNumber; i++) if (min > ThePolygon.m_Vertex[i].y) min = ThePolygon.m_Vertex[i].y; return min; } int getMaxy(MyPolygon ThePolygon) { int max = ThePolygon.m_Vertex[0].y; for (int i = 1; i < ThePolygon.m_VerticeNumber; i++) if (max < ThePolygon.m_Vertex[i].y) max = ThePolygon.m_Vertex[i].y; return max; } //交换 void Swap(int* a, int i, int j) { int temp = a[i]; a[i] = a[j]; a[j] = temp; } //随机快速排序 int qsort(int* a, int begin, int end) { int i, j, temp; i = begin - 1; j = begin; for (; j < end; j++) { if (a[j] <= a[end - 1]) Swap(a, ++i, j); } return i; } void randqsort(int* a, int begin, int n) { while (begin >= n) return; srand((unsigned)time(NULL)); int key = (begin + rand() % (n - begin)); Swap(a, key, n - 1); int m = qsort(a, begin, n); randqsort(a, begin, m); randqsort(a, m + 1, n); } void ScanFill(CDC* pDC, MyPolygon ThePolygon, CPoint startPoint, COLORREF fillCol) { int miny, maxy; int sx, sy, tx, ty; miny = getMiny(ThePolygon); maxy = getMaxy(ThePolygon); int* index = new int[100]; int* judge = new int[ThePolygon.m_VerticeNumber]; memset(index, -1, sizeof(int) * 100); memset(judge, 0, sizeof(int) * ThePolygon.m_VerticeNumber); int x; CPoint Point; for (int i = miny; i <= maxy; i++) { //记录扫描线与边线的交点 int temp = 0; for (int i1 = 0, L = ThePolygon.m_VerticeNumber, j1 = L - 1; i1 < L; j1 = i1, i1++) { sx = ThePolygon.m_Vertex[i1].x; sy = ThePolygon.m_Vertex[i1].y; tx = ThePolygon.m_Vertex[j1].x; ty = ThePolygon.m_Vertex[j1].y; int lowy, heighty; lowy = (sy < ty) ? sy : ty; heighty = (sy > ty) ? sy : ty; //水平线 if (ty == sy) { if (i == ty) { int xmax, xmin; xmax = (sx > tx) ? sx : tx; xmin = (sx < tx) ? sx : tx; for (int xx = xmin; xx <= xmax; xx++) { Point.x = xx; Point.y = i; pDC->SetPixelV(Point, fillCol); } } continue; } //没有交点 if (i<lowy || i>heighty) continue; x = sx + (i - sy) * (tx - sx) / (ty - sy); //判断交点(x,i)是不是顶点 if ((x == ThePolygon.m_Vertex[i1].x && i == ThePolygon.m_Vertex[i1].y)) { //判断顶点是不是极值点 //即判断与交点相关联的两条线的另外两个顶点是不是在交点的同一侧 if (i1 != L - 1 && judge[i1] == 0) { if ((i - ThePolygon.m_Vertex[i1 + 1].y) * (i - ThePolygon.m_Vertex[j1].y) < 0)//异号 index[temp++] = x; else//同号 极值点 记录两次 { index[temp++] = x; index[temp++] = x; } } else if (i1 == L - 1 && judge[i1] == 0) { if ((i - ThePolygon.m_Vertex[0].y) * (i - ThePolygon.m_Vertex[j1].y) < 0) {//异号 index[temp++] = x; } else//同号 极值点 记录两次 { index[temp++] = x; index[temp++] = x; } } judge[i1] = 1; continue; } else if ((x == ThePolygon.m_Vertex[j1].x && i == ThePolygon.m_Vertex[j1].y)) { if (j1 != 0 && judge[j1] == 0) { if ((i - ThePolygon.m_Vertex[i1].y) * (i - ThePolygon.m_Vertex[j1 - 1].y) < 0) {//异号 index[temp++] = x; } else//同号 极值点 { index[temp++] = x; index[temp++] = x; } } else if (j1 == 0 && judge[j1] == 0) { if ((i - ThePolygon.m_Vertex[i1].y) * (i - ThePolygon.m_Vertex[l - 1].y) < 0)//异号 index[temp++] = x; else//同号 极值点 { index[temp++] = x; index[temp++] = x; } } judge[j1] = 1; continue; } //交点不是顶点 index[temp++] = x; } //将index排序 randqsort(index, 0, temp);//随机快速 //填充多边形 for (int n = 0, m = n + 1; m < temp && index[n] != -1; n += 2, m = n + 1) { for (int xx = index[n]; xx <= index[m]; xx++) { Point.x = xx; Point.y = i; pDC->SetPixelV(Point, fillCol); } } //清零 for (int k = 0; k < 100; k++) index[k] = -1; } delete[] index; delete[] judge; }
任务B(种子填充算法):
实现代码:
源代码:
void FloodFill4(CDC* pDC, int x, int y, int Color) { if((x<20 )&& (x>-20)&& (y > -20) && (y < 20)&&(pDC->GetPixel(x ,y) != Color)){ pDC->SetPixel(x, y, Color); FloodFill4(pDC, x, y - 1,Color); FloodFill4(pDC, x + 1, y,Color); FloodFill4(pDC, x - 1, y,Color); FloodFill4(pDC, x, y + 1,Color); } else{ return; } }
三、算法结果
任务A:
任务B:
-
PolygonFill多边形填充算法
2012-10-14 21:38:42此多边形扫描填充算法源代码非常给力陈正鸣老师经典代码. -
多边形填充算法vc 实现
2010-01-22 23:49:09实现用扫描线算法 和种子算法对多边形进行填充 还可以画线和多边形 -
【计算机图形学 】扫描线多边形填充算法 | OpenGL+鼠标交互
2021-10-16 18:04:42实现多边形扫描线填充算法,并和鼠标进行交互。 具体原理略过,会贴上完整代码,可直接运行。 环境: vs2019,OpenGL的库(可以搜索如何用vs使用OpenGL的库,可以使用vs自带的插件或者其他方法,很方便) 要点: 1...其他计算机图形学实验
前言
实现多边形扫描线填充算法,并和鼠标进行交互。
具体原理略过,会贴上完整代码,可直接运行。环境:
vs2019,OpenGL的库(可以搜索如何用vs使用OpenGL的库,可以使用vs自带的插件或者其他方法,很方便)要点:
1.NET和AET的创建,改动
2.改变鼠标点击和鼠标拖拽的响应事件。最终效果:
用鼠标随意画顶点,然后展示填充过程
对应控制台会输出顶点坐标和个数
思路借鉴
步骤
1.点的结构体
struct point { float x, y; point(){} point(int xx, int yy): x(xx), y(yy) {} }; vector<point> vertice; //顶点
2. AET 活性边表、NET新边表 的结构体
typedef struct XET { float x; float dx; // 从当前扫描线到下一条扫描线间x的增量,即斜率的倒数 float ymax; //该边所交的最高扫描线的坐标值ymax XET* next; }AET, NET; //AET 活性边表; NET新边表
3. 扫描线算法实现
void PolyScan() { /*得到最高点的y坐标*/ int Max_Y = 0; for (int i = 0; i < vertice.size(); i++) /*Max_Y = max(Max_Y, vertice[i].y);*/ if (vertice[i].y > Max_Y) Max_Y = vertice[i].y; //初始化AET表 AET* pAET = new AET; pAET->next = NULL; //初始化NET表 NET* pNET[800]; //吊桶 for (int i = 0; i <= Max_Y; i++) { pNET[i] = new NET; pNET[i]->next = NULL;; } //扫描并且建立NET表 int len = vertice.size(); //顶点个数 for (int i = 0; i <= Max_Y; i++) { for (int j = 0; j < len; j++) //扫描每个点 { if (i == vertice[j].y) { //如果一个点和前一个点有一条边相连,则该点和后面一个点也相连 //!这个式子 便于最后一个顶点和第一个点相连 和 防止出现负数 //判断当前点的高低,使ymax、DX、DY的计算有变化 if (vertice[(j - 1 + len) % len].y > vertice[j].y) { //前一个点在当前点的上方 NET* p = new NET; p->x = vertice[j].x; p->ymax = vertice[(j - 1 + len) % len].y;//与当前扫描线相交的活性边 的 最高点即为相邻顶点的y float DX = vertice[(j - 1 + len) % len].x - vertice[j].x; float DY = vertice[(j - 1 + len) % len].y - vertice[j].y; p->dx = DX / DY;//dx为直线斜率的倒数 p->next = pNET[i]->next; pNET[i]->next = p; } if (vertice[(j + 1) % len].y > vertice[j].y) { //后一个点在当前点的上方 NET* p = new NET; p->x = vertice[j].x; p->ymax = vertice[(j + 1) % len].y; float DX = vertice[(j + 1) % len].x - vertice[j].x; float DY = vertice[(j + 1) % len].y - vertice[j].y; p->dx = DX / DY;//dx为直线斜率的倒数 p->next = pNET[i]->next; pNET[i]->next = p; } } } } //建立并且更新活性边表AET //各条扫描线i for (int i = 0; i <= Max_Y; i++) { /*把新边表NET[i] 中的边结点用插入排序法插入AET表,使之按x坐标递增顺序排列*/ //计算每条扫描线上不同线产生的新的交点x,更新AET NET* p = pAET->next; while (p) { p->x = p->x + p->dx; //更新x坐标 p = p->next; } //断表排序,不再开辟空间 AET* tq = pAET; p = pAET->next; tq->next = NULL; while (p)//顺着链表往下走 { //找到第一个比它大的数字tq->next->next->x,则从p->next到tq->next都是比p->x小的 while (tq->next != NULL && tq->next->x <= p->x) tq = tq->next; //插入p到tq和tq->next之间 NET* t = p->next; p->next = tq->next; tq->next = p; p = t; 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; //插入p NET* t = p->next; p->next = q->next; q->next = p; p = t; q = pAET;//回到头 } //配对后填充颜色 p = pAET->next; while (p && p->next != NULL) { for (float j = p->x; j <= p->next->x; j++) { //扫描线画点 draw_a_point(j, i); //cout << "(" << j << ", " << i << ")" << endl; } p = p->next->next;//考虑端点情况 } } glFlush(); }
4. 改变鼠标响应函数
void mymouse(int button, int state, int x, int y) { //左键 if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) { draw_a_point(x, window_height - y); point p(x, window_height - y); vertice.push_back(p); cout << "顶点" << vertice.size() << ": (" << x << ", " << window_height - y << ")" << endl; } //右键 if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) { glClearColor(1, 1, 1, 1);//设置绘制窗口颜色为白色 glColor3f(0, 1, 1); //绘制多边形 glBegin(GL_LINES); for (int i = 0; i < vertice.size(); i++) { if (i == vertice.size() - 1)//画完最后一个点,使其闭合 { glVertex2f(vertice[0].x, vertice[0].y); glVertex2f(vertice[i].x, vertice[i].y); } else { glVertex2f(vertice[i].x, vertice[i].y); glVertex2f(vertice[i + 1].x, vertice[i + 1].y); } } glEnd(); glFlush(); } //鼠标中间 if (button == GLUT_MIDDLE_BUTTON && state == GLUT_DOWN) { //cout << "center: (" << x << ", " << y << ")" << endl; //BoundaryFill4(x, window_height - y); //BoundaryFill4_Stack(x, window_height - y); cout << "多边形顶点个数为" << vertice.size() << "。 " << "开始扫描线填充。" << endl; PolyScan(); } }
完整代码
//扫描线算法 #include<iostream> #include<gl/glut.h> #include<algorithm> #include<vector> #include<stack> #include<queue> using namespace std; const int window_width = 800, window_height = 600; const int maxn = 99999; struct point { float x, y; point(){} point(int xx, int yy): x(xx), y(yy) {} }; vector<point> vertice; //顶点 typedef struct XET { float x; float dx; // 从当前扫描线到下一条扫描线间x的增量,即斜率的倒数 float ymax; //该边所交的最高扫描线的坐标值ymax XET* next; }AET, NET; //AET 活性边表; NET新边表 void draw_a_point(int x, int y); void PolyScan(); void mymouse(int button, int state, int x, int y); void display(); int main(int argc, char* argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutInitWindowPosition(100, 50); glutInitWindowSize(window_width, window_height); glutCreateWindow("扫描线填充"); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0, window_width, 0, window_height); glClearColor(1, 1, 1, 1); glClear(GL_COLOR_BUFFER_BIT); glutMouseFunc(&mymouse); glutDisplayFunc(&display); glutMainLoop(); return 0; } //画点函数 void draw_a_point(int x, int y) { glBegin(GL_POINTS); glColor3f(0, 1, 1); glVertex2f(x, y); glEnd(); glFlush(); } void PolyScan() { /*得到最高点的y坐标*/ int Max_Y = 0; for (int i = 0; i < vertice.size(); i++) /*Max_Y = max(Max_Y, vertice[i].y);*/ if (vertice[i].y > Max_Y) Max_Y = vertice[i].y; //初始化AET表 AET* pAET = new AET; pAET->next = NULL; //初始化NET表 NET* pNET[800]; //吊桶 for (int i = 0; i <= Max_Y; i++) { pNET[i] = new NET; pNET[i]->next = NULL;; } //扫描并且建立NET表 int len = vertice.size(); //顶点个数 for (int i = 0; i <= Max_Y; i++) { for (int j = 0; j < len; j++) //扫描每个点 { if (i == vertice[j].y) { //如果一个点和前一个点有一条边相连,则该点和后面一个点也相连 //!这个式子 便于最后一个顶点和第一个点相连 和 防止出现负数 //判断当前点的高低,使ymax、DX、DY的计算有变化 if (vertice[(j - 1 + len) % len].y > vertice[j].y) { //前一个点在当前点的上方 NET* p = new NET; p->x = vertice[j].x; p->ymax = vertice[(j - 1 + len) % len].y;//与当前扫描线相交的活性边 的 最高点即为相邻顶点的y float DX = vertice[(j - 1 + len) % len].x - vertice[j].x; float DY = vertice[(j - 1 + len) % len].y - vertice[j].y; p->dx = DX / DY;//dx为直线斜率的倒数 p->next = pNET[i]->next; pNET[i]->next = p; } if (vertice[(j + 1) % len].y > vertice[j].y) { //后一个点在当前点的上方 NET* p = new NET; p->x = vertice[j].x; p->ymax = vertice[(j + 1) % len].y; float DX = vertice[(j + 1) % len].x - vertice[j].x; float DY = vertice[(j + 1) % len].y - vertice[j].y; p->dx = DX / DY;//dx为直线斜率的倒数 p->next = pNET[i]->next; pNET[i]->next = p; } } } } //建立并且更新活性边表AET //各条扫描线i for (int i = 0; i <= Max_Y; i++) { /*把新边表NET[i] 中的边结点用插入排序法插入AET表,使之按x坐标递增顺序排列*/ //计算每条扫描线上不同线产生的新的交点x,更新AET NET* p = pAET->next; while (p) { p->x = p->x + p->dx; //更新x坐标 p = p->next; } //断表排序,不再开辟空间 AET* tq = pAET; p = pAET->next; tq->next = NULL; while (p)//顺着链表往下走 { //找到第一个比它大的数字tq->next->next->x,则从p->next到tq->next都是比p->x小的 while (tq->next != NULL && tq->next->x <= p->x) tq = tq->next; //插入p到tq和tq->next之间 NET* t = p->next; p->next = tq->next; tq->next = p; p = t; 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; //插入p NET* t = p->next; p->next = q->next; q->next = p; p = t; q = pAET;//回到头 } //配对后填充颜色 p = pAET->next; while (p && p->next != NULL) { for (float j = p->x; j <= p->next->x; j++) { //扫描线画点 draw_a_point(j, i); //cout << "(" << j << ", " << i << ")" << endl; } p = p->next->next;//考虑端点情况 } } glFlush(); } void mymouse(int button, int state, int x, int y) { //左键 if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) { draw_a_point(x, window_height - y); point p(x, window_height - y); vertice.push_back(p); cout << "顶点" << vertice.size() << ": (" << x << ", " << window_height - y << ")" << endl; } //右键 if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) { glClearColor(1, 1, 1, 1);//设置绘制窗口颜色为白色 glColor3f(0, 1, 1); //绘制多边形 glBegin(GL_LINES); for (int i = 0; i < vertice.size(); i++) { if (i == vertice.size() - 1)//画完最后一个点,使其闭合 { glVertex2f(vertice[0].x, vertice[0].y); glVertex2f(vertice[i].x, vertice[i].y); } else { glVertex2f(vertice[i].x, vertice[i].y); glVertex2f(vertice[i + 1].x, vertice[i + 1].y); } } glEnd(); glFlush(); } //鼠标中间 if (button == GLUT_MIDDLE_BUTTON && state == GLUT_DOWN) { //cout << "center: (" << x << ", " << y << ")" << endl; //BoundaryFill4(x, window_height - y); //BoundaryFill4_Stack(x, window_height - y); cout << "多边形顶点个数为" << vertice.size() << "。 " << "开始扫描线填充。" << endl; PolyScan(); } } void display() { glClear(GL_COLOR_BUFFER_BIT); glColor3f(0.0, 0.4, 0.2); glPointSize(1); glBegin(GL_POINTS); PolyScan(); glEnd(); glFlush(); }
总结
扫描线算法部分,建立NET 和 建立并且更新活性边表AET 这两个地方比较复杂,可以带入图中多想
-
计算机图形学 ———— 扫描线多边形填充算法 (讲解)
2018-10-15 00:04:05扫描线多边形区域填充算法是按扫描线顺序(由下到上),计算扫描线与多边形的相交区间,再用要求的颜色显示这些区间的象素,即完成填充工作。 区间的端点可以通过计算扫描线与多边形边界线的交点获得。 ...一.基本原理
扫描线多边形区域填充算法是按扫描线顺序(由下到上),计算扫描线与多边形的相交区间,再用要求的颜色显示这些区间的象素,即完成填充工作。
区间的端点可以通过计算扫描线与多边形边界线的交点获得。
对于一条扫描线,多边形的填充过程可以分为四个步骤:
(1)求交:计算扫描线与多边形各边的交点;
(2)排序:把所有交点按x值递增顺序排序;
(3)配对:第一个与第二个,第三个与第四个等等;每对交点代表扫描线与多边形的一个相交区间;
(4)填色:把相交区间内的象素置成多边形颜色;
如图所示,扫描线 6 与多边形的边界 线交于四点A、B、C、D。 这四点把扫描线分 为五个区间 [0, 2],[2, 3.5],[3.5, 7],[7, 11], [11, 2]。 其中, [2, 3.5], [7, 11] 两个区间落在 多边形内,该区间内的象素应取多边形色。 其它区间内的象素取背景色。 这里的四个交点在计算时未必是按从左到 右的顺序获得。 例如,当多边形采用顶点序列 P1P2P3P4P5P6 表示时,把扫描线 6 分别与边 P1P2,P2P3,P3P4,P4P5,P5P6,P6P1 相 交, 得到交点序列D、C、B、A,必须经过排 序,才能得到从左到右,按 x 递增顺序排列的 交点 x 坐标序列。
二.问题
(1)当扫描线与多边形顶点相交时,交点 的取舍问题(保证交点正确配对):
检查相交顶点的两条边的另外两个顶点的y值。按这两个y值中大于交点y值的个数是0,1,2来决定是取零个、一个、还是两个。
当扫描线与多边形顶点相交 时,会出现异常情况。例如图所示,扫描线 2 与 P1 相交。 按前述方法求得交点 ( x 坐标)序列:2,2,8。这将导致 [2, 8] 区间内的 象素取背景色, 而这个区间的象素正是属于多 边形内部,需要填充的。 所以,我们拟考虑当 扫描线与多边形的顶点相交时,相同的交点只 取一个。 这样,扫描线 2 与多边形的交点序列就成为 2,8。正是我们所希望的结果。 然而,按新的规定,扫描线 7 与多边形的 交点序列为 2,9,11。 这将导致错把[2,9]区间作为多边形内部来填充。 为了正确地进行交点取舍,必须对上述两种 情况区别对待。 在第一种情况,扫描线交于一顶 点,而共享顶点的两条边分别落在扫描线的两边 。 这时,交点只算一个。在第二种情况,共享交 点的两条边在扫描线的同一边,这时交点作为零个或两个, 取决于该点是多边形的局部最高点还是局部最低点。 具体实现时,只需检查顶点的两条边的另 外两个端点的 y 值。按这两个 y 值中大于交点 y 值的个数是 0,1,2 来决定是取零个、一 个、还是二个。 例如,扫描线 1 交于顶点 P2,由于共享该顶点的两条边的另外二个顶点 均高于扫描线,故取交点 P2 两次。 这使得 P2 象素用多边形颜色设置。 再考虑扫描线 2。在 P1 处,由于 P6 高于扫描线,而 P2 低于扫描 线,所以该交点只算一个。 而在 P6 处,由于 P1和P5均在下方,所以扫描线 7 与之相交时,交点算零个,该点不予填充。 // (如果某一条边,平行于某一条扫描线;那这一条边,不予记录)///
(2)多边形边界上象素的取舍问题(避免填充扩大化):
对扫描线与多边形的相交区间取左闭右开。
例如,对左下角为 (1,1),右上角为(3,3)的正方形填充 时,若对边界上所有象素均进行填充, 被填充的象素覆盖的面积为 3×3 单位,而方形实际面积只有 2×2 单 位。显然, 这个扩大化问题是由于对边界上的所有象素进行填充引起的。 为了克服这个问 题,规定落在右/上边界的象素不予填充,而落在左/下边界的象素予以填充。 在具体实现时,只要对扫描线与多边形的 相交区间取*左闭右开*。 容易看出,我们在前面 一个问题所采用的方法,即扫描线与多边形顶 点相交时,交点的取舍方法,保证了多边形的 “下闭上开”——丢弃上方水平边以及上方非 水平边上作为局部最高点的顶点。 重合点的处理:当扫描线和边界相交于边界顶点时,同时产生两个交点,通常采用 “起闭终开”或“起开终闭” 。 水平边处理:水平边不参加求交计算,跳过。
三. 解决步骤
为了计算每条扫描线与多边形各边的交点, 最简单的方法是把多边形的所有边放在一个表中。
这样处理效率很低。这是因为一条扫描线 往往只与少数几条边相交,甚至与整个多边形 都不相交。若在处理每条扫描线时,不分青红 皂白地把所有边都拿来与扫描线求交,则其中 决大多数计算都是徒劳无用的。
为了提高效率,在处理一条扫描线时,仅 对与它相交的多边形的边进行求交运算。我们 把与当前扫描线相交的边称为活性边,并把它 们按与扫描线交点 x 坐标递增的顺序存放在一个链表中,称此链表为活性边表(AET)。
假定当前扫描线与多边形某一条边的交点的x坐标为x,则下一条扫描线与该边的交点不要重计算,只要加一个增量△x。(连贯性)
设该边的直线方程为:ax+by+c=0;
若y=yi,x=xi;则当y = yi+1时,
xi+1=xi-b/a
其中ΔX= -b/a为常数,
另外使用增量法计算时,我们需要知道一条边何时不再与下一条扫描线相交,以便及时把它从活性边表中删除出去。
x i+1 = ( - by i+1 –c)/a = x i –b/a 其中△x = -b/a 为常量。△x 可以存放在对应 边的活性边表结点中。另外,使用增量法计算 时,我们需要知道一条边何时与下一条扫描线 相交,以便及时把它从活性边表中删除出去, 避免下一步进行无谓的计算。综上所述,活性边表的结点中至少应为对应边保存如下内容:
第1项存当前扫描线与边的交点坐标x值;
第2项存从当前扫描线到下一条扫描线间x的增量D x;
第3项存该边所交的最高扫描线号ymax;
第4项存指向下一条边的指针(Λ代表一条边的退出,即结束或抛弃);
扫描线6的活性边表 :
扫描线7的活性边表:
为了方便活性边表的建立与更新,我们为每一条扫描线建立一个新边表(NET),存放在该扫描线第一次出现的边。也就是说,若某边的较低端点为ymin,则该边就放在扫描线ymin的新边表中。
1.这个结构实际上是一个指针数组,数组的每个变量是个指针, 这个指针指向所有的以这个y值作为起点的边。 2.把多边形所有的边全部填成这样的结构,插到这个指针数组里面来。 3.从上面这个指针数组里面就知道多边形是从哪里开始的。 在这个指针数组里只有1、2、3、5处有边,因此当从下往上进行扫描转换的时候, 从y=1开始做,而在1这条线上有两条边进来了,然后就把这两条边放进活性边表来处理。 4.当处理y=2这条扫描线时(活性边里有2条边),先看活性边表里是否有边要退出和加入, 实际上p1p2边要退出了(y=ymax),p1p6边要加入了。 退出的边要从活性边表里去掉,不退出的边要进行处理,即把x值加上一个增量。 (这时p1p2变后边需要加一个Λ) 5.后面持续步骤3,4,只到y=5这扫描线结束。 6.即每做一次新的扫描线时,要对已有的边进行三个处理: 一是否被去除掉;如果不被去除; 第二就要对它的数据进行更新,所谓更新数据就是要更新它的x值,即x+△x; 最后,就是有没有新的边进来,新的边在NET里,可以插入排序插进来。
四.算法描述
这个算法过程从来没有求交,这套数据结构使得你不用求交点!避免了求交运算。
扫描线多边形填充算法的主要步骤:
- 建立NET(NewEdgeList)
- 从最低扫描线开始到最高扫描线循环
- 建立或调整AET(ActiveEdgeList)
- 按照AET中的接点顺序填充
void polyfill (多边形 polygon, 颜色 color) { for (各条扫描线i ) { 初始化新边表头指针NET [i];把ymin = i 的边放进边表NET [i]; } y = 最低扫描线号; 初始化活性边表AET为空; for (各条扫描线i ) { 把新边表NET[i]中的边结点用插入排序法插入AET表,使之按x坐标递增顺序排列; 遍历AET表,把y max= i 的结点从AET表中删除,并把y max > i结点的x值递增D x; 若允许多边形的边自相交,则用冒泡排序法对AET表重新排序; 遍历AET表,把配对交点区间(左闭右开)上的象素(x, y),用drawpixel (x, y, color) 改写象素颜色值; } }
-
图形学初步----------多边形填充算法
2018-11-05 14:24:12如果相邻点位于轮廓线内,那么这个点就成为新的种子点,然后继续递归地搜索下去,种子填充算法只适用于光栅扫描设备。 二、多边形填充 多边形 多边形在计算机中有顶点表示和点阵表示两种。 顶点表示 ... -
计算机图形学大实验多边形填充(扫描线法、种子填充法、种子栈填充法)
2017-11-02 17:01:32计算机图形学的大实验,直线、圆、多边形画法,多边形填充算法,包括扫描线填充、四方向种子填充和种子栈填充,方法是,先画好多边形,点击多边形填充方法,选择好颜色后,点击多边形,就可自动填充。注意,种子填充... -
图像多边形填充算法
2018-03-26 20:05:45算法原理: 参考连接:http://alienryderflex.com/polygon/,这里介绍得很详细,但是参考其中代码,实现下来,发现其中有一个bug,不知道是不是我自己的问题。 多边形绘制在原始图上: 为了高效,先确定多边形... -
实验三计算机图形学多边形填充算法.doc
2022-05-06 18:35:00实验三计算机图形学多边形填充算法.doc -
多边形扫描线填充(有序边算法实现)
2019-07-30 11:22:27编写C++MFC程序,要求在MFC的视图中利用鼠标画多边形,并按要求利用横线或竖线进行填充。利用对话框控制线的数量或密度,以及横线或竖线,并可以重复画线或填充。 -
多边形填充算法java实现
2011-03-04 16:54:49这是用java实现的多边形填充算法,是扫描线算法,按照课本上编写的,调试过,绝对没问题。 -
计算机图形学多边形填充算法.pptx
2020-03-26 00:20:134.3.2 多边形填充的扫描线算法;区域的连续性; (1)梯形的两底边分别在y= 和y= 两条扫描线上腰在多边 形P的边上或在显示屏幕的边界上 ;扫描线的连续性;边的连续性;奇点定义 ; 如果把每一奇点简单地计为一个交点则交点... -
快速多边形填充算法
2015-08-22 10:48:48多边形填充在图形、图像处理中经常用到。本文对已有的快速填充方法做了进一步的改进,减少了不必要的运算。对填充算法的思路给出了简要的描述,并给出了伪代码以及程序最终的运行结果。 -
【计算机图形学】【实验报告】DDA画线算法、Bresenham中点画线算法、多边形填充算法(附代码)
2021-11-23 17:28:13【计算机图形学】【实验报告】DDA画线算法、Bresenham中点画线算法、多边形填充算法(附代码) -
多边形边缘填充算法.zip
2019-07-01 19:08:531)用边缘填充算法填充示例多边形; 2)实现调用系统调色板选择多边形填充颜色; 3)多边形外接矩形为整个屏幕客户区。 -
C++ 多边形边缘填充算法
2017-12-20 10:39:51C++ 多边形边缘填充算法主要于图像填充的开发,代码调理清晰,有助于图像处理方面开发。 -
基于扫描线的任意多边形填充算法
2016-09-23 09:52:02基于扫描线的任意多边形填充算法 -
计算机图形学多边形填充算法PPT学习教案.pptx
2021-10-11 14:01:32计算机图形学多边形填充算法PPT学习教案.pptx -
实验三、画圆及凸多边形填充算法.doc
2021-10-07 08:20:50实验三、画圆及凸多边形填充算法.doc -
opengl画多边形以及填充
2019-01-21 16:11:06实现了MFC框架下,基于opengl画直线,圆,多边形以及填充的算法。