GDI双缓冲的一些学习

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GDI双缓冲的一些学习相关的知识,希望对你有一定的参考价值。

  重新回到VC做了几个月了,之前都是些业务代码的多一些,这两天开始要修改一个软件的界面,突然发现改起来挺吃力的,搞个背景图片都研究老半天,改来改去结果发现改错窗口,很是郁闷;看到里面一些代码之后,总有些疑问,所以回来研究了一下。

  双缓冲:主要是为了避免直接绘制在设备上会导致闪烁的问题,针对网上的一些文章,对流程做了一下梳理,流程主要是这样的:

  1、从设备DC创建第一层内存DC;

CDC backDC;
backDC.CreateCompatibleDC(pDC);

  2、创建设备兼容的位图;

HBITMAP hbmp = ::CreateCompatibleBitmap(pDC->m_hDC, rect.Width(), rect.Height());

  3、将位图选入第一层DC;

CBitmap* pOldBitmap = backDC.SelectObject(CBitmap::FromHandle(hbmp));

  4、创建第二层内存DC;

CDC srcDC;
srcDC.CreateCompatibleDC(pDC);

  5、导入要绘画的图片,将位图选入第二层DC,然后复制到第一层缓冲里

HBITMAP bmpDest = (HBITMAP)::LoadImage(AfxGetInstanceHandle(), strFileName, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if(bmpDest == NULL)
  return FALSE;
CBitmap *pOldBitmapSrc = srcDC.SelectObject(CBitmap::FromHandle(bmpDest));
BITMAP bm;
GetObject(bmpDest, sizeof(bm), &bm); backDC.SetStretchBltMode(HALFTONE); //防止失真 backDC.StretchBlt(0, 0, rect.Width()/2, rect.Height(), &srcDC, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);

  6、将第一层位图绘制好的内容,复制到第一层DC中;

pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &backDC, 0, 0, SRCCOPY);

  7、释放相关的资源(DC, HBITMAP等);

 

  对于双缓冲来说,还有额外一种作用,就是将要绘制的图片,存到bmp文件中;对于上面第二步创建的位图hbmp,在最后得到它就是要输出的内容,我们可以针对这个HBITMAP的句柄,来对他进行相关的操作,可以用来绘制到不同的窗口,或者保存到图片,保存图片的代码是根据网上找到的代码进行保存的,如下:

BOOL SaveBitmapToFile(HBITMAP hBitmap, LPCTSTR lpFileName) 
{ 
    HDC hDC; //设备描述表 
    int iBits; //当前显示分辨率下每个像素所占字节数 
    WORD wBitCount; //位图中每个像素所占字节数 
    DWORD dwPaletteSize=0, //定义调色板大小,   ,  
            dwBmBitsSize,  //位图中像素字节大小
            dwDIBSize,  //位图文件大小
            dwWritten;  //写入文件字节数
    BITMAP Bitmap; //位图属性结构 
    BITMAPFILEHEADER bmfHdr; //位图文件头结构 
    BITMAPINFOHEADER bi; //位图信息头结构 
    LPBITMAPINFOHEADER lpbi; //指向位图信息头结构 

    HANDLE fh, hDib, hPal,hOldPal=NULL; //定义文件,分配内存句柄,调色板句柄 

    //计算位图文件每个像素所占字节数 
    HDC hWndDC = ::CreateDC("DISPLAY",NULL,NULL,NULL); 
    hDC = ::CreateCompatibleDC( hWndDC ) ; 
    iBits = ::GetDeviceCaps(hDC, BITSPIXEL) * ::GetDeviceCaps(hDC, PLANES); 
    ::DeleteDC(hDC); 
    
    if (iBits <= 1) 
        wBitCount = 1; 
    else if (iBits <= 4) 
        wBitCount = 4; 
    else if (iBits <= 8) 
        wBitCount = 8; 
    else if (iBits <= 24) 
        wBitCount = 24; 
    else 
        wBitCount = 24 ; 

    //计算调色板大小 
    if (wBitCount <= 8) 
        dwPaletteSize = (1 << wBitCount) * sizeof(RGBQUAD); 

    //设置位图信息头结构 
    GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&Bitmap); 
    bi.biSize = sizeof(BITMAPINFOHEADER); 
    bi.biWidth = Bitmap.bmWidth; 
    bi.biHeight = Bitmap.bmHeight; 
    bi.biPlanes = 1; 
    bi.biBitCount = wBitCount; 
    bi.biCompression = BI_RGB; 
    bi.biSizeImage = 0; 
    bi.biXPelsPerMeter = 0; 
    bi.biYPelsPerMeter = 0; 
    bi.biClrUsed = 0; 
    bi.biClrImportant = 0; 

    dwBmBitsSize = ((Bitmap.bmWidth * wBitCount+31)/32) * 4 * Bitmap.bmHeight ; 

    //为位图内容分配内存 
    hDib = GlobalAlloc(GHND,dwBmBitsSize+dwPaletteSize+sizeof(BITMAPINFOHEADER)); 
    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib); 
    *lpbi = bi; 
    
    // 处理调色板 
    hPal = GetStockObject(DEFAULT_PALETTE); 
    if (hPal) 
    { 
        hDC = ::GetDC(NULL); 
        hOldPal = ::SelectPalette(hDC, (HPALETTE)hPal, FALSE); 
        RealizePalette(hDC); 
    } 
    
    // 获取该调色板下新的像素值 
    GetDIBits(hDC, hBitmap, 0, (UINT) Bitmap.bmHeight, 
    (LPSTR)lpbi + sizeof(BITMAPINFOHEADER) 
    +dwPaletteSize, 
    (LPBITMAPINFO ) 
    lpbi, DIB_RGB_COLORS); 

    //恢复调色板 
    if (hOldPal) 
    { 
        SelectPalette(hDC, (HPALETTE)hOldPal, TRUE); 
        RealizePalette(hDC); 
        ::ReleaseDC(NULL, hDC); 
    } 
    
    //创建位图文件 
    fh = CreateFile(lpFileName, GENERIC_WRITE, 
                        0, NULL, CREATE_ALWAYS, 
                        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); 

    if (fh == INVALID_HANDLE_VALUE) 
        return FALSE; 

    // 设置位图文件头 
    bmfHdr.bfType = 0x4D42; // "BM" 
    dwDIBSize = sizeof(BITMAPFILEHEADER) 
                    + sizeof(BITMAPINFOHEADER) 
                    + dwPaletteSize + dwBmBitsSize; 
    bmfHdr.bfSize = dwDIBSize; 
    bmfHdr.bfReserved1 = 0; 
    bmfHdr.bfReserved2 = 0; 
    bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) 
            + (DWORD)sizeof(BITMAPINFOHEADER) 
            + dwPaletteSize; 

    // 写入位图文件头 
    WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL); 

    // 写入位图文件其余内容 
    WriteFile(fh, (LPSTR)lpbi, dwDIBSize, &dwWritten, NULL); 

    //清除 
    GlobalUnlock(hDib); 
    GlobalFree(hDib); 
    CloseHandle(fh); 
    
    return TRUE; 
}

  类似的,我们也可以用双缓冲配合StretchBlt来做一些图片修改尺寸之类的操作;

  做了一些界面编程之后,觉得美工真的很重要,大概是以前做前端界面做得少了吧。

  

 

以上是关于GDI双缓冲的一些学习的主要内容,如果未能解决你的问题,请参考以下文章

GDI双缓冲

GDI双缓冲绘图

C 和 Windows GDI 中的双缓冲 *framework*

实例解说双缓冲

使用 GDI+ 在表单外渲染图形

在具有 DWM 合成的窗口上使用 GDI 绘图时是不是可以防止撕裂伪影?