很多人都知道设置了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
修改、tableView
、collectionView
视图中,那么开启光栅化反而影响了效率; - 离屏渲染缓存内容有时间限制,缓存的内容如果
100ms
内没有被使用,那么它就会丢弃,无法进行复用; - 离屏渲染的缓存空间有限,大小相当于屏幕像素点的2.5倍,超过的话也会失效,无法进行复用了;
总结来说,如果可以避免触发离屏渲染,尽量避免,否则如果视图可以复用并且是静态内容(也就是内部结构和内容不发生变化的视图),才考虑开启光栅化,但是也要考虑光栅缓存的利用率,如果整屏视图中利用到光栅缓存的视图很少,那反而更耗费性能。(可以通过CoreAnimation InStruments
工具查看并通过Xcode
打开「Color Hits Green and Misses Red」
观察离屏渲染对缓存的使用,绿色代表了用到了光栅缓存,红色则是需要重新渲染的部分,红色意味着栅格化反而有负面的性能影响了)
回到一开始的问题,设置圆角而触发离屏渲染是有条件的。
先来看看一个视图的渲染层级。
再来看看苹果官方文档上关于设置圆角的说法。
可以看到,苹果告诉我们,设置了cornerRadius
只会设置backgroundColor
和border
的圆角,而内容图层contents
,并不会设置圆角。除非你同时设置了layer.masksToBounds
(对应view.clipsToBounds
),才会也对contents
设置圆角。
离屏渲染的逻辑
这张图演示的是著名的油画算法。指的是先绘制远的部分,再绘制近的部分。我们图层的渲染也是同理,系统会先绘制最底层的图层,再往上一层层的绘制,最后形成了图层树,苹果建议开发者在建立UI的时候,图层树不要太复杂,层级不要太多,不然也是会有性能影响。
- 正常渲染:如果是不触发离屏渲染的正常渲染,苹果在绘制完最底层的图层,从帧缓冲区显示到屏幕上之后,就丢弃了,并不会保存起来,再画第二层,也是如此,显示完了就丢弃,从而节省了空间。
- 离屏渲染:如果对一个多图层图像进行圆角处理,就需要对所有图层进行圆角(包括内容
contents
),如果按照正常渲染,一层用完就丢弃,这样就达不到显示的效果。这时候就需要开辟一个离屏缓冲区去保存这些图层,等到所有图层都做了圆角处理,就把它们从离屏缓冲区里取出来进行合并显示。
这就解释了为什么有些圆角会触发离屏渲染,纠其根本就是用到了离屏缓冲区。
我总结了处理圆角的几种可用方式.
1.最简单的就是让UI同事切一个带圆角的图。
- 如果内容
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
,则会对contents
和contents
下面背景色层裁剪,所以是多层裁剪。而改为label.backgroundColor
,masksToBounds
则只会对contents
层裁剪圆角,不存在layer
的背景色层,所以不会触发离屏渲染。
建议使用方式
用cornerRadius+layer.backgroundColor
方式对label设置圆角,或者cornerRadius+label.backgroundColor+ masksToBounds
设置圆角。
- 使用
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];
- 采用
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
- 使用混合图层模拟
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
这样的视图里设置 cell
或 cell.contentView
的alpha
属性小于1并不能检测离屏渲染的黄色特征,性能上也没有明显差别。经过摸索发现:只有设置 tableView
的alpha
小于1时才会触发离屏渲染,对性能无明显影响;设置 cell
的alpha
属性并不会对整体的透明度产生影响,只有设置 cell.contentView
才有效。
5.添加了阴影layer.shadow
会离屏渲染。原因在于虽然layer
本身是一块矩形区域,但是阴影默认是作用在其中非透明区域的,而且需要显示在所有layer
内容的下方,因此根据画家算法必须被渲染在先。但矛盾在于此时阴影的本体(layer和其子layer)都还没有被组合到一起,怎么可能在第一步就画出只有完成最后一步之后才能知道的形状呢?这样一来又只能另外申请一块内存,把本体内容都先画好,再根据渲染结果的形状,添加阴影到frame buffer
,最后把内容画上去(这只是我的猜测,实际情况可能更复杂)。不过我们可以通过shadowPath
属性预先告诉CoreAnimation
阴影的几何形状,那么阴影就可以先被独立渲染出来,不需要依赖layer
本体,也就不再需要离屏渲染了。在原来阴影写法上设置添加阴影路径的方法解决离屏渲染layer.shadowPath = [UIBezierPath bezierPathWithRect:view.bounds].CGPath;
。
6.采用了光栅化的layer
,layer
光栅化会开启离屏渲染,而主动开启的目的是将离屏渲染的结果缓存,那么屏幕下一帧渲染就可以复用这个成果,避免屏幕刷新频繁触发离屏渲染。处理的layer
包括子layer
必须是静态的,如果layer
会resize
、动画,或者在tableView
、collectionView
中,由于滚动的每一帧变化都会触发每个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
}