您的位置:首页 > 其它

24位位图转4位彩色图(BMP)

2013-04-15 14:31 417 查看
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;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: