iOS离屏渲染,你真的懂吗

很多人都知道设置了layer的圆角属性cornerRadius并裁减clipsToBounds/layer.masksToBounds = YES之后会触发离屏渲染,在类似tableView这种在整个界面上呈现出很多圆角视图就会造成卡顿,所以不建议在一个页面上用这种方式大量设置圆角,但是所有的圆角都会触发离屏渲染吗?我们来看看下面的例子。

  • 先通过Xcode模拟器打开离屏渲染效果.


真机调试则跑起来后在Xcode顶部菜单选择Debug--ViewDebugging--Rendering--Color OffScreen-Rendered Yellow开启。

  • 我们来看下下面代码跑起来的效果
///都用圆角
    //imageView没有设置背景色,不会离屏
    UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 30, 100, 100)];
    imgView.layer.cornerRadius = 20;
    imgView.layer.masksToBounds = YES;
    imgView.image = [UIImage imageNamed:@"测试.png"];
    [self.view addSubview:imgView];
    
    //imageView设置背景色,用clipsToBounds/masksToBounds会离屏
    UIImageView *imgView2 = [[UIImageView alloc] initWithFrame:CGRectMake(100, 150, 100, 100)];
    imgView2.layer.cornerRadius = 20;
//    imgView2.clipsToBounds = YES;
    imgView2.layer.masksToBounds = YES;
    imgView2.backgroundColor = [UIColor whiteColor];
    imgView2.image = [UIImage imageNamed:@"测试.png"];
    [self.view addSubview:imgView2];
    
    //imageView设置边框,会离屏
    UIImageView *imgView3 = [[UIImageView alloc] initWithFrame:CGRectMake(100, 270, 100, 100)];
    imgView3.layer.cornerRadius = 20;
    imgView3.layer.masksToBounds = YES;
    imgView3.image = [UIImage imageNamed:@"测试.png"];
    imgView3.layer.borderWidth = 2;
    imgView3.layer.borderColor = [UIColor redColor].CGColor;
    [self.view addSubview:imgView3];
    
    //按钮只设置背景色,不会离屏
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
    btn.frame = CGRectMake(100, 400, 100, 100);
    btn.layer.cornerRadius = 20;
    btn.layer.masksToBounds = YES;
    btn.backgroundColor = [UIColor redColor];
    [self.view addSubview:btn];
    
    //按钮设置背景图,会离屏
    UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn1.frame = CGRectMake(100, 520, 100, 100);
    [btn1 setImage:[UIImage imageNamed:@"测试.png"] forState:UIControlStateNormal];
    btn1.layer.cornerRadius = 20;
    btn1.layer.masksToBounds = YES;
    [self.view addSubview:btn1];
  • 可以看到黄色的区域发生了离屏渲染,分别是第2、3、5个视图,而第1、4个正常。说明了离屏渲染的触发是有条件的。

我们先来了解下屏幕的渲染流程,下面分别是正常渲染流程和离屏渲染流程


苹果使用了CPU绘制和GPU绘制这两种不同的机制处理,单个视图的绘制可能同时需要这两种机制。这两种机制有不同的性能考虑。

CPU“离屏渲染”

当你实现了drawRect并使用CoreGraphics,或者使用CoreText绘图,也会触发CPU"离屏渲染”,这种方式是CPU去同步绘图,它将bits写入位图缓冲区。

这种新开一块CGContext来画图的操作,没有直接将像素数据放到 frame buffer,而是暂时放到了CGContext。进一步来说,其实所有CPU进行的光栅化操作(如文字渲染、图片解码),都无法直接绘制到由GPU掌管的frame buffer,只能暂时先放在另一块内存之中,说起来都属于“离屏渲染"
其实通过CPU渲染就是俗称的“软件渲染”,而真正的离屏渲染发生在GPU
不信的话,你此时打开Xcode调试的“Color offscreen rendered yellow”开关,你会发现这片区域不会被标记为黄色,说明Xcode并不认为这属于离屏渲染。

GPU离屏渲染

真正的离屏渲染指的是在渲染服务器(一个独立的进程)中进行的,并通过GPU执行。当OpenGL(现在是Metal)渲染器去绘制每一层时,它可能不得不停止一些子层次结构,并将它们组合到一个单独的缓冲区中。

假设我们的屏幕刷新率为60FPS,也就是一秒钟能显示60帧,那么在正常渲染流程里,会先把图像数据放在帧缓冲区(Frame Buffer)里面,然后屏幕刷新的时候视频控制器不断从帧缓冲区里取数据。
GPU离屏渲染的时候,会先把CPU处理好的数据放在帧缓冲区之外另外开辟的离屏缓冲区(OffScreen Buffer)里面,等把要一起显示的数据都叠加到离屏缓冲区里之后,再交由帧缓冲区按正常渲染流程进行。
举两个例子:

  • 当我们使用遮罩效果layer.mask的时候,也会触发离屏渲染,它的渲染过程中,GPU首先渲染好遮罩层layer,这时候并不能交给帧缓冲区给屏幕显示,需要等到整个图像遮罩效果处理后才能交给帧缓冲区。这时候它就把遮罩层放在离屏缓冲区,然后再渲染好另外一个图层,之后也放到离屏缓冲区里,两个图层合并之后再交给帧缓冲区,最后显示到屏幕上。而这一次mask发生了两次离屏渲染和一次主屏渲染,相当于普通视图的3倍,若再加上下文环境切换,一次 layer.mask就是普通渲染的30倍以上耗时操作,原因下面会解释。

我做了个测试来直观感受使用遮罩的性能损耗。
当使用layer.cornerRadius+layer.masksToBounds处理圆角视图,在滚动视图里快速滚动时,可以看到处于中间的FPS变化在56左右,前面几秒是debug模式应用启动,帧率比较低不用在意。

而如果改为使用

    UIBezierPath* path = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:byRoundingCorners cornerRadii:cornerRadius];
    CAShapeLayer* shape = [CAShapeLayer layer];
    shape.path = path.CGPath;
    self.layer.mask = shape;

可以看到FPS已经低于50了。所以尽量不要过多的使用layer.mask去处理视图,特别是频繁滚动的列表视图。

  • 当我们使用毛玻璃效果UIVisualEffectView,渲染流程是先拿到渲染内容(Render Content)、捕获内容(capture Content)、垂直模糊(Vertical Blur)、水平模糊(Horizontal Blur),分别把这4步的图层放到离屏缓冲区里,然后拿出来合成(Compositing Pass)最终的模糊效果图。

所以离屏渲染的原理是:APP进行额外的渲染和合并操作,在离屏缓冲区(OffScreen Buffer)组合,之后交给帧缓冲区(Frame Buffer),最后显示到屏幕上。

这样(触发离屏渲染)带来的影响是什么呢?
1.需要开辟额外的存储空间;
2.从Frame Buffer切换到OffScreen Buffer,再从OffScreen Buffer转存到Frame Buffer,上下文环境的切换需要时间;

这就解释了为什么离屏渲染这么耗时。原因主要在创建缓冲区和上下文切换。创建新的缓冲区代价都不算大,付出最大代价的是上下文切换。

上下文切换:首先我要保存当前屏幕渲染环境,然后切换到一个新的绘制环境,申请绘制资源,初始化环境,然后开始一个绘制,绘制完毕后销毁这个绘制环境,例如需要切换到On-Screen Rendering或者再开始一个新的离屏渲染重复之前的操作。

所以开启了大量的离屏渲染就会容易掉帧,造成卡顿。

既然离屏渲染有这些性能问题,那为什么还要用呢?

  • 当我们需要一些特殊的效果,这种效果不能一次性渲染完成,需要使用离屏缓冲区来保存中间状态,就不得不使用离屏渲染,这种情况是系统自动触发,比如经常使用的圆角、阴影、高斯模糊、遮罩(mask),抗锯齿(edge antialiasing)等。
  • 可以提升渲染的效率,当一个效果无法避免触发离屏渲染,那么可以缓存这个结果并复用,来降低性能影响。这种情况是需要我们手动触发的,也就是开启光栅化

主动使用离屏渲染的原因:光栅化
上面说到光栅化shouldRasterize = YES)可以会将处理渲染后的视图缓存复用,提高性能。
使用方法:

view.layer.shouldRasterize = YES;
view.layer.rasterizationScale = UIScreen.mainScreen.scale;
苹果官方文档的说明

但是光栅化的开启并不一定会带来好处,使用建议:

  • 如果layer不是静态,需要被频繁修改,比如处于动画中、size修改、tableViewcollectionView视图中,那么开启光栅化反而影响了效率;
  • 离屏渲染缓存内容有时间限制,缓存的内容如果100ms内没有被使用,那么它就会丢弃,无法进行复用;
  • 离屏渲染的缓存空间有限,大小相当于屏幕像素点的2.5倍,超过的话也会失效,无法进行复用了;

总结来说,如果可以避免触发离屏渲染,尽量避免,否则如果视图可以复用并且是静态内容(也就是内部结构和内容不发生变化的视图),才考虑开启光栅化,但是也要考虑光栅缓存的利用率,如果整屏视图中利用到光栅缓存的视图很少,那反而更耗费性能。(可以通过CoreAnimation InStruments工具查看并通过Xcode打开「Color Hits Green and Misses Red」观察离屏渲染对缓存的使用,绿色代表了用到了光栅缓存,红色则是需要重新渲染的部分,红色意味着栅格化反而有负面的性能影响了)

回到一开始的问题,设置圆角而触发离屏渲染是有条件的。

先来看看一个视图的渲染层级。


再来看看苹果官方文档上关于设置圆角的说法。


可以看到,苹果告诉我们,设置了cornerRadius只会设置backgroundColorborder的圆角,而内容图层contents,并不会设置圆角。除非你同时设置了layer.masksToBounds(对应view.clipsToBounds),才会也对contents设置圆角。

离屏渲染的逻辑

这张图演示的是著名的油画算法。指的是先绘制远的部分,再绘制近的部分。我们图层的渲染也是同理,系统会先绘制最底层的图层,再往上一层层的绘制,最后形成了图层树,苹果建议开发者在建立UI的时候,图层树不要太复杂,层级不要太多,不然也是会有性能影响。

  • 正常渲染:如果是不触发离屏渲染的正常渲染,苹果在绘制完最底层的图层,从帧缓冲区显示到屏幕上之后,就丢弃了,并不会保存起来,再画第二层,也是如此,显示完了就丢弃,从而节省了空间。
  • 离屏渲染:如果对一个多图层图像进行圆角处理,就需要对所有图层进行圆角(包括内容contents),如果按照正常渲染,一层用完就丢弃,这样就达不到显示的效果。这时候就需要开辟一个离屏缓冲区去保存这些图层,等到所有图层都做了圆角处理,就把它们从离屏缓冲区里取出来进行合并显示。

这就解释了为什么有些圆角会触发离屏渲染,纠其根本就是用到了离屏缓冲区。

我总结了处理圆角的几种可用方式.
1.最简单的就是让UI同事切一个带圆角的图。

  1. 如果内容contents没有内容,只有背景色,就不用使用masksToBounds/ClipsToBounds了。直接设置cornerRadius就好了。
    3.设置imageView的圆角并裁减。比如我们要设置带图片UIButton的圆角,可以这样设置。
   //按钮设置背景图,会离屏
    UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn1.frame = CGRectMake(100, 100, 100, 100);
    [btn1 setImage:[UIImage imageNamed:@"测试.png"] forState:UIControlStateNormal];
    btn1.layer.cornerRadius = 20;
    btn1.layer.masksToBounds = YES;
    [self.view addSubview:btn1];
    
    //改为对button上的imageView裁剪圆角
    UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeCustom];
    btn2.frame = CGRectMake(100, 220, 100, 100);
    [btn2 setImage:[UIImage imageNamed:@"测试.png"] forState:UIControlStateNormal];
    btn2.imageView.layer.cornerRadius = 20;
    btn2.imageView.layer.masksToBounds = YES;
    [self.view addSubview:btn2];

这样就不会离屏渲染了。


  • 4.CATextLayer, Core Text设置了文本内容也会触发离屏渲染,而label设置圆角不当的时候也会触发离屏渲染
    //文字圆角
    UILabel *lab = [[UILabel alloc] initWithFrame:CGRectMake(220, 30, 50, 50)];
    lab.text = @"文字";
    //设置layer的背景色会离屏渲染
    lab.layer.backgroundColor = [UIColor redColor].CGColor;
    //设置label背景色不会离屏渲染
//    lab.backgroundColor = [UIColor redColor];
    lab.layer.cornerRadius = 20;
    //当设置layer背景色时,圆角不用裁剪也能生效。
    lab.layer.masksToBounds = YES;
    //在masksToBounds基础上设置了边框就会导致离屏
//    lab.layer.borderWidth = 1;
    [self.view addSubview:lab];
    
    //添加子视图后只要有裁剪,不论是在设置哪里的背景色都会离屏。
    
//    UIView *viewinLab = [[UIView alloc] initWithFrame:CGRectMake(30, 0, 20, 20)];
//    viewinLab.backgroundColor = [UIColor blueColor];
//    [lab addSubview:viewinLab];

a. 会触发离屏渲染的情况:前提条件:cornerRadius+masksToBounds,加上以下任一条件。
1⃣️设置了layer.backgroundColor
2⃣️设置了边框
3⃣️添加子视图
b. 不会触发离屏渲染的情况:
cornerRadius+masksToBounds+label.backgroundColor,或者cornerRadius+layer.backgroundColor(加上masksToBounds会离屏渲染)。

总结:
圆角处理会导致离屏渲染的根本原因就是对多层进行裁剪,用到了离屏缓冲区。单纯cornerRadius+layer.backgroundColor设置圆角只会对contents圆角,在此之上加了masksToBounds,则会对contentscontents下面背景色层裁剪,所以是多层裁剪。而改为label.backgroundColormasksToBounds则只会对contents层裁剪圆角,不存在layer的背景色层,所以不会触发离屏渲染。

建议使用方式
cornerRadius+layer.backgroundColor方式对label设置圆角,或者cornerRadius+label.backgroundColor+ masksToBounds设置圆角。

  1. 使用YY_Image的处理方式
    这个方法里还可以接收边框设置,需要的时候可以一步到位。也可以自己提取里面的圆角处理方式使用。
- (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius
                                 corners:(UIRectCorner)corners
                             borderWidth:(CGFloat)borderWidth
                             borderColor:(UIColor *)borderColor
                          borderLineJoin:(CGLineJoin)borderLineJoin {
    
    if (corners != UIRectCornerAllCorners) {
        UIRectCorner tmp = 0;
        if (corners & UIRectCornerTopLeft) tmp |= UIRectCornerBottomLeft;
        if (corners & UIRectCornerTopRight) tmp |= UIRectCornerBottomRight;
        if (corners & UIRectCornerBottomLeft) tmp |= UIRectCornerTopLeft;
        if (corners & UIRectCornerBottomRight) tmp |= UIRectCornerTopRight;
        corners = tmp;
    }
    
    UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
    CGContextScaleCTM(context, 1, -1);
    CGContextTranslateCTM(context, 0, -rect.size.height);
    
    CGFloat minSize = MIN(self.size.width, self.size.height);
    if (borderWidth < minSize / 2) {
        UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)];
        [path closePath];
        
        CGContextSaveGState(context);
        [path addClip];
        CGContextDrawImage(context, rect, self.CGImage);
        CGContextRestoreGState(context);
    }
    
    if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
        CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
        CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
        CGFloat strokeRadius = radius > self.scale / 2 ? radius - self.scale / 2 : 0;
        UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, borderWidth)];
        [path closePath];
        
        path.lineWidth = borderWidth;
        path.lineJoinStyle = borderLineJoin;
        [borderColor setStroke];
        [path stroke];
    }
    
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

6.如果不是图片,处理指定部分圆角,可以采用UIBezierPath+CAShapeLayer,将layer添加到view上面,而不使用mask

    UIBezierPath* path = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:byRoundingCorners cornerRadii:cornerRadius];
    CAShapeLayer* shape = [CAShapeLayer layer];
    shape.path = path.CGPath;
    //bgColor为视图背景色
    shape.fillColor = bgColor.CGColor;
    shape.strokeColor = UIColor.blueColor.CGColor;
    [self.layer insertSublayer:shape atIndex:0];
  1. 采用CGContext异步渲染,CPU绘图的方式,GPU使用率低。使用的时候可以放在后台绘制,这样CPU使用率低很多,帧率提高很多。

第一种方式:不使用贝塞尔曲线,只使用CGContext,所以不能指定部分圆角。

let img = UIImageView(frame: CGRect(x: 100, y: 20, width: 20, height: 20))
img.image = UIImage(named: "11")
navigationBar.addSubview(img)
let frame = img.bounds
UIGraphicsBeginImageContextWithOptions(frame.size, false, 0)
let ctx = UIGraphicsGetCurrentContext()
ctx?.addEllipse(in: frame)
ctx?.clip()
img.draw(frame)
        
let ig = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
img.image = ig

第二种方式:处理UIImage,能指定部分圆角。

let img = UIImageView(frame: CGRect(x: 100, y: 20, width: 20, height: 20))
img.image = UIImage(named: "11")
navigationBar.addSubview(img)
let frame = img.bounds
UIGraphicsBeginImageContextWithOptions(frame.size, false, 0)
UIBezierPath.init(roundedRect: frame, byRoundingCorners: [.topRight], cornerRadii: CGSize(width: 10, height: 10)).addClip()
img.draw(frame)
let ig = UIGraphicsGetImageFromCurrentImageContext()
img.image = ig
UIGraphicsEndImageContext()

第三种方式:视图渲染1,能指定部分圆角,适用所有视图

let frame = img.bounds
UIGraphicsBeginImageContextWithOptions(frame.size, false, 0)
        
UIBezierPath.init(roundedRect: frame, byRoundingCorners: [.topRight], cornerRadii: CGSize(width: 10, height: 10)).addClip()
img.layer.render(in: UIGraphicsGetCurrentContext()!)
let ig = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
img.layer.contents = ig?.cgImage

第四种方式:视图渲染2,能指定部分圆角,适用所有视图

let renderer = UIGraphicsImageRenderer(size: size)
let frame = img.bounds
img.layer.contents = renderer.image { rendererContext in
UIBezierPath.init(roundedRect: frame, byRoundingCorners: [.topRight], cornerRadii: CGSize(width: 10, height: 10)).addClip()
img.layer.render(in: rendererContext.cgContext)
}.cgImage
  1. 使用混合图层模拟mask效果,比如在要添加圆角的视图上再叠加一个部分透明的视图,只对圆角部分进行遮挡,来达到mask的效果,不会离屏渲染,并且可以指定任意角为圆角,推荐使用
// 绘制圆形
- (UIImage *)drawCircleRadius:(float)radius viewSize:(CGSize)viewSize fillColor:(UIColor *)fillColor {
    UIGraphicsBeginImageContextWithOptions(viewSize, false, [UIScreen mainScreen].scale);
    
    // 1、获取当前上下文
    CGContextRef contextRef = UIGraphicsGetCurrentContext();
    
    //2.描述路径
    // ArcCenter:中心点 radius:半径 startAngle起始角度 endAngle结束角度 clockwise:是否顺时针
    UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(viewSize.width * 0.5, viewSize.height * 0.5) radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:NO];
//    [bezierPath closePath];
    
    // 3.外边
    [bezierPath moveToPoint:CGPointMake(0, 0)];
    [bezierPath addLineToPoint:CGPointMake(viewSize.width, 0)];
    [bezierPath addLineToPoint:CGPointMake(viewSize.width, viewSize.height)];
    [bezierPath addLineToPoint:CGPointMake(0, viewSize.height)];
    [bezierPath closePath];
    
    //4.设置颜色
    [fillColor setFill];
    [bezierPath fill];
    
    CGContextDrawPath(contextRef, kCGPathStroke);
    UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return resultImage;
}

最后,我们来看下常见的触发离屏渲染都有哪些情景。

1.使用layer.mask,iOS8以上可以使用maskView属性。mask没办法避免离屏渲染。解决方式上面已经罗列了。

2.抗锯齿,可以设置 allowsEdgeAntialiasing = NO(默认就是NO),这就是为什么我们在游戏里如果FPS太低的情况会建议关闭抗锯齿的原因。(但是我自己在测试时开启了抗锯齿没看到离屏渲染的黄色特征,可能已经被苹果优化了。)

3.使用layer.masksToBounds(view.clipsToBounds)+layer.cornerRadius > 0去裁减。iOS9以后UIImageView使用这种方式不会再离屏渲染了,综合性能上也很不错。

4.设置了组透明度(allowsGroupOpacity )开启,并且当视图透明度(layer.opacity )小于1时,有子视图或者背景图的情况会导致离屏渲染。这个属性为YES会导致视图里包含的所有其他子视图的透明度也跟随父视图的透明度,子视图的透明度上限为父视图的透明度,iOS7之后苹果默认帮我们开启了这个属性,可以通过allowsGroupOpacity = NO关闭,自己根据需要去设置单个图层透明度。
然而在 TableView这样的视图里设置 cellcell.contentViewalpha属性小于1并不能检测离屏渲染的黄色特征,性能上也没有明显差别。经过摸索发现:只有设置 tableViewalpha小于1时才会触发离屏渲染,对性能无明显影响;设置 cellalpha属性并不会对整体的透明度产生影响,只有设置 cell.contentView 才有效。

5.添加了阴影layer.shadow会离屏渲染。原因在于虽然layer本身是一块矩形区域,但是阴影默认是作用在其中非透明区域的,而且需要显示在所有layer内容的下方,因此根据画家算法必须被渲染在先。但矛盾在于此时阴影的本体(layer和其子layer)都还没有被组合到一起,怎么可能在第一步就画出只有完成最后一步之后才能知道的形状呢?这样一来又只能另外申请一块内存,把本体内容都先画好,再根据渲染结果的形状,添加阴影到frame buffer,最后把内容画上去(这只是我的猜测,实际情况可能更复杂)。不过我们可以通过shadowPath属性预先告诉CoreAnimation阴影的几何形状,那么阴影就可以先被独立渲染出来,不需要依赖layer本体,也就不再需要离屏渲染了。在原来阴影写法上设置添加阴影路径的方法解决离屏渲染layer.shadowPath = [UIBezierPath bezierPathWithRect:view.bounds].CGPath;

6.采用了光栅化的layerlayer光栅化会开启离屏渲染,而主动开启的目的是将离屏渲染的结果缓存,那么屏幕下一帧渲染就可以复用这个成果,避免屏幕刷新频繁触发离屏渲染。处理的layer包括子layer必须是静态的,如果layerresize、动画,或者在tableViewcollectionView中,由于滚动的每一帧变化都会触发每个cell的重新绘制,因此一旦存在离屏渲染,对性能冲击将是极大的。

7.使用系统的高斯模糊UIVisualEffect会离屏渲染,替换使用CIGaussianBlur实现模糊效果。示例如下

func blurEffect(blurLevel: CGFloat) -> UIImage{
        let context = CIContext(options: nil)
        let ciImage = CIImage(cgImage: self.cgImage!)
        let filter = CIFilter(name: "CIGaussianBlur")
        filter?.setValue(ciImage, forKey: kCIInputImageKey)
        filter?.setValue(blurLevel, forKey: "inputRadius")
        let result = filter?.value(forKey: kCIOutputImageKey) as! CIImage
        let outImage = context.createCGImage(result, from: ciImage.extent)
        let blurImage = UIImage(cgImage: outImage!)
        return blurImage
    }

推荐文章:
关于iOS离屏渲染的深入研究
离屏渲染优化详解:实例示范+性能测试

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345