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

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值