美文网首页性能优化iOS开发中填坑工具iOSer 干货部落
老生常谈的圆角图片优化,结合SDWebImage的解决方案。

老生常谈的圆角图片优化,结合SDWebImage的解决方案。

作者: 大灰灰iOS | 来源:发表于2016-03-06 00:36 被阅读4899次

很早就想写一篇讲述圆角优化的博客了。。结果发现这篇文章iOS 高效添加圆角效果实战讲解写得很好:摘录文章的总结部分,当拓展阅读了:

总结
如果能够只用 cornerRadius 解决问题,就不用优化。
如果必须设置 masksToBounds,可以参考圆角视图的数量,如果数量较少(一页只有几个)也可以考虑不用优化。
UIImageView 的圆角通过直接截取图片实现,其它视图的圆角可以通过 Core Graphics 画出圆角矩形实现。

过早的优化是恶魔,这句话确实很有道理

Premature optimization is the root of all evil --Donald Knuth

当时我做圆角优化的场景,是一个collectionView很多头像的展示。如果单纯的用layer设置cornerRadius和masksToBounds,会产生离屏渲染,小心别让圆角成了你列表的帧数杀手,这篇文章已经写的很清楚了,就不赘述了。

self.xxView.layer.cornerRadius = 5.0f;
self.xxView.layer.masksToBounds = YES;

Advanced Graphics and Animations for iOS Apps(session 419) 学习与延伸这篇文章也介绍了离屏渲染的一些知识,有需要的同学可以去拓展阅读一下。

正文

其实写到这里我已经很方了。。因为感觉好多人都写过了。。
这边再留两篇文章:
最早接触到圆角优化是里脊串写的一次对MKMapView的性能优化
近来还有叶孤城___reviewcode.cn里提出的解决方案。
你们可以先看我的文章,然后再去拓展阅读,反正上面那么多链接你们也要看一会[认真脸]。

既然原理上面的文章都讲过很多了,我真不好意思再讲一遍了,我就来说说我怎么做的吧。
首先给imageView一个catagory,核心方法如下:

- (void)lhy_loadImageUrlStr:(NSString *)urlStr placeHolderImageName:(NSString *)placeHolderStr radius:(CGFloat)radius;

当然也有些简便的方法方便不同场景调用:

- (void)lhy_loadImageUrlStr:(NSString *)urlStr;
- (void)lhy_loadImageUrlStr:(NSString *)urlStr radius:(CGFloat)radius;

核心方法是这样写的:

- (void)lhy_loadImageUrlStr:(NSString *)urlStr placeHolderImageName:(NSString *)placeHolderStr radius:(CGFloat)radius {
    //something
    //这里有针对不同需求的处理,我就没贴出来了
    //...

    NSURL *url;

    if (placeHolderStr == nil) {
        placeHolderStr = @"你通用的占位图地址";
    }
   
    //这里传CGFLOAT_MIN,就是默认以图片宽度的一半为圆角
    if (radius == CGFLOAT_MIN) {
        radius = self.frame.size.width/2.0;
    }

    url = [NSURL URLWithString:urlStr];

    if (radius != 0.0) {
        //头像需要手动缓存处理成圆角的图片
        NSString *cacheurlStr = [urlStr stringByAppendingString:@"radiusCache"];
        UIImage *cacheImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:cacheurlStr];
        if (cacheImage) {
            self.image = cacheImage;
        }
        else {
            [self sd_setImageWithURL:url placeholderImage:[UIImage imageNamed:placeHolderStr] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
                if (!error) {
                    UIImage *radiusImage = [UIImage createRoundedRectImage:image size:self.frame.size radius:radius];
                    self.image = radiusImage;
                    [[SDImageCache sharedImageCache] storeImage:radiusImage forKey:cacheurlStr];
                    //清除原有非圆角图片缓存
                    [[SDImageCache sharedImageCache] removeImageForKey:urlStr];
                }
            }];
        }
    }
    else {
        [self sd_setImageWithURL:url placeholderImage:[UIImage imageNamed:placeHolderStr] completed:nil];
    }
}

圆角的绘制方法是一个imageView的catagory

static void addRoundedRectToPath(CGContextRef context, CGRect rect, float ovalWidth,
                                 float ovalHeight)
{
    float fw, fh;
    
    if (ovalWidth == 0 || ovalHeight == 0)
    {
        CGContextAddRect(context, rect);
        return;
    }
    
    CGContextSaveGState(context);
    CGContextTranslateCTM(context, CGRectGetMinX(rect), CGRectGetMinY(rect));
    CGContextScaleCTM(context, ovalWidth, ovalHeight);
    fw = CGRectGetWidth(rect) / ovalWidth;
    fh = CGRectGetHeight(rect) / ovalHeight;
    
    //根据圆角路径绘制
    CGContextMoveToPoint(context, fw, fh/2); 
    CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1); 
    CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1); 
    CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1); 
    CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1);
    
    CGContextClosePath(context);
    CGContextRestoreGState(context);
}

+ (id)createRoundedRectImage:(UIImage*)image size:(CGSize)size radius:(NSInteger)r
{
    int w = size.width;
    int h = size.height;
    
    UIImage *img = image;
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedFirst);
    //CGContextRef context = UIGraphicsGetCurrentContext();
    CGRect rect = CGRectMake(0, 0, w, h);
    
    CGContextBeginPath(context);
    addRoundedRectToPath(context, rect, r, r);
    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);
    
    return img;
}

这样只要两个catagory,这个头像圆角结合SDWebImage缓存的应用场景,就可以即插即用啦。

简书已经弃用,欢迎移步我的小专栏:
https://xiaozhuanlan.com/dahuihuiiOS

相关文章

网友评论

  • Wayne_Wang:感谢楼主慷慨分享,小弟遇到了个问题是 图片加载出来旋转了90度,测试发现,是根据手机拍照的方向有关,我竖屏拍照图片会被旋转90度然后加载出来,当我横屏拍照的时候图片就是正常显示。但是我直接用sd加载的时候就不会。
    Wayne_Wang:对于这个问题我测试发现是imageView的catagory设置圆形图的问题,我用另一种drawInRect的方法去做的时候图片就不会旋转了。
  • 独木舟的木:兄弟,你那保存图片到磁盘和删除图片到磁盘的方法在最新的 SDWebImage 中过时了,应该用下面的:
    // 保存图片到磁盘
    - (void)storeImage:(nullable UIImage *)image
    forKey:(nullable NSString *)key
    completion:(nullable SDWebImageNoParamsBlock)completionBlock;
    // 清除原有非圆角图片缓存
    - (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion;
    大灰灰iOS:@独木舟的木 么么哒,等我回去看看改下
  • 650c659e28f7:你好 多谢你详细的解释
    但这句话 的if条件
    ```objc
    if (radius != 0.0)
    ```
    是不是改成
    ```objc
    if (radius > 0.0001)
    ```
    会更好些
    650c659e28f7:@大灰灰iOS CGFLOAT_MIN 和 0.001是个精度的问题 可能我表述的不清楚 我的意思是 float的值 是不是不应该用 = 去判断
    大灰灰iOS:@1ast0bject 可能我想打0.f的。。然后打成了0.0...还有0.0001不如用CGFLOAT_MIN..
  • 浮游lb:你好,使用该方式绘制,如果圆角图片数量多,会造成CUP和内存猛涨,求教是否有良好解决方案?还有,SD回调中设置裁剪后的圆角图,会造成一个现象:先显示方形图,一瞬间后再修改为圆角图,也就是肉眼能很明显看到图片是从方形跳到到圆形,求教解决方案 :smile:
    大灰灰iOS:@isLin冰 你的跳动要做一个原型的占位图,然后每次加载都要清空cell。。内存问题可以提前渔预加载,最近在做一套更好的解决方案。。。
  • 7728d3c96a71:就过来踩几脚,大灰灰6666,我是小健。 :smile:
    大灰灰iOS:@Victory_LAU :mask::mask:公司没招收新人的计划
    7728d3c96a71:@大灰灰iOS 收徒弟么:heart_eyes:打下手也行啊:heart_eyes:
    大灰灰iOS:@Victory_LAU 不666,都好久没写了
  • QuerySky:测试了一下,图片模糊有两个原因:
    1.图片没有按照控件的比例来进行圆角绘制
    2.SDWebImage获取下来的图片会根据设备适配,比实际尺寸小2/1.5倍
    这样导致图片需要的部分不对,也被拉伸了
    4fb788cc8fd1:@QuerySky 那需要在哪里加点东西?
    大灰灰iOS:@QuerySky 666,我之前就在模拟器上看了。。没走真机。。。多谢多谢。。
  • fe86d48aad87:找到原因了,是UIImage分类那两段绘制圆角的方法有问题。

    在UIImageView分类SDWebImage下载图片成功回调那儿,改用下面段方法绘制成圆角图片就可以了。
    UIImage *radiusImage = [image drawRectWithRoundedCorner:radius size:self.bounds.size]; // 得到清晰圆角图片

    UIImage分类:
    - (UIImage *)drawRectWithRoundedCorner:(CGFloat)radius size:(CGSize)sizeToFit
    {

    CGRect rect = CGRectMake(0, 0, sizeToFit.width, sizeToFit.height);
    UIGraphicsBeginImageContextWithOptions(rect.size, false, [UIScreen mainScreen].scale);

    CGContextAddPath(UIGraphicsGetCurrentContext(), [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radius, radius)].CGPath);
    CGContextClip(UIGraphicsGetCurrentContext());

    [self drawInRect:rect];
    CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke);
    UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return outputImage;
    }
    大灰灰iOS:@heyuze drawRect的效率问题。。可能会比较耗资源。。不过这里SDWeb的回调应该是异步的,关系不大
  • 0fcbeabcf3b5:你的第二个category应该是UIImage的吧?
    大灰灰iOS:@Ke_Vin 这个之前一直想搞。。还没来得及搞。。楼上有个人回复的contentScale你可以试试看
    0fcbeabcf3b5:@大灰灰iOS 绘制出来后图片太模糊了,有什么解决办法么?
    大灰灰iOS:@Ke_Vin 对滴
  • 咖啡凯:👏🏻
  • 再见远洋:有没有发现绘制出来的图片是很模糊的 可以通过设置某一个属性来解决吗??
    Raybon_lee:@再见远洋 我记得是在layer层
    fe86d48aad87:@Raybon_lee 这个属性放哪儿呢
    Raybon_lee:@再见远洋 contentScale ,试一下这个
  • 明明明Y:关于图片优化。记得有一条 好像是少用[UIImage ImageNamed:...] 而是直接给图片的URL(本地或者网络的) 用ImageName 会遍历文件.
    大灰灰iOS:@明明明Y 我内存优化(一)的文章里有写。。常用的如占位图,用ImageNamed比较好,会有缓存。
  • 再见远洋:话说没有github地址呢?

    Wayne_Wang:感谢楼主慷慨分享,小弟遇到了个问题是 图片加载出来旋转了90度,测试发现,是根据手机拍照的方向有关,我竖屏拍照图片会被旋转90度然后加载出来,当我横屏拍照的时候图片就是正常显示。但是我直接用sd加载的时候就不会。
    大灰灰iOS:@再见远洋 没特意做了。。代码都在文章里了。。

本文标题:老生常谈的圆角图片优化,结合SDWebImage的解决方案。

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