0

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.

celticminstrel
  • 1,637
  • 13
  • 21

1 Answers1

2

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);
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks! With a minor tweak, that seems to work pretty well (I just had to reverse the order of the four mask parameters). Unfortunately, it appears that many programs don't read the alpha channel when getting data from the clipboard (and GIMP doesn't even recognize there's an image). Oh well! – celticminstrel Jun 18 '15 at 02:58
  • However, I can copy-paste with alpha from Paint.net to GIMP, so there must be a way. I'll have to keep looking. – celticminstrel Jun 18 '15 at 03:05
  • @celticminstrel FYI, Gimp adds clipboard type "PNG" to just dump a full png image on the clipboard, and increasingly more applications support that (I know MS Office does). That's how it gets around the alpha limitation. – Nyerguds Aug 02 '23 at 14:05