CBitmapEx - 免费 C++ 位图操作类
一篇关于免费 C++ 位图操作类的文章

引言
本文介绍了一个“简单”的 C++ 位图操作类 CBitmapEx
。很多时候,我发现 MFC 库除了读取位图文件之外,只提供了功能非常有限的 CBitmap
类。我需要缩放、旋转、应用不同滤镜等功能,但它就是没有。另一个类 CDC
类(设备上下文)提供了一些额外的选项,如拉伸、透明和 Alpha 混合,但它实在太慢了(许多不同的人多次证明)。
因此,我决定编写另一个(是的,又一个)位图操作类,它不依赖 MFC,并提供原始 MFC 实现中没有的一些扩展功能。这个类可以加载任何 8、16、24 或 32 位位图,但内部以 32 位位图工作。结果可以保存为硬盘上的 24 位位图,也可以绘制到提供的设备上下文 (DC) 上。
背景
Code Project 上有许多关于此主题的文章,因此请随意浏览并将其最终结果与此实现进行比较。
Using the Code
使用此类别非常简单,请参见下方
#include "BitmapEx.h"
// Load bitmap
CBitmapEx bitmapEx;
bitmapEx.Load(_T("Enter bitmap source file path here..."));
// Do whatever you need to do here
bitmapEx.Rotate(45);
bitmapEx.Sepia();
bitmapEx.Scale(50, 50);
// Draw the results on the screen (get hDC somewhere else)
bitmapEx.Draw(hDC);
// Save bitmap
bitmapEx.Save(_T("Enter bitmap destination file path here..."));
有许多 public
方法可用。请阅读其余的文本,其中解释了所有这些方法。
创建/加载/保存位图
您可以使用此类从硬盘或内存流中加载任何 8bpp 到 32bpp 的位图。此外,您可以从 Windows HBITMAP
句柄加载任何位图,但它必须超过 8bpp。该类会将其转换为内部使用的 32bpp 位图。保存后,位图将再次转换为 24bpp 位图,并保存到硬盘或内存流中。您也可以将其作为 32bpp 位图保存到 HBITMAP
句柄中。您还可以使用提供的方法创建空白(空)位图。
要创建/加载/保存位图到硬盘或内存流,或 HBITMAP
句柄,请使用以下方法
void Create(long width, long height);
void Create(CBitmapEx& bitmapEx);
void Create(CBitmapEx* pBitmapEx);
void Load(LPTSTR lpszBitmapFile);
void Load(LPBYTE lpBitmapData);
void Load(HBITMAP hBitmap);
void Save(LPTSTR lpszBitmapFile);
void Save(LPBYTE lpBitmapData);
void Save(HBITMAP& hBitmap);
使用第一种方法,您可以创建几乎任何大小的位图(当然,可用 RAM 可能是限制因素)。这是一种通用的“空白位图”。使用第二种和第三种方法,您可以从现有位图创建新位图(这模拟了所谓的“复制构造函数”)。通过这种方式,您实际上是将现有位图复制到内存中。这在进行位图过滤并需要原始位图备份时会很有用。接下来的三种方法为您提供了从文件或内存流,或 HBITMAP
加载位图的选项。最后,最后三种方法为您提供了将位图保存到文件或内存流或 HBITMAP
句柄的简单方法。从/向文件流(在本地或网络驱动器上)加载和保存位图是通用操作。但是,如果您更喜欢使用一些 Web 服务器的动态图像(在内存中生成),那么从/向内存流加载和保存可能是一个选择。如果您需要将结果嵌入到 HBITMAP
句柄中,请使用相应的方法。
通用转换方法
该类提供了一些通用的位图转换方法,如缩放、旋转、裁剪、翻转和镜像。在缩放和旋转操作期间,位图细节可能会丢失,因此实现了三种基本插值方法
- 最近邻(最快,无插值,低质量)
- 双线性(快速,质量更好)
- 三次 B 样条(慢,质量最好)
您可以使用 SetResampleMode()
成员函数设置插值方法。以下是转换方法列表
void Scale(long horizontalPercent=100, long verticalPercent=100);
void Rotate(long degrees=0, _PIXEL bgColor=_RGB(0,0,0));
void Crop(long x, long y, long width, long height);
void FlipHorizontal();
void FlipVertical();
void MirrorLeft();
void MirrorRight();
void MirrorTop();
void MirrorBottom();
您可以沿 x 和/或 y 轴缩放位图,将其从 0 旋转到 360 度,或执行翻转和/或镜像。当您翻转或镜像位图时,不需要插值,因为您不会改变原始位图尺寸。
执行位图旋转时,可以设置背景颜色。默认是 黑色。
以下是所描述的转换方法的一些示例(仅限 Internet Explorer:将鼠标指针放在每个图像上方以查看应用的转换)










简单位图过滤
已实现了 22 种简单滤镜(还将添加更多)来执行简单的位图卷积滤镜。Clear()
方法将用您提供的颜色填充位图。
void Clear(_PIXEL clearColor=_RGB(0,0,0));
void Negative();
void Grayscale();
void Sepia(long depth=34);
void Emboss();
void Engrave();
void Pixelize(long size=4);
void Brightness(long brightness=0);
void Contrast(long contrast=0);
void Blur();
void GaussianBlur();
void Sharp();
void Colorize(_PIXEL color);
void Rank(BOOL bMinimum=TRUE);
void Spread(long distanceX=8, long distanceY=8);
void Offset(long offsetX=16, long offsetY=16);
void BlackAndWhite(long offset=128);
void EdgeDetect();
void GlowingEdges(long threshold=2, long scale=5);
void EqualizeHistogram(long levels=255);
void Median();
void Posterize(long levels=4);
void Solarize(long threshold=128);
对于某些滤镜,例如 Sepia()
和 Pixelize()
等,您可以更改输入参数并获得不同的输出结果。
以下是所描述的滤镜方法的一些示例(仅限 Internet Explorer:将鼠标指针放在每个图像上方以查看应用的滤镜)
























绘制位图
以下方法列表构成了 CBitmapEx
类的核心
void Draw(HDC hDC);
void Draw(HDC hDC, long dstX, long dstY);
void Draw(long dstX, long dstY, long width, long height,
CBitmapEx& bitmapEx, long srcX, long srcY);
void Draw(long dstX, long dstY, long width, long height,
CBitmapEx& bitmapEx, long srcX, long srcY, long alpha);
void Draw(long dstX, long dstY, long dstWidth, long dstHeight,
CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth, long srcHeight);
void Draw(long dstX, long dstY, long dstWidth, long dstHeight,
CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth,
long srcHeight, long alpha);
void Draw(_QUAD dstQuad, CBitmapEx& bitmapEx);
void Draw(_QUAD dstQuad, CBitmapEx& bitmapEx, long alpha);
void Draw(_QUAD dstQuad, CBitmapEx& bitmapEx, long srcX, long srcY,
long srcWidth, long srcHeight);
void Draw(_QUAD dstQuad, CBitmapEx& bitmapEx, long srcX, long srcY,
long srcWidth, long srcHeight, long alpha);
void DrawTransparent(long dstX, long dstY, long width, long height,
CBitmapEx& bitmapEx, long srcX, long srcY, _PIXEL transparentColor=_RGB(0,0,0));
void DrawTransparent(long dstX, long dstY, long width, long height,
CBitmapEx& bitmapEx, long srcX, long srcY, long alpha,
_PIXEL transparentColor=_RGB(0,0,0));
void DrawTransparent(long dstX, long dstY, long dstWidth, long dstHeight,
CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth,
long srcHeight, _PIXEL transparentColor=_RGB(0,0,0));
void DrawTransparent(long dstX, long dstY, long dstWidth, long dstHeight,
CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth,
long srcHeight, long alpha, _PIXEL transparentColor=_RGB(0,0,0));
void DrawTransparent(_QUAD dstQuad, CBitmapEx& bitmapEx,
_PIXEL transparentColor=_RGB(0,0,0));
void DrawTransparent(_QUAD dstQuad, CBitmapEx& bitmapEx, long alpha,
_PIXEL transparentColor=_RGB(0,0,0));
void DrawTransparent(_QUAD dstQuad, CBitmapEx& bitmapEx, long srcX,
long srcY, long srcWidth, long srcHeight, _PIXEL transparentColor=_RGB(0,0,0));
void DrawTransparent(_QUAD dstQuad, CBitmapEx& bitmapEx, long srcX,
long srcY, long srcWidth, long srcHeight, long alpha,
_PIXEL transparentColor=_RGB(0,0,0));
void DrawBlended(long dstX, long dstY, long width, long height,
CBitmapEx& bitmapEx, long srcX, long srcY, long startAlpha,
long endAlpha, DWORD mode=GM_NONE);
void DrawBlended(long dstX, long dstY, long dstWidth, long dstHeight,
CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth,
long srcHeight, long startAlpha, long endAlpha, DWORD mode=GM_NONE);
void DrawMasked(long dstX, long dstY, long width, long height,
CBitmapEx& bitmapEx, long srcX, long srcY,
_PIXEL transparentColor=_RGB(255,255,255));
void DrawAlpha(long dstX, long dstY, long width, long height,
CBitmapEx& bitmapEx, long srcX, long srcY, long alpha,
_PIXEL alphaColor=_RGB(0,0,0));
void DrawCombined(long dstX, long dstY, long width, long height,
CBitmapEx& bitmapEx, long srcX, long srcY, DWORD mode=CM_SRC_AND_DST);
void DrawCombined(long dstX, long dstY, long dstWidth, long dstHeight,
CBitmapEx& bitmapEx, long srcX, long srcY, long srcWidth,
long srcHeight, DWORD mode=CM_SRC_AND_DST);
void DrawTextA(long dstX, long dstY, LPSTR lpszText, _PIXEL textColor,
long textAlpha, LPTSTR lpszFontName, long fontSize,
BOOL bBold=FALSE, BOOL bItalic=FALSE);
void DrawTextW(long dstX, long dstY, LPWSTR lpszText,
_PIXEL textColor, long textAlpha, LPTSTR lpszFontName,
long fontSize, BOOL bBold=FALSE, BOOL bItalic=FALSE);
您可以看到许多不同的位图绘制方法。将源和目标大小作为输入参数的方法将执行带缩放的位图绘制。最终质量和速度将取决于您选择的插值方法。在大多数情况下,双线性插值是速度和质量之间的一个良好选择。这类似于 StretchBlt()
GDI 方法。
除了通用绘制方法外,还有一些特殊方法。使用以 _QUAD
为输入参数的 Draw()
函数,您可以将位图绘制到由四个点定义的任何多边形中。它类似于 PlgBlt()
GDI 方法,只是提供更多的自由。也就是说,它可以进行“投影”而不是仅仅“仿射”位图变换。有些人会发现这非常有用。
另一组可用的位图绘制方法是 DrawTransparent()
方法组。它可以完成上面描述的所有操作;甚至可以在源位图中指定透明颜色。此外,还可以执行位图“投影”变换。
下一组是 DrawBlended()
方法。它们提供了一种将源位图“渐变地”绘制到目标位图上的方式。支持四种类型的渐变:水平、垂直、对角和径向。
两种特殊方法 DrawMasked()
和 DrawAlpha()
用于阴影等特效。
名为 DrawCombined()
的方法用于在源位图和目标位图之间执行逻辑操作(类似于 GDI 中的栅格操作码)。
名为 DrawText()
的方法用于在位图上输出 ANSI 和 UNICODE 文本。它们还在内部使用字形栅格位图执行文本抗锯齿。
所有描述的位图绘制方法都提供了一个不透明度参数(alpha),可以在绘制操作期间应用。这类似于使用 AlphaBlend()
GDI 方法。
通过结合不同的位图绘制方法,可以实现一些非常酷炫的效果。
要将位图输出到屏幕(或任何其他设备上下文),请使用前两种方法,并以 HDC
作为输入参数。
以下是所描述的绘图方法的一些示例(仅限 Internet Explorer:将鼠标指针放在每个图像上方以查看应用的绘图)










更改位图中的颜色
这是 CBitmapEx
类中定义的一个特殊方法。它允许开发人员将位图中现有的颜色替换为另一种颜色。可以指定新颜色的透明度和“强度”。此外,此操作可以在局部(在某个区域)或整个图像中进行。请参阅下面的图像


以下代码片段展示了如何完成此操作
CBitmapEx bitmap;
bitmap.Load(_T("Original3.bmp"));
bitmap.ReplaceColor(345, 275, _RGB(255,0,0), 10, 150, FALSE); // Sea rock
bitmap.ReplaceColor(105, 305, _RGB(255,255,0), 10, 90, FALSE); // Bottom of the sea
bitmap.Save(_T("Original3_r.bmp"));
此处理可能需要一些时间,因此请不要立即期待最终结果。此外,为了获得最佳输出位图,可能需要尝试不同的选项。以下是 ReplaceColor()
方法的定义
void ReplaceColor(long x, long y, _PIXEL newColor,
long alpha=20, long error=100, BOOL bImage=TRUE);
实际上,您从 x
和 y
位置获取像素,并将其颜色更改为 newColor
。同时,您指定了要应用的新颜色的透明度级别 alpha
,以及“强度”,即 error
值。最后一个参数 bImage
用于重新着色整个图像,或者仅着色由“强度”定义的区域。您必须通过实验才能了解此方法在不同图像上的工作方式。如果您想更改图像的背景颜色,或者重新着色灰度图像,这可能会很有用。
创建阴影效果
这是一种非常流行的效果,使用 CBitmapEx
类可以轻松实现。请参见下图



以下代码片段展示了如何完成此操作
CBitmapEx bitmap1, bitmap2, bitmap3;
bitmap1.Load(_T("Original4.bmp"));
bitmap2.Load(_T("Original5.bmp"));
bitmap3.Create(bitmap2.GetWidth(), bitmap2.GetHeight());
bitmap3.DrawMasked(0, 0, bitmap2.GetWidth(), bitmap2.GetHeight(),
bitmap2, 0, 0, bitmap2.GetPixel(0, 0));
bitmap3.Blur();
bitmap3.Blur();
bitmap3.Blur();
bitmap1.DrawAlpha(4, 4, bitmap3.GetWidth(), bitmap3.GetHeight(),
bitmap3, 0, 0, 25, _RGB(0,0,0));
bitmap1.DrawTransparent(0, 0, bitmap2.GetWidth(),
bitmap2.GetHeight(), bitmap2, 0, 0, 50, bitmap2.GetPixel(0, 0));
bitmap1.Save(_T("Original4_s.bmp"));
这个处理速度很快,所以您不必等待很长时间才能得到最终结果。这个过程的瓶颈是您调用的 Blur()
方法的数量。此外,使用特殊方法 DrawMasked()
,您可以创建任何带有透明背景的图像的单色蒙版。另一个特殊方法 DrawAlpha()
以任何颜色(这里我们需要阴影是黑色的,对吧)和选定的透明度级别(阴影主要是半透明的)渲染创建的蒙版。
在位图上绘制文本
您可以使用 CBitmapEx
类在位图上输出一些文本。请参见下图

以下代码片段展示了如何完成此操作
CBitmapEx bitmap,;
bitmap.Load(_T("Original4.bmp"));
bitmap.DrawTextW(0, 0, _T("Enter your text here..."), _RGB(255,0,0),
50, _T("Times New Roman"), 12);
bitmap.Save(_T("Original4_t.bmp"));
您可以绘制 ANSI 或 UNICODE 文本,设置文本颜色和不透明度,选择不同的字体及其大小,还可以使文本 粗体 或 斜体。您可以使用 MeasureText()
方法获取文本宽度和高度。
创建火焰效果
CBitmapEx
类可以产生“燃烧的火焰”效果。请参阅以下代码
CBitmapEx bitmap;
bitmap.Create(300, 300);
bitmap.Clear();
bitmap.CreateFireEffect();
// Call the following method for some time (i.e. a few seconds)
bitmap.UpdateFireEffect();
请看下面的结果


以下是所用方法的定义
void CreateFireEffect();
void UpdateFireEffect(BOOL bLarge=TRUE, long iteration=5, long height=16);
通过改变 UpdateFireEffect()
方法中的参数,您可以控制火焰(速度和大小)。该方法应在某个线程或循环中调用,以获得“燃烧的火焰”动画。
创建水面效果
CBitmapEx
类可以产生“水”效果。请参阅以下代码
CBitmapEx bitmap;
bitmap.Load(_T("Undersea.bmp"));
bitmap.CreateWaterEffect();
bitmap.MakeWaterBlob(100, 100, 20, -500);
bitmap.MakeWaterBlob(150, 150, 20, 500);
bitmap.MakeWaterBlob(200, 200, 20, -500);
// Call the following method for some time (i.e. a few seconds)
bitmap.UpdateWaterEffect();
请看下面的结果

以下是所用方法的定义
void CreateWaterEffect();
void UpdateWaterEffect(long iteration=5);
void MakeWaterBlob(long x, long y, long size, long height);
通过更改 UpdateWaterEffect()
方法和 MakeWaterBlob()
方法中的参数,您可以控制水(水滴、速度、大小)。该方法应在某个线程或循环中调用,以获得“水”动画。
创建烟雾(或云朵)效果
CBitmapEx
类可以产生“烟雾”(或“云朵”)效果。请参阅以下代码
CBitmapEx bitmap;
bitmap.Load(_T("Original4.bmp"));
bitmap.CreateSmokeEffect();
// Call the following method for some time (i.e. a few seconds)
bitmap.UpdateSmokeEffect();
请看下面的结果

以下是所用方法的定义
void CreateSmokeEffect();
void UpdateSmokeEffect(long offsetX=0, long offsetY=0, long offsetZ=0);
您可以通过更改 UpdateSmokeEffect()
方法中的参数来控制烟雾(或云朵)(速度、大小)。此方法应在某个线程或循环中调用,以获得“烟雾”(或“云朵”)动画。
位图从 RGB 到 HSV 再到 RGB
以下是位图从 RGB 颜色空间转换为 HSV,反之亦然时可能有用的一些方法列表
_PIXEL _RGB2HSV(_PIXEL rgbPixel);
_PIXEL _HSV2RGB(_PIXEL hsvPixel);
void ConvertToHSV();
void ConvertToRGB();
_COLOR_MODE GetColorMode() {return m_ColorMode;}
这些颜色转换在实现上面描述的 ReplaceColor()
方法时非常有用。
位图信息方法
各种位图信息方法可用,请参阅下面的列表
LPBITMAPFILEHEADER GetFileInfo() {return &m_bfh;}
LPBITMAPINFOHEADER GetInfo() {return &m_bih;}
long GetWidth() {return m_bih.biWidth;}
long GetHeight() {return m_bih.biHeight;}
long GetPitch() {return m_iPitch;}
long GetBpp() {return m_iBpp;}
long GetPaletteEntries() {return m_iPaletteEntries;}
LPRGBQUAD GetPalette() {return m_lpPalette;}
DWORD GetSize() {return m_dwSize;}
LPBYTE GetData() {return m_lpData;}
void SetResampleMode(RESAMPLE_MODE mode=RM_NEARESTNEIGHBOUR)
{m_ResampleMode = mode;}
RESAMPLE_MODE GetResampleMode() {return m_ResampleMode;}
BOOL IsValid() {return (m_lpData != NULL);}
void GetRedChannel(LPBYTE lpBuffer);
void GetGreenChannel(LPBYTE lpBuffer);
void GetBlueChannel(LPBYTE lpBuffer);
void GetRedChannelHistogram(long lpBuffer[256], BOOL bPercent=FALSE);
void GetGreenChannelHistogram(long lpBuffer[256], BOOL bPercent=FALSE);
void GetBlueChannelHistogram(long lpBuffer[256], BOOL bPercent=FALSE);
直接像素访问方法
这些重要方法也已提供,请看下面
_PIXEL GetPixel(long x, long y);
void SetPixel(long x, long y, _PIXEL pixel);
然而,这些方法比直接内存访问要慢。您可以使用 GetData()
成员函数获取位图内存缓冲区。使用此内存缓冲区来执行您计划进行的任何“实时”位图处理。出于测试目的,使用直接像素访问方法更安全。
使用 CBitmapEx 类注意事项
使用所有这些方法,您应该能够完成您需要的任何类型的绘图和转换。嗯,几乎是任何类型。当然还有更多的滤镜要添加,以及更多的转换要应用。
此外,读者们的评论在更新原始源代码时非常有益。新的方法,例如从/向内存流加载/保存,以及使用插入点在自定义设备上下文 (DC) 上绘图的另一种方法,或者使用像素而不是百分比进行缩放,都已添加。
许多用于绘图的原始方法现在已用“内联汇编”编写,以提高速度。原始源代码保留注释以便更好地理解。欢迎所有关于 ASM 代码的优化(因为我不是这方面的专家)。
关于演示项目
有一个专门为演示项目编写的特殊类,名为 CEffectorBuilder
,用于创建动画。这个类使用原始的 CBitmapEx
类加载位图文件,然后对它们应用不同的转换。上面的截图显示了演示项目屏幕输出。这个类中有许多绘图和转换效果可用,所以请随意尝试。最终结果可能会非常棒。
关注点
在使用这个类的过程中,我找到了多次加速任何图形操作的完美方法。它是通过使用定点算术完成的。它几乎应用于 CBitmapEx
类中的所有操作,几乎在每个涉及浮点数的除法和乘法操作中。因此,不要惊讶于您仅使用此类别即可实现的实时动画速度。请查看演示项目,了解使用 CBitmapEx
类的实时动画项目的示例。