如何从Windows剪贴板读取位图

5
我正在编写一个极小的C++程序来帮助我制作动画精灵。我希望它能够接收我从Photoshop复制到剪贴板的数据,并在我的程序中进行操作,然后用转换后的数据覆盖原有的剪贴板内容。
但问题是,我不知道如何读取来自Photoshop的初始剪贴板数据。我可以使用“GetClipboardData(CF_DIB)”加载剪贴板,并获得有效句柄,但我不知道如何使用该句柄。我已经尝试过SFML的“Image::LoadFromMemory(handle,GlobalSize(handle))”,它能够从内存中加载位图文件,但似乎并没有起作用。
我是否需要解析整个格式?在这种情况下,我应该查找哪种格式结构?是否有任何方法可以快速地操纵数据,使其看起来像位图文件?是否更容易/可能只使用Windows API将其保存到文件中? (然后我可以使用SFML加载该文件进行编辑)
这只是一个对于我自己而言的快速而简单的工具,因此效率或稳健性并不重要。

3
格式为:[一个包含BITMAPINFO结构和位图数据的内存对象]。 - Jerry Coffin
@JerryCoffin 是的,但是 BITMAPINFO 是一个可变长度的结构体,要想确定它的长度需要仔细检查 BITMAPINFOHEADER - dialer
2个回答

7
从维基百科了解位图结构,然后将其写入文件并写出像素。我已在Windows 8.1上使用Paint进行了测试。我打开了一个图片,然后按Ctrl + C复制到剪贴板。然后运行以下代码,它将剪贴板中的图像复制到桌面:
#include <iostream>
#include <fstream>
#include <windows.h>


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_DIB))
    {
        if (OpenClipboard(NULL))
        {
            HANDLE hClipboard = GetClipboardData(CF_DIB);
            if (hClipboard != NULL && hClipboard != INVALID_HANDLE_VALUE)
            {
                void* dib = GlobalLock(hClipboard);

                if (dib)
                {
                    BITMAPINFOHEADER* info = reinterpret_cast<BITMAPINFOHEADER*>(dib);
                    BITMAPFILEHEADER fileHeader = {0};
                    fileHeader.bfType = 0x4D42;
                    fileHeader.bfOffBits = 54;
                    fileHeader.bfSize = (((info->bmiHeader.biWidth * info->bmiHeader.biBitCount + 31) & ~31) / 8
                              * info->bmiHeader.biHeight) + fileHeader.bfOffBits;

                    std::cout<<"Type: "<<std::hex<<fileHeader.bfType<<std::dec<<"\n";
                    std::cout<<"bfSize: "<<fileHeader.bfSize<<"\n";
                    std::cout<<"Reserved: "<<fileHeader.bfReserved1<<"\n";
                    std::cout<<"Reserved2: "<<fileHeader.bfReserved2<<"\n";
                    std::cout<<"Offset: "<<fileHeader.bfOffBits<<"\n";
                    std::cout<<"biSize: "<<info->bmiHeader.biSize<<"\n";
                    std::cout<<"Width: "<<info->bmiHeader.biWidth<<"\n";
                    std::cout<<"Height: "<<info->bmiHeader.biHeight<<"\n";
                    std::cout<<"Planes: "<<info->bmiHeader.biPlanes<<"\n";
                    std::cout<<"Bits: "<<info->bmiHeader.biBitCount<<"\n";
                    std::cout<<"Compression: "<<info->bmiHeader.biCompression<<"\n";
                    std::cout<<"Size: "<<info->bmiHeader.biSizeImage<<"\n";
                    std::cout<<"X-res: "<<info->bmiHeader.biXPelsPerMeter<<"\n";
                    std::cout<<"Y-res: "<<info->bmiHeader.biYPelsPerMeter<<"\n";
                    std::cout<<"ClrUsed: "<<info->bmiHeader.biClrUsed<<"\n";
                    std::cout<<"ClrImportant: "<<info->bmiHeader.biClrImportant<<"\n";

                    std::ofstream file("C:/Users/Brandon/Desktop/Test.bmp", std::ios::out | std::ios::binary);
                    if (file)
                    {
                        file.write(reinterpret_cast<char*>(&fileHeader), sizeof(BITMAPFILEHEADER));
                        file.write(reinterpret_cast<char*>(info), sizeof(BITMAPINFOHEADER));
                        file.write(reinterpret_cast<char*>(++info), bmp.dib.biSizeImage);
                    }

                    GlobalUnlock(dib);
                }
            }

            CloseClipboard();
        }
    }

    return 0;
}

1
@Gigi 因为C++是强类型的,而fstream只有“write(char*)”签名。因此,您必须将所有其他类型转换为char*才能写入它们。或者,如果它是紧密打包的,您可以使用file.write(reinterpret_cast<char*>(&bmp), sizeof(bmp))代替所有这些file.write - Brandon
1
@Gigi;不是因为我正在写二进制。更多细节请参见:https://dev59.com/rl3Va4cB1Zd3GeqPCaa- - Brandon
这段代码有两个关键错误。它假设 biSizeImage 总是设置为合理的值。然而,对于 BI_RGB 位图,它可能被设置为零(在这种情况下,该值必须从位图的宽度和高度推导出来)。此外,它假设像素数据紧随 BITMAPINFOHEADER 之后。这是不正确的。这只适用于 BI_RGB。如果 biCompression 是其他任何值(例如,在截屏时使用的 BI_BITFIELDS),则在标头和像素位之间将有可变数量的字节。 - dialer
@dialer 它完全不会假定偏移量。它计算并强制指定位图头大小:bmp.header.offset = 54; 和像素大小:bmp.header.bfSize = info->biSizeImage + bmp.header.offset; 在这种情况下,当BITMAPINFOHEADER的头文件大小为54时,像素的确切位置是什么。当请求为CF_DIB时,这是100%保证的。截图永远不会被压缩。但是,如果第三方程序这样做,那么此代码将无法工作,您需要修复它。对于BI_PNG和BI_JPEG也可能发生同样的事情,但这太多了,这个回答讲不完。 - Brandon
有误的代码行是最后一个 file.write,其中 info + 1 不正确。我所有机器上的任何截图都将 biCompression 设置为 BI_BITFIELDS 而不是 BI_RGB(尽管从 mspaint 复制会设置为 BI_RGB)。而且 BI_BITFIELDS 意味着在 BITMAPINFOHEADER 后面跟随三个颜色掩码(DWORD)。即使设置了 biSizeImage,这些也不包括在内。这意味着你只是碰巧将位掩码复制到了正确的位置。但是你总共缺少了 3 个 DWORD 的数据,因为你复制的字节数是 biSizeImage 而不是 3*4 + biSizeImage - dialer
显示剩余3条评论

4

起初我并不想发表答案,毕竟你已经有了一个足够好的答案。但是我被迫这样做了,另外,当你搜索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结构的开头到像素数据数组的开头的偏移量(以字节为单位)。如果可选的位掩码和颜色表不存在,则此偏移量为常数40sizeof(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外),尽管较旧的CRT中%zu无法工作,谁知道呢
  • 错误处理不适用于生产环境。
  • CF_DIB的解释实际上是指“它是打包的DIB”。没有官方保证它将是一个普通的BITMAPINFOHEADER,即biSize == 40,尽管很可能情况是如此。
  • BITMAPFINO文档解释了该结构实际上长度是可变的,并且需要考虑BITMAPINFOHEADER.biClrUsed,但它未提到BI_BITFIELDS
  • BITMAPINFOHEADER有更多细节,但未提及BI_ALPHABITFIELDS或位掩码仅出现在多态BITMAPINFOHEADER结构实际上是一个普通的BITMAPINFOHEADER(即biSize == 40)的事实。后续版本,如BITMAPV5HEADER,无条件地包括位掩码。
  • 总而言之,BMP文件格式的维基百科文章包含了一个更简明、更连贯的DIB内存布局解释。
  • Paint的早期版本处理剪贴板,包括在GetDIBPixelDataOffset中所做的偏移量计算的方式非常类似(显然我不能在这里逐字张贴)。它不会假设biSize == 40。Paint的新版本使用COleServerItem处理剪贴板。
  • 最后,由GIMP和其他跨平台软件使用的GTK源代码实现了CF_DIB的非常相似。该代码甚至处理Web浏览器特定格式,因此更难跟踪。它是transmute_cf_dib_to_image_bmp函数。函数的length参数来自GlobalSize。请注意,它也不会假设biSize == 40
  • 如果使用CF_BITMAP调用SetClipboardData,它需要使用DDB(如果将其传递给真正的DIB,则会默默失败)。隐式转换为DIB的CF_BITMAP使用BI_BITFIELDS,这也适用于屏幕截图(至少如果原始DDB与屏幕DC兼容)。
  • 将位图放入剪贴板是一个全新的困扰。如果使用CF_BITMAP将DDB放入剪贴板,则不会复制该位图(至少最初不会)。如果任何程序对其进行操作,则所有访问剪贴板的程序都将看到受到操纵的位图。但是,只要有一个应用程序请求它作为CF_DIB,Windows就会施加一些魔术,那就不再正确了,位图现在已经是副本。这不适用于作为CF_DIB放入剪贴板的位图,这些位图立即免疫其他程序的操作。相比之下,CF_DIB似乎具有较少的令人不快的含义和惊

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接