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

Get 32 bit RGBA image from Windows clipboard

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

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

更新:这是我目前的情况:

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 APIs 将此图像绘制到屏幕上。

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

这里是 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;
    }
}

我发现的基本策略是检查剪贴板上是否有原始 PNG,如果可用则首先使用它。那是最简单的。一些应用程序,例如 GIMP,将图像作为 PNG 复制到剪贴板。

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

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

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

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