win32 Bitblt And Capturing an Image

BitBlt 函数 执行 位块传输 ,传输的内容是 一个device context (DC)中的一个矩形区域的像素的颜色数据。
传输过程是从一个device contex(DC)传送到另外一个Device Contex(DC)
并且可以针对ROP 参数的设置,来改动像素的值。

BOOL BitBlt(
  HDC   hdc,
  int   x,
  int   y,
  int   cx,
  int   cy,
  HDC   hdcSrc,
  int   x1,
  int   y1,
  DWORD rop
);

BOOL BitBlt(

    HDC hdcDest, 

    int nXDest, 

    int nYDest, 

    int nWidth, 

    int nHeight, 

    HDC hdcSrc, 

    int nXSrc, 

    int nYSrc, 

    DWORD dwRop
);

参数:

hdcDest
[in] Handle to the destination device context. DC

nXDest
[in] 指定目标矩形区域的左上角的X坐标值

nYDest
[in]指定目标矩形区域的左上角的Y坐标值

nWidth
[in] 指定源和目的矩形的宽度

nHeight
[in] 指定源和目的矩形的高度

hdcSrc
[in] Handle to the source device context. DC

nXSrc
[in]指定源矩形的左上角那一点的X轴的坐标

nYSrc
[in] 指定源矩形的左上角那一点的Y轴的坐标

dwRop
[in] Specifies a raster-operation code.
指定光栅操作码
这个操作码定义源矩形中的颜色数据,如何和目标矩形中的颜色数据 结合,以生成最终的颜色。

下面是常用的光栅操作码:主要看(SRCCOPY

Value Description
BLACKNESS Fills the destination rectangle using the color associated with index 0 in the physical palette. This color is black for the default physical palette.
DSTINVERT Inverts the destination rectangle.
MERGECOPY Merges the colors of the source rectangle with the specified pattern by using the Boolean AND operator.
MERGEPAINT Merges the colors of the inverted source rectangle with the colors of the destination rectangle by using the Boolean OR operator.
NOTSRCCOPY Copies the inverted source rectangle to the destination.
NOTSRCERASE Combines the colors of the source and destination rectangles by using the Boolean OR operator and then inverts the resultant color.
PATCOPY Copies the specified pattern into the destination bitmap.
PATINVERT Combines the colors of the specified pattern with the colors of the destination rectangle by using the Boolean XOR operator.
PATPAINT Combines the colors of the pattern with the colors of the inverted source rectangle by using the Boolean OR operator.The result of this operation is combined with the colors of the destination rectangle by using the Boolean OR operator.
SRCAND Combines the colors of the source and destination rectangles by using the Boolean AND operator.
SRCCOPY Copies the source rectangle directly to the destination rectangle.
SRCERASE Combines the inverted colors of the destination rectangle with the colors of the source rectangle by using the Boolean AND operator.
SRCINVERT Combines the colors of the source and destination rectangles by using the Boolean XOR operator.
SRCPAINT Combines the colors of the source and destination rectangles by using the Boolean OR operator.
WHITENESS Fills the destination rectangle using the color associated with index 1 in the physical palette.This color is white for the default physical palette.

返回值

如果成功,返回非0
如果失败,返回 0
得到进一步的信息,使用函数GetLastError.

例子代码

下面的代码,演示如何使用BitBlt 复制一个 bitmap中的像素到另外一个bitmap中。
注意:
为了代码的易读,没有包括错误检查。
这个代码例子不能用在release配置中,除非修改后包含了安全error执行。

HBITMAP CopyBitmap( HBITMAP hbm) {
    HDC hdcSrc = CreateCompatibleDC(NULL);
    HDC hdcDst = CreateCompatibleDC(NULL);
    HBITMAP hbmOld, hbmOld2, hbmNew;
    BITMAP bm;
    GetObject(hbm, sizeof(bm), &bm);
    hbmOld = SelectObject(hdcSrc, hbm);
    hbmNew = CreateBitmap( bm.bmWidth, bm.bmHeight, bm.bmPlanes,
bm.bmBitsPixel,
            NULL);
    hbmOld2 = SelectObject(hdcDst, hbmNew);
    BitBlt(hdcDst, 0, 0, bm.bmWidth, bm.bmHeight, hdcSrc, 0, 0, SRCCOPY);
    SelectObject(hdcSrc, hbmOld);
    DeleteDC(hdcSrc);
    DeleteDC(hdcDst);
    return hbmNew;
}

备注

如果源device context正在旋转或者剪切,BitBlt将会返回错误。
If other transformations exist in the source device context (and a matching transformation is not in effect in the destination device context), the rectangle in the destination device context is stretched, compressed, or rotated as necessary.
If the color formats of the source and destination device contexts do not match, the BitBlt function converts the source color format to match the destination format.
When an enhanced metafile is being recorded, an error occurs if the source device context identifies an enhanced-metafile device context.
不是所有的设置都支持 BitBlt函数。
For more information, see the RC_BITBLT raster capability entry in the GetDeviceCaps function, as well as the MaskBlt and StretchBlt functions.
如果源device context 和目标device context 是不同的设备,BitBlt会返回错误。

For information about blitting to displays with right-to-left orientations, see Creating Bitmaps.

Creating Bitmaps (Windows CE 5.0)

https://docs.microsoft.com/en-us/previous-versions/windows/embedded/ms906571%28v%3dmsdn.10%29

备注2

BitBlt only does clipping on the destination DC.
BitBlt只是在目标DC上执行粘贴。
If a rotation or shear transformation is in effect in the source device context, BitBlt returns an error. If other transformations exist in the source device context (and a matching transformation is not in effect in the destination device context), the rectangle in the destination device context is stretched, compressed, or rotated, as necessary.
If the color formats of the source and destination device contexts do not match, the BitBlt function converts the source color format to match the destination format.
When an enhanced metafile is being recorded, an error occurs if the source device context identifies an enhanced-metafile device context.
不是所有的设置都支持 BitBlt函数。
For more information, see the RC_BITBLT raster capability entry in the GetDeviceCaps function as well as the following functions: MaskBlt, PlgBlt, and StretchBlt.
如果源device context 和目标device context 是不同的设备,BitBlt会返回错误。

如果要在不同的设备DC之间传送数据,需要将内存位图(memory bitmap)转换为DIB,转换的方式是调用GetDIBits。在其他的设备上,显示DIB,需要调用SetDIBits 或者 StretchDIBits 。
那么在 相同类型的设备的DC 之间传送数据,可以直接调用BitBlt 传送内存位图(memory bitmap)

ICM: No color management is performed when blits occur.

例子: Capturing an Image.

https://docs.microsoft.com/zh-cn/windows/desktop/gdi/capturing-an-image

bitmap 和 BMP文件 不是一个概念。

你可以使用bitmap来捕捉图像,可以存储该图像在内存中,在你的应用的窗口的不同位置显示该图像,或者在其他的窗口显示该图像。
有时候,你可能只是希望你的应用,捕捉图像(images),并且临时的保存它们。
比如,当你放大或者缩小绘图应用中的图片的时候,应用必须临时保存正常视图大小的图像,然后显示缩放的视图。
然后,当用户选择正常视图的时候,应用必须替换缩放的图像为临时保存的正常视图图像。
为了临时保存图像,应用,需要调用CreateCompatibleDC 来创建一个DC,这个DC是兼容当前窗口DC的。
当你创建一个compatible DC之后,你创建一个合适尺寸的bitmap,创建的方法是调用CreateCompatibleBitmap函数,
然后通过调用SelectObject函数,来选择该bitmap到当前的device context— compatible DC。

当创建了 compatible device context之后,并且合适的bitmap 已经选择到该 compatible device context之后,
你就可以进行图像的捕捉了。
使用BitBlt函数来捕捉图像。
This function performs a bit block transfer that is, it copies data from a source bitmap into a destination bitmap.
这个函数执行一个位块传输,它从一个源bitmap复制数据到一个目标bitmap。

但是,这个函数的两个参数不是bitmap的句柄。
BitBlt 使用句柄是两个不同的device contexts。
复制源DC中的bitmap对应的bitmap数据,到目标DC中的bitmap。
BitBlt receives handles that identify two device contexts and copies the bitmap data from a bitmap selected into the source DC into a bitmap selected into the target DC.

在这个例子中,目标DC 是一个compatible DC
所以当BitBlt 完成传输,图像数据已经保存在内存中了。
为了重新显示图像,再一次调用BitBlt,指定compatible DC 为源DC,一个窗口DC作为目标DC。

下面的例子代码来自一个应用,该应用捕捉整个桌面的图像。缩小到当前窗口的大小,然后保存到一个文件中。

在win7 64 位下面:
使用vs2010 创建一个 MFC 对话框工程,
添加一个按钮,并且为该按钮添加一个事件:
最终的工具界面如下:
这里写图片描述

点击 按钮之后的,界面如下:(捕捉的是整个桌面)
这里写图片描述

关键代码:

int CaptureAnImage(HWND hWnd);

void CCaptureImageDlg::OnBnClickedBtnCapture()
{
    // TODO: 在此添加控件通知处理程序代码
    HWND hWnd;

    hWnd = AfxGetMainWnd()->m_hWnd;

    CaptureAnImage(hWnd);
}


int CaptureAnImage(HWND hWnd)
{
    HDC hdcScreen;
    HDC hdcWindow;
    HDC hdcMemDC = NULL;
    HBITMAP hbmScreen = NULL;
    BITMAP bmpScreen;

    // Retrieve the handle to a display device context for the client 
    // area of the window. 
    hdcScreen = GetDC(NULL);
    hdcWindow = GetDC(hWnd);

    // Create a compatible DC which is used in a BitBlt from the window DC
    hdcMemDC = CreateCompatibleDC(hdcWindow); 

    if(!hdcMemDC)
    {
        MessageBox(hWnd, L"CreateCompatibleDC has failed",L"Failed", MB_OK);
        goto done;
    }

    // Get the client area for size calculation
    RECT rcClient;
    GetClientRect(hWnd, &rcClient);

    //This is the best stretch mode
    SetStretchBltMode(hdcWindow,HALFTONE);

    //The source DC is the entire screen and the destination DC is the current window (HWND)
    if(!StretchBlt(hdcWindow, 
               0,0, 
               rcClient.right, rcClient.bottom, 
               hdcScreen, 
               0,0,
               GetSystemMetrics (SM_CXSCREEN),
               GetSystemMetrics (SM_CYSCREEN),
               SRCCOPY))
    {
        MessageBox(hWnd, L"StretchBlt has failed",L"Failed", MB_OK);
        goto done;
    }

    // Create a compatible bitmap from the Window DC
    hbmScreen = CreateCompatibleBitmap(hdcWindow, rcClient.right-rcClient.left, rcClient.bottom-rcClient.top);

    if(!hbmScreen)
    {
        MessageBox(hWnd, L"CreateCompatibleBitmap Failed",L"Failed", MB_OK);
        goto done;
    }

    // Select the compatible bitmap into the compatible memory DC.
    SelectObject(hdcMemDC,hbmScreen);

    // Bit block transfer into our compatible memory DC.
    if(!BitBlt(hdcMemDC, 
               0,0, 
               rcClient.right-rcClient.left, rcClient.bottom-rcClient.top, 
               hdcWindow, 
               0,0,
               SRCCOPY))
    {
        MessageBox(hWnd, L"BitBlt has failed", L"Failed", MB_OK);
        goto done;
    }

    // Get the BITMAP from the HBITMAP
    GetObject(hbmScreen,sizeof(BITMAP),&bmpScreen);

    BITMAPFILEHEADER   bmfHeader;    
    BITMAPINFOHEADER   bi;

    bi.biSize = sizeof(BITMAPINFOHEADER);    
    bi.biWidth = bmpScreen.bmWidth;    
    bi.biHeight = bmpScreen.bmHeight;  
    bi.biPlanes = 1;    
    bi.biBitCount = 32;    
    bi.biCompression = BI_RGB;    
    bi.biSizeImage = 0;  
    bi.biXPelsPerMeter = 0;    
    bi.biYPelsPerMeter = 0;    
    bi.biClrUsed = 0;    
    bi.biClrImportant = 0;

    DWORD dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;

    // Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that 
    // call HeapAlloc using a handle to the process's default heap. Therefore, GlobalAlloc and LocalAlloc 
    // have greater overhead than HeapAlloc.
    HANDLE hDIB = GlobalAlloc(GHND,dwBmpSize); 
    char *lpbitmap = (char *)GlobalLock(hDIB);    

    // Gets the "bits" from the bitmap and copies them into a buffer 
    // which is pointed to by lpbitmap.
    GetDIBits(hdcWindow, hbmScreen, 0,
        (UINT)bmpScreen.bmHeight,
        lpbitmap,
        (BITMAPINFO *)&bi, DIB_RGB_COLORS);

    // A file is created, this is where we will save the screen capture.
    HANDLE hFile = CreateFile(L"captureqwsx.bmp",
        GENERIC_WRITE,
        0,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL, NULL);   

    // Add the size of the headers to the size of the bitmap to get the total file size
    DWORD dwSizeofDIB = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    //Offset to where the actual bitmap bits start.
    bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER); 

    //Size of the file
    bmfHeader.bfSize = dwSizeofDIB; 

    //bfType must always be BM for Bitmaps
    bmfHeader.bfType = 0x4D42; //BM   

    DWORD dwBytesWritten = 0;
    WriteFile(hFile, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
    WriteFile(hFile, (LPSTR)&bi, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
    WriteFile(hFile, (LPSTR)lpbitmap, dwBmpSize, &dwBytesWritten, NULL);

    //Unlock and Free the DIB from the heap
    GlobalUnlock(hDIB);    
    GlobalFree(hDIB);

    //Close the handle for the file that was created
    CloseHandle(hFile);

    //Clean up
done:
    DeleteObject(hbmScreen);
    DeleteObject(hdcMemDC);
    ReleaseDC(NULL,hdcScreen);
    ReleaseDC(hWnd,hdcWindow);

    return 0;
}

整体工程代码:如下:
https://download.csdn.net/download/wowocpp/10500454

使用GetDIBits直接读取位图数据

https://blog.csdn.net/iamshuke/article/details/5749948


#include <math.h>
void CDibtestDlg::OnOK() 
{
    // TODO: Add extra validation here
    HDC hDesktopDC = ::GetDC(NULL);
    HDC hTmpDC = CreateCompatibleDC(hDesktopDC);
    HBITMAP hBmp = CreateCompatibleBitmap(hDesktopDC, 351, 250);    //351x250, 示例数据
    SelectObject(hTmpDC, hBmp);
    BitBlt(hTmpDC, 0, 0, 351, 250, hDesktopDC, 0, 0, SRCCOPY);
    DeleteObject(hTmpDC);

    BITMAP bm;
    PBITMAPINFO bmpInf;
    if(GetObject(hBmp,sizeof(bm),&bm)==0)
    {
        ::ReleaseDC(NULL,hDesktopDC);
        return ;
    }

    int nPaletteSize=0;
    if(bm.bmBitsPixel<16)
        nPaletteSize=(int)pow(2,bm.bmBitsPixel);

    bmpInf=(PBITMAPINFO)LocalAlloc(LPTR,sizeof(BITMAPINFOHEADER)+
        sizeof(RGBQUAD)*nPaletteSize+(bm.bmWidth+7)/8*bm.bmHeight*bm.bmBitsPixel);

    BYTE* buf=((BYTE*)bmpInf) + 
        sizeof(BITMAPINFOHEADER)+
        sizeof(RGBQUAD)*nPaletteSize;

    //-----------------------------------------------
    bmpInf->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmpInf->bmiHeader.biWidth = bm.bmWidth;
    bmpInf->bmiHeader.biHeight = bm.bmHeight;
    bmpInf->bmiHeader.biPlanes = bm.bmPlanes;
    bmpInf->bmiHeader.biBitCount = bm.bmBitsPixel;
    bmpInf->bmiHeader.biCompression = BI_RGB;
    bmpInf->bmiHeader.biSizeImage = (bm.bmWidth+7)/8*bm.bmHeight*bm.bmBitsPixel;
    //-----------------------------------------------

    if(!::GetDIBits(hDesktopDC,hBmp,0,(UINT)bm.bmHeight,buf,bmpInf,DIB_RGB_COLORS))
    {
        ::ReleaseDC(NULL,hDesktopDC);
        LocalFree(bmpInf);
        return ;
    }

    ::ReleaseDC(NULL,hDesktopDC);

    CString sMsg;
    sMsg.Format("BitsPixel:%d,width:%d,height:%d",
        bm.bmBitsPixel,bm.bmWidth,bm.bmHeight);
    AfxMessageBox(sMsg);

    CClientDC dc(this);

    int nOffset;
    BYTE r,g,b;
    int nWidth = bm.bmWidth*bm.bmBitsPixel/8;
    nWidth = ((nWidth+3)/4)*4; //4字节对齐

    if(bmpInf->bmiHeader.biBitCount == 8)
    {       
        for(int i=0; i<bm.bmHeight; i++)
        {
            for(int j=0; j<bm.bmWidth; j++)
            {
                RGBQUAD rgbQ;
                rgbQ = bmpInf->bmiColors[buf[i*nWidth+j]];
                dc.SetPixel(j,bm.bmHeight-i,RGB(rgbQ.rgbRed,rgbQ.rgbGreen,rgbQ.rgbBlue)); //测试显示
            }
        }
    }
    else if(bmpInf->bmiHeader.biBitCount == 16)
    {
        for(int i=0; i<bm.bmHeight; i++)
        {
            nOffset = i*nWidth;
            for(int j=0; j<bm.bmWidth; j++)
            {
                b = buf[nOffset+j*2]&0x1F;
                g = buf[nOffset+j*2]>>5;
                g |= (buf[nOffset+j*2+1]&0x03)<<3;
                r = (buf[nOffset+j*2+1]>>2)&0x1F;

                r *= 8;
                b *= 8;
                g *= 8;

                dc.SetPixel(j, bm.bmHeight-i, RGB(r,g,b)); //测试显示
            }
        }
    }
    else if(bmpInf->bmiHeader.biBitCount == 24)
    {
        for(int i=0; i<bm.bmHeight; i++)
        {
            nOffset = i*nWidth;
            for(int j=0; j<bm.bmWidth; j++)
            {
                b = buf[nOffset+j*3];
                g = buf[nOffset+j*3+1];
                r = buf[nOffset+j*3+2];

                dc.SetPixel(j, bm.bmHeight-i, RGB(r,g,b)); //测试显示
            }
        }
    }
    else if(bmpInf->bmiHeader.biBitCount == 32)
    {
        for(int i=0; i<bm.bmHeight; i++)
        {
            nOffset = i*nWidth;
            for(int j=0; j<bm.bmWidth; j++)
            {
                b = buf[nOffset+j*4];
                g = buf[nOffset+j*4+1];
                r = buf[nOffset+j*4+2];

                dc.SetPixel(j, bm.bmHeight-i, RGB(r,g,b)); //测试显示
            }
        }
    }

    DeleteObject(hBmp);
    LocalFree(bmpInf);

    //CDialog::OnOK();
}

问题:

1,

在我的代码中,第二次调用GetDIBits()前所申请的内存,只考虑了DIB数据的大小,未考虑BITMAPINFO及COLOR TABLE的空间(因为前面已有专门的变量存放这些信息);

而正常的代码,第二次调用GetDIBits()前所申请的内存空间,包括了BITMAPINFO、COLOR TABLE以及实际DIB数据的内容(将前面获得的BITMAPINFO、COLOR TABLE都拷贝到此空间)。

2,

Windows GDI中有两个用来得到位图图像数据的API,分别是GetBitmapBits和GetDIBits;
按照MSDN的解释,前者是用来得到设备独立位图的BITS,
后者是得到兼容位图的BITS,

所以在调用该函数的时候,
第一个主要的区别是:GetDIBits需要提供一个设备内容,同时需要将位图的HANDLE选进这个设备内容(DC)才能能够得到位图的信息。
我想上面的区别大家可能都知道,
其实它还隐藏着另一个区别:就是对于同一个位图,得到的BITS内容的BUFFER不一样!

大家都知道BMP文件存储数据是倒叙的,也就是从图像的右下角开始存储,文件的最后是图像的左上角(这个来历可以看:WINDOWS编程中介绍);
使用GetBitmapBits取得的BUFFER,位图的右下角的内容为第一个字节,实际上和真正的图像字节应该是一样的,

而GetDIBits刚好相反,其BUFFER的顺序符合BMP文件中的顺序,如果按照正常的坐标,其存储顺序应该是倒叙。
所以在程序中要合理的使用这两个API来得到你想要的位图数据。

调用GDI+或CxImage或Image Magick或PhotoShop的图片放大缩小函数。

猜你喜欢

转载自blog.csdn.net/wowocpp/article/details/80813183