iOS图片设置圆角性能问题

作者: 齐滇大圣 | 来源:发表于2016-01-06 20:09 被阅读10292次

    一般我们在iOS开发的过程中设置圆角都是如下这样设置的。

     avatarImageView.clipsToBounds = YES;
     [avatarImageView.layer setCornerRadius:50];
    
     这样设置会触发离屏渲染,比较消耗性能。比如当一个页面上有十几头像这样设置了圆角
     会明显感觉到卡顿。
    
     注意:png图片UIImageView处理圆角是不会产生离屏渲染的。(ios9.0之后不会离屏渲染,ios9.0之前还是会离屏渲染)。
    

    所有如果要高性能的设置圆角就需要找另外的方法了。下面是我找到的一些方法并写了一个例子。

    IMG_1816.PNG

    设置圆角的方法

    • 直接使用setCornerRadius
      这种就是最常用的,也是最耗性能的。

    • setCornerRadius设置圆角之后,shouldRasterize=YES光栅化

      avatarImageView.clipsToBounds = YES;
      [avatarImageView.layer setCornerRadius:50];
      avatarImageView.layer.shouldRasterize = YES;
      avatarImageViewUrl.layer.rasterizationScale=[UIScreen mainScreen].scale;  //UIImageView不加这句会产生一点模糊
      
      shouldRasterize=YES设置光栅化,可以使离屏渲染的结果缓存到内存中存为位图,
      使用的时候直接使用缓存,节省了一直离屏渲染损耗的性能。
      
      但是如果layer及sublayers常常改变的话,它就会一直不停的渲染及删除缓存重新
      创建缓存,所以这种情况下建议不要使用光栅化,这样也是比较损耗性能的。
      
    • 直接覆盖一张中间为圆形透明的图片(推荐使用)
      这种方法就是多加了一张透明的图片,GPU计算多层的混合渲染blending也是会消耗
      一点性能的,但比第一种方法还是好上很多的。

    • UIImage drawInRect绘制圆角
      这种方式GPU损耗低内存占用大,而且UIButton上不知道怎么绘制,可以用
      UIimageView添加个点击手势当做UIButton使用。

      UIGraphicsBeginImageContextWithOptions(avatarImageView.bounds.size, NO, [UIScreen mainScreen].scale);
      [[UIBezierPath bezierPathWithRoundedRect:avatarImageView.bounds
                                    cornerRadius:50] addClip];
      [image drawInRect:avatarImageView.bounds];
      avatarImageView.image = UIGraphicsGetImageFromCurrentImageContext();
      UIGraphicsEndImageContext();
      
      这段方法可以写在SDWebImage的completed回调里,在主线程异步绘制。
      也可以封装到UIImageView里,写了个DSRoundImageView。后台线程异步绘制,不会阻塞主线程。
      

    问题:这种方法图片很多的话CUP消耗会高,内存占用也会暴增,而且后台线程绘制会比在主线程绘制占用更多的内存,不知道怎么解决?求大神指教!

    • SDWebImage处理图片时Core Graphics绘制圆角

        //UIImage绘制为圆角
        int w = imageSize.width;
        int h = imageSize.height;
        int radius = imageSize.width/2;
        
        UIImage *img = image;
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);
        CGRect rect = CGRectMake(0, 0, w, h);
        
        CGContextBeginPath(context);
        addRoundedRectToPath(context, rect, radius, radius);
        CGContextClosePath(context);
        CGContextClip(context);
        CGContextDrawImage(context, CGRectMake(0, 0, w, h), img.CGImage);
        CGImageRef imageMasked = CGBitmapContextCreateImage(context);
        img = [UIImage imageWithCGImage:imageMasked];
        
        CGContextRelease(context);
        CGColorSpaceRelease(colorSpace);
        CGImageRelease(imageMasked);
      

      以上代码我写成了UIImage的类别:UIImage+DSRoundImage.h
      并在SDWebImage库里处理image的时候使用类别方法绘制圆角并缓存。


    使用Instruments的Core Animation查看性能

    • Color Offscreen-Rendered Yellow
      开启后会把那些需要离屏渲染的图层高亮成黄色,这就意味着黄色图层可能存在性能问题。

    • Color Hits Green and Misses Red
      如果shouldRasterize被设置成YES,对应的渲染结果会被缓存,如果图层是绿色,就表示这些缓存被复用;如果是红色就表示缓存会被重复创建,这就表示该处存在性能问题了。

    用Instruments测试得

    • 第一种方法,UIImageView和UIButton都高亮为黄色。

    • 第二种方法,UIImageView和UIButton都高亮为绿色

    • 第三种方法,无任何高亮,说明没离屏渲染。
      这种圆片覆盖的方法一般只用在底色为纯色的时候,如果圆角图片的父View是张图片的时候就没办法了,而且底色如果是多种颜色的话那要做多张不同颜色的圆片覆盖。(可以用代码取底色的颜色值给圆片着色)

    • 第四种方法无任何高亮,说明没离屏渲染(但是CPU消耗和内存占用会很大)

    • 第五种方法无任何高亮,说明没离屏渲染,而且内存占用也不大。(暂时感觉是最优方法)


    问题回复:

    • 有回复提到还有一种mask方法。
      这种方法比第一种方法其实更卡顿。一次mask发生了两次离屏渲染和一次主屏渲染。 具体可以参考小心别让圆角成了你列表的帧数杀手

    • @nerozhao说第四种比第一种更卡。

      我刚在demo里加了个例子测试了一下,第一种能明显的感觉到卡顿,第四种还是挺顺畅
      的,有兴趣的可以自己试试看。第四种是解决了离屏渲染GPU的问题。
      

    可以用Instruments的 GPU Driver进行测试:

    • Renderer Utilization
      如果这个值超过了~50%,就意味着你的动画可能对帧率有所限制,很可能因为离屏渲染或者是重绘导致的过度混合。
    • Tiler Utilization
      如果这个值超过了~50%,就意味着你的动画可能限制于几何结构方面,也就是在屏幕上有太多的图层占用了。
    Instruments

    图上面一部分是第一种方法的数据,下面一部分是第四种方法的数据。
    第一种方法的Renderer Utilization 和 Tiler Utilization 基本在90%左右。帧率在20左右。
    第四种方法的Renderer Utilization 和 Tiler Utilization 基本在20%左右。帧率接近60。
    帧率越接近60滑动越顺畅。

      但是经过跟@nerozhao的讨论发现第四种Core Graphics绘制圆角会有大量的内存占用,
      而且每次绘制的时候CUP消耗会很大。
    
      由于@nerozhao使用了UITableView进行测试,因为UITableView滚动的时候是一直在
      复用的,UIImageView会重复绘制,所以会一直消耗CUP,然后你就能看的明显的卡顿。
    
      @nerozhao在UITableView里图片的绘制在后台线程进行绘制,解决了卡顿问题,但是
      由于是在后台线程的异步绘制所以在滚动的时候会看到图片先是正方形然后再变成圆形。
    
      而我使用的是UIScrollerView进行的测试,只有第一次绘制的时候会占用CUP资源,
      所以滑动的时候还是挺流畅的,但是内存消耗还是很大。如果是主线程绘制的话会阻塞一
      点时间的主线程,而后台线程绘制的话内存消耗会更大,特别容易崩溃。
    

    所以第四种方法当图片特别多的时候很容易Received memory warning导致崩溃


    解决问题参考文章

    • UIImage drawInRect绘制圆角内存暴增问题

    文章:
    内存恶鬼drawRect - 谈画图功能的内存优化


    最后

    关于研究过程及各种设置圆角方法的例子测试对比 github源码
    如果我的文章对你有帮助欢迎github Star
    如果你有什么问题或者想交流的可以联系我。QQ:398411773

    相关文章

      网友评论

      • 迷恋代码:楼主,为什么我这边用第二种方法是黄色
      • 上北以北:mask是最好的
      • iOS104:第五种方法在cell中如果有多个圆角,FPS还是会很低,亲测圆角问题只有在iOS 8的系统才有问题,iOS 8以上没有问题。
      • CodeGeass:maskToBounds和clipsToBounds性能是一样的吗
        超_iOS:同问
      • 尼古拉斯_小巍:『UIImage drawInRect绘制圆角

        这种方式GPU损耗低内存占用大,而且UIButton上不知道怎么绘制,可以用
        UIimageView添加个点击手势当做UIButton使用。

        UIGraphicsBeginImageContextWithOptions(avatarImageView.bounds.size, NO, [UIScreen mainScreen].scale);
        [[UIBezierPath bezierPathWithRoundedRect:avatarImageView.bounds
        cornerRadius:50] addClip];
        [image drawInRect:avatarImageView.bounds];
        avatarImageView.image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        这段方法可以写在SDWebImage的completed回调里,在主线程异步绘制。
        也可以封装到UIImageView里,写了个DSRoundImageView。后台线程异步绘制,不会阻塞主线程。
        问题:这种方法图片很多的话CUP消耗会高,内存占用也会暴增,而且后台线程绘制会比在主线程绘制占用更多的内存,不知道怎么解决?求大神指教!』

        这段代码不能写到SDWebImage的completed回调里,因为回调本身就是返回到了主线程,写到回调里 相当于还是在主线程上做的裁剪
      • dedenc:你好 我用了你说的那种设置圆角的方法 可是还是会模糊
        self.iconImageView.layer.cornerRadius = self.iconImageView.frame.size.width / 2;
        self.iconImageView.layer.masksToBounds = YES;
        self.iconImageView.layer.shouldRasterize = YES;
        self.iconImageView.layer.rasterizationScale =[UIScreen mainScreen].scale;
        不加最后两句是不模糊的,求指教
      • 苏格拉木有底oo:你好我使用Core Graphics绘制圆角,但是结果却未能如愿, 想问问你是哪里出了问题, 方便留个邮箱吗?我把图片和代码发你
        苏格拉木有底oo:@齐滇大圣 你好,非常感谢,不过问题我自己解决了,是一些别的地方的问题影响了图片的显示
        齐滇大圣:@苏格拉木有底oo 398411773@qq.com
      • Raybon_lee:不错,这个最近在测试
      • newbiecoder:今天刚考虑这个问题,太及时了
        齐滇大圣:@newbiecoder 我发你一张:smile:
        newbiecoder:@齐滇大圣 我们这的美工都是大爷!愁啊!我都开始自学ps了😓
        齐滇大圣:@newbiecoder 还有很多没考虑到的,建议使用圆片覆盖
      • niuxinghua:xx.layer.rasterizationScale=[UIScreen mainScreen].scale;就不会模糊了。。。
        dedenc:@niuxinghua 你好 我加了这句话就模糊了 请问是问什么
        再见远洋:@niuxinghua 我也发现使用绘制的方式导致图片模糊了:relieved:
        齐滇大圣:@niuxinghua 多谢提醒
      • RanMeng:确实是这样的
        齐滇大圣:@RanMeng 确实什么样?哪里有错吗?
      • caiwenshu:不是有一个mask的方法吗
        齐滇大圣:@caiwenshu mask方法比cornerRadius更耗性能,http://www.cocoachina.com/ios/20150803/12873.html 这篇文章里有写,mask是两次离屏渲染一次主屏渲染
      • nerozhao:第四种其实比第一种更卡
        iOS104:@齐滇大圣 还有一个关注点就是FPS,测试过所有的方法,圆角太多的话,所有的方法FPS都很低
        nerozhao:@齐滇大圣 来个邮箱给你demo
        齐滇大圣:@nerozhao 为什么?你测试过了吗?求指教!我看别人说的这种方式应该好一些吧,因为这是把绘制圆角放到了cup上,不用离屏渲染了,很多时候卡是因为GPU上耗性能。我自己没测试过,明天去测测看!

      本文标题:iOS图片设置圆角性能问题

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