Search code examples
c++bitmapclipboardalphadib

How can I put a device-independent bitmap into the Windows clipboard using just direct WinAPI? (No MFC or other wrappers)


I've been trying to get this to work for awhile now, but I can't seem to figure it out, and hours of Googling has yet to reveal any useful results.

I have an array of 32-bit pixels in RGBA order, and I want to create a device-independent bitmap from them and place it on the clipboard using SetClipboardData(CF_DIBV5, dib) or similar (ideally, I want to preserve the alpha channel). Registering a custom clipboard type is not an option, since the point of putting it on the clipboard is so that it can be pasted into another program. Bonus points if I don't have to manually convert my pixel data into some other format (such as planar BGRA).

My current code goes like this (it's all within a set_clipboard_img function):

if(!OpenClipboard(hwnd)) return;
BITMAPV5HEADER* info = (BITMAPV5HEADER*) GlobalAlloc(GMEM_MOVEABLE, sizeof(BITMAPV5HEADER));
info->bV5Size = sizeof(BITMAPV5HEADER);
info->bV5Width = img_width;
info->bV5Height = -img_height;
info->bV5Planes = 1; // The docs say this is the only valid value here.
info->bV5BitCount = 32;
info->bV5Compression = BI_BITFIELDS;
info->bV5SizeImage = img_width * img_height * 4;
info->bV5RedMask   = 0xff000000;
info->bV5GreenMask = 0x00ff0000;
info->bV5BlueMask  = 0x0000ff00;
info->bV5AlphaMask = 0x000000ff;
unsigned char* buf;
// One of the sources I found said that I can pass a BITMAPV5HEADER in place of the BITMAPINFO, hence the first reinterpret_cast.
HBITMAP dib = CreateDIBSection(NULL, reinterpret_cast<BITMAPINFO*>(info), DIB_RGB_COLORS, reinterpret_cast<void**>(&buf), NULL, 0);
if(dib == NULL) {
    CloseClipboard();
    return;
}
// img_pixels_ptr is a unsigned char* to the pixels in non-planar RGBA format
std::copy_n(img_pixels_ptr, info->bV5SizeImage, buf);
EmptyClipboard();
auto result = SetClipboardData(CF_DIBV5, dib);
if(result == NULL) {
    char str[256];
    str[255] = 0;
    FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, str, 255, NULL);
    std::cerr << "Error setting clipboard: " << str << std::endl;
    // Here I get "handle not valid". I have no idea _why_ it's not valid, though.
}
CloseClipboard();

Ultimately, I'll also need to be able to reverse the process (getting a potentially-transparent bitmap off the clipboard), but one thing at a time.


Solution

  • You cannot pass an HBITMAP to SetClipboardData(). It requires an HGLOBAL from GlobalAlloc() instead. That is why SetClipboardData() is failing with an ERROR_INVALID_HANDLE error.

    You need to put your BITMAPV5HEADER and pixel data directly into the allocated HGLOBAL and put it as-is onto the clipboard, forget using CreateDIBSection() at all:

    Standard Clipboard Formats

    CF_DIBV5
    17
    A memory object containing a BITMAPV5HEADER structure followed by the bitmap color space information and the bitmap bits.

    Try something more like this:

    void printErr(const char *msg)
    {
        char str[256] = {0};
        FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, str, 255, NULL);
        std::cerr << msg << ": " << str << std::endl;
    }
    
    ...
    
    DWORD size_pixels = img_width * img_height * 4;
    
    HGLOBAL hMem = GlobalAlloc(GHND, sizeof(BITMAPV5HEADER) + size_pixels);
    if (!hMem)
    {
        printErr("Error allocating memory for bitmap data");
        return;
    }
    
    BITMAPV5HEADER* hdr = (BITMAPV5HEADER*) GlobalLock(hMem);
    if (!hdr)
    {
        printErr("Error accessing memory for bitmap data");
        GlobalFree(hMem);
        return;
    }
    
    hdr->bV5Size = sizeof(BITMAPV5HEADER);
    hdr->bV5Width = img_width;
    hdr->bV5Height = -img_height;
    hdr->bV5Planes = 1;
    hdr->bV5BitCount = 32;
    hdr->bV5Compression = BI_BITFIELDS;
    hdr->bV5SizeImage = size_pixels;
    hdr->bV5RedMask   = 0xff000000;
    hdr->bV5GreenMask = 0x00ff0000;
    hdr->bV5BlueMask  = 0x0000ff00;
    hdr->bV5AlphaMask = 0x000000ff;
    
    // img_pixels_ptr is a unsigned char* to the pixels in non-planar RGBA format
    CopyMemory(hdr+1, img_pixels_ptr, size_pixels);
    GlobalUnlock(hMem);
    
    if (!OpenClipboard(hwnd))
    {
        printErr("Error opening clipboard");
    }
    else
    {
        if (!EmptyClipboard())
            printErr("Error emptying clipboard");
    
        else if (!SetClipboardData(CF_DIBV5, hMem))
            printErr("Error setting bitmap on clipboard");
    
        else
            hMem = NULL; // clipboard now owns the memory
    
        CloseClipboard();
    }
    
    if (hMem)
        GlobalFree(hMem);