版權聲明:本文爲博主原創文章,未經博主容許不得轉載java
系列博客:源碼閱讀系列android
源碼:GifDecodergit
你們要是看到有錯誤的地方或者有啥好的建議,歡迎留言評論github
前言:閱讀優秀的源碼能夠大大提升咱們的開發水平,遂開個新坑 記錄優秀源碼(Android源代碼、各類開源庫等等)的分析和解讀,學習別人是怎樣實現某個功能的。本期咱們的主角是 GIF的解碼,咱們將從GIF解碼的源碼 GifDecoder入手,分析其實現的原理和過程,但願能幫到你們~( GifDecoder源碼(博主已對源碼裏面各方法及參數進行了註釋,請放心食用 ~)連接已在上方貼出來了,該源碼參考了Glide開源庫解析GIF部分的代碼,但因爲是好久以前看到的,具體出處已無從考證,有知道的小夥伴能夠留言告訴我)數組
目錄
- GIF結構簡述
- GifDecoder的初始化
- 判斷傳入文件格式
- 讀取GIF大小、顏色深度等全局屬性
- 提取各幀圖片
相關博文連接
gif 格式圖片詳細解析app
在分析源碼以前,咱們得先對GIF圖片的構成有一個初步的瞭解(詳細解析請看上方連接),見下圖ide
圖中加粗部分既是保存咱們所須要提取圖片的地方(一幀圖像對應一個圖像塊)。雖然咱們知道了存儲每一幀圖像信息的位置,但咱們不能直接從中取出圖片,由於在計算機中,全部的文件都是以二進制的形式存儲的,而Java讀取文件須要按順序一個一個字節地讀。所以GIF的解碼過程,實際上就是從文件頭(File Header)開始,按順序遍歷每個字節,當讀到咱們須要的信息(圖像數據)時,就將其提取出來。下面咱們就開始分析GifDecoder是如何實現GIF解碼的post
先來看看GifDecoder的初始化和使用示例,代碼以下學習
try {
InputStream is = getContentResolver().openInputStream(uri);
GifDecoder gifDecoder = new GifDecoder();
int code = gifDecoder.read(is);
if (code == GifDecoder.STATUS_OK) {//解碼成功
GifDecoder.GifFrame[] frameList = gifDecoder.getFrames();
} else if (code == gifDecoder.STATUS_FORMAT_ERROR) {//圖片格式不是GIF
} else {//圖片讀取失敗
}
}catch (FileNotFoundException e){
e.printStackTrace();
}
複製代碼
其中參數uri爲GIF圖片的Uri路徑,frameList爲解碼的結果,即GIF圖片中各幀的集合,裏面包括各幀靜態圖Bitmap和延遲時間。GifFrame是保存各幀的對象,具體實現和內部屬性以下spa
/** * 各幀對象 */
public static class GifFrame {
public Bitmap image;//靜態圖Bitmap
public int delay;//圖像延遲時間
public GifFrame(Bitmap im, int del) {
image = im;
delay = del;
}
}
複製代碼
GifDecoder定義了三種解碼狀態
public static final int STATUS_OK = 0;//解碼成功
public static final int STATUS_FORMAT_ERROR = 1;//圖片格式錯誤
public static final int STATUS_OPEN_ERROR = 2;//打開圖片失敗
複製代碼
從GifDecoder的使用示例中,咱們能夠看到GifDecoder解碼GIF圖片的入口爲read(InputStream is)方法,具體實現以下
protected int status;//解碼狀態
protected Vector<GifFrame> frames;//存放各幀對象的數組
protected int frameCount;//幀數
protected int[] gct; //全局顏色列表
protected int[] lct; //局部顏色列表
/** * 解碼入口,讀取GIF圖片輸入流 * @param is * @return */
public int read(InputStream is) {
init();
if (is != null) {
in = is;
readHeader();
if (!err()) {
readContents();
if (frameCount < 0) {
status = STATUS_FORMAT_ERROR;
}
}
} else {
status = STATUS_OPEN_ERROR;
}
try {
is.close();
} catch (Exception e) {
e.printStackTrace();
}
return status;
}
/** * 初始化參數 */
protected void init() {
status = STATUS_OK;
frameCount = 0;
frames = new Vector<GifFrame>();
gct = null;
lct = null;
}
/** * 判斷當前解碼過程是否出錯 * @return */
protected boolean err() {
return status != STATUS_OK;
}
複製代碼
能夠看到read(InputStream is)方法中體現了完整的解碼流程以及狀態判斷,其調用的readHeader()和readContents()即爲具體的GIF內部數據讀取方法。下一節咱們將深刻readHeader()方法看看GifDecoder是如何處理GIF文件頭的
解碼以前確定要先判斷解碼的對象是否爲GIF圖片,readHeader()中就實現了此判斷過程,判斷文件格式的代碼部分以下
/** * 讀取GIF 文件頭、邏輯屏幕標識符、全局顏色列表 */
protected void readHeader() {
//根據文件頭判斷是否GIF圖片
String id = "";
for (int i = 0; i < 6; i++) {
id += (char) read();
}
if (!id.toUpperCase().startsWith("GIF")) {
status = STATUS_FORMAT_ERROR;
return;
}
//解析GIF邏輯屏幕標識符和全局顏色列表
...
}
/** * 按順序一個一個讀取輸入流字節,失敗則設置讀取失敗狀態碼 * @return */
protected int read() {
int curByte = 0;
try {
curByte = in.read();
} catch (Exception e) {
status = STATUS_FORMAT_ERROR;
}
return curByte;
}
複製代碼
怎麼理解這段代碼呢?前文咱們提到文件頭(File Header)中包含了GIF的文件署名和版本號,共佔6個字節(見下圖),其中前3個字節存放的是GIF的文件署名,即G、I、F三個字符,那麼這段代碼就很好理解了,就是根據讀取出來的文件頭字符串開頭是否爲G、I 、F來判斷此文件格式符不符合要求
readHeader方法中還有一部分代碼,以下
protected boolean gctFlag;//是否使用了全局顏色列表
protected int bgIndex; //背景顏色索引
protected int gctSize; //全局顏色列表大小
protected int bgColor; //背景顏色
protected void readHeader() {
//根據文件頭判斷是否GIF圖片
...
//讀取GIF邏輯屏幕標識符
readLSD();
//讀取全局顏色列表
if (gctFlag && !err()) {
gct = readColorTable(gctSize);
bgColor = gct[bgIndex];//根據索引在全局顏色列表拿到背景顏色
}
}
複製代碼
其對應的正是GIF數據流(GIF Data Stream)的前兩部分邏輯屏幕標識符(Logical Screen Descriptor)與全局顏色列表(Global Color Table)的解析,也就是說readHeader()完成了讀取GIF圖像數據前全部全局屬性、配置信息的讀取與解析。接下來咱們先看readLSD()方法是如何解析邏輯屏幕標識符(Logical Screen Descriptor)(見下圖)的
protected int width;//完整的GIF圖像寬度
protected int height;//完整的GIF圖像高度
protected int pixelAspect; //像素寬高比(Pixel Aspect Radio)
/** * 讀取邏輯屏幕標識符(Logical Screen Descriptor)與全局顏色列表(Global Color Table) */
protected void readLSD() {
//獲取GIF圖像寬高
width = readShort();
height = readShort();
/** * 解析全局顏色列表(Global Color Table)的配置信息 * 配置信息佔一個字節,具體各Bit存放的數據以下 * 7 6 5 4 3 2 1 0 BIT * | m | cr | s | pixel | */
int packed = read();
gctFlag = (packed & 0x80) != 0;//判斷是否有全局顏色列表(m,0x80在計算機內部表示爲1000 0000)
gctSize = 2 << (packed & 7);//讀取全局顏色列表大小(pixel)
//讀取背景顏色索引和像素寬高比(Pixel Aspect Radio)
bgIndex = read();
pixelAspect = read();
}
/** * 讀取兩個字節的數據 * @return */
protected int readShort() {
return read() | (read() << 8);
}
複製代碼
根據readLSD()的讀取結果,咱們知道了此GIF圖像中是否含有全局顏色列表(Global Color Table)(見下圖),若是有,就調用readColorTable(int ncolors)方法獲取全局顏色列表
/** * 讀取顏色列表 * @param ncolors 列表大小,即顏色數量 * @return */
protected int[] readColorTable(int ncolors) {
int nbytes = 3 * ncolors;//一個顏色佔3個字節(r g b 各佔1字節),所以佔用空間爲 顏色數量*3 字節
int[] tab = null;
byte[] c = new byte[nbytes];
int n = 0;
try {
n = in.read(c);
} catch (Exception e) {
e.printStackTrace();
}
if (n < nbytes) {
status = STATUS_FORMAT_ERROR;
} else {//開始解析顏色列表
tab = new int[256];//設置最大尺寸避免邊界檢查
int i = 0;
int j = 0;
while (i < ncolors) {
int r = ((int) c[j++]) & 0xff;
int g = ((int) c[j++]) & 0xff;
int b = ((int) c[j++]) & 0xff;
tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
}
}
return tab;
}
複製代碼
至此readHeader方法咱們就分析完了,接下來分析readContents方法是如何提取GIF圖像的各幀圖片的
咱們先直接觀察readContents方法內部是如何運做的
/** * 讀取圖像塊內容 */
protected void readContents() {
boolean done = false;
while (!(done || err())) {
int code = read();
switch (code) {
//圖象標識符(Image Descriptor)開始
case 0x2C:
readImage();
break;
//擴展塊開始
case 0x21: //擴展塊標識,固定值0x21
code = read();
switch (code) {
case 0xf9: //圖形控制擴展塊標識(Graphic Control Label),固定值0xf9
readGraphicControlExt();
break;
case 0xff: //應用程序擴展塊標識(Application Extension Label),固定值0xFF
readBlock();
String app = "";
for (int i = 0; i < 11; i++) {
app += (char) block[i];
}
if (app.equals("NETSCAPE2.0")) {
readNetscapeExt();
} else {
skip(); // don't care
}
break;
default: //其餘擴展都選擇跳過
skip();
}
break;
case 0x3b://標識GIF文件結束,固定值0x3B
done = true;
break;
case 0x00: //可能會出現的壞字節,可根據須要在此處編寫壞字節分析等相關內容
break;
default:
status = STATUS_FORMAT_ERROR;
}
}
}
複製代碼
readContents()的核心流程就是根據塊的標識來判斷當前解碼的位置,調用相應的方法對數據塊進行解碼。若是GIF版本爲89a,則數據塊中可能含有擴展塊(可選)。其中圖像延遲時間存放在圖形控制擴展(Graphic Control Extension)中,所以咱們重點分析如何讀取圖形控制擴展(Graphic Control Extension)(見下圖),其餘擴展塊解碼你們能夠對照着代碼註釋和GIF結構的相關知識自行研究,這裏就很少贅述了
解碼圖形控制擴展(Graphic Control Extension)的方法爲readGraphicControlExt(),有了上圖對各字節的說明其代碼也就很容易理解了,以下
/** * 讀取圖形控制擴展塊 */
protected void readGraphicControlExt() {
read();//按讀取順序,此處爲塊大小
int packed = read();//讀取處置方法、用戶輸入標誌等
dispose = (packed & 0x1c) >> 2; //從packed中解析出處置方法(Disposal Method)
if (dispose == 0) {
dispose = 1; //elect to keep old image if discretionary
}
transparency = (packed & 1) != 0;//從packed中解析出透明色標誌
delay = readShort() * 10;//讀取延遲時間(毫秒)
transIndex = read();//讀取透明色索引
read();//按讀取順序,此處爲標識塊終結(Block Terminator)
}
複製代碼
GIF中可能含有多個圖像塊,圖像塊包含圖象標識符(Image Descriptor)(見下圖)、局部顏色列表(Local Color Table)(根據局部顏色列表標誌肯定是否存在)以及基於顏色列表的圖象數據(Table-Based Image Data)
readContents()方法中遍歷了全部圖像塊,並調用readImage方法進行解碼,代碼及註釋以下
protected boolean lctFlag;//局部顏色列表標誌(Local Color Table Flag)
protected boolean interlace;//交織標誌(Interlace Flag)
protected int lctSize;//局部顏色列表大小(Size of Local Color Table)
/** * 按順序讀取圖像塊數據: * 圖象標識符(Image Descriptor) * 局部顏色列表(Local Color Table)(有的話) * 基於顏色列表的圖象數據(Table-Based Image Data) */
protected void readImage() {
/** * 開始讀取圖象標識符(Image Descriptor) */
ix = readShort();//x方向偏移量
iy = readShort();//y方向偏移量
iw = readShort();//圖像寬度
ih = readShort();//圖像高度
int packed = read();
lctFlag = (packed & 0x80) != 0;//局部顏色列表標誌(Local Color Table Flag)
interlace = (packed & 0x40) != 0;//交織標誌(Interlace Flag)
// 3 - sort flag
// 4-5 - reserved
lctSize = 2 << (packed & 7);//局部顏色列表大小(Size of Local Color Table)
/** * 開始讀取局部顏色列表(Local Color Table) */
if (lctFlag) {
lct = readColorTable(lctSize);//解碼局部顏色列表
act = lct;//如有局部顏色列表,則圖象數據是基於局部顏色列表的
} else {
act = gct; //不然都以全局顏色列表爲準
if (bgIndex == transIndex) {
bgColor = 0;
}
}
int save = 0;
if (transparency) {
save = act[transIndex];//保存透明色索引位置原來的顏色
act[transIndex] = 0;//根據索引位置設置透明顏色
}
if (act == null) {
status = STATUS_FORMAT_ERROR;//若沒有顏色列表可用,則解碼出錯
}
if (err()) {
return;
}
/** * 開始解碼圖像數據 */
decodeImageData();
skip();
if (err()) {
return;
}
frameCount++;
image = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
setPixels(); //將像素數據轉換爲圖像Bitmap
frames.addElement(new GifFrame(image, delay));//添加到幀圖集合
// list
if (transparency) {
act[transIndex] = save;//重置回原來的顏色
}
resetFrame();
}
複製代碼
readImage方法中分三步進行:讀取圖象標識符(Image Descriptor)、讀取局部顏色列表(Local Color Table)和解碼圖像數據。其中圖像數據是如何解碼並轉換成Bitmap圖像由於太複雜這裏就不詳細展開描述了,之後可能會專門寫個番外篇進行分析,固然小夥伴們也能夠自行閱讀分析這部分源碼:decodeImageData()、setPixels()
至此 GifDecoder就基本分析完了,若是有講解不到位的地方歡迎你們留言指正。若是你們看了感受還不錯麻煩點個贊,大家的支持是我最大的動力~