Device Independent Bitmap

1. 什麼是DIB?為什麼要學DIB?

    首先,先來看看什麼是DIB,DIB就是Device Independent Bitmap,也就是「裝置無關的點陣圖」。那麼聰明的你一定會想到說,那有沒有所謂的「裝置相關點陣圖」咧?沒錯,那就是DDB(Device Dependent Bitmap)。這麼說明白了嗎?明白了才有鬼,因為前面只是做中英文的對照而已啦,我還沒開始說明所謂「無關」和「相關」所代表的義意,所以各位當然是有聽沒有懂啦。

    所謂「相關」,在很多書上都說明的很模糊,在坊主我的定義來說,就是「顯示模式」和「檔案格式」的關係有互相依賴的,如果兩者沒有配合好的話,畫面就會一團亂。

    所謂「無關」,當然就是指「顯示模式」和「檔案格式」是沒有絕對關係的,沒有配合好也可正常顯示。

    這裡又帶出另外一個問題,就是「顯示模式」和「檔案格式」。「顯示模式」,也就是Display Mode,是目前你的windows所使用的解析度(Resolution)和色彩位元數(Bit Per Pixel)的總稱,如:1024x768x8bpp 指的是Resolution = 1024pixel x 768pixel,而Bit Per Pixel = 8bpp。而因為8bpp是2的8次方,一共有256色,所以也可以表示成1024x768x256。如果是High Color就是16bpp,而True Color就是24或32bpp。

    同樣的,一個圖片在儲存時,也是用這樣方式來儲存。例如,各位如果有ACDSee的話,可以開幾個圖片來看看,看他下面的圖片資料上是否寫著???x???x???。

    而在「檔案」讀入將之「顯示」在畫面上時,如果採用DDB進行貼圖而色彩位元數不配合的話就會一團亂了。這就是為什麼要有DIB了,這樣了解了吧。

    想想,如果沒有DIB的幫忙,我們如果遇到8bpp的圖檔,而要顯示在16bpp的顯示模式的話,光轉換格式寫程式就會寫到快瘋掉了。所以咧,要好好運用DIB的API,尤其是玩DirectDraw的人,更要學會這個東東,要不然老是「硬幹」不幹到吐血才怪咧!

 再者,因為到目前為止,坊主我還沒遇到過有用16bpp存檔的檔案格式,就拿「.bmp」的格式來說,也是從8bpp直接跳到24bpp,沒有所謂16bpp存檔的檔案。而各位如果對3D加速卡有所認知的話,應該知道加速只有加速8bpp,16bpp和32bpp吧。所以說,再猛的Programmer恐怕也很不想「硬幹」出這樣的複雜的圖形轉換工程,對吧。

    這樣說明應該很清楚了,呼……那我先在這裡說一下DDB的API,而DIB的API則在下一節來討論,有興趣的讀者可以用下面的API來秀圖,然後故意把顯示模式調整一下,讓它們不配合,看看是不是有亂掉。(PS.先調整好顯示模示,再跑程式就看得出來了。)

//注意24bit per pixel = 3 Byte per Pixel所以資料緩衝區會多乘上3
char pic_buffer[100*100*3]; //24bpp圖形資料緩衝區,Width=100,Height=100,
HBITMAP hbmp=CreateBitmap(100, 100, 1, 24, pic_buffer);
HDC hComDC=CreateCompatibleDC(NULL);
SelectObject(hComDC, hbmp);
HDC hDC=GetDC(hWnd);
BitBlt(hDC, 0, 0, 100, 100, hComDC, 0, 0, SRCCOPY);
ReleaseDC(hWnd,hDC);
DeleteDC(hComDC);

 

2. DIB Essentials

 重點來了,要學DIB之前要先學一下「.bmp」的檔案格式,雖然說DIB的API所使用的structure正好和「.bmp」的檔案格式相同,當然嚕,因為那都是Microsoft自己弄出來的嘛!而大家千萬別誤會,以為DIB只能秀「.bmp」的圖哦。

    首先,先來一下DIB所要用的這個structure的定義:

typedef struct tagBITMAPINFO { // bmi 
    BITMAPINFOHEADER bmiHeader; 
    RGBQUAD bmiColors[1]; 
} BITMAPINFO;

    其中bmiHeader,它也是另一個structure宣告如下:

typedef struct tagBITMAPINFOHEADER{ // bmih 
    DWORD biSize;             //這個structure所需之byte數
    LONG biWidth;             //圖圖的寬
    LONG biHeight;            //圖圖的高
    WORD biPlanes;            //通常為1
    WORD biBitCount           //也就是Bit Per Pixel
    DWORD biCompression;      //如果沒有壓縮的話,其值為BI_RGB
    DWORD biSizeImage;        //圖圖所佔的大小,以byte表示,如未壓縮,可填0
    LONG biXPelsPerMeter;     //列印時會參考這個值來縮放比例,一般為10000
    LONG biYPelsPerMeter;     //列印時會參考這個值來縮放比例,一般為10000
    DWORD biClrUsed;          //填0,使用最大色彩數
    DWORD biClrImportant;     //填0,每個色彩都很重要
} BITMAPINFOHEADER; 

    而bmiColor是有調色盤(Palette)的顯示模式時才有用到,也就是說包含8bbp及以下的顯示模式有用到,而16bpp,24bpp,及32bpp就沒有所謂的調色盤了。其structure宣告如下:

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

    好了,structure介紹到這裡,知道「.bmp」格式的人一定覺得很熟悉吧,因為「.bmp」檔案格式只是在最前面再加一個BITMAPFILEHEADER,其他的都一樣哦!而關於這些宣告,想了解更清楚的可以去看看MSDN。

    再來,因為使用DIB時,必需要Initiate一塊很大的記憶體來存放圖圖資料,而圖圖資料也不是隨便亂放的哦,要以「.bmp」檔案格式一樣的放法才能正確顯示。所以接下來就來看看最重要一個東東,也就是圖圖的資料儲存。

    如果你有一張很大張的圖,大到可以讓ADCSee等看圖軟體慢慢顯示給你看,你就會發現「.bmp」圖檔和其他圖檔有一個很大的差別。圖圖的構成基本元基為點(pixel),再來是線(scan line),而最後匯集成「面」。而「.bmp」檔案是把圖圖的scan line 倒著放,也就是說,你讀到檔案的第一條scanline,其實是圖圖的最後一條scanline,以此類堆。所以當你有機會看到秀圖軟體以很慢的速度秀bmp圖時,一定會從底部顯示到頂部。至次為什麼要把圖倒著放呢?這……坊主我也想不出任何理由,如果你有空就打電話給Bill Gates問問看吧!

←這是正常的圖圖。 ←存成檔案後,水平scanline會倒著放,變成上下相反,而左右不變的結果。

    還有一點也是相當重要的,在「.bmp」圖檔中,每條scan line的byte數一定是4的倍數。也就是說,如果有一張圖圖,Width=23,而且使用24bpp存檔,則其scan line本來應該是23*3=69bytes per scanline,但因為一定要4的倍數,所以要加上4-(69%4)=3bytes的Padding,而擴充為72bytes per scanline。

    呼……終於結束了基礎的介紹,下一節就更精彩了。看完下一節你就會愛上這個DIB的API滴。

 

3. DIB's Application Programming Interface

    廢話不多說,馬上切入我們的正題吧,在這節我們要介紹的API只有一個,「API不在多,功能強則靈」,而Microsoft Windows所提供的DIB API不多,而其中我所要介紹的是SetDIBitsToDevice(),因為只有它是最重要的。而因為它沒有被包在MFC裡,所以使用MFC的也要來叫用這個API哦!

int SetDIBitsToDevice(
    HDC hdc,                 // 所要畫上去的目標Device Context
    int XDest,               // 目標左上角x座標
    int YDest,               // 目標左上角y座標
    DWORD dwWidth,           // 來源圖圖寬度
    DWORD dwHeight,          // 來源圖圖高度
    int XSrc,                // 來源圖圖所要畫上去的左上角x座標
    int YSrc,                // 來源圖圖所要畫上去的左上角y座標
    UINT uStartScan,         // 圖圖陣列中第一條scanline所在的位置,一般為0
    UINT cScanLines,         // scanline的個數,一般來說等於圖圖高度
    CONST VOID *lpvBits,     // 圖圖資料陣列,記得要initial過
    CONST BITMAPINFO *lpbmi, // 圖圖資料structure,記得要initial過
    UINT fuColorUse          // 一般設為DIB_RGB_COLORS
);

 嗯嗯,打完收工,再來就是實作的部分了,看看我如何在Document/View下做DIB吧。

 首先是Document的部分:

void CDIBtestDoc::Serialize(CArchive& ar)
{
    int i;
    if (ar.IsStoring())
    {
        // TODO: add storing code here
    }
    else
    {
        // TODO: add loading code here
        if(
lpBuffer!=NULL)
            delete[]
lpBuffer;
        if(
lpBmpBuf!=NULL)
            delete[]
lpBmpBuf;
        ar.Read(&
imghead,sizeof(IMGHEAD));
        switch(
imghead.dwBitPerPixel)
        {
        case 8:
           
lpPalette=new PALETTE[256];
            ar.Read(
lpPalette,256*sizeof(PALETTE));
           
lpBuffer=new char[imghead.dwWidth*imghead.dwHeight];
            ar.Read(
lpBuffer,imghead.dwWidth*imghead.dwHeight);
           
bmi=(BITMAPINFO *)new char[sizeof(BITMAPINFOHEADER)+sizeof(RGBQUAD)*256];
           
bmi->bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
           
bmi->bmiHeader.biWidth=(long)imghead.dwWidth;
           
bmi->bmiHeader.biHeight=(long)imghead.dwHeight;
           
bmi->bmiHeader.biPlanes=1;
           
bmi->bmiHeader.biBitCount=(WORD)imghead.dwBitPerPixel;
           
bmi->bmiHeader.biCompression=imghead.dwCompression;
           
bmi->bmiHeader.biSizeImage=imghead.dwWidth*imghead.dwHeight;
           
bmi->bmiHeader.biXPelsPerMeter=10000;
           
bmi->bmiHeader.biYPelsPerMeter=10000;
           
bmi->bmiHeader.biClrUsed=0;
           
bmi->bmiHeader.biClrImportant=0;
            for(i=0;i<256;i++)
            {
               
bmi->bmiColors[i].rgbBlue=lpPalette[i].Blue;
               
bmi->bmiColors[i].rgbGreen=lpPalette[i].Green;
               
bmi->bmiColors[i].rgbRed=lpPalette[i].Red;
               
bmi->bmiColors[i].rgbReserved=0;
            }
            break;
        case 16:
        case 24:
        case 32:
           
lpPalette=NULL;
           
lpBuffer=new char[imghead.dwWidth*imghead.dwHeight*imghead.dwBitPerPixel/8];
            ar.Read(
lpBuffer,imghead.dwWidth*imghead.dwHeight*imghead.dwBitPerPixel/8);
           
bmi=(BITMAPINFO *)new char[sizeof(BITMAPINFOHEADER)];
           
bmi->bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
           
bmi->bmiHeader.biWidth=(long)imghead.dwWidth;
           
bmi->bmiHeader.biHeight=(long)imghead.dwHeight;
           
bmi->bmiHeader.biPlanes=1;
           
bmi->bmiHeader.biBitCount=(WORD)imghead.dwBitPerPixel;
           
bmi->bmiHeader.biCompression=imghead.dwCompression;
           
bmi->bmiHeader.biSizeImage=imghead.dwWidth*imghead.dwHeight*imghead.dwBitPerPixel/8;
           
bmi->bmiHeader.biXPelsPerMeter=10000;
           
bmi->bmiHeader.biYPelsPerMeter=10000;
           
bmi->bmiHeader.biClrUsed=0;
           
bmi->bmiHeader.biClrImportant=0;
            break;
        }
        // Convert to Windows .bmp order
       
Redundancy=(4-(imghead.dwWidth*imghead.dwBitPerPixel/8)%4)%4;
       
lpBmpBuf=new char [(imghead.dwWidth*imghead.dwBitPerPixel/8+Redundancy)*imghead.dwHeight];
        for(DWORD i=0;i<
imghead.dwHeight;i++)
        {
            MoveMemory(
lpBmpBuf+(imghead.dwHeight-(i+1))*
                (
imghead.dwWidth*imghead.dwBitPerPixel/8+Redundancy),
               
lpBuffer+i*imghead.dwWidth*imghead.dwBitPerPixel/8,
               
imghead.dwWidth*imghead.dwBitPerPixel/8);
        }
    }
}

 再來是View的部分:

void CDIBtestView::OnDraw(CDC* pDC)
{
    CDIBtestDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    // TODO: add draw code for native data here
    if(pDoc->
lpBmpBuf!=NULL)
    {
        SetScrollSizes(MM_TEXT,CSize(pDoc->
imghead.dwWidth,pDoc->imghead.dwHeight));
        SetDIBitsToDevice(pDC->m_hDC,0,0,
        pDoc->
imghead.dwWidth,pDoc->imghead.dwHeight,0,0,
            0,pDoc->
imghead.dwHeight,pDoc->lpBmpBuf,pDoc->bmi,DIB_RGB_COLORS);
    }
}

    其中綠色字體為我宣告的變數,列舉如下:

typedef struct _IMGHEAD
{
char CopyRight[20];
DWORD dwWidth;
DWORD dwHeight;
DWORD dwBitPerPixel;
DWORD dwCompression;
DWORD dwPaletteOffset;
DWORD dwBufferOffset;
DWORD dwReserved1;
DWORD dwReserved2;
}IMGHEAD, *LPIMGHEAD;

typedef struct _PALETTE
{
BYTE Red;
BYTE Green;
BYTE Blue;
} PALETTE;

BITMAPINFO CDIBtestDoc::bmi;      //存放bmp structure
IMGHEAD CDIBtestDoc::imghead;     //存放self-define picture file structure
PALETTE *CDIBtestDoc::lpPalette;  //存放self-define palette file structure
char *CDIBtestDoc::lpBmpBuf;      //存放bmp 圖形陣列
char *CDIBtestDoc::lpBuffer;      //存放self-define 圖形陣列
long CDIBtestDoc::Redundancy      // Scan Line 的Padding數