I need help setting a transparent image to the clipboard. I keep getting "handle is invalid". Basically, I need a "second set of eyes" to look over the following code. (The complete working project at ftp://missico.net/ImageVisualizer.zip.)
This is an image Debug Visualizer class library, but I made the included project to run as an executable for testing. (Note that window is a toolbox window and show in taskbar is set to false.) I was tired of having to perform a screen capture on the toolbox window, open the screen capture with an image editor, and then deleting the background added because it was a screen capture. So I thought I would quickly put the transparent image onto the clipboard. Well, the problem is...no transparency support for Clipboard.SetImage. Google to the rescue...not quite.
This is what I have so far. I pulled from a number of sources. See the code for the main reference. My problem is the "invalid handle" when using CF_DIBV5. Do I need to use BITMAPV5HEADER and CreateDIBitmap?
Any help from you GDI/GDI+ Wizards would be greatly appreciated.
public static void SetClipboardData(Bitmap bitmap, IntPtr hDC) {
const uint SRCCOPY = 0x00CC0020;
const int CF_DIBV5 = 17;
const int CF_BITMAP = 2;
//'reference
//'http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/816a35f6-9530-442b-9647-e856602cc0e2
IntPtr memDC = CreateCompatibleDC(hDC);
IntPtr memBM = CreateCompatibleBitmap(hDC, bitmap.Width, bitmap.Height);
SelectObject(memDC, memBM);
using (Graphics g = Graphics.FromImage(bitmap)) {
IntPtr hBitmapDC = g.GetHdc();
IntPtr hBitmap = bitmap.GetHbitmap();
SelectObject(hBitmapDC, hBitmap);
BitBlt(memDC, 0, 0, bitmap.Width, bitmap.Height, hBitmapDC, 0, 0, SRCCOPY);
if (!OpenClipboard(IntPtr.Zero)) {
throw new System.Runtime.InteropServices.ExternalException("Could not open Clipboard", new Win32Exception());
}
if (!EmptyClipboard()) {
throw new System.Runtime.InteropServices.ExternalException("Unable to empty Clipboard", new Win32Exception());
}
//IntPtr hClipboard = SetClipboardData(CF_BITMAP, memBM); //works but image is not transparent
//all my attempts result in SetClipboardData returning hClipboard = IntPtr.Zero
IntPtr hClipboard = SetClipboardData(CF_DIBV5, memBM);
//because
if (hClipboard == IntPtr.Zero) {
// InnerException: System.ComponentModel.Win32Exception
// Message="The handle is invalid"
// ErrorCode=-2147467259
// NativeErrorCode=6
// InnerException:
throw new System.Runtime.InteropServices.ExternalException("Could not put data on Clipboard", new Win32Exception());
}
if (!CloseClipboard()) {
throw new System.Runtime.InteropServices.ExternalException("Could not close Clipboard", new Win32Exception());
}
g.ReleaseHdc(hBitmapDC);
}
}
private void __copyMenuItem_Click(object sender, EventArgs e) {
using (Graphics g = __pictureBox.CreateGraphics()) {
IntPtr hDC = g.GetHdc();
MemoryStream ms = new MemoryStream();
__pictureBox.Image.Save(ms, ImageFormat.Png);
ms.Seek(0, SeekOrigin.Begin);
Image imag = Image.FromStream(ms);
// Derive BitMap object using Image instance, so that you can avoid the issue
//"a graphics object cannot be created from an image that has an indexed pixel format"
Bitmap img = new Bitmap(new Bitmap(imag));
SetClipboardData(img, hDC);
g.ReleaseHdc();
}
}
There are a few things you can do to tighten up the codebase a bit, and I've done some homework on CF_DIBV5 that you may find helpful.
First off, in
__copyMenuItem_Click()
, we have four complete copies of your image, which is much more than necessary.__pictureBox.Image
Image imag = Image.FromStream(ms);
new Bitmap(imag)
Bitmap img = new Bitmap(new Bitmap(imag));
(the outer bitmap)Furthermore, your
MemoryStream
,imag
,new Bitmap(imag)
, andimg
do not get disposed, which could result in problems.Without changing the intent of the code (and probably without solving the handle issue), you could rewrite the method like this:
The next thing that looked like it would require attention was the line:
I am fairly certain that you must marshal out the BITMAPV5HEADER structure to pass bits to the clipboard when using CF_DIBV5. I've been wrong before, but I would verify that memBM actually contains the header. A good indicator is whether the first DWORD (UInt32) has the value 124 (the size of the header in bytes).
My final remarks are more recommendation than a second pair of eyes. I found that photo applications like GIMP, Paint.NET, Fireworks, and PhotoScape appear to have poor or non-existant support for CF_DIBV5 (Format17) pasting. you might consider copying to the clipboard the PNG format, with an opaque bitmap as backup just in case the target application does not support PNG. I use an extension method to facilitate this:
With the extension method in hand, your
__copyMenuItem_Click()
method could be reduced to the following, and theSetClipboardData()
method could be removed altogether:Now, as we already discussed on another thread, PNG support may not cut it for you. I've tested this approach on a few applications; however, it will be up to you to determine whether this is sufficient transparency support for your requirements.
Discussion of everything I looked into would be too lengthy for Stack Overflow. I have additional sample code and discussion available at my blog: http://www.notesoncode.com/articles/2010/08/16/HandlingTransparentImagesOnTheClipboardIsForSomeReasonHard.aspx
Good luck!
I see three problems:
The invalid handle error might come from leaving
memBM
selected intomemDC
. You should always select the bitmap out of a DC before passing it anywhere else.BitBlt
is a GDI call (not GDI+). GDI doesn't preserve the alpha channel. On newer versions of Windows, you can useAlphaBlend
to composite a bitmap with alpha onto a background, but the composite won't have an alpha channel.You've created a compatible bitmap, which means the bitmap has the same color format as the DC you passed in (which I'm assuming is the same as the screen). So your bitmap could end up as 16-bit, 24-bit, 32-bit, or even 8-bit, depending on how the screen is set. If
BitBlt
had preserved the alpha channel of the original, you'd likely lose it when converting to the screen format.Bitmap.GetHbitmap()
will actually composite the bitmap to an opaque background, losing the alpha channel. This question addressed how to get an HBITMAP with the alpha channel intact.