2

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.

Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • 1
    `GetDIBits` and `SetDIBits` transform bitmap data between DIBs and DDBs, converting according to the parameters from `BITMAPINFO`. – GSerg Jun 18 '16 at 20:49
  • `GetClipboardData(CF_BITMAP)` should be enough, unless you want alpha-transparency levels. Show what you have done so far and what problem you have with specific programs (Microsoft paint.exe shouldn't be a problem) – Barmak Shemirani Jun 19 '16 at 05:18
  • I'm working through understanding the getDIBits function and will update if I can't get it working. I do need alpha. Looks like for non-indexed bitmaps, alpha is in the fourth byte (each pixel must use a long even if only RGB) but there's nothing in the header to determine if there's alpha. – Tenfour04 Jun 19 '16 at 16:27
  • @BarmakShemirani @GSerg I updated with my code so far. Unsure if I added correctly to get the bits pointer. This is unusable with `GetDIBits` or `SetDIBits` because I have a DIB, not a DDB `HBITMAP`. I need `CF_DIBV5 `because I need alpha if it's on the clipboard. – Tenfour04 Jun 21 '16 at 02:54

2 Answers2

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;
    }
}
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • In your last function, I'm not following what's going on. Is `CreateDIBSection` the only way to get a Bitmap from the clipboard? And what does SetDIBits actually do? What I need is simply an array of bytes of uncompressed 32bit image data. I am using it to load the image into OpenGL. Actually, if I could just convert the clipboard content directly into a .bmp, .png, or .jpg byte array (same bytes that could be saved as a file), I could easily pass it into the `stdb_image.h` 3rd party library to get the image data in whatever format I want. – Tenfour04 Jun 22 '16 at 02:39
  • The `BITMAPV5HEADER` mentions JPG or PNG compression. I don't know if that means the following bits are literally the file data for one of those image types, or simply that that style of compression is being used. – Tenfour04 Jun 22 '16 at 02:46
  • If `header->bV5BitCount` is 32, then `bits` points to the "32bit image data" that you want. If it is 24, then it must be converted to 32bit. – Barmak Shemirani Jun 22 '16 at 03:14
  • So is CreateDIBSection the way that you convert it? And what if it's 32 bit compressed data? – Tenfour04 Jun 22 '16 at 03:15
  • I don't know, your question is getting too complicated. Note that if the source is jpeg file, the third party program may choose to make it available to other program as `CF_DIB`, usually 32bit but sometimes as 24bit or as palette image. – Barmak Shemirani Jun 22 '16 at 03:34
  • Although this didn't answer my actual question, it led me in the right direction. Bounty incoming. :) – Tenfour04 Jun 27 '16 at 00:57
2

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.

Tenfour04
  • 83,111
  • 11
  • 94
  • 154