如何将 RGBA 图像复制到 Windows' 剪贴板

How to copy an RGBA image to Windows' Clipboard

如何将 32 位(每像素)RGBA 图像复制到 Windows' 剪贴板?经过大量试验后,我已经达到了这个功能,但我的图像数据根本没有“粘贴”。它也没有出现在剪贴板的历史记录中。

稍微编辑它以使用 CF_DIBBITMAPINFOHEADER header 已在该历史记录中生成一个“复制”条目,并在粘贴时生成正确大小的图像,尽管粘贴了pngCF_DIB 的背后导致程序以非常有趣和 non-benign 的方式出现故障。

我的目标是将带有 alpha 通道的图像复制到剪贴板,并且在 hand-off 期间不让颜色与此 alpha 相乘。我做错了什么..?

bool copyBitmapIntoClipboard(Window & window, const Bitmap & in) {
    //  this section is my code for creating a png file
    StreamWrite stream = StreamWrite::asBufferCreate();
    in.savePng(stream);
    uint64 bufSize = 0;
    char * buf = stream._takeBuffer(bufSize, false);
    // "buf"      <-- contains the PNG payload
    // "bufSize"  <-- is the size of this payload

    // beyond this point, it's just standard windows' stuff that doesn't rely on my code
    BITMAPV5HEADER header;
    header.bV5Size          = sizeof(BITMAPV5HEADER);
    header.bV5Width         = in.getX(); // <-- size of the bitmap in pixels, width and height
    header.bV5Height        = in.getY();
    header.bV5Planes        = 1;
    header.bV5BitCount      = 0;
    header.bV5Compression   = BI_PNG;
    header.bV5SizeImage     = bufSize;
    header.bV5XPelsPerMeter = 0;
    header.bV5YPelsPerMeter = 0;
    header.bV5ClrUsed       = 0;
    header.bV5ClrImportant  = 0;
    header.bV5RedMask       = 0xFF000000;
    header.bV5GreenMask     = 0x00FF0000;
    header.bV5BlueMask      = 0x0000FF00;
    header.bV5AlphaMask     = 0x000000FF;
    header.bV5CSType        = LCS_sRGB;
    header.bV5Endpoints;    // ignored
    header.bV5GammaRed      = 0;
    header.bV5GammaGreen    = 0;
    header.bV5GammaBlue     = 0;
    header.bV5Intent        = 0;
    header.bV5ProfileData   = 0;
    header.bV5ProfileSize   = 0;
    header.bV5Reserved      = 0;

    HGLOBAL gift = GlobalAlloc(GMEM_MOVEABLE, sizeof(BITMAPV5HEADER) + bufSize);
    if (gift == NULL) 
        return false;

    HWND win = window.getWindowHandle();
    if (!OpenClipboard(win)) {
        GlobalFree(gift);
        return false;
    }
    EmptyClipboard();

    void * giftLocked = GlobalLock(gift);
    if (giftLocked) {
        memcpy(giftLocked, &header, sizeof(BITMAPV5HEADER));
        memcpy((char*)giftLocked + sizeof(BITMAPV5HEADER), buf, bufSize);
    }
    GlobalUnlock(gift);

    SetClipboardData(CF_DIBV5, gift);

    CloseClipboard();
    return true;
}

至少根据我的经验,尝试使用 BITMAPV5HEADER 传输 png 数据几乎是完全失败的,除非您基本上计划将其严格用作内部格式。

一种至少对相当数量的应用程序有效的策略是注册 PNG 剪贴板格式,并将 PNG 文件的内容放入剪贴板(没有其他 header).代码看起来像这样:

bool copyBitmapIntoClipboard(Window & window, const Bitmap & in) {
    //  this section is my code for creating a png file
    StreamWrite stream = StreamWrite::asBufferCreate();
    in.savePng(stream);
    uint64 bufSize = 0;
    char * buf = stream._takeBuffer(bufSize, false);
    // "buf"      <-- contains the PNG payload
    // "bufSize"  <-- is the size of this payload

    HGLOBAL gift = GlobalAlloc(GMEM_MOVEABLE, bufSize);
    if (gift == NULL) 
        return false;

    HWND win = window.getWindowHandle();
    if (!OpenClipboard(win)) {
        GlobalFree(gift);
        return false;
    }
    EmptyClipboard();

    auto fmt = RegisterClipboardFormat("PNG"); // or `L"PNG", as applicable

    void * giftLocked = GlobalLock(gift);
    if (giftLocked) {
        memcpy((char*)giftLocked, buf, bufSize);
    }
    GlobalUnlock(gift);

    SetClipboardData(fmt, gift);

    CloseClipboard();
    return true;
}

我使用过这样的代码,并成功地将内容粘贴到至少 LibreOffice Write 和 Calc、MS Word 和 Paint.Net 的最新版本中。

如果您告诉它复制位图,这也是 Chrome(例如)将作为第一种(首选)格式生成的格式。

另一方面,FireFox 生成了一大堆格式,但不是这个。它会产生一个 CF_DIBV5,但至少如果没记错的话,它有 pre-multiplied alpha(或者它可能完全失去 alpha——我不太记得了。不会像你那样保留它'无论如何我都想要)。

Gimp 将接受 32 位 RGB 格式 DIB,在 left-over 字节中带有 alpha,并使用该 alpha。无论好坏,据我所知,这是关于 只有 的东西,它可以将某些东西粘贴到 Gimp 中并保留其 alpha(不是 pre-multiplied) .

备注

  • 随着版本的更新,它们支持的格式可能会发生变化,所以即使(例如)我上次尝试时 PNG 不能与 Gimp 一起使用,但现在可能可以了。

  • 您可以将相同的数据以不同的格式添加到剪贴板。您希望从“最佳”格式(最忠实地保留数据的格式)开始,然后逐步降低到最差的格式。所以当你做一个副本时,你可能想要做 PNG,然后是带有 alpha 通道的 RGB,然后是 CF_BITMAP(这将是 pre-multiply alpha,但总比没有好)。