场景
经常看到各种高效裁剪圆角的文章,正好之前做过一点数字图像处理,就打算用空域处理的办法,写个裁剪圆角的算法,一定要尽可能的快的,不然界面容易卡顿。
裁圆角很简单,对于图像上的一个点(x, y),判断其在不在圆角矩形内,在的话 alpha 是原值,不在的话 alpha 设为 0 即可。如下图
15F6A143-2704-402D-88EA-DB80B0266F80.png我遍历所有像素,判断每个像素在不在4个圆的圆内就行了,4个角,每个角有一个四分之一的圆。
一个优化就是,我不需要遍历全部的像素就能裁出圆角,只需要考虑类似左下角三角形的区域就行了,左下,左上,右上,右下,一共4个三角形区域(另外3个图中没画出),for循环的时候,就循环这个4个三角形区域就行了。
所以对于一幅 w * h 的图像,设圆角大小为 n,n <= min(w, h) / 2,其复杂度为 O(n) = 2(n^2),最坏的情况计算量也不会超过 wh / 2
对于一个像素点(x, y),判断其在不在圆内的公式
如果 (x-cx)^2 + (y-cy)^2 <= r^2 就表示点 (x, y) 在圆内,反之不在。
理论说完了,下面看实际的测试数据。
测试结果与分析
根据上面的分析,我写了一个裁剪圆角的程序,叫为 my裁剪。
还用了苹果 CoreGraphics 库的 CGContext 裁剪圆角,叫为 CGContext 裁剪。
还用了 UIKit 的 UIBezierPath 裁剪圆角,叫为 贝塞尔裁剪。
下面来对比三种方法,哪种最快,分别是
1. my裁剪
2. CGContext裁剪
3. 贝塞尔裁剪
实验数据:
一张 png 格式 512 * 512 的 lena 女神的标准实验图像。
圆角大小分别取 10,50,100,250,这4个值。
每次实验裁剪 10000 张图片数据,获得总耗时。
因为图片是 512 * 512 的,最大的圆角为 512 / 2 = 256,所以超过 256 的会被强制设在 256,所以实验中用了个近似 256 的 250 做为最大的测试数据。
实验前关闭所有比较耗CPU的软件。实验中不操作电脑,避免影响实验结果准确性,最好真机测试,关掉后台所有APP。最后得到了以下实验数据,并绘制成表格。
表格.jpeg根据上图可以看出 my 裁剪运行时间看起来像指数上升的,是不是会更慢?
答:不是的,看 x 轴的坐标间距,10 到 50 到 100,然后直接跳到 250 了,不是均等分的!要是画均等分的话,图会非常非常长。
如果要 x, y 轴刻度均等分的话,画出来的图大概是这样的,如下图:
my 裁剪时间随着圆角大小线性上升,到达 256 的时候,是最大值了。
实验过程中的具体数据。
- 圆角为 10 的情况
裁剪方法 | 用时 ms |
---|---|
my裁剪 | 2085 |
CGContext裁剪 | 15270 |
贝塞尔裁剪 | 14317 |
- 圆角为 50 的情况
裁剪方法 | 用时 ms |
---|---|
my裁剪 | 2419 |
CGContext裁剪 | 14676 |
贝塞尔裁剪 | 14612 |
- 圆角为 100 的情况
裁剪方法 | 用时 ms |
---|---|
my裁剪 | 3662 |
CGContext裁剪 | 14646 |
贝塞尔裁剪 | 14597 |
- 圆角为 250 的情况
裁剪方法 | 用时 ms |
---|---|
my裁剪 | 12399 |
CGContext裁剪 | 14692 |
贝塞尔裁剪 | 14313 |
结论与分析
从上面数据可以看出:
时间上:不管圆角大小 n 是多少,CGContext 和 UIBezierPath,耗时都在 14.6 秒左右。而 my裁剪在圆角小的时候,性能较好,耗时在 3 秒左右,随着圆角增到250,耗时也去到了 12 秒,但最坏不会超过 w * h / 2,在 n < min(w, h) / 2 时,具有较高的性能,比CGContext, UIBezierPath要快。
空间上:内存使用上,没精确测量,大致看了一下,裁剪1万张 512 * 512的图片,3种算法的内存使用都在 10MB 左右,还可以接受,但 UIBezierPath 裁剪时居然会写磁盘。
另外,在图像编码/解码中,用了 CGDataProviderRef,CGImageRef,这两个对象,它们的速度应该是很快的了,如果自己写的话,预测可以更快。
最后口说无凭,我发个代码可以自己下载运行看看结果(下载地址)
使用方法都在代码的 ViewController.m 文件里,就这么一个文件,很好找,没有其他了。请在 iphone6,6 plus 的屏幕下运行。本实验重点在于3个算法,无关紧要的东西没做太多处理。
网友评论
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
我参考了你的文章,写了一份Swift版本。(在Swift中操作C指针真的挺绕。。)
1.[self dealImage:image cornerRadius:100]这个方法与self.imageV2.layer.cornerRadius = 100;self.imageV2.layer.masksToBounds = YES; 这样的方式处理圆角. 作者自己创建的方法的圆角处理并不到位.
2.假如忽略上面的内容来说,圆角处理并不圆滑.满足性能再丢失了图像的裁剪效果,不太明智.
3.处理图片少的,其实系统提供的方法的时间也差不多.
但是发现有些问题,“my裁剪”产出的图像的圆角不够平滑,而另外两种方法产出的圆角是十分平滑的。
是不是性能上的差距主要就是在处理上舍去了平滑处理,所以效率更高呢?
如果把边缘平滑算法加上,再测测效率,可能更加有说服力
现在的算法已经很棒啦!加油
2016-12-19 14:49:37.680 ClipCorner[98213:2837474] ---start
2016-12-19 14:50:10.371 ClipCorner[98213:2837474] my裁剪用时:32.545 秒
2016-12-19 14:50:13.918 ClipCorner[98213:2837474] ---start
2016-12-19 14:50:28.565 ClipCorner[98213:2837474] CGContext裁剪用时:14.611 秒
2016-12-19 14:50:34.532 ClipCorner[98213:2837474] ---start
2016-12-19 14:50:49.696 ClipCorner[98213:2837474] 贝塞尔裁剪用时:15.125 秒
把自定义的剪切算法里的 释放内存方法更改了一下,发现解决问题了。
希望作者看下一是否存在这样的问题。
更改内容是这样的
CGDataProviderRelease(pv);
CGImageRelease(content);
// free(imgData); // 释放空间
2016-12-19 13:00:54.136 ClipCorner[7686:224485] my裁剪用时:0.193 秒
2016-12-19 13:00:55.370 ClipCorner[7686:224485] ---start
2016-12-19 13:00:55.514 ClipCorner[7686:224485] CGContext裁剪用时:0.140 秒
2016-12-19 13:00:56.723 ClipCorner[7686:224485] ---start
2016-12-19 13:00:56.876 ClipCorner[7686:224485] 贝塞尔裁剪用时:0.149 秒