图形学入门(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 分类 分类计数预测的数据对象是离散的.如短信是否为垃圾短信,用 ...
随机推荐
- Vue 获取页面后跳转其他页面
<template> <div> <img :src="detail.img" /> <h1>{{ detail.title }}& ...
- 006——C#关闭窗口 添加FormClosing事件
(一)参考文献:C# 添加FormClosing事件 (二)在 项目名.Designer.cs中的InitializeComponent()添加: this.FormClosing += new S ...
- 字符串转换json格式
前台json转字符串传递后台时 用到: data: JSON.stringify({ "zh": zhanghao, "mm": mima }), 当后台返回前 ...
- exam9.6&&7
emmm 改题稍紧张,以后几篇并一起写 9.6 (前十并没有参加本次考试) 于是我就rank8了 一道题一道题来 先说T1: 显然是一个高精度GCD,于是打算用计算器算一下时间复杂度 众所周知gcd是 ...
- NetworkX系列教程(10)-算法之四:拓扑排序与最大流问题
小书匠Graph图论 重头戏部分来了,写到这里我感觉得仔细认真点了,可能在NetworkX中,实现某些算法就一句话的事,但是这个算法是做什么的,用在什么地方,原理是怎么样的,不清除,所以,我决定先把图 ...
- Linux find,grep 命令
使用实验楼Linux环境开发,部分内容有所参考,link:https://www.shiyanlou.com/ 概述: find: 在目录中搜索文件,它的使用权限是所有用户 命令格式: find [路 ...
- LBA逻辑块地址
LBA简介 磁盘读取发展 IO操作读取硬盘的三种方式: chs方式 :小于8G (8064MB) LBA28方式:小于137GB LBA48方式:小于144,000,000 GB LBA方式访问使用了 ...
- shiro 配置注解后无权访问不进行页面跳转异常:org.apache.shiro.authz.UnauthorizedException: Subject does not have permission
该问题需要使用异常管理: <!-- 无权访问跳转的页面 --> <bean class="org.springframework.web.servlet.handler.S ...
- Qt学习大全
这边文章的目的是把自己之前写的关于Qt的文章整理归纳成一个Qt学习的专栏,会提供之前文章的导航,同时也会留一些坑待自己日后填. 1.Qt 元对象系统 2.Qt的信号和槽 3.Qt的插件开发 4.Qml ...
- DES算法实现
概述(团队项目) DES是一个分组加密算法,它以64位为分组对数据加密.同时DES也是一个对称算法:加密和解密用的是同一个算法.DES是一个包含16个阶段的"替换–置换"的分组加密 ...