有些時(shí)候你可能需要繪制一個(gè)很大的圖片,常見的例子就是一個(gè)高像素的照片或者是地球表面的詳細(xì)地圖。iOS應(yīng)用通暢運(yùn)行在內(nèi)存受限的設(shè)備上,所以讀取整個(gè)圖片到內(nèi)存中是不明智的。載入大圖可能會相當(dāng)?shù)芈?,那些對你看上去比較方便的做法(在主線程調(diào)用UIImage
的-imageNamed:
方法或者-imageWithContentsOfFile:
方法)將會阻塞你的用戶界面,至少會引起動畫卡頓現(xiàn)象。
能高效繪制在iOS上的圖片也有一個(gè)大小限制。所有顯示在屏幕上的圖片最終都會被轉(zhuǎn)化為OpenGL紋理,同時(shí)OpenGL有一個(gè)最大的紋理尺寸(通常是20482048,或40964096,這個(gè)取決于設(shè)備型號)。如果你想在單個(gè)紋理中顯示一個(gè)比這大的圖,即便圖片已經(jīng)存在于內(nèi)存中了,你仍然會遇到很大的性能問題,因?yàn)镃ore Animation強(qiáng)制用CPU處理圖片而不是更快的GPU(見第12章『速度的曲調(diào)』,和第13章『高效繪圖』,它更加詳細(xì)地解釋了軟件繪制和硬件繪制)。
CATiledLayer
為載入大圖造成的性能問題提供了一個(gè)解決方案:將大圖分解成小片然后將他們單獨(dú)按需載入。讓我們用實(shí)驗(yàn)來證明一下。
這個(gè)示例中,我們將會從一個(gè)2048*2048分辨率的雪人圖片入手。為了能夠從CATiledLayer
中獲益,我們需要把這個(gè)圖片裁切成許多小一些的圖片。你可以通過代碼來完成這件事情,但是如果你在運(yùn)行時(shí)讀入整個(gè)圖片并裁切,那CATiledLayer
這些所有的性能優(yōu)點(diǎn)就損失殆盡了。理想情況下來說,最好能夠逐個(gè)步驟來實(shí)現(xiàn)。
清單6.11 演示了一個(gè)簡單的Mac OS命令行程序,它用CATiledLayer
將一個(gè)圖片裁剪成小圖并存儲到不同的文件中。
清單6.11 裁剪圖片成小圖的終端程序
#import
int main(int argc, const char * argv[])
{
@autoreleasepool{
?//handle incorrect arguments
if (argc < 2) {
NSLog(@"TileCutter arguments: inputfile");
return 0;
}
//input file
NSString *inputFile = [NSString stringWithCString:argv[1] encoding:NSUTF8StringEncoding];
//tile size
CGFloat tileSize = 256; //output path
NSString *outputPath = [inputFile stringByDeletingPathExtension];
//load image
NSImage *image = [[NSImage alloc] initWithContentsOfFile:inputFile];
NSSize size = [image size];
NSArray *representations = [image representations];
if ([representations count]){
NSBitmapImageRep *representation = representations[0];
size.width = [representation pixelsWide];
size.height = [representation pixelsHigh];
}
NSRect rect = NSMakeRect(0.0, 0.0, size.width, size.height);
CGImageRef imageRef = [image CGImageForProposedRect:&rect context:NULL hints:nil];
//calculate rows and columns
NSInteger rows = ceil(size.height / tileSize);
NSInteger cols = ceil(size.width / tileSize);
//generate tiles
for (int y = 0; y < rows; ++y) {
for (int x = 0; x < cols; ++x) {
//extract tile image
CGRect tileRect = CGRectMake(x*tileSize, y*tileSize, tileSize, tileSize);
CGImageRef tileImage = CGImageCreateWithImageInRect(imageRef, tileRect);
//convert to jpeg data
NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:tileImage];
NSData *data = [imageRep representationUsingType: NSJPEGFileType properties:nil];
CGImageRelease(tileImage);
//save file
NSString *path = [outputPath stringByAppendingFormat: @"_%02i_%02i.jpg", x, y];
[data writeToFile:path atomically:NO];
}
}
}
return 0;
}
這個(gè)程序?qū)?0482048分辨率的雪人圖案裁剪成了64個(gè)不同的256256的小圖。(256*256是CATiledLayer
的默認(rèn)小圖大小,默認(rèn)大小可以通過tileSize
屬性更改)。程序接受一個(gè)圖片路徑作為命令行的第一個(gè)參數(shù)。我們可以在編譯的scheme將路徑參數(shù)硬編碼然后就可以在Xcode中運(yùn)行了,但是以后作用在另一個(gè)圖片上就不方便了。所以,我們編譯了這個(gè)程序并把它保存到敏感的地方,然后從終端調(diào)用,如下面所示:
> path/to/TileCutterApp path/to/Snowman.jpg
The app is very basic, but could easily be extended to support additional arguments such as tile size, or to export images in formats other than JPEG. The result of running it is a sequence of 64 new images, named as follows:
這個(gè)程序相當(dāng)基礎(chǔ),但是能夠輕易地?cái)U(kuò)展支持額外的參數(shù)比如小圖大小,或者導(dǎo)出格式等等。運(yùn)行結(jié)果是64個(gè)新圖的序列,如下面命名:
Snowman_00_00.jpg
Snowman_00_01.jpg
Snowman_00_02.jpg
...
Snowman_07_07.jpg
既然我們有了裁切后的小圖,我們就要讓iOS程序用到他們。CATiledLayer
很好地和UIScrollView
集成在一起。除了設(shè)置圖層和滑動視圖邊界以適配整個(gè)圖片大小,我們真正要做的就是實(shí)現(xiàn)-drawLayer:inContext:
方法,當(dāng)需要載入新的小圖時(shí),CATiledLayer
就會調(diào)用到這個(gè)方法。
清單6.12演示了代碼。圖6.12是代碼運(yùn)行結(jié)果。
清單6.12 一個(gè)簡單的滾動CATiledLayer
實(shí)現(xiàn)
#import "ViewController.h"
#import
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIScrollView *scrollView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//add the tiled layer
CATiledLayer *tileLayer = [CATiledLayer layer];?
tileLayer.frame = CGRectMake(0, 0, 2048, 2048);
tileLayer.delegate = self; [self.scrollView.layer addSublayer:tileLayer];
//configure the scroll view
self.scrollView.contentSize = tileLayer.frame.size;
//draw layer
[tileLayer setNeedsDisplay];
}
- (void)drawLayer:(CATiledLayer *)layer inContext:(CGContextRef)ctx
{
//determine tile coordinate
CGRect bounds = CGContextGetClipBoundingBox(ctx);
NSInteger x = floor(bounds.origin.x / layer.tileSize.width);
NSInteger y = floor(bounds.origin.y / layer.tileSize.height);
//load tile image
NSString *imageName = [NSString stringWithFormat: @"Snowman_%02i_%02i", x, y];
NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@"jpg"];
UIImage *tileImage = [UIImage imageWithContentsOfFile:imagePath];
//draw tile
UIGraphicsPushContext(ctx);
[tileImage drawInRect:bounds];
UIGraphicsPopContext();
}
@end
更多建議: