24位點陣圖轉4位彩色圖(BMP)
之前的“24位點陣圖轉4位灰度圖”中已經說明了,調色盤與圖象資料格式。
這裡對圖象資料格式做下補充,並講解24位點陣圖轉4位彩色圖的演算法
1.圖象資料格式
在我完成這個演算法的編碼時,執行效果有一個非常嚴重的錯誤,就是所有的藍和紅色反了。也就是說,應該是藍色的地方呈現了紅色,應該是紅色的地方呈現了蘭色。
我的分析為:因為一般來說,BMP檔案的資料是從上到下、從左到右的(參考:精通Visual C++數字圖象處理典型演算法及實現,第二版18頁).
所以我們在快取中
R = pBuffer[dwIndex++];
G = pBuffer[dwIndex++];
B = pBuffer[dwIndex++];
這樣度取RGB,實際上是不對的,這樣就把R和B讀反了,最先讀出來的應該是B然後是G然後是R。
通過實驗,我把整個螢幕都弄成藍色(非純藍)的然後截圖,然後把按RGB順序讀取截圖的數值輸入到檔案中,之後在WINDOWS 的調色盤中,輸入引數RGB觀察顏色,發現呈現出的顏色是某種暗綠色,而將R與B調換之後,呈現出的顏色正是我螢幕的顏色。這樣就更證明了我的假設。當我把所有度曲顏色的R與B調換之後,非常完美,完全呈現了正確的顏色。
B = pBuffer[dwIndex++];
G = pBuffer[dwIndex++];
R = pBuffer[dwIndex++];
2.轉換演算法
首先來確定一下,4位16色,到底有哪16色。
首先R,G,B各三種顏色,再有R,G,B兩兩組合又有三種顏色。
再就是以上的六種顏色有深淺之分,這就12種顏色
例如(255,0,0)淺紅
(128,0,0)深紅
然後
(0,0,0)黑色
(64,64,64)深灰
(128,128,128)淺灰
(255,255,255)白色
這是一共16種顏色。
我是這樣想的,在顏色中除了灰度的顏色,其他的所有顏色的R,G,B的值只有0,128,255三種。
由這三個值組成的所有組合一共27個,分別如下
0 0 0 0
1 0 0 128
2 0 0 255
3 0 128 0
4 0 128 128
5 0 128 255
6 0 255 0
7 0 255 128
8 0 255 255
9 128 0 0
10 128 0 128
11 128 0 255
12 128 128 0
13 128 128 128
14 128 128 255
15 128 255 0
16 128 255 128
17 128 255 255
18 255 0 0
19 255 0 128
20 255 0 255
21 255 128 0
22 255 128 128
23 255 128 255
24 255 255 0
25 255 255 128
26 255 255 255
按照這個循序排列有一個好處。就是如果我知道(R,G,B )的值就能通過公式計算找到他在陣列中的位置。
例如(128,255,255)
第一個數是R=128則這組顏色的標號一定是9-17這一組中,
G=255,那麼這個顏色的標號一定是9-17這組中的第7-9個
B=255,那麼這個顏色的標號一定是9-17這組中的第7-9個的第3個
另p表示顏色的標號,那麼
if(R==0) p = 0;
else if(R==128) p = 9;
else if(R==255) p = 18;
if(G==0) p += 0;
else if(G==128) p += 3;
else if(G==255) p += 6;
if(B==0) p += 0;
else if(B==128) p += 1;
else if(B==255) p += 2;
這樣p最後得到的就是顏色的標號.
具體程式碼如下
int GetR(UCHAR R)
{
if(R==0)
return 0;
if(R==128)
return 9;
if(R==255)
return 18;
}
int GetG(UCHAR G)
{
if(G==0)
return 0;
if(G==128)
return 3;
if(G==255)
return 6;
}
int GetB(UCHAR B)
{
if(B==0)
return 0;
if(B==128)
return 1;
if(B==255)
return 2;
}
int p = GetR(R) + GetG(G) + GetB(B);
為什麼要把這27種顏色標號呢??因為這27種顏色中我們只需要12種(黑色,灰色,白色另做處理,標號中保留這三種顏色是為了方便公式計算),我的想法是先將一種顏色轉換成這27種顏色中的一種,然後在看看這種顏色和那12種顏色中哪個最接近。
建立如下16色調色盤
void SetRGB(RGBQUAD &pa,UCHAR R,UCHAR G,UCHAR B)
{
pa.rgbRed = R;
pa.rgbGreen = G;
pa.rgbBlue = B;
pa.rgbReserved = 0;
}
// 建立調色盤
RGBQUAD pa[16];
SetRGB(pa[0],0,0,0);
SetRGB(pa[1],0,0,128);
SetRGB(pa[2],0,0,255);
SetRGB(pa[3],0,128,0);
SetRGB(pa[4],0,128,128);
SetRGB(pa[5],0,255,0);
SetRGB(pa[6],0,255,255);
SetRGB(pa[7],128,0,0);
SetRGB(pa[8],128,0,128);
SetRGB(pa[9],128,128,0);
SetRGB(pa[10],128,128,128);
SetRGB(pa[11],255,255,255);
SetRGB(pa[12],255,0,0);
SetRGB(pa[13],255,0,255);
SetRGB(pa[14],255,255,0);
SetRGB(pa[15],64,64,64);
按照這個順序排列16色,那麼我27色的陣列中儲存的就是這27種顏色對應這16色的標號
例如27色中的(0,0,0)就對應16色中的(0,0,0)所以這個27色陣列中color[0]=0;
而(0,128,255)這種顏色,通過調色盤比較,這個顏色近似(0,128,128)這樣沒有丟失顏色只是顏色更深一些。
通過這種比較得出這27色的陣列為
int color[27] = {0,1,2,3,4,4,5,4,6,7,8,8,9,10,10,9,10,6,12,8,13,9,10,13,14,14,11};
現在來總結下如何從一個顏色得到的這27色。
我實驗了一下,發現一個顏色的R,G,B三個數的方差小於20(近似數,只是眼睛的觀察取得和計算方差得到這個數值,沒有數學證明依據,如果哪位數學大牛能用數學證明出一個數更合適,希望可以分享一下)時,這個顏色很接近灰度色。再根據平均值判斷下更接近0,64,128,255哪個。
如果不是灰度色,我的原則是突出重要顏色,忽略次要顏色。
例如0 63 240平均值是101,方差是101.597顯然它不是灰度色,
而主要顏色是B=240,重要顏色向高進,次要顏色向低舍
這樣這個顏色就近似成(0,0,255)在27色陣列中p = 0 + 0 + 2 = 2; 在16色中標號color[2] = 2;
再例如(70,140,200)平均數為136近似成(0,255,255)color[p = 0 + 6 + 2] = 6
(100,129,200)平均數為143近似為(0,128,255) color[p = 0 + 3 + 2] = 4(0,128,128)
具體程式碼如下
UCHAR GetL(UCHAR C)//忽略次要顏色,向低舍
{
if(C>=0&&C<128)
{
return 0;
}
if(C>=128&&C<255)
return 128;
return 255;
}
UCHAR GetH(UCHAR C)//重要顏色,向高進
{
if(C>0&&C<=128)
{
return 128;
}
if(C>128&&C<=255)
return 255;
return 0;
}
/**********
*計算標記*
***********/
int GetR(UCHAR R)
{
if(R==0)
return 0;
if(R==128)
return 9;
if(R==255)
return 18;
}
int GetG(UCHAR G)
{
if(G==0)
return 0;
if(G==128)
return 3;
if(G==255)
return 6;
}
int GetB(UCHAR B)
{
if(B==0)
return 0;
if(B==128)
return 1;
if(B==255)
return 2;
}
/******************************
*獲取更接近的灰度,通過平均值*
*******************************/
int GetGray(double ave)
{
int t1,t2;
if(ave>=0&&ave<=64)
{
t1 = ave - 0;
t2 = 128 - ave;
if(t1<t2)
return 0;
return 64;
}
if(ave>64&&ave<=128)
{
t1 = ave - 64;
t2 = 128 - ave;
if(t1<t2)
return 64;
return 128;
}
t1 = ave - 128;
t2 = 255 - ave;
if(t1<t2)
return 128;
return 255;
}
int GetColor(double ave,UCHAR C)//返回一個顏色近似後的值
{
if(C>ave)//突出主要顏色
return GetH(C);
return GetL(C);//忽略次要顏色
}
int GetRGB(UCHAR R,UCHAR G,UCHAR B)//返回調色盤中的標號
{
int color[27] = {0,1,2,3,4,4,5,4,6,7,8,8,9,10,10,9,10,6,12,8,13,9,10,13,14,14,11};
//計算平均值
double ave = (R + G + B)/3;
//計算方差
double var = ((R-ave)*(R-ave)+(G-ave)*(G-ave)+(B-ave)*(B-ave))/3.0;
var = sqrt(var);
if(var<=20)//判斷是否為灰度色
{
switch(GetGray(ave))
{
case 0: return 0;
case 64: return 15;
case 128: return 10;
case 255: return 11;
}
}
R = GetColor(ave,R);
G = GetColor(ave,G);
B = GetColor(ave,B);
int p = GetR(R) + GetG(G) + GetB(B);
return color[p];
}
3.具體程式碼
BOOL Convert24To4Cai(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];
DWORD dwIndex = 0, dwOldIndex = 0;
while( dwIndex < dwOldSize )//一位元組表示兩個畫素
{
USHORT R,G,B;
////////////////////////////////////////////////////////////////
// 第一個畫素
B = pBuffer[dwIndex++];
G = pBuffer[dwIndex++];
R = pBuffer[dwIndex++];
int maxcolor = GetRGB(R,G,B);
//第二個畫素
B = pBuffer[dwIndex++];
G = pBuffer[dwIndex++];
R = pBuffer[dwIndex++];
int maxcolor2 = GetRGB(R,G,B);
pNew[dwOldIndex++] = ( maxcolor<<4 )| maxcolor2;//合成一個位元組表示兩個畫素
}
// out.Close();
////////////////////////////////////////////////////////////////////////////////
// 完工, 把結果儲存到新檔案中
// 修改屬性
bmHdr.bfSize = sizeof(bmHdr)+sizeof(bmInfo)+sizeof(RGBQUAD)*16+dwNewSize;
bmHdr.bfOffBits = bmHdr.bfSize - dwNewSize;
bmInfo.biBitCount = 4;
bmInfo.biSizeImage = dwNewSize;
// 建立調色盤
RGBQUAD pa[16];
SetRGB(pa[0],0,0,0);
SetRGB(pa[1],0,0,128);
SetRGB(pa[2],0,0,255);
SetRGB(pa[3],0,128,0);
SetRGB(pa[4],0,128,128);
SetRGB(pa[5],0,255,0);
SetRGB(pa[6],0,255,255);
SetRGB(pa[7],128,0,0);
SetRGB(pa[8],128,0,128);
SetRGB(pa[9],128,128,0);
SetRGB(pa[10],128,128,128);
SetRGB(pa[11],255,255,255);
SetRGB(pa[12],255,0,0);
SetRGB(pa[13],255,0,255);
SetRGB(pa[14],255,255,0);
SetRGB(pa[15],64,64,64);
// 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;
}