先來用通俗的語句講解位元影像和調色盤的概念。
我們知道,自然界中的所有顏色都可以由紅、綠、藍(R,G,B)三基色組合而成。針對含有紅、綠、藍色成分的多少,可以對其分別分成0~255個等級,而紅、綠、藍的不同組合共有256×256×256種,因此約能表示1600萬種顏色(16m色)。對於人眼而言,這已經是"真彩色"了。這就是24位元影像或其以上的位元影像比如32,,34之類的,,,這種位元影像的映像資料中的每個像素都用了三個位元組來描述記錄它.
什麼是映像資料?接下來會談到一個位元影像檔案結構,它包括檔案頭(用來說明檔案),檔案資訊頭(位元影像屬性),,映像資料(位元影像主體資料所在)
對每個像素進行了(R,G,B)量化的映像就是位元影像,其在電腦中對應檔案的副檔名一般為.bmp。既然用R,G,B的量化值就可以直接記錄一張位元影像的所有像素,那我們需要調色盤幹什麼呢?
首先,我們可以計算完全利用(R,G,B)組合來儲存一個800×600的位元影像所需要的空間為:
800×600×3 = 1440000(位元組)= 1.37M(位元組)
3是記錄每個像素RGB值所用的位元組數,這裡說的是24位元影像,RGB值就用來描述一個像素,,位元影像是由像素組成的,,因此用一張位元影像大小乘它的像素數就可以直接描述一張位元影像
驚人的大!因此,調色盤橫空出世了,它的功能在於緩解位元影像檔案儲存體空間(顯存或系統記憶體)過大的問題。
在win os中存在三種調色盤,,硬體調色盤,邏輯調色盤,系統調色盤,,winos用"調色盤管理器"機制來管理調色盤,,調色盤存在於一個位元影像檔案中,一個表單的DC中,,或OS中,,硬體調色盤就是顯卡適配器所能實際表達的色彩深度,,邏輯調色盤就是winos通過調色盤管理機製為每個表單應用程式DC分配的調色盤(系統調色盤只有一個,而邏輯調色盤可以有多個,,它的本質就是一塊記憶體中的地區用於描述當前應用使用到的調色盤,我們都知道調色盤是一個結構),,所以邏輯調色盤的用途在於類比硬體調色盤,,以使windows作為一個os可以為介面顯示,映像顯示等應用提供它們各自專用的活動的調色盤,,當邏輯調色盤色深小於或大小硬體調色盤時,,winos通過調色盤管理機制自動讓二者諧和,,系統調色盤就是winos當前正在使用到的調色盤,邏輯調色盤可以通過調色盤管理機制轉變為當前系統調色盤,,,但是不管winos的調色盤管理機制如何,,最終的調色盤都要靠硬體調色盤來實現
假設一個位元影像為16色,16色就是上面談到的可以表示多少種顏色,計算一下,2的4次方=16,因此它是4位元影像,我們只需要在映像資料中用4個bit就可以儲存這個位元影像的每個像素在16種顏色中所處的等級,接下來會談到調色盤索引所佔的空間位元組數,,因為它只是一個索引,所佔的空間會比映像資料RGB結構小很多,,,再設其像素總數為800×600(位元影像大小)。然後調色盤提供了這16種等級對應的(R,G,B)值,這樣,存這個16色位元影像只需要:
800×600×4/8(0.5個位元組) = 240000(位元組)= 0.22 M(位元組) 注意:16色圖查詢它在調色盤中的RGB組合所用的索引需要佔用4個BIT,,這是為什嗎?下面解釋一下:
4位2進位數可以表示16種情況,8位BIT可以表示現實中我們使用的十進位的0~255種情況,,
額外的儲存R,G,B表的開銷(即調色盤Palette,也稱為顏色尋找表LUT)僅僅為16×3=48位元組。
儲存空間被大為減少!
常見的位元影像有單色(實際是1位元影像,,2的一次方為2,,因此它是2色圖,,黑白色)、16色(實際是4位元影像,,2的4次方16,,因此它能表示16種顏色)、256色(實際是8位元影像,,這種位元影像的每個像素都用8位剛好一個位元組來表示,2的8次方為256,因此它能表示256種顏色也即256種RGB的組合也即這種位元影像的色深)、16位(2的16次方=65536)及24位(2的24次方=1677萬種顏色和256級灰階值
色深差不了很多,所以效果其實與16位元影像不相差幾多)真彩色5種,對於前三者(即不大於256色)都可以調色盤方式進行儲存,而對16位及24位真彩色以調色盤進行儲存是不划算的,它們直接按照R,G,B分量進行儲存。
在此基礎上我們來分析DDB位元影像(Device-dependent bitmap,與裝置相關的位元影像)與DIB位元影像(Device-independent bitmap,與裝置無關的位元影像)的概念以及二者的區別。
DDB依賴於具體裝置,它只能存在於記憶體中(視頻記憶體或系統記憶體),其顏色模式必須與特定的輸出裝置相一致,使用系統調色盤。一般只能載入色彩較簡單的DDB位元影像,對於顏色較豐富的位元影像,需使用DIB才能長期儲存。
DIB不依賴於具體裝置,可以用來永久性地儲存圖象。DIB一般是以*.BMP檔案的形式儲存在磁碟中的,有時也會儲存在*.DIB檔案中。 DIB位元影像的特點是將顏色資訊儲存在位元影像檔案自身的顏色表中,應用程式要根據此顏色表為DIB建立邏輯調色盤。因此,在輸出一幅DIB位元影像之前,程式應該將其邏輯調色盤選入到相關的裝置上下文並實現到系統調色盤中。
下面再介紹一下位元影像及位元影像檔案的讀寫操作以及具體的對其的編程工作
一、位元影像檔案結構
位元影像檔案由三部分組成:檔案頭 + 位元影像資訊 + 位元影像像素資料
1、位元影像檔案頭。位元影像檔案頭主要用於識別位元影像檔案。以下是位元影像檔案頭結構的定義:
typedef struct tagBITMAPFILEHEADER { // bmfh
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
其中的bfType值應該是“BM”(0x4d42),標誌該檔案是位元影像檔案。bfSize的值是位元影像檔案的大小。
2、位元影像資訊中所記錄的值用於分配記憶體,設定調色盤資訊,讀取像素值等。
以下是位元影像資訊結構的定義:
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[1];
} BITMAPINFO;
可見位元影像資訊也是由兩部分組成的:位元影像資訊頭 + 顏色表
2.1位元影像資訊頭。位元影像資訊頭包含了單個像素所用位元組數以及描述顏色的格式,此外還包括位元影像的寬度、高度、目標裝置的位平面數、映像的壓縮格式。以下是位元影像資訊頭結構的定義:
typedef struct tagBITMAPINFOHEADER{ // bmih
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
下表是對結構體當中各個成員的說明:
結構成員
biSize結構BITMAPINFOHEADER的位元組數,即sizeof(BITMAPINFOHEADER)*
biWidth
以像素為單位的映像寬度*
biHeight
以像素為單位的映像長度*
biplanes
目標裝置的位平面數
biBitCount
每個像素的位元*(1)
biCompression
映像的壓縮格式(這個值幾乎總是為0)
biSizeImage
以位元組為單位的映像資料的大小(對BI_RGB壓縮方式而言)
biXPelsPermeter
水平方向上的每米的像素個數
biYpelsPerMeter
垂直方向上的每米的像素個數
biClrused
調色盤中實際使用的顏色數(2)
biClrImportant
現實位元影像時必須的顏色數(3)
說明:*是需要加以注意的部分,(*的使用是一個行業規定)因為它們是我們在進行位元影像操作時經常參考的變數
(1)對於每個像素的位元組數,分別有一下意義:
0,用在JPEG格式中
1,單色圖,調色盤中含有兩種顏色,也就是我們通常說的黑白圖片
4,16色圖
8,256色圖,通常說的灰階圖
16,64K圖,一般沒有調色盤,映像資料中每兩個位元組表示一個像素,5個或6個位表示一個RGB分量
24,16M真彩色圖,一般沒有調色盤,映像資料中每3個位元組表示一個像素,每個位元組表示一個RGB分量
32,4G真彩色,一般沒有調色盤,每4個位元組表示一個像素,相對24位真彩圖而言,加入了一個透明度,即RGBA模式
(2)這個值通常為0,表示使用biBitCount確定的全部顏色,例外是使用的顏色樹木小於制定的色彩深度的顏色數目的最大值。
(3)這個值通常為0,表示所有的顏色都是必需的
2.2顏色表。顏色表一般是針對16位以下的映像而設定的,對於16位和16位以上的映像,由於其位元影像像素資料中直接對對應像素的RGB(A)顏色進行描述,因而省卻了調色盤。而對於16位一下的映像,由於其位元影像像素資料中記錄的只是調色盤索引值,因而需要根據這個索引到調色盤去取得相應的RGB(A)顏色。顏色表的作用就是建立調色盤。
顏色表是由顏色表項組成的,顏色表項結構的定義如下:
typedef struct tagRGBQUAD { // rgbq
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
其中需要注意的問題是,RGBQUAD結構中的顏色順序是BGR,而不是平常的RGB。
3、位元影像資料。最後,在位元影像檔案頭、位元影像資訊頭、位元影像顏色表之後,便是位元影像的主體部分:位元影像資料。根據不同的位元影像,位元影像資料所佔據的位元組數也是不同的,比如,對於8位位元影像,每個位元組代表了一個像素,對於16位位元影像,每兩個位元組代表了一個像素,對於24位位元影像,每三個位元組代表了一個像素,對於32位位元影像,每四個位元組代表了一個像素。
二、位元影像檔案讀寫操作
認識了位元影像檔案的結構以後,對特定位元影像檔案進行讀寫操作就顯得簡單了。本文附帶的原始碼中包含了一個能夠方便進行位元影像讀寫操作的C++類。以下給出該類的使用參考,對於實現代碼中的關鍵區段做出了講解。
1、類的聲明
class CFG_DIB : public CObject 一個dib類,,封裝了與dib描述與操作有關的所有東東(結構,,函數,變數)
{
public:
//預設建構函式
CFG_DIB();
//建構函式,根據圖象寬和高,以及記錄每個象素所需位元組數來初始化
CFG_DIB(int width, int height, int nBitCounts);
virtual ~CFG_DIB(); //解構函式
public:
HBITMAP m_hBitmap; //注意位元影像檔案檔案頭與位元影像資訊檔頭是不一樣的,,這裡定義了一個指向位元影像的指標
LPBYTE m_lpDIBits; //DIB位的起始位置
LPBITMAPINFOHEADER m_lpBMPHdr; //BITMAPINFOHEADER資訊
LPVOID m_lpvColorTable; //顏色表資訊
HPALETTE m_hPalette; //條調色盤
private:
DWORD m_dwImageSize; //非BITMAPINFOHEADER或BITMAPFILEHEADER的位
int m_nColorEntries; //顏色表項的個數
//顯示參數
public:
CPoint m_Dest; //目的矩形域的左上方座標
CSize m_DestSize; //顯示矩形的寬度和高度
CPoint m_Src; //原矩形左下角座標
CSize m_SrcSize; //原矩形寬度和高度
public:
void InitDestroy(); //初始設定變數
void ComputePaletteSize(int nBitCounts); //計算調色盤大小
void ComputeImage(); //計算圖象大小
//從BMP檔案中讀入DIB資訊
BOOL ReadFile(CFile* pFile);
//從BMP檔案中讀入DIB資訊,與ReadFile不同的是使用CreateSection建立位元影像位
BOOL ReadSection(CFile* pFile, CDC* pDC = NULL);
//將DIB寫入檔案,儲存成BMP圖片格式
BOOL WriteFile(CFile* pFile);
//建立新的位元影像檔案,根據參數width,height,nBitCounts分配記憶體空間
BOOL NewFile(int width, int height, int nBitCounts);
//關閉位元影像檔案
BOOL CloseFile();
//顯示位元影像
BOOL Display(CDC* pDC);
HBITMAP CreateBitmap(CDC* pDC); //用DIB建立DDB
HBITMAP CreateSection(CDC* pDC = NULL); //建立位元影像位元據,即象素資料
//如果DIB沒有顏色表,可以用邏輯調色盤
BOOL SetLogPalette(CDC* pDC);
//如果DIB有顏色表,可以建立系統調色盤 //一個位元影像檔案可以有顏色表也可以沒有
BOOL SetWinPalette();
//把DIB對象的邏輯調色盤選進裝置環境裡,然後實現調色盤
UINT UseLogPalette(CDC* pDC);
//得到BitmapInfoHeader的大小,包含顏色表資料
int GetHeaderSize()
{
return sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * m_nColorEntries;
}
//得到映像的高度
int GetHeight()
{
if(m_lpBMPHdr == NULL) return 0;
return m_lpBMPHdr->biHeight;
}
//得到映像的寬度
int GetWidth()
{
if(m_lpBMPHdr == NULL) return 0;
return m_lpBMPHdr->biWidth;
}
//得到映像的大小
int GetImageSize()
{
return m_dwImageSize;
}
long GetLineBit(); //得到一行的象素數
};
2、位元影像的讀取。
CFG_DIB提供了兩個從位元影像檔案讀取位元影像資料的方法:ReadFile和ReadSection,二者不同之處,前者使用動態分配記憶體的方法初始化儲存位位元影像資料的指標,後者則使用API函數,根據位元影像資訊初始化儲存位元影像資料的指標。
方法1 m_lpDIBits = (LPBYTE) new char[m_dwImageSize];
方法2 m_hBitmap = ::CreateDIBSection(pDC->GetSafeHdc(), (LPBITMAPINFO) m_lpBMPHdr, DIB_RGB_COLORS,
(LPVOID*) &m_lpDIBits, NULL, 0);
3、位元影像讀取過程中的調色盤的建立和調用。
關於調色盤的詳細情況,本文不作詳細介紹,只是對讀取位元影像的過程中需要調用的對調色盤進行操作的相關函數進行說明。
讀取檔案的過程中,計算出調色盤大小,然後調用建立調色盤函數:
ComputePaletteSize(m_lpBMPHdr->biBitCount);
SetWinPalette();
在顯示位元影像之前,設定調色盤:if(m_hPalette != NULL) {
::SelectPalette(pDC->GetSafeHdc(), m_hPalette, TRUE);
}
4、位元影像的顯示。
位元影像的顯示還是調用Windows的API函數來進行,需要傳遞的參數包括當前位元影像資訊頭,位元影像資料等: ::StretchDIBits(pDC->GetSafeHdc(), m_Dest.x, m_Dest.y,
m_DestSize.cx, m_DestSize.cy,
m_Src.x, m_Src.y,
m_SrcSize.cx, m_SrcSize.cy,
m_lpDIBits, (LPBITMAPINFO) m_lpBMPHdr,
DIB_RGB_COLORS, SRCCOPY);
其中的m_Dest,m_DestSize,m_Src,m_SrcSize分別代表了映像在當前裝置上顯示的左上方座標和範圍以及需要顯示的源映像的左下角座標和範圍。此處需要說明的是,位元影像資料的位元組數組是從映像的最下面一行開始逐行想上儲存的,所以使用者在選取源位元影像的現實範圍的時候需要特別注意!
m_Dest,m_DestSize,m_Src,m_SrcSize需要在現實之前設定好。
5、位元影像的儲存。位元影像的儲存用WriteFile實現。
6、新位元影像的建立。新位元影像的建立由NewFile實現。需要的參數是位元影像的寬度、高度、以及位元影像像素佔用的位元。
7、其它問題。存取位元影像資料的位元組數組有個問題需要引起開發人員的注意:位元組數組中每個掃描行的位元組數必需是4的倍數,如果不足要用0補齊。
以下是處理的辦法:DWORD dwBytes = ((DWORD) m_lpBMPHdr->biWidth * m_lpBMPHdr->biBitCount) / 32;
if(((DWORD) m_lpBMPHdr->biWidth * m_lpBMPHdr->biBitCount) % 32) {
dwBytes++;
}
dwBytes *= 4;
m_dwImageSize = dwBytes * m_lpBMPHdr->biHeight;
這段代碼按照要求算出了用於記錄映像資料的位元組數組的大小。
三、CFG_DIB的使用
以下是CFG_DIB的使用範例程式碼。
#include "fg_dib.h"
CFG_DIB m_fgdib;
//new file
m_fgdib.NewFile(width, height, nbitnum);
//open file
CFile* pf;
pf = new CFile;
pf->Open(sFileName, Cfile::modeRead);
m_fgdib.ReadFile(pf);
pf->Close();
delete pf;
//draw BMP
m_fgdib.m_Dest.x = 0;
m_fgdib.m_Dest.y = 0;
m_fgdib.m_DestSize.cx = m_fgdib.GetWidth();
m_fgdib.m_DestSize.cy = m_fgdib.GetHeight();
m_fgdib.m_Src.x = 0;
m_fgdib.m_Src.y = 0;
m_fgdib.m_SrcSize.cx = m_fgdib.GetWidth();
m_fgdib.m_SrcSize.cy = m_fgdib.GetHeight();
CDC* pDC = GetDC();
m_fgdib.Display(pDC);
//close BMP
m_fgdib.CloseFile();