计算机图形学实验三 《交互式绘制多边形》
这个只是先行版,最近比赛太多了有点忙不过来,讲得不好还请包含
视频讲解:咕咕咕~
一.交互式技术
人机交互就是指人与计算机之间使用某种对话语言,以一定的交互方式,为完成确定任务的人与计算机之间的信息交换过程。
1.消息响应函数
消息响应函数就是收到某些指定消息的时候,做出某些动作的函数,也叫消息处理函数。
有两种方法去添加消息响应函数一种是用户手动添加两个文件((.h)和 (.cpp))的内容,
一种就是用MFC自带的这里只说自带的且常用的。
消息响应函数有很多这里介绍常用的:
1 2 3 4 5 6
| WM_LBUTTONDOWN WM_LBUTTONUP
WM_MOUSEMOVE
|
接下来说一下如何添加
点击类向导然后
类名切换成你的View类如果你的项目叫Test那这个就是TestView类,然后点击消息,在那个搜索消息框里面搜索要添加的函数,点击之后右边现有处理程序框就会显示,然后点确定就好了。
以按下左键消息函数为例:
1 2 3 4 5 6 7 8
| void CInteractionView::OnLButtonDown(UINT nFlags, CPoint point) { CDC* pDC=GetDC(); pDC->LineTo(point); CView::OnLButtonDown(nFlags, point); }
|
这样写的意思就是当你按下左键的时候就会画一条直线,左键弹起就会小时,因为必须的按下左键才会显示导致我没法截图你们可以自己试试。
2.橡皮筋
这是我们想实现的结果,就是交互式的绘制多边形,大概思路就是用MouseMove(鼠标移动消息响应函数)去实现随着鼠标移动线向橡皮筋一样去实时的绘制,然后按下左键去确定点。
先看MouseMove
1 2 3 4 5 6 7 8 9
| void CInteractionView::OnMouseMove(UINT nFlags, CPoint point) { CDC* pDC = GetDC(); pDC->LineTo(point); CView::OnMouseMove(nFlags, point); }
|
这么写的意思就是移动到那线就画到哪那他就会这样
你会发现之前的线还在残留且不连续,假如你写个代码就绘制一条线那也会出现屏闪的情况
所以这里我们引入双缓冲技术
双缓冲
双缓冲是指一个显示缓冲区(显示设备上下文)和一个内存缓冲区(内存设备上下文)
在图形图像显示过程中,计算机从显示缓冲区取数据然后显示,很多图形的操作都很复杂需要大量的计算,很难访问一次显示缓冲区就能写入待显示的完整图形数据,通常需要 多次访问显示缓冲区,每次访问时写入最新计算的图形数据。而这样造成的后果是一个需要复杂计算的图形,你看到的效果可能是一部分一部分地显示出来的,造成很大的闪烁不连贯。 而使用双缓冲,可以使你先将计算的中间结果存放在另一个缓冲区中,但全部的计算结束,该缓冲区已经存储了完整的图形之后,再将该缓冲区的图形数据一次性复制到显示缓冲区
双缓冲就是用来解决单缓冲擦除图像时带来的屏幕闪烁问题
有些人可能会用SetROP2(R2_NOT)来实现这个原理就是清楚上一次绘制的案但毕竟不是我们自己写的所以这里我来讲如何自己实现双缓冲
现在TestView.h头文件(你的工程名字叫啥就是啥view)声明DoubleBuffer函数
然后再TestView.cpp源文件去实现对于函数代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void CInteractionView::DoubleBuffer(CDC* pDC) { CRect rect; GetClientRect(&rect); CDC MemDC; CBitmap NewBitmap, * pOldBitmap; MemDC.CreateCompatibleDC(pDC); NewBitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()); pOldBitmap = MemDC.SelectObject(&NewBitmap); MemDC.FillSolidRect(rect, pDC->GetBkColor()); DrawObject(pDC); pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &MemDC, 0, 0, SRCCOPY); MemDC.SelectObject(pOldBitmap); NewBitmap.DeleteObject(); MemDC.DeleteDC(); ReleaseDC(pDC); }
|
这样就实现了双缓冲机制,代码都有注释不是什么高深的东西就是字面意思,不要想得太复杂也不用纠结调用的函数原理是啥你只需要知道他有啥用就行,如果看不懂注释的意思那就不要强求自己了你就当一个模板记住就行,要用的时候直接复制下来就行。
3.引力域
我们绘制的是一个封闭的多边形,但是最后要封闭的时候你很难精确的去点到那个起点那个像素点,所以我们这里引入引力域技术,效果就是在起点周围你点击鼠标他就会自动连上起点然后封闭。
很简单具体看我的OnLButtonDown函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void CInteractionView::OnLButtonDown(UINT nFlags, CPoint point) { if (bLBDown && abs(point.x - P[0].x) <= 5 && abs(point.y - P[0].y) <= 5) { P[nCount] = P[0]; is_Close = true; } else P[nCount++] = point; bLBDown = TRUE; CView::OnLButtonDown(nFlags, point); }
|
那就说完了我把我的其他的代码发出来你们可以自己试试玩玩
OnMouseMove:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void CInteractionView::OnMouseMove(UINT nFlags, CPoint point) { CDC* pDC = GetDC(); if (bLBDown) { if (abs(point.x - P[0].x) <= 5 && abs(point.y - P[0].y) <= 5) { SetCursor(LoadCursor(NULL, IDC_HAND)); } } if (!is_Close)P[nCount] = point;
ReleaseDC(pDC); Invalidate(FALSE); CView::OnMouseMove(nFlags, point); }
|
DrawObject:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void CInteractionView::DrawObject(CDC* pDC) { pDC->SelectStockObject(GRAY_BRUSH); for (int i=0;i<=nCount;i++) { if (!i) { pDC->MoveTo(P[i]); } else pDC->LineTo(P[i]); }
if (bLBDown) { pDC->Rectangle(P[0].x - 5, P[0].y - 5, P[0].x + 5, P[0].y + 5); } }
|
到这里就实现了交互式绘制多边形
二.有效边表填充算法
也可以说是y向连贯性算法,有效边表填充算法就是对x扫描线的一个优化,这个可能理解有点困难如果你的c/c++基础很差,数据结构也不会,指针概念模糊,那可能这里很难听懂,对此我建议开摆多看几遍我会把这些你们没学遗漏的知识讲一遍。不过有一说一这个确实先对来说难,比起种子填充算法来说,种子填充就是个简单的BFS或者DFS搜索涉及的前置知识不多。
1.X扫描线算法
X-扫描线算法一共就四步:
求交: 确定扫描线和多边形的交点
排序:把所有交点按递增顺序排序(按交点x值递增排序,确保交点两两配对时填充区间的正确性)
配对:确定填充区间
着色:着色规则有说法的,就我拿一个bool flag初始化为false,这个flag如果为false表示在多边形外面否则在里面,一开始为false,当你碰到一个交点的时候就代表从外进入到内,所以将flag=true,只要flag为true就涂色,因为当你碰到一个交点那你肯定要么是从外到内要么就是内到外,所以状态改变一下就好了
边界像素处理原则:
假设你要填充的是黑色正方形,那对于边界上的点该怎么取舍呢,如果你边界上的点全涂色,那就是左边那个图的情况,那肯定是不对的因为你要涂色的图形才2x2大小而你画的是个3x3大小的,我们这里采用的是左闭右开,上闭下开原则
对于顶点到底是算一个还是算两个呢,如果算一个那看上面那个多边形图的红色扫平描线,如果算一个点,那么红色扫描小经过p3的时候flag就会变成true那么他就会把(p3,p5)区间填充颜色,那就错了,那假如顶点算两个点那你看紫色扫描线,当她经过p2点的时候flag因为经过两个点就还是false,那么就不会填充p2到边界这段,那也错了,那该如何处理这种情况呢,处理办法就是这个顶点上面有多少条边就把他当几个点。你这么想如果它上面没有边或者有两条边那他肯定是尖尖,所以y方向上是不需要填充的,如果只有一条边,那他的y方向上就需要填充。
2.指针
因为接下来要讲的链表和有效边表算法都涉及到指针,担心有些同学指针概念模糊不会用这里还是简单讲一下,因为指针用了要是用不好后果还是很可怕的有时候。
我们知道C/C++的变量存放在内存中,而内存其实就是一组有序字节组成的数组,每个字节有唯一的内存地址。CPU 通过内存寻址对存储在内存中的某个指定数据对象的地址进行定位。这里,数据对象是指存储在内存中的一个指定数据类型的数值或字符串,它们都有一个自己的地址,而指针便是保存这个地址的变量。也就是说:指针是一种保存变量地址的变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include<bits/stdc++.h> using namespace std; typedef long long ll; const int N =1e6+5;
int main(){ ios::sync_with_stdio(false); int* a=NULL; char *b=NULL; int x=10; a=&x; cout<<"x变量的地址:"<<a<<endl; cout<<"a地址存放的数据:"<<*a<<endl; int** p=&a; cout<<"a指针的地址:"<<p<<endl; cout<<"p地址存放的数据:"<<*p<<endl; return 0; }
|
指针也可以进行加减还有很多关于指针的东西,但这里就不说了感兴趣可以自己网上查查,这里至少简单了解一下指针,不然接下来会不好理解。
3.链表
因为接下来说的几种数据结构都涉及到了链表的思路如果你会那就忽略这里
链表是一种这样的数据结构,其中的对象按线性顺序排列。数组的线性顺序是由数组下标决定的,然而与数组不同的是,链表的顺序是由各个对象的指针决定的。链表为动态集合提供了一种简单且灵活的表示方法。相对于数组,链表优点是使用直观,便于快速、随机地存取线性表中的任一元素,但缺点是对其进行 插入和删除操作时需要移动大量的数组元素,同时由于数组属于静态内存分配,定义数组时必须指定数组的长度,程序一旦运行,其长度就不能再改变,实际使用个数不能超过数组元素最大长度的限制,否则就会发生下标越界的错误,低于最大长度时又会造成系统资源的浪费,因此空间效率差。
这里就介绍一下单链表的组成,插入,查找,删除等操作,主要是有链表得这么一个概念。
链表组成部分就两部分
数据域就存储该节点对应的数据内容,而指针域就是储存下一个节点的指针。
接下来看一下具体代码咋写,不理解的我在下面放了图示可以和注释一起帮助理解,如果还是不理解也可以网上看看别人的讲解,我讲的也不好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include <iostream> using namespace std; struct Link{ int data; Link *Next; }; int main(){ ios::sync_with_stdio(false);
Link * p = (Link*)malloc(sizeof(Link)); Link * temp = p; int i = 0; for (int i = 0; i < 5; i++) { Link *a = (Link*)malloc(sizeof(Link)); a->data = i; a->Next = NULL; temp->Next = a; temp = temp->Next; } cout<<"输出链表:"<<endl; temp=p; while(temp->Next!=NULL){ cout<<temp->Next->data<<' '; temp=temp->Next; } cout<<endl; int val=3; Link *del; temp=p; while(temp->Next!=NULL){ if(temp->Next->data==val){ del=temp->Next; temp->Next=temp->Next->Next; free(del); break; } temp=temp->Next; } cout<<"删除结点为3的结点后的链表:"<<endl; temp=p; while(temp->Next!=NULL){ cout<<temp->Next->data<<' '; temp=temp->Next; } return 0; }
|
运行结果:
4.有效边表
如果一条扫描线我们每条边都去和他进行求交,那会很麻烦,就比如我们对n边型进行求交那就是O(N)的时间复杂度,就是要求N次交10000条边就是10000次,那会很慢,我们只需要对有效边去求交就行了,有效边就是和扫描线与交点的边,为此我们建立有效边表这一数据结构。
数据结构就是存储,组织数据的一种方式,说简单点就是以一种特定的形式去存储一些数据,来达到优化的目的,99%的数据结构都是拿来优化的。
有效边表的格式:
1 2 3 4 5 6 7 8 9 10 11 12 13
| class CAET { public: CAET(); virtual ~CAET(); public: double x; int yMax; double k; Point1 ps; Point1 pe; CAET* pNext; };
|
其中x就是有效边与扫描线的交点的x坐标,ymax就是这条边最大的y坐标,1/k是该直线的斜率倒数,next是下一条边的结点指针
引入有效边表的目的是为了方便我们去计算扫描线与边的交点,我们可以根据之前所学的直线算法的分量思想去推算出下一条扫描线与该边的交点,简单地说就是我们让y坐标+1,这样x坐标只需要去加1/k,这样我们就可以根据有效边表去高效的计算出扫描线与有效边的交点
可以根据计算机图形学书P118有效边表的建立过程自己模拟一遍,这里就不赘述了。
5.桶表和边表
不过这里又有一个问题,我们虽然可以高效的计算出扫描线与有效边的交点,但是我们不知道什么时候让有效边插入,就是我们不知道什么时候去和某一条有效边去进行求交,所以我们这里引入了桶表和边表,边表用以存放多边形上各条边出现的信息,因为水平边斜率倒数是无穷大且水平边就是扫描线所以不做考虑。
桶表大概就是这样:
1 2 3 4 5 6 7 8 9 10
| class CBucket { public: CBucket(); virtual ~CBucket(); public: int ScanLine; CAET *pET; CBucket *pNext; };
|
你可以理解为桶表就是所有扫描线的集合,然后每条扫描线都有一个边表指针表示在该桶表节点上的边表,这里边表的插入就类似于链表,桶表本身就是链表只是在它的每一个节点上又去接上了一个边表,将每条边的信息(也就是边表)链入与该边最小y坐标(ymin)相对应的桶处。也就是说,若某边的较低端点为ymin,则该边就存放在相应的扫描线桶中。
对于每一条扫描线,如果新增多条边,则按x|ymin(较低端的x坐标)坐标递增的顺序存放在一个链表中,若x|ymin 相等,则按照1/k由小到大排序(接下来就会讲如何排序),这样就形成边表,(不存在x相等,k还想等情况)。
可以配合图来理解
这是边表,结构其实类似于有效边表,所以可以和有效边表用一个类来表示,存放的是一条边较低端的端点x坐标(用来判断边表在桶中的排序),和最高端的y坐标(用来判断有效边何时无效),以及斜率倒数。
这是桶表和边表这样看上去就很好理解了
6.动态链表冒泡排序
刚刚也提到了排序,我们要对边表进行一个排序,为啥要排序,是因为我们要填充颜色,我们要先配对确定填充空间,然后填色,忘了的可以看看上面说的那四个步骤,排序规则是x小在前面,如果一样那就比斜率倒数,不存在x和斜率都相等的情况,因为我们排序的边表有个前提条件就是y坐标相同因为在同一条扫描线上。
那怎么对动态链表排序呢,这里有很多方法,这里说一个最简单不过效率也是最低的冒泡排序,其实我认为这里用堆来优化应该是最好的,因为堆是没插入一个新的东西才会up(),down(),效率每次都是log(N),总共就是Mlog(N),但是其他log级别排序比如快排,是每次都是Nlog(N)总的算下来应该是NMlog(N),,而冒泡本身是N^2,排序M次则是M*N^2,不过优点就是很稳定。对于排序我的B站有相关讲解感兴趣的可以去看看然后自己试着去优化,这里先讲冒泡。
先看一下冒泡排序的流程:
简单地说就是她一共是两层循环,外层是确定排序位置,内部进行相邻比较,就是
1 2 3 4 5 6 7
| for(int i=0;i<n;i++){ 外部是确定位置就当i等于0的时候那进行完内部循环数组下标为0的点就是正确排序的点 for(int j=i;j+1<n;j++){ if(j>j+1)swap(j,j+1); } }
|
要是不理解冒泡排序的思路可以看一下别人的博客或者视频不难,我主要说动态链表的排序,其实这里最好视频讲但现在没时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| void CFill::ETOrder() {
CAET* pT1 = NULL, * pT2 = NULL; int Count = 1; pT1 = pHeadE; if (pT1 == NULL) { return; } if (NULL == pT1->pNext) { return; } while (NULL != pT1->pNext) { Count++; pT1 = pT1->pNext; } for (int i = 1; i < Count; i++) { pT1 = pHeadE; if (pT1->x > pT1->pNext->x || (pT1->x == pT1->pNext->x && pT1->k > pT1->pNext->k)) { pT2 = pT1->pNext; pT1->pNext = pT1->pNext->pNext; pT2->pNext = pT1; pHeadE = pT2; } pT1 = pHeadE; while (pT1->pNext->pNext != NULL) { pT2 = pT1; pT1 = pT1->pNext; if (pT1->x > pT1->pNext->x || (pT1->x == pT1->pNext->x && pT1->k > pT1->pNext->k)) { pT2->pNext = pT1->pNext; pT1->pNext = pT1->pNext->pNext; pT2->pNext->pNext = pT1; pT1 = pT2->pNext; } } }
}
|
这样就排序完了
7.总流程
首先一共就四步,其他的什么桶表边表都是用来辅助优化的。
- 求交: 确定扫描线和多边形的交点
- 排序:把所有交点按递增顺序排序(按交点x值递增排序,确保交点两两配对时填充区间的正确性)
- 配对:确定填充区间
- 着色:着色规则有说法的,就我拿一个bool flag初始化为false,这个flag如果为false表示在多边形外面否则在里面,一开始为false,当你碰到一个交点的时候就代表从外进入到内,所以将flag=true,只要flag为true就涂色,因为当你碰到一个交点那你肯定要么是从外到内要么就是内到外,所以状态改变一下就好了
为了加快求交速度引入了有效边,为了知道何时插入有效边,且帮助排序引入了,桶表和边表,排序算法也是为了优化排序速度和稳定性,配对就是两两配对就是边界顶点根据点上面的有效边数确定它到底算几个点,然后就是着色。
这是我fill.h就是实现填充的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #pragma once #include "Bucket.h" #include"Point1.h" class CFill { public: CFill(); virtual ~CFill(); void SetPoint(Point1* p, int); void CreateBucket(); void CreateEdge(); void AddET(CAET*); void ETOrder(); void Gouraud(CDC*); void ClearMemory(); void DeleteAETChain(CAET* pAET); protected: int PNum; Point1* P; CAET* pHeadE, * pCurrentE, * pEdge; CBucket* pHeadB, * pCurrentB; };
|
fill.cpp源文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
| #include "pch.h" #include "Fill.h" #define Round(f) int(floor(f+0.5)) CFill::CFill() { PNum = 0; P = NULL; pEdge = NULL; pHeadB = NULL; pHeadE = NULL; }
CFill::~CFill() { if (P != NULL) { delete[] P; P = NULL; } ClearMemory(); }
void CFill::SetPoint(Point1* p, int m) { P = new Point1[m]; for (int i = 0; i < m; i++) { P[i]=p[i]; } PNum = m; }
void CFill::CreateBucket() { int yMin, yMax; yMin = yMax = P[0].y; for (int i = 0; i < PNum; i++) { if (P[i].y < yMin) { yMin = P[i].y; } if (P[i].y > yMax) { yMax = P[i].y; } } for (int y = yMin; y <= yMax; y++) { if (yMin == y) { pHeadB = new CBucket; pCurrentB = pHeadB; pCurrentB->ScanLine = yMin; pCurrentB->pET = NULL; pCurrentB->pNext = NULL; } else { pCurrentB->pNext = new CBucket; pCurrentB = pCurrentB->pNext; pCurrentB->ScanLine = y; pCurrentB->pET = NULL; pCurrentB->pNext = NULL; } } }
void CFill::CreateEdge() { for (int i = 0; i < PNum; i++) { pCurrentB = pHeadB; int j = (i + 1) % PNum; if (P[i].y < P[j].y) { pEdge = new CAET; pEdge->x = P[i].x; pEdge->yMax = P[j].y; pEdge->k = (P[j].x - P[i].x) / (P[j].y - P[i].y); pEdge->ps = P[i]; pEdge->pe = P[j]; pEdge->pNext = NULL; while (pCurrentB->ScanLine != P[i].y) { pCurrentB = pCurrentB->pNext; } } if (P[j].y < P[i].y) { pEdge = new CAET; pEdge->x = P[j].x; pEdge->yMax = P[i].y; pEdge->k = (P[i].x - P[j].x) / (P[i].y - P[j].y); pEdge->ps = P[i]; pEdge->pe = P[j]; pEdge->pNext = NULL; while (pCurrentB->ScanLine != P[j].y) { pCurrentB = pCurrentB->pNext; } } if (P[i].y != P[j].y) { pCurrentE = pCurrentB->pET; if (pCurrentE == NULL) { pCurrentE = pEdge; pCurrentB->pET = pCurrentE; } else { while (pCurrentE->pNext != NULL) { pCurrentE = pCurrentE->pNext; } pCurrentE->pNext = pEdge; } } } }
void CFill::Gouraud(CDC* pDC) { CAET* pT1 = NULL, * pT2 = NULL; pHeadE = NULL; for (pCurrentB = pHeadB; pCurrentB != NULL; pCurrentB = pCurrentB->pNext) { for (pCurrentE = pCurrentB->pET; pCurrentE != NULL; pCurrentE = pCurrentE->pNext) { pEdge = new CAET; pEdge->x = pCurrentE->x; pEdge->yMax = pCurrentE->yMax; pEdge->k = pCurrentE->k; pEdge->ps = pCurrentE->ps; pEdge->pe = pCurrentE->pe; pEdge->pNext = NULL; AddET(pEdge); } ETOrder(); pT1 = pHeadE; if (pT1 == NULL) return; while (pCurrentB->ScanLine >= pT1->yMax) { CAET* pAETTEmp = pT1; pT1 = pT1->pNext; delete pAETTEmp; pHeadE = pT1; if (pHeadE == NULL) return; } if (pT1->pNext != NULL) { pT2 = pT1; pT1 = pT2->pNext; }
while (pT1 != NULL) { if (pCurrentB->ScanLine >= pT1->yMax) { CAET* pAETTemp = pT1; pT2->pNext = pT1->pNext; pT1 = pT2->pNext; delete pAETTemp; } else { pT2 = pT1; pT1 = pT2->pNext; } } BOOL bInFlag = FALSE; double xb, xe; for (pT1 = pHeadE; pT1 != NULL; pT1 = pT1->pNext) { if (FALSE == bInFlag) { xb = pT1->x; bInFlag = TRUE; } else { xe = pT1->x; for (double x = xb; x < xe; x++) { pDC->SetPixelV(Round(x), pCurrentB->ScanLine, RGB(255,0,0)); } bInFlag = FALSE; } } for (pT1 = pHeadE; pT1 != NULL; pT1 = pT1->pNext) { pT1->x = pT1->x + pT1->k; } } }
void CFill::AddET(CAET* pNewEdge) { CAET* pCE = pHeadE; if (pCE == NULL) { pHeadE = pNewEdge; pCE = pHeadE; } else { while (pCE->pNext != NULL) { pCE = pCE->pNext; } pCE->pNext = pNewEdge; } }
void CFill::ETOrder() {
CAET* pT1 = NULL, * pT2 = NULL; int Count = 1; pT1 = pHeadE; if (NULL == pT1) { return; } if (NULL == pT1->pNext) { return; } while (NULL != pT1->pNext) { Count++; pT1 = pT1->pNext; } for (int i = 1; i < Count; i++) { pT1 = pHeadE; if (pT1->x > pT1->pNext->x || (pT1->x == pT1->pNext->x && pT1->k > pT1->pNext->k)) { pT2 = pT1->pNext; pT1->pNext = pT1->pNext->pNext; pT2->pNext = pT1; pHeadE = pT2; } pT1 = pHeadE; while (pT1->pNext->pNext != NULL) { pT2 = pT1; pT1 = pT1->pNext; if (pT1->x > pT1->pNext->x || (pT1->x == pT1->pNext->x && pT1->k > pT1->pNext->k)) { pT2->pNext = pT1->pNext; pT1->pNext = pT1->pNext->pNext; pT2->pNext->pNext = pT1; pT1 = pT2->pNext; } } }
}
void CFill::ClearMemory() { DeleteAETChain(pHeadE); CBucket* pBucket = pHeadB; while (pBucket != NULL) { CBucket* pBucketTemp = pBucket->pNext; DeleteAETChain(pBucket->pET); delete pBucket; pBucket = pBucketTemp; } pHeadB = NULL; pHeadE = NULL; }
void CFill::DeleteAETChain(CAET* pAET) { while (pAET != NULL) { CAET* pAETTemp = pAET->pNext; delete pAET; pAET = pAETTemp; } }
|
三.种子填充算法
1.DFS(栈)
2.BFS(队列)
3.基础剪枝