从Windows剪贴板获取32位RGBA图像

时间:2016-06-18 20:36:50

标签: c windows bitmap dib

我希望我的应用程序(适用于RGBA8888图像)能够从Windows剪贴板粘贴图像。因此它应该能够从剪贴板中读取来自任何常见光栅图像应用程序的图像,如Gimp,Photoshop,MSPaint等。

通过阅读剪贴板功能,似乎我应该能够调用GetClipboardData(CF_DIBV5)来访问剪贴板上几乎任何位图类型,因为Windows会自动在它与CF_BITMAP和CF_DIB之间进行转换。但是通过阅读DIB格式,我发现存在大量可能的位深度,RGB顺序,可选压缩等组合。看起来我正在做的事情将是一项常见任务,但我不知道看到Windows API中的任何转换函数(除非我在搜索时很差),这似乎需要一周的时间来编写以支持所有可能的格式。所以我想知道我是否忽视了一些明显的东西。或者,如果有某种假设我可以简化这个...就像所有流行的图像应用程序碰巧以未压缩/未索引的格式将图像复制到剪贴板。

更新:这是我到目前为止所拥有的:

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);
    }
}

更新2:

为了澄清,我需要字面上未压缩的32位图像数据(RRGGBBAA),我可以操作但是我喜欢在跨平台的应用程序中。我没有必要使用Windows API将此图像绘制到屏幕上。

我知道名为stdb_image.h的第三方库可以将.bmps,.jpgs和.pngs加载到我需要的数据类型中。因此,如果有一种方法可以将剪贴板数据转换为位图或png文件数据而不会丢失alpha,那么我将处于良好的状态。

2 个答案:

答案 0 :(得分:1)

我发现的基本策略是检查剪贴板上是否有原始PNG,并在可用时首先使用该PNG。这是最简单的。某些应用程序(如GIMP)将图像作为PNG复制到剪贴板。

然后检查CF_DIBV5。实际位的位置取决于"压缩"是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;

如果标题说压缩是BI_BITFIELDS,那么数据已经是我需要的。

如果标题说压缩是BI_RGB并且位数是24或32,那么我可以解压缩字节。 24字节意味着行大小可能不会落在DWORD边界上,因此您必须注意它。

最后,低于24的位数可能意味着索引颜色,我还没有工作。

答案 1 :(得分:0)

以下是CF_DIBV5CF_DIB的使用示例。最好使用CF_DIB作为备份选项。请注意,此代码不适用于基于调色板的图像(如果不保证32位,那么请进一步查看该方法)

您可以使用SetDIBitsToDevice直接在HDC上绘图,或使用SetDIBits

GDI函数不支持alpha透明度(除TransparentBlt之类的几个函数外),通常你必须使用GDI +之类的库。

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();
}

如果原始图像是基于调色板的,则必须转换为32位。或者,您可以向数据添加BITMAPFILEHEADER(假设源是位图),然后传递给另一个库。

这是使用CreateDIBitmapGetDIBits来确保像素位于32位的示例:

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;
    }
}