我们知道Visual C++的CBitmap类和静态图片控件的功能是比较弱的它只能显示出在资源中的图标位图光标以及图元文件的内容而不像VB中的Image控件可以显示出绝大多数的外部图像文件(BMPGIFJPEG等) 因此想要在对话框或其他窗口中显示外部图像文件则只能借助于第三方提供的控件或代码现在MFC和ATL共享的新类CImage为图像处理提供了许多相应的方法这使得Visual C++在图像方面的缺憾一去不复返了
CImage类概述
CImage是MFC和ATL共享的新类它能从外部磁盘中调入一个JPEGGIFBMP和PNG格式的图像文件加以显示而且这些文件格式可以相互转换由于CImage在不同的Windows操作系统中其某些性能是不一样的因此在使用时要特别注意例如CImage::PlgBlt和CImage::MaskBlt只能在 Windows NT 或更高版本中使用但不能运行在Windows / 应用程序中CImage::AlphaBlend和CImage::TransparentBlt也只能在 Windows /或其更高版本中使用即使在Windows 运行程序还必须将stdafxh文件中的WINVER和_WIN_WINNT的预定义修改成x才能正常使用
CImage封装了DIB(设备无关位图)的功能因而可以让我们能够处理每个位图像素它具有下列最酷特性
AlphaBlend支持像素级的颜色混合从而实现透明和半透明的效果
PlgBlt能使一个矩形区域的位图映射到一个平行四边形区域中而且还可能使用位屏蔽操作
TransparentBlt在目标区域中产生透明图像SetTransparentColor用来设置某种颜色是透明色
MaskBlt在目标区域中产生源位图与屏蔽位图合成的效果
使用CImage的一般方法
使用CImage的一般方法是这样的过程
() 打开应用程序的stdafxh文件添加CImage类的包含文件#include <atlimageh>
() 定义一个CImage类对象然后调用CImage::Load方法装载一个外部图像文件
() 调用CImage::Draw方法绘制图像Draw方法具有如下定义
BOOL Draw( HDC hDestDC int xDest int yDest
int nDestWidth int nDestHeight int xSrc int ySrc
int nSrcWidth int nSrcHeight );
BOOL Draw( HDC hDestDC const RECT& rectDest const RECT& rectSrc );
BOOL Draw( HDC hDestDC int xDest int yDest );
BOOL Draw( HDC hDestDC const POINT& pointDest );
BOOL Draw( HDC hDestDC int xDest int yDest
int nDestWidth int nDestHeight );
BOOL Draw( HDC hDestDC const RECT& rectDest );
其中hDestDC用来指定绘制的目标设备环境句柄(xDest yDest)和pointDest用来指定图像显示的位置这个位置和源图像的左上角点相对应nDestWidth和nDestHeight分别指定图像要显示的高度和宽度xSrcySrcnSrcWidth和nSrcHeight用来指定要显示的源图像的某个部分所在的位置和大小rectDest和rectSrc分别用来指定目标设备环境上和源图像所要显示的某个部分的位置和大小
需要说明的是Draw方法综合了StretchBltTransparentBlt和AlphaBlend函数的功能默认时Draw的功能和StretchBlt相同但当图像含有透明色或Alpha通道时它的功能又和TransparentBltAlphaBlend相同因此在一般情况下我们都应该尽量调用CImage::Draw方法来绘制图像
例如下面的示例Ex_Image是实现这样的功能当选择文件ò打开菜单命令后弹出一个文件打开对话框当选定一个图像文件后就会在窗口客户区中显示该图像文件内容这个示例的具体步骤如下
() 创建一个默认的单文档程序项目Ex_Image
() 打开stdafxh文件中添加CImage类的包含文件atlimageh
() 在CEx_ImageView类添加ID_FILE_OPEN的COMMAND事件映射程序并添加下列代码
void CEx_ImageView::OnFileOpen()
{
CString strFilter;
CSimpleArray<GUID> aguidFileTypes;
HRESULT hResult;
// 获取CImage支持的图像文件的过滤字符串
hResult = m_ImageGetExporterFilterString(strFilteraguidFileTypes
_T( All Image Files) );
if (FAILED(hResult)) {
MessageBox(GetExporterFilter调用失败!);
return;
}
CFileDialog dlg(TRUE NULL NULL OFN_FILEMUSTEXIST strFilter);
if(IDOK != dlgDoModal())
return;
m_ImageDestroy();
// 将外部图像文件装载到CImage对象中
hResult = m_ImageLoad(dlgGetFileName());
if (FAILED(hResult)) {
MessageBox(调用图像文件失败!);
return;
}
// 设置主窗口标题栏内容
CString str;
strLoadString(AFX_IDS_APP_TITLE);
AfxGetMainWnd()>SetWindowText(str + +dlgGetFileName());
Invalidate(); // 强制调用OnDraw
}
() 定位到CEx_ImageView::OnDraw函数处添加下列代码
void CEx_ImageView::OnDraw(CDC* pDC)
{
CEx_ImageDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!m_ImageIsNull()) {
m_ImageDraw(pDC>m_hDC);
}
}
() 打开Ex_ImageViewh文件添加一个公共的成员数据m_Image
public:
CImage m_Image;
() 编译并运行单击打开工具按钮在弹出的对话框中指定一个图像文件后单击打开按钮其结果如图所示
将图片用其它格式保存
CImage::Save方法能将一个图像文件按另一种格式来保存它的原型如下
HRESULT Save( LPCTSTR pszFileName REFGUID guidFileType= GUID_NULL);
其中pszFileName用来指定一个文件名guidFileType用来指定要保存的图像文件格式当为GUID_NULL时其文件格式由文件的扩展名来决定这也是该函数的默认值它还可以是GUID_BMPFile(BMP文件格式)GUID_PNGFile(PNG文件格式)GUID_JPEGFile(JPEG文件格式)和GUID_GIFFile(GIF文件格式)
例如下面的过程是在Ex_Image示例基础上进行的我们在CEx_ImageView类添加ID_FILE_SAVE_AS的COMMAND事件映射程序并添加下列代码
void CEx_ImageView::OnFileSaveAs()
{
if (m_ImageIsNull()) {
MessageBox(你还没有打开一个要保存的图像文件!);
return;
}
CString strFilter;
strFilter = 位图文件|*bmp|JPEG 图像文件|*jpg| \
GIF 图像文件|*gif|PNG 图像文件|*png||;
CFileDialog dlg(FALSENULLNULLNULLstrFilter);
if ( IDOK != dlgDoModal())
return;
// 如果用户没有指定文件扩展名则为其添加一个
CString strFileName;
CString strExtension;
strFileName = dlgm_ofnlpstrFile;
if (dlgm_ofnnFileExtension == )
{
switch (dlgm_ofnnFilterIndex)
{
case :
strExtension = bmp; break;
case :
strExtension = jpg; break;
case :
strExtension = gif; break;
case :
strExtension = png; break;
default:
break;
}
strFileName = strFileName + + strExtension;
}
// 图像保存
HRESULT hResult = m_ImageSave(strFileName);
if (FAILED(hResult))
MessageBox(保存图像文件失败!);
}
柔化和锐化处理
在图像处理中我们通常用一些数学手段对图像进行除去噪声强调或抽取轮廓特征等图像空间的变换所谓图像空间的变换是借助于一个称之为模板的局部像素域来完成的不同的模板具有不同的图像效果
. 柔化
图像的柔化是除去图像中点状噪声的一个有效方法所谓柔化是指使图像上任何一个像素与其相邻像素的颜色值的大小不会出现陡突的一种处理方法设在一个 x 的模板中其系数为
中间有底纹的表示中心元素即用那个元素作为处理后的元素很明显上述模板(称之为Box模板)是将图像上每个像素用它近旁(包括它本身)的个像素的平均值取代这样处理的结果在除噪的同时也降低图像的对比度使图像的轮廓模糊为了避免这一缺陷我们对各点引入加权系数将原来的模板改为
新的模板可一方面除去点状噪声同时能较好地保留原图像的对比度因此该模板得到了广泛的应用由于这个模板是通过二维高斯(Gauss)函数得到的故称为高斯模板
. 锐化
锐化和柔化恰恰相反它通过增强高频分量减少图像中的模糊因此又称为高通滤波锐化处理在增强图像边缘效果的同时增加了图像的噪声常用的锐化模板是拉普拉斯模板
用此模板处理后的图像轮廓线条将明显得到增强轮廓线以外的部分将变得较暗而轮廓线部分将变得比较明亮
使用程序对模板进行运算时要考虑到溢出点的处理所谓溢出点指的是大于或小于的点处理时可令大于的点取而小于的点取其正值
. 实现代码
实现柔化和锐化时我们先调用CImage::GetPixel来依次读取相应的像素然后用柔化和锐化模板进行处理最后调用CImage::SetPixel函数将处理后的像素写回到CImage对象中具体的代码如下
void FilterImage(CImage* image int nType)
{
if (image>IsNull())
return;
int smoothGauss[] = {}; // 高斯模板
int sharpLaplacian[] = {}; // 拉普拉斯模板
int opTemp[];
float aver; // 系数
if ( nType> ) nType = ;
switch( nType ){
case : // 高斯模板
aver = (float)(/);
memcpy( opTemp smoothGauss *sizeof(int));
break;
case : // 拉普拉斯模板
aver = ;
memcpy( opTemp sharpLaplacian *sizeof(int));
break;
}
int ij;
int nWidth = image>GetWidth();
int nHeight = image>GetHeight();
for (i = ; i< nWidth; i++){
for (j = ; j< nHeight; j++){
int rr = gg = bb = ;
int index = ;
for (int col = ; col <= ; col++){
for (int row = ; row <= ; row++){
COLORREF clr = image>GetPixel( i+row j+col);
rr += GetRValue(clr) * opTemp[index];
gg += GetGValue(clr) * opTemp[index];
bb += GetBValue(clr) * opTemp[index];
index++;
}
}
rr = (int)(rr*aver);
gg = (int)(gg*aver);
bb = (int)(bb*aver);
// 处理溢出点
if ( rr> ) rr = ;
else if ( rr< ) rr = rr;
if ( gg> ) gg = ;
else if ( gg< ) gg = gg;
if ( bb> ) bb = ;
else if ( bb< ) bb = bb;
// 错位重写以避免前一个像素被新的像素覆盖
image>SetPixel( i j RGB(rrggbb));
}
}
}
图是使用上述代码将某个图像处理后的结果
变成黑白图片
由于许多图像文件使用颜色表来发挥显示设备的色彩显示能力因而将一张彩色图片变成黑色图片时需要调用CImage::IsIndexed来判断是否使用颜色表若是则修改颜色表否则直接将像素进行颜色设置例如下面的代码
void CEx_ImageView::MakeBlackAndwhite(CImage* image)
{
if (image>IsNull()) return;
if (!image>IsIndexed()) {
// 直接修改像素颜色
COLORREF pixel;
int maxY = image>GetHeight() maxX = image>GetWidth();
byte rgbavg;
for (int x=; x<maxX; x++) {
for (int y=; y<maxY; y++) {
pixel = image>GetPixel(xy);
r = GetRValue(pixel);
g = GetGValue(pixel);
b = GetBValue(pixel);
avg = (int)((r + g + b)/);
image>SetPixelRGB(xyavgavgavg);
}
}
} else {
// 获取并修改颜色表
int MaxColors = image>GetMaxColorTableEntries();
RGBQUAD* ColorTable;
ColorTable = new RGBQUAD[MaxColors];
image>GetColorTable(MaxColorsColorTable);
for (int i=; i<MaxColors; i++)
{
int avg = (ColorTable[i]rgbBlue + ColorTable[i]rgbGreen + ColorTable[i]rgbRed)/;
ColorTable[i]rgbBlue = avg;
ColorTable[i]rgbGreen = avg;
ColorTable[i]rgbRed = avg;
}
image>SetColorTable(MaxColorsColorTable);
delete(ColorTable);
}
}
至此我们介绍了GDI+和CImage的一般使用方法和技巧当然它们本身还有许多更深入的方法由于篇幅所限这里不再一一讨论