CImage 像素访问性能优化






4.27/5 (5投票s)
本文介绍了一个简单的性能改进的 CImage 类包装器。
引言
正如您可能知道的,尤其是当您通过 Google 找到这篇文章时 :), ATL CImage
类的像素访问性能非常糟糕。尽管这是一个相当普遍的问题,但我没有找到解决该问题的最简单(当然不是最佳 :) )的解决方案。基于 Bitmap usage extension library,我创建了一个简单的包装类,它通过直接访问位图位来提供像素访问。通过一个小的重构,它可以很容易地分离出来,以便为独立的 DIB 提供像素访问。
该类的设计方式是为了允许轻松优化或扩展已经使用 CImage
类的软件项目,或者在新项目中使用 CImage
,而无需担心像素访问性能。
包装类的公共接口和用法
class CImagePixelAccessOptimizer
{
public:
CImagePixelAccessOptimizer( CImage* _image );
CImagePixelAccessOptimizer( const CImage* _image );
~CImagePixelAccessOptimizer();
COLORREF GetPixel( int _x, int _y ) const;
void SetPixel( int _x, int _y, const COLORREF _color );
};
如果您的代码中需要快速的逐像素访问,您所要做的就是创建一个 CImagePixelAccessOptimizer
类的临时堆栈变量,然后更改/添加对 SetPixel
和 GetPixel
方法的调用,以便它们使用临时优化器对象,而不是直接使用 CImage
对象。来自我的领域的一个例子是简单的图像旋转
CImagePixelAccessOptimizer tempImageOpt( pTmpImage );
CImagePixelAccessOptimizer currImageOpt( pCurrentImage );
for( unsigned x=0; x < uOrgWidth; ++x )
{
for( unsigned y=0; y < uOrgHeight; ++y )
{
tempImageOpt.SetPixel( uOrgHeight - y - 1, x, currImageOpt.GetPixel( x, y ) );
}
}
这可能不是旋转图像的最快方式,但它有效,并且很好地说明了问题。
一些内部原理
该类封装了这里和那里找到的简单方法,这些方法允许您基于 DIB 表的本机格式直接从 DIB 表中访问像素信息。使用临时类对象的事实使原始代码尽可能简单,但同时为您提供了所有需要的优化区域。 GetPixel
和 SetPixel
调用之间恒定的每个操作都在 CImagePixelAccessOptimizer
类的构造函数中执行和记住。 DIB 表的行宽计算,或获取调色板表和图像尺寸,仅执行一次。
感谢这一点,GetPixel
和 SetPixel
方法可能非常快,下降到只有一个 switch
语句和一个相当简单的表间接或两次。
inline COLORREF CImagePixelAccessOptimizer::GetPixel( int _x, int _y ) const
{
ASSERT( PositionOK( _x, _y ) );
FOR_GET_SET_PIXEL_ASSERT( const COLORREF color = m_image->GetPixel( _x, _y ) );
const RGBQUAD* rgbResult = NULL;
RGBQUAD tempRgbResult;
switch( m_bitCnt )
{
case 1: //Monochrome
rgbResult = &m_colors[ *(m_bits + m_rowBytes*_y + _x/8) &
(0x80 >> _x%8) ];
break;
case 4:
rgbResult = &m_colors[ *(m_bits + m_rowBytes*_y + _x/2) &
((_x&1) ? 0x0f : 0xf0) ];
break;
case 8:
rgbResult = &m_colors[ *(m_bits + m_rowBytes*_y + _x) ];
break;
case 16:
{
WORD dummy = *(LPWORD)(m_bits + m_rowBytes*_y + _x*2);
tempRgbResult.rgbBlue = (BYTE)(0x001F & dummy);
tempRgbResult.rgbGreen = (BYTE)(0x001F & (dummy >> 5));
tempRgbResult.rgbRed = (BYTE)(0x001F & dummy >> 10 );
rgbResult = &tempRgbResult;
}
break;
case 24:
rgbResult = (LPRGBQUAD)(m_bits + m_rowBytes*_y + _x*3);
break;
case 32:
rgbResult = (LPRGBQUAD)(m_bits + m_rowBytes*_y + _x*4);
break;
default:
//error
ASSERT( false );
break;
}
const COLORREF rgbResultColorRef = RGB( rgbResult->rgbRed,
rgbResult->rgbGreen, rgbResult->rgbBlue );
GET_SET_PIXEL_ASSERT( rgbResultColorRef == color );
return rgbResultColorRef;
}
调试
如果您发现代码存在颜色设置错误或位置错误的问题,请尝试取消注释以下内容
#define ENABLE_GET_SET_PIXEL_VERIFICATION
它将启用检查,其中优化的结果将与 CImage
类本身提供的行为进行比较 - 请报告您发现的任何问题。
用于检查的代码可以在上面的例子中看到。如果定义了 ENABLE_GET_SET_PIXEL_VERIFICATION
,那么 GET_SET_PIXEL_ASSERT
就变成了一个“标准” ASSERT
( ;) ) 语句,而 FOR_GET_SET_PIXEL_ASSERT
就变成了封闭的语句。如果未定义 ENABLE_GET_SET_PIXEL_VERIFICATION
,则两个定义都给出空语句。感谢这一点,您可以使用单个定义启用额外的代码和断言,同时保持代码的干净和简单(没有三行 #ifdef
)。
默认情况下,CImagePixelAccessOptimizer
不使用额外的验证,因为它会把我们带回到我们开始时的性能方面 :)。
成功案例 ;)
我已经优化了我简单的图像查看和坏像素检测程序 ImageViewer 中几乎所有的像素访问性能问题,使用这种方法 - 从一个主要的用户体验问题,像素访问性能在几个小时内变成了一个没有问题的问题 - 现在,它对您来说只需几秒钟。 :)
说实话,完全不使用内置的 CImage
类可能不是最好的主意,但是如果您已经在那里或者不想安装/链接一些第三方库到您的项目中,那么位于单个头文件中的这个简单包装器可能正是您需要的。您可以免费获得它,但有一个例外 :) - 在运行来自 Bitmap usage extension library 的代码时,我发现一个问题导致了“内存无法读取”的问题 - 该代码从 24 位 DIB 表的末尾复制了整个 RGBQUAD
结构 - RGBQUAD
结构的保留成员位于为 DIB 分配的内存之外。如果您发现任何类似的情况或代码无法正确工作的图像,请告诉我。