iOS開發系統--離屏渲染之圓角、陰影簡單測試

基本概念

在OpenGL中,GPU屏幕渲染有如下兩種方式:緩存

Onscreen-Rendered:當前屏幕渲染,指的是GPU的渲染操做是在當前用於顯示的屏幕緩衝區中進行。
Offscreen-Rendered:即離屏渲染,指的是GPU在當前屏幕緩衝區之外新開闢一個緩衝區進行渲染操做。性能

Offscreen-rendered缺點

與當前屏幕渲染相比,離屏渲染的代價是很高的,主要體如今兩個方面:測試

建立新緩衝區:要想進行離屏渲染,首先要建立一個新的緩衝區。
上下文切換:離屏渲染的整個過程,須要屢次切換上下文環境。先是從當前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結束之後,將離屏緩衝區的渲染結果顯示到屏幕上又須要將上下文環境從離屏切換到當前屏幕。可是,上下文環境的切換是要付出很大代價的。spa

哪些可引發Offscreen-rendered

有如下方式能夠引發離屏渲染: 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
相關文章
相關標籤/搜索