图形学入门(3)——区域填充算法(region filling)
继续图形学之旅,我们已经解决了如何画线和画圆的问题,接下来要解决的是,如何往一个区域内填充颜色?对一个像素填充颜色只需调用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个步骤实现:
- 初始化:堆栈置空,将初始种子点(x,y)入栈
- 出栈:若栈空则算法结束,否则取栈顶元素(x,y),以y作为当前扫描线
- 填充并确定种子点所在区段:从种子点(x,y)出发,向左右两个方向填充,直到边界。标记区段左右断点为xl和xr。
- 确定新的种子点:在区间[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)的更多相关文章
- openGL实现图形学扫描线种子填充算法
title: "openGL实现图形学扫描线种子填充算法" date: 2018-06-11T19:41:30+08:00 tags: ["图形学"] cate ...
- Python3入门机器学习经典算法与应用
<Python3入门机器学习经典算法与应用> 章节第1章 欢迎来到 Python3 玩转机器学习1-1 什么是机器学习1-2 课程涵盖的内容和理念1-3 课程所使用的主要技术栈第2章 机器 ...
- opengl实现直线扫描算法和区域填充算法
总体介绍 1. 使用线性扫描算法画一条线,线性离散点 2. 利用区域填充算法画多边形区域,区域离散的点 开发环境VS2012+OpenGL 开发平台 Intel core i5,Intel H ...
- Python3入门机器学习经典算法与应用☝☝☝
Python3入门机器学习经典算法与应用 (一个人学习或许会很枯燥,但是寻找更多志同道合的朋友一起,学习将会变得更加有意义✌✌) 使用新版python3语言和流行的scikit-learn框架,算法与 ...
- Python3入门机器学习经典算法与应用✍✍✍
Python3入门机器学习经典算法与应用 整个课程都看完了,这个课程的分享可以往下看,下面有链接,之前做java开发也做了一些年头,也分享下自己看这个视频的感受,单论单个知识点课程本身没问题,大家看的 ...
- 机器学习入门:K-近邻算法
机器学习入门:K-近邻算法 先来一个简单的例子,我们如何来区分动作类电影与爱情类电影呢?动作片中存在很多的打斗镜头,爱情片中可能更多的是亲吻镜头,所以我们姑且通过这两种镜头的数量来预测这部电影的主题. ...
- 图形学入门(1)——直线生成算法(DDA和Bresenham)
开一个新坑,记录从零开始学习图形学的过程,现在还是个正在学习的萌新,写的不好请见谅. 首先从最基础的直线生成算法开始,当我们要在屏幕上画一条直线时,由于屏幕由一个个像素组成,所以实际上计算机显示的直线 ...
- Java入门与基础算法班 - 课程大纲
第1章 零基础转CS,如何准备? · 转专业找CS工作怎么办? · 零基础如何在最短时间内拿到offer? · 如何写好简历? · IT技术面试内容有哪些? · JAVA语言怎么入门? 第2章 数组与 ...
- 机器学习入门KNN近邻算法(一)
1 机器学习处理流程: 2 机器学习分类: 有监督学习 主要用于决策支持,它利用有标识的历史数据进行训练,以实现对新数据的表示的预测 1 分类 分类计数预测的数据对象是离散的.如短信是否为垃圾短信,用 ...
随机推荐
- postgresql slow sql report 慢查询报告
其实也不完全算PostgreSQL的技巧,应该算是SHELL技巧. 首先要配置好postgresql.conf,让PG记录下慢查询,并且日志固定格式,如下: log_destination = 'cs ...
- surprise库官方文档分析(二):使用预测算法
1.使用预测算法 Surprise提供了一堆内置算法.所有算法都派生自AlgoBase基类,其中实现了一些关键方法(例如predict,fit和test).可以在prediction_algorith ...
- Java面向对象6 (AA - AE)
整理音乐(SDUT 2053) import java.util.*; public class Main { public static void main(String[] args) { Sca ...
- springBoot学习(二):初学JPA
依旧跟着大佬学习JPA,在这里记下过程. JPA主要做的事情是简化数据库操作,至少现在为止,我觉得还是挺方便简单明了的.在原本代码的基础下,更改UserController.User.UserRepo ...
- 搭建vue-cli
https://www.cnblogs.com/wisewrong/p/8570309.html https://www.jianshu.com/p/1ee1c410dc67
- excel被保护或者锁定时候按住alt和enter可以输入换行
excel被保护或者锁定时候按住alt和enter可以输入换行
- エンジニア死滅シタ世界之学べない学校 [MISSION LEVEL: C]-Python3
答案 # coding: utf-8 # 自分の得意な言語で # Let's チャレンジ!! N=input() w_a=0 w_b=0 gpc_dict={ "gg":0,&qu ...
- 使用SQL中的update更新多个字段值
使用SQL中的update更新多个字段值,set后面的条件要用逗号不能用and set后面的多个条件之间没有关联也不可以有关联,所以就不能用and了:where 条件后面 可以为and 如: upda ...
- JavaScript中获取html元素常用手法和区分
对于许多前端开发项目来说,获取元素进行操作是必不可少的,例如tab标签,全屏切换,自动滚播等效果都需要通过获取节点元素来实现.下面我来总结下JavaScript最常用的4个Document对象中获取元 ...
- 如何在Windows Server 2008服务器中把Tomcat启动程序添加到服务中
转自:https://blog.51cto.com/zdytesting/2314093 tomcat所在的bin目录: 添加服务: service install service_name 移除服务 ...