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

 

 

目录
相关文章
|
API Android开发 C++
ToplingDB 如何减小写放大:功大欺理
ToplingDB,虽然 fork 自 RocksDB 并且兼容其 API,但实现了脱胎换骨的改进,最重要的就是实现了性能更高的 CSPP MemTable(Rep) 和 SST。
278 0
|
7月前
|
编解码 JavaScript 算法
通过PHAsset获取的图片上传后变大和图像被旋转90度问题完美解决方案
通过PHAsset获取的图片上传后变大和图像被旋转90度问题完美解决方案
100 4
|
7月前
|
算法 计算机视觉
OpenCV(四十四):亚像素级别角点位置优化
OpenCV(四十四):亚像素级别角点位置优化
193 0
|
7月前
|
计算机视觉
[OpenCv] 自适应阀值的二值化处理
[OpenCv] 自适应阀值的二值化处理
50 1
|
存储 数据可视化 索引
校正图像亮度不均匀问题并分析前景对象
校正图像亮度不均匀问题并分析前景对象
116 0
失真函数、失真矩阵与平均失真
失真函数、失真矩阵与平均失真
217 0
|
存储 数据可视化
基于 ggridges 绘制剩余使用寿命密度图
基于 ggridges 绘制剩余使用寿命密度图
125 0
|
计算机视觉 C++
图像等比例缩小【OpenCV】
图像等比例缩小【OpenCV】
237 0
图像等比例缩小【OpenCV】
Halcon颜色通道分离与合并RGB--HSI--HSV;饱和度/亮度均衡
Halcon颜色通道分离与合并RGB--HSI--HSV;饱和度/亮度均衡
983 0
|
移动开发 算法
大厂面试高频题详解:包裹黑色像素点的最小矩形
大厂面试高频题详解:包裹黑色像素点的最小矩形
大厂面试高频题详解:包裹黑色像素点的最小矩形