在你的iPad上調整圖片尺寸

本文翻譯自Brian's Brain的Resize Images on Your iPad程序員

在個人上一篇文章中,我描述了試圖用圖片平鋪的方式來解決在ipad上展現「大型圖片」的問題的第一次嘗試。在這種方式中,你把圖片拉伸成不一樣的尺寸,而後把每一個圖片分割成一張張正方形的片斷。經過使用Cocoa框架提供的CATiledlayer類,你能夠在不一樣的縮放層級下,繪製所須要的圖片片斷。多線程

可是,在iPad 1上運行時,當我試圖爲大型圖片計算分割的片斷時,仍然偶爾會把內存用完。所以,在Pholio2.1版本中,選擇了一個更加簡單的方法。當用戶提供了一個大的圖像,我將其縮小到一個可管理的大小。我選擇把圖片長和寬的像素控制在1500像素之內。這樣顯示這張圖片將須要9MB內存,用戶仍然能夠放大一點來看到更多的細節。框架

記住,平鋪圖片的技術須要你屢次調整圖片的尺寸,而且在每一個尺寸,你都須要計算和保存圖片的片斷。這些都須要耗費時間和內存。如今這種方法,不但簡單,並且更快。。。我只須要調整和保存圖片一次,在UIImageViewController 中顯示一個已經被調整大小的圖片,而不是使用CATiledLayer顯示一張圖片的片斷,一樣也會防止用戶在滾動到一個新的圖片時產生閃爍。 這種方法的惟一缺點就是:用戶不能放大來看清他們圖片的實際像素。ide

儘管如此,仍然有些技巧來提升調整圖片尺寸的效率,我用Pholio這個應用來告訴你這些技巧吧。函數


技巧一:有一個簡單的調整圖片尺寸的程序(方法)

在Pholio中,這個方法就是 - [IPPhoto optimize] 。 我將會更加詳細地講解裏面的一些細節,但在一個更高的視角,這個方法有下面這些關鍵的屬性:單元測試

  • 同步。 這意味着能夠很簡單地進行單元測試。 也意味着慢。。。能夠更多地關心多線程中的其餘問題。測試

  • 防止過多佔用內存。這個問題就是在調整大尺寸圖片時會消耗大量的內存,個人工做就是確保在這個程序返回時全部可能的內存都被釋放了。儘量少地把IPPhoto對象放到任何的自動釋放池中。由於調整圖片尺寸、釋放內存等操做都會佔用系統資源。優化

  • 作了全部必要的準備工做,以使圖片高效率地顯示在iPad上。這意味着,全部調整大圖片尺寸,爲全部圖片生成縮略圖的工做都會在- [IPPhoto optimize ] 方法裏面完成。ui

  • 關鍵:我組織代碼的其他部分,確保IPPhoto對象延遲加載,僅在我調用以後纔在數據模型中構造。這意味着一切須要顯示在屏幕上的數據模型都是公平的。atom

通過圍繞在調整圖片大小的一個同步程序的編寫後,能夠很容易推理出程序的正確行爲。可是由於這個程序須要很長的運行時間,必需要在後臺線程中執行,不然影響用戶界面的響應。


技巧二:使用後臺線程 - 可是不要太多

個人第一個錯誤是天真地讓全部用GCD調度這個程序 - [IPPhoto optimize] 都在後臺線程隊列中響應。而後,一旦圖片在後臺完成優化,我會在主線程中把這個圖片插入到模型中。

問題?太多的後臺優化!由於每次調用 - [IPPhoto optimize] 都會消耗不少內存,這樣同一時間超過一個優化都會把iPad 1 弄垮。而這就是我把不一樣的優化放到後臺隊列中發生的事情。不知道還好,原來GCD會同時調度安排工做運行。

爲了解決這個問題,我引入了一個新的類, IPPhotoOptimizationManager(.h, .m )。這個優化圖片的管理器能夠完成下面的任務:

  • 它建立一個單一的NSOperationQueue 來進行全部的內存密集型操做,如調整大小。而後使用 - [NSOperationQueue setMaxConcurrentOperationCount: ] 方法,確保同一時間最多隻有一個後臺優化。

  • 它定義了一個對一張或多張圖片進行優化的隊列,並在主線程中調用上面的優化程序來簡單地完成工做。

  • 它維護一個全部正在進行和正在等待的優化的計數器。

  • 每當正在進行的優化操做數量改變,都會通知委託,我使用這個提示用戶優化正在進行。

經過使用 IPPhotoOptimizationManager 類,Pholio 能夠控制後臺的優化。


技巧三:ImageIO是你的朋友

我原始的調整圖片尺寸的代碼來自[ Trevor's Bike Shed ][2]的幫助。它運行良好,同時也可做爲你開始編寫調整圖片尺寸代碼的開始。

可是,由於每隔一段時間個人程序就會由於內存爆滿而崩潰,我決定直接使用跟底層的源代碼:Apple提供的多才多藝的ImageIO庫。ImageIO是一個C語言寫的程序庫,而不是用Objective-C,所以會有一點難度,但它是讀取和調整圖片的最有效方式。

我主要在 - [IPPhoto optimize] 方法中使用ImageIO。下面是它如何工做的。首先,使用ImageIO,能夠從一個圖片源開始(CGImageSourceRef)。你能夠從任何文件中經過生成一個文件URL來建立一個圖片源。Pholio 代碼是 :

NSURL *imageUrl = [NSURL fileURLWithPath:self.filename];
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageUrl, NULL);

記住 CGImageSourceRef 是Core Foudation 中的一個對象,全部你須要調用 CFRelease() 來釋放它。

有了圖片源,Pholio接着肯定是否須要調整圖片尺寸。我須要調整最大像素爲1500以上的全部圖片(在代碼中,爲常量kIPPhotoMaxEdgeSize)。注意,ImageIO可讓你得到圖片的元數據,而不用讀取整個圖片到內存中,這個能夠用來肯定圖片的尺寸。

CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
  CFNumberRef pixelWidthRef  = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth);
  CFNumberRef pixelHeightRef = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight);
  CGFloat pixelWidth = [(NSNumber *)pixelWidthRef floatValue];
  CGFloat pixelHeight = [(NSNumber *)pixelHeightRef floatValue];
  CGFloat maxEdge = MAX(pixelWidth, pixelHeight);
  if (maxEdge > kIPPhotoMaxEdgeSize) {
      //  Need to resize
  }
  CFRelease(imageProperties);

好了,若是我肯定了我須要調整圖片的大小,那是如何實現的?答案就是ImageIO 的CGImageSourceCreateThumbnailAtIndex() 方法。這個函數在使用時特別彆扭,由於你須要經過字典來傳遞最有意義的參數(這纔是ImageIO作的全部工做,可是到目前爲止,我已經可以在其餘的ImageIO調用中,忽略它)。我是有點懶惰的程序員,全部我把CFDictionaryRef和NSDictionary之間的轉換封裝到NSDictionary 的構造函數中。

NSDictionary *thumbnailOptions = [NSDictionary dictionaryWithObjectsAndKeys:(id)kCFBooleanTrue,         
                                kCGImageSourceCreateThumbnailWithTransform,
                                kCFBooleanTrue, kCGImageSourceCreateThumbnailFromImageAlways,
                                [NSNumber numberWithFloat:kIPPhotoMaxEdgeSize], kCGImageSourceThumbnailMaxPixelSize,
                                nil];
 CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, (CFDictionaryRef)thumbnailOptions);

上面的代碼說明:

  • 調整大小後的圖片應該包含原始圖片中存在的全部旋轉轉換(kCGImageSourceCreateThumbnailWithTransform)。

  • 調整大小後的圖片應該至少在一邊上是 kIPPhotoMaxEdgeSize 的像素(kCGImageSourceThumbnailMaxPixelSize)。

  • 這個函數應該調整我所指定的圖片,而不是重用文件中存在的小圖片(kCGImageSourceCreateThumbnailFromImageAlways)。

這樣,我就有了一個有效的CGImageRef 對象了。我把它壓縮爲一個JPEG圖片,並保存它:

UIImage *resizedImage = [UIImage imageWithCGImage:thumbnail];
  NSData *jpegData = UIImageJPEGRepresentation(resizedImage, 0.8);
  [jpegData writeToFile:self.filename atomically:YES];
  CFRelease(thumbnail);

技巧四:不要依賴UIImage 會釋放它的內存數據

前面的三個小技巧使Pholio對iPad的1可靠性產生了巨大的差別,可是,它仍然太容易了在大量的大圖像時程序崩潰。

我花了一些時間在 Instruments 中,看看我可否找出發生了什麼事。當你的應用收到內存警告時,最重要的事情是查看什麼形成了大量髒內存。這個VM Tracker instruments 能夠向你展現關於髒內存的信息。我在 Instruments 上發現,在虛擬機中進入多個頁面以及導入圖像後,我有超過140MB的髒內存,甚至在收到內存警告後! 這個 VM Tracker 告訴我,大部分的髒內存便籤是 70. 若是你能夠相信互聯網, 這種內存來自於內存中加載的圖片。。。。有道理,這就是個人程序所作的。

爲何在收到內存警告後會使用這麼多圖片數據?誠然,每當調用 - [IPPhoto image]時,我都會須要加載UIImage對象,確歷來沒有明確地卸載UIImage 對象。然而,根據UIImage 的說明文檔,「在低內存下,圖片數據能夠從一個UIImage對象中被清除以釋放系統內存。」 因此我預計大圖片會從內存中自動清除。

我得出的結論是 UIImage 不能準確地清除圖片,所以,我須要手動管理這些圖片。我在IPPhoto 中 寫了下面的簡單方法:

- (void)unloadImage {
     [image_ release], image_ = nil;
}

在Pholio, 只有一個類會以全分辨率顯示圖片: IPPhotoScrollView。 我作了如下兩個小改動:

一、 當IPPhotoScrollView 被告知要顯示一個新 IPPhoto 對象時,它會給 舊的 IPPhoto對象發送一個 unloadImage 消息。

二、 在 - [IPPhotoScrollView dealloc ]中方法中,我給當前的IPPhoto 對象 發送一個 unloadImage 消息。

這兩個改動意味着當我再也不須要顯示給用戶時,我明確地 卸載了 圖片。

結果呢??在模擬器中,這個髒數據降低到了54MB - 這些簡單的改動 減小了 60% 的 髒內存。

作了這些改動以後(直接使用Image IO, 確保同一時間不超過一個圖片優化,當再也不顯示時卸載圖片),我可以完美地同時在iPad 1 和 iPad 2上處理大圖片了。

相關文章
相關標籤/搜索