Hoodlum1980 (fafa)'s Technological Blog

Languages mainly using and digging: C / CPP, ASM, C#, Python. Other languages:Java.

博客园 首页 新随笔 联系 订阅 管理

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

  补充说明:   -- 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复杂控件的综合使用

 

 

posted on 2010-01-26 22:50  hoodlum1980  阅读(4493)  评论(0编辑  收藏  举报