利用預渲染加速iOS設備的圖像顯示

轉:點擊打開連接html

      最近在作一個UITableView的例子,發現滾動時的性能還不錯。但來回滾動時,第一次顯示的圖像不如再次顯示的圖像流暢,出現前會有稍許的停頓感。因而我猜測顯示過的圖像確定是被緩存起來了,查了下文檔後發現果真如此。後來在《Improving Image Drawing Performance on iOS》一文中找到了一些提示:原來在顯示圖像時,解壓和重採樣會消耗不少CPU時間;而若是預先在一個bitmap context裏畫出圖像,再緩存這個圖像,就能省去這些繁重的工做了。接着我就寫了個例子程序來驗證:ios

 

//  ImageView.h
#import <UIKit/UIKit.h>

@interface ImageView : UIView {
    UIImage *image;
}

@property (retain, nonatomic) UIImage *image;
@end
//  ImageView.m
#include <mach/mach_time.h>
#import "ImageView.h"

@implementation ImageView

#define LABEL_TAG 1

static const CGRect imageRect = {{0, 0}, {100, 100}};
static const CGPoint imagePoint = {0, 0};

@synthesize image;

- (void)awakeFromNib {
    if (!self.image) {
        self.image = [UIImage imageNamed:@"random.jpg"];
    }
}

- (void)drawRect:(CGRect)rect {
    if (CGRectEqualToRect(rect, imageRect)) {
        uint64_t start = mach_absolute_time();
        [image drawAtPoint:imagePoint];
        uint64_t drawTime = mach_absolute_time() - start;
        
        NSString *text = [[NSString alloc] initWithFormat:@"%lld", drawTime];
        UILabel *label = (UILabel *)[self viewWithTag:LABEL_TAG];
        label.text = text;
        [text release];
    }
}

- (void)dealloc {
    [super dealloc];
    [image release];
}

@end

控制器的代碼我就不列出了,就是點按鈕時,更新view(調用[self.view setNeedsDisplayInRect:imageRect]),畫出一張圖,並在label中顯示消耗的時間。值得一提的是,在模擬器上能夠直接用clock()函數得到微秒級的精度,但iOS設備上精度爲10毫秒。因而我找到了mach_absolute_time(),它在Mac和iOS設備上都有納秒級的精度。測試用的是一張200x200像素的JPEG圖像,命名時加了@2x,在iPhone 4上第一次顯示時花了約300微秒,再次顯示約65微秒。接下來就是見證奇蹟的時刻了,把這段代碼加入程序:緩存

static const CGSize imageSize = {100, 100};

- (void)awakeFromNib {
    if (!self.image) {
        self.image = [UIImage imageNamed:@"random.jpg"];
        if (NULL != UIGraphicsBeginImageContextWithOptions)
            UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0);
        else
            UIGraphicsBeginImageContext(imageSize);
        [image drawInRect:imageRect];
        self.image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    }
}

這裏須要判斷一下UIGraphicsBeginImageContextWithOptions是否爲NULL,由於它是iOS 4.0才加入的。
因爲JPEG圖像是不透明的,因此第二個參數就設爲YES。第三個參數是縮放比例,iPhone 4是2.0,其餘是1.0。雖然這裏能夠用[UIScreen mainScreen].scale來獲取,但實際上設爲0後,系統就會自動設置正確的比例了。值得一提的是,圖像自己也有縮放比例,普通的圖像是1.0(除了UIImage imageNamed:外,大部分API都只能得到這種圖像,並且縮放比例是不可更改的),高清圖像是2.0。圖像的點和屏幕的像素就是依靠2者的縮放比例來計算的,例如普通圖像在視網膜顯示屏上是1:4,而高清圖像在視網膜顯示屏上則是1:1。接下來的drawInRect:把圖像畫到了當前的image context裏,這時就完成了解壓縮和重採樣的工做了。而後再從image context裏獲取新的image,這個image的縮放比例也能正確地和設備匹配。再點下按鈕,發現時間已經縮短到12微秒左右了,以後的畫圖穩定在15微秒左右。還能更快嗎?讓咱們來試試Core Graphics。先定義一個全局的CGImageRef變量:app

static CGImageRef imageRef;

再在awakeFromNib中設置一下它的值:框架

 

 

imageRef = self.image.CGImage;

最後在drawRect:中繪製:dom

 

 

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, imageRect, imageRef);

搞定運行一下,發現時間增長到33微秒左右了,並且圖像還上下顛倒了⋯
這個緣由是UIKit和Core Graphics的座標系y軸是相反的,因而加上2行代碼來修正:函數

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, 100);
CGContextScaleCTM(context, 1, -1);
CGContextDrawImage(context, imageRect, imageRef);

這下圖像終於正常顯示了,時間縮短到了14微秒左右,成效不大,看來直接用-drawAtPoint:和-drawInRect:也足夠好了。
固然,這個例子正確的作法是用viewDidLoad或loadView,不過我懶得列出控制器代碼,因此就放awakeFromNib裏了。

性能

 

2011年9月22日更新勘誤:
剛看到Mach Absolute Time Units這篇Q&A,發現mach_absolute_time()的單位是Mach absolute time unit,而不是納秒。它們之間的換算關係和CPU相關,不是一個常量。最簡單的辦法是用CoreServices框架的AbsoluteToNanoseconds和AbsoluteToDuration函數來轉換。此外也能夠用mach_timebase_info函數來獲取這個比值。我在iPhone 4上測得的numer和denom分別爲125和3,比值約爲42,所以本文所述的時間都須要乘以42。
測試

相關文章
相關標籤/搜索