# 文件格式
????圖片加載性能取決于加載大圖的時間和解壓小圖時間的權衡。很多蘋果的文檔都說PNG是iOS所有圖片加載的最好格式。但這是極度誤導的過時信息了。
????PNG圖片使用的無損壓縮算法可以比使用JPEG的圖片做到更快地解壓,但是由于閃存訪問的原因,這些加載的時間并沒有什么區別。
????清單14.6展示了標準的應用程序加載不同尺寸圖片所需要時間的一些代碼。為了保證實驗的準確性,我們會測量每張圖片的加載和繪制時間來確保考慮到解壓性能的因素。另外每隔一秒重復加載和繪制圖片,這樣就可以取到平均時間,使得結果更加準確。
清單14.6
~~~
#import "ViewController.h"
static NSString *const ImageFolder = @"Coast Photos";
@interface ViewController ()
@property (nonatomic, copy) NSArray *items;
@property (nonatomic, weak) IBOutlet UITableView *tableView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//set up image names
self.items = @[@"2048x1536", @"1024x768", @"512x384", @"256x192", @"128x96", @"64x48", @"32x24"];
}
- (CFTimeInterval)loadImageForOneSec:(NSString *)path
{
//create drawing context to use for decompression
UIGraphicsBeginImageContext(CGSizeMake(1, 1));
//start timing
NSInteger imagesLoaded = 0;
CFTimeInterval endTime = 0;
CFTimeInterval startTime = CFAbsoluteTimeGetCurrent();
while (endTime - startTime < 1) {
//load image
UIImage *image = [UIImage imageWithContentsOfFile:path];
//decompress image by drawing it
[image drawAtPoint:CGPointZero];
//update totals
imagesLoaded ++;
endTime = CFAbsoluteTimeGetCurrent();
}
//close context
UIGraphicsEndImageContext();
//calculate time per image
return (endTime - startTime) / imagesLoaded;
}
- (void)loadImageAtIndex:(NSUInteger)index
{
//load on background thread so as not to
//prevent the UI from updating between runs dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//setup
NSString *fileName = self.items[index];
NSString *pngPath = [[NSBundle mainBundle] pathForResource:filename
ofType:@"png"
inDirectory:ImageFolder];
NSString *jpgPath = [[NSBundle mainBundle] pathForResource:filename
ofType:@"jpg"
inDirectory:ImageFolder];
//load
NSInteger pngTime = [self loadImageForOneSec:pngPath] * 1000;
NSInteger jpgTime = [self loadImageForOneSec:jpgPath] * 1000;
//updated UI on main thread
dispatch_async(dispatch_get_main_queue(), ^{
//find table cell and update
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
cell.detailTextLabel.text = [NSString stringWithFormat:@"PNG: %03ims JPG: %03ims", pngTime, jpgTime];
});
});
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.items count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//dequeue cell
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Cell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleValue1 reuseIdentifier:@"Cell"];
}
//set up cell
NSString *imageName = self.items[indexPath.row];
cell.textLabel.text = imageName;
cell.detailTextLabel.text = @"Loading...";
//load image
[self loadImageAtIndex:indexPath.row];
return cell;
}
@end
~~~
????PNG和JPEG壓縮算法作用于兩種不同的圖片類型:JPEG對于噪點大的圖片效果很好;但是PNG更適合于扁平顏色,鋒利的線條或者一些漸變色的圖片。為了讓測評的基準更加公平,我們用一些不同的圖片來做實驗:一張照片和一張彩虹色的漸變。JPEG版本的圖片都用默認的Photoshop60%“高質量”設置編碼。結果見圖片14.5。

圖14.5 不同類型圖片的相對加載性能
????如結果所示,相對于不友好的PNG圖片,相同像素的JPEG圖片總是比PNG加載更快,除非一些非常小的圖片、但對于友好的PNG圖片,一些中大尺寸的圖效果還是很好的。
????所以對于之前的圖片傳送器程序來說,JPEG會是個不錯的選擇。如果用JPEG的話,一些多線程和緩存策略都沒必要了。
????但JPEG圖片并不是所有情況都適用。如果圖片需要一些透明效果,或者壓縮之后細節損耗很多,那就該考慮用別的格式了。蘋果在iOS系統中對PNG和JPEG都做了一些優化,所以普通情況下都應該用這種格式。也就是說在一些特殊的情況下才應該使用別的格式。
### 混合圖片
????對于包含透明的圖片來說,最好是使用壓縮透明通道的PNG圖片和壓縮RGB部分的JPEG圖片混合起來加載。這就對任何格式都適用了,而且無論從質量還是文件尺寸還是加載性能來說都和PNG和JPEG的圖片相近。相關分別加載顏色和遮罩圖片并在運行時合成的代碼見14.7。
清單14.7 從PNG遮罩和JPEG創建的混合圖片
~~~
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//load color image
UIImage *image = [UIImage imageNamed:@"Snowman.jpg"];
//load mask image
UIImage *mask = [UIImage imageNamed:@"SnowmanMask.png"];
//convert mask to correct format
CGColorSpaceRef graySpace = CGColorSpaceCreateDeviceGray();
CGImageRef maskRef = CGImageCreateCopyWithColorSpace(mask.CGImage, graySpace);
CGColorSpaceRelease(graySpace);
//combine images
CGImageRef resultRef = CGImageCreateWithMask(image.CGImage, maskRef);
UIImage *result = [UIImage imageWithCGImage:resultRef];
CGImageRelease(resultRef);
CGImageRelease(maskRef);
//display result
self.imageView.image = result;
}
@end
~~~
????對每張圖片都使用兩個獨立的文件確實有些累贅。JPNG的庫([https://github.com/nicklockwood/JPNG](https://github.com/nicklockwood/JPNG))對這個技術提供了一個開源的可以復用的實現,并且添加了直接使用`+imageNamed:`和`+imageWithContentsOfFile:`方法的支持。
### JPEG 2000
????除了JPEG和PNG之外iOS還支持別的一些格式,例如TIFF和GIF,但是由于他們質量壓縮得更厲害,性能比JPEG和PNG糟糕的多,所以大多數情況并不用考慮。
????但是iOS之后,蘋果低調添加了對JPEG 2000圖片格式的支持,所以大多數人并不知道。它甚至并不被Xcode很好的支持 - JPEG 2000圖片都沒在Interface Builder中顯示。
????但是JPEG 2000圖片在(設備和模擬器)運行時會有效,而且比JPEG質量更好,同樣也對透明通道有很好的支持。但是JPEG 2000圖片在加載和顯示圖片方面明顯要比PNG和JPEG慢得多,所以對圖片大小比運行效率更敏感的時候,使用它是一個不錯的選擇。
????但仍然要對JPEG 2000保持關注,因為在后續iOS版本說不定就對它的性能做提升,但是在現階段,混合圖片對更小尺寸和質量的文件性能會更好。
### PVRTC
????當前市場的每個iOS設備都使用了Imagination Technologies PowerVR圖像芯片作為GPU。PowerVR芯片支持一種叫做PVRTC(PowerVR Texture Compression)的標準圖片壓縮。
????和iOS上可用的大多數圖片格式不同,PVRTC不用提前解壓就可以被直接繪制到屏幕上。這意味著在加載圖片之后不需要有解壓操作,所以內存中的圖片比其他圖片格式大大減少了(這取決于壓縮設置,大概只有1/60那么大)。
????但是PVRTC仍然有一些弊端:
* 盡管加載的時候消耗了更少的RAM,PVRTC文件比JPEG要大,有時候甚至比PNG還要大(這取決于具體內容),因為壓縮算法是針對于性能,而不是文件尺寸。
* PVRTC必須要是二維正方形,如果源圖片不滿足這些要求,那必須要在轉換成PVRTC的時候強制拉伸或者填充空白空間。
* 質量并不是很好,尤其是透明圖片。通常看起來更像嚴重壓縮的JPEG文件。
* PVRTC不能用Core Graphics繪制,也不能在普通的`UIImageView`顯示,也不能直接用作圖層的內容。你必須要用作OpenGL紋理加載PVRTC圖片,然后映射到一對三角板來在`CAEAGLLayer`或者`GLKView`中顯示。
* 創建一個OpenGL紋理來繪制PVRTC圖片的開銷相當昂貴。除非你想把所有圖片繪制到一個相同的上下文,不然這完全不能發揮PVRTC的優勢。
* PVRTC使用了一個不對稱的壓縮算法。盡管它幾乎立即解壓,但是壓縮過程相當漫長。在一個現代快速的桌面Mac電腦上,它甚至要消耗一分鐘甚至更多來生成一個PVRTC大圖。因此在iOS設備上最好不要實時生成。
????如果你愿意使用OpehGL,而且即使提前生成圖片也能忍受得了,那么PVRTC將會提供相對于別的可用格式來說非常高效的加載性能。比如,可以在主線程1/60秒之內加載并顯示一張2048×2048的PVRTC圖片(這已經足夠大來填充一個視網膜屏幕的iPad了),這就避免了很多使用線程或者緩存等等復雜的技術難度。
????Xcode包含了一些命令行工具例如*texturetool*來生成PVRTC圖片,但是用起來很不方便(它存在于Xcode應用程序束中),而且很受限制。一個更好的方案就是使用Imagination Technologies?*PVRTexTool*,可以從[http://www.imgtec.com/powervr/insider/sdkdownloads免費獲得。](http://www.imgtec.com/powervr/insider/sdkdownloads%E5%85%8D%E8%B4%B9%E8%8E%B7%E5%BE%97%E3%80%82)
????安裝了PVRTexTool之后,就可以使用如下命令在終端中把一個合適大小的PNG圖片轉換成PVRTC文件:
~~~
/Applications/Imagination/PowerVR/GraphicsSDK/PVRTexTool/CL/OSX_x86/PVRTexToolCL -i {input_file_name}.png -o {output_file_name}.pvr -legacypvr -p -f PVRTC1_4 -q pvrtcbest
~~~
????清單14.8的代碼展示了加載和顯示PVRTC圖片的步驟(第6章`CAEAGLLayer`例子代碼改動而來)。
清單14.8 加載和顯示PVRTC圖片
~~~
#import "ViewController.h"
#import
#import
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *glView;
@property (nonatomic, strong) EAGLContext *glContext;
@property (nonatomic, strong) CAEAGLLayer *glLayer;
@property (nonatomic, assign) GLuint framebuffer;
@property (nonatomic, assign) GLuint colorRenderbuffer;
@property (nonatomic, assign) GLint framebufferWidth;
@property (nonatomic, assign) GLint framebufferHeight;
@property (nonatomic, strong) GLKBaseEffect *effect;
@property (nonatomic, strong) GLKTextureInfo *textureInfo;
@end
@implementation ViewController
- (void)setUpBuffers
{
//set up frame buffer
glGenFramebuffers(1, &_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
//set up color render buffer
glGenRenderbuffers(1, &_colorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderbuffer);
[self.glContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.glLayer];
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_framebufferWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_framebufferHeight);
//check success
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
NSLog(@"Failed to make complete framebuffer object: %i", glCheckFramebufferStatus(GL_FRAMEBUFFER));
}
}
- (void)tearDownBuffers
{
if (_framebuffer) {
//delete framebuffer
glDeleteFramebuffers(1, &_framebuffer);
_framebuffer = 0;
}
if (_colorRenderbuffer) {
//delete color render buffer
glDeleteRenderbuffers(1, &_colorRenderbuffer);
_colorRenderbuffer = 0;
}
}
- (void)drawFrame
{
//bind framebuffer & set viewport
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
glViewport(0, 0, _framebufferWidth, _framebufferHeight);
//bind shader program
[self.effect prepareToDraw];
//clear the screen
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(0.0, 0.0, 0.0, 0.0);
//set up vertices
GLfloat vertices[] = {
-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f
};
//set up colors
GLfloat texCoords[] = {
0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f
};
//draw triangle
glEnableVertexAttribArray(GLKVertexAttribPosition);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, 0, texCoords);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
//present render buffer
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
[self.glContext presentRenderbuffer:GL_RENDERBUFFER];
}
- (void)viewDidLoad
{
[super viewDidLoad];
//set up context
self.glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:self.glContext];
//set up layer
self.glLayer = [CAEAGLLayer layer];
self.glLayer.frame = self.glView.bounds;
self.glLayer.opaque = NO;
[self.glView.layer addSublayer:self.glLayer];
self.glLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking: @NO, kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8};
//load texture
glActiveTexture(GL_TEXTURE0);
NSString *imageFile = [[NSBundle mainBundle] pathForResource:@"Snowman" ofType:@"pvr"];
self.textureInfo = [GLKTextureLoader textureWithContentsOfFile:imageFile options:nil error:NULL];
//create texture
GLKEffectPropertyTexture *texture = [[GLKEffectPropertyTexture alloc] init];
texture.enabled = YES;
texture.envMode = GLKTextureEnvModeDecal;
texture.name = self.textureInfo.name;
//set up base effect
self.effect = [[GLKBaseEffect alloc] init];
self.effect.texture2d0.name = texture.name;
//set up buffers
[self setUpBuffers];
//draw frame
[self drawFrame];
}
- (void)viewDidUnload
{
[self tearDownBuffers];
[super viewDidUnload];
}
- (void)dealloc
{
[self tearDownBuffers];
[EAGLContext setCurrentContext:nil];
}
@end
~~~
????如你所見,非常不容易,如果你對在常規應用中使用PVRTC圖片很感興趣的話(例如基于OpenGL的游戲),可以參考一下`GLView`的庫([https://github.com/nicklockwood/GLView](https://github.com/nicklockwood/GLView)),它提供了一個簡單的`GLImageView`類,重新實現了`UIImageView`的各種功能,但同時提供了PVRTC圖片,而不需要你寫任何OpenGL代碼。
- Introduction
- 1. 圖層樹
- 1.1 圖層與視圖
- 1.2 圖層的能力
- 1.3 使用圖層
- 1.4 總結
- 2. 寄宿圖
- 2.1 contents屬性
- 2.2 Custom Drawing
- 2.3 總結
- 3. 圖層幾何學
- 3.1 布局
- 3.2 錨點
- 3.3 坐標系
- 3.4 Hit Testing
- 3.5 自動布局
- 3.6 總結
- 4. 視覺效果
- 4.1 圓角
- 4.2 圖層邊框
- 4.3 陰影
- 4.4 圖層蒙板
- 4.5 拉伸過濾
- 4.6 組透明
- 4.7 總結
- 5. 變換
- 5.1 仿射變換
- 5.2 3D變換
- 5.3 固體對象
- 5.4 總結
- 6. 專用圖層
- 6.1 CAShapeLayer
- 6.2 CATextLayer
- 6.3 CATransformLayer
- 6.4 CAGradientLayer
- 6.5 CAReplicatorLayer
- 6.6 CAScrollLayer
- 6.7 CATiledLayer
- 6.8 CAEmitterLayer
- 6.9 CAEAGLLayer
- 6.10 AVPlayerLayer
- 6.11 總結
- 7. 隱式動畫
- 7.1 事務
- 7.2 完成塊
- 7.3 圖層行為
- 7.4 呈現與模型
- 7.5 總結
- 8. 顯式動畫
- 8.1 屬性動畫
- 8.2 動畫組
- 8.3 過渡
- 8.4 在動畫過程中取消動畫
- 8.5 總結
- 9. 圖層時間
- 9.1 CAMediaTiming協議
- 9.2 層級關系時間
- 9.3 手動動畫
- 9.4 總結
- 10. 緩沖
- 10.1 動畫速度
- 10.2 自定義緩沖函數
- 10.3 總結
- 11. 基于定時器的動畫
- 11.1 定時幀
- 11.2 物理模擬
- 12. 性能調優
- 12.1. CPU VS GPU
- 12.2 測量,而不是猜測
- 12.3 Instruments
- 12.4 總結
- 13. 高效繪圖
- 13.1 軟件繪圖
- 13.2 矢量圖形
- 13.3 臟矩形
- 13.4 異步繪制
- 13.5 總結
- 14. 圖像IO
- 14.1 加載和潛伏
- 14.2 緩存
- 14.3 文件格式
- 14.4 總結
- 15. 圖層性能
- 15.1 隱式繪制
- 15.2 離屏渲染
- 15.3 混合和過度繪制
- 15.4 減少圖層數量
- 15.5 總結