24位轉8位位圖,不是灰度,有顏色的,儘量減少失真

具體做法見函數內註釋

void CDib::ConverTo8Bit()
{
 //24位轉8位,儘量減少失真
 //方法:
 //首先根據24位圖顏色信息,取顏色的高4位進行組合成12位的信息,然後作爲索引記錄數組,數組內容記錄出現的次數
 //然後對數組排序,計算顏色命中次數,取最大的256種作爲調色板
 //對24位位圖中其他顏色計算平方誤差,用調色板中顏色與24位各點顏色差的平方,再把紅綠藍三色作和,取結果,最小的最爲匹配
 //將最匹配的256色調色板索引寫入256色位圖相應的位置,並保存
 LPBITMAPINFOHEADER  lpbi;
 if(!hdib)
  return;
 lpbi = (BITMAPINFOHEADER*)hdib;
 int height = lpbi->biHeight;
 //兩個寬度
 LONG lLineBytes24=((lpbi->biWidth*24+31)/32*4);
 LONG lLineBytes8=((lpbi->biWidth*8+31)/32*4);
 //源圖像的指針
 BYTE* srcBits =NULL;
 //使其指向源圖像的圖像數據起始地址
 srcBits = (BYTE*)lpbi+sizeof(BITMAPINFOHEADER);
 //轉換後的圖像數據指針(大小爲圖片大小和調色板大小之和
 int iPaletteSize = sizeof(RGBQUAD)*256;//調色板大小
 BYTE* dstBits = (BYTE*)malloc(iPaletteSize+lLineBytes8*height);
 RGBQUAD* pRGB = (RGBQUAD *)dstBits;//調色板指針,指向數據部分開始地址
 //尋找原來24位圖裏的顏色信息,並按出現的頻率加權值
 DWORD   ColorHits[4096];   //顏色命中
 WORD   ColorIndex[4096]; //顏色索引
 //ColorHits爲記錄顏色使用頻率的數組,ColorIndex爲記錄顏色索引值的數組 
 memset(ColorHits, 0, 4096 * sizeof(DWORD)); 
 memset(ColorIndex, 0, 4096 * sizeof(WORD));
 //計算命中次數
 for(int y=0; y<height; y++)
 {
  BYTE* lpBits=srcBits+y*lLineBytes24;
  for(int x=0; x<lLineBytes24; )
  {
   int B = (int)(*(lpBits+x)&0xf0); //只取高四位
   x++;
   int G = (int)(*(lpBits+x)&0xf0);
   x++;
   int R = (int)(*(lpBits+x)&0xf0);
   x++;
   int ClrIndex=(B<<4) + G + (R>>4);  //拼成一個位整數,由於只取高四位,故拼後爲4*3=12位,即2的12次方爲4096項,所以索引裏爲4096大小的數組
   ColorHits[ClrIndex]++;//命中次數加1
  }
 }
 DWORD PalCounts=0;//最多調色板項數目
 //清除數組中爲0的元素,爲下面的排序提高效率
 for(int ClrIndex=0; ClrIndex<4096; ClrIndex++)
 {
  if( ColorHits[ClrIndex]!=0 )
  {
   ColorHits[PalCounts] = ColorHits[ClrIndex]; //由於ClrIndex>=PalCounts,故可以這樣賦值
   ColorIndex[PalCounts]=ClrIndex;      //注意調整相應的索引值 
   PalCounts++;   //顏色數加
  }
 }
 //排序,從大到小,標準的冒泡排序
 for (int i = 0;   i < PalCounts-1; i++) 
 {
  for (int j = i + 1; j < PalCounts; j++)  
  {  
   if (ColorHits[j] > ColorHits[i])   // 把大的值排到前面
   {  
    DWORD dwTmp = ColorHits[i];  
    ColorHits[i] = ColorHits[j];  
    ColorHits[j] = dwTmp;      
    //注意調整相應的索引值 
    dwTmp = ColorIndex[i];  
    ColorIndex[i] = ColorIndex[j];  
    ColorIndex[j] = (WORD)dwTmp;  
   }  
  } 
 }
 //爲新的調色板分配內存 
 //RGBQUAD pRGB[256];   // 臨時的顏色表信息,後面要用到
 for   (int i   =   0;   i   <   256;   i++)    
 {  
  //由位索引值得到R,G,B的最高位值 
  pRGB[i].rgbRed=(BYTE)((ColorIndex[i]   &   0x00f)   <<   4);  
  pRGB[i].rgbGreen=(BYTE)((ColorIndex[i]   &   0x0f0));  
  pRGB[i].rgbBlue=(BYTE)((ColorIndex[i]   &   0xf00)   >>   4);  
  pRGB[i].rgbReserved =(BYTE)0;
  ColorHits[i] = i;   //ColorHits作爲顏色記數的作用已經完成了,下面的作用是記錄位索引值對應的調色板中的索引值
 }
 //其餘的顏色依據最小平方誤差近似爲前中最接近的一種 
 long   ColorError1,ColorError2;
 if (PalCounts > 256)  
 {  
  for (int i = 256; i < PalCounts; i++)  
  {  
   //ColorError1記錄最小平方誤差,一開始賦一個很大的值 
   ColorError1=1000000000;  
   //由位索引值得到R,G,B的最高位值 
   long Blue = (long)((ColorIndex[i] & 0xf00) >> 4);  
   long Green = (long)((ColorIndex[i] & 0x0f0));  
   long Red = (long)((ColorIndex[i] & 0x00f) << 4);  
   int ClrIndex   =   0;  
   for   (int j   =   0;   j   <   256;   j++)  
   {  
    //ColorError2計算當前的平方誤差 
    ColorError2=(long)(Blue - pRGB[j].rgbBlue)*  
     (Blue - pRGB[j].rgbBlue)+   (long)(Green - pRGB[j].rgbGreen)*  
     (Green - pRGB[j].rgbGreen)+  
     (long)(Red - pRGB[j].rgbRed)*  
     (Red - pRGB[j].rgbRed);    
    if   (ColorError2   <   ColorError1)  
    {   //找到更小的了 
     ColorError1   =   ColorError2;  
     ClrIndex   =   j;   //記錄對應的調色板的索引值 
    }  
   }  
   //ColorHits記錄位索引值對應的調色板中的索引值 
   ColorHits[i]   =   ClrIndex;  
  }
 }
 //匹配24位的顏色信息,並把調色板索引寫入8位圖裏
 BYTE* pDstBits = dstBits+iPaletteSize;//指向目標位圖調色板後
 for(int y=0; y<height; y++)
 {
  BYTE* lpBits=srcBits+y*lLineBytes24;
  for(int x=0,n=0; x<lLineBytes24; )
  {
   int Blue = (int)(*(lpBits+x)&0xf0); //只取高四位
   x++;
   int Green = (int)(*(lpBits+x)&0xf0);
   x++;
   int Red = (int)(*(lpBits+x)&0xf0);
   x++;
   //拼成一個位整數 
   int ClrIndex=(Blue<<4)+Green+(Red>>4);
   for (int i = 0; i < PalCounts; i++)
   {
    if (ClrIndex == ColorIndex[i])  
    {  
     //根據索引值取得對應的調色板中的索引值 
     *pDstBits = (unsigned   char)ColorHits[i];//寫入目標位圖
     *(pDstBits+ lLineBytes8*y + n) = (unsigned   char)ColorHits[i];//寫入目標位圖
     n++;
     break;  
    }
   }
  }
 }
 //8位BMP的信息頭
 BITMAPINFOHEADER bi;
 bi.biBitCount=8;
 bi.biClrImportant=0;
 bi.biClrUsed=0;
 bi.biCompression=0L;
 bi.biHeight=Height();
 bi.biPlanes=1;
 bi.biSize=sizeof(BITMAPINFOHEADER);
 bi.biSizeImage=Height()*lLineBytes8;
 bi.biWidth=Width();
 bi.biXPelsPerMeter=0;
 bi.biYPelsPerMeter=0;
 SetBitmapinfoAndBits(bi, dstBits);
 CString FileName;
 strFileName.Delete(strFileName.GetLength()-4, 4);
 FileName.Format("%s的8位圖.bmp",strFileName);
 //調用保存文件函數
 SaveFile(FileName);
 //收尾清除指針內存
 //free(srcBits);
 srcBits = NULL;
 free(dstBits);
 dstBits = NULL;
 AfxMessageBox("已經轉換成8位圖,另存爲:"+FileName);
}
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章