Win32 clipboard and alpha channel images

冷暖自知 提交于 2019-11-30 13:07:15

I'm sure there is a right way of storing the alpha in CF_DIBV5, but it really doesn't matter. Applications already handle it inconsistently, so if you want your application to play nicely with others you can't use CF_DIBV5.

I researched copying and pasting transparent bitmaps a while ago. My aim was to successfully paste a transparent bitmap into two versions of Office and GIMP. I looked at several possible formats:

CF_BITMAP

Transparency is always ignored.

CF_DIB

Using 32bpp BI_RGB in the usual 0xAARRGGBB format. GIMP supports this but nothing else does.

CF_DIBV5

GIMP doesn't support this.

"PNG"

Paste supported: GIMP, Word 2000, Excel 2000, Excel 2007 and PowerPoint 2007.
Paste unsupported: Word 2007 and OneNote 2007.

All of these applications successfully export "PNG" if you copy a bitmap.

However, Word and OneNote 2007 will paste a PNG file copied from Explorer. So I came up with the following:

Solution for Copying

Convert your transparent bitmap to PNG format.

Advertise the following clipboard formats:

"PNG" - the raw PNG data.
CF_DIB - for applications (like paint) that don't handle transparency.
CFSTR_FILEDESCRIPTOR - make the PNG look like a file. The file descriptor should have an invented filename with a ".png" extension.
CFSTR_FILECONTENTS - the contents must be exposed as an IStream; just using an HGLOBAL doesn't seem to work. The data is identical to the "PNG" data.

Having done this I could successfully paste transparent bitmaps into GIMP, Office 2000 and Office 2007. You can also paste the PNG directly into an Explorer folder.

Update

I realised that I've only answered half the question. This is great for copying, but no use if you want to paste from an application that only copies CF_DIBV5 (like Firefox).

I'd recommend that you use "PNG" if it's available, otherwise fall back to CF_DIBV5, treating it as premultiplied. This will correctly handle Word 2010 (which exports "PNG"), Firefox and Chrome. XnView only exports non-multiplied CF_DIBV5, so this won't work correctly. I'm not sure you can do any better.

lscf - A Tool for Exploring Clipboard Formats

This is the source of a tool for displaying a list of available clipboard formats. It can also write one to a file. I called it lscf. Create a win32 console application in Visual Studio and paste this source over the main function. It has one very minor bug: it never displays the "Unknown format" error if you mistype a format name.

#include <Windows.h>

#include <stdio.h>
#include <tchar.h>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))

LPCTSTR cfNames[] = {
    _T("CF_TEXT"),
    _T("CF_BITMAP"),
    _T("CF_METAFILEPICT"),
    _T("CF_SYLK"),
    _T("CF_DIF"),
    _T("CF_TIFF"),
    _T("CF_OEMTEXT"),
    _T("CF_DIB"),
    _T("CF_PALETTE"),
    _T("CF_PENDATA"),
    _T("CF_RIFF"),
    _T("CF_WAVE"),
    _T("CF_UNICODETEXT"),
    _T("CF_ENHMETAFILE"),
    _T("CF_HDROP"),
    _T("CF_LOCALE"),
    _T("CF_DIBV5")
};

int LookupFormat(LPCTSTR name)
{
    for (int i = 0; i != ARRAY_SIZE(cfNames); ++i)
    {
        if (_tcscmp(cfNames[i], name) == 0)
            return i + 1;
    }

    return RegisterClipboardFormat(name);
}

void PrintFormatName(int format)
{
    if (!format)
        return;

    if ((format > 0) && (format <= ARRAY_SIZE(cfNames)))
    {
        _tprintf(_T("%s\n"), cfNames[format - 1]);
    }
    else
    {
        TCHAR buffer[100];

        if (GetClipboardFormatName(format, buffer, ARRAY_SIZE(buffer)))
            _tprintf(_T("%s\n"), buffer);
        else
            _tprintf(_T("#%i\n"), format);
    }
}

void WriteFormats()
{
    int count = 0;
    int format = 0;
    do
    {
        format = EnumClipboardFormats(format);
        if (format)
        {
            ++count;
            PrintFormatName(format);
        }
    }
    while (format != 0);

    if (!count)
        _tprintf(_T("Clipboard is empty!\n"));
}

void SaveFormat(int format, LPCTSTR filename)
{
    HGLOBAL hData = (HGLOBAL)GetClipboardData(format);

    LPVOID data = GlobalLock(hData);

    HANDLE hFile = CreateFile(filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
    if (hFile != INVALID_HANDLE_VALUE)
    {
        DWORD bytesWritten;
        WriteFile(hFile, data, GlobalSize(hData), &bytesWritten, 0);
        CloseHandle(hFile);
    }

    GlobalUnlock(hData);
}

int _tmain(int argc, _TCHAR* argv[])
{
    if (!OpenClipboard(0))
    {
        _tprintf(_T("Cannot open clipboard\n"));
        return 1;
    }

    if (argc == 1)
    {
        WriteFormats();
    }
    else if (argc == 3)
    {
        int format = LookupFormat(argv[1]);
        if (format == 0)
        {
            _tprintf(_T("Unknown format\n"));
            return 1;
        }

        SaveFormat(format, argv[2]);
    }
    else
    {
        _tprintf(_T("lscf\n"));
        _tprintf(_T("List available clipboard formats\n\n"));
        _tprintf(_T("lscf CF_NAME filename\n"));
        _tprintf(_T("Write format CF_NAME to file filename\n\n"));
    }

    CloseClipboard();

    return 0;
}

I was stuck on this problem for a while despite the detailed main answer. It would not seem to preserve alpha (even through a clipboard viewer).

It turns out, the solution is as simple as this:

  1. export CF_DIB (no need for V5) with 32-bit pre-multiplied alpha
  2. and export the "PNG" format With that, it seemed to be able to paste in all applications I tested (Paint.NET, GIMP, LibreOffice, and so forth).

Essentially, as long as alpha was pre-multiplied, alpha was preserved in CF_DIB in almost every program I used. In a rare one-off case, "PNG" was needed.

To be clear: CF_DIBV5 was not needed.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!