Search code examples
winapigdi+clipboard

How to place GDI+ Bitmap onto the clipboard?


I want to place a GDI+ Bitmap onto the clipboard. The obvious way would be to:

So i try pseudo-code:

void PlaceBitmapOnClipboard(Bitmap image)
{
   //Convert GDI+ Bitmap to GDI Bitmap
   HBITMAP bmp;
   image.GetHBITMAP(0, @bmp);

   OpenClipboard(this.Handle);
      EmptyClipboard();
      SetClipboardData(CF_BITMAP, bmp);
   CloseClipboard();
}

Error checking has been omitted for expository purposes; but neither function fails:

  • Not GetHBITMAP and its HRESULT style error
  • Nor does SetClipboardData return a null

But when i try to use the CF_BITMAP on the clipboard, it can't be pasted into Paint:

enter image description here

So, what is the correct code to fill in the function:

void PlaceBitmapOnClipboard(Bitmap image)
{
   //TODO: Ask Stackoverflow to figure this out

}

Solution

  • We need to call:
    SetClipboardData(CF_BITMAP, hbitmap_ddb)

    Where hbitmap_ddb must be a compatible bitmap (DDB), not DIB which we get from Gdiplus::GetHBitmap


    Or we call:
    SetClipboardData(CF_DIB, hmemory)

    Where hmemory is not HBITMAP. hmemory is described in documentation as:

    A memory object containing a BITMAPINFO structure followed by the bitmap bits.


    Example using CF_BITMAP

    Use CreateDIBitmap to create a compatible bitmap based on our DIB bitmap. Then call SetClipboardData with the new DDB bitmap.

    Gdiplus::Bitmap gdibmp(L"file.bmp");
    if(gdibmp.GetLastStatus() != Gdiplus::Ok)
        return;
    HBITMAP hbitmap;
    auto status = gdibmp.GetHBITMAP(0, &hbitmap);
    if(status != Gdiplus::Ok)
        return;
    BITMAP bm;
    GetObject(hbitmap, sizeof bm, &bm);
    DIBSECTION ds;
    if(sizeof ds == GetObject(hbitmap, sizeof ds, &ds))
    {
        HDC hdc = GetDC(NULL);
        HBITMAP hbitmap_ddb = CreateDIBitmap(hdc, &ds.dsBmih, CBM_INIT,
            ds.dsBm.bmBits, (BITMAPINFO*)&ds.dsBmih, DIB_RGB_COLORS);
        ReleaseDC(NULL, hdc);
        if(OpenClipboard(hwnd))
        {
            EmptyClipboard();
            SetClipboardData(CF_BITMAP, hbitmap_ddb);
            CloseClipboard();
        }
        DeleteObject(hbitmap_ddb);
    }
    DeleteObject(hbitmap);
    

    Example using CF_DIB

    Use GlobalAlloc to allocate memory, and copy BITMAPINFOHEADER to that memory, followed by the bits. There is no need to worry about the color table, because Gdiplus::HBitmap returns 32-bit bitmap (at least on modern displays as far as I know)

    Gdiplus::Bitmap gdibmp(L"file.bmp");
    if(gdibmp.GetLastStatus() != Gdiplus::Ok)
        return;
    
    HBITMAP hbitmap;
    auto status = gdibmp.GetHBITMAP(NULL, &hbitmap);
    if(status != Gdiplus::Ok)
        return;
    BITMAP bm;
    GetObject(hbitmap, sizeof bm, &bm);
    
    BITMAPINFOHEADER bi =
    { sizeof bi, bm.bmWidth, bm.bmHeight, 1, bm.bmBitsPixel, BI_RGB };
    
    std::vector<BYTE> vec(bm.bmWidthBytes * bm.bmHeight);
    auto hdc = GetDC(NULL);
    GetDIBits(hdc, hbitmap, 0, bi.biHeight, vec.data(), (BITMAPINFO*)&bi, 0);
    ReleaseDC(NULL, hdc);
    
    auto hmem = GlobalAlloc(GMEM_MOVEABLE, sizeof bi + vec.size());
    auto buffer = (BYTE*)GlobalLock(hmem);
    memcpy(buffer, &bi, sizeof bi);
    memcpy(buffer + sizeof bi, vec.data(), vec.size());
    GlobalUnlock(hmem);
    
    if(OpenClipboard(hwnd))
    {
        EmptyClipboard();
        SetClipboardData(CF_DIB, hmem);
        CloseClipboard();
    }
    DeleteObject(hbitmap);