Sample code: Noc.Demo.Clipboard.zip (163.36 KB)
I came across an interesting problem, the other day. The task at had was to copy an image to the Clipboard and maintain the alpha channel for pasting into applications such as Word, PowerPoint, Gimp, or Paint.NET.
What boggles my mind is: this is actually somewhat... well... hard.
Clipboard handling that the .NET Framework forgot
Don't get me wrong. Getting an image onto the clipboard is somewhat easy. It's a one-liner, really:
Clipboard.SetImage(myImage);
The problem with this approach is that the image loses its alpha channel, converting an image like this:

... to an image like this:

The resulting image is a 24-bit per pixel bitmap, with the transparent background filled by a soft gray. I have it on good report that older versions of windows swap out the transparent color for a virulent blue.
Now, I've seen applications cop out of transparency by using black or white, or even a gut wrenching magenta, for the intent of masking out the default color at run time. But gray? Why not try to be right some of the time instead of being wrong all of the time. Who ever really wants to swap out transparency for soft gray?
Covering up the gray with a little color
We all know someone who does it. Age creeps up and it's off to the salon for some dye.
When transparency is not strictly a requirement, a little bit of code can get us on the way to filling an image with a solid background. For this, I prefer the following extension method:
public static Image CreateOpaqueBitmap(this Image image, Color backgroundColor)
{
var bitmap = new Bitmap(image.Width, image.Height, PixelFormat.Format24bppRgb);
using (var graphics = Graphics.FromImage(bitmap))
{
graphics.Clear(backgroundColor);
graphics.DrawImage(image, 0, 0, image.Width, image.Height);
}
return bitmap;
}
With the extension method in hand, adding an opaque image to the clipboard was never easier:
using (var bitmap = image.CreateOpaqueBitmap(Color.Magenta))
{
Clipboard.Clear();
Clipboard.SetImage(bitmap);
}

But I really need transparency!
Creating opaque images really only massages an ache, but really doesn't solve the problem. What can be done about transparency?
A complete answer requires a little understanding of Windows clipboard formats. It helps to think of clipboard data as a set of key-value pairs. The key identifies what format the data is expected to be, and the value is the actual data. The clipboard can have multiple kinds of data, all representing (supposedly) the same thing, at any given time.
Some applications can take advantage of this fact to provide text data along with an image, so that pastes will provide something if pasting in a text editor, and something else if pasted in an image editor.
What is more often the case is that an application will provide multiple image formats to increase compatibility with other image software. As a matter of fact, Windows provides free type conversions between some of the most common image types (e.g. Bitmap to System.Drawing.Bitmap).
It helps to get a feel for what formats are supported for pasting in an application and their relative priority by inspecting what formats are generated when copying from the application. Here are formats generated when copying images from a few favorite applications:
- GIMP: PNG, DeviceIndependentBitmap, System.Drawing.Bitmap, Bitmap, Format17
- Paint.NET: System.Drawing.Bitmap, Bitmap, PaintDotNet.MaskedSurface
- Fireworks: Fireworks Internal Clipboard Format 3.0, PNG, DeviceIndependentBitmap
- MS Paint: Embed Source, Object Descriptor, MetaFilePict, DeviceIndependentBitmap
- Print Screen (on the keyboard): System.Drawing.Bitmap, Bitmap, DeviceIndependentBitmap, Format17
- PhotoScape: System.Drawing.Bitmap, Bitmap, DeviceIndependentBitmap, Format17
- MS Word: Art::GVML ClipFormat, System.Drawing.Bitmap, Bitmap, PNG, JFIF, GIF, EnhancedMetafile, MetaFilePict, Object Descriptor
- MS PowerPoint: Preferred DropEffect, InShellDragLoop, PowerPoint 12.0 Internal Shapes, Object Descriptor, Art::GVML ClipFormat, PNG, JFIF, GIF, System.Drawing.Bitmap, Bitmap, EnhancedMetafile, MetaFilePict, PowerPoint 12.0 Internal Theme, PowerPoint 12.0 Internal Color Scheme
Adding transparency with PNG.
It would seem that DeviceIndependentBitmap, Bitmap, and System.Drawing.Bitmap are the most common formats in the group; however, they are 24-bit opaque bitmaps. Generating a transparent clipboard data type requires that we properly encode data using a format that supports an alpha channel.
In the applications above, the PNG (Portable Network Graphics) format is generated by GIMP, Fireworks, Word, and PowerPoint. We can hope for transparency success in a broad array of applications merely by placing PNG-formatted data on the clipboard. Lucky for us, it doesn't take much to write:
using (var stream = new MemoryStream())
{
image.Save(stream, ImageFormat.Png);
var data = new DataObject("PNG", stream);
Clipboard.Clear();
Clipboard.SetDataObject(data, true);
}
Adding transparency with 32-bit ARGB bitmap
Not every application supports PNG formats. It is probably a good idea to have a secondary format at hand to deal with transparency. It is possible to get a 32bpp image into the clipboard with a little bit of work.
The format at hand, Format17 (a.k.a. CF_DIBV5)
I almost hate to bring it up, really, mostly because so many applications either mishandle it, or ignore it altogether. Take the print screen utility, for example. It does generate valid 32bpp Format17 content, but zeroes all the alpha bytes, and keeps a zero-ing byte mask for alpha just in case. In other words, it may be using a transparency-enabled format, but the content isn't transparent. GIMP mishandles the BITMAPV5HEADER, by accidentally omitting the bV5SizeImage field that is responsible for identifying how many bits are in the bitmap. (In fairness to GIMP, a clever application could compute the value based on other fields in the header.) Paint.NET appears to disregard formats other than 24bpp bitmaps and its own internal MaskedSurface format. Both PhotoScape and GIMP either disregard Format17 altogether (in favor of Bitmap formats) or mishandle the alpha, giving paste results like this:

So, with "You probably won't find this useful," as a disclaimer, let's see how it's done.
The first method is a utility method that copies a 32bpp image into global memory using interop and a little native marshalling. Basically, the method creates a bitmap header and marshals it out into memory, then adds the image bits, last row first, after the header. Note that all the interop methods and constants are omitted for brevity.
private static IntPtr CreatePackedDIBV5(this Bitmap bitmap)
{
BitmapData bmData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
uint bufferLen = (uint)(Marshal.SizeOf(typeof(BITMAPV5HEADER)) + bmData.Height * bmData.Stride);
IntPtr hMem = Kernel32.NativeMethods.GlobalAlloc(User32.GHND | User32.GMEM_DDESHARE, bufferLen);
IntPtr packedDIBV5 = Kernel32.NativeMethods.GlobalLock(hMem);
BITMAPV5HEADER bmi = (BITMAPV5HEADER)Marshal.PtrToStructure(packedDIBV5, typeof(BITMAPV5HEADER));
bmi.bV5Size = (uint)Marshal.SizeOf(typeof(BITMAPV5HEADER));
bmi.bV5Width = bmData.Width;
bmi.bV5Height = bmData.Height;
bmi.bV5Planes = 1;
bmi.bV5BitCount = 32;
bmi.bV5Compression = User32.BI_BITFIELDS;
bmi.bV5SizeImage = (uint)(bmData.Height * bmData.Stride);
bmi.bV5XPelsPerMeter = 0;
bmi.bV5YPelsPerMeter = 0;
bmi.bV5ClrUsed = 0;
bmi.bV5ClrImportant = 0;
bmi.bV5RedMask = 0x00FF0000;
bmi.bV5GreenMask = 0x0000FF00;
bmi.bV5BlueMask = 0x000000FF;
bmi.bV5AlphaMask = 0xFF000000;
bmi.bV5CSType = 0x73524742; // User32.LCS_WINDOWS_COLOR_SPACE;
bmi.bV5Endpoints.ciexyzBlue.ciexyzX = 0;
bmi.bV5Endpoints.ciexyzBlue.ciexyzY = 0;
bmi.bV5Endpoints.ciexyzBlue.ciexyzZ = 0;
bmi.bV5Endpoints.ciexyzGreen.ciexyzX = 0;
bmi.bV5Endpoints.ciexyzGreen.ciexyzY = 0;
bmi.bV5Endpoints.ciexyzGreen.ciexyzZ = 0;
bmi.bV5Endpoints.ciexyzRed.ciexyzX = 0;
bmi.bV5Endpoints.ciexyzRed.ciexyzY = 0;
bmi.bV5Endpoints.ciexyzRed.ciexyzZ = 0;
bmi.bV5GammaRed = 0;
bmi.bV5GammaGreen = 0;
bmi.bV5GammaBlue = 0;
bmi.bV5ProfileData = 0;
bmi.bV5ProfileSize = 0;
bmi.bV5Reserved = 0;
bmi.bV5Intent = User32.LCS_GM_IMAGES;
Marshal.StructureToPtr(bmi, packedDIBV5, false);
long offsetBits = bmi.bV5Size;
IntPtr bits = (IntPtr)(packedDIBV5.ToInt32() + offsetBits);
for (int y = 0; y < bmData.Height; y++)
{
IntPtr DstDib = (IntPtr)(bits.ToInt32() + (y * bmData.Stride));
IntPtr SrcDib = (IntPtr)(bmData.Scan0.ToInt32() + ((bmData.Height - 1 - y) * bmData.Stride));
for (int x = 0; x < bmData.Width; x++)
{
Marshal.WriteInt32(DstDib, Marshal.ReadInt32(SrcDib));
DstDib = (IntPtr)(DstDib.ToInt32() + 4);
SrcDib = (IntPtr)(SrcDib.ToInt32() + 4);
}
}
bitmap.UnlockBits(bmData);
Kernel32.NativeMethods.GlobalUnlock(hMem);
return hMem;
}
With the utility method at hand, adding an image to the clipboard using CF_DIBV5 (Format17) formatting is as easy as:
public static void Copy32BppBitmapToClipboard(this Image image)
{
using (var bitmap = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppArgb))
{
using (var bitmapGraphics = Graphics.FromImage(bitmap))
{
bitmapGraphics.DrawImage(image, 0, 0, image.Width, image.Height);
}
var packedDIBV5 = CreatePackedDIBV5(bitmap);
User32.NativeMethods.OpenClipboard(IntPtr.Zero);
User32.NativeMethods.EmptyClipboard();
User32.NativeMethods.SetClipboardData(User32.CF_DIBV5, packedDIBV5);
User32.NativeMethods.CloseClipboard();
}
}
Bringing it home
I consider the 32bpp CF_DIBV5 format to be a lot of work, considering the poor support of the format offered by so many applications. I've found that taking a pragmatic approach, aiming for PNG transparency where supported, and accepting the background color of my choice otherwise, made for a manageable codebase with decent application compatibility. I use the following extension method to add images to the clipboard:
public static void CopyMultiFormatBitmapToClipboard(this Image image)
{
using (var opaque = image.CreateOpaqueBitmap(Color.White))
using (var stream = new MemoryStream())
{
image.Save(stream, ImageFormat.Png);
Clipboard.Clear();
var data = new DataObject();
data.SetData(DataFormats.Bitmap, true, opaque);
data.SetData("PNG", true, stream);
Clipboard.SetDataObject(data, true);
}
}
The results are fairly decent. Here are a few of the applications I tried:
- GIMP: transparency supported
- Fireworks: transparency supported
- PhotoScape: white background
- Paint.NET: white background
- MS PowerPoint: transparency supported
- MS Word: white background
- MS Paint: white background
Happy coding!
Noc.Demo.Clipboard.zip (163.36 KB)