CAShapeLayer的缠绕规则
在实现镂空的效果时改变路径的方向,会影响最终实现的镂空效果。某个区域是否被渲染是有规则约束的,通过给定区域内的任意一点到路径外画一条射线,根据与路径的交叉数判断点是否在区域内。
在图形学中,若多边形不是自相交的,那么可以简单的判断这个点在多边形内部还是外部;若多边形是自相交的,那么就需要根据非零缠绕规则和奇-偶规则判断。
苹果文档中也有关于缠绕规则的描述 Winding Rules。
1. 两种缠绕规则
奇-偶规则
奇-偶规则,即 Odd-even Rule。从任意位置 P 作一条射线,若与该射线相交的多边形边的数目为奇数,则p是多边形内部点,否则是外部点。
非零缠绕规则
非零缠绕规则,即 Nonzero Winding Number Rule。首先使多边形的边变为矢量,环绕数初始化为零。再从任意位置 P 作一条射线。当从 P 点沿射线方向移动时,对在每个方向上穿过射线的边计数,每当多边形的边从右到左穿过射线时,环绕数加1,从左到右时,环绕数减1。处理完多边形的所有相关边之后,若环绕数为非零,则 P 为内部点,否则,P 是外部点。
非零缠绕规则和奇偶规则会出现判断矛盾的情况,如下图左侧表示用 奇偶规则判断绕环数为2 ,表示在多边形外,所以没有填充。右侧图用非零缠绕规则判断出绕数为2,非0表示在多边形内部,所以填充。
填充操作适用于开放式路径和闭合路径。开放式路径会从路径的最后一个点到第一个点创建一个隐式的线(不渲染),来闭合路径。
2. 闭合路径
CGRect aRect = CGRectMake(100, 100, 200, 200);
UIBezierPath * aPath = [UIBezierPath bezierPathWithRect:aRect];
CGRect bRect = CGRectInset(aRect, 50, 50);
UIBezierPath * bPath = [UIBezierPath bezierPathWithRect:bRect];
[aPath appendPath:bPath];
CAShapeLayer * shapeLayer = [CAShapeLayer layer];
shapeLayer.path = aPath.CGPath;
shapeLayer.fillColor = [UIColor yellowColor].CGColor;
[self.view.layer addSublayer:shapeLayer];
2.1 非零缠绕规则
CAShapeLayer 的 fillRule 属性默认为非零缠绕。那么在闭合路径下,一般有两种路径形式:
- 内、外边框同向
aPath 与 bRect 创建时默认方向相同,内部的点向外画射线,射线由右至左跨过路径两次,aRect 以内的所有的点的射线交叉数只有两种情况:0-1=-1,或者0-1-1=-2。都不为0,所以内部的点都在路径之内,需要渲染。
- 内、外边框反向
aPath 与 bRect 默认方向相同,系统提供有路径方向翻转的方法:
// 将路径翻转
UIBezierPath * bPath = [[UIBezierPath bezierPathWithRect:bRect] bezierPathByReversingPath];
内边框与外边框反向,bPath 以内的点的射线与路径交叉只有一种:0+1-1=0,因此 bPath 内部的点都在路径最终路径之外,bPath 以内的点不需要渲染;bPath 以外 aPath 以内的点的射线与路径交叉有两种:0-1=-1,或者0+1+1-1=1。两种情况都不为0,所以在路径之内,需要渲染。
2.2 奇-偶规则
CAShapeLayer 的 fillRule 属性默认为非零缠绕,使用奇-偶规则需要添加如下代码:
// 修改fillRule为奇偶
shapeLayer.fillRule = kCAFillRuleEvenOdd;
奇偶缠绕规则下,与内外路径方向无影响,只判断射线与路径的交叉,所以有两种情况:bRect内部的点,射线与路径的交叉有两个,为偶数,所以不在范围内,不需要渲染;bRect之外aRect以内,射线与路径的交叉数可能为1或3,为奇数,所以需要渲染。
3. 开放式路径
开放式路径会从路径的最后一个点到第一个点创建一个隐式的线将路径闭合(注意方向),如下图不规则的path被隔成数个区域,非零缠绕规则与奇-偶规则依然适用。
参考文章:
https://www.jianshu.com/p/dcf4140536bb
https://blog.csdn.net/g0ose/article/details/54933038