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


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

      补充说明:   -- 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)

      代码如下:

    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通道中的数据,代码如下所示:

    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的同等大小图像去接收某个通道数据)

    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复杂控件的综合使用

  • 相关阅读:
    9、Spring Boot 2.x 集成 Thymeleaf
    【专题】Spring Boot 2.x 面试题
    8、Spring Boot 2.x 服务器部署
    7、Spring Boot 2.x 集成 Redis
    6、Spring Boot 2.x 集成 MyBatis
    5、Spring Boot 2.x 启动原理解析
    4、Spring Boot 2.x 自动配置原理
    3、Spring Boot 2.x 核心技术
    2、Spring Boot 2.x 快速入门
    centOS下安装JDK1.8.60,glassfish4.1.1以及MySQL
  • 原文地址:https://www.cnblogs.com/hoodlum1980/p/1657098.html
Copyright © 2020-2023  润新知