首先要聲明的是,這個4位(16)色圖比較特殊,不是彩色的16色圖,而已一個用4位16色,類比的灰階圖
什麼是灰階圖?
灰階圖是指只含亮度資訊不含彩色資訊的圖象,就像我們平時看到的亮度由暗到明的黑白照片,亮度變化是連續的。因此,要表示
灰階圖,就需要把亮度值進行亮化。通常分成0-255共256個層級,0最暗(全黑),255最亮(全白)。
BMP格式的檔案中並沒有灰階圖這個概念,但是可以很容易的用BMP檔案來表示灰階圖。一般的方法是用256色的調色盤,這個調色盤
每一項的RGB值都是相同的,即從(0,0,0),(1,1,1)一直到(255,255,255),(0,0,0)表示全黑(255,255,255)表示全白
1.BMP位元影像的格式
BMP檔案的結構分為4部分,本文假定讀者都已經瞭解BMP位元影像的格式(幾乎所有教VC的書上多媒體部分都有講,再google一下也很容
易就查得到,這裡主要介紹其中的調色盤,和圖象資料部分。
對於非真彩的位元影像,都有一個調色盤,調色盤的格式如下
typedef struct tagRGBQUAD{
BYTE rgbBlue;//藍色的分量
BYTE rgbGreen;//綠色的分量
BYTE rgbRed;//紅色的分量
BYTE rgbReserved;//保留值不用管它為0就好
}RGBQUAD;
一般的調色版是一個,由上面的結構體組成的結構體數組,儲存具體的顏色資訊,而位元影像中,圖象資料部分儲存的只是調色盤的下標
。這樣做就可以大大的節省空間的。
例如:
RGBQUAD rgb[2];
rgb[0].rgbBlue = 0;
rgb[0].rgbGreen = 0;
rgb[0].rgbRed = 0;
rgb[0].rgbReserved = 0;
rgb[1].rgbBlue = 255;
rgb[1].rgbGreen = 255;
rgb[1].rgbRed = 255;
rgb[1].rgbReserved = 255;
這個長度為2的RGBQUAD數組就是一個1位2色黑白圖的調色盤,
在位元影像資料部分只需要用1位的長度儲存0表示黑,1表示白就可以了,1位元組可以表示8個像素的資訊,比用3位元組直接表示R,G,B節
省了24倍的儲存空間
(一般來說,BMP檔案的資料是從上到下、從左至右的(參考:精通Visual C++數字圖象處理典型演算法及實現,第二版18頁).注意:最先讀出來的應該是B然後是G然後是R)
而真彩圖則不然,比如24位元影像,那麼他就需要一個數組大小為2的24次方的調色盤,而調色盤的下標也需要3個位元組才儲存,這樣還
不如直接就R,G,B這三個分量來直接表示每一個像素的色值。使用調色盤技術還浪費了一個256*256*256*3位元組大的調色盤空間.
而這裡要用4位表示一個灰階圖,那麼它的調色盤只有16項,每一項的RGB值同通常由256色構成的灰階圖的調色盤一樣的道理
這裡這樣建立這個調色盤
RGBQUAD pa[16];
BYTE c;
for(int i=0;i<16;i++)
{
c= i * 17;
pa[i].rgbRed = c;
pa[i].rgbGreen = c;
pa[i].rgbBlue = c;
pa[i].rgbReserved = 0;
}
2.轉換演算法
現在的圖象是24位真彩的,表示它的資料部分,3位元組表示一個像素,這三個位元組分別表示RGB。
我們現在要做的是求每一像素點的RGB值的平均值,然後用16色調色盤中最接近這個顏色亮度的值來表示它。
而4位的圖象是1個位元組表示2個像素,在這裡需要特殊注意
具體演算法實現代碼如下,pBuffer是儲存圖象資料的數組
USHORT R,G,B;
////////////////////////////////////////////////////////////////
// 第一個像素
B = pBuffer[dwIndex++];
G = pBuffer[dwIndex++];
R = pBuffer[dwIndex++];
int maxcolor = (R+G+B)/3;
maxcolor /= 17;//計算在16色調色盤中的下標
//第二個像素
B = pBuffer[dwIndex++];
G = pBuffer[dwIndex++];
R = pBuffer[dwIndex++];
int maxcolor2 = (R+G+B)/3;
maxcolor2 /= 17;
pNew[dwOldIndex++] = ( maxcolor<<4 )| maxcolor2;//合成一個位元組表示兩個像素
3.實現代碼
完整的實現代碼如下
BOOL Convert24To4(LPCTSTR lpszSrcFile, LPCTSTR lpszDestFile)//24->4灰階圖
{
BITMAPFILEHEADER bmHdr; // BMP檔案頭
BITMAPINFOHEADER bmInfo; // BMP檔案資訊
HANDLE hFile, hNewFile;
DWORD dwByteWritten = 0;
// 開啟源檔案控制代碼
hFile = CreateFile(lpszSrcFile,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
return FALSE;
// 建立新檔案
hNewFile = CreateFile(lpszDestFile,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hNewFile == INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
return FALSE;
}
// 讀取源檔案BMP頭和檔案資訊
ReadFile(hFile, &bmHdr, sizeof(bmHdr), &dwByteWritten, NULL);
ReadFile(hFile, &bmInfo, sizeof(bmInfo), &dwByteWritten, NULL);
TRACE("biSize: %d , biWidth: %d , biHeight: %d , biBitCount: %d , biSizeImage: %d
/n",bmInfo.biSize,bmInfo.biWidth,bmInfo.biHeight,bmInfo.biBitCount,bmInfo.biSizeImage);
TRACE("biX: %d , biY: %d , biClrUsed: %d , biClrImportant: %d
/n",bmInfo.biXPelsPerMeter,bmInfo.biYPelsPerMeter,bmInfo.biClrUsed,bmInfo.biClrImportant);
// 只處理24位未壓縮的映像
if (bmInfo.biBitCount != 24 || bmInfo.biCompression!=0)
{
CloseHandle(hNewFile);
CloseHandle(hFile);
DeleteFile(lpszDestFile);
return FALSE;
}
// 計算映像資料大小
DWORD dwOldSize = bmInfo.biSizeImage;
if(dwOldSize == 0) // 重新計算
{
dwOldSize = bmHdr.bfSize - sizeof(bmHdr) - sizeof(bmInfo);
}
TRACE("Old Width: %d , Old Height: %d ,Old Size: %d bytes/n",bmInfo.biWidth,bmInfo.biHeight,dwOldSize);
long wid = bmInfo.biWidth % 4;
if(wid>0)
{
wid = 4 - wid;
}
wid += bmInfo.biWidth;
DWORD dwNewSize;
dwNewSize = wid * bmInfo.biHeight / 2; //計算轉換後新圖象大小
TRACE("New Size: %d bytes/n", dwNewSize);
// 讀取未經處理資料
UCHAR *pBuffer = NULL;
pBuffer = new UCHAR[dwOldSize]; // 申請未經處理資料空間
if(pBuffer == NULL)
{
CloseHandle(hNewFile);
CloseHandle(hFile);
DeleteFile(lpszDestFile);
return FALSE;
}
// 讀取資料
ReadFile(hFile, pBuffer, dwOldSize, &dwByteWritten, NULL);
UCHAR *pNew = new UCHAR[dwNewSize];
UCHAR color = 0;
DWORD dwIndex = 0, dwOldIndex = 0;
while( dwIndex < dwOldSize )//一位元組表示兩個像素
{
USHORT R,G,B;
////////////////////////////////////////////////////////////////
// 第一個像素
B = pBuffer[dwIndex++];
G = pBuffer[dwIndex++];
R = pBuffer[dwIndex++];
int maxcolor = (R+G+B)/3;
maxcolor /= 17;
//第二個像素
B = pBuffer[dwIndex++];
G = pBuffer[dwIndex++];
R = pBuffer[dwIndex++];
int maxcolor2 = (R+G+B)/3;
maxcolor2 /= 17;
pNew[dwOldIndex++] = ( maxcolor<<4 )| maxcolor2;//合成一個位元組表示兩個像素
}
////////////////////////////////////////////////////////////////////////////////
// 完工, 把結果儲存到新檔案中
// 修改屬性
bmHdr.bfSize = sizeof(bmHdr)+sizeof(bmInfo)+sizeof(RGBQUAD)*16+dwNewSize;
bmHdr.bfOffBits = bmHdr.bfSize - dwNewSize;
bmInfo.biBitCount = 4;
bmInfo.biSizeImage = dwNewSize;
// 建立調色盤
RGBQUAD pa[16];
UCHAR c;
for(int i=0;i<16;i++)
{
c= i * 17;
pa[i].rgbRed = c;
pa[i].rgbGreen = c;
pa[i].rgbBlue = c;
pa[i].rgbReserved = 0;
}
// BMP頭
WriteFile(hNewFile, &bmHdr, sizeof(bmHdr), &dwByteWritten, NULL);
// 檔案資訊頭
WriteFile(hNewFile, &bmInfo, sizeof(bmInfo), &dwByteWritten, NULL);
// 調色盤
WriteFile(hNewFile, pa, sizeof(RGBQUAD)*16, &dwByteWritten, NULL);
// 檔案資料
WriteFile(hNewFile, pNew, dwNewSize, &dwByteWritten, NULL);
delete []pBuffer;
delete []pNew;
// 關閉檔案控制代碼
CloseHandle(hNewFile);
CloseHandle(hFile);
return TRUE;
}
4.疑問
既然可以由24位真菜圖轉換為4位灰階圖,那麼一定有一個合適的方法把它轉換成4位彩色圖,而具體的區別就是
調色盤不同(調色盤都要表示哪些顏色),再有最重要的是原來的顏色用現有的16色,哪個表示更合適.