65.9K
CodeProject 正在变化。 阅读更多。
Home

CBitmapEx - 免费 C++ 位图操作类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (126投票s)

2008年9月6日

CPOL

11分钟阅读

viewsIcon

992611

downloadIcon

18085

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

Effector - Screenshot

引言

本文介绍了一个“简单”的 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:将鼠标指针放在每个图像上方以查看应用的转换)

Original Enlarged 200% Rotated 45deg. Cropped Flipped horizontal Flipped vertical Mirrored left Mirrored right Mirrored top Mirrored bottom

简单位图过滤

已实现了 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:将鼠标指针放在每个图像上方以查看应用的滤镜)

Original Negative Grayscale Sepia Emboss Engrave Pixelize Brightness Contrast Blur Gaussian Blur Sharp Colorize RankMin RankMax Spread Offset Black and White Edge detect Glowing edges Histogram Equalization Median Posterize Solarize

绘制位图

以下方法列表构成了 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:将鼠标指针放在每个图像上方以查看应用的绘图)

Original 1 Original 2
Simple alpha blending Horizontal alpha blending Vertical alpha blending Radial alpha blending Perspective alpha blending Forward diagonal alpha blending Backward diagonal alpha blending Combined drawing

更改位图中的颜色

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

Original 3 Original 3 Recolored

以下代码片段展示了如何完成此操作

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);

实际上,您从 xy 位置获取像素,并将其颜色更改为 newColor。同时,您指定了要应用的新颜色的透明度级别 alpha,以及“强度”,即 error 值。最后一个参数 bImage 用于重新着色整个图像,或者仅着色由“强度”定义的区域。您必须通过实验才能了解此方法在不同图像上的工作方式。如果您想更改图像的背景颜色,或者重新着色灰度图像,这可能会很有用。

创建阴影效果

这是一种非常流行的效果,使用 CBitmapEx 类可以轻松实现。请参见下图

Original 4 Original 5 Drop Shadow Effect

以下代码片段展示了如何完成此操作

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 类在位图上输出一些文本。请参见下图

Text on the bitmap

以下代码片段展示了如何完成此操作

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();

请看下面的结果

Large fire effect Small fire effect

以下是所用方法的定义

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();

请看下面的结果

Water effect

以下是所用方法的定义

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();

请看下面的结果

Smoke effect

以下是所用方法的定义

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 类的实时动画项目的示例。

© . All rights reserved.