DIB位圖(Bitmap)的讀取和保存


設備無關位圖(Device Independent Bitmap)是可以保存在磁盤的位圖文件,可以從磁盤讀取到內存或者從內存保存到磁盤上。它的文件結構是標准化的,可以在Windows/Linux/Unix等平台上顯示相同的效果。本文主要介紹了

  1. 如果將位圖文件從磁盤讀到內存中
  2. 在內存中對位圖文件進行操作后,如何將位圖保存到磁盤

1 讀取位圖到內存中

1.1 DIB文件結構

要將位圖文件(.bmp)從磁盤讀取到內存,首先要了解其文件結構。DIB的文件組成有以下4個部分:

  1. 文件表頭,主要包含了文件的類型(必須是BM),文件的大小(所占用的字節數)和位圖的像素矩陣的便宜量。
  2. 信息表頭,包含了兩部分內容:位圖的相關信息(位圖的大小、位深度、位面數、壓縮和編碼等)和指向RGB顏色表(調色盤)的指針。
  3. RGB色彩對照表,也就是調色板,不一定會有。16位及以上直接使用RGB通道表示顏色,一般不需要調色板。
  4. 位圖的像素信息矩陣,表示具體的像素。1,4,8位顏色,保存的是調色板的索引,具體的顏色根據索引在調色板中查找;16位及其以上不使用調色板,直接使用RGB組成像素顏色。

1.2 在Windows下DIB的內存結構

要將DIB數據讀取到內存,就需要在內存中分配相應的空間。Windows提供了幾種結構體,結構體中的字段對應着DIB文件的各個信息值,具體如下
alter

引用自 http://blog.csdn.net/wenzhou1219/article/details/26162869

將DIB讀取到內存只需要將磁盤數據填充到相應到結構體即可。在磁盤上DIB需要連續的結構存儲,在內存中則不需要連續的存儲空間,可以分段將數據讀取到相應的結構體中。
讀取DIB到內存的具體步驟:

  1. 將文件頭信息讀取到BITMAPFILEHEADER結構體中。
  2. 將位圖頭信息讀取到BITMAPINFOHEADER結構體中。
  3. 如果有調色板,則將其信息讀取到RGBQUAD中。
  4. 讀取位圖像素信息到像素矩陣中。
fp.Read(&bmfileHeader, sizeof(BITMAPFILEHEADER)); // 讀取BMP文件頭 
...
//讀取文件信息頭
ret = fp.Read(&bmHeader, sizeof(BITMAPINFOHEADER)); 
...
fp.Read(m_dibBits, GetBodySize());  //讀取像素信息 

1.3 結構體各字段信息

BITMAPFILEHEADER

代表文件頭信息的結構體BITMAPFILEHEADER的聲明如下:

typedef struct tagBITMAPFILEHEADER {
        WORD    bfType;
        DWORD   bfSize;
        WORD    bfReserved1;
        WORD    bfReserved2;
        DWORD   bfOffBits;
} BITMAPFILEHEADER  

其中,

  • bfType是文件類型,該字段必須是BM,如果不是則說明該文件不是DIB。
  • bfSize是位圖文件的大小(字節數)
  • bfReserved1和bfReserved2 是保留字段
  • bfOffBits 從BITMAPFILEHEADER的起始位置到位圖像素的字節偏移量。
BITMAPINFO

在上面提到,位圖信息和位圖的調色板是存放在同一個結構體中的,該結構體就是BITMAPINFO,其聲明如下

typedef struct tagBITMAPINFO {
  BITMAPINFOHEADER bmiHeader;
  RGBQUAD          bmiColors[1];
} BITMAPINFO, *PBITMAPINFO;

bmiHeader是位圖頭信息
bmiColors是調色板

BITMAPINFOHEADER

位圖的頭信息結構BITMAPINFOHEADER,該結構包含了DIB的尺寸和顏色格式等信息,聲明如下

typedef struct tagBITMAPINFOHEADER{
        DWORD      biSize;
        LONG       biWidth;
        LONG       biHeight;
        WORD       biPlanes;
        WORD       biBitCount;
        DWORD      biCompression;
        DWORD      biSizeImage;
        LONG       biXPelsPerMeter;
        LONG       biYPelsPerMeter;
        DWORD      biClrUsed;
        DWORD      biClrImportant;
} BITMAPINFOHEADER  

其中,

  • biSize是該結構體所占用的字節說
  • biWidth DIB的寬(以像素為單位),如果biCompression是BI_JPEG或者BI_PNG,則biWidth是解壓縮后JPEG或者PNG圖像的寬度。
  • biHeight,DIB的高(以像素為單位)。
    • 如果biHeight是正的,則DIB的像素是按照從下往上(bottom-up,也就是像素數組的第一行保存的實際是DIB的最后一行像素值),圖像源點在左下角。
    • 如果biHeight是負的,則DIB的像素是按照從上往下保存的(up-bottom),而且像素的數據是不能被壓縮的其biCompression必須是BI_RGB或者BI_FIELDS
    • 如果biCompression是BI_JPEG或者BI_PNG,則biHeight是解壓縮后JPEG或者PNG的高
  • biPlanes 目標設備的平面數,總是設為1.
  • biBitCount,每個像素所占用的位數。0,表示JPEG或者PNG指定每個像素所占用的位數。還可以是1,4,8,16,24,32。
  • biCompression,數據的壓縮方法(up-down的DIB不能被壓縮),可以是以下值:
    • BI_RGB / BI_FIELDS未被壓縮
    • BI_RLE4 / BI_RLE8 使用游程長度編碼 (RLE,run-length encode)
    • BI_JPEG / BI_PNG 指示該圖像是JPEG或者PNG圖像。
  • biSizeImage 圖像的字節數,對於BI_RGB的DIB其值為0.
  • biXPelsPerMeter / biYPelsPerMeter 顯示該DIB的目標設備所需的分辨率(單位是像素每米)
  • biClrUsed DIB實際使用調色板中的顏色個數,通常為0表示使用調色板中的全部顏色。
  • biClrImportant 顯示DIB所必須的顏色個數,通常為0表示全部顏色都是必須的。

詳細的解釋參見 https://msdn.microsoft.com/en-us/library/dd183376(v=vs.85).aspx

RGBQUAD
typedef struct tagRGBQUAD {
  BYTE rgbBlue;
  BYTE rgbGreen;
  BYTE rgbRed;
  BYTE rgbReserved;
} RGBQUAD;

注意,其存儲順序是BGR。

1.3 DIB的結構實例


是一幅寬和高都是32的位圖(放大后,可以看到表示像素的一個個方塊),其有16種顏色。下面是該圖像的16進制數據

  • 首先是文件的頭信息 BITMAPFILEINFO 共有14個字節(0x00-0x0D),其起始的兩個字節為0x4d42(大端存儲,高位在前)表示文件類型為BM,最后4個字節 0x0076是位圖的像素信息相對於文件頭的偏移量,也就是從0x0076為圖像的像素信息。
  • 下面是位圖的頭信息 BITMAPINFOHEADER共有40個字節(0x0E-0x35),起始的4個字節是0x0028就是該結構體的大小,緊接着的4個字節是圖像的寬0x0020
  • 跟着是圖像的調色板,0x36 - 0x75。調色板共有16種顏色,也就是說有調色板中有16個RGBQUAD,其大小為16 * 4.
  • 最后是位圖的數據 0x76-0x275

1.4 讀取

    CFile fp(dibName, CFile::modeRead | CFile::typeBinary);
    BITMAPFILEHEADER bmfileHeader;
    BITMAPINFOHEADER bmHeader;
    ULONGLONG headpos;
    int paletteSize = 0;
    int ret, cbHeaderSize;
    headpos = fp.GetPosition(); // 獲取文件指針的位置
    ret = fp.Read(&bmfileHeader, sizeof(BITMAPFILEHEADER)); // 讀取BMP文件頭
    if (bmfileHeader.bfType != 0x4d42) //判斷文件類型標頭是不是x4d42,表示該文件為BMP類型文件
    {
        AfxMessageBox(_T("文件不是bmp!"));
        return;
    }
    //讀取文件信息頭
    ret = fp.Read(&bmHeader, sizeof(BITMAPINFOHEADER));
    // 計算RGBQUAD的大小
    switch (bmHeader.biBitCount)
    {
    case 1:
        paletteSize = 2;
        break;
    case 4:
        paletteSize = 16;
        break;
    case 8:
        paletteSize = 256;
        break;
    }
    // 為BITMAPINFO分配存儲空間
    cbHeaderSize = sizeof(BITMAPINFOHEADER)+paletteSize * sizeof(RGBQUAD);
    m_dibInfo = (BITMAPINFO*) new char[cbHeaderSize];
    m_dibInfo->bmiHeader = bmHeader;
    if (paletteSize) //是否有調色板
    {
        ret = fp.Read(&(m_dibInfo->bmiColors[0]), paletteSize * sizeof(RGBQUAD));
        if (ret != int(paletteSize * sizeof(RGBQUAD) ) )
        {
            delete[] m_dibInfo;
            m_dibInfo = NULL;
            return;
        }
    }
    //為像素數組分配存儲空間,大小由GetBodySize決定
    m_dibBits = (void*) new char[GetBodySize()];
    fp.Seek(headpos + bmfileHeader.bfOffBits, CFile::begin); // 將文件指針移動到DIB像素數組
    ret = fp.Read(m_dibBits, GetBodySize());
    if (ret != int(GetBodySize()))
    {
        delete[] m_dibInfo;
        delete[] m_dibBits;
        m_dibInfo = NULL;
        m_dibBits = NULL;
    }
    fp.Close();  

知道了DIB的文件結構后,讀取其到內存還是挺簡單的,需要注意的是DIB的像素數組的大小。由於DIB的寬度需要時4的倍數,不是的話需要填充0將其湊成4的倍數,所以其像素數組的大小不能簡單的width * height * biBitCount / 8,其中biBitCount是每個像素占用的位數。其具體的計算方法如下

  1. 首先計算一行所占用的字節數 bytesPerLine
    bytesPerLine = ((m_dibInfo->bmiHeader.biWidth * m_dibInfo->bmiHeader.biBitCount + 31) / 32 ) * 4
  2. 將bytesPerLine乘以圖像的高
    bytesPerLine * m_dibInfo->bmiHeader.biHeight

1.5 總結

本文主要對DIB的文件結構以及其對應的內存中的結構體做了一個總結,並對一個具體的DIB16進制數據結構進行分析,最后實現了如何將一個DIB數據讀取到內存中。

一直對位圖結構不是很了解,趁着在公司實習沒有具體的工作安排,對DIB的結構作了個總結。至於如何將處理后的位圖數據寫回磁盤文件,在知道位圖結構的情況下,只需要填充相應的結構字段就行了,需要注意的還是位圖的寬需要是4的倍數,不是的話要用0補齊。本來想寫個DEMO的但是太累,明天再說吧。


免責聲明!

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



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