美文网首页iOS猿媛圈iOS进阶iOS
[iOS] 图像处理 - 一种高效裁剪图片圆角的算法

[iOS] 图像处理 - 一种高效裁剪图片圆角的算法

作者: ck2016 | 来源:发表于2016-12-13 11:25 被阅读5583次

    场景

    经常看到各种高效裁剪圆角的文章,正好之前做过一点数字图像处理,就打算用空域处理的办法,写个裁剪圆角的算法,一定要尽可能的快的,不然界面容易卡顿。

    裁圆角很简单,对于图像上的一个点(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 轴刻度均等分的话,画出来的图大概是这样的,如下图:

    表格2.jpeg

    my 裁剪时间随着圆角大小线性上升,到达 256 的时候,是最大值了。

    实验过程中的具体数据。

    • 圆角为 10 的情况
    裁剪方法 用时 ms
    my裁剪 2085
    CGContext裁剪 15270
    贝塞尔裁剪 14317
    圆角为 10
    • 圆角为 50 的情况
    裁剪方法 用时 ms
    my裁剪 2419
    CGContext裁剪 14676
    贝塞尔裁剪 14612
    圆角为 50
    • 圆角为 100 的情况
    裁剪方法 用时 ms
    my裁剪 3662
    CGContext裁剪 14646
    贝塞尔裁剪 14597
    圆角为 100
    • 圆角为 250 的情况
    裁剪方法 用时 ms
    my裁剪 12399
    CGContext裁剪 14692
    贝塞尔裁剪 14313
    圆角为 250

    结论与分析

    从上面数据可以看出:
    时间上:不管圆角大小 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个算法,无关紧要的东西没做太多处理。

    相关文章

      网友评论

      • 请叫我喵_喵:不说赞一个,我都不好意思用了~~~ 真棒!
        请叫我喵_喵:@请叫我喵_喵 遍历时内存偏移存在越界的危险.
        请叫我喵_喵:po *p
        error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
        ck2016:@请叫我喵_喵 :+1:
      • 马克吐温Coder:裁剪出来的图形有些问题。直接调用UIBezierPathClip 参数填写200. 裁剪是圆形,是正确的。dealImage调用参数填写200 裁剪的出的并不是圆形。楼主的算法应该有些误差吧。如果这样的话,那3个方法对比出的结果也就有些问题了
        ck2016:@马克吐温_ 我试试
      • Hesse_Huang:http://www.jianshu.com/p/0778bee3794f
        我参考了你的文章,写了一份Swift版本。(在Swift中操作C指针真的挺绕。。)
        Hesse_Huang:@ck2016 CocoaChina微信号
        ck2016:@ynboh 问一下,你在哪里看我的文章的
        ck2016:@ynboh 我去看看,牛逼啊
      • 北漂少年郎:我一直用self.view.layer.cornerRadius 这个切,这个切都有什么弊端那,请教楼主
        ck2016:@逍遥若水 不在屏幕缓存区画图
        北漂少年郎:@ck2016 什么是离屏渲染
        ck2016:@逍遥若水 会发生离屏渲染,数量多了会掉帧
      • d6b305902a5e:楼主,我平时一般都是用CornerRadius直接切圆角 。这是属于你说的哪种方式???还是单独的属于另一种方式切割???这种方式速度几何???
        ck2016:@蚂蚁偷钻石 行,界面不卡就行了,卡了就想办法优化
      • KevinTing:卤煮啊,你这个测试得在真机上测试的数据才有说服力吧
        ck2016:@KevinTing 是啊,我忘了啊,电脑上干扰多
      • ff0f6b05526c:因为图像是对称的,所以4个角其实可以变成1个角
        ck2016:@流年如秋 啥意思
        _涼城:@ck2016 那在遍历一个的情况下处理相对应的另外3个可行么
        ck2016:@ff0f6b05526c 不行,像素还是要遍历那么多个的
      • RemisKrlet: iphone 6测试,10000次,radius 256像素,自定义处理,CG,UIBezier分别是 44.448s, 29.132, 29.157s。次数少,radius小的情况下CG比UIBezier慢
        ck2016:@RemisKrlet 这个真不好测,有些网友可能是后台程序影响,或者一次按了好多个按钮导致的,按下button后,不动其他的操作,给他算完,次数调到1000试试
        RemisKrlet:@ck2016 不清楚,你这边是多少
        ck2016:@RemisKrlet 这不科学,应该是哪里错了,居然44秒,
      • li_礼光:测试了一下Demo,算法是比原生的要快.以我个人的观点,有些问题吧.
        1.[self dealImage:image cornerRadius:100]这个方法与self.imageV2.layer.cornerRadius = 100;self.imageV2.layer.masksToBounds = YES; 这样的方式处理圆角. 作者自己创建的方法的圆角处理并不到位.
        2.假如忽略上面的内容来说,圆角处理并不圆滑.满足性能再丢失了图像的裁剪效果,不太明智.
        3.处理图片少的,其实系统提供的方法的时间也差不多.
        ck2016:@靓模袭地球 嗯,边缘一圈的像素点没与背景衔接(双线性插值能很好的平滑边缘),数据量小时间差距就小了
      • 从来吃不胖:方法的确挺好的!
        但是发现有些问题,“my裁剪”产出的图像的圆角不够平滑,而另外两种方法产出的圆角是十分平滑的。
        是不是性能上的差距主要就是在处理上舍去了平滑处理,所以效率更高呢?
        ck2016:@从来吃不胖 圆角100的圆,周长是600多个像素,扩3圈就是1800个像素左右吧,就是for循环多算1800次
        从来吃不胖:@ck2016 边缘一圈的这一圈,甚至是往外扩散的2~3圈,都需要做过渡平滑处理,也就是alpha值过渡,这个如果要做的精确,计算量要比现在的DEMO复杂很多吧?:disappointed_relieved:
        如果把边缘平滑算法加上,再测测效率,可能更加有说服力:+1:
        现在的算法已经很棒啦!加油
        ck2016:@从来吃不胖 不是,要平滑一点的话,可以把边缘那一圈的像素调低alpha值,与背景衔接,几乎不会影响性能
      • 丷笑者:支持楼主,不过测试了一下13张图片以下的情况,11-13张之间3种方式耗时差别不大,只有在13张以上并且圆角裁剪的时候,裁剪才能优于其他2种方式
      • NateLam:楼主, 问一个比较弱智的问题, 参数CornerRadius在切圆的时候要填多少, 你demo里面原图边长是200, 参数填100, 结果点击"计算"出来的图不是圆形...
        ck2016:图有长和宽,长宽都是200的话,参数就写100,就是圆了
      • 诸子百家谁的天下:请求加入专题!
        ck2016:@诸子百家谁的天下 好的
        ck2016:@诸子百家谁的天下 怎么加
        诸子百家谁的天下:@诸子百家谁的天下 另模拟器实在是不太准确的,应该使用真机测试!
      • 丶大明:LZ,我count=10000,radius=250 用模拟器iphone6 :cold_sweat: 测试:
        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 秒
        丶大明:@ck2016 恩,好
        ck2016:@丶大明 亲关掉一些耗CPU的软件,再测一遍看看
      • iOS_愛OS:你demo 中的图好熟悉,毕设搞图像算法用的全是这张图
        ck2016:@iOS_愛OS 有几张标准实验图像来的
      • littlewish:mark 找时间试试demo
        ck2016:@littlewish :relaxed:
      • e220c343b18f:试了一下你这个方法, 发现在圆角小于100时,剪切后图片获取不到。
        把自定义的剪切算法里的 释放内存方法更改了一下,发现解决问题了。
        希望作者看下一是否存在这样的问题。

        更改内容是这样的
        CGDataProviderRelease(pv);
        CGImageRelease(content);
        // free(imgData); // 释放空间
        ck2016:@林98 内存释放得好
        ck2016:@林98 明天我看看 :+1:
      • Zhengjun_Sun:这算法好🤔🤔🤔
        ck2016:@Satinettes :blush:
      • 不留名的黄子嘉:一般不是生成一个遮罩盖上去就好吗? 生成一个CAShapeLayer 如果是cell 也就是几个遮罩的事情而已。直接盖在控件上面 传入什么图片都只能显示遮罩内容?
        ck2016:@不留名的黄子嘉 谢谢
        不留名的黄子嘉:作者确实厉害 :smile: 走drawInRect方法都会消耗性能 研究下用其它方法能不能超过作者的demo。=。=
      • 口袋海贼王_:感觉一个屏幕的圆角数量不会太多,100个都属于多的了,数量少的时候露珠你的处理时间明显要长、
        口袋海贼王_:@ck2016 不对啊,你应该是姿势不对吧,露珠,哈哈哈。我的测试效果。2016-12-19 13:00:53.940 ClipCorner[7686:224485] ---start
        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 秒
        ck2016:@口袋海贼王_ 测试了10000, 100, 10 的情况,没有你说的情况发生
        ck2016:@口袋海贼王_ 也就是说把 10000张,改为 100 张测试吗,我待会试试
      • 白云心城:谢谢分享!向作者学习! :+1:
        ck2016:@白云心城 谢谢
      • 蓝天大海:作者好样的,棒棒的
        ck2016:@广杭小子 谢谢
      • 难却却:牛🐂
        ck2016:@难却却 谢谢
      • f680dfb8c555:厉害
        ck2016:@_silenceZ :relaxed:
      • YungFan:拼算法的时候
        ck2016:@YungFan :relaxed: :relaxed: :relaxed:
      • 80c9a4518c6f:支持原创
        ck2016:@SkyerWalker :relaxed: :relaxed: :relaxed:

      本文标题:[iOS] 图像处理 - 一种高效裁剪图片圆角的算法

      本文链接:https://www.haomeiwen.com/subject/abmmmttx.html