在Windows开发中,经常会遇到从剪贴板获取图片并转换格式的需求。DIB(Device-Independent Bitmap)是Windows剪贴板中常见的图片格式,但为了更高效的存储和传输,我们通常需要将其转换为PNG格式。本文将详细介绍实现方法。

什么是DIB格式?

DIB(Device-Independent Bitmap)是Windows操作系统中用于表示位图图像的标准格式。与设备相关的位图不同,DIB包含了颜色表信息,可以在不同设备上保持一致的显示效果。

剪贴板中的DIB格式通常以CF_DIBCF_DIBV5格式存在,包含以下信息:

  • 位图信息头(BITMAPINFOHEADER或BITMAPV5HEADER)
  • 颜色表(可选)
  • 位图数据

方法一:使用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 == NULL) {
        CloseClipboard();
        return NULL;
    }
    
    BITMAPINFO* pBMI = (BITMAPINFO*)GlobalLock(hData);
    if (pBMI == NULL) {
        CloseClipboard();
        return NULL;
    }
    
    // 创建DIB位图
    HDC hDC = GetDC(NULL);
    BYTE* pBits;
    HBITMAP hBitmap = CreateDIBSection(hDC, pBMI, DIB_RGB_COLORS, (void**)&pBits, NULL, 0);
    
    if (hBitmap) {
        // 复制位图数据
        int headerSize = pBMI->bmiHeader.biSize;
        int colorTableSize = 0;
        if (pBMI->bmiHeader.biBitCount <= 8) {
            colorTableSize = pBMI->bmiHeader.biClrUsed * sizeof(RGBQUAD);
            if (colorTableSize == 0) {
                colorTableSize = (1 << pBMI->bmiHeader.biBitCount) * sizeof(RGBQUAD);
            }
        }
        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 bytesPerLine = ((width * pBMI->bmiHeader.biBitCount + 31) / 32) * 4;
            dataSize = bytesPerLine * 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;          // number of image encoders
    UINT size = 0;         // size of the image encoder array in bytes
    
    GetImageEncodersSize(&num, &size);
    if (size == 0) {
        return -1;  // Failure
    }
    
    ImageCodecInfo* pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    if (pImageCodecInfo == NULL) {
        return -1;  // Failure
    }
    
    GetImageEncoders(num, size, pImageCodecInfo);
    
    for (UINT j = 0; j < num; ++j) {
        if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0) {
            *pClsid = pImageCodecInfo[j].Clsid;
            free(pImageCodecInfo);
            return j;  // Success
        }
    }
    
    free(pImageCodecInfo);
    return -1;  // Failure
}

bool SaveHBITMAPToPNG(HBITMAP hBitmap, const wchar_t* filename) {
    // 初始化GDI+
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    
    // 创建Bitmap对象
    Bitmap* bitmap = Bitmap::FromHBITMAP(hBitmap, NULL);
    if (bitmap == NULL) {
        GdiplusShutdown(gdiplusToken);
        return false;
    }
    
    // 获取PNG编码器CLSID
    CLSID pngClsid;
    int result = GetEncoderClsid(L"image/png", &pngClsid);
    if (result == -1) {
        delete bitmap;
        GdiplusShutdown(gdiplusToken);
        return false;
    }
    
    // 保存为PNG
    Status status = bitmap->Save(filename, &pngClsid, NULL);
    
    delete bitmap;
    GdiplusShutdown(gdiplusToken);
    
    return (status == Ok);
}

步骤3:完整调用示例

int main() {
    // 获取剪贴板DIB
    HBITMAP hBitmap = GetDIBFromClipboard();
    if (hBitmap == NULL) {
        MessageBox(NULL, L"剪贴板中没有DIB格式的图片", L"错误", MB_OK);
        return 1;
    }
    
    // 保存为PNG
    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实现

如果你使用的是C#,实现会更加简洁:

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;

class ClipboardImageConverter {
    public static void ConvertDIBToPNG(string outputPath) {
        // 检查剪贴板是否有图片
        if (Clipboard.ContainsImage()) {
            // 获取剪贴板图片
            Image image = Clipboard.GetImage();
            
            // 保存为PNG
            image.Save(outputPath, ImageFormat.Png);
            Console.WriteLine("PNG图片保存成功!");
        } else {
            Console.WriteLine("剪贴板中没有图片!");
        }
    }
}

方法三:使用Python实现

Python可以使用Pillow库和pywin32库实现:

import win32clipboard
from PIL import Image
import io

def dib_to_png(output_path):
    try:
        # 打开剪贴板
        win32clipboard.OpenClipboard()
        
        # 检查是否有DIB格式
        if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_DIB):
            # 获取DIB数据
            data = win32clipboard.GetClipboardData(win32clipboard.CF_DIB)
            
            # 使用Pillow处理
            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")

注意事项

  1. 剪贴板访问权限:确保在访问剪贴板前没有其他程序正在占用
  2. GDI+初始化:使用C++方案时,别忘了初始化和关闭GDI+
  3. 资源释放:正确释放HBITMAP等GDI对象,避免内存泄漏
  4. DIB格式变体:注意处理CF_DIBV5等扩展格式
  5. PNG压缩:可以根据需要调整PNG压缩级别
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐