利用预渲染加速iOS设备的图像显示

最近在作一个UITableView的例子,发现滚动时的性能还不错。但来回滚动时,第一次显示的图像不如再次显示的图像流畅,出现前会有稍许的停顿感。
因而我猜测显示过的图像确定是被缓存起来了,查了下文档后发现果真如此。
后来在《Improving Image Drawing Performance on iOS》一文中找到了一些提示:原来在显示图像时,解压和重采样会消耗不少CPU时间;而若是预先在一个bitmap context里画出图像,再缓存这个图像,就能省去这些繁重的工做了。

接着我就写了个例子程序来验证:html

 1 //  ImageView.h
 2 
 3 #import <UIKit/UIKit.h>
 4 
 5 
 6 @interface ImageView : UIView {
 7     UIImage *image;
 8 }
 9 
10 @property (retain, nonatomic) UIImage *image;
11 
12 @end
 1 //  ImageView.m
 2 
 3 #include <mach/mach_time.h>
 4 #import "ImageView.h"
 5 
 6 
 7 @implementation ImageView
 8 
 9 #define LABEL_TAG 1
10 
11 static const CGRect imageRect = {{0, 0}, {100, 100}};
12 static const CGPoint imagePoint = {0, 0};
13 
14 @synthesize image;
15 
16 - (void)awakeFromNib {
17     if (!self.image) {
18         self.image = [UIImage imageNamed:@"random.jpg"];
19     }
20 }
21 
22 - (void)drawRect:(CGRect)rect {
23     if (CGRectEqualToRect(rect, imageRect)) {
24         uint64_t start = mach_absolute_time();
25         [image drawAtPoint:imagePoint];
26         uint64_t drawTime = mach_absolute_time() - start;
27         
28         NSString *text = [[NSString alloc] initWithFormat:@"%lld", drawTime];
29         UILabel *label = (UILabel *)[self viewWithTag:LABEL_TAG];
30         label.text = text;
31         [text release];
32     }
33 }
34 
35 - (void)dealloc {
36     [super dealloc];
37     [image release];
38 }
39 
40 @end

控制器的代码我就不列出了,就是点按钮时,更新view(调用[self.view setNeedsDisplayInRect:imageRect]),画出一张图,并在label中显示消耗的时间。
值得一提的是,在模拟器上能够直接用clock()函数得到微秒级的精度,但iOS设备上精度为10毫秒。因而我找到了mach_absolute_time(),它在Mac和iOS设备上都有纳秒级的精度。

测试用的是一张200x200像素的JPEG图像,命名时加了@2x,在iPhone 4上第一次显示时花了约300微秒,再次显示约65微秒。

接下来就是见证奇迹的时刻了,把这段代码加入程序:ios

 1 static const CGSize imageSize = {100, 100};
 2 
 3 - (void)awakeFromNib {
 4     if (!self.image) {
 5         self.image = [UIImage imageNamed:@"random.jpg"];
 6         if (NULL != UIGraphicsBeginImageContextWithOptions)
 7             UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0);
 8         else
 9             UIGraphicsBeginImageContext(imageSize);
10         [image drawInRect:imageRect];
11         self.image = UIGraphicsGetImageFromCurrentImageContext();
12         UIGraphicsEndImageContext();
13     }
14 }

这里须要判断一下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变量:缓存

1 static CGImageRef imageRef;

再在awakeFromNib中设置一下它的值:app

1 imageRef = self.image.CGImage;

最后在drawRect:中绘制:框架

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

搞定运行一下,发现时间增长到33微秒左右了,并且图像还上下颠倒了⋯

这个缘由是UIKit和Core Graphics的坐标系y轴是相反的,因而加上2行代码来修正:dom

1 CGContextRef context = UIGraphicsGetCurrentContext();
2 CGContextTranslateCTM(context, 0, 100);
3 CGContextScaleCTM(context, 1, -1);
4 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。函数

相关文章
相关标签/搜索