- 扫描转换矩形
- 扫描转换多边形
- 逐点判断法
- 扫描线算法
- 边缘填充算法
- 扫描转换扇形
- 区域填充(种子填充法)
- 递归填充算法
- 扫描线算法
- 以图像填充区域
- 字符的表示与输出
区域填充(种子填充法)
区域
- 以点阵表示的图形, 是像素的集合
区域的表示
- 内点表示
- 区域内部的所有像素着同一个颜色
- 内部像素与边界像素不同颜色
- 边界表示
- 区域边界的所有像素着同一个颜色
- 内部像素与边界像素不同颜色
区域的连通性
- 四连通
- 一个像素点能够访问到
上下左右
四个方位的像素- 四连通的要求比八连通高, 下图为四连通区域
- 八连通
- 一个像素点能访问到
上下左右
以及对角线
的八个方位的像素- 下图为八连通区域
四连通与八连通区别
四连通区域是可以视为八连通区域的, 但是在 边界 的处理上有所区别
四连通区域看做四连通区域时, 边界是八连通 就可以了
四连通区域看做八连通区域时, 边界必须是四连通的, 否则会造成填充泄露
以下图为例,
△
区域只有在区域看做八连通时才是边界必须要包含的
递归填充算法
对于内点表示的四连通区域, 递归的种子填充算法如下
void FloodFill4(int x,int y,int oldColor,int newColor)
{ //区域原来填充着旧色
if(GetPixel(x,y) == oldColor) //检测当前点,如颜色为旧色
{
PutPixel(x,y,newColor); //上新色
FloodFill4(x,y+1,oldColor,newColor); //检测上点
FloodFill4(x,y-1,oldColor,newColor); //检测下点
FloodFill4(x-1,y,oldColor,newColor); //检测左点
FloodFill4(x+1,y,oldColor,newColor); //检测右点
}
}
对于边界表示的四连通区域, 递归的种子填充算法如下
void BoundaryFill4(int x,int y,int boundaryColor,int newColor)
{ //x,y种子点
int color;
color = GetPixel(x,y);
if((color != boundaryColor) && (color != newColor))
{ // 前者说明已画到边界,后者说明该点已画过
PutPixel(x,y,newColor);
BoundaryFill4(x,y+1, boundaryColor,newColor);
BoundaryFill4(x,y-1, boundaryColor,newColor);
BoundaryFill4(x-1,y, boundaryColor,newColor);
BoundaryFill4(x+1,y, boundaryColor,newColor);
}
}
内点表示与边界表示的八连通区域的填充算法就是在以上两个算法的基础上添加其他四个方向的递归调用
扫描线算法
基于扫描线的种子填充算法 是递归的种子填充算法的改进
- 减少了递归次数, 提高算法的效率
内点表示的四连通区域的基于扫描线的种子区域填充算法
算法思路
当给定种子点
(x,y)
时
填充种子点所在的扫描线上的位于给定区域的一个区段
确定与这一区段相通的上下两条扫描线上位于给定区域内的区段,并依次保存下来
反复这个过程,直到填充结束
算法步骤
- 初始化:堆栈置空, 将种子点
(x,y)
入栈- 出栈:若栈空则结束, 否则取栈顶元素
(x,y)
, 以Y = y
作为当前扫描线- 填充并确定种子点所在区段:从种子点
(x,y)
出发, 沿当前扫描线向左、右两个方向填充, 直到边界. 分别标记区段的左、右端点坐标为xl
和xr
- 确定新的种子点:在区间
[xl,xr]
中检查与当前扫描线y上、下相邻的两条扫描线上的象素. 若存在非边界、未填充的象素, 则把 每一区间的 最右象素 作为种子点压入堆栈, 返回第2
步
#include <stack>
struct Seed {
int x;
int y;
};
void ScanLineFill4(int x, int y, int oldColor, int newColor) {
std::stack<Seed> s;
Seed pt;
pt.x = x;
pt.y = y;
s.push(pt); // 将种子点压入堆栈
while (!s.empty()) { // 栈非空时循环
pt = s.top();
s.pop();
x = pt.x;
y = pt.y;
int xl, xr;
// 向右填充
while (GetPixel(x, y) == oldColor) {
PutPixel(x, y, newColor);
x++;
}
xr = x - 1; // 记录最右点
// 向左填充
x = pt.x - 1;
while (GetPixel(x, y) == oldColor) {
PutPixel(x, y, newColor);
x--;
}
xl = x + 1; // 记录最左点
// 处理上方扫描线
x = xl;
y = pt.y + 1;
while (x <= xr) {
bool spanNeedFill = false;
while (GetPixel(x, y) == oldColor && x <= xr) {
spanNeedFill = true;
x++;
}
if (spanNeedFill) {
s.push({x - 1, y}); // 将种子点压栈
}
while (GetPixel(x, y) != oldColor && x <= xr) {
x++;
}
}
// 处理下方扫描线
x = xl;
y = pt.y - 1;
while (x <= xr) {
bool spanNeedFill = false;
while (GetPixel(x, y) == oldColor && x <= xr) {
spanNeedFill = true;
x++;
}
if (spanNeedFill) {
s.push({x - 1, y}); // 将种子点压栈
}
while (GetPixel(x, y) != oldColor && x <= xr) {
x++;
}
}
}
}
以图像填充区域
填充图元的四种方式
- 均匀着色方式
- 将图元内部像素置成同一颜色
- 位图不透明方式
- 若像素对应的位图单元为1,则以前景色显示该像素;若为0,则以背景色显示该像素
- 位图透明方式
- 若像素对应的位图单元为1,则以前景色显示该像素;若为0,则不做任何处理
- 像素图填充方式
- 以像素对应的像素图单元的颜色值显示该像素
建立区域与图像的对应关系是以像素图填充方式的关键, 有两种方式
- 整个绘图空间与图像空间
1-1
映射- 局部坐标系和图像空间
1-1
映射
**方法1 **- 整个绘图空间与图像空间 1-1
映射
图像 P
尺寸为 M×N
,将其瓦块式排列得图像空间 (u,v)
绘图空间 (x,y)
与图像空间 (u,v)
的 1-1
映射 x = u, y = v
,则 (x,y)
的颜色值为 P
上 (x%M)(y%N)
, 如下图所示
- 区域运动时,其内部的图像不动
- 适用于动画漫游, 如透过车窗看景色
方法2 - 局部坐标系和图像空间 1-1
映射
如下图所示
以矩形角点 A
为原点建立局部坐标系,其坐标 (x',y')
与图像空间 (u,v)
的 1-1映射为x' = u,y' = v
对区域内像素 (x,y)
,先求局部坐标 (x',y')
,再与该像素对应的颜色P(x'%M)(y'%N)
显示
- 区域运动时,局部坐标系跟着动,内部图像也动
- 适用于图像作为区域表面属性的情况,例如桌面上的木纹
字符的表示与输出
字符分为 点阵字符 和 矢量字符
点阵字符
- 每个字符由一个位图表示,并把它用一个称为字符掩膜的矩阵来表示
- 点阵字符的显示分为两步
- 首先从字库中将它的位图检索出来
- 然后将检索到的位图写到帧缓冲器中
- 在实际应用中,同一个字符有多种字体(如宋体、楷体等),每种字体又有多种大小型号,因此字库的存储空间十分庞大
- 为了减少存储空间,一般采用压缩技术
矢量字符
- 矢量字符记录字符的笔画信息而不是整个位图
- 具有存储空间小,美观、变换方便等优点
- 对于字符的旋转、放大、缩小等几何变换,矢量字符只需要对其几何图素进行变换就可以了
- 矢量字符的显示也分为两步
- 首先从字库中将它的字符信息,然后取出端点坐标
- 对其进行适当的几何变换, 再显示字符。
- 轮廓字形法是当今国际上最流行的一种字符表示方法,其压缩比大,且能保证字符质量