解决 StretchBlt 产生的像素堆积问题

简介:   ==========================================================   补充说明:   -- hoodlum1980 2010年1月28日   ==========================================================   我很快发现这篇文章实际上意义不大了。

  ==========================================================

  补充说明:   -- hoodlum1980 2010年1月28日

  ==========================================================

  我很快发现这篇文章实际上意义不大了。因为这是因为没有设置我们需要的拉伸模式导致的问题。

  图像失真是由于 StretchBlt 的默认模式是 BLACKONWHITE:(对产生重叠的像素进行AND操作)导致的。事实上解决这个问题的正确方式是在 StretchBlt 之前调用 SetStretchBltMode 函数设置模式,下文中采用的方法实际上是 COLORONCOLOR 模式(即删除像素),这种模式将完全舍弃那些产生重叠的行列信息。下面解释一下这些模式:(内容来自 MSDN)

  

BLACKONWHITE

  在保留像素和损失像素之间执行逻辑与(AND)操作。如果图片是单色位图,则被舍弃的黑色像素会在被保留的白色像素上保持黑色。

 

COLORONCOLOR

  删除像素。不保留那些被舍弃行列上的像素信息。 (备注:即本文后面采用的下采样方式)

 

HALFTONE

  把源矩形中的像素映射到目标矩形时,使像素的平均值近似相同。使用这种模式,应用程序必须然后调用  SetBrushOrgEx 校正画刷起始点,否则可能产生偏差。

 

WHITEONBLACK

  在保留像素和损失像素之间执行逻辑或(OR)操作。如果图片是单色位图,则被舍弃的白色像素会在被保留的黑色像素上保持白色。


  在拉伸绘制时,我们应该先进行模式设置:

 

     hdc = BeginPaint(hWnd, &ps);              
     SetStretchBltMode( hdc,  HALFTONE );
     HDC hMemDC = CreateCompatibleDC(hdc);
     SelectObject(hMemDC, m_Bitmap);
     StretchBlt( hdc, 0, 0, 102, 136, hMemDC, 0, 0, 331,372, SRCCOPY );
     DeleteDC(hMemDC);

  EndPaint(hWnd, *ps);

 

  以下是原文内容:

  ======================

 

  本文所提到的问题是一个在实际项目中遇到的问题,在 VC 中,通过 StretchBlt 函数来完成缩小位图,将导致像素堆积(效果可参考下图)。具体体现就是 GDI 可能在 StretchBlt 的实现是比较简单的,导致使用拉伸绘制后的图像分辨率严重失真,以至于不能符合应用的要求。因此我们必须解决这个问题。(PS:在我印象中,可能在 GDI+ 中是不存在这个问题的。)

  问题出现时,最开始我以为是在保存过程中的图像压缩质量导致的问题,但我把图像质量设置到 100% 时,图像质量依然没有任何改善,然后我发现其实图像质量的降低是发生在 StretchBlt 这一步,(DestRect 比原图小)一旦做了这个操作 ,则图像就变得面目全非,难以辨认细节。因此我很快的在网上搜索一些资料,也想过是不是要放弃CImage,该用网上的开源的CxImage。最终我采用的是在《CTreeCtrl和CListCtrl复杂控件的综合使用》一文中使用的方法:在matlab中叫做重采样(上采样-updample,下采样-downsample)。

  在GDI中,如果是放大的拉伸绘制,产生的结果就是像素被线性放大,将出现明显锯齿,实际上问题不大。(一般的应用程序在放大图像时, 会在像素方格内进行线性插值来柔和图像。)因此本文主要讨论的是缩小的拉伸绘制。

  缩小的拉伸绘制的原理非常简单,就是把图像缩小以后,我们把目标图像上的每个像素,按缩放比例去原图中选取相应像素,拷贝到目标图像中。 为了加快操作,我们使用图像的数据块进行操作。(在.NET中对应的大概是Bitmap.LockBits)

  代码如下:

img_405b18b4b6584ae338e0f6ecaf736533.gif code_stretchbltfast
// 缩放复制
void  StretchBltFast(CImage *  pDest,  int  xDest,  int  yDest,  int  cxDest,  int  cyDest, 
    CImage *  pSrc,  int  xSrc,  int  ySrc,  int  cxSrc,  int  cySrc)
{
    
int  i,j,k;
    LPBYTE pBitsSrc  =  (LPBYTE)(pSrc -> GetBits());  // 数据块起始位置
    LPBYTE pBitsDest  =  (LPBYTE)(pDest -> GetBits()); // 数据块起始位置
    LPBYTE pixAddrSrc  =  pBitsSrc;
    LPBYTE pixAddrDest  =  pBitsDest;

    
int  strideSrc  =  pSrc -> GetPitch();  // pitch有时为负
     int  strideDest  =  pDest -> GetPitch();
    
int  bytesPerPixelSrc  =  pSrc -> GetBPP() / 8 ;
    
int  bytesPerPixelDest  =  pDest -> GetBPP() / 8 ;
    
    
for  (j  =   0 ; j  <  cyDest; j ++ )
    {
        
for  (i  =   0 ; i  <  cxDest; i ++ )
        {
            pixAddrSrc  =  pBitsSrc  +  (j  *  cySrc  /  cyDest)  *  strideSrc  +  (i  *  cxSrc  /  cxDest)  *   bytesPerPixelSrc;
            pixAddrDest  =  pBitsDest  +  strideDest  *  j   +  i   *  bytesPerPixelDest;

            
// 复制当前像素
             for  (k  =   0 ; k  <  bytesPerPixelDest; k ++ , pixAddrDest ++ )
            {
                 * pixAddrDest  =   * pixAddrSrc;

                
// 是否可以移动到下一个通道?
                 if (k  <  bytesPerPixelSrc  -   1 ) pixAddrSrc ++ ;
            }
        }
    }
}


  使用上面的重采样方法和GDI的StretchBlt方法绘制的图像效果如下:(可见重采样方法的效果是要好过StretchBlt)

  


  补充一些其他讨论:

  (1)MSDN中提到CImage可以使用32bpp的图片进行 alpha 合成, 即使用第四个通道作为每个像素的 alpha 值。本质上是通过调用 AlphaBlend 来实现的。根据 AlphaBlend 函数的要求,在绘制(draw)前必须预先把alpha通道应用到位图的RGB通道上(RGB*Alpha/255); 绘制结果如下所示:


 

  

 

  预先应用alpha通道将改变RGB通道中的数据,代码如下所示:

 

img_405b18b4b6584ae338e0f6ecaf736533.gif Code_PreMultiplied
// 对CImage预先应用alpha通道
void  PreMultiplied(CImage *  pImg)
{
    
int  i, j;
    LPBYTE pPixel;

    
// 必须是32bpp
     if (pImg -> GetBPP()  !=   32 )
        
return ;

    LPBYTE pBytes 
=  (LPBYTE)pImg -> GetBits();
    
int  stride  =  pImg -> GetPitch();

    
int  width  =  pImg -> GetWidth();
    
int  height  =  pImg -> GetHeight();

    
for (j = 0 ; j < height; j ++ )
    {
        
for (i = 0 ; i < width;i ++ )
        {
            pPixel 
=  pBytes  +  j  *  stride  +  i  *   4 ;
            pPixel[
0 ]  =  (BYTE)((UINT)pPixel[ 0 ]  *  pPixel[ 3 ] / 0xff );
            pPixel[
1 ]  =  (BYTE)((UINT)pPixel[ 1 ]  *  pPixel[ 3 ] / 0xff );
            pPixel[
2 ]  =  (BYTE)((UINT)pPixel[ 2 ]  *  pPixel[ 3 ] / 0xff );
        }
    }
}

 

  在上图中,左上角是一个32bpp的图像绘制结果。 在下方我分别绘制了 0,1,2,3 每个通道的图像(转变成灰度图像),其中RGB通道是在应用Alpha通道前的数据。(可以事先创建一个24bpp的同等大小图像去接收某个通道数据)

 

img_405b18b4b6584ae338e0f6ecaf736533.gif code_getchannel
// 获取指定的通道,填充到pDest中(灰度图像)
void  GetChannel(CImage *  pDest, CImage *  pSrc,  int  channel)
{
    
int  i, j, k;

    LPBYTE pPixelDest, pPixelSrc;
    LPBYTE pBytesDest  =  (LPBYTE)pDest -> GetBits();
    LPBYTE pBytesSrc  =  (LPBYTE)pSrc -> GetBits();

    
int  strideDest  =  pDest -> GetPitch();
    
int  strideSrc  =  pSrc -> GetPitch();

    
int  width  =  pDest -> GetWidth();
    
int  height  =  pSrc -> GetHeight();
    
int  bppDest  =  pDest -> GetBPP();
    
int  bppSrc  =  pSrc -> GetBPP();

    
for (j = 0 ; j < height; j ++ )
    {
        
for (i = 0 ; i < width;i ++ )
        {
            pPixelSrc  =  pBytesSrc  +  j  *  strideSrc  +  i * bppSrc / 8   +  channel;
            pPixelDest  =  pBytesDest  +  j  *  strideDest  +  i * bppDest / 8 ;
            
for (k = 0 ; k  <  bppDest / 8 ; k ++ , pPixelDest ++ )
            {
                
* pPixelDest  =   * pPixelSrc;
            }
        }
    }
}
 


 

   本文参考以下资料:

  (1) CTreeCtrl和CListCtrl复杂控件的综合使用 ;

 

 

目录
相关文章
|
算法 C++ 索引
【C++STL基础入门】深入浅出string类查找字串、返回字串和交换操作
【C++STL基础入门】深入浅出string类查找字串、返回字串和交换操作
1407 1
|
8月前
|
机器学习/深度学习 存储 人工智能
线上观看人次18万+!智算技术沙龙圆满落幕(附 PPT 下载)
与现场超 100 位参会嘉宾一起为大模型效能提升与自主算力平台落地提供创新思路。
|
9月前
|
安全 前端开发 测试技术
微信小游戏的验收
微信小游戏验收需从技术、交付、业务、安全四维度严格把关,涵盖性能、源码、逻辑、防作弊等关键点,确保产品可玩、可控、可持续运营,避免外包风险与后续维护陷阱。
|
算法 自动驾驶 机器人
MATLAB中实现LSD直线检测
MATLAB中实现LSD直线检测
|
9月前
|
小程序 JavaScript 前端开发
2026最新基于Vue+thinkPhP6前后端分离的婚恋交友管理系统/交友小程序源码搭建
基于ThinkPHP6+Vue.js构建,采用MySQL存储数据、Redis提升性能,实现用户管理、智能匹配与实时聊天。前端使用Vue+UniApp多端适配,Element UI与uView优化交互,通过WebSocket与JWT保障实时通信与安全认证,支持H5、小程序及App全平台部署。
456 0
|
11月前
|
人工智能 自然语言处理 搜索推荐
2025年汽车行业智能营销推荐,营销 agent 产品选型指南
面对汽车消费者决策复杂化,传统营销亟需升级。本文聚焦2025年汽车行业智能营销趋势,解析瓴羊Quick Audience等主流“营销Agent”产品在全域数据融合、AI人群圈选、自动化旅程等方面的核心能力,结合不同车企类型提供选型建议,助力实现精准转化与合规运营。(238字)
|
安全 IDE 开发工具
电脑错误代码0xc0000001解决步骤
以下是解决电脑错误代码0xc0000001的详细步骤,综合了多个高可信度来源的修复方案:
|
Prometheus 监控 关系型数据库
数据库同步革命:MySQL GTID模式下主从配置的全面解析
数据库同步革命:MySQL GTID模式下主从配置的全面解析
2052 0
|
存储 机器学习/深度学习 算法
蓝桥杯练习题(三):Python组之算法训练提高综合五十题
蓝桥杯Python编程练习题的集合,涵盖了从基础到提高的多个算法题目及其解答。
1037 3
蓝桥杯练习题(三):Python组之算法训练提高综合五十题
|
缓存 Serverless 调度
Moka:ACS 容器计算服务助力CI/CD场景提效降本 70%
Moka,作为国内发展最快的HR SaaS厂商之一,在ACS产品初期就与阿里云产品团队紧密联系产品需求及体验反馈,本文将着重分享我们使用ACS在CI/CD 场景的最佳实践。

热门文章

最新文章