概述
拉伸过滤
我们使用 UIImageView 时,经常设置 contentMode 控制拉伸方式,比如 scaleAspectFill,CALayer 对应的是 contentsGravity 设置 resizeAspectFill。
拉伸图片(除了 icon,我们很少使用尺寸完全符合视图大小的图片),如果了解计算机图形学或计算机视觉,肯定对最常用的三种图片放缩算法不陌生:最近取样,双线性,三线性。这里只简单说一下思路,其中最广泛使用的双线性(iOS 默认使用)的具体实现代码我在视觉一栏文章里有写,有兴趣的可以看一下。
假设原图 40*40,拉伸目标 80*120,也就是水平 2 倍,垂直 3 倍。目标中的点例如 (5, 8),除以倍数,对应着原图的 (2.5, 2.67)。
- 最近取样:(2.5, 2.67) 实际取原图 (2, 3),当然可以取 (3, 3),这个区别不大,直接取出来作为新图的像素。
- 双线性:根据 (2.5, 2.67) 取出周围四个点 (2, 2),(2, 3),(3, 2),(3, 3) 的像素,按照线性位置取各自的比例,加权:先算 x 或 y 方向,比如先算 0.5 倍的 p(2, 2) 加 0.5 倍的 p(3, 2),得到 p1,0.5 倍的 p(2, 3) 加 0.5 倍的 p(3, 3) 得到 p2;然后 0.67 倍的 p2 加 0.33 倍的 p1,得到最终结果。
- 三线性:重点是 三线性会产生 mipmap,呃,就是会预生成一系列不同大小的图片,双线性的结果在不同大小图片间再次线性插值,得到最终结果。
iOS 默认使用双线性,通过设置 CALayer 的 magnificationFilter、minificationFilter 属性可以分别指定放大缩小时使用的算法,枚举值分别是 kCAFilterNearest,kCAFilterLinear,kCAFilterTrilinear。设置不同的拉伸算法看起来有什么区别呢,或者说各自的应用场景是什么?
例一,大图:
例二,小图:
对于大图,最近过滤速度非常快,但效果不好。双线性和三线性效果差不多,但是三线性存储了一系列不同大小的图片,多次缩放取值时,可以根据目标大小直接从系列里取接近的图片,空间换时间。
计算机里只有水平/垂直,但现实图片需要斜线,使用最近过滤可以直观地感受到斜线有锯齿感,因为只考虑单像素点,不考虑附近点的颜色。而这种特点在极少斜线的图中恰恰成为了优势:保留了像素之间的差异。线性过滤更像是保留形状。例二没有斜线的小图,使用最近过滤的优势很大,反而线性过滤考虑周围像素点导致了失真。
总的来说,最近过滤的应用场景比较小:少有斜线。
组透明
我们很熟悉 UIView 的 alpha,即透明度,对应 CALayer 的 opacity。在 iOS 6 及以前,图层施加透明度后,父图层的效果加上子图层的结果作为最终结果;后面为了性能优化,先不考虑透明度,父子图层叠加后,统一施加透明度。这种行为就是组透明(group opacity),后面用伪代码详细解释。
如下图,假设现在有一个白色视图 v,子视图 label 背景也是白色。设置 v 的 alpha 0.5,会显示上图的效果(现在默认开启组透明:默认是读取 Info.plist 文件中 UIViewGroupOpacity 的值,如果为空,iOS 6 以上则默认开启,否则默认关闭);手动设置 v.layer.allowsGroupOpacity 为 false,出现下方的效果。
图层树的渲染顺序是前序遍历,即从根节点开始,先绘制自己,然后遍历自己的子图层,挨个绘制。绘制每个子图层也是这个顺序。组透明属性影响了 opacity 的施加时机,伪代码 v1(以下都是瞎写的)如下,后面详细解释:
func draw() -> BackStore {
var backStore = BackStore()
return draw(in: backStore)
}
func draw(in backStore: BackStore) -> BackStore {
drawSelf(to: backStore)
if allowsGroupOpacity {
for sublayer in sublayes { // 已经按照 zPosition 排序
// 没有新生成 store
var store = backStore.subStore(of sublayer)
sublayer.draw(in: store)
}
applyOpacity(to: backStore)
} else {
applyOpacity(to: backStore)
for sublayer in sublayes { // 已经按照 zPosition 排序
var store = sublayer.draw()
applyOpacity(to: store)
backStore.apply(store)
}
}
return backStore
}
经过 layout 引擎计算好视图位置之后,来到渲染引擎。window 图层作为根节点,图层树开始调用 draw()。BackStore 可以理解为像素点的二维数组,代表渲染内容。最开始的 BackStore 初始化的大小肯定是屏幕大小,然后在这块区域内绘制。
先绘制本身的内容,比如 contents 内的图片;
如果该图层允许组透明,开始遍历子图层,subStore 函数标记当前仓储中 子图层位置对应的区域,子图层直接在这块区域里面绘制,不用额外申请仓储。里面肯定会考虑 masksToBounds 等行为,超出自己区域如果不裁剪,需要写到区域之外,处理方式很多就不展开了,例如 store 存一个当前标记区域就可以。所有子图层绘制完,对整个 store 施加 opacity。
如果不允许组透明,先对 store 施加 opacity,遍历子图层,每个子图层自己申请内存画自己,对于结果施加 opacity,然后让整体 store 整合子图层的 store。关于不裁剪、位置,子图层创建 store 时创建屏幕大小的 store,画在某个位置,记录…
前面例子中 allowsGroupOpacity 为 false 时,按照上面的逻辑,label 的显示内容应该是:父视图白色 0.5 + 根视图灰色 0.5,得到的颜色 (浅灰) 的 0.5 加上自己的白色 0.5,总体相当于白色 0.5 * 0.5 + 0.5 = 0.75,灰色 0.5 * 0.5 = 0.25 的混合色,父视图是 0.5/0.5 的混合色,出现了较差的效果。
opaque
字面意思 不透明的,然而这个属性唯一的影响是,创建某个图层的 BackStore 时,是否含有 alpha 通道。如果自定义的视图保证是不透明的,而且充满了 bounds,就可以设置 opaque 为 true,当然其他情况下也可以设置,只是显示效果就无法预测了。
因此,前面伪代码 v1 中需要判断,如果图层 isOpaque,则不施加 opacity,提高了速度,BackStore 也减少了内存占用。至于画出来的内容是否真的不透明,无所谓了。
shouldRasterize 光栅化
开启光栅化后,应用透明度之前,图层及其子图层都会被整合成一张图片,然后施加透明度就和组透明的效果一样了。看起来和前面组透明的伪代码一样,但光栅化的主要的作用在于,光栅化的图片会被缓存到内存中,下次显示时如果缓存没有失效则不用再次生成,直接显示,在后面性能优化章节中会详述。开启 shouldRasterize 属性后,需要设置 rasterizationScale 属性匹配屏幕的 scale,和之前提过的 contentScale 相似。
最后
以上就是寂寞纸飞机为你收集整理的iOS CoreAnimation (八) 拉伸过滤,组透明,magnificationFilter,allowsGroupOpacity,opaque,光栅化 shouldRasterize的全部内容,希望文章能够帮你解决iOS CoreAnimation (八) 拉伸过滤,组透明,magnificationFilter,allowsGroupOpacity,opaque,光栅化 shouldRasterize所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复