以下是256色图转换成灰度图示例代码:
/**************************************************************** * 函数名称: * Convert256toGray() * * 参数: * HDIB hDIB -图像的句柄 * * 返回值: * 无 * * 功能: * 将256色位图转化为灰度图 * ***************************************************************/ void Convert256toGray(HDIB hDIB) { LPSTR lpDIB; // 由DIB句柄得到DIB指针并锁定DIB lpDIB = (LPSTR) ::GlobalLock((HGLOBAL)hDIB); // 指向DIB象素数据区的指针 LPSTR lpDIBBits; // 指向DIB象素的指针 BYTE * lpSrc; // 图像宽度 LONG lWidth; // 图像高度 LONG lHeight; // 图像每行的字节数 LONG lLineBytes; // 指向BITMAPINFO结构的指针(Win3.0) LPBITMAPINFO lpbmi; // 指向BITMAPCOREINFO结构的指针 LPBITMAPCOREINFO lpbmc; // 获取指向BITMAPINFO结构的指针(Win3.0) lpbmi = (LPBITMAPINFO)lpDIB; // 获取指向BITMAPCOREINFO结构的指针 lpbmc = (LPBITMAPCOREINFO)lpDIB; // 灰度映射表 BYTE bMap[256]; // 计算灰度映射表(保存各个颜色的灰度值),并更新DIB调色板 int i,j; for (i = 0; i < 256; i ++) { // 计算该颜色对应的灰度值 bMap[i] = (BYTE)(0.299 * lpbmi->bmiColors[i].rgbRed + 0.587 * lpbmi->bmiColors[i].rgbGreen + 0.114 * lpbmi->bmiColors[i].rgbBlue + 0.5); // 更新DIB调色板红色分量 lpbmi->bmiColors[i].rgbRed = i; // 更新DIB调色板绿色分量 lpbmi->bmiColors[i].rgbGreen = i; // 更新DIB调色板蓝色分量 lpbmi->bmiColors[i].rgbBlue = i; // 更新DIB调色板保留位 lpbmi->bmiColors[i].rgbReserved = 0; } // 找到DIB图像象素起始位置 lpDIBBits = ::FindDIBBits(lpDIB); // 获取图像宽度 lWidth = ::DIBWidth(lpDIB); // 获取图像高度 lHeight = ::DIBHeight(lpDIB); // 计算图像每行的字节数 lLineBytes = WIDTHBYTES(lWidth * 8); // 更换每个象素的颜色索引(即按照灰度映射表换成灰度值) //逐行扫描 for(i = 0; i < lHeight; i++) { //逐列扫描 for(j = 0; j < lWidth; j++) { // 指向DIB第i行,第j个象素的指针 lpSrc = (unsigned char*)lpDIBBits + lLineBytes * (lHeight - 1 - i) + j; // 变换 *lpSrc = bMap[*lpSrc]; } } //解除锁定 ::GlobalUnlock ((HGLOBAL)hDIB); }
24位彩色图转换成4位灰度图
首先要声明的是,这个4位(16)色图比较特殊,不是彩色的16色图,而已一个用4位16色,模拟的灰度图
什么是灰度图?
灰度图是指只含亮度信息不含彩色信息的图象,就像我们平时看到的亮度由暗到明的黑白照片,亮度变化是连续的。因此,要表示灰度图,就需要把亮度值进行亮化。通常分成0-255共256个级别,0最暗(全黑),255最亮(全白)。BMP格式的文件中并没有灰度图这个概念,但是可以很容易的用BMP文件来表示灰度图。一般的方法是用256色的调色板,这个调色板
每一项的RGB值都是相同的,即从(0,0,0),(1,1,1)一直到(255,255,255),(0,0,0)表示全黑(255,255,255)表示全白
1.BMP位图的格式
BMP文件的结构分为4部分,本文假定读者都已经了解BMP位图的格式(几乎所有教VC的书上多媒体部分都有讲,再google一下也很容
易就查得到,这里主要介绍其中的调色板,和图象数据部分。
对于非真彩的位图,都有一个调色板,调色板的格式如下
typedef struct tagRGBQUAD{
BYTE rgbBlue;//蓝色的分量 BYTE rgbGreen;//绿色的分量 BYTE rgbRed;//红色的分量 BYTE rgbReserved;//保留值不用管它为0就好
}RGBQUAD;一般的调色版是一个,由上面的结构体组成的结构体数组,存储具体的颜色信息,而位图中,图象数据部分存储的只是调色板的下标
。这样做就可以大大的节省空间。
例如:RGBQUAD rgb[2];rgb[0].rgbBlue = 0;rgb[0].rgbGreen = 0;rgb[0].rgbRed = 0;rgb[0].rgbReserved = 0;rgb[1].rgbBlue = 255;rgb[1].rgbGreen = 255;rgb[1].rgbRed = 255;rgb[1].rgbReserved = 255;
这个长度为2的RGBQUAD数组就是一个1位2色黑白图的调色板,在位图数据部分只需要用1位的长度存储0表示黑,1表示白就可以了,1字节可以表示8个像素的信息,比用3字节直接表示R,G,B节
省了24倍的存储空间
而真彩图则不然,比如24位图,那么他就需要一个数组大小为2的24次方的调色板,而调色板的下标也需要3个字节才储存,这样还
不如直接就R,G,B这三个分量来直接表示每一个像素的色值。使用调色板技术还浪费了一个256*256*256*3字节大的调色板空间.
而这里要用4位表示一个灰度图,那么它的调色板只有16项,每一项的RGB值同通常由256色构成的灰度图的调色板一样的道理
这里这样建立这个调色板
RGBQUAD pa[16]; BYTE c; for(int i=0;i<16;i++) { c= i * 17; pa[i].rgbRed = c; pa[i].rgbGreen = c; pa[i].rgbBlue = c; pa[i].rgbReserved = 0; }
2.转换算法
现在的图象是24位真彩的,表示它的数据部分,3字节表示一个像素,这三个字节分别表示RGB。我们现在要做的是求每一像素点的RGB值的平均值,然后用16色调色板中最接近这个颜色亮度的值来表示它。而4位的图象是1个字节表示2个像素,在这里需要特殊注意
具体算法实现代码如下,pBuffer是储存图象数据的数组
USHORT R,G,B;
// 第一个像素 R = pBuffer[dwIndex++]; G = pBuffer[dwIndex++]; B = pBuffer[dwIndex++];
int maxcolor = (R+G+B)/3;
maxcolor /= 17;//计算在16色调色板中的下标
//第二个像素 R = pBuffer[dwIndex++]; G = pBuffer[dwIndex++]; B = pBuffer[dwIndex++];
int maxcolor2 = (R+G+B)/3;
maxcolor2 /= 17; pNew[dwOldIndex++] = ( maxcolor<<4 )| maxcolor2;//合成一个字节表示两个像素
3.实现代码
完整的实现代码如下BOOL Convert24To4(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];
UCHAR color = 0; DWORD dwIndex = 0, dwOldIndex = 0; while( dwIndex < dwOldSize )//一字节表示两个像素 { USHORT R,G,B;
// 第一个像素 R = pBuffer[dwIndex++]; G = pBuffer[dwIndex++]; B = pBuffer[dwIndex++];
int maxcolor = (R+G+B)/3;
maxcolor /= 17;
//第二个像素 R = pBuffer[dwIndex++]; G = pBuffer[dwIndex++]; B = pBuffer[dwIndex++];
int maxcolor2 = (R+G+B)/3;
maxcolor2 /= 17; pNew[dwOldIndex++] = ( maxcolor<<4 )| maxcolor2;//合成一个字节表示两个像素
}
// 完工, 把结果保存到新文件中
// 修改属性 bmHdr.bfSize = sizeof(bmHdr)+sizeof(bmInfo)+sizeof(RGBQUAD)*16+dwNewSize; bmHdr.bfOffBits = bmHdr.bfSize - dwNewSize; bmInfo.biBitCount = 4; bmInfo.biSizeImage = dwNewSize;
// 创建调色板 RGBQUAD pa[16]; UCHAR c; for(int i=0;i<16;i++) { c= i * 17; pa[i].rgbRed = c; pa[i].rgbGreen = c; pa[i].rgbBlue = c; pa[i].rgbReserved = 0; }
// 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;}
4.疑问
既然可以由24位真菜图转换为4位灰度图,那么一定有一个合适的方法把它转换成4位彩色图,而具体的区别就是调色板不同(调色板都要表示哪些颜色),再有最重要的是原来的颜色用现有的16色,哪个表示更合适.