简介:

 

 这篇文章介绍了一个叫CBitmapEx的简单的C++位图操作类。很多次我面对这样一个事实:MFC库只能提供一个只带有有限特征的C++位图操作类​​CBitmap​​​​。我需要诸如比例尺、旋转角度、应用不同滤波等等特征,但是​​​​CBitmap​​​​并不具有。另一个类​​​​CDC​​ (设备上下文)提供了诸如拉伸度、透明性和透明混色等更多的选项,但它运行得太慢(被很多人很多次所证实)。

 

所以,我决定去写另一个(是的,另一个)不依赖MFC的位图操作类,并且提供一些目前在原来MFC没有实现的特性。这个类能加载任何8位、16位、24位和32位的位图,但在里面是和32位位图运行的。能够将结果图像保存24位位图到硬盘,或者它能够在设备上下文中画出位图。

 

背景

 

     在Code Project有很多文章讨论过这个话题,所以你可以随意翻阅它们并将它们的最终结果和我的实现作比较。

 

代码用法

   使用这个类非常简单,请看下面:



  1. #include "BitmapEx.h"
  2. // 装载位图
  3. CBitmapEx bitmapEx;
  4. bitmapEx.Load(_T("Enter bitmap source file path here..."));
  5. // Do whatever you need to do here
  6. bitmapEx.Rotate(45);
  7. bitmapEx.Sepia();
  8. bitmapEx.Scale(50, 50);
  9. // 在屏幕上绘制位图(首先在别的地方得到设备句柄)
  10. bitmapEx.Draw(hDC);
  11. // 保存位图
  12. bitmapEx.Save(_T("Enter bitmap destination file path here..."));


 

    

这儿有很多的公共的方法。请看下面:

 

创建位图/加载位图/保存位图

你能使用这个类去加载磁盘或者内存字节流上任何8位到32位的位图。这个类将把它转化为内部的32位位图。之后进行保存操作,位图将被再次转化为24位位图并且被保存在磁盘或内存。你能通过提供的方法来创建空白位图。

 

使用如下方法,可以在磁盘或内存中创建、加载和保存位图:



  1. void Create(long width, long height);
  2. void Create(CBitmapEx& bitmapEx);
  3. void Load(LPTSTR lpszBitmapFile);
  4. void Load(LPBYTE lpBitmapData);
  5. void Save(LPTSTR lpszBitmapFile);
  6. void Save(LPBYTE lpBitmapData);


 

     

一般的变换方法

   这个类提供了一些诸如缩放、旋转和镜像等一般的变换方法。在缩放和旋转操作中位图的细节信息将会掉失,所以实现了三种插值方法:

最近邻插值(插值速度最快,无修改信息,图像质量低)

双线性插值法(插值速度快,图像质量较好)

三次B样条(插值速度慢,图像质量最好)

 

你可以使用​​SetResampleMode()​​​​函数来确定使用哪种插值方法。下面是变换方法列表:​

   



  1. void Scale(long horizontalPercent=100, long verticalPercent=100);
  2. void Rotate(long degrees=0, _PIXEL bgColor=_RGB(0,0,0));
  3. void FlipHorizontal();
  4. void FlipVertical();
  5. void MirrorLeft();
  6. void MirrorRight();
  7. void MirrorTop();
  8. void MirrorBottom();


 

    

 你可以围绕x轴或y轴进行缩放位图,从0度到360度进行旋转位图,或者对位图进行倒立和镜像。当你对位图进行倒立或镜像,不要求你指定插值方式因为你没有改变位图尺寸。

 

当你旋转位图时你可以设置背景颜色,默认背景颜色是黑色。

 

简单的位图过滤

这儿实现了6种简单的滤波方法(将来会实现更多)来做一个简单的位图卷积过滤。​​Clear()​​​​方法可以使用你指定的颜色来填充位图。​

 

    



  1. void Clear(_PIXEL clearColor=_RGB(0,0,0));
  2. void Negative();
  3. void Grayscale();
  4. void Sepia(long depth=34);
  5. void Emboss();
  6. void Engrave();
  7. void Pixelize(long size=4);

    

对一些滤波,比如​​Sepia()​​ 和 ​​Pixelize()​​​​,​​你可以通过改变输入参数来得到不同的输出结果。

 

绘制位图

   下面是CBitmapEx类的核心函数列表:



  1. void Draw(HDC hDC);
  2. void Draw(HDC hDC, long dstX, long dstY);
  3. void Draw(long dstX, long dstY, long width, long height, CBitmapEx& bitmapEx,
  4.     long srcX, long srcY);
  5. void Draw(long dstX, long dstY, long width, long height, CBitmapEx& bitmapEx,
  6.     long srcX, long srcY, long alpha);
  7. void Draw(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx,
  8.     long srcX, long srcY, long srcWidth, long srcHeight);
  9. void Draw(long dstX, long dstY, long dstWidth, long dstHeight, CBitmapEx& bitmapEx,
  10.     long srcX, long srcY, long srcWidth, long srcHeight, long alpha);
  11. void Draw(_QUAD dstQuad, CBitmapEx& bitmapEx);
  12. void Draw(_QUAD dstQuad, CBitmapEx& bitmapEx, long alpha);
  13. void Draw(_QUAD dstQuad, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth,
  14.     long srcHeight);
  15. void Draw(_QUAD dstQuad, CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth,
  16.     long srcHeight, long alpha);
  17. void DrawTransparent(long dstX, long dstY, long width, long height, CBitmapEx& bitmapEx,
  18.     long srcX, long srcY, _PIXEL transparentColor=_RGB(0,0,0));
  19. void DrawTransparent(long dstX, long dstY, long width, long height, CBitmapEx& bitmapEx,
  20.     long srcX, long srcY, long alpha, _PIXEL transparentColor=_RGB(0,0,0));
  21. void DrawTransparent(long dstX, long dstY, long dstWidth, long dstHeight,
  22.     CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight,
  23.     _PIXEL transparentColor=_RGB(0,0,0));
  24. void DrawTransparent(long dstX, long dstY, long dstWidth, long dstHeight,
  25.     CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight,
  26.     long alpha, _PIXEL transparentColor=_RGB(0,0,0));
  27. void DrawTransparent(_QUAD dstQuad, CBitmapEx& bitmapEx,
  28.     _PIXEL transparentColor=_RGB(0,0,0));
  29. void DrawTransparent(_QUAD dstQuad, CBitmapEx& bitmapEx, long alpha,
  30.     _PIXEL transparentColor=_RGB(0,0,0));
  31. void DrawTransparent(_QUAD dstQuad, CBitmapEx& bitmapEx, long srcX, long srcY,
  32.     long srcWidth, long srcHeight, _PIXEL transparentColor=_RGB(0,0,0));
  33. void DrawTransparent(_QUAD dstQuad, CBitmapEx& bitmapEx, long srcX, long srcY,
  34.     long srcWidth, long srcHeight, long alpha, _PIXEL transparentColor=_RGB(0,0,0));
  35. void DrawBlended(long dstX, long dstY, long width, long height, CBitmapEx& bitmapEx,
  36.     long srcX, long srcY, long startAlpha, long endAlpha, DWORD mode=GM_NONE);
  37. void DrawBlended(long dstX, long dstY, long dstWidth, long dstHeight,
  38.     CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight,
  39.     long startAlpha, long endAlpha, DWORD mode=GM_NONE);


 

    

你可以看到这儿有很多不同的位图绘制方法。指定原尺寸和目标尺寸能够以不同的比例显示位图。最终的图像质量和显示速度取决你选择的插值方法。在大多数的情况下,双线性插值法是平衡显示速度和图像质量的一种比较好的方法。它类似于GDI的​​StretchBlt()​​函数。

 

     除了一般的显示方法,这儿还有特别的显示方法。使用​​Draw()​​函数通过设置​​_QUAD​​参数你可以在你指定的四个角顶点的多边形绘制位图。它类似于GDI的​​PlgBlt()​​函数,只是它提供了更多的自由,那就是,它能做投影变化同时不仅仅是仿射位图变换。一些人将会发现这个非常有用。

 

另一组可用的位图绘制方法是​​DrawTransparent()​​函数组。它所能做就是:它能在原位图中指定一种透明颜色。另外,它能显示位图的投影变化。

 

所有的位图绘制函数能够提供一个不透明性参数只要它在绘制操作中能被应用。这个类似于使用GDI的​​AlphaBlend()​​函数。

     通过结合不同的位图绘制方法,能够出现一些很酷的显示效果。

 

    为了使用前面两种函数在屏幕上显示位图或者其它设备环境,需要一个设备句柄作为输入参数。

 

位图信息函数

 

下面是一些可用的获取位图信息的函数:

 



  1. LPBITMAPFILEHEADER GetFileInfo() {return &m_bfh;}
  2. LPBITMAPINFOHEADER GetInfo() {return &m_bih;}
  3. long GetWidth() {return m_bih.biWidth;}
  4. long GetHeight() {return m_bih.biHeight;}
  5. long GetPitch() {return m_iPitch;}
  6. long GetBpp() {return m_iBpp;}
  7. long GetPaletteEntries() {return m_iPaletteEntries;}
  8. LPRGBQUAD GetPalette() {return m_lpPalette;}
  9. DWORD GetSize() {return m_dwSize;}
  10. LPBYTE GetData() {return m_lpData;}
  11. void SetResampleMode(RESAMPLE_MODE mode=RM_NEARESTNEIGHBOUR) {m_ResampleMode = mode;}
  12. RESAMPLE_MODE GetResampleMode() {return m_ResampleMode;}
  13. BOOL IsValid() {return (m_lpData != NULL);}


 

    

直接的像素访问函数

 

     这方面也提供了一些重要的函数,请看下面:

 



  1. _PIXEL GetPixel(long x, long y);
  2. void SetPixel(long x, long y, _PIXEL pixel);

    

这些方法然而比一般的直接内存访问要慢。你可以通过使用​​GetData()​​成员函数来得到位图内存缓冲。使用这个位图内存缓冲你能做任何你设想为位图实时处理。对有测试性质的意图这种方法比直接的像素访问要安全。

 

 

使用CBitmapEx类的注意事项:

 

     使用所有这些方法你能做任何类型你需要的绘制和变换。对,几乎是所有。而且更多的滤波将被添加进来,更多的变换将得到应用。

 

还有,读者的评论对于源码更新非常有用。新的函数,比如从内存流中加载和保存已经添加,同时添加的函数还有通过插入点在客户设备绘制位图。

 

原来许多的绘制函数现在已改由内置的汇编函数实现来获取更高的速度。源码已经添加注释以助于更好的理解。所有关于汇编程序的优化都是欢迎的(因为我并非这方面的专家)。

 

关于示例工程

 

     工程中的​​CEffectorBuilder​​​​类是​​一个特别的类,仅仅是作为一个示例去创建动画。这个类使用​​CBitmapEx​​类去加载位图文件并对它们作各种变换。上面的截图显示示例工程的屏幕输出。这儿有很多可用绘制和变换的效果你可以自由去体验。最终结果看起来很好!

 

有趣的地方

 

     使用这个类我已经很多次发现一种完美的图形加速方法。它就是使用固定点运算。它被应用于​​CBitmapEx​​类的每个角落,几乎在每个单独的浮点乘法和除法。所以,不要对你仅仅使用这个类就能取得实时的动画速度感到惊奇。看看使用​​CBitmapEx​​类的示例工程所取得的实时动画速度。