圖像解碼之三——giflib解碼gif圖片


前面已經介紹過了libjpeg解碼jpeg圖片和libpng解碼png圖片,本文將會介紹怎樣用giflib解碼gif圖片。giflib可以在這里下載

gif文件格式簡單介紹

    在解碼jpeg圖片和png圖片的時候我們不需要對jpeg和png文件格式有了解就可以解碼了(了解jpeg和png當然更好),但是在使用giflib解碼gif的時候,我們必須要對gif文件有很簡單的了解。

    gif文件中可以存放一幀或者多幀圖像數據,並且可以存放圖像控制信息,因此可以存儲動畫圖片。

    gif文件由文件頭開頭,文件尾結尾,中間是一些連續的數據塊(block)。這些數據塊又分為圖像數據塊和擴展數據塊(extension),圖像數據塊可以理解成存放一幀的圖像數據。擴展數據塊存放的是一些輔助信息,比如指示怎樣顯示圖像數據等等。

    gif文件中的圖像基於調色板的,因此一張gif文件中的圖像最多只能有255中顏色,因此gif文件只能存儲比較簡單的圖像。gif文件中有兩種調色板 ——全局調色板和圖像局部調色板。當一幀圖像有局部調色板時,則以局部調色板來解碼該幀圖像,如果該幀圖像沒有局部調色板則用全局調色板來解碼該圖像。

    更詳細的信息可以查閱giflib的文檔中的gif89.txt文件,或者在網絡搜索相關的信息。

giflib中數據類型

    在giflib中最重要的數據類型為GifFileType,定義如下:

   1: typedef struct GifFileType {
   2:     int SWidth, SHeight,                   /* Screen dimensions. */
   3:     SColorResolution,          /* How many colors can we generate? */
   4:     SBackGroundColor;        /* I hope you understand this one... */
   5:     ColorMapObject *SColorMap;                  /* NULL if not exists. */
   6:     int ImageCount;                  /* Number of current image */
   7:     GifImageDesc Image;               /* Block describing current image */
   8:     struct SavedImage *SavedImages;    /* Use this to accumulate file state */
   9:     VoidPtr Private;      /* The regular user should not mess with this one! */
  10: } GifFileType

    以上代碼來自gif_lib.h文件,使用giflib庫解碼gif文件都需要包含這個文件。

1、以S開頭的變量標識屏幕(Screen)。SaveImages變量用來存儲已經讀取過得圖像數據。

2、Private變量用來保存giflib私有數據,用戶不應該訪問該變量。

3、其他變量都標識當前圖像。

初始化giflib

    初四化giflib比較簡單,只需要打開相應的gif數據就可以了。giflib和libpng一樣提供了兩種打開源數據的方式,一種是以文件流方式打開gif文件,另外一種用戶可以自定義輸入回調函數給giflib來完成初始化。

    文件流初始化giflib的代碼如下:

   1: if ((GifFile = DGifOpenFileName(*FileName)) == NULL) {
   2:     PrintGifError();
   3:     exit(EXIT_FAILURE);
   4: }

    也可以以文件句柄方式初始化giflib,例如:

   1: if ((GifFile = DGifOpenFileHandle(0)) == NULL) {
   2:     PrintGifError();
   3:     exit(EXIT_FAILURE);
   4: }

    重點介紹一下怎樣用自定義輸入回調函數來初始化giflib,因為這個可以適配各式各樣的數據輸入方式比如網絡等,參考代碼如下:

   1: if ((GifFile = DGifOpen(&gif, gif_input_cb)) == NULL) {
   2:     PrintGifError();
   3:     exit(EXIT_FAILURE);
   4: }

    gif_input_cb為自定義輸入回調函數,該函數負責giflib的數據輸入。

    我們另外需要注意以上三個函數都返回一個GifFileType類型的指針,該指針以后在調用giflib的函數時,用作第一個參數傳入。

初始化屏幕

    所有的gif圖像共享一個屏幕(Screen),這個屏幕和我們的電腦屏幕不同,只是一個邏輯概念。所有的圖像都會繪制到屏幕上面。

    首先我們需要給屏幕分配內存:

   1: if ((ScreenBuffer = (GifRowType *)
   2:         malloc(GifFile->SHeight * sizeof(GifRowType *))) == NULL)
   3:         GIF_EXIT("Failed to allocate memory required, aborted.");

    另外我們需要以背景顏色(GifFile->SBackGroundColor)初始化屏幕buffer。

   1: Size = GifFile->SWidth * sizeof(GifPixelType);/* Size in bytes one row.*/
   2: if ((ScreenBuffer[0] = (GifRowType) malloc(Size)) == NULL) /* First row. */
   3:     GIF_EXIT("Failed to allocate memory required, aborted.");
   4:  
   5: for (i = 0; i < GifFile->SWidth; i++)  /* Set its color to BackGround. */
   6:     ScreenBuffer[0][i] = GifFile->SBackGroundColor;
   7: for (i = 1; i < GifFile->SHeight; i++) {
   8:     /* Allocate the other rows, and set their color to background too: */
   9:     if ((ScreenBuffer[i] = (GifRowType) malloc(Size)) == NULL)
  10:         GIF_EXIT("Failed to allocate memory required, aborted.");
  11:  
  12:     memcpy(ScreenBuffer[i], ScreenBuffer[0], Size);
  13: }

解碼gif數據

    我們上面已經提到gif數據是以順序存放的塊來存儲的,DGifGetRecordType函數用來獲取下一塊數據的類型。因此解碼gif數據的代碼組織如下:

   1: do {
   2:     if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
   3:         PrintGifError();
   4:         exit(EXIT_FAILURE);
   5:     }
   6:     
   7:     switch (RecordType) {
   8:         case IMAGE_DESC_RECORD_TYPE:
   9:             break;
  10:         case EXTENSION_RECORD_TYPE:
  11:             break;
  12:         case TERMINATE_RECORD_TYPE:
  13:             break;
  14:         default:            /* Should be traps by DGifGetRecordType. */
  15:             break;
  16:     }
  17: } while (RecordType != TERMINATE_RECORD_TYPE);

    循環解析gif數據,並根據不同的類型進行不同的處理。

處理圖像數據

    首先先介紹怎樣處理IMAGE_DESC_RECORD_TYPE類型的數據。這代表這是一個圖像數據塊,這個圖像數據需要繪制到前面提到的屏幕buffer上面,相應的代碼如下:

   1: if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
   2:     PrintGifError();
   3:     exit(EXIT_FAILURE);
   4: }
   5: Row = GifFile->Image.Top; /* Image Position relative to Screen. */
   6: Col = GifFile->Image.Left;
   7: Width = GifFile->Image.Width;
   8: Height = GifFile->Image.Height;
   9: GifQprintf("\n%s: Image %d at (%d, %d) [%dx%d]:     ",
  10:     PROGRAM_NAME, ++ImageNum, Col, Row, Width, Height);
  11: if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
  12:    GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
  13:     fprintf(stderr, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
  14:     exit(EXIT_FAILURE);
  15: }
  16: if (GifFile->Image.Interlace) {
  17:     /* Need to perform 4 passes on the images: */
  18:     for (Count = i = 0; i < 4; i++)
  19:         for (j = Row + InterlacedOffset[i]; j < Row + Height;
  20:                      j += InterlacedJumps[i]) {
  21:             GifQprintf("\b\b\b\b%-4d", Count++);
  22:             if (DGifGetLine(GifFile, &ScreenBuffer[j][Col], Width) == GIF_ERROR) {
  23:                 PrintGifError();
  24:                 exit(EXIT_FAILURE);
  25:             }
  26:         }
  27: }
  28: else {
  29:     for (i = 0; i < Height; i++) {
  30:         GifQprintf("\b\b\b\b%-4d", i);
  31:         if (DGifGetLine(GifFile, &ScreenBuffer[Row++][Col],
  32:             Width) == GIF_ERROR) {
  33:             PrintGifError();
  34:             exit(EXIT_FAILURE);
  35:         }
  36:     }
  37: }
  38:  
  39: /* Get the color map */
  40: ColorMap = (GifFile->Image.ColorMap
  41:     ? GifFile->Image.ColorMap
  42:     : GifFile->SColorMap);
  43: if (ColorMap == NULL) {
  44:     fprintf(stderr, "Gif Image does not have a colormap\n");
  45:     exit(EXIT_FAILURE);
  46: }
  47:  
  48: DumpScreen2RGB(OutFileName, OneFileFlag,
  49:        ScreenBuffer, GifFile->SWidth, GifFile->SHeight);

    這里面有幾點需要注意的是:

1、gif數據交織處理,參照上面的代碼。

2、另外注意調色板的選擇,如果當前圖像數據中有局部調色板就用局部調色板來解碼數據,否則用全局調色板來解碼數據。

3、屏幕數據的解碼,根據你的顯示要求選擇輸出格式。

    解析屏幕數據,假設需要把數據轉換成ARGB8888的格式,代碼如下:

   1: static void DumpScreen2RGBA(UINT8* grb_buffer, GifRowType *ScreenBuffer, int ScreenWidth, int ScreenHeight)
   2: {
   3:     int i, j;
   4:     GifRowType GifRow;
   5:     static GifColorType *ColorMapEntry;
   6:     unsigned char *BufferP;
   7:  
   8:     for (i = 0; i < ScreenHeight; i++) {
   9:         GifRow = ScreenBuffer[i];
  10:         BufferP = grb_buffer + i * (ScreenWidth * 4);
  11:         for (j = 0; j < ScreenWidth; j++) {
  12:             ColorMapEntry = &ColorMap->Colors[GifRow[j]];
  13:             *BufferP++ = ColorMapEntry->Blue;
  14:             *BufferP++ = ColorMapEntry->Green;
  15:             *BufferP++ = ColorMapEntry->Red;
  16:             *BufferP++ = 0xff;
  17:         }
  18:     }
  19: }

    解析屏幕數據的時候需要住處理透明色。這里先埋一個伏筆,等介紹完擴展塊,再來重新實現這個函數。

處理擴展塊

    上面提到擴展塊主要實現一些輔助功能,擴展塊影響其后的圖像數據解碼。這里面比較重要的擴展塊是圖像控制擴展塊(Graphic control extension)。可以參考giflib文檔中的gif89.txt文件了解圖像控制擴展塊的詳細內容。這個擴展塊中有兩個內容我們比較關心:

延時時間(delay time)——后面的圖像延時多長時間再顯示,如果解碼線程不是主線程的話,可以在這里延時一下再處理后面的數據。

透明色(transparent color)——在后面的圖像解碼時,遇到同樣的顏色值,則跳過不解碼,繼續處理后續的點。

    如下是一種可供參考的處理方式:

   1: UINT32 delay = 0;
   2: if( ExtCode == GIF_CONTROL_EXT_CODE 
   3:     && Extension[0] == GIF_CONTROL_EXT_SIZE) {
   4:     delay = (Extension[3] << 8 | Extension[2]) * 10;
   5:     /* Can sleep here */
   6: }
   7:  
   8: /* handle transparent color */
   9: if( (Extension[1] & 1) == 1 ) {
  10:     trans_color = Extension[4];
  11: }
  12: else
  13:     trans_color = -1;

    這里GIF_CONTROL_EXT_CODE為0xF9表明該擴展塊是一個圖像控制擴展塊,GIF_CONTROL_EXT_SIZE為4,圖像控制擴 展塊的大小。我們可以看到解析出delay信息之后,就地delay。解析出透明顏色值之后,則標識透明色,否則標識為-1。解析圖片的時候可以根據透明 色的值進行相應的處理,參考如下解析圖像的函數:

   1: static void DumpScreen2RGBA(UINT8* grb_buffer, GifRowType *ScreenBuffer, int ScreenWidth, int ScreenHeight)
   2: {
   3:     int i, j;
   4:     GifRowType GifRow;
   5:     static GifColorType *ColorMapEntry;
   6:     unsigned char *BufferP;
   7:  
   8:     for (i = 0; i < ScreenHeight; i++) {
   9:         GifRow = ScreenBuffer[i];
  10:         BufferP = grb_buffer + i * (ScreenWidth * 4);
  11:         for (j = 0; j < ScreenWidth; j++) {
  12:             if( trans_color != -1 && trans_color == GifRow[j] ) {
  13:                  BufferP += 4;
  14:                  continue;
  15:               }   
  16:  
  17:             ColorMapEntry = &ColorMap->Colors[GifRow[j]];
  18:             *BufferP++ = ColorMapEntry->Blue;
  19:             *BufferP++ = ColorMapEntry->Green;
  20:             *BufferP++ = ColorMapEntry->Red;
  21:             *BufferP++ = 0xff;
  22:         }
  23:     }
  24: }

    注意我們這里假設grb_buffer已經正確的初始化,如果他是垃圾數據,那么得到結果肯定是錯誤的。

    至此gif文件解析完成了,gif圖片和gif動畫都可以正確的解析,並顯示了。

總結

    從上面的情況我們看出如果想使用giflib還是需要對gif格式有一個簡單的了解,這點要求比libjpeg和libpng要求要高了一些。至此圖像處理系列已經完成,歡迎大家批評指正。

原文


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM