780 likes | 1.15k Views
第 4 章 二维填充图元的生成. 本章目标. 掌握如何绘制填充图元(矩形、多边形等) 扫面转换算法(扫描线算法) 填充算法 学会使用 OpenGL 的绘制函数. 主要内容. 填充图元 扫描转换矩形 扫描转换多边形 扫描转换扇形区域 区域填充 以图像填充区域 字符的表示与输出 OpenGL 相关函数. 4.1 填充图元. 填充( Filling ) 矩形 (Rectangle) 多边形 (Polygon) 扇形 (Ellipse Arc) 步骤 确定那些像素位于填充图元的内部 用指定颜色绘制这些像素 两类方法
E N D
本章目标 • 掌握如何绘制填充图元(矩形、多边形等) • 扫面转换算法(扫描线算法) • 填充算法 • 学会使用OpenGL的绘制函数
主要内容 • 填充图元 • 扫描转换矩形 • 扫描转换多边形 • 扫描转换扇形区域 • 区域填充 • 以图像填充区域 • 字符的表示与输出 • OpenGL相关函数
4.1 填充图元 • 填充(Filling) • 矩形(Rectangle) • 多边形(Polygon) • 扇形(Ellipse Arc) • 步骤 • 确定那些像素位于填充图元的内部 • 用指定颜色绘制这些像素 • 两类方法 • 扫描转换(Scan Converting): 参数->点阵 • 填充(Filling) : 点阵->点阵
4.2 扫描转换矩形 • 含义 • 用指定颜色填充矩形内部区域 • 定义矩形的参数 • 左下角坐标(xmin, ymin) • 右上角坐标(xmax, ymax) (xmax, ymax) (xmin, ymin) • void FillRectangle(Rectangle *rect,int color) • { int x,y; • for(y = rect->ymin; y <= rect->ymax; y++) • for(x = rect->xmin; x <= rect->xmax; x++) • SetPixel(x, y, color); • }/*end of FillRectangle() */
4.2 扫描转换矩形 • 问题 • 矩形是简单的多边形,那么为什么要单独处理矩形? • 比一般多边形可简化计算 • 应用非常多,如窗口系统 • 共享边界如何处理? • 原则:左闭右开,下闭上开 属于谁?
4.3 扫描转换多边形 • 多边形的表示方法 • 顶点表示 顶点序列P0P1P2…Pn • 点阵表示 • 扫描转换多边形:将顶点表示形式转换成点阵表示形式 • 三种方法:逐点判断法;扫描线算法;边缘填充法 • 多边形分类(只考虑:简单多边形,即多边形边不自相交) • 凸多边形(convex):内角小于180度 • 凹多边形(concave):存在内角大于180度
4.3 扫描转换多边形 如何识别多边形的凸凹性 方法1:观察多边形边的延长线是否划分顶点在两侧 方法2:向量的叉积 每条边建立一个向量,测试相邻边的叉积z坐标的正负 (1)如果叉积同号,那么是凸多边形 (2)如果叉积不同号,那么是凹多边形 (E1×E2 )z > 0 (E2×E3 )z > 0 (E3×E4 )z < 0 (E4×E5)z > 0 (E5×E6 )z > 0 (E6×E1 )z > 0
4.3 扫描转换多边形 向量叉积(Cross Product of Two Vector) 当a与b为二维向量时,a×b矢量中x,y分量为0
P1 P2 4.3.1 逐点判断法 • 基本原理 • 判断绘图窗口内的像素是否位于多边形内,若是,则用指定颜色绘制该像素 • 问题 • 如何判断点在多边形的内外关系? • 射线法 • 累计角度法* • 编码法* (xmax, ymax) (xmin, ymin)
4.3.1 逐点判断法 • 算法 • 假设判断点是否在多边形内的函数为IsInside() #define MAX 100 typedef struct { // 多边形顶点个数 int PolygonNum; //多边形顶点数组 Point vertexces[MAX] } Polygon; // 多边形结构 void FillPolygon(Polygon *P, int polygonColor) { int x,y; for(y = ymin;y <= ymax;y++) for(x = xmin;x <= xmax;x++) if(IsInside(P,x,y)) SetPixel(x, y, polygonColor); else SetPixel(x,y,backgroundColor); }/*end of FillPolygonPbyP() */
p1 v1 p2 p3 4.3.1 逐点判断法 • 判断点是否在多边形内-射线法 步骤 • 从待判别点 v 发出射线 • 求与多边形交点个数 k • k 的奇偶性决定了点与多边形的内外关系 • 偶数:外 • 奇数:内 v2
4.3.1 逐点判断法 • 判断点是否在多边形内-射线法 奇异情况 • 射线在边上:无数个点 判断是否与边同线 • 交点为顶点:算几个? • 异侧:1个 • 同侧:2个
4.3.1 逐点判断法 • 逐点判断扫描转换方法 • 特点 • 程序简单 • 测试点是否在多边形内的算法速度太慢,效率低 • 改进 • 逐点判断法孤立考虑各个像素与多边形的内外关系 • 利用内部点的连续性
4.3.1 逐点判断法 • 思考题 下图是某油田油井分布图,已知每口油井的位置(x, y坐标值)和产油量,如何求任意多边形(虚线所示)中的总产油量? 利用射线法判断油井是否在多边形内?
4.3.2 扫描线算法 • 英文:Scan-Line Algorithm • 目标 • 利用相邻像素之间的连贯性,提高算法效率 • 处理对象:简单多边形 • 非自交多边形 (边与边之间除了顶点外无其它交点) • 扫描线(Scanning Line) • 平行于坐标轴的直线 • 一般取平行于X轴 • 区间:扫描线与边的交点间的线段
4.3.2 扫描线算法 • 连贯性(Coherence) • 边的连贯性(Edge Coherence) • 某条边与当前扫描线相交,也可能与下一条扫描线相交 • 扫描线的连贯性(Scan-line Coherence) • 当前扫描线与各边的交点顺序与 下一条扫描线与各边的交点顺序可能相同或类似 • 区间的连贯性(Span Coherence) • 同一区间上的像素取同一颜色属性
4.3.2 扫描线算法 • 基本原理 • 将整个绘图窗口内扫描多边形的问题分解到一条条扫描线,只要完成每条扫描线的绘制就实现了多边形的扫描转换 • 一条扫描线与多边形的边有偶数个交点,每2个点形成一区间 • 步骤(对于每一条扫描线) (1)计算扫描线与边的交点 (2)交点按x坐标从小到大排序 (3)交点两两配对,填充区间
4.3.2 扫描线算法 • 计算交点 • 分类 • 第一类交点:位于同一条边上的后继交点--(P2, P4) • 第二类交点:新出现的边与扫描线的交点--(P3) • 计算:由扫描线y=e与多边形的交点递推计算扫描线 y=e+1的交点 • 第一类交点:x’=x+1/m • 第二类交点: 线段的下端点即为交点 P3 P2 P4 P0 P1
4.3.2 扫描线算法 • 计算交点(续) • 交点取整规则: • 要求:使生成的像素全部位于多边形之内 • 用于线画图元扫描转换的四舍五入原则导致部分像素位于多边形之外,从而不可用 扫描转换 位于多边形内
4.3.2 扫描线算法 • 取整规则 假定非水平变与扫描线 y=e 相交,交点的横坐标为x 规则1 X为小数,即交点落于扫描线上两个相邻像素之间 (a)交点位于左边之上,向右取整 (b)交点位于右边之上,向左取整
4.3.2 扫描线算法 规则2 落在右上边界的像素不予填充。 具体实现时,只要对扫描线与多边形的相交区间左闭右开
4.3.2 扫描线算法 规则3 扫描线与多边形的顶点相交时,采用上开下闭及右开左闭取交点,保证交点正确配对。 检查两相邻边在扫描线的哪一侧。 只要检查顶点的两条边的另外两个端点的Y值,两个Y值中大于交点Y值的个数是0,1,2,来决定取0,1,2个交点
G F H Y D C J B A A,B算交点 C不算,D算 I不算,H算 G,F不算 4.3.2 扫描线算法 • 计算交点(续) • 水平边 • 不考虑 • 排序 • 扫描线连贯性 • 采用插入排序 • 交点两两配对与区间绘制 • 区间连续性 • 连续绘制区间上的像素
4.3.2 扫描线算法 • 算法实现-数据结构 (1)边的分类表ET (Edge Table)(又称新边表) • 按照边的下端点 y 坐标,对非水平边进行分类的链表 • 下端点 y 坐标值等于i 的边属于第i类 • 作用:避免盲目求交
4.3.2 扫描线算法 ET定义 • 每条扫描线,对应一个链表 • 链表中每个结点的结构 typedef struct {int ymax; float x, deltax; Edge *nextEdge; }Edge; ET的结点信息: ymax: 边的上端点的y坐标值 x:边的下端点的x坐标 deltax:边的斜率的倒数 nextEdge: 下一条边的指针
4.3.2 扫描线算法 结点结构解释 typedef struct {int ymax; float x, deltax; Edge *nextEdge; }Edge; float x, deltax; 用于递推计算交点 x’=x+1/m int ymax;当扫描线 y = e+1 == ymax,说明下一条扫描线与此边不相交
4.3.2 扫描线算法 (2)活性边表AEL(Active Edge List) 存放活性边的顺序链表,且按交点 x 的值从小到大排序 活性边:与当前扫描线相交的边 边结构定义: typedef struct {int ymax; float x, deltax; Edge *nextEdge; }Edge; AEL 的结点信息: • ymax: 所交边的最高扫描线号 • x:当前扫描线与边的交点的x坐标 • deltax:边的斜率的倒数 • nextEdge: 下一条边的指针
4.3.2 扫描线算法 • 实例 (a) Y=6对应的活性边表 (b) Y=7对应的活性边表
4.3.2 扫描线算法 • 算法(Scan-Line Algorithm) 1、建立ET; 2、将扫描线纵坐标y的初值置为ET中非空 元素的最小序号,如图中,y=1; 3、置AEL为空; 4、执行下列步骤直至ET和AEL都为空. 4.1、如ET中的第y类非空,则将其中的所有 边取出并插入AEL中; 4.2、如果有新边插入AEL,则对AEL中各边排序; 4.3、对AEL中的边两两配对,(1和2为一对,3和4为一对,…),将每对边中x坐标按规则取整,获得有效的填充区段,再填充. 4.4、将当前扫描线纵坐标 y 值递值1; 4.5、将AEL中满足y = ymax边删去(因为每条边被看作下闭上开的); 4.6、对AEL中剩下的每一条边的x递增deltax,即x = x+deltax.
4 5 -1 5 7 5/4 ^ 4.3.2 扫描线算法 4.3.2 扫描线算法 • 例子 y=5 y=6 y=7 y=8 8 2 0 11 12 0 ^ AET:y=1 y=2 y=3 y=4 11 12 0 ^ 8 2 0 4 4 -1 5 33/4 5/4 ^ 8 2 0 8 7 -5 4 3 -1 5 19/2 5/4 ^ 11 7 5/4 11 12 0 ^ 5 43/4 5/4 ^ 8 2 0 11 12 0 ^ 11 33/4 5/4
4.3.2 扫描线算法 • 思考问题 • 算法如何体现连贯性? • 对凸多边形而言,算法是否可以简化?如何简化? • 对三角形而言,算法如何简化? 三角形广泛应用于物体建模
思考题 • 思考问题 如何高效计算平面上n 条线段的交点? 一般方法:两两求交点,时间复杂性n2 利用扫描线和边的连贯性,时间复杂性m×n’ (m为扫描线条数, n’为与扫描线相交边的平均条数, n’<< n)
4.3.3 边缘填充算法* • 写像素的逻辑操作 • 主要包括:拷贝、异或(求余)等 • 写模型:像素的颜色与源像素及像素当前颜色相关
4.3.3 边缘填充算法* • 求余运算 • 假定A为一个正整数,则 M 的余定义为 A – M, 记为 • 求余运算可用异或逻辑运算实现 • 例 • 性质
4.3.3 边缘填充算法* • 求余运算(续) • 应用:光标移动、橡皮筋线和加亮菜单等操作 • 如:交互绘制线段时的橡皮筋线 交互方式: (1)点击鼠标左键输入线段的一个端点 (2)点击右键(或左键)输入另一端点 (3)鼠标移动过程中绘制瞬时线段 注意:不能采用直接绘制(复制、拷贝)方法
4.3.3 边缘填充算法* • 边缘填充算法 • 光栅图形中,如果某区域已着上值为M的颜色值后,做偶数次求余运算,该区域颜色不变;而做奇数次求余运算,则该区域颜色变为值为 的颜色 • 这一规律应用于多边形扫描转换,称为边缘填充算法。 • 算法基本思想 • 对于每条扫描线和每条多边形边的交点,将该扫描线上交点右方的所有像素取余 • M为填充色,A为当前背景色
4.3.3 边缘填充算法* • 算法1(以扫描线为中心的边缘填充算法) • 1、将当前扫描线上的所有像素着上值为 颜色; • 2、求余: for(i=0; i<=m; i++) 在当前扫描线上,从横坐标为xi的交点向右求余; 图中次序:x0, x1, x2, x3 次序可以任意
4.3.3 边缘填充算法* • 算法2(以边为中心的边缘填充算法) 1、将绘图窗口的背景色置为 ; 2、对多边形的每一条非水平边做: 从该边上的每个像素开始向右求余
4.3.3 边缘填充算法* • 特点 • 适合用于具有帧缓存的图形系统。处理后,按扫描线顺序读出帧缓存的内容,送入显示设备 • 优点:算法简单 • 缺点:对于复杂图形,每一像素可能被访问多次,输入/输出的量比扫描线算法大得多
4.4 扫描转换扇形区域 • 扇形区域的描述 • 圆的半径R • 起始角度:1 • 终止角度:2 • 原理:同扫描转换多边形 • 对每条扫描线,首先计算与扇形区域边界的交点,再用指定颜色绘制绘制配对交点间的像素 • 问题 • 如何确定扫描线与直线段和圆弧段的交点及相交顺序?
4.4 扫描转换扇形区域 • 方法:分类 • 按点 P1(x, y) 和P2(x, y) 点所处象限的不同,需要将扇形区域分成4×4=16种情况 • 假设 P1 点落在第一象限 • 扫描线和区域边界只有2个交点 • 扇形区域的扫描转换分四种情况 (1) P2落在第一象限 区域:OP1A和AP1P2
4.4 扫描转换扇形区域 (2)P2落在第二象限,此时又分为两种情况 • 当 时 • 三个区域: OAP1、AP1BP2和P2BC • 当 时 • 三个区域: OAP2、AP1BP2和P1BC
4.4 扫描转换扇形区域 (3) P2落在第三象限 • 三个区域: P1CA、BOP1A和P2OB (4) P2落在第四象限 • 三个区域: AP1D、BOP1 A、CP2OB 和CEP2
逆旋转270度 扫描转换 P2 旋转270度 P1 P2 P1 P2 P2 P1 P1 P1落在第四象限 P1落在第一象限 P1落在第四象限 4.4 扫描转换扇形区域 • 遗留问题:当 P1落在其它区域时? • P1 落在第二、三、四象限时,将扇形区域绕坐标原点顺时针旋转90度(180,270),使 P1落在第1象限 • 扫描转换 • 再逆时针旋转90度(180,270)
4.5 区域填充 • 区域:点阵表示的图形,像素集合 • 表示方法:内点表示、边界表示 • 内点表示 • 枚举出区域内部的所有像素 • 内部的所有像素为同一个颜色 • 边界像素与内部像素的颜色不同 • 边界表示 • 枚举出边界上所有的像素 • 边界上的所有像素为同一颜色 • 内部像素与边界像素的颜色不同
4.5 区域填充 • 种子填充法 • 对区域重新着色的过程 将指定的颜色从种子点开始扩展到整个区域 • 区域填充算法要求区域是连通的
4.5 区域填充 • 连通性 • 4连通区域:区域中任意两点可通过上下左右四个方向互相 到达 • 8连通区域:区域中任意两点可通过上下左右和对角线八 个方向互相到达
4.5 区域填充 4连通与8连通区域的区别 • 连通性: 4连通可看作8连通区域,但对边界有要求不同 • 依据区域内点能否访问到区域外的点,对边界的要求是 • 4连通区域,边界只要8连通即可 • 8连通区域,边界必须是4连通 • 例:如左图 (1)4连通区域,边界为 像素 (2)8连通区域,边界为 和 像素
4.5.1 种子填充法 • 种子填充算法(泛滥填充:flood-fill) (1)内点表示的4连通区域 • 种子P(x,y),原色oldColor,新颜色newColor 方法:先判断P(x, y)的颜色,若其值不等于oldColor,说明该像素位于区域外,或已设置为newColor,算法结束; 否则,置像素颜色 为newColor,再对 其相邻的上下左右四 个像素分别作为种子 作上述递归处理。 void FloodFill4(int x,int y,int oldColor, int newColor) { if(GetPixel(x,y) == oldColor) { SetPixel(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); } }/*end of FloodFill4() */