每日一问06——imageView的圆角优化

基本方法

使用layer的cornerRadius属性和masksToBounds。

self.imageView.layer.cornerRadius = 8.f;
self.imageView.layer.masksToBounds = YES;

这样设置圆角是最基本的一种方式,但当屏幕中有多个圆角图片存在会发生明显的界面卡顿现象。
原因是cornerRadius和masksToBounds同时使用会发生离屏渲染(离屏渲染参考每日一问03)
值得一提的是iOS9以后,cornerRadius = YES在UIImageView上不会发生离屏渲染,但在button等其他控件上使用还是会发生。

这种方法使用场景就是同界面中没有过多的圆角图片。

基本方案的优化

setCornerRadius设置圆角之后,shouldRasterize=YES光栅化
原理是开启了光栅化以后,可以使离屏渲染的结果缓存到内存中存为位图, 使用的时候直接使用缓存,节省了一直离屏渲染损耗的性能。
问题:
1.被光栅化的图片如果超过100ms没有被使用,则会被移除
2.系统限制了缓存的大小为2.5X Screen Size.如果过度使用,超出缓存之后,同样会造成大量的offscreen渲染

所以如果不是同一张图片多次使用,这种方案也不可取。

使用 mask layer

UIBezierPath* maskPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:imageView.bounds.size];
    
    CAShapeLayer* maskLayer = [CAShapeLayer layer];
    maskLayer.frame = imageView.bounds;
    maskLayer.path = maskPath.CGPath;
    
    imageView.layer.mask = maskLayer;

这种方法本质上是用遮罩层 mask 来实现,因此同样无可避免的会导致离屏渲染。

使用混合图层

在要添加圆角的视图上再叠加一个部分透明的视图,只对圆角部分进行遮挡。多一个图层会增加合成的工作量,但这点工作量与离屏渲染相比微不足道,性能上无论各方面都和无效果持平。

透明图片

这种方式在性能上非常的好,但是缺点就是角度不同需要设计师提供多张不同的图片,非常不灵活。

image.png

直接裁剪image

实现一,这个方案的思路是将离屏渲染的消耗从GPU转交给CPU,让CPU提前处理一下bitmap数据。这算是CPU的离屏渲染。

CGRect rect = CGRectMake(0, 0, size.width, size.height);
    
    UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radius, radius)];
    CGContextAddPath(ctx,path.CGPath);
    CGContextClip(ctx);
    [self drawInRect:rect];
    CGContextDrawPath(ctx, kCGPathFillStroke);
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;

实现二,这个方案采用SDWebImage处理图片时Core Graphics绘制圆角。

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);  // Start at lower right corner
    CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1);  // Top right corner
    CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1); // Top left corner
    CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1); // Lower left corner
    CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1); // Back to lower right
    
    CGContextClosePath(context);
    CGContextRestoreGState(context);
}

- (UIImage *)createImageWith:(CGSize)imageSize Radius:(CGFloat)radius{
    int w = imageSize.width;
    int h = imageSize.height;
    UIImage *img = self;
    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);
    
    return img;
}

同样没有离屏渲染但这个方案在性能上比方案一更好,主要体现在CPU和内存消耗更低。从实现上来分析,方案二的优点主要是采用了强制解压生成位图。优化了CPU进行解压那一块的消耗。(参考每日一问04)

总结:圆角优化这个话题虽然已经过去很久,网上也给出了很多成熟的解决方案,但导致需要优化的原理和优化方案的原理我们还是应该了解的。

相关文章

iOS 高效添加圆角效果实战讲解
iOS 高效添加圆角效果实战讲解-
圆角卡顿刨根问底
iOS图片高性能设置圆角
iOS UIView 圆角和加边框方式总结

你可能感兴趣的:(每日一问06——imageView的圆角优化)