1. 程式人生 > >24位點陣圖轉4位彩色圖(BMP)

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;
}