最近在做图像采集的工作,需要处理图像数据,所以学习了一下位图显示,而且只看了设备相关位图DDB。基本上实现了位图的显示、位图数据的处理等功能。这里就记录一下我自己的理解,不一定全都对,仅供参考而已。
要显示位图,需要做如下工作:
CStatic* pStatic=(CStatic*)GetDlgItem(IDC_DISPLAY2);
CDC* pDC=pStatic->GetDC();
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAP2);
BITMAP bmp;
bitmap.GetBitmap(&bmp);
CDC dcCompatible;
dcCompatible.CreateCompatibleDC(pDC);
dcCompatible.SelectObject(&bitmap);
CRect rect;
pStatic->GetClientRect(&rect);
pDC->StretchBlt(0,0,rect.Width(),rect.Height(), &dcCompatible,0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY);
程序中,创建了一个位图对象bitmap,bitmap.LoadBitmap(IDB_BITMAP2);将一幅位图加载到了bitmap这个对象中,按我的理解,这个对象是存在在内存中的,所以我们对这个对象的操作并不会对原来那幅位图有什么影响。另外,还定义了一个BITMAP结构:bmp。之所以定义这个结构,是为了获得位图的宽度、高度等图像信息,如果我们知道加载的位图的高度、宽度、一个像素占多少字节等信息,那么我们无需定义这个bmp结构,不过为了方便,还是定义一个的好。要显示位图,还要创建与当前DC兼容的DC,当前DC怎么获得?就是下面这两句:
CStatic* pStatic=(CStatic*)GetDlgItem(IDC_DISPLAY2);
CDC* pDC=pStatic->GetDC();
我要将位图在对话框中的静态文本控件中显示,因此定义了一个指向静态CStatic对象的指针,这样,当前DC就需要使用pStatic->GetDC();来获取。然后创建一个与当前DC兼容的DC: dcCompatible.CreateCompatibleDC(pDC);。这个DC创建了之后,就把内存中的位图对象bitmap选入这个DC:dcCompatible.SelectObject(&bitmap);,从而确定这个兼容DC的显示表面的大小。这里要搞清楚一个关系。做了以上各步骤之后,其实与那个位图对象已经没多少关系了,bitmap这个位图对象的相关信息已经在dcCompatible这个保存着了,而要显示的目标区域则由当前DC:pDC所指示出来,这也就是为什么要创建兼容DC,只有两个DC兼容,才能顺利的把图像从dcCompatible复制到pDC中进行显示。我们看到最后的那句
pDC->StretchBlt(0,0,rect.Width(),rect.Height(), &dcCompatible,0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY);
实际上就是起到将dcCompatible中的图像在pDC的区域上显示的作用。这句话中还有有一个参数rect,它是一个CRect对象,用来得到客户区的大小,也就是我们所要显示的区域的大小,在这里也就是静态文本框,一个矩形的区域,所以调用pStatic的成员函数GetClientRect()来获取客户区域。
这样,一幅位图就显示了出来。上述这段程序是对话框中的一个按钮的响应函数。我之前看到有人说,要把画图程序放在OnDraw或者OnPaint之类的函数中去。我觉得其实放在哪里都无所谓,只要知道了要显示位图需要计算机做什么工作,就能顺利将位图显示出来。
学会显示位图之后,就得准备处理位图。做图像采集的时候,获得的是一个指向内存中一块区域的指针pData,这块区域中存储着图像的灰度值。而如果要处理加载的位图,也需要获取其位图数据的指针。因此两者的处理方法是差不多的。这里就以处理加载的位图为例进行说明。
要获取位图数据的指针,可以使用GetBitmapBits(),而要将处理后的数据COPY到位图对象中,则使用SetBitmapBits(),具体的用法可以查阅MSDN。我加载的位图是256色的,它的一个像素数据占4个字节,前三个字节分别表示RGB,第四个字节为保留字节。因此,如果这幅位图宽度是1000个像素,那么其字节宽度就是4000个字节。一般的图像处理都是针对灰度图像,也就是说一个像素的RGB值是相等的,从黑色(0,0,0)到白色(255,255,255)变化。因此,我处理图像的时候,只需要处理它的像素数据的代表R的字节,然后代表G和B的字节就都等于R的值就可以了。下面是代码:
BYTE *pmydata; //定义一个指针用来指向位图图像数据在内存中的存储区域
pmydata=new BYTE[bmp.bmWidthBytes*bmp.bmHeight]; //根据位图的高度宽度初始化一下
bitmap.GetBitmapBits(bmp.bmWidthBytes*bmp.bmHeight,pmydata); //将位图对象的数据COPY到pmydata指向的区域,bitmap是位图对象,bmp是位图结构,可参考上一篇文章的定义
for(int i=0;i<bmp.bmWidthBytes*bmp.bmHeight;i+=4) //这里只是将RGB三个字节的值取平均值,再取个反
{ //因为一个像素有4个字节,故i+=4
BYTE temp=0;
temp=(pmydata[i]+pmydata[i+1]+pmydata[i+2])/3;
pmydata[i]=255-temp;//R值
pmydata[i+1]=255-temp;//G值
pmydata[i+2]=255-temp;//B值
}
这样就实现了图像数据的简单处理,然后将处理的数据COPY回位图对象:
bitmap.SetBitmapBits(bmp.bmWidthBytes*bmp.bmHeight,pmydata); //将处理后的数据COPY进位图对象
随后按照上一篇文章的步骤显示位图即可。
另外要说一下,对于图像采集上来的灰度值数据,一个像素只有一个字节,而上面显示的图像的像素数据则是4个字节,这应该怎么来转换一下呢?我用的方法是定义一个COLORREF数组:
COLORREF* m_ColorData=new COLORREF[width*height];
这个COLORREF实际上就是DWORD,上面这句话定义了一个指向一块存放DWORD类型的数据的指针,也就是说,m_ColorData[i]与m_ColorData[i+1]之间有4个字节的距离,这正好与256色图像的像素数据存放方式相对应。将灰度值数据处理好之后,转化成RGB值:
//将灰度值转换为RGB值进行显示
for(i=0;i<width*height;i++)
m_ColorData[i]=RGB( m_bytes[i], m_bytes[i], m_bytes[i]);
其中的m_bytes[i]就是灰度值,上面这句话的意思是将m_ColorData[i]的四个字节中的前三个字节赋值为:m_bytes[i], m_bytes[i], m_bytes[i]。然后建立一个位图对象bitmap,使用SetBitmapBits()函数设置它的图像数据的值。
bitmap.SetBitmapBits(width*height*sizeof(COLORREF),m_ColorData);。然后按照前面说的步骤将这个位图对象显示处理即可。