具體做法見函數內註釋
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);
}