【问题标题】:How to read a bitmap from the Windows Clipboard如何从 Windows 剪贴板读取位图
【发布时间】:2015-08-13 15:48:28
【问题描述】:

我正在编写一个非常小的 C++ 程序来帮助我制作精灵动画。我希望它把我从 photoshop 复制到剪贴板的数据,在我的程序中操作它,然后用转换覆盖剪贴板。

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

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

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

对于我自己来说,这只是一个快速而肮脏的工具,可以在 Photoshop 中节省大量繁重的工作,因此效率或稳健性根本不重要。

【问题讨论】:

标签: c++ windows bitmap sfml


【解决方案1】:

从 Wikipedia 学习位图结构,然后将其写入文件,然后写入像素..

我已经在 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;
}

【讨论】:

  • 太棒了!但是有一个问题,为什么需要所有这些演员?
  • @Gigi 因为 C++ 是强类型的,fstream 只有“write(char*)”签名。因此,您必须强制转换所有其他类型才能编写它们。或者,您可以这样做:file.write(reinterpret_cast&lt;char*&gt;(&amp;bmp), sizeof(bmp)),如果它是紧凑的,而不是所有那些 file.write 的。
  • 你不能像这样使用重载的&lt;&lt; 运算符吗? cppforschool.com/assignment/file-handling-sol/…
  • @Gigi;不,因为我正在编写二进制文件。详情请见:stackoverflow.com/questions/8277485/…
  • 此代码有 2 个严重错误。它假定biSizeImage 将始终设置为一个合理的值。但是,对于BI_RGB 位图,它可以设置为零(在这种情况下,必须从位图宽度和高度推导出该值)。此外,它假设像素数据紧跟BITMAPINFOHEADER。这是不正确的。这仅适用于BI_RGB。如果biCompression 是其他任何东西(例如在截屏时的BI_BITFIELDS),则在标头之后和像素位之前会有可变数量的字节。
【解决方案2】:

我最初不打算发布答案,毕竟你已经有了一个足够好的答案。不过我想我还是被逼着这么做的,另外,这是你搜索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
  • 如果用CF_BITMAP 调用SetClipboardData,它需要一个DDB(如果你传递一个HBITMAP,它会默默地失败,这实际上是一个DIB)。隐式转换为 DIB 的 CF_BITMAP 使用 BI_BITFIELDS,这也适用于屏幕截图(至少在原始 DDB 与屏幕 DC 兼容的情况下)。
  • 将位图放入剪贴板是一种全新的蠕虫。如果使用CF_BITMAP 将 DDB 放入剪贴板,则不会复制该位图(至少最初不会复制)。如果有任何程序对其进行操作,则所有访问剪贴板的程序都将看到被操作的位图。但是,一旦任何应用程序以CF_DIB 的形式请求它,Windows 就会应用一堆魔法,而这不再是真的,位图现在是一个副本。这不适用于作为CF_DIB 放入剪贴板的位图,这些位图会立即不受其他程序的操作。 CF_DIB 似乎减少了令人不快的影响和意外,而且似乎也被大多数应用程序使用。虽然您可以尝试在将原始位图放入剪贴板时保留其格式,但我选择对传出数据使用固定的包罗万象的格式,因为它已经够疯狂了。
  • SetClipboardData 暗示 CF_DIB 不适用于 Windows 应用商店应用程序,但我无法证实这一说法。再说一次,关于 NULL 所有者的段落也是不正确的。
  • PutBitmapInClipboard_AsDIBPutBitmapInClipboard_From32bppTopDownRGBAData 都尽可能保留 alpha 通道,尽管 GDI 绘图函数不支持 alpha 并且会破坏它(SFML 可以很好地处理它)。将 alpha 通道放在 MSB 中并使用BI_RGB 似乎是在 DIB 中存储 alpha 的事实标准。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-11
    • 1970-01-01
    • 1970-01-01
    • 2019-09-25
    • 2013-11-16
    • 1970-01-01
    • 1970-01-01
    • 2019-04-22
    相关资源
    最近更新 更多