1. 程式人生 > >文件格式(圖像 IO 14.3)

文件格式(圖像 IO 14.3)

loader 實現 face inpu pro x86 attr 4.6 gef

文件格式

圖片加載性能取決於加載大圖的時間和解壓小圖時間的權衡。很多蘋果的文檔都說PNG是iOS所有圖片加載的最好格式。但這是極度誤導的過時信息了。

PNG圖片使用的無損壓縮算法可以比使用JPEG的圖片做到更快地解壓,但是由於閃存訪問的原因,這些加載的時間並沒有什麽區別。

清單14.6展示了標準的應用程序加載不同尺寸圖片所需要時間的一些代碼。為了保證實驗的準確性,我們會測量每張圖片的加載和繪制時間來確保考慮到解壓性能的因素。另外每隔一秒重復加載和繪制圖片,這樣就可以取到平均時間,使得結果更加準確。

清單14.6

技術分享
 1 #import "ViewController.h"
 2 
 3 static
NSString *const ImageFolder = @"Coast Photos"; 4 5 @interface ViewController () 6 7 @property (nonatomic, copy) NSArray *items; 8 @property (nonatomic, weak) IBOutlet UITableView *tableView; 9 10 @end 11 12 @implementation ViewController 13 14 - (void)viewDidLoad 15 { 16 [super viewDidLoad];
17 //set up image names 18 self.items = @[@"2048x1536", @"1024x768", @"512x384", @"256x192", @"128x96", @"64x48", @"32x24"]; 19 } 20 21 - (CFTimeInterval)loadImageForOneSec:(NSString *)path 22 { 23 //create drawing context to use for decompression 24 UIGraphicsBeginImageContext(CGSizeMake(1
, 1)); 25 //start timing 26 NSInteger imagesLoaded = 0; 27 CFTimeInterval endTime = 0; 28 CFTimeInterval startTime = CFAbsoluteTimeGetCurrent(); 29 while (endTime - startTime < 1) { 30 //load image 31 UIImage *image = [UIImage imageWithContentsOfFile:path]; 32 //decompress image by drawing it 33 [image drawAtPoint:CGPointZero]; 34 //update totals 35 imagesLoaded ++; 36 endTime = CFAbsoluteTimeGetCurrent(); 37 } 38 //close context 39 UIGraphicsEndImageContext(); 40 //calculate time per image 41 return (endTime - startTime) / imagesLoaded; 42 } 43 44 - (void)loadImageAtIndex:(NSUInteger)index 45 { 46 //load on background thread so as not to 47 //prevent the UI from updating between runs dispatch_async( 48 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 49 //setup 50 NSString *fileName = self.items[index]; 51 NSString *pngPath = [[NSBundle mainBundle] pathForResource:filename 52 ofType:@"png" 53 inDirectory:ImageFolder]; 54 NSString *jpgPath = [[NSBundle mainBundle] pathForResource:filename 55 ofType:@"jpg" 56 inDirectory:ImageFolder]; 57 //load 58 NSInteger pngTime = [self loadImageForOneSec:pngPath] * 1000; 59 NSInteger jpgTime = [self loadImageForOneSec:jpgPath] * 1000; 60 //updated UI on main thread 61 dispatch_async(dispatch_get_main_queue(), ^{ 62 //find table cell and update 63 NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0]; 64 UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; 65 cell.detailTextLabel.text = [NSString stringWithFormat:@"PNG: %03ims JPG: %03ims", pngTime, jpgTime]; 66 }); 67 }); 68 } 69 70 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 71 { 72 return [self.items count]; 73 } 74 75 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 76 { 77 //dequeue cell 78 UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Cell"]; 79 if (!cell) { 80 cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleValue1 reuseIdentifier:@"Cell"]; 81 } 82 //set up cell 83 NSString *imageName = self.items[indexPath.row]; 84 cell.textLabel.text = imageName; 85 cell.detailTextLabel.text = @"Loading..."; 86 //load image 87 [self loadImageAtIndex:indexPath.row]; 88 return cell; 89 } 90 91 @end
View Code

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創建的混合圖片

技術分享
 1 #import "ViewController.h"
 2 
 3 @interface ViewController ()
 4 
 5 @property (nonatomic, weak) IBOutlet UIImageView *imageView;
 6 
 7 @end
 8 
 9 @implementation ViewController
10 
11 - (void)viewDidLoad
12 {
13     [super viewDidLoad];
14     //load color image
15     UIImage *image = [UIImage imageNamed:@"Snowman.jpg"];
16     //load mask image
17     UIImage *mask = [UIImage imageNamed:@"SnowmanMask.png"];
18     //convert mask to correct format
19     CGColorSpaceRef graySpace = CGColorSpaceCreateDeviceGray();
20     CGImageRef maskRef = CGImageCreateCopyWithColorSpace(mask.CGImage, graySpace);
21     CGColorSpaceRelease(graySpace);
22     //combine images
23     CGImageRef resultRef = CGImageCreateWithMask(image.CGImage, maskRef);
24     UIImage *result = [UIImage imageWithCGImage:resultRef];
25     CGImageRelease(resultRef);
26     CGImageRelease(maskRef);
27     //display result
28     self.imageView.image = result;
29 }
30 
31 @end
View Code

對每張圖片都使用兩個獨立的文件確實有些累贅。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免費獲得。

安裝了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圖片

技術分享
  1 #import "ViewController.h" 
  2 #import  
  3 #import 
  4 
  5 @interface ViewController ()
  6 
  7 @property (nonatomic, weak) IBOutlet UIView *glView;
  8 @property (nonatomic, strong) EAGLContext *glContext;
  9 @property (nonatomic, strong) CAEAGLLayer *glLayer;
 10 @property (nonatomic, assign) GLuint framebuffer;
 11 @property (nonatomic, assign) GLuint colorRenderbuffer;
 12 @property (nonatomic, assign) GLint framebufferWidth;
 13 @property (nonatomic, assign) GLint framebufferHeight;
 14 @property (nonatomic, strong) GLKBaseEffect *effect;
 15 @property (nonatomic, strong) GLKTextureInfo *textureInfo;
 16 
 17 @end
 18 
 19 @implementation ViewController
 20 
 21 - (void)setUpBuffers
 22 {
 23     //set up frame buffer
 24     glGenFramebuffers(1, &_framebuffer);
 25     glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
 26     //set up color render buffer
 27     glGenRenderbuffers(1, &_colorRenderbuffer);
 28     glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
 29     glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderbuffer);
 30     [self.glContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.glLayer];
 31     glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_framebufferWidth);
 32     glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_framebufferHeight);
 33     //check success
 34     if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
 35         NSLog(@"Failed to make complete framebuffer object: %i", glCheckFramebufferStatus(GL_FRAMEBUFFER));
 36     }
 37 }
 38 
 39 - (void)tearDownBuffers
 40 {
 41     if (_framebuffer) {
 42         //delete framebuffer
 43         glDeleteFramebuffers(1, &_framebuffer);
 44         _framebuffer = 0;
 45     }
 46     if (_colorRenderbuffer) {
 47         //delete color render buffer
 48         glDeleteRenderbuffers(1, &_colorRenderbuffer);
 49         _colorRenderbuffer = 0;
 50     }
 51 }
 52 
 53 - (void)drawFrame
 54 {
 55     //bind framebuffer & set viewport
 56     glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
 57     glViewport(0, 0, _framebufferWidth, _framebufferHeight);
 58     //bind shader program
 59     [self.effect prepareToDraw];
 60     //clear the screen
 61     glClear(GL_COLOR_BUFFER_BIT);
 62     glClearColor(0.0, 0.0, 0.0, 0.0);
 63     //set up vertices
 64     GLfloat vertices[] = {
 65         -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f
 66     };
 67     //set up colors
 68     GLfloat texCoords[] = {
 69         0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f
 70     };
 71     //draw triangle
 72     glEnableVertexAttribArray(GLKVertexAttribPosition);
 73     glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
 74     glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, vertices);
 75     glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, 0, texCoords);
 76     glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 77     //present render buffer
 78     glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
 79     [self.glContext presentRenderbuffer:GL_RENDERBUFFER];
 80 }
 81 
 82 - (void)viewDidLoad
 83 {
 84     [super viewDidLoad];
 85     //set up context
 86     self.glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
 87     [EAGLContext setCurrentContext:self.glContext];
 88     //set up layer
 89     self.glLayer = [CAEAGLLayer layer];
 90     self.glLayer.frame = self.glView.bounds;
 91     self.glLayer.opaque = NO;
 92     [self.glView.layer addSublayer:self.glLayer];
 93     self.glLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking: @NO, kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8};
 94     //load texture
 95     glActiveTexture(GL_TEXTURE0);
 96     NSString *imageFile = [[NSBundle mainBundle] pathForResource:@"Snowman" ofType:@"pvr"];
 97     self.textureInfo = [GLKTextureLoader textureWithContentsOfFile:imageFile options:nil error:NULL];
 98     //create texture
 99     GLKEffectPropertyTexture *texture = [[GLKEffectPropertyTexture alloc] init];
100     texture.enabled = YES;
101     texture.envMode = GLKTextureEnvModeDecal;
102     texture.name = self.textureInfo.name;
103     //set up base effect
104     self.effect = [[GLKBaseEffect alloc] init];
105     self.effect.texture2d0.name = texture.name;
106     //set up buffers
107     [self setUpBuffers];
108     //draw frame
109     [self drawFrame];
110 }
111 
112 - (void)viewDidUnload
113 {
114     [self tearDownBuffers];
115     [super viewDidUnload];
116 }
117 
118 - (void)dealloc
119 {
120     [self tearDownBuffers];
121     [EAGLContext setCurrentContext:nil];
122 }
123 
124 @end
View Code

如你所見,非常不容易,如果你對在常規應用中使用PVRTC圖片很感興趣的話(例如基於OpenGL的遊戲),可以參考一下GLView的庫(https://github.com/nicklockwood/GLView),它提供了一個簡單的GLImageView類,重新實現了UIImageView的各種功能,但同時提供了PVRTC圖片,而不需要你寫任何OpenGL代碼。

文件格式(圖像 IO 14.3)