Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Win32 clipboard and alpha channel images

Tags:

c

windows

winapi

2d

My application should be able to copy 32-bit images (RGB + alpha channel) to the clipboard and paste these images from the clipboard. For this I plan to use CF_DIBV5 because the BITMAPV5HEADER structure has a field bV5AlphaMask.

The problem is that there doesn't seem to be a consensus as to how exactly the image data should be stored in the clipboard. While doing some tests I found out that there are several differences betweeen the applications making it next to impossible to come up with a general solution.

Here are my observations:

  1. When I copy an alpha channel image from Word 2010 or XnView to the clipboard, it is stored without premultiplying pixel data.

  2. When I copy an image using Firefox or Chrome, however, the pixel data seems to be premultiplied by the alpha channel.

  3. Firefox sets bV5AlphaMask to 0xff000000 whereas most other applications do not set this at all but keep it 0. This is strange because these applications put DIBs onto the clipboard that actually contain an alpha channel in the highest 8 bits but still they set bV5AlphaMask to 0. So one has to make the assumption that if bit depth is 32 that there is an alpha channel even if bV5AlphaMask is 0.

To cut a long story short my basic question is this: Is there some official information as to how alpha channel data should be stored on the clipboard? I'm especially interested to find out whether or not the data must be premultiplied. As you can see above, Word 2010 and XnView do not premultiply, while Firefox and Chrome do. But it is of essential importance to know whether or not the color channels should be premultiplied.

Thanks a lot for shedding some light onto this!

UPDATE 2 Pasting into Paint.NET works fine now. It was caused by a bug in my code which did not set the color channels to 0 if the alpha channel was 0, i.e. the premultiplication wasn't done correctly in this case which seems to have confused Paint.NET.

Still unsolved is the problem with Internet Explorer 10. When copying a PNG with alpha channel to the clipboard, IE 10 just puts a 24-bit CF_DIBV5 on the clipboard but Paint.NET can paste this bitmap WITH alpha channel so there must be another format that IE 10 exposes to the clipboard. Maybe it exposes a PNG uses CFSTR_FILECONTENTS and CFSTR_FILEDESCRIPTOR.

UPDATE I've now implemented it in the way described by arx below and it works pretty well. However, there are still two things that keep me puzzled:

1) Pasting alpha channel images from my app into Paint.NET doesn't preserve the alpha channel. The image appears opaque in Paint.NET. HOWEVER, pasting from Firefox and Chrome into Paint.NET works perfectly, the alpha channel is preserved! I've dumped the complete DIBV5 and it is identical to my app, but still it works with FF and Chrome but not with my app so there must be something else to it! Firefox and Chrome must be doing something else that my app doesn't do!?

2) The same is true for Internet Explorer 10. Pasting an alpha channel image from IE 10 to my app doesn't work at all... I'm getting a DIB that has a bit depth of 24, i.e. no alpha channel at all. When pasting from IE 10 to Paint.NET, however, the alpha channel is there! So there must be something more to it here as well...

like image 339
Andreas Avatar asked Mar 28 '13 18:03

Andreas


2 Answers

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;
}
like image 182
arx Avatar answered Oct 12 '22 18:10

arx


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.

like image 40
Mike Weir Avatar answered Oct 12 '22 18:10

Mike Weir