6.7 CATiledLayer 圖層

2021-09-14 16:22 更新

CATiledLayer

有些時(shí)候你可能需要繪制一個(gè)很大的圖片,常見(jiàn)的例子就是一個(gè)高像素的照片或者是地球表面的詳細(xì)地圖。iOS應(yīng)用通暢運(yùn)行在內(nèi)存受限的設(shè)備上,所以讀取整個(gè)圖片到內(nèi)存中是不明智的。載入大圖可能會(huì)相當(dāng)?shù)芈?,那些?duì)你看上去比較方便的做法(在主線(xiàn)程調(diào)用UIImage-imageNamed:方法或者-imageWithContentsOfFile:方法)將會(huì)阻塞你的用戶(hù)界面,至少會(huì)引起動(dòng)畫(huà)卡頓現(xiàn)象。

能高效繪制在iOS上的圖片也有一個(gè)大小限制。所有顯示在屏幕上的圖片最終都會(huì)被轉(zhuǎn)化為OpenGL紋理,同時(shí)OpenGL有一個(gè)最大的紋理尺寸(通常是20482048,或40964096,這個(gè)取決于設(shè)備型號(hào))。如果你想在單個(gè)紋理中顯示一個(gè)比這大的圖,即便圖片已經(jīng)存在于內(nèi)存中了,你仍然會(huì)遇到很大的性能問(wèn)題,因?yàn)镃ore Animation強(qiáng)制用CPU處理圖片而不是更快的GPU(見(jiàn)第12章『速度的曲調(diào)』,和第13章『高效繪圖』,它更加詳細(xì)地解釋了軟件繪制和硬件繪制)。

CATiledLayer為載入大圖造成的性能問(wèn)題提供了一個(gè)解決方案:將大圖分解成小片然后將他們單獨(dú)按需載入。讓我們用實(shí)驗(yàn)來(lái)證明一下。

小片裁剪

這個(gè)示例中,我們將會(huì)從一個(gè)2048*2048分辨率的雪人圖片入手。為了能夠從CATiledLayer中獲益,我們需要把這個(gè)圖片裁切成許多小一些的圖片。你可以通過(guò)代碼來(lái)完成這件事情,但是如果你在運(yùn)行時(shí)讀入整個(gè)圖片并裁切,那CATiledLayer這些所有的性能優(yōu)點(diǎn)就損失殆盡了。理想情況下來(lái)說(shuō),最好能夠逐個(gè)步驟來(lái)實(shí)現(xiàn)。

清單6.11 演示了一個(gè)簡(jiǎn)單的Mac OS命令行程序,它用CATiledLayer將一個(gè)圖片裁剪成小圖并存儲(chǔ)到不同的文件中。

清單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)大小可以通過(guò)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è)置圖層和滑動(dòng)視圖邊界以適配整個(gè)圖片大小,我們真正要做的就是實(shí)現(xiàn)-drawLayer:inContext:方法,當(dāng)需要載入新的小圖時(shí),CATiledLayer就會(huì)調(diào)用到這個(gè)方法。

清單6.12演示了代碼。圖6.12是代碼運(yùn)行結(jié)果。

清單6.12 一個(gè)簡(jiǎn)單的滾動(dòng)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

以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)