在OpenGL中,GPU屏幕渲染有如下两种方式:缓存
Onscreen-Rendered:当前屏幕渲染,指的是GPU的渲染操做是在当前用于显示的屏幕缓冲区中进行。
Offscreen-Rendered:即离屏渲染,指的是GPU在当前屏幕缓冲区之外新开辟一个缓冲区进行渲染操做。性能
与当前屏幕渲染相比,离屏渲染的代价是很高的,主要体如今两个方面:测试
建立新缓冲区:要想进行离屏渲染,首先要建立一个新的缓冲区。
上下文切换:离屏渲染的整个过程,须要屡次切换上下文环境。先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束之后,将离屏缓冲区的渲染结果显示到屏幕上又须要将上下文环境从离屏切换到当前屏幕。可是,上下文环境的切换是要付出很大代价的。spa
有如下方式能够引发离屏渲染: 3d
shadows(阴影)
cornerRadius+clipToBounds/maskToBounds设置圆角
shouldRasterize设置为YES
masks(遮罩)
edge antialiasing(抗锯齿)
group opacity(不透明)
重写drawRect交由CPU渲染 code
这里只做圆角和阴影测试。blog
模拟器的 Debug -> 选取 Color Off-screen Rendered.图片
Color Offscreen-Rendered Yellow
开启后会把那些须要离屏渲染的图层高亮成黄色,这就意味着黄色图层可能存在性能问题。 ip
Color Hits Green and Misses Red
若是shouldRasterize被设置成YES,对应的渲染结果会被缓存,若是图层是绿色,就表示这些缓存被复用;若是是红色就表示缓存会被重复建立,这就表示该处存在性能问题了。ci
CGFloat imageW = 180.0; UIImage *image = [UIImage imageNamed:@"example.jpg"]; //方法1:直接调用系统方法 UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; imageView.contentMode = UIViewContentModeScaleAspectFill; imageView.frame = CGRectMake(0, 30, imageW, imageW); imageView.centerX = self.view.centerX; imageView.layer.cornerRadius = 30; imageView.layer.masksToBounds = YES; [self.view addSubview:imageView]; //方法2:图片绘制圆角 CGFloat cornerRadius = 30.0 / imageW * MIN(image.size.width, image.size.height); UIImageView *imageView2 = [[UIImageView alloc] initWithImage:[image roundedWithRadius:cornerRadius]]; imageView2.contentMode = UIViewContentModeScaleAspectFill; imageView2.frame = CGRectMake(0, 0, imageW, imageW); imageView2.centerX = self.view.centerX; imageView2.centerY = self.view.centerY + 10; [self.view addSubview:imageView2]; //方法3:内部也是调用了方法2 UIImageView *imageView3 = [UIImageView imageViewWithImage:image imageViewSize:CGSizeMake(imageW, imageW) cornerRadius:30]; imageView3.contentMode = UIViewContentModeScaleAspectFill; imageView3.frame = CGRectMake(0, 0, imageW, imageW); imageView3.centerX = self.view.centerX; imageView3.y = self.view.height - imageW - 10; [self.view addSubview:imageView3];
方法1:使用cornerRadius+masksToBounds方法绘制圆角,从图中能够看到圆角处变成了黄色,说明触发了离屏渲染。
方法2与方法3:内部都采起对图片绘制圆角,没有变成黄色。
CGFloat imageW = 180.0; UIImage *image = [UIImage imageNamed:@"example.jpg"]; //方法1:直接调用系统方法 UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; imageView.contentMode = UIViewContentModeScaleAspectFill; imageView.frame = CGRectMake(0, 30, imageW, imageW); imageView.centerX = self.view.centerX; imageView.layer.cornerRadius = 30; imageView.layer.shadowOffset = CGSizeMake(4, 4); imageView.layer.shadowColor = [UIColor redColor].CGColor; imageView.layer.shadowRadius = 3; imageView.layer.shadowOpacity = 0.5; //只是经过设置layer属性,圆角跟阴影是没法共存的,masksToBounds设置为YES会形成阴影没法显示,设置为NO则圆角没法显示。 imageView.layer.masksToBounds = NO; [self.view addSubview:imageView]; //方法2:图片绘制圆角,经过设置阴影达到圆角与阴影共存 CGFloat cornerRadius = 30.0 / imageW * MIN(image.size.width, image.size.height); UIImageView *imageView2 = [[UIImageView alloc] initWithImage:[image roundedWithRadius:cornerRadius]]; imageView2.contentMode = UIViewContentModeScaleAspectFill; imageView2.frame = CGRectMake(0, 0, imageW, imageW); imageView2.layer.shadowOffset = CGSizeMake(4, 4); imageView2.layer.shadowColor = [UIColor redColor].CGColor; imageView2.layer.shadowRadius = 3; imageView2.layer.shadowOpacity = 0.5; imageView2.centerX = self.view.centerX; imageView2.centerY = self.view.centerY + 10; [self.view addSubview:imageView2]; //方法3:内部也是调用了方法2,只不过多设置了一个shadowPath UIImageView *imageView3 = [UIImageView imageViewWithImage:image imageViewSize:CGSizeMake(imageW, imageW) cornerRadius:30 horizontal:4 vertical:4 shadowColor:[UIColor redColor] shadowOpacity:0.5 shadowRadius:3]; imageView3.contentMode = UIViewContentModeScaleAspectFill; imageView3.frame = CGRectMake(0, 0, imageW, imageW); imageView3.centerX = self.view.centerX; imageView3.y = self.view.height - imageW - 10; [self.view addSubview:imageView3];
方法1:经过layer属性设置圆角与阴影。经过此方法,圆角跟阴影是没法共存的,masksToBounds设置为YES会形成阴影没法显示,设置为NO则圆角没法显示。并且从图中能够看到,整个UIImageView都变成了黄色,说明阴影的设置触发了离屏渲染。
方法2:经过上面圆角测试中的方法2对圆角绘制圆角,而后经过layer添加阴影。从图中能够看到,圆角与阴影共存了,整个UIImageView都变成了黄色,说明阴影的设置触发了离屏渲染。
方法3:也是经过layer方法添加阴影,可是多添加了一个shadowPath。
imageView.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)].CGPath;
UIImage+TM.h
// // UIImage+TM.h // TMDemo // // Created by TIM on 2018/7/23. // Copyright © 2018年 tim. All rights reserved. // #import <UIKit/UIKit.h> // 圆角 typedef NS_ENUM(NSInteger, YKImageRoundedCornerCorner) { YKImageRoundedCornerCornerTopLeft = 1, YKImageRoundedCornerCornerTopRight = 1 << 1, YKImageRoundedCornerCornerBottomRight = 1 << 2, YKImageRoundedCornerCornerBottomLeft = 1 << 3 }; @interface UIImage (TM) /** 高性能绘圆形图片 @return 圆形图片 */ - (UIImage *)rounded; /** 高性能绘制圆角图片(默认绘制4个圆角) @param radius 圆角 @return 圆角图片 */ - (UIImage *)roundedWithRadius:(CGFloat)radius; /** 高性能绘制圆角图片 @param radius 圆角 @param cornerMask 要绘制的圆角 @return 圆角图片 */ - (UIImage *)roundedWithRadius:(CGFloat)radius cornerMask:(YKImageRoundedCornerCorner)cornerMask; @end
UIImage+TM.m
// // UIImage+TM.m // TMDemo // // Created by TIM on 2018/7/23. // Copyright © 2018年 tim. All rights reserved. // #import "UIImage+TM.h" @implementation UIImage (TM) // UIKit坐标系统原点在左上角,y方向向下的(坐标系A),但在Quartz中坐标系原点在左下角,y方向向上的(坐标系B)。图片绘制也是颠倒的。 void addRoundedRectToPath(CGContextRef context, CGRect rect, float radius, YKImageRoundedCornerCorner cornerMask) { //原点在左下方,y方向向上。移动到线条2的起点。 CGContextMoveToPoint(context, rect.origin.x, rect.origin.y + radius); //画出线条2, 目前画线的起始点已经移动到线条2的结束地方了。 CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height - radius); //若是左上角须要画圆角,画出一个弧线出来。 if (cornerMask & YKImageRoundedCornerCornerTopLeft) { //已左上的正方形的右下脚为圆心,半径为radius, 180度到90度画一个弧线, CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius, radius, M_PI, M_PI / 2, 1); } else { //若是不须要画左上角的弧度。从线2终点,画到线3的终点, CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height); //线3终点,画到线4的起点 CGContextAddLineToPoint(context, rect.origin.x + radius, rect.origin.y + rect.size.height); } //画线4的起始,到线4的终点 CGContextAddLineToPoint(context, rect.origin.x + rect.size.width - radius, rect.origin.y + rect.size.height); //画右上角 if (cornerMask & YKImageRoundedCornerCornerTopRight) { CGContextAddArc(context, rect.origin.x + rect.size.width - radius, rect.origin.y + rect.size.height - radius, radius, M_PI / 2, 0.0f, 1); } else { CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height); CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height - radius); } CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y + radius); //画右下角弧线 if (cornerMask & YKImageRoundedCornerCornerBottomRight) { CGContextAddArc(context, rect.origin.x + rect.size.width - radius, rect.origin.y + radius, radius, 0.0f, -M_PI / 2, 1); } else { CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y); CGContextAddLineToPoint(context, rect.origin.x + rect.size.width - radius, rect.origin.y); } CGContextAddLineToPoint(context, rect.origin.x + radius, rect.origin.y); //画左下角弧线 if (cornerMask & YKImageRoundedCornerCornerBottomLeft) { CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + radius, radius, -M_PI / 2, M_PI, 1); } else { CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y); CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + radius); } CGContextClosePath(context); } - (UIImage *)rounded { if (!self) return nil; CGFloat radius = MIN(self.size.width, self.size.height) / 2.0; return [self roundedWithRadius:radius]; } - (UIImage *)roundedWithRadius:(CGFloat)radius { return [self roundedWithRadius:radius cornerMask:YKImageRoundedCornerCornerBottomLeft | YKImageRoundedCornerCornerBottomRight | YKImageRoundedCornerCornerTopLeft | YKImageRoundedCornerCornerTopRight]; } - (UIImage *)roundedWithRadius:(CGFloat)radius cornerMask:(YKImageRoundedCornerCorner)cornerMask { if (!self) return nil; if (radius <= 0) return self; //UIImage绘制为圆角 int w = self.size.width; int h = self.size.height; UIImage *newImage = 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, cornerMask); CGContextClosePath(context); CGContextClip(context); CGContextDrawImage(context, CGRectMake(0, 0, w, h), newImage.CGImage); CGImageRef imageMasked = CGBitmapContextCreateImage(context); newImage = [UIImage imageWithCGImage:imageMasked]; CGContextRelease(context); CGColorSpaceRelease(colorSpace); CGImageRelease(imageMasked); return newImage; } @end
UIImageView+TM.h
// // UIImageView+TM.h // TMDemo // // Created by TIM on 2018/7/23. // Copyright © 2018年 tim. All rights reserved. // #import <UIKit/UIKit.h> @interface UIImageView (TM) /** 高性能绘制带圆角图片 @param image 原始图片 @param imageViewSize UIImageView的size,绘制必须先肯定size @param cornerRadius 圆角 @return UIImageView */ + (UIImageView *)imageViewWithImage:(UIImage *)image imageViewSize:(CGSize)imageViewSize cornerRadius:(CGFloat)cornerRadius; /** 高性能绘制带圆角+阴影图片 @param image 原始图片 @param imageViewSize UIImageView的size,绘制必须先肯定size @param cornerRadius 圆角 @param horizontal 水平阴影 @param vertical 垂直阴影 @param shadowColor 阴影颜色 @param shadowOpacity shadowOpacity @param shadowRadius shadowRadius @return UIImageView */ + (UIImageView *)imageViewWithImage:(UIImage *)image imageViewSize:(CGSize)imageViewSize cornerRadius:(CGFloat)cornerRadius horizontal:(CGFloat)horizontal vertical:(CGFloat)vertical shadowColor:(UIColor *)shadowColor shadowOpacity:(float)shadowOpacity shadowRadius:(CGFloat)shadowRadius; @end
UIImageView+TM.m
// // UIImageView+TM.m // TMDemo // // Created by TIM on 2018/7/23. // Copyright © 2018年 tim. All rights reserved. // #import "UIImageView+TM.h" #import "UIImage+TM.h" @implementation UIImageView (TM) + (UIImageView *)imageViewWithImage:(UIImage *)image imageViewSize:(CGSize)imageViewSize cornerRadius:(CGFloat)cornerRadius { if (image && cornerRadius > 0) { //cornerRadius是相对imageViewSize的圆角,须要转换成image的实际圆角 CGFloat radis = cornerRadius / MIN(imageViewSize.width, imageViewSize.height) * MIN(image.size.width, image.size.height); image = [image roundedWithRadius:radis cornerMask:YKImageRoundedCornerCornerBottomLeft | YKImageRoundedCornerCornerBottomRight | YKImageRoundedCornerCornerTopLeft | YKImageRoundedCornerCornerTopRight]; } UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; CGRect frame = imageView.frame; frame.size = imageViewSize; imageView.frame = frame; return imageView; } + (UIImageView *)imageViewWithImage:(UIImage *)image imageViewSize:(CGSize)imageViewSize cornerRadius:(CGFloat)cornerRadius horizontal:(CGFloat)horizontal vertical:(CGFloat)vertical shadowColor:(UIColor *)shadowColor shadowOpacity:(float)shadowOpacity shadowRadius:(CGFloat)shadowRadius { UIImageView *imageView = [self imageViewWithImage:image imageViewSize:imageViewSize cornerRadius:cornerRadius]; imageView.layer.shadowOffset = CGSizeMake(horizontal, vertical); imageView.layer.shadowColor = [shadowColor CGColor]; imageView.layer.shadowOpacity = shadowOpacity; imageView.layer.shadowRadius = shadowRadius; imageView.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)].CGPath; return imageView; } @end