从 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_DIBV5
和 CF_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
添加到数据中(假设源是位图),然后传递给另一个库。
这是使用 CreateDIBitmap
和 GetDIBits
确保像素为 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 的位计数可能意味着索引颜色,我还没有工作。
我希望我的应用程序(适用于 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_DIBV5
和 CF_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
添加到数据中(假设源是位图),然后传递给另一个库。
这是使用 CreateDIBitmap
和 GetDIBits
确保像素为 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 的位计数可能意味着索引颜色,我还没有工作。