有些时候你可能需要绘制一个很大的图片,常见的例子就是一个高像素的照片或者是地球表面的详细地图。iOS应用通畅运行在内存受限的设备上,所以读取整个图片到内存中是不明智的。载入大图可能会相当地慢,那些对你看上去比较方便的做法(在主线程调用UIImage的-imageNamed:方法或者-imageWithContentsOfFile:方法)将会阻塞你的用户界面,至少会引起动画卡顿现象。
能高效绘制在iOS上的图片也有一个大小限制。所有显示在屏幕上的图片最终都会被转化为OpenGL纹理,同时OpenGL有一个最大的纹理尺寸(通常是20482048,或40964096,这个取决于设备型号)。如果你想在单个纹理中显示一个比这大的图,即便图片已经存在于内存中了,你仍然会遇到很大的性能问题,因为Core Animation强制用CPU处理图片而不是更快的GPU(见第12章『速度的曲调』,和第13章『高效绘图』,它更加详细地解释了软件绘制和硬件绘制)。
CATiledLayer为载入大图造成的性能问题提供了一个解决方案:将大图分解成小片然后将他们单独按需载入。让我们用实验来证明一下。
小片裁剪
这个示例中,我们将会从一个2048*2048分辨率的雪人图片入手。为了能够从CATiledLayer中获益,我们需要把这个图片裁切成许多小一些的图片。你可以通过代码来完成这件事情,但是如果你在运行时读入整个图片并裁切,那CATiledLayer这些所有的性能优点就损失殆尽了。理想情况下来说,最好能够逐个步骤来实现。
清单6.11 演示了一个简单的Mac OS命令行程序,它用CATiledLayer将一个图片裁剪成小图并存储到不同的文件中。
清单6.11 裁剪图片成小图的终端程序
1 #import 2 3 int main(int argc, const char * argv[]) 4 { 5 @autoreleasepool{ 6 //handle incorrect arguments 7 if (argc < 2) { 8 NSLog(@"TileCutter arguments: inputfile"); 9 return 0; 10 } 11 12 //input file 13 NSString *inputFile = [NSString stringWithCString:argv[1] encoding:NSUTF8StringEncoding]; 14 15 //tile size 16 CGFloat tileSize = 256; //output path 17 NSString *outputPath = [inputFile stringByDeletingPathExtension]; 18 19 //load image 20 NSImage *image = [[NSImage alloc] initWithContentsOfFile:inputFile]; 21 NSSize size = [image size]; 22 NSArray *representations = [image representations]; 23 if ([representations count]){ 24 NSBitmapImageRep *representation = representations[0]; 25 size.width = [representation pixelsWide]; 26 size.height = [representation pixelsHigh]; 27 } 28 NSRect rect = NSMakeRect(0.0, 0.0, size.width, size.height); 29 CGImageRef imageRef = [image CGImageForProposedRect:&rect context:NULL hints:nil]; 30 31 //calculate rows and columns 32 NSInteger rows = ceil(size.height / tileSize); 33 NSInteger cols = ceil(size.width / tileSize); 34 35 //generate tiles 36 for (int y = 0; y < rows; ++y) { 37 for (int x = 0; x < cols; ++x) { 38 //extract tile image 39 CGRect tileRect = CGRectMake(x*tileSize, y*tileSize, tileSize, tileSize); 40 CGImageRef tileImage = CGImageCreateWithImageInRect(imageRef, tileRect); 41 42 //convert to jpeg data 43 NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:tileImage]; 44 NSData *data = [imageRep representationUsingType: NSJPEGFileType properties:nil]; 45 CGImageRelease(tileImage); 46 47 //save file 48 NSString *path = [outputPath stringByAppendingFormat: @"_%02i_%02i.jpg", x, y]; 49 [data writeToFile:path atomically:NO]; 50 } 51 } 52 } 53 return 0; 54 }