典型的BMP图像文件由四部分组成:
(1) 位图头文件数据结构,它包含BMP图像文件的类型、文件大小和位图起始位置等信息;
typedef struct tagBITMAPFILEHEADER { WORD bfType;//位图文件的类型,必须为BM(1-2字节) DWORD bfSize;//位图文件的大小,以字节为单位(3-6字节,低位在前) WORD bfReserved1;//位图文件保留字,必须为0(7-8字节) WORD bfReserved2;//位图文件保留字,必须为0(9-10字节) DWORD bfOffBits;//位图数据的起始位置,以相对于位图(11-14字节,低位在前) } BITMAPFILEHEADER;
(2) 位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息;
typedef struct tagBITMAPINFOHEADER{ DWORD biSize;//本结构所占用字节数(15-18字节) LONG biWidth;//位图的宽度,以像素为单位(19-22字节) LONG biHeight;//位图的高度,以像素为单位(23-26字节) WORD biPlanes; WORD biBitCount;//每个像素所需的位数,必须是1(双色),(29-30字节) //4(16色),8(256色)16(高彩色)或24(真彩色)之一 DWORD biCompression; DWORD biSizeImage;//位图的大小(其中包含了为了补齐行数是4的倍数而添加的空字节),以字节为单位(35-38字节) LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER;
(3) 调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的BMP)就不需要调色板;
(4) 位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值。
1. 打开位图并显示
BITMAPFILEHEADER bmpHeader;//文件头 BITMAPINFOHEADER bmpInfo;//信息头 CFileDialog dlg(TRUE, "*.BMP", NULL, NULL,"位图文件(*.BMP)|*.bmp;*.BMP|",this); CFile bmpFile;//记录打开文件 CString strFileName;//记录选择文件路径 if (!dlg.DoModal() == IDOK) return; strFileName = dlg.GetPathName(); //以只读的方式打开文件 if(!bmpFile.Open(strFileName, CFile::modeRead|CFile::typeBinary)) return; //读取文件头到bmpHeader if (bmpFile.Read(&bmpHeader,sizeof(BITMAPFILEHEADER)) != sizeof(BITMAPFILEHEADER)) { AfxMessageBox("read bmp header failed!"); return; } /*0x4d42=’BM’,表示是Windows支持的BMP格式。 (注意:查ascii表B 0x42,M0x4d,bfType 为两个字节,B为low字节,M为high字节 所以bfType=0x4D42,而不是0x424D */ if (bmpHeader.bfType != 0x4d42) { AfxMessageBox("invalid file type!"); return; } //读取文件信息头bmpInfo if (bmpFile.Read(&bmpInfo,sizeof(BITMAPINFOHEADER)) != sizeof(BITMAPINFOHEADER)) { AfxMessageBox("read bmp infor header failed!"); return; } //确认是24位位图 if (bmpInfo.biBitCount != 24)//图像的位数 { AfxMessageBox("File is not 24 bit.Application doesn't support this kind of file!"); return; } /* typedef struct tagBITMAPINFO { BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[1]; } BITMAPINFO; */ pBmpInfo = (BITMAPINFO *)new char[sizeof(BITMAPINFOHEADER)]; if (!pBmpInfo) { AfxMessageBox("memory error!"); return; } //为图像数据申请空间 memcpy(pBmpInfo, &bmpInfo, sizeof(BITMAPINFOHEADER)); //计算颜色表区域大小:结构体的大小(包含颜色表)-颜色数据的偏移量 DWORD dataBytes = bmpHeader.bfSize - bmpHeader.bfOffBits; pBmpData = (BYTE*)new char[dataBytes]; if (!pBmpData) { AfxMessageBox("memory error!"); delete pBmpData; return; } if (bmpFile.Read(pBmpData,dataBytes) != dataBytes) { AfxMessageBox("Read bmp data failed!"); delete pBmpInfo; delete pBmpData; return; } //bmpFile.Close(); CWnd *pWnd=GetDlgItem(IDC_IMAGE);//获得pictrue控件窗口的句柄 CRect rect; pWnd->GetClientRect(&rect);//获得pictrue控件所在的矩形区域 CDC *pDC=pWnd->GetDC();//获得pictrue控件的DC //显示图片 pDC->SetStretchBltMode(COLORONCOLOR); StretchDIBits(pDC->GetSafeHdc(),0,0,rect.Width(),rect.Height(),0,0,bmpInfo.biWidth,bmpInfo.biHeight,pBmpData,pBmpInfo,DIB_RGB_COLORS,SRCCOPY); iBmpWidth = bmpInfo.biWidth; iBmpHeight = bmpInfo.biHeight;
2. 将24位图转化为16位位图(从RGB888到RGB565)
需要将原来的颜色表数据分离成R,G,B三组,然后R舍弃3位,G舍弃2位,B舍弃3位。
a. 分离,读取RGB数据存到三个BYTE*数组里m_pR, m_pG, m_pB;
LARGE_INTEGER liSize; liSize.QuadPart = 0; ::GetFileSizeEx(bmpFile, &liSize); int nBitmapSize = abs(iBmpHeight) * WIDTHBYTES(iBmpWidth * bmpInfo.biBitCount); if(bmpInfo.biPlanes != 1) { break; } if(bmpInfo.biBitCount != 1 && bmpInfo.biBitCount != 4 && bmpInfo.biBitCount != 8 && bmpInfo.biBitCount != 16 && bmpInfo.biBitCount != 24 && bmpInfo.biBitCount != 32) { break; } if(bmpInfo.biCompression != BI_RGB && bmpInfo.biCompression != BI_BITFIELDS) { break; } if(bmpInfo.biWidth <= 0 || bmpInfo.biHeight == 0) { break; } if(bmpHeader.bfOffBits + nBitmapSize > liSize.QuadPart) { break; } //m_pR,m_pG,m_pB位BYTE *; m_pR = new BYTE[bmpInfo.biWidth * abs(bmpInfo.biHeight)]; m_pG = new BYTE[bmpInfo.biWidth * abs(bmpInfo.biHeight)]; m_pB = new BYTE[bmpInfo.biWidth * abs(bmpInfo.biHeight)]; if(bmpInfo.biBitCount < 16) { //... } else if(bmpInfo.biBitCount == 16) { //... } else if(bmpInfo.biBitCount == 24) { ::SetFilePointer(bmpFile, bmpHeader.bfOffBits, NULL, SEEK_SET); BYTE *pData; pData = new BYTE[nBitmapSize]; DWORD dwByteRead = 0; dwByteRead = 0; ::ReadFile(bmpFile, pData, nBitmapSize, &dwByteRead, NULL); //pR, pG, pB是临时指针 BYTE *pR = m_pR; BYTE *pG = m_pG; BYTE *pB = m_pB; for(int j = 0; j < abs(bmpInfo.biHeight); j++) { BYTE *pTemp = pData + WIDTHBYTES(bmpInfo.biWidth * bmpInfo.biBitCount) * j; for(int i = 0; i < bmpInfo.biWidth; i++) { *pB++ = *pTemp++; *pG++ = *pTemp++; *pR++ = *pTemp++; } } delete[] pData; } else if(bmpInfo.biBitCount == 32) { //... }
b. 转化,从24位转化成16位
//新文件的头信息 BITMAPFILEHEADER bmfh; BITMAPINFOHEADER bmih; memset(&bmfh, 0, sizeof(bmfh)); memset(&bmih, 0, sizeof(bmih)); int nBitmapSize = abs(bmpInfo.biHeight) * WIDTHBYTES(bmpInfo.biWidth * 16); length = nBitmapSize; bmfh.bfType = 'MB'; bmfh.bfOffBits = sizeof(bmfh) + sizeof(bmih) + 12; bmfh.bfSize = bmfh.bfOffBits + nBitmapSize; bmih.biSize = sizeof(bmih); bmih.biWidth = bmpInfo.biWidth; bmih.biHeight = bmpInfo.biHeight; bmih.biPlanes = 1; bmih.biBitCount = 16; bmih.biCompression = BI_BITFIELDS; bmih.biSizeImage = nBitmapSize; /* 转化后的颜色表保存在pData中 */ BYTE *pData; pData = new BYTE[nBitmapSize]; memset(pData, 0, nBitmapSize); myData = new char[nBitmapSize]; memset(myData,0,nBitmapSize); char * inverseData = new char[nBitmapSize]; memset(inverseData, 0, nBitmapSize); BYTE *pR = m_pR; BYTE *pG = m_pG; BYTE *pB = m_pB; /* 以下是转化的核心代码 */ /* 转化过程图像的宽和高是不变的,变得是每个像素点从3个字节变成了2个字节 */ for(int j = 0; j < abs(bmih.biHeight); j++) { /* 临时指针pTemp 每次从新的一行开始 */ WORD *pTemp = (WORD *)(pData + WIDTHBYTES(bmih.biWidth * 16) * j); for(int i = 0; i < bmih.biWidth; i++) { #if 0 *pTemp++ = ((WORD)(*pR++ << 8) & 0xf800) | ((WORD)(*pG++ << 3) & 0x07e0) | ((WORD)(*pB++ >> 3) & 0x001f); #else /* 分别去掉低3,2,3位 */ int nR = (*pR++ + 4) >> 3; int nG = (*pG++ + 2) >> 2; int nB = (*pB++ + 4) >> 3; /* nR位5位,不能超过31,nG为6位,不能超过63,nB同nR */ if(nR > 31) nR = 31; if(nG > 63) nG = 63; if(nB > 31) nB = 31; /* 将新的R,G,B数据拼到2个字节里,比例为5:6:5 */ *pTemp++ = (nR << 11) | (nG << 5) | nB; #endif } }
3. 将图片上下对称翻转。图像点阵是image_width * image_height 的大小,翻转时只需要将 上下两半对应的位置的点对换就行了。经上面的转换后,图像中每个点占2个字节了,所以每个点换两个字节就行。
int image_width = bmih.biWidth; int image_height = bmih.biHeight; int index = 2;//表示16色,占2个字节 for(int h = 0; h < image_height/2; h++) for (int w = 0; w < image_width; w++) { /* iCoordM 和 iCoordN分别是上下对称的点在颜色表字节数组中的坐标, 交换iCoordM位置和iCoordM+1位置2个字节就行了。 */ const int iCoordM = index*(h*image_width + w); const int iCoordN = index*((image_height - h -1)*image_width + w); BYTE Tmp = pData[iCoordM]; pData[iCoordM] = pData[iCoordN]; pData[iCoordN] = Tmp; Tmp = pData[iCoordM+1]; pData[iCoordM + 1] = pData[iCoordN + 1]; pData[iCoordN + 1] = Tmp; /*如果是24位图像,就加上下面的内容,就是再交换一个字节*/ /*Tmp = pData[iCoordM + 2]; pData[iCoordM + 2] = pData[iCoordN + 2]; pData[iCoordN + 2] = Tmp;*/ }
4. 缩放
/* new_heigth和new_width为目标图片的大小,可自定义 */ int new_height = 430; int new_width = 160; int newSize = new_width*new_height*2; length = newSize; BYTE * newData = new BYTE[newSize]; /* 分配新的内存 */ memset(newData, 0, new_width*new_height*2); for(int h = 0; h < image_height; h++) for (int w = 0; w < image_width; w++) { /* 计算每个像素的起始位置 */ const int iCoordM = index * (h * image_width + w); //const int iCoordN = index*((image_height - h -1)*image_width + w); BYTE Tmp = pData[iCoordM]; int x = int( double(h)/image_height * new_height);//新行数 int y = int( double(w)/image_width * new_width);//新列数 /* 将原来图片的每个像素按比例位置映射到新的图片上 原来的位置是(w, h),通过比例就可以计算出新的位置是 (int(double(w)/image_width*new_width), int(double(h)/image_height*new_height)), 然后将新的位置开始的2个字节等于原来位置的2个字节,就完成了缩放。 */ newData[(x*new_width + y)*2] = Tmp; newData[(x*new_width + y)*2 + 1] = pData[iCoordM+1]; }
(完)