如何从Windows剪贴板中读取位图

时间:2015-05-30 23:10:17

标签: c++ windows bitmap sfml

我正在编写一个非常小的C ++程序来帮助我激活精灵。我想把它从photoshop复制到剪贴板,在我的程序中操作它,然后用变换覆盖剪贴板。

但问题是我不确定如何从photoshop读取初始剪贴板。

我可以使用GetClipboardData(CF_DIB)加载剪贴板,并获得有效的句柄,但我不知道如何使用该句柄。我尝试过使用能够从内存中加载位图文件的SFML Image::LoadFromMemory(handle, GlobalSize(handle)),但这似乎不起作用。

我是否需要实际解析整个格式?在这种情况下,我会看到什么样的格式结构?是否有可能以任何方式快速破坏数据,使其看起来像位图文件?使用Windows API简单地将其保存到文件是否更容易/可能? (然后我可以用SFML加载该文件进行编辑,那样)

对于我来说,这只是一个快速而又脏的工具,可以在photoshop中保存大量繁琐的工作,因此效率或稳健性并不重要。

2 个答案:

答案 0 :(得分:5)

从维基百科学习位图结构,然后将其写入文件,然后写出像素。

我在Windows 8.1上使用Paint测试了下面的内容。我用油漆打开了一个图像,然后按下Ctrl + C复制到剪贴板..然后我运行了以下代码并将剪贴板图像复制到桌面:

#include <iostream>
#include <fstream>
#include <windows.h>

typedef struct
{
    std::uint32_t biSize;
    std::int32_t  biWidth;
    std::int32_t  biHeight;
    std::uint16_t  biPlanes;
    std::uint16_t  biBitCount;
    std::uint32_t biCompression;
    std::uint32_t biSizeImage;
    std::int32_t  biXPelsPerMeter;
    std::int32_t  biYPelsPerMeter;
    std::uint32_t biClrUsed;
    std::uint32_t biClrImportant;
} DIB;

typedef struct
{
    std::uint16_t type;
    std::uint32_t bfSize;
    std::uint32_t reserved;
    std::uint32_t offset;
} HEADER;

typedef struct
{
    HEADER header;
    DIB dib;
} BMP;


int main()
{
    std::cout<<"Format Bitmap: "<<IsClipboardFormatAvailable(CF_BITMAP)<<"\n";
    std::cout<<"Format DIB: "<<IsClipboardFormatAvailable(CF_DIB)<<"\n";
    std::cout<<"Format DIBv5: "<<IsClipboardFormatAvailable(CF_DIBV5)<<"\n";

    if (IsClipboardFormatAvailable(CF_BITMAP) || IsClipboardFormatAvailable(CF_DIB) || IsClipboardFormatAvailable(CF_DIBV5))
    {
        if (OpenClipboard(NULL))
        {
            HANDLE hClipboard = GetClipboardData(CF_DIB);

            if (!hClipboard)
            {
                hClipboard = GetClipboardData(CF_DIBV5);
            }

            if (hClipboard != NULL && hClipboard != INVALID_HANDLE_VALUE)
            {
                void* dib = GlobalLock(hClipboard);

                if (dib)
                {
                    DIB *info = reinterpret_cast<DIB*>(dib);
                    BMP bmp = {0};
                    bmp.header.type = 0x4D42;
                    bmp.header.offset = 54;
                    bmp.header.bfSize = info->biSizeImage + bmp.header.offset;
                    bmp.dib = *info;

                    std::cout<<"Type: "<<std::hex<<bmp.header.type<<std::dec<<"\n";
                    std::cout<<"bfSize: "<<bmp.header.bfSize<<"\n";
                    std::cout<<"Reserved: "<<bmp.header.reserved<<"\n";
                    std::cout<<"Offset: "<<bmp.header.offset<<"\n";
                    std::cout<<"biSize: "<<bmp.dib.biSize<<"\n";
                    std::cout<<"Width: "<<bmp.dib.biWidth<<"\n";
                    std::cout<<"Height: "<<bmp.dib.biHeight<<"\n";
                    std::cout<<"Planes: "<<bmp.dib.biPlanes<<"\n";
                    std::cout<<"Bits: "<<bmp.dib.biBitCount<<"\n";
                    std::cout<<"Compression: "<<bmp.dib.biCompression<<"\n";
                    std::cout<<"Size: "<<bmp.dib.biSizeImage<<"\n";
                    std::cout<<"X-res: "<<bmp.dib.biXPelsPerMeter<<"\n";
                    std::cout<<"Y-res: "<<bmp.dib.biYPelsPerMeter<<"\n";
                    std::cout<<"ClrUsed: "<<bmp.dib.biClrUsed<<"\n";
                    std::cout<<"ClrImportant: "<<bmp.dib.biClrImportant<<"\n";

                    std::ofstream file("C:/Users/Brandon/Desktop/Test.bmp", std::ios::out | std::ios::binary);
                    if (file)
                    {
                        file.write(reinterpret_cast<char*>(&bmp.header.type), sizeof(bmp.header.type));
                        file.write(reinterpret_cast<char*>(&bmp.header.bfSize), sizeof(bmp.header.bfSize));
                        file.write(reinterpret_cast<char*>(&bmp.header.reserved), sizeof(bmp.header.reserved));
                        file.write(reinterpret_cast<char*>(&bmp.header.offset), sizeof(bmp.header.offset));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biSize), sizeof(bmp.dib.biSize));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biWidth), sizeof(bmp.dib.biWidth));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biHeight), sizeof(bmp.dib.biHeight));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biPlanes), sizeof(bmp.dib.biPlanes));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biBitCount), sizeof(bmp.dib.biBitCount));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biCompression), sizeof(bmp.dib.biCompression));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biSizeImage), sizeof(bmp.dib.biSizeImage));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biXPelsPerMeter), sizeof(bmp.dib.biXPelsPerMeter));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biYPelsPerMeter), sizeof(bmp.dib.biYPelsPerMeter));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biClrUsed), sizeof(bmp.dib.biClrUsed));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biClrImportant), sizeof(bmp.dib.biClrImportant));
                        file.write(reinterpret_cast<char*>(info + 1), bmp.dib.biSizeImage);
                    }

                    GlobalUnlock(dib);
                }
            }

            CloseClipboard();
        }
    }

    return 0;
}

答案 1 :(得分:1)

我最初不打算发布答案,毕竟您已经有了足够好的答案。但我想无论如何我都是被迫这样做的,此外,这是您搜索GetClipboardData CF_DIB时弹出的主要问题,所以不妨尝试提出一个更完整的解决方案。

不幸的是,剪贴板格式是一个雷区。 GDI 位图是一个更大的雷区。 CF_DIB 为您提供了一个“打包的 DIB”,如果您真的想用它做任何有意义的事情,您确实需要在某种程度上解析它。这是布局(伪代码):

struct PACKED_DIB {
    struct BITMAPINFO {
        BITMAPINFOHEADER bih; // 40 bytes
        DWORD optional_RGB_bitmaks[]; // (variable size)
        DWORD optional_color_table[]; // (variable size)
    }
    BYTE pixel_data_array[]; // (variable size)
}

结构的总大小由 GlobalSize() 给出。任何进一步处理所需的关键信息位是从 BITMAPINFO 结构的开头到像素数据数组的开头的偏移量(以字节为单位)。如果可选位掩码和颜色表不存在,则此偏移量是常数 40 (sizeof(BITMAPINFOHEADER))。 是否是这种情况完全取决于应用程序如何将位图放入剪贴板。大多数应用程序都这样做,因为这是最简单的方法。

此代码计算偏移量:

// Returns the offset, in bytes, from the start of the BITMAPINFO, to the start of the pixel data array, for a packed DIB.
static INT GetPixelDataOffsetForPackedDIB(const BITMAPINFOHEADER *BitmapInfoHeader)
{
    INT OffsetExtra = 0;

    if (BitmapInfoHeader->biSize == sizeof(BITMAPINFOHEADER) /* 40 */)
    {
        // This is the common BITMAPINFOHEADER type. In this case, there may be bit masks following the BITMAPINFOHEADER
        // and before the actual pixel bits (does not apply if bitmap has <= 8 bpp)
        if (BitmapInfoHeader->biBitCount > 8)
        {
            if (BitmapInfoHeader->biCompression == BI_BITFIELDS)
            {
                OffsetExtra += 3 * sizeof(RGBQUAD);
            }
            else if (BitmapInfoHeader->biCompression == 6 /* BI_ALPHABITFIELDS */)
            {
                // Not widely supported, but technically a valid DIB format.
                // You *can* get this in the clipboard, although neither GDI nor stb_image will like it.
                OffsetExtra += 4 * sizeof(RGBQUAD);
            }
        }
    }

    if (BitmapInfoHeader->biClrUsed > 0)
    {
        // We have no choice but to trust this value.
        OffsetExtra += BitmapInfoHeader->biClrUsed * sizeof(RGBQUAD);
    }
    else
    {
        // In this case, the color table contains the maximum number for the current bit count (0 if > 8bpp)
        if (BitmapInfoHeader->biBitCount <= 8)
        {
            // 1bpp: 2
            // 4bpp: 16
            // 8bpp: 256
            OffsetExtra += sizeof(RGBQUAD) << BitmapInfoHeader->biBitCount;
        }
    }

    return BitmapInfoHeader->biSize + OffsetExtra;
}

下面的程序演示了您可以使用此偏移量做的几件事:

  • 将剪贴板图像写入 .bmp 文件
  • 使用 SFML (sf::Image::loadFromMemory) 从内存加载
  • 将其从 SFML 图像放回剪贴板
  • 将其转换为 HBITMAP(以便在 GDI 中使用)
  • 将它从 GDI 放回剪贴板 HBITMAP
#include <sdkddkver.h>
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#if DEMO_SFML
#include <SFML/Graphics.hpp>
#endif


static BOOL OpenClipboard_ButTryABitHarder(HWND ClipboardOwner);
static INT GetPixelDataOffsetForPackedDIB(const BITMAPINFOHEADER *BitmapInfoHeader);
static void PutBitmapInClipboard_AsDIB(HBITMAP hBitmap);
static void PutBitmapInClipboard_From32bppTopDownRGBAData(INT Width, INT Height, const void *Data32bppRGBA);


int wmain(int argc, wchar_t *argv[])
{
    if (!OpenClipboard_ButTryABitHarder(NULL))
    {
        // Could not open clipboard. This usually indicates that another application is permanently blocking it.
        return 1;
    }

    HGLOBAL ClipboardDataHandle = (HGLOBAL)GetClipboardData(CF_DIB);
    if (!ClipboardDataHandle)
    {
        // Clipboard object is not a DIB, and is not auto-convertible to DIB
        CloseClipboard();
        return 0;
    }

    BITMAPINFOHEADER *BitmapInfoHeader = (BITMAPINFOHEADER *)GlobalLock(ClipboardDataHandle);
    assert(BitmapInfoHeader); // This can theoretically fail if mapping the HGLOBAL into local address space fails. Very pathological, just act as if it wasn't a bitmap in the clipboard.

    SIZE_T ClipboardDataSize = GlobalSize(ClipboardDataHandle);
    assert(ClipboardDataSize >= sizeof(BITMAPINFOHEADER)); // Malformed data. While older DIB formats exist (e.g. BITMAPCOREHEADER), they are not valid data for CF_DIB; it mandates a BITMAPINFO struct. If this fails, just act as if it wasn't a bitmap in the clipboard.

    INT PixelDataOffset = GetPixelDataOffsetForPackedDIB(BitmapInfoHeader);


    // ============================================================================================================
    // ============================================================================================================
    //
    // Example 1: Write it to a .bmp file
    //


    // The clipboard contains a packed DIB, whose start address coincides with BitmapInfoHeader, and whose total size is ClipboardDataSize.
    // By definition, we can jam the whole DIB memory into a BMP file as-is, except that we need to prepend a BITMAPFILEHEADER struct.
    // The tricky part is that for BITMAPFILEHEADER.bfOffBits, which must be calculated using the information in BITMAPINFOHEADER.

    // The BMP file layout:
    // @offset 0:                              BITMAPFILEHEADER
    // @offset 14 (sizeof(BITMAPFILEHEADER)):  BITMAPINFOHEADER
    // @offset 14 + BitmapInfoHeader->biSize:  Optional bit masks and color table
    // @offset 14 + DIBPixelDataOffset:        pixel bits
    // @offset 14 + ClipboardDataSize:         EOF
    size_t TotalBitmapFileSize = sizeof(BITMAPFILEHEADER) + ClipboardDataSize;
    wprintf(L"BITMAPINFOHEADER size:          %u\r\n", BitmapInfoHeader->biSize);
    wprintf(L"Format:                         %hubpp, Compression %u\r\n", BitmapInfoHeader->biBitCount, BitmapInfoHeader->biCompression);
    wprintf(L"Pixel data offset within DIB:   %u\r\n", PixelDataOffset);
    wprintf(L"Total DIB size:                 %zu\r\n", ClipboardDataSize);
    wprintf(L"Total bitmap file size:         %zu\r\n", TotalBitmapFileSize);

    BITMAPFILEHEADER BitmapFileHeader = {};
    BitmapFileHeader.bfType = 0x4D42;
    BitmapFileHeader.bfSize = (DWORD)TotalBitmapFileSize; // Will fail if bitmap size is nonstandard >4GB
    BitmapFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + PixelDataOffset;

    HANDLE FileHandle = CreateFileW(L"test.bmp", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (FileHandle != INVALID_HANDLE_VALUE)
    {
        DWORD dummy = 0;
        BOOL Success = true;
        Success &= WriteFile(FileHandle, &BitmapFileHeader, sizeof(BITMAPFILEHEADER), &dummy, NULL);
        Success &= WriteFile(FileHandle, BitmapInfoHeader, (DWORD)ClipboardDataSize, &dummy, NULL);
        Success &= CloseHandle(FileHandle);
        if (Success)
        {
            wprintf(L"File saved.\r\n");
        }
    }

#if DEMO_SFML

    // ============================================================================================================
    // ============================================================================================================
    //
    // Example 2: Load it from memory in SFML
    //


    // SFML expects a whole bitmap file, including its BITMAPFILEHEADER, in memory.
    // So this is similar to Example 1, except in memory.
    BYTE *BitmapFileContents = (BYTE *)malloc(TotalBitmapFileSize);
    assert(BitmapFileContents);
    memcpy(BitmapFileContents, &BitmapFileHeader, sizeof(BITMAPFILEHEADER));
    // Append DIB
    memcpy(BitmapFileContents + sizeof(BITMAPFILEHEADER), BitmapInfoHeader, ClipboardDataSize);
    sf::Image image;
    image.loadFromMemory(BitmapFileContents, TotalBitmapFileSize);
    // The memory can be freed once the image has been loaded in SFML.
    free(BitmapFileContents);

    // Manipulate it:
    image.flipHorizontally();
                
    // Put it back in the clipboard:
    PutBitmapInClipboard_From32bppTopDownRGBAData(image.getSize().x, image.getSize().y, image.getPixelsPtr());

#else

    // ============================================================================================================
    // ============================================================================================================
    //
    // Example 3: Convert to HBITMAP for GDI
    //


    BYTE *PixelDataFromClipboard = (BYTE *)BitmapInfoHeader + PixelDataOffset;
    // This will only work if the DIB format is supported by GDI. Not all formats are supported.
    BYTE *PixelDataNew;
    HBITMAP hBitmap = CreateDIBSection(NULL, (BITMAPINFO *)BitmapInfoHeader, DIB_RGB_COLORS, (void **)&PixelDataNew, NULL, 0);
    assert(hBitmap);

    // Need to copy the data from the clipboard to the new DIBSection.
    BITMAP BitmapDesc = {};
    GetObjectW(hBitmap, sizeof(BitmapDesc), &BitmapDesc);
    SIZE_T PixelDataBytesToCopy = (SIZE_T)BitmapDesc.bmHeight * BitmapDesc.bmWidthBytes;
    SIZE_T PixelDataBytesAvailable = ClipboardDataSize - PixelDataOffset;
    if (PixelDataBytesAvailable < PixelDataBytesToCopy)
    {
        // Malformed data; doesn't contain enough pixels.
        PixelDataBytesToCopy = PixelDataBytesAvailable;
    }
    memcpy(PixelDataNew, PixelDataFromClipboard, PixelDataBytesToCopy);
    // NOTE: While it is possible to create a DIB section without copying the pixel data, in general you'd want to
    //       copy it anyway because the clipboard needs to be closed asap.

    // Draw something on it.
    PixelDataNew[7] = 0;
    PixelDataNew[11] = 100;
    HDC hdc = CreateCompatibleDC(NULL);
    assert(hdc);
    SelectObject(hdc, hBitmap);
    RECT rc = { 0, 0, BitmapDesc.bmWidth / 2, BitmapDesc.bmHeight / 2 };
    HBRUSH brush = CreateSolidBrush(RGB(250, 100, 0));
    FillRect(hdc, &rc, brush);
    DeleteObject(brush);
    DeleteDC(hdc);


    // ============================================================================================================
    // ============================================================================================================
    //
    // Copy it back to the clipboard.
    //


    PutBitmapInClipboard_AsDIB(hBitmap);
#endif // DEMO_SFML

    GlobalUnlock(ClipboardDataHandle);

    CloseClipboard();

    return 0;
}


static BOOL OpenClipboard_ButTryABitHarder(HWND hWnd)
{
    for (int i = 0; i < 20; ++i)
    {
        // This can fail if the clipboard is currently being accessed by another application.
        if (OpenClipboard(hWnd)) return true;
        Sleep(10);
    }
    return false;
}


// Returns the offset, in bytes, from the start of the BITMAPINFO, to the start of the pixel data array, for a packed DIB.
static INT GetPixelDataOffsetForPackedDIB(const BITMAPINFOHEADER *BitmapInfoHeader)
{
    INT OffsetExtra = 0;

    if (BitmapInfoHeader->biSize == sizeof(BITMAPINFOHEADER) /* 40 */)
    {
        // This is the common BITMAPINFOHEADER type. In this case, there may be bit masks following the BITMAPINFOHEADER
        // and before the actual pixel bits (does not apply if bitmap has <= 8 bpp)
        if (BitmapInfoHeader->biBitCount > 8)
        {
            if (BitmapInfoHeader->biCompression == BI_BITFIELDS)
            {
                OffsetExtra += 3 * sizeof(RGBQUAD);
            }
            else if (BitmapInfoHeader->biCompression == 6 /* BI_ALPHABITFIELDS */)
            {
                // Not widely supported, but valid.
                OffsetExtra += 4 * sizeof(RGBQUAD);
            }
        }
    }

    if (BitmapInfoHeader->biClrUsed > 0)
    {
        // We have no choice but to trust this value.
        OffsetExtra += BitmapInfoHeader->biClrUsed * sizeof(RGBQUAD);
    }
    else
    {
        // In this case, the color table contains the maximum number for the current bit count (0 if > 8bpp)
        if (BitmapInfoHeader->biBitCount <= 8)
        {
            // 1bpp: 2
            // 4bpp: 16
            // 8bpp: 256
            OffsetExtra += sizeof(RGBQUAD) << BitmapInfoHeader->biBitCount;
        }
    }

    return BitmapInfoHeader->biSize + OffsetExtra;
}


// Helper function for interaction with libraries like stb_image.
// Data will be copied, so you can do what you want with it after this function returns.
static void PutBitmapInClipboard_From32bppTopDownRGBAData(INT Width, INT Height, const void *Data32bppRGBA)
{
    // Nomenclature: Data at offset 0 is R top left corner, offset 1 is G top left corner, etc.
    //               This is pretty much the opposite of what a HBITMAP normally does.
    assert(Width > 0);
    assert(Height > 0);
    assert(Data32bppRGBA);

    // GDI won't help us here if we want to preserve the alpha channel. It doesn't support BI_ALPHABITFIELDS, and
    // we can't use BI_RGB directly because BI_RGB actually means BGRA in reality.
    // That means, unfortunately it's not going to be a simple memcpy :(
    
    DWORD PixelDataSize = 4/*32bpp*/ * Width * Height;
    // We need BI_BITFIELDS for RGB color masks here.
    size_t TotalSize = sizeof(BITMAPINFOHEADER) + PixelDataSize;
    HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, TotalSize);
    assert(hGlobal);
    void *mem = GlobalLock(hGlobal);
    assert(mem);

    BITMAPINFOHEADER *bih = (BITMAPINFOHEADER *)mem;
    bih->biSize = sizeof(BITMAPINFOHEADER);
    bih->biWidth = Width;
    bih->biHeight = -Height; // Negative height means top-down bitmap
    bih->biPlanes = 1;
    bih->biBitCount = 32;
    bih->biCompression = BI_RGB;
    bih->biSizeImage = PixelDataSize;

    BYTE *PixelData = (BYTE *)mem + sizeof(BITMAPINFOHEADER);
    DWORD NumPixels = Width * Height;
    for (DWORD i = 0; i < NumPixels; ++i)
    {
        // Convert RGBA to BGRA
        DWORD tmp = ((DWORD *)Data32bppRGBA)[i];
        DWORD tmp2 = tmp & 0xff00ff00; // assumes LE
        tmp2 |= (tmp >> 16) & 0xff;
        tmp2 |= (tmp & 0xff) << 16;
        ((DWORD *)PixelData)[i] = tmp2;
    }

    GlobalUnlock(hGlobal);

    EmptyClipboard();
    SetClipboardData(CF_DIB, hGlobal);

    // The hGlobal now belongs to the clipboard. Do not free it.
}


// Bitmap will be copied, so you can do what you want with it after this function returns.
static void PutBitmapInClipboard_AsDIB(HBITMAP hBitmap)
{
    // Need this to get the bitmap dimensions.
    BITMAP desc = {};
    int tmp = GetObjectW(hBitmap, sizeof(desc), &desc);
    assert(tmp != 0);

    // We need to build this structure in a GMEM_MOVEABLE global memory block:
    //   BITMAPINFOHEADER (40 bytes)
    //   PixelData (4 * Width * Height bytes)
    // We're enforcing 32bpp BI_RGB, so no bitmasks and no color table.
    // NOTE: SetClipboardData(CF_DIB) insists on the size 40 version of BITMAPINFOHEADER, otherwise it will misinterpret the data.

    DWORD PixelDataSize = 4/*32bpp*/ * desc.bmWidth * desc.bmHeight; // Correct alignment happens implicitly.
    assert(desc.bmWidth > 0);
    assert(desc.bmHeight > 0);
    size_t TotalSize = sizeof(BITMAPINFOHEADER) + PixelDataSize;
    HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, TotalSize);
    assert(hGlobal);
    void *mem = GlobalLock(hGlobal);
    assert(mem);

    BITMAPINFOHEADER *bih = (BITMAPINFOHEADER *)mem;
    bih->biSize = sizeof(BITMAPINFOHEADER);
    bih->biWidth = desc.bmWidth;
    bih->biHeight = desc.bmHeight;
    bih->biPlanes = 1;
    bih->biBitCount = 32;
    bih->biCompression = BI_RGB;
    bih->biSizeImage = PixelDataSize;
    HDC hdc = CreateCompatibleDC(NULL);
    assert(hdc);
    HGDIOBJ old = SelectObject(hdc, hBitmap);
    assert(old != nullptr); // This can fail if the hBitmap is still selected into a different DC.
    void *PixelData = (BYTE *)mem + sizeof(BITMAPINFOHEADER);
    // Pathologial "bug": If the bitmap is a DDB that originally belonged to a device with a different palette, that palette is lost. The caller would need to give us the correct HDC, but this is already insane enough as it is.
    tmp = GetDIBits(hdc, hBitmap, 0, desc.bmHeight, PixelData, (BITMAPINFO *)bih, DIB_RGB_COLORS);
    assert(tmp != 0);
    // NOTE: This will correctly preserve the alpha channel if possible, but it's up to the receiving application to handle it.
    DeleteDC(hdc);

    GlobalUnlock(hGlobal);

    EmptyClipboard();
    SetClipboardData(CF_DIB, hGlobal);

    // The hGlobal now belongs to the clipboard. Do not free it.
}

我打算将此代码主要用于生产,因为我自己需要它,如果有人发现问题,我很乐意听到。

一些补充说明供参考:

  • 在 Win10 上测试
  • 在 WinXP 上测试过(SFML 除外),虽然 %zu 在较旧的 CRT 中不起作用,谁知道
  • 错误处理不是生产就绪的。
  • the explanation of CF_DIB 真正想说的是“这是一个压缩的 DIB”。没有官方保证它会是一个普通的 BITMAPINFOHEADER,即 biSize == 40,尽管很可能是这种情况。
  • BITMAPFINO documentation 说明结构的长度确实是可变的,需要考虑 BITMAPINFOHEADER.biClrUsed,但没有提及 BI_BITFIELDS
  • BITMAPINFOHEADER 对此有更多详细信息,但没有提及 BI_ALPHABITFIELDS 或仅当多态 BITMAPINFOHEADER 结构实际上是一个普通的 BITMAPINFOHEADER 时才存在位掩码的事实(即biSize == 40)。更高版本(如 BITMAPV5HEADER)无条件地包含位掩码。
  • 总而言之,wikipedia article on the BMP file format 包含对 DIB 内存布局的更加简洁和连贯的解释。
  • 旧版本的 Paint 处理剪贴板,包括偏移量计算,其方式与我上面在 GetDIBPixelDataOffset 中所做的非常相似(显然我无法在此处逐字贴出)。它不假设 biSize == 40。较新版本的 Paint 使用 COleServerItem 进行剪贴板处理。
  • 作为最终参考,GIMP 和其他跨平台软件使用的 GTK 的源代码实现 CF_DIB 非常相似。该代码甚至可以处理特定于 Web 浏览器的格式,因此有点难以遵循。这是transmute_cf_dib_to_image_bmp function。该函数的 length 参数来自 GlobalSize。请注意,它也不假定 biSize == 40
  • 如果使用 SetClipboardData 调用 CF_BITMAP,则它需要一个 DDB(如果您传递给它一个实际上是 DIB 的 HBITMAP,它将默默地失败) .隐式转换为 DIB 的 CF_BITMAP 使用 BI_BITFIELDS,这也适用于屏幕截图(至少如果原始 DDB 与屏幕 DC 兼容)。
  • 将位图放入剪贴板是一种全新的蠕虫。如果用 CF_BITMAP 将 DDB 放入剪贴板,则不会复制该位图(至少最初不会)。如果任何程序对其进行操作,所有访问剪贴板的程序都会看到被操作的位图。但是,只要有任何应用程序将其作为 CF_DIB 请求,Windows 就会应用一堆魔法,而这不再正确,位图现在是一个副本。这不适用于作为 CF_DIB 放入剪贴板的位图,这些位图立即不受其他程序的操作影响。 CF_DIB 似乎没有令人不快的暗示和意外,而且似乎也被大多数应用程序使用。虽然您可以在将原始位图放入剪贴板时尝试保留其格式,但我选择对传出数据使用固定的全能格式,因为这已经够疯狂了。
  • SetClipboardData 暗示 CF_DIB 不适用于 Windows Store 应用程序,但我无法确认该声明。再说一次,关于 NULL 所有者的段落也是不正确的。
  • 如果可能,PutBitmapInClipboard_AsDIBPutBitmapInClipboard_From32bppTopDownRGBAData 都会保留 alpha 通道,尽管演示的 GDI 绘图函数不支持 alpha 并且会破坏它(SFML 会很好地处理它)。将 alpha 通道放入 MSB 并使用 BI_RGB 似乎是在 DIB 中存储 alpha 的事实上的标准。