zoukankan      html  css  js  c++  java
  • 解决 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复杂控件的综合使用

  • 相关阅读:
    深入A标签点击触发事件而不跳转的详解
    js、css、html判断浏览器的各种版本
    深入理解this对象
    背景透明文字不透明的最佳方法兼容IE(以背景黑色透明度0.5为例)
    解决ie6支持最大高度最小高度的方法
    js点击更多显示更多内容效果
    artdialog关闭弹出窗口
    (巧用)事件代理
    CSS3盒模型display:-webkit-box;的使用
    文件上传input type="file"样式美化
  • 原文地址:https://www.cnblogs.com/hoodlum1980/p/1657098.html
Copyright © 2011-2022 走看看