windowsDDB&DIB

    windows支持兩種位圖格式,DDB(device-dependent bitmap),DIB(device-independent bitmap)。設備相關位圖用於windows顯示系統中,其圖像格式與顯卡格式兼容,因此顯示速度很快。設備不相關位圖定義了位圖的文件格式,用於位圖傳輸,由於其數據格式可能與顯卡格式不一致,直接使用設備不相關位圖顯示圖像時需要進行轉換,因此顯示速度較慢。

    歷史上顯卡支持16色或者256色,分別使用4位或者8位表示一個像素顏色。在16色系統中,僅支持黑白灰,紅綠藍,青品紅黃等基本顏色。在256色系統中,windows保留了20中基本顏色,剩餘236中顏色通過顏色查找表定義。因此,基於以上色彩體系創建的位圖需要設備相關的顏色查找表來解釋真實顏色。

    另外兩種不需要查找表的顏色包括16位色與24位色。使用16位(2位元組)表示紅綠藍,每個顏色分量使用5位(或者綠色使用6位),被稱為 high color。使用24位(3位元組)表示紅綠藍,每個顏色分量使用一個位元組,被稱為 true color。在沒有查找表情況下,位圖紅綠藍分量排列順序可能不一致,因此DDB顯示速度要優於DIB。

   現代顯示器一般都是32位真彩色,可以通過 ::GetDeviceCaps(hdc, BITSPIXEL) 獲得每個像素上的位數,在本機測試值為32,通過 ::GetDeviceCaps(hdc, PLANES) 獲得位面數,一般情況下該值為1。

   windows提供了BITMAP結構體來描述DDB,定義如下: 

    

/* Bitmap Header Definition */
typedef struct tagBITMAP
  {
    LONG        bmType;
    LONG        bmWidth;
    LONG        bmHeight;
    LONG        bmWidthBytes;
    WORD        bmPlanes;
    WORD        bmBitsPixel;
    LPVOID      bmBits;
  } BITMAP, *PBITMAP, NEAR *NPBITMAP, FAR *LPBITMAP;

    其中,bmWidth, bmHeight表示DDB的尺寸,bmPlanes通常為1,bmBitsPixel表示每個像素需要多少位來表示。

    有兩個參數需要特別注意:

     bmBits並不是指向DDB的一個指針,應用程序無法直接操作顯存,多數情況下該指針為空,但可以通過特定函數獲得DDB數據區的一個拷貝;

     bmWidthBytes一般不需要賦值, windows會根據規則計算出正確的值,其規則是位圖每行位元組為2的倍數,一般計算公式為 bmWidthBytes = 2 *((bmWidth * bmBitsPixel + 15) / 16)。

 

    windows提供了兩個基本函數進行DDB繪製,BitBlt 直接將DDB一個區域拷貝到另一個區域中,StretchBlt 引入了縮放模式。

    以下函數塊僅在左上角繪製一條直線段,然後利用 BitBlt 將其拷貝到其他區域,關鍵代碼如下:

    

::MoveToEx(hdc, 0, 0, NULL);
::LineTo(hdc, 10, 10);
for (int y = 50; y < cyClient; y += cySource)
    for (int x = 50; x < cxClient; x += cxSource)
    {
        BitBlt(hdc, x, y, cxSource, cySource, 
                hdc, 0, 0, SRCCOPY);
    }

 

    得到如下顯示效果:

    

    使用 StretchBlt 替代 BitBlt ,結果如下:

    

     代碼如下:

::MoveToEx(hdc, 0, 0, NULL);
::LineTo(hdc, 10, 10);
for (int y = 50; y < cyClient; y += 30)
    for (int x = 50; x < cxClient; x += 30)
    {
        ::StretchBlt(hdc, x, y, 20, 20,
            hdc, 0, 0, 10, 10, SRCCOPY);
    }

     StretchBlt 將10*10區域拷貝到20*20區域,當目標區域與原始區域尺寸不一致時,必然存在插值操作,使用函數 SetStretchBltMode 設置插值模式,windows 定義了如下模式:

     .BLACKONWHITE:位與操作,保留插值區域中最暗的值(縮小時應用該值,放大時直接映射到原始區域中)

     .WHITEONBLACK:位或操作,保留插值區域中最亮的值(縮小時應用該值,放大時直接映射到原始區域中)

     .COLORONCOLOR:抽取冗餘行列,即得到縮小圖像(放大時同樣直接映射到原始區域中)

     .HALFTONE:使用均值作為目標結果(縮小時先平均再採樣,放大時使用插值)

      以上插值模式中,COLORONCOLOR速度最快,HALFTONE速度最慢,BLACKONWHITE與WHITEONBLACK應用在一些特殊場景中,一般使用COLORONCOLOR。

    注意參數 SRCCOPY 定義了拷貝操作運算,該運算針對每個像素做同樣的操作,這是一個三元運算,其操作數包括源像素,目標像素以及模式像素(畫刷),該三元運算包括256中組合,下面僅給出部分重要組合:

     .SRCCOPY: dest = src

     .SRCPAINT: dest = source OR dest

     .SRCAND: dest = source AND dest

     .SRCINVERT: dest = source XOR dest

     .SRCERASE: dest = source AND (NOT dest )

     .NOTSRCCOPY: dest = (NOT source)

    有了以上拷貝模式,很自然會想到是否可以使用某些運算組合實現橢圓圖像繪製,這裡所謂橢圓圖像是指對矩形圖像繪製橢圓區域,典型應用如界面上圓角按鈕。

 

// load image
hBitmapImag = (HBITMAP)LoadImage(hInstance, 
                        TEXT("img.bmp"),
            IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | 
                        LR_CREATEDIBSECTION);

// obtain image size
GetObject(hBitmapImag, sizeof(BITMAP), &bitmap);
cxBitmap = bitmap.bmWidth;
cyBitmap = bitmap.bmHeight;

// Select the original image into a memory DC
hdcMemImag = CreateCompatibleDC(NULL);
SelectObject(hdcMemImag, hBitmapImag);

// Create the monochrome mask bitmap and memory DC
hBitmapMask = CreateBitmap(cxBitmap, cyBitmap, 1, 1, NULL);
hdcMemMask = CreateCompatibleDC(NULL);
SelectObject(hdcMemMask, hBitmapMask);

// Color the mask bitmap black with a white ellipse
SelectObject(hdcMemMask, GetStockObject(BLACK_BRUSH));
Rectangle(hdcMemMask, 0, 0, cxBitmap, cyBitmap);
SelectObject(hdcMemMask, GetStockObject(WHITE_BRUSH));
Ellipse(hdcMemMask, 0, 0, cxBitmap, cyBitmap);

// Mask the original image
BitBlt(hdcMemImag, 0, 0, cxBitmap, cyBitmap,
            hdcMemMask, 0, 0, SRCAND);

// Center image
x = (cxClient - cxBitmap) / 2;
y = (cyClient - cyBitmap) / 2;

// Do the bitblts
SelectObject(hdc, GetStockObject(GRAY_BRUSH));
Rectangle(hdc, 0, 0, cxClient, cyClient);
BitBlt(hdc, x, y, cxBitmap, cyBitmap, hdcMemMask, 0, 0, 0x220326);
BitBlt(hdc, x, y, cxBitmap, cyBitmap, hdcMemImag, 0, 0, SRCPAINT);

DeleteDC(hdcMemImag);
DeleteDC(hdcMemMask);

 

   

 

    以上代碼實現了繪製圖像橢圓區域,下面詳細解讀實現過程:

    1)首先加載DDB圖像,並獲取圖像尺寸

    2)創建內存兼容DC,並將DDB圖像加載到DC

    3)創建與原始圖像尺寸一致的模板圖像,模板圖像為二值圖像,並加載到內存DC中

    4)對模板圖像橢圓區域內設置為1,橢圓區域外設置為0

    5)使用 SRCAND 操作將模板圖像繪製到原圖像中,此時原圖像保留圖像區域內像素值,橢圓區域外被設置為0

    6)為了使圖像居中顯示,計算繪製起點x,y

    7)將整個區域填充為中灰色

    8)使用 0x220326 操作得到一個橢圓中心為黑色,橢圓外圍保持不變的DC,該操作沒有命名,具體執行邏輯為 Dest = Dest AND (Not Source)

    9)使用 SRCPAINT 操作得到最終結果,該操作執行 Dest = Source OR Dest

    windows 同樣提供了一個更加簡便的函數來實現以上功能,使用 PlgBlt 函數可以實現任意自定義區域繪製,同時該函數還可以做圖像變換功能。

     BOOL PlgBlt( HDC hdcDest, CONST POINT * lpPoint, HDC hdcSrc,  int xSrc, int ySrc, int width,

                           int height,  HBITMAP hbmMask,  int xMask,  int yMask);

    參數 hbmMask 為一個二值圖像,作為原始圖像的繪製模板

    參數 hdcSrc 為原始圖像,同時定義了繪製區間

    參數 hdcDest 為目標DC,圖像將安裝指定規則繪製到目標DC上

    參數 lpPoint 包含了三個點,在目標區域上構成了一個平行四邊形,三個點分別對應左上,右上,左下,第四個點(右下)可以根據平行四邊形規則計算得出,

    將原始圖像矩形映射到目標平行四邊形上即構成了一個變換,該變換包括平移,旋轉,縮放,放射變換。

    下面給出 PlgBlt 函數的使用效果:

      

      左邊圖像僅包括平移與縮放,右邊圖像為任意仿射變換,主要代碼如下:

 

POINT pt[3];
pt[0].x = 50;
pt[0].y = 50;
pt[1].x = 300;
pt[1].y = 50;
pt[2].x = 50;
pt[2].y = 300;
PlgBlt(hdc, pt, hdcMemImag, 0, 0, cxBitmap, cyBitmap,
hBitmapMask, 0, 0);

pt[0].x = 350;
pt[0].y = 50;
pt[1].x = 600;
pt[1].y = 0;
pt[2].x = 500;
pt[2].y = 400;
PlgBlt(hdc, pt, hdcMemImag, 0, 0, cxBitmap, cyBitmap, hBitmapMask, 0, 0);

    

   以上是DDB的一些主要內容,windows同樣提供了DIB,其目的是為了圖像文件傳輸與存儲,因為DDB高度依賴設備。

   windows DIB 結構集成自 OS/2,包括以下幾個部分:

    1)文件頭,定義為 BITMAPFILEHEADER,包括文件識別及尺寸等基本信息;

    2)信息頭,OS/2定義為BITMAPCOREHEADER, windows 對其進行擴展,定義為 BITMAPINFOHEADER,在windows中兩者均可使用;

    3)查找表,高彩色或者真彩色不需要查找表;

    4)圖像數據區;

    這裡不詳細解釋所有結構體成員,僅對一些自認為很重要的地方進行說明。

    1)BITMAPCOREHEADER 與 BITMAPINFOHEADER 的成員變量 bcPlanes 與 biPlanes 永遠為1;

    2)BITMAPCOREHEADER 的成員變量 bcBitCount可取值為 1,2,4,8,24,而windows擴展下 BITMAPINFOHEADER的成員變量biBitCount 可取值1,2,4,8,16,24,32;

    3)BITMAPCOREINFO 使用查找表 RGBTRIPLE,而 BITMAPINFO 使用查找表 RGBQUAD;

    4)由於 OS/2 風格定義也被 windows 支持,當拿到一個信息頭結構體後,可以通過 bcSize/biSize 字段來判斷到底是哪種風格位圖,因為兩個結構體尺寸分別為 12,40位元組;

    5)由於結構體在內存中的對齊方式由編譯器確定,不同編譯器可能得到不同大小的結構體,當將圖像結構寫入文件時,會導致無法正確讀取結構體(對齊方式未知),因此需要使用 packed 模式存儲結構體變量;

    6)圖像數據區原點位於左下角(滿足笛卡爾坐標系),而顯示系統原點一般位於左上角,這會導致顯示圖像時上下翻轉;

    7)位圖數據行一般是4位元組整數倍,而DDB要求為兩位元組整數倍,所以當DIB轉換為DDB時自然滿足對齊要求;

    8)biCompression 可取值 BI_RGB,BI_RLE8, BI_RLE4,BI_BITFIELDS,具體解釋如下:

          .1位位圖時,biCompression 取值只能為 BI_RGB;

          .4位位圖時,biCompression  取值可以為 BI_RGB 或者 BI_RLE4, BI_RLE4為行程碼壓縮數據,該壓縮算法直觀簡單,因此編解碼速度會很快,但壓縮率有限;

          .8位位圖時,biCompression  取值可以為 BI_RGB 或者 BI_RLE8, BI_RLE8為行程碼壓縮數據;

          .24位位圖時,biCompression 取值只能為 BI_RGB;

          .16位或者32位位圖時,biCompression 取值可以為 BI_RGB 或者 BI_BITFIELDS

          .只要 biCompression 取值為 BI_RGB,不管16位,24位或者32位位圖,其排列順序均為blue->green->red->alpha(可能沒有),只是16位位圖時每個顏色佔5位,24位與32位位圖時每個顏色佔8位;

          .當 biCompression 取值為 BI_BITFIELDS 時,在 BITMAPCOREHEADER 後緊跟3個DWORD數據作為顏色位標記符,程序根據這3個DWORD數據提取色彩;

    9)顏色查找表(TODO)

    與DDB類似,windows同樣提供了兩個函數用於顯示DIB, SetDIBitsToDevice直接將DIB顯示在DC上, StretchDIBits 提供了縮放顯示,定義如下:

    int SetDIBitsToDevice(_In_ HDC hdc, _In_ int xDest, _In_ int yDest, _In_ DWORD w, _In_ DWORD h, _In_ int xSrc,

                                      _In_ int ySrc, _In_ UINT StartScan, _In_ UINT cLines, _In_ CONST VOID * lpvBits, _In_ CONST BITMAPINFO * lpbmi, _In_ UINT ColorUse);

    int StretchDIBits(_In_ HDC hdc, _In_ int xDest, _In_ int yDest, _In_ int DestWidth, _In_ int DestHeight, _In_ int xSrc, _In_ int ySrc, _In_ int SrcWidth, _In_ int SrcHeight,

                             _In_opt_ CONST VOID * lpBits, _In_ CONST BITMAPINFO * lpbmi, _In_ UINT iUsage, _In_ DWORD rop);

    在實際應用中,直接對DIB顯示更多使用 StretchDIBits 函數進行縮放顯示,那麼是否可以對使用 StretchDIBits  函數完成圖像仿射變換及橢圓顯示呢?可以採用如下方法實現:

    1)直接對DIB圖像數據應用仿射變換;

    2)使用類似位邏輯操作(如SRCPAINT等 )實現指定區域透明顯示功能;

 

    通過結合DDB與DIB,可以利用DDB的速度優勢,以及DIB的靈活性(TODO).

 

    參考資料:Programming Windows by Charles Petzold

Tags: