首先,先來看看什麼是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);
重點來了,要學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滴。
廢話不多說,馬上切入我們的正題吧,在這節我們要介紹的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數