Home > Net >  How to copy an RGBA image to Windows' Clipboard
How to copy an RGBA image to Windows' Clipboard

Time:05-18

How might one copy a 32bit (per pixel) RGBA image to Windows' Clipboard? I've arrived to this function after a lot of trial, but no luck in having my image data "paste" at all. It does not appear in the Clipboard's history either.

Slightly editing it to use CF_DIB and the BITMAPINFOHEADER header has yielded a "copy" entry in that history and an image of the correct size when pasted, though sticking a png on the back of a CF_DIB has caused programs to glitch out in incredibly interesting and non-benign ways.

My goal is to copy an image with an alpha channel to the Clipboard, and to have the colors not be multiplied against this alpha during the hand-off. What am I be doing wrong..?

bool copyBitmapIntoClipboard(Window & window, const Bitmap & in) {
    //  this section is my code for creating a png file
    StreamWrite stream = StreamWrite::asBufferCreate();
    in.savePng(stream);
    uint64 bufSize = 0;
    char * buf = stream._takeBuffer(bufSize, false);
    // "buf"      <-- contains the PNG payload
    // "bufSize"  <-- is the size of this payload

    // beyond this point, it's just standard windows' stuff that doesn't rely on my code
    BITMAPV5HEADER header;
    header.bV5Size          = sizeof(BITMAPV5HEADER);
    header.bV5Width         = in.getX(); // <-- size of the bitmap in pixels, width and height
    header.bV5Height        = in.getY();
    header.bV5Planes        = 1;
    header.bV5BitCount      = 0;
    header.bV5Compression   = BI_PNG;
    header.bV5SizeImage     = bufSize;
    header.bV5XPelsPerMeter = 0;
    header.bV5YPelsPerMeter = 0;
    header.bV5ClrUsed       = 0;
    header.bV5ClrImportant  = 0;
    header.bV5RedMask       = 0xFF000000;
    header.bV5GreenMask     = 0x00FF0000;
    header.bV5BlueMask      = 0x0000FF00;
    header.bV5AlphaMask     = 0x000000FF;
    header.bV5CSType        = LCS_sRGB;
    header.bV5Endpoints;    // ignored
    header.bV5GammaRed      = 0;
    header.bV5GammaGreen    = 0;
    header.bV5GammaBlue     = 0;
    header.bV5Intent        = 0;
    header.bV5ProfileData   = 0;
    header.bV5ProfileSize   = 0;
    header.bV5Reserved      = 0;

    HGLOBAL gift = GlobalAlloc(GMEM_MOVEABLE, sizeof(BITMAPV5HEADER)   bufSize);
    if (gift == NULL) 
        return false;

    HWND win = window.getWindowHandle();
    if (!OpenClipboard(win)) {
        GlobalFree(gift);
        return false;
    }
    EmptyClipboard();

    void * giftLocked = GlobalLock(gift);
    if (giftLocked) {
        memcpy(giftLocked, &header, sizeof(BITMAPV5HEADER));
        memcpy((char*)giftLocked   sizeof(BITMAPV5HEADER), buf, bufSize);
    }
    GlobalUnlock(gift);

    SetClipboardData(CF_DIBV5, gift);

    CloseClipboard();
    return true;
}

CodePudding user response:

At least in my experience, trying to transfer png data with a BITMAPV5HEADER is nearly a complete loss, unless you're basically planning on using it strictly as an internal format.

One strategy that does work at least for a fair number of applications, is to register the PNG clipboard format, and just put the contents of a PNG file into the clipboard (with no other header). Code would look something like this:

bool copyBitmapIntoClipboard(Window & window, const Bitmap & in) {
    //  this section is my code for creating a png file
    StreamWrite stream = StreamWrite::asBufferCreate();
    in.savePng(stream);
    uint64 bufSize = 0;
    char * buf = stream._takeBuffer(bufSize, false);
    // "buf"      <-- contains the PNG payload
    // "bufSize"  <-- is the size of this payload

    HGLOBAL gift = GlobalAlloc(GMEM_MOVEABLE, bufSize);
    if (gift == NULL) 
        return false;

    HWND win = window.getWindowHandle();
    if (!OpenClipboard(win)) {
        GlobalFree(gift);
        return false;
    }
    EmptyClipboard();

    auto fmt = RegisterClipboardFormat("PNG"); // or `L"PNG", as applicable

    void * giftLocked = GlobalLock(gift);
    if (giftLocked) {
        memcpy((char*)giftLocked, buf, bufSize);
    }
    GlobalUnlock(gift);

    SetClipboardData(fmt, gift);

    CloseClipboard();
    return true;
}

I've used code like this with, and successfully pasted the contents into recent versions of at least LibreOffice Write and Calc, MS Word, and Paint.Net.

This is also the format Chrome (for one example) will produce as the first (preferred) format if you tell it to copy a bitmap.

On the other hand, FireFox produces a whole plethora of formats, but not this one. It will produce a CF_DIBV5, but at least if memory serves, it has pre-multiplied alpha (or maybe it loses alpha completely--I don't remember for sure. Doesn't preserve it as you'd want anyway).

Gimp will accept 32-bit RGB format DIB, with alpha in the left-over byte, and make use of that alpha. For better or worse, as far as I've been able to figure out that's about the only thing that works to paste something into Gimp with its alpha preserved (not pre-multiplied).

Notes

  • As versions are updated, the formats they support may well change, so even though (for example) PNG didn't work with Gimp the last time I tried, it might now.

  • You can add the same data into the clipboard in different formats. You want to start from the "best" format (the one that preserves the data most faithfully), and work your way down to the worst. So when you do a copy, you might want to do PNG, then RGB with an alpha channel, then CF_BITMAP (which will pre-multiply alpha, but may still be better than nothing).

  • Related