继续图形学之旅,我们已经解决了如何画线和画圆的问题,接下来要解决的是,如何往一个区域内填充颜色?对一个像素填充颜色只需调用SetPixel之类的函数就行了,所以这个问题其实就是:如何找到一个区域内的所有像素?

区域的表示方法

定义一个区域可以有两种方法,即内点表示法边界表示法,内点表示就是指用一种颜色表示区域内的点,只要当前像素是这种颜色就在区域内,边界表示就是用一种颜色表示区域边界,只要当前像素是这种颜色就表示到达了区域边界。

简单的种子填充算法

最简单暴力的填充算法即是从区域内一点出发,向四周扩散填充,到达区域边界时停止,常见的有四邻法八邻法两种,顾名思义,一个是向上下左右四个方向扩散填充,另一个是向周围八个方向扩散,四邻法可以确保不溢出区域边界,但有可能出现一次填不满区域的情况,八邻法则相反,一定能填充满当前区域,但有从对角线溢出边界的危险。

边界表示的四邻法代码实现:

void BoundaryFill4(HDC hdc, int x, int y, COLORREF boundaryColor, COLORREF newColor)
{
COLORREF c = GetPixel(hdc, x, y);
if (c != newColor && c != boundaryColor)
{
SetPixel(hdc, x, y, newColor);
BoundaryFill4(hdc, x + 1, y, boundaryColor, newColor);
BoundaryFill4(hdc, x - 1, y, boundaryColor, newColor);
BoundaryFill4(hdc, x, y + 1, boundaryColor, newColor);
BoundaryFill4(hdc, x, y - 1, boundaryColor, newColor);
}
}

我们用之前学习的Bresenham画线算法画一个矩形,然后用这个算法填充它。

	Bresenham_Line(150, 150, 150, 200, hdc, RGB(0, 0, 0));
Bresenham_Line(150, 200, 200, 200, hdc, RGB(0, 0, 0));
Bresenham_Line(200, 200, 200, 150, hdc, RGB(0, 0, 0));
Bresenham_Line(200, 150, 150, 150, hdc, RGB(0, 0, 0));
BoundaryFill4(hdc, 175, 175, RGB(0, 0, 0), RGB(255, 0, 0));

运行效果:

很显然,这种递归的填充算法简单好理解,但效率是不可接受的,实际上我运行时填充100*100像素的区域就直接GG了(堆栈溢出),显然我们需要提高算法效率,避免过多的递归调用。

扫描线种子填充算法

为了提高效率可以使用扫描线种子填充算法,这里的扫描线就是与x轴相平行的线,该算法可以由以下4个步骤实现:

  1. 初始化:堆栈置空,将初始种子点(x,y)入栈
  2. 出栈:若栈空则算法结束,否则取栈顶元素(x,y),以y作为当前扫描线
  3. 填充并确定种子点所在区段:从种子点(x,y)出发,向左右两个方向填充,直到边界。标记区段左右断点为xl和xr。
  4. 确定新的种子点:在区间[xl,xr]中检查与当前扫描线y上下相邻的两条扫描线上的像素。若存在非边界、未填充像素,则把每一区间最右像素作为种子点压入堆栈,返回第二步。

代码实现:

void ScanLineFill4(HDC hdc, int x, int y, COLORREF oldColor, COLORREF newColor)
{
int xl, xr;
bool SpanNeedFill;
pair<int, int> seed;
stack<pair<int, int>> St; seed.first = x; seed.second = y;
St.push(seed);
while (!St.empty())
{
seed = St.top();
St.pop();
y = seed.second;
x = seed.first; while (GetPixel(hdc,x,y) == oldColor)//向右填充
{
SetPixel(hdc, x, y, newColor);
x++;
}
xr = x - 1;
x = seed.first - 1;
while (GetPixel(hdc, x, y) == oldColor)//向左填充
{
SetPixel(hdc, x, y, newColor);
x--;
}
xl = x + 1; //处理上方的一条扫描线
x = xl;
y = y + 1;
while (x<xr)
{
SpanNeedFill = false;
while (GetPixel(hdc,x,y)==oldColor)
{
SpanNeedFill = true;
x++;
}
if (SpanNeedFill)
{
seed.first = x - 1; seed.second = y;
St.push(seed);
SpanNeedFill = false;
}
while (GetPixel(hdc, x, y) != oldColor && x < xr)x++;
} //处理下方的一条扫描线
x = xl;
y = y - 2;
while (x < xr)
{
SpanNeedFill = false;
while (GetPixel(hdc, x, y) == oldColor)
{
SpanNeedFill = true;
x++;
}
if (SpanNeedFill)
{
seed.first = x - 1; seed.second = y;
St.push(seed);
SpanNeedFill = false;
}
while (GetPixel(hdc, x, y) != oldColor && x < xr)x++;
}
}
}

这次画一个不太规则的图形试试吧。

Bresenham_Line(100, 100, 150, 150, hdc, RGB(0, 0, 0));
Bresenham_Line(150, 150, 200, 100, hdc, RGB(0, 0, 0));
Bresenham_Line(200, 100, 200, 300, hdc, RGB(0, 0, 0));
Bresenham_Line(200, 300, 100, 300, hdc, RGB(0, 0, 0));
Bresenham_Line(100, 300, 100, 100, hdc, RGB(0, 0, 0));
ScanLineFill4(hdc, 150, 175, RGB(255, 255, 255), RGB(0, 255, 0));

这次由于对每一个待填充区段只需要压栈一次,所以效率提高了,也没有堆栈溢出的危险,但说实话上面的填充进行了接近十秒钟才完成,如果画图软件使用这种填充算法估计是没人会用了吧......

有序边表的扫描线算法

接下来是最复杂的一种的扫描线算法,需要多边形的所有边信息,主要思想是求得每一条扫描线与多边形的交点,从而两两配对得到处在多边形内的区间,对这些区间进行上色,但要求扫描线与多边形的交点,直接暴力地遍历每条边肯定是不可行的,我们需要引入活性边表AET新边表NET来辅助计算。

NET中存放的是在该扫描线第一次出现的边,也就是最低端点的y值等于当前扫描线位置的边,对每一个结点,需要存储当前x值、直线斜率倒数和直线最高点y值,如下图所示:

通过NET就可以容易地得到AET,AET中存放的是扫描线与多边形的交点。我们从下往上遍历每条扫描线,对于扫描线i来说,将NET[i]中结点插入,将AET[i-1]中ymax=i的结点删除,其余结点将x值加上斜率倒数之后插入,就得到了AET[i]。

有了AET之后,只需要配对每两个交点,把区间内像素上色就可以了,但要注意,由于要从左到右配对,所以

AET表应时刻保持按x坐标递增排序。

伪代码:

void PolyFill(polygon,color)
{
初始化新边表NET和活性边表AET;
for(每条扫描线i)
{
把ymin=i的边放进边表NET[i];
}
for(每条扫描线i)
{
把新边表NET[i]中结点插入AET[i](x坐标递增有序排列);
AET[i-1]中ymax!=i的结点加入AET[i];
遍历AET[i],把配对交点区间中像素上色;
}
}

边界标志算法

还有一种基于扫描线思想的边界标志算法,比较适合用硬件实现。基本思想是对多边形每条边进行扫描转换,找到多边形边界的所有像素,对每条与多边形相交的扫描线按从左到右的顺序扫描每个像素,用一个布尔值inside表示当前点是否在多边形内(初始为false),只要扫描到多边形边界像素,就把inside取反,若inside为真,则表示该点在多边形内,则填充该像素。

伪代码:

void edgemark_fill(polydef,color)
{
对多边形每条边扫描转换;
inside=false;
for(每条扫描线)
{
for(扫描线上每个像素)
{
if(该像素是边界像素)
inside=!inside;
else if(inside==true)
SetPixel(x,y,color);
}
}
}

图形学入门(3)——区域填充算法(region filling)的更多相关文章

  1. openGL实现图形学扫描线种子填充算法

    title: "openGL实现图形学扫描线种子填充算法" date: 2018-06-11T19:41:30+08:00 tags: ["图形学"] cate ...

  2. Python3入门机器学习经典算法与应用

    <Python3入门机器学习经典算法与应用> 章节第1章 欢迎来到 Python3 玩转机器学习1-1 什么是机器学习1-2 课程涵盖的内容和理念1-3 课程所使用的主要技术栈第2章 机器 ...

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

    总体介绍 1.   使用线性扫描算法画一条线,线性离散点 2.   利用区域填充算法画多边形区域,区域离散的点 开发环境VS2012+OpenGL 开发平台 Intel core i5,Intel H ...

  4. Python3入门机器学习经典算法与应用☝☝☝

    Python3入门机器学习经典算法与应用 (一个人学习或许会很枯燥,但是寻找更多志同道合的朋友一起,学习将会变得更加有意义✌✌) 使用新版python3语言和流行的scikit-learn框架,算法与 ...

  5. Python3入门机器学习经典算法与应用✍✍✍

    Python3入门机器学习经典算法与应用 整个课程都看完了,这个课程的分享可以往下看,下面有链接,之前做java开发也做了一些年头,也分享下自己看这个视频的感受,单论单个知识点课程本身没问题,大家看的 ...

  6. 机器学习入门:K-近邻算法

    机器学习入门:K-近邻算法 先来一个简单的例子,我们如何来区分动作类电影与爱情类电影呢?动作片中存在很多的打斗镜头,爱情片中可能更多的是亲吻镜头,所以我们姑且通过这两种镜头的数量来预测这部电影的主题. ...

  7. 图形学入门(1)——直线生成算法(DDA和Bresenham)

    开一个新坑,记录从零开始学习图形学的过程,现在还是个正在学习的萌新,写的不好请见谅. 首先从最基础的直线生成算法开始,当我们要在屏幕上画一条直线时,由于屏幕由一个个像素组成,所以实际上计算机显示的直线 ...

  8. Java入门与基础算法班 - 课程大纲

    第1章 零基础转CS,如何准备? · 转专业找CS工作怎么办? · 零基础如何在最短时间内拿到offer? · 如何写好简历? · IT技术面试内容有哪些? · JAVA语言怎么入门? 第2章 数组与 ...

  9. 机器学习入门KNN近邻算法(一)

    1 机器学习处理流程: 2 机器学习分类: 有监督学习 主要用于决策支持,它利用有标识的历史数据进行训练,以实现对新数据的表示的预测 1 分类 分类计数预测的数据对象是离散的.如短信是否为垃圾短信,用 ...

随机推荐

  1. 55、servlet3.0-ServletContext注册三大组件

    55.servlet3.0-ServletContext注册三大组件 使用ServletContext 注册 Servlet.Filter.Listener 使用编码的方式,在项目启动的时候给 Ser ...

  2. 使用AJAX传输不了值时

    当时候AJAX往后台传递值时  传不到后台  这时我们就要检查程序是否有问题了 一般AJAX程序 $.ajax( { type: "POST", url: "Login. ...

  3. C/C++ -- 判断字符串中存在中文

    电脑系统中的英文字符串和中文字符最根本的区别就在于: 1.英文的 ASCII 码,其最高位为 0,占一个字节 注:英文的ASCII码范围是在0到127,二进制为(0000 0000 ~ 0111 11 ...

  4. 2019 ICPC Asia Yinchuan Regional

    目录 Contest Info Solutions A. Girls Band Party B. So Easy D. Easy Problem E. XOR Tree F. Function! G. ...

  5. TensorFlow(十):卷积神经网络实现手写数字识别以及可视化

    上代码: import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data mnist = inpu ...

  6. 扩展kmp学习笔记

    kmp没写过,扩展kmp没学过可还行. 两个愿望,一次满足 (该博客仅用于防止自己忘记,不保证初学者能看懂我在瞎bb什么qwq) 用途 对于串\(s1,s2\),可以求出\(s2\)与\(s1\)的每 ...

  7. (转)服务端监控工具:nmon的使用

    在性能测试过程中,对服务端的各项资源使用情况进行监控是很重要的一环.这篇博客,介绍下服务端监控工具:nmon的使用方法.. 一.认识nmon 1.简介 nmon是一种在AIX与各种Linux操作系统上 ...

  8. IdentityServer4入门四:应用Implicit模式保护网站(上)

    我们先新增一个网站,名为“ClientMvc",也是asp.net core Web应用程序(模型视图控制器) 使用nuget安装以下引用 Microsoft.AspNetCore.Auth ...

  9. tcp流式传输和udp数据报传输

    所有的书上都说, tcp是流式传输, 这是什么意思? 假设A给B通过TCP发了200字节, 然后又发了300字节, 此时B调用recv(设置预期接受1000个字节), 那么请问B实际接受到多少字节? ...

  10. Vue源码分析(一) : new Vue() 做了什么

    Vue源码分析(一) : new Vue() 做了什么 author: @TiffanysBear 在了解new Vue做了什么之前,我们先对Vue源码做一些基础的了解,如果你已经对基础的源码目录设计 ...