skip to Main Content

I want my app (which works with RGBA8888 images) to be able to paste images from the Windows clipboard. So it should be able to read images off the clipboard that come from any common raster image apps like Gimp, Photoshop, MSPaint, etc.

From reading up on the clipboard functions, it seems I should be able to call GetClipboardData(CF_DIBV5) to get access to pretty much any bitmap type that’s on the Clipboard since Windows automatically converts between that and CF_BITMAP and CF_DIB. But from reading up on the DIB format, I see that there is an immense number of possible combinations of bit depth, RGB order, optional compression, etc. It seems like what I’m doing would be a common task, but I don’t see any conversion functions in the Windows API (unless I’m poor at searching), and this seems like something that would take a week to write to support all possible formats. So I’m wondering if I’ve overlooked something obvious. Or if there is some kind of assumption I can make to simplify this…like if all the popular image apps happen to copy images to the clipboard in uncompressed/unindexed formats.

UPDATE: Here’s what I have so far:

HGLOBAL clipboard = GetClipboardData(CF_DIBV5);
exists = clipboard != NULL;
int dataLength = GlobalSize(clipboard);
exists = dataLength != 0;
if (exists) {
    LPTSTR lockedClipboard = GlobalLock(clipboard);
    exists = lockedClipboard != NULL;
    if (exists) {
        BITMAPV5HEADER *header = (BITMAPV5HEADER*)lockedClipboard;
        LONG width = header->bV5Width;
        LONG height = header->bV5Height;
        BYTE *bits = header + sizeof(header) + header->bV5ClrUsed * sizeof(RGBQUAD);

        //Now what? Need function to convert the bits to something uncompressed.

        GlobalUnlock(clipboard);
    }
}

UPDATE 2:

To clarify, I need literally uncompressed 32 bit image data (RRGGBBAA) which I can manipulate however I like in a cross-platform app. I have no need to use Windows APIs to draw this image to screen.

I am aware of a 3rd party library called stdb_image.h that can load .bmps, .jpgs, and .pngs into the type of data I need. So if there’s a way I can turn the clipboard data into bitmap or png file data without losing alpha, then I’ll be in good shape.

2

Answers


  1. Chosen as BEST ANSWER

    The basic strategy I've found is to check if there's a raw PNG on the clipboard and use that first if available. That's the easiest. Some apps, such as GIMP, copy images as PNG to the clipboard.

    Then check for CF_DIBV5. The location of the actual bits depends on whether the "compression" is BI_BITFIELDS:

    int offset = bitmapV5Header->bV5Size + bitmapV5Header->bV5ClrUsed * (bitmapV5Header->bV5BitCount > 24 ? sizeof(RGBQUAD) : sizeof(RGBTRIPLE));
    if (compression == BI_BITFIELDS)
        offset += 12; //bit masks follow the header
    BYTE *bits = (BYTE*)bitmapV5Header + offset;
    

    If the header says compression is BI_BITFIELDS, then the data is already as I needed it.

    If the header says compression is BI_RGB and the bit count is 24 or 32, then I can unpack the bytes. 24 bytes means row size might not land on a DWORD boundary, so you have to watch for that.

    Finally, lower bit counts than 24 likely mean indexed color, which I don't have working yet.


  2. Here is example of usage for CF_DIBV5 and CF_DIB. It’s best to use CF_DIB as backup option. Note, this code won’t work for palette based images (if it is not guaranteed 32bit then see the method further down)

    You can use SetDIBitsToDevice to draw directly on HDC, or use SetDIBits

    GDI functions don’t support alpha transparency (except for a couple of functions like TransparentBlt), in general you have to use libraries such as GDI+ for that.

    void foo(HDC hdc)
    {
        if (!OpenClipboard(NULL))
            return;
    
        HANDLE handle = GetClipboardData(CF_DIBV5);
        if (handle)
        {
            BITMAPV5HEADER* header = (BITMAPV5HEADER*)GlobalLock(handle);
            if (header)
            {
                BITMAPINFO bmpinfo;
                memcpy(&bmpinfo.bmiHeader, header, sizeof(BITMAPINFOHEADER));
                bmpinfo.bmiHeader.biSize = sizeof(BITMAPINFO);
    
                //(use `header` to access other BITMAPV5HEADER information)
    
                int w = bmpinfo.bmiHeader.biWidth;
                int h = bmpinfo.bmiHeader.biHeight;
                const char* bits = (char*)(header) + header->bV5Size;
    
                //draw using SetDIBitsToDevice
                SetDIBitsToDevice(hdc,0,0,w,h,0,0,0,h,bits,&bmpinfo,DIB_RGB_COLORS);
            }
        }
        else
        {
            handle = GetClipboardData(CF_DIB);
            if (handle)
            {
                BITMAPINFO* bmpinfo = (BITMAPINFO*)GlobalLock(handle);
                if (bmpinfo)
                {
                    int w = bmpinfo->bmiHeader.biWidth;
                    int h = bmpinfo->bmiHeader.biHeight;
                    const char* bits = (char*)(bmpinfo)+bmpinfo->bmiHeader.biSize;
                    SetDIBitsToDevice(hdc, 0, 0, w, h, 0, 0, 0, h, bits, bmpinfo, 0);
                }
            }
        }
    
        CloseClipboard();
    }
    

    If the original image is palette based, you would have to convert to 32bit. Alternatively you could add BITMAPFILEHEADER to the data (assuming the source is bitmap) then pass to the other library.

    This is an example using CreateDIBitmap and GetDIBits to make sure the pixels are in 32bit:

    HANDLE handle = GetClipboardData(CF_DIB);
    if (handle)
    {
        BITMAPINFO* bmpinfo = (BITMAPINFO*)GlobalLock(handle);
        if (bmpinfo)
        {
            int offset = (bmpinfo->bmiHeader.biBitCount > 8) ?
                0 : sizeof(RGBQUAD) * (1 << bmpinfo->bmiHeader.biBitCount);
            const char* bits = (const char*)(bmpinfo)+bmpinfo->bmiHeader.biSize + offset;
            HBITMAP hbitmap = CreateDIBitmap(hdc, &bmpinfo->bmiHeader, CBM_INIT, bits, bmpinfo, DIB_RGB_COLORS);
    
            //convert to 32 bits format (if it's not already 32bit)
            BITMAP bm;
            GetObject(hbitmap, sizeof(bm), &bm);
            int w = bm.bmWidth;
            int h = bm.bmHeight;
            char *bits32 = new char[w*h*4];
    
            BITMAPINFOHEADER bmpInfoHeader = { sizeof(BITMAPINFOHEADER), w, h, 1, 32 };
            HDC hdc = GetDC(0);
            GetDIBits(hdc, hbitmap, 0, h, bits32, (BITMAPINFO*)&bmpInfoHeader, DIB_RGB_COLORS);
            ReleaseDC(0, hdc);
    
            //use bits32 for whatever purpose...
    
            //cleanup
            delete[]bits32;
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search