您好,登錄后才能下訂單哦!
引言:GIF圖像格式是常見的一種動態圖片格式,無論是在Web端還是在移動端都經常遇到,但是考慮目前iOS還無法原生展現GIF圖片,而對于GIF的原生支持暫時也沒有像JPG、PNG等圖像格式支持得這么全面,因此本文從圖片的合成與分解角度來為大家講解GIF的知識,結合ImageIO框架可以更方便地實現GIF圖片的合成與分解。
本文選自《iOS動畫——核心技術與案例實戰》。
GIF在iOS中的使用場景有以下三個方面。
(1)GIF圖片分解為單幀圖片。
(2)一系列單幀圖片合成GIF圖片。
(3)iOS系統上展示GIF動畫效果。
在GIF的合成和分解方面將會接觸到iOS圖像處理核心框架ImageIO,作為iOS系統中圖像處理的核心框架,它為我們提供了各種豐富的API,本文將要實現的GIF分解與合成功能,通過ImageIO就可以很方便地實現。GIF動畫展示效果將結合UIImageView和定時器,利用逐幀展示的方式為大家呈現GIF動畫效果。
GIF分解為單幀圖片的過程如下。
整個過程劃分為5個模塊、4個過程,分別如下。
(1)本地讀取GIF圖片,將其轉換為NSdata數據類型。
(2)將NSData作為ImageIO模塊的輸入。
(3)獲取ImageIO的輸出數據:UIImage。
(4)將獲取到的UIImage數據存儲為JPG或者PNG格式保存到本地。
在整個GIF圖片分解的過程中,ImageIO是處理過程的核心部分。它負責對GIF文件格式進行解析,并將解析之后的數據轉換為一幀幀圖片輸出。幸運的是我們并不是“輪子”的創造者,而是只要使用輪子即可。所以在本書中我們不去研究GIF分解合成算法的具體實現方式,而是將注意力聚焦在如何使用ImageIO框架實現需要的功能上。
在正式分析代碼之前,先來看看整個工程的文件結構,如圖。
源文件使用的是plane.gif文件。ViewController.swift文件中的viewDidLoad()方法中包含了GIF圖片分解為單幀圖片并保存到本地的所有代碼。下面就結合“GIF分解為單幀圖片的過程”來實現這一功能。
功能模塊一:讀取GIF文件并將之轉換為NSdata類型。
1 let gifPath:NSString = Bundle.main.path(forResource: "plane", ofType: "gif")! as NSString2 let gifData:Data = try! Data(contentsOf: URL(fileURLWithPath: gifPath as String))
代碼第1行通過path方法獲取文件名為plane、文件格式為gif的文件地址。第2行獲取文件信息并加載到gifData(NSData類型)變量中。至此已經完成整個處理流程的第一個環節。
功能模塊二:利用ImageIO框架,遍歷所有GIF子幀。需要注意的是使用ImageIO必須把讀取到的NSdata數據轉換為ImageIO可以處理的數據類型,這里使用CGImageSourceRef實現。其相應功能模塊的處理流程如下所示。
1 let gifDataSource:CGImageSource = CGImageSourceCreateWithData(gifData as CFData, nil)!2 let gifImageCount:Int = CGImageSourceGetCount(gifDataSource)3 for i in 0...gifImageCount-1{ let p_w_picpathref:CGImage? =CGImageSourceCreateImageAtIndex(gifDataSource, i, nil) let p_w_picpath:UIImage = UIImage(cgImage: p_w_picpathref!,scale:UIScreen.main.scale,orientation:UIImageOrientation.up ) }
下面是GIF數據處理流程中ImageIO部分功能描述。代碼第1行實現將GIF原始數據類型NSdata轉換為ImageIO可以直接處理的數據類型CGImageSourceRef。第2行獲取當前GIF圖片的分幀個數。我們知道GIF圖片都是由一幀幀圖片組成的,那么這一行就是為了獲取構成GIF圖片的張數。第3行對CGImageSource數據按照圖片的序號進行遍歷,將遍歷出的結果使用UIImage系統方法將之轉換為UIImage。
這里重點為大家介紹兩種方法。
CGImageSourceCreateImageAtIndex方法的作用是返回GIF中其中某一幀圖像的CGImage類型數據。該方法有三個參數,參數1為GIF原始數據,參數2 為GIF子幀中的序號(該序號從0開始),參數3為GIF數據提取的一些選擇參數,因為這里不是很常用,所以設置為nil。
public func CGImageSourceCreateImageAtIndex(_ isrc: CGImageSource, _ index: Int, _ options: CFDictionary?) -> CGImage?
以下為UIImage類的方法,這個方法用于實例化UIImage實例對象。該方法有三個參數,參數1為需要構建UIImage的內容,注意這里的內容是CGImage類型,參數2為手機物理像素與手機和手機顯示分辨率的換算系數,參數3表明構建的UIImage的圖像方向。通過這個方法就可以在某種手機分辨率下構建指定方向的圖像,當然圖像的類型是UIImage類型。
public init(CGImage cgImage: CGImage, scale: CGFloat, orientation: UIImageOrientation)
通過上述兩步已經獲取了UIImage,然而UIImage并不是通常我們看到的圖像格式,此圖像格式最大的特點是無法存儲為本地可以查看的圖片格式,因此如果需要將圖像保存在本地,就需要在這之前將已經得到的UIImage數據類型轉換為PNG或者JPG類型的圖像數據,然后才能把圖像存儲到本地。
下面是完整的GIF圖像分解保存代碼:
override func viewDidLoad() {1 super.viewDidLoad()2 let gifPath:NSString = Bundle.main.path(forResource:"plane", ofType: "gif")! as NSString3 let gifData:Data = try! Data(contentsOf:URL(fileURLWithPath: gifPath as String))4 let gifDataSource:CGImageSource =CGImageSourceCreateWithData(gifData as CFData, nil)!5 let gifImageCount:Int =CGImageSourceGetCount(gifDataSource)6 for i in 0...gifImageCount-1{7 let p_w_picpathref:CGImage? =CGImageSourceCreateImageAtIndex(gifDataSource, i, nil)8 let p_w_picpath:UIImage = UIImage(cgImage: p_w_picpathref!,scale:UIScreen.main.scale,orientation:UIImageOrientation.up )9 let p_w_picpathData:Data = UIImagePNGRepresentation(p_w_picpath)!10 var docs=NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)11 let documentsDirectory = docs[0] as String12 let p_w_picpathPath = documentsDirectory+"/\(i)"+".png"13 try? p_w_picpathData .write(to: URL(fileURLWithPath:p_w_picpathPath), options: [.atomic])14 print("\(p_w_picpathPath)") } }
代碼第1行使用UIImagePNGRepresentation方法將UIImage數據類型存儲為PNG格式的data數據類型,第2行代碼和第3行代碼獲取應用的Document目錄,第4行調用write方法將圖片寫入到本地文件中。如果大家想查看最終寫入的效果,可以在最后一行添加print信息,將文件寫入路徑打印出來,觀察圖像寫入是否成功。
通過上述代碼中的最后一行print(“(p_w_picpathPath)”)可以獲取圖片最終保存的路徑。進入該路徑下可以看到下圖所示的圖片最終分解結果。
根據上下圖,在Mac系統下,利用系統圖片的查看工具來查看GIF圖片的分幀結果,對比圖中內容,可以看出GIF圖片分解的結果是正確的。
多幀圖像合成GIF的過程和GIF分解多幀圖像的過程互逆,GIF圖片分解過程倒過來推,就是GIF圖像合成的過程。這里將上面分解的67張序列單幀圖像作為需要處理的輸入源進行講述。
從功能上來說,GIF圖片的合成分為以下三個主要部分。
(1)加載待處理的67張原始數據源。
(2)在Document目錄下構建GIF文件。
(3)設置GIF文件屬性,利用ImageIO編碼GIF文件。
如下代碼是根據GIF構建的三個主要步驟進行編寫的。第一部分代碼的功能是將67張PNG圖片讀取到NSMutableArray數組中。代碼第1行初始化可變數組,第2行遍歷67張本地圖片,第3行按照圖片的命名規律,構建67張圖片名稱,第4行加載本地圖片。最后一行將讀取的圖片依次加載到p_w_picpaths可變數組中。
// Part1:讀取67張png圖片1 let p_w_picpaths:NSMutableArray = NSMutableArray()2 for i in 0...66{// 遍歷本地67張圖片3 let p_w_picpathPath = "\(i).png" // 構建圖片名稱4 let p_w_picpath:UIImage = UIImage(named: p_w_picpathPath)!//5 p_w_picpaths.addObject(p_w_picpath)// 將圖片添加到數組中}
代碼第二部分的功能是構建在Document目錄下的GIF文件路徑。具體實現如下所示。
// Part2:在Document目錄創建gif文件1 var docs=NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)2 let documentsDirectory = docs[0] as String3 let gifPath = documentsDirectory+"/plane.gif"4 print("\(gifPath)")5 let url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, gifPath as CFString!,CFURLPathStyle.cfurlposixPathStyle, false)6 let destion = CGImageDestinationCreateWithURL(url!, kUTTypeGIF, p_w_picpaths.count, nil)
代碼1一行和第2行獲取Document路徑地址,第3行代碼通過字符串拼接時組成完整的Document路徑下plane.gif文件路徑。為了方便查看GIF文件所在路徑,第4行代碼將GIF文件路徑打印出來。第5行代碼將plane.gif文件路徑由string類型轉換為URL類型。最后一行代碼是ImageIO中構建GIF圖片非常重要的方法,我們重點來分析該方法的作用和功能。
public func CGImageDestinationCreateWithURL(_ url: CFURL, _ type: CFString, _ count: Int, _ options: CFDictionary?) -> CGImageDestination?
CGImageDestinationCreateWithURL方法的作用是創建一個圖片的目標對象,為了便于大家理解,這里把圖片目標對象比喻為一個集合體。
CGImageDestination結構
集合體中描述了構成當前圖片目標對象的一系列參數,如圖片的URL地址、圖片類型、圖片幀數、配置參數等。本代碼中將plane.gif的本地文件路徑作為參數1傳遞給這個圖片目標對象,參數2描述了圖片的類型為GIF圖片,參數3表明當前GIF圖片構成的幀數,參數4暫時給它一個空值。
到目前為止,待處理圖片源已經加載到代碼中,GIF圖片Destination也已經完成構建,下面就需要使用ImageIO框架把多幀PNG圖片編碼到GIF圖片中,其處理流程如下。
具體實現代碼如下:
// Part3:設置gif圖片屬性,利用67張png圖片構建gif1 let cgp_w_picpathPropertiesDic = [kCGImagePropertyGIFDelayTime as String:0.1]//設置每幀之間播放時間2 let cgp_w_picpathPropertiesDestDic =[kCGImagePropertyGIFDictionary as String:cgp_w_picpathPropertiesDic];3 for cgp_w_picpath in p_w_picpaths{4 CGImageDestinationAddImage(destion!, (cgp_w_picpath as AnyObject).cgImage!!,cgp_w_picpathPropertiesDestDic as CFDictionary?);}// 依次為gif圖像對象添加每一幀元素5 let gifPropertiesDic:NSMutableDictionary =NSMutableDictionary()6 gifPropertiesDic.setValue(kCGImagePropertyColorModelRGB,forKey: kCGImagePropertyColorModel as String)7 gifPropertiesDic.setValue(16, forKey:kCGImagePropertyDepth as String)// 設置圖像的顏色深度8 gifPropertiesDic.setValue(1, forKey:kCGImagePropertyGIFLoopCount as String)// 設置Gif執行次數9 let gifDictionaryDestDic = [kCGImagePropertyGIFDictionary as String:gifPropertiesDic]10 CGImageDestinationSetProperties(destion!,gifDictionaryDestDic as CFDictionary?);//為gif圖像設置屬性11 CGImageDestinationFinalize(destion!);
代碼第1行設置GIF圖片屬性,設置當前GIF中每幀圖片展示時間間隔為0.1s。代碼第2行構建一個GIF圖片屬性字典,字典使用GIF每幀之間的時間間隔初始化。代碼第4行使用遍歷的方法將已經準備好的圖片快速追加到GIF圖片的Destination中。代碼第5行初始化一個可變字典對象,該字典對象主要用于設置GIF圖片中每幀圖片屬性。第6行設置圖片彩色空間格式為RGB(Red Green Blue三基色)類型。第7行設置圖片顏色深度。一般來說黑白圖像也稱為二值圖像,顏色深度為1,表示2的一次方,即兩種顏色:黑和白。灰度圖像一般顏色深度為8,表示2的8次方,共計256種顏色,即從黑色到白色的漸變過程有256種。對于彩×××片來說一般有16位深度和32位深度之說,這里設置為16位深度彩×××片。代碼第8行設置GIF圖片執行的次數,這里設置為執行一次。代碼第9行和第10行負責將以上圖片設置的各種屬性添加到GIF的Destination目標中。最后一行完成GIF的Destination目標文件構建。
可以打印出當前GIF圖片的路徑,在該路徑下可以看到最終生成的GIF圖片。
iOS原生并不支持直接顯示GIF圖片,由前面的分析可知,GIF圖片由一幀幀的單幀圖片構成,所以只要實現GIF圖片的分解,接下來就是多組圖片顯示的問題了。為大家介紹另外一種圖片展現形式,即基于UIImageView展現GIF多幀圖片。
經過對GIF圖片展示思路的分析可以知道,在iOS下展現GIF分為兩步:第一步分解GIF圖片為單幀圖片,第二步在iOS下展現多幀圖片。UIImageView是一個用來展現圖片的UI組件,不過它還有一些動畫屬性可以用來進行逐幀動畫展現。
考慮到第一步GIF圖片已經分解,所以這里把分解之后的67張圖片先加載進來。
UIImageView多幀圖像展示具體實現代碼如下。
1 var p_w_picpaths:[UIImage] = []2 for i in 0...66{// 遍歷本地67張圖片3 let p_w_picpathPath = "\(i).png" // 構建圖片名稱4 let p_w_picpath:UIImage = UIImage(named: p_w_picpathPath)!5 p_w_picpaths.append(p_w_picpath)// 將圖片添加到數組中 }6 let p_w_picpathView = UIImageView()7 p_w_picpathView.frame = self.view.bounds8 p_w_picpathView.contentMode = UIViewContentMode.Center9 self.view.addSubview(p_w_picpathView)10 p_w_picpathView.animationImages = p_w_picpaths11 p_w_picpathView.animationDuration = 512 p_w_picpathView.animationRepeatCount = 113 p_w_picpathView.startAnimating()
代碼第1行初始化一個子元素為UIImage類型的數組對象。第2行到第5行通過for循環將67張圖片依次加載到當前數組中。第6行實例化一個UIImageView實例對象。第7行和第8行設置UIImageView實例對象的frame位置屬性以及圖片的拉伸方式,這里設置為居中顯示。第9行將UIImageView添加到self.view圖層上。第10行將初始化加載的67張圖片添加到UIImageView實例的animationImages上,相當于設置UIImageView的內容。第11行設置UIImageView圖片動畫播放周期。第12行設置動畫重復次數。最后一行啟動UIImageView多幀圖片展示動畫。
本文選自《iOS動畫——核心技術與案例實戰》,點此鏈接可在博文視點官網查看此書。
想及時獲得更多精彩文章,可在微信中搜索“博文視點”或者掃描下方二維碼并關注。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。