一个比CBitmap更优秀的类 ----- CImage

前两天有个朋友让我帮他编一个图象处理方面的程序,需要支持目前常见的图像格式.这可把我难住了.一直以来都只会用Visual C++的CBitmap类加载各种位图资源,然后处理.Visual C++的CBitmap类的功能是比较弱的,它只能显示出在资源中的图标、位图、光标以及图元文件的内容,而不像VB中的Image控件可以显示出绝大多数的外部图像文件(BMP、GIF、JPEG等)。如果想要在对话框或其他窗口中显示外部图像文件则只能借助于第三方提供的控件或代码,未免过于繁琐.

    这阵子在学C#,装了visual studio2003.索性在网上找了一下,竟然发现.net引入了一个功能非常强大的新类 ----- CImage.有了CImage类,Visual C++在图像方面的缺憾将一去不复返。CImage是MFC和ATL共享的新类,它能从外部磁盘中调入一个JPEG、GIF、BMP和PNG格式的图像文件加以显示,而且这些文件格式可以相互转换。例如通过简单的几句,就可以实现CImage类和CBitmap类实例的:

HBITMAP hBitmap=image.Detach();
CBitmap bmp;
bmp.Attach(hBitmap);

这样一来,就又回归到以前操纵CBitmap的方式了.CImage本身封装了DIB(设备无关位图)的功能,因而能够处理每个位图像素。

它具有下列最酷特性:
  1、AlphaBlend支持像素级的颜色混合,从而实现透明和半透明的效果。
  2、PlgBlt能使一个矩形区域的位图映射到一个平行四边形区域中,而且还可能使用位屏蔽操作。
  3、TransparentBlt在目标区域中产生透明图像,SetTransparentColor用来设置某种颜色是透明色。
  4、MaskBlt在目标区域中产生源位图与屏蔽位图合成的效果。

由于CImage在不同的Windows操作系统中其某些性能是不一样的,因此在使用时要特别注意。例如,CImage::PlgBlt和CImage::MaskBlt只能在 Windows NT 4.0 或更高版本中使用,但不能运行在Windows 95/98 应用程序中。CImage::AlphaBlend和CImage::TransparentBlt也只能在 Windows 2000/98或其更高版本中使用。即使在Windows 2000运行程序还必须将stdafx.h文件中的WINVER和_WIN32_WINNT的预定义修改成0x0500才能正常使用。

使用CImage的一般方法

  使用CImage的一般方法是这样的过程:

  (1) 打开应用程序的stdafx.h文件添加CImage类的包含文件:

#include <atlimage.h>

  (2) 定义一个CImage类对象,然后调用CImage::Load方法装载一个外部图像文件。

  (3) 调用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分别指定图像要显示的高度和宽度,xSrc、ySrc、nSrcWidth和nSrcHeight用来指定要显示的源图像的某个部分所在的位置和大小。rectDest和rectSrc分别用来指定目标设备环境上和源图像所要显示的某个部分的位置和大小。

  需要说明的是,Draw方法综合了StretchBlt、TransparentBlt和AlphaBlend函数的功能。默认时,Draw的功能和StretchBlt相同。但当图像含有透明色或Alpha通道时,它的功能又和TransparentBlt、AlphaBlend相同。因此,在一般情况下,我们都应该尽量调用CImage::Draw方法来绘制图像。

  例如,下面的示例Ex_Image是实现这样的功能:当选择"文件"ò"打开"菜单命令后,弹出一个文件打开对话框。当选定一个图像文件后,就会在窗口客户区中显示该图像文件内容。这个示例的具体步骤如下:

  (1) 创建一个默认的单文档程序项目Ex_Image。

  (2) 打开stdafx.h文件中添加CImage类的包含文件atlimage.h。

  (3) 在CEx_ImageView类添加ID_FILE_OPEN的COMMAND事件映射程序,并添加下列代码:

void CEx_ImageView::OnFileOpen()
{
 CString strFilter;
 CSimpleArray<GUID> aguidFileTypes;
 HRESULT hResult;

 // 获取CImage支持的图像文件的过滤字符串
 hResult = m_Image.GetExporterFilterString(strFilter,aguidFileTypes,
_T( "All Image Files") );
 if (FAILED(hResult)) {
  MessageBox("GetExporterFilter调用失败!");
  return;
 }
 CFileDialog dlg(TRUE, NULL, NULL, OFN_FILEMUSTEXIST, strFilter);
 if(IDOK != dlg.DoModal())
  return;

 m_Image.Destroy();
 // 将外部图像文件装载到CImage对象中
 hResult = m_Image.Load(dlg.GetFileName());
 if (FAILED(hResult)) {
  MessageBox("调用图像文件失败!");
  return;
 }

 // 设置主窗口标题栏内容
 CString str;
 str.LoadString(AFX_IDS_APP_TITLE);
 AfxGetMainWnd()->SetWindowText(str + " - " +dlg.GetFileName());

 Invalidate(); // 强制调用OnDraw
}


  (4) 定位到CEx_ImageView::OnDraw函数处,添加下列代码:

void CEx_ImageView::OnDraw(CDC* pDC)
{
 CEx_ImageDoc* pDoc = GetDocument();
 ASSERT_VALID(pDoc);
 if (!m_Image.IsNull()) {
  m_Image.Draw(pDC->m_hDC,0,0);
 }
}


  (5) 打开Ex_ImageView.h文件,添加一个公共的成员数据m_Image:

public:
CImage m_Image;


  (6) 编译并运行。单击"打开"工具按钮,在弹出的对话框中指定一个图像文件后,单击"打开"按钮,其结果如图7.21所示。

将图片用其它格式保存

  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_Image.IsNull()) {
  MessageBox("你还没有打开一个要保存的图像文件!");
  return;
 }

 CString strFilter;
 strFilter = "位图文件|*.bmp|JPEG 图像文件|*.jpg| /
GIF 图像文件|*.gif|PNG 图像文件|*.png||";
 CFileDialog dlg(FALSE,NULL,NULL,NULL,strFilter);
 if ( IDOK != dlg.DoModal())
  return;

 // 如果用户没有指定文件扩展名,则为其添加一个
 CString strFileName;
 CString strExtension;

 strFileName = dlg.m_ofn.lpstrFile;
 if (dlg.m_ofn.nFileExtension == 0)
 {
  switch (dlg.m_ofn.nFilterIndex)
  {
   case 1:
    strExtension = "bmp"; break;
   case 2:
    strExtension = "jpg"; break;
   case 3:
    strExtension = "gif"; break;
   case 4:
    strExtension = "png"; break;
   default:
    break;
  }
  strFileName = strFileName + '.' + strExtension;
 }

 // 图像保存
 HRESULT hResult = m_Image.Save(strFileName);
 if (FAILED(hResult))
  MessageBox("保存图像文件失败!");

将图片用其它格式保存

  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_Image.IsNull()) {
  MessageBox("你还没有打开一个要保存的图像文件!");
  return;
 }

 CString strFilter;
 strFilter = "位图文件|*.bmp|JPEG 图像文件|*.jpg| /
GIF 图像文件|*.gif|PNG 图像文件|*.png||";
 CFileDialog dlg(FALSE,NULL,NULL,NULL,strFilter);
 if ( IDOK != dlg.DoModal())
  return;

 // 如果用户没有指定文件扩展名,则为其添加一个
 CString strFileName;
 CString strExtension;

 strFileName = dlg.m_ofn.lpstrFile;
 if (dlg.m_ofn.nFileExtension == 0)
 {
  switch (dlg.m_ofn.nFilterIndex)
  {
   case 1:
    strExtension = "bmp"; break;
   case 2:
    strExtension = "jpg"; break;
   case 3:
    strExtension = "gif"; break;
   case 4:
    strExtension = "png"; break;
   default:
    break;
  }
  strFileName = strFileName + '.' + strExtension;
 }

 // 图像保存
 HRESULT hResult = m_Image.Save(strFileName);
 if (FAILED(hResult))
  MessageBox("保存图像文件失败!");
变成黑白图片

  由于许多图像文件使用颜色表来发挥显示设备的色彩显示能力,因而将一张彩色图片变成黑色图片时需要调用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 r,g,b,avg;
  for (int x=0; x<maxX; x++) {
   for (int y=0; y<maxY; y++) {
    pixel = image->GetPixel(x,y);
    r = GetRValue(pixel);
    g = GetGValue(pixel);
    b = GetBValue(pixel);
    avg = (int)((r + g + b)/3);
    image->SetPixelRGB(x,y,avg,avg,avg);
   }
  }
 } else {
  // 获取并修改颜色表
  int MaxColors = image->GetMaxColorTableEntries();
  RGBQUAD* ColorTable;
  ColorTable = new RGBQUAD[MaxColors];
  image->GetColorTable(0,MaxColors,ColorTable);
  for (int i=0; i<MaxColors; i++)
  {
   int avg = (ColorTable[i].rgbBlue + ColorTable[i].rgbGreen + ColorTable[i].rgbRed)/3;
   ColorTable[i].rgbBlue = avg;
   ColorTable[i].rgbGreen = avg;
   ColorTable[i].rgbRed = avg;
  }
  image->SetColorTable(0,MaxColors,ColorTable);
  delete(ColorTable);
 }
}

  至此,我们介绍了GDI+和CImage的一般使用方法和技巧。当然,它们本身还有许多更深入的方法,由于篇幅所限,这里不再一一讨论。



你可能感兴趣的:(C++,windows,image,command,null,扩展)