Windows 剪贴板 DIB 格式转 PNG 实战指南
Windows 开发中,剪贴板的图片常以 DIB 格式存在。DIB 适合内存中的位图表示,但不便存储和传输。本文介绍三种语言(C++、C#、Python)下将
CF_DIB转为 PNG 的完整方案。
背景:什么是 DIB?
DIB(Device-Independent Bitmap)是 Windows 的位图标准格式。与设备相关位图(DDB)不同,DIB 自带颜色表,跨设备显示一致。
剪贴板中的 DIB 以 CF_DIB 或 CF_DIBV5 存在,结构分三部分:
| 区域 | 说明 |
|---|---|
| 位图信息头 | BITMAPINFOHEADER 或 BITMAPV5HEADER |
| 颜色表 | 可选,位深 ≤ 8bit 时存在 |
| 像素数据 | 位图的实际像素值 |
方案一:C++ + GDI+
最原生的 Windows API 方案,适合集成到 C++ 桌面应用中。
1. 从剪贴板获取 DIB
#include <windows.h>
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
using namespace Gdiplus;
HBITMAP GetDIBFromClipboard()
{
if (!OpenClipboard(NULL)) return NULL;
HANDLE hData = GetClipboardData(CF_DIB);
if (!hData) {
CloseClipboard();
return NULL;
}
BITMAPINFO* pBMI = (BITMAPINFO*)GlobalLock(hData);
if (!pBMI) {
CloseClipboard();
return NULL;
}
HDC hDC = GetDC(NULL);
BYTE* pBits;
HBITMAP hBitmap = CreateDIBSection(hDC, pBMI, DIB_RGB_COLORS,
(void**)&pBits, NULL, 0);
if (hBitmap) {
// 计算颜色表大小
int colorTableSize = 0;
if (pBMI->bmiHeader.biBitCount <= 8) {
colorTableSize = pBMI->bmiHeader.biClrUsed * sizeof(RGBQUAD);
if (colorTableSize == 0)
colorTableSize = (1 << pBMI->bmiHeader.biBitCount) * sizeof(RGBQUAD);
}
int headerSize = pBMI->bmiHeader.biSize;
BYTE* pSrcBits = (BYTE*)pBMI + headerSize + colorTableSize;
// 计算位图数据大小
int dataSize = pBMI->bmiHeader.biSizeImage;
if (dataSize == 0) {
int width = pBMI->bmiHeader.biWidth;
int height = abs(pBMI->bmiHeader.biHeight);
int stride = ((width * pBMI->bmiHeader.biBitCount + 31) / 32) * 4;
dataSize = stride * height;
}
CopyMemory(pBits, pSrcBits, dataSize);
}
GlobalUnlock(hData);
ReleaseDC(NULL, hDC);
CloseClipboard();
return hBitmap;
}
2. HBITMAP → PNG
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0, size = 0;
GetImageEncodersSize(&num, &size);
if (size == 0) return -1;
ImageCodecInfo* pCodecs = (ImageCodecInfo*)malloc(size);
if (!pCodecs) return -1;
GetImageEncoders(num, size, pCodecs);
for (UINT j = 0; j < num; ++j) {
if (wcscmp(pCodecs[j].MimeType, format) == 0) {
*pClsid = pCodecs[j].Clsid;
free(pCodecs);
return j;
}
}
free(pCodecs);
return -1;
}
bool SaveHBITMAPToPNG(HBITMAP hBitmap, const wchar_t* filename)
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
Bitmap* bitmap = Bitmap::FromHBITMAP(hBitmap, NULL);
if (!bitmap) {
GdiplusShutdown(gdiplusToken);
return false;
}
CLSID pngClsid;
bool ok = false;
if (GetEncoderClsid(L"image/png", &pngClsid) != -1)
ok = (bitmap->Save(filename, &pngClsid, NULL) == Ok);
delete bitmap;
GdiplusShutdown(gdiplusToken);
return ok;
}
3. 完整调用
int main()
{
HBITMAP hBitmap = GetDIBFromClipboard();
if (!hBitmap) {
MessageBox(NULL, L"剪贴板中没有 DIB 格式的图片", L"错误", MB_OK);
return 1;
}
if (SaveHBITMAPToPNG(hBitmap, L"clipboard_image.png"))
MessageBox(NULL, L"PNG 保存成功!", L"成功", MB_OK);
else
MessageBox(NULL, L"PNG 保存失败!", L"错误", MB_OK);
DeleteObject(hBitmap);
return 0;
}
方案二:C# + .NET
.NET 封装了底层细节,实现最简洁:
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
class ClipboardImageConverter
{
public static void ConvertDIBToPNG(string outputPath)
{
if (Clipboard.ContainsImage())
{
using Image image = Clipboard.GetImage();
image.Save(outputPath, ImageFormat.Png);
Console.WriteLine("PNG 保存成功!");
}
else
{
Console.WriteLine("剪贴板中没有图片!");
}
}
}
需要添加
System.Drawing.CommonNuGet 包(.NET 6+)。
方案三:Python + Pillow + pywin32
import win32clipboard
from PIL import Image
import io
def dib_to_png(output_path):
try:
win32clipboard.OpenClipboard()
if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_DIB):
data = win32clipboard.GetClipboardData(win32clipboard.CF_DIB)
image = Image.open(io.BytesIO(data))
image.save(output_path, "PNG")
print(f"PNG 已保存到: {output_path}")
else:
print("剪贴板中没有 DIB 格式的图片")
except Exception as e:
print(f"错误: {e}")
finally:
win32clipboard.CloseClipboard()
# 使用
dib_to_png("clipboard_output.png")
安装依赖:pip install pillow pywin32
方案对比
| C++ | C# | Python | |
|---|---|---|---|
| 代码量 | 多 | 极少 | 少 |
| 性能 | 最高 | 高 | 中 |
| 依赖 | GDI+(系统自带) | .NET Runtime | Pillow + pywin32 |
| 适用场景 | 桌面应用集成 | .NET 应用 | 脚本 / 批处理 |
注意事项
- 剪贴板独占 — 调用
OpenClipboard后尽快操作并CloseClipboard,避免阻塞其他程序 - GDI+ 生命周期 — C++ 方案务必配对调用
GdiplusStartup/GdiplusShutdown - 资源释放 — HBITMAP 用完立删,GDI 资源有限
- DIB 变体 —
CF_DIBV5头部更大,需按biSize判断头大小后再定位像素数据 - PNG 压缩 — 可调整 GDI+ 的 EncoderParameters 控制压缩级别
本文覆盖了从底层 Win32 API 到高层脚本三种实现路径,可根据项目技术栈选择最合适的方案。