图形学入门(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 分类 分类计数预测的数据对象是离散的.如短信是否为垃圾短信,用 ...
随机推荐
- UWB DWM1000 开源项目框架
UWB 目前比较火热,不论国内还是国外目前都掀起一股热潮. 但是实际工程代码很少,开源代码更少. 目前代码主要有 1 DecaWave Release的定位源码,代码基于TWR,一个非常大的状态机. ...
- sql server 游标的基本用法
DECLARE @Id NVARCHAR(MAX) DECLARE @UserName NVARCHAR(MAX) DECLARE @Password NVARCHAR(MAX) DECLARE @N ...
- 将Eclipse,MyEclipse等编辑器的项目管理框颜色改为护眼豆沙绿的方法
转载链接:https://blog.csdn.net/caibaoH/article/details/77005977
- mybatis执行insert后马上能获取自增主键的语句写法
<!--keyColumn keyProperty useGeneratedKeys 用于在插入数据后,能直接使用user.getId()获取主键--> <insert id=&qu ...
- LibreOJ #6217. 扑克牌
二次联通门 : LibreOJ #6217. 扑克牌 /* LibreOJ #6217. 扑克牌 背包.... 回到家之后简直了...sb题想半天 */ #include <cstdio> ...
- 基于Shiro的登录功能 设计思路
认证流程 Shiro的认证流程可以看作是个“有窗户黑盒”, 整个流程都有框架控制,对外的入口只有subject.login(token);,这代表“黑盒” 流程中的每一个组件,都可以使用Spring ...
- codeforces#1257 F. Make Them Similar ( 经典中间相遇问题 )
题目链接: http://codeforces.com/contest/1257/problem/F 题意: 给出$n$个30位整数 找到一个数,让它与这$n$个数分别异或,得到的$n$个数二进制1的 ...
- Ansible 模式
一.Ansible 命令 1.Ansible 命令执行的方式有两种:Ad-Hoc.Ansible-playbooks,这两种方式没有本质的区别,Ad-Hoc用于临时执行命令:Ansible-playb ...
- Tkinter 之ListBox列表标签
一.参数说明 参数 作用 background (bg) 设置背景颜色 borderwidth (bd) 指定 Listbox 的边框宽度,通常是 2 像素 cursor 指定当鼠标在 Listbo ...
- vue 页面间传值
使用params传参 ,不能使用path 只能使用name 使用params传参,刷新参数会消失 router/index.js import Vue from 'vue' import Router ...