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

CImage 像素访问性能优化

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.27/5 (5投票s)

2007年7月10日

CPOL

3分钟阅读

viewsIcon

59713

downloadIcon

912

本文介绍了一个简单的性能改进的 CImage 类包装器。

Screenshot - CImage_pixel_access_optimization.jpg

引言

正如您可能知道的,尤其是当您通过 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 类的临时堆栈变量,然后更改/添加对 SetPixelGetPixel 方法的调用,以便它们使用临时优化器对象,而不是直接使用 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 表中访问像素信息。使用临时类对象的事实使原始代码尽可能简单,但同时为您提供了所有需要的优化区域。 GetPixelSetPixel 调用之间恒定的每个操作都在 CImagePixelAccessOptimizer 类的构造函数中执行和记住。 DIB 表的行宽计算,或获取调色板表和图像尺寸,仅执行一次。

感谢这一点,GetPixelSetPixel 方法可能非常快,下降到只有一个 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 分配的内存之外。如果您发现任何类似的情况或代码无法正确工作的图像,请告诉我。

© . All rights reserved.