8

Note: This question is about pasting from the clipboard, not copying to the clipboard. There are several posts about copying to the clipboard, but couldn't find one that addresses this question.

How can I paste an image with transparency, for example this one, into a winforms app and retain transparency?

I have tried using System.Windows.Forms.GetImage(), but that produces a bitmap with a black background.

I am copying this image from Google Chrome, which supports several clipboard formats, including DeviceIndependentBitmap and Format17.

bright
  • 4,700
  • 1
  • 34
  • 59

2 Answers2

13

Chrome copies the image to the clipboard in a 24bpp format. Which turns the transparency into black. You can get a 32bpp format out of the clipboard but that requires handling the DIB format. There's no built-in support for that in System.Drawing, you need a little helper function that make the conversion:

    private Image GetImageFromClipboard() {
        if (Clipboard.GetDataObject() == null) return null;
        if (Clipboard.GetDataObject().GetDataPresent(DataFormats.Dib)) {
            var dib = ((System.IO.MemoryStream)Clipboard.GetData(DataFormats.Dib)).ToArray();
            var width = BitConverter.ToInt32(dib, 4);
            var height = BitConverter.ToInt32(dib, 8);
            var bpp = BitConverter.ToInt16(dib, 14);
            if (bpp == 32) {
                var gch = GCHandle.Alloc(dib, GCHandleType.Pinned);
                Bitmap bmp = null;
                try {
                    var ptr = new IntPtr((long)gch.AddrOfPinnedObject() + 40);
                    bmp = new Bitmap(width, height, width * 4, System.Drawing.Imaging.PixelFormat.Format32bppArgb, ptr);
                    return new Bitmap(bmp);
                }
                finally {
                    gch.Free();
                    if (bmp != null) bmp.Dispose();
                }
            }
        }
        return Clipboard.ContainsImage() ? Clipboard.GetImage() : null;
    }

Sample usage:

    protected override void OnPaint(PaintEventArgs e) {
        using (var bmp = GetImageFromClipboard()) {
            if (bmp != null) e.Graphics.DrawImage(bmp, 0, 0);
        }
    }

Which produced this screen-shot with the form's BackgroundImage property set to a stock bitmap:

enter image description here

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Brilliant. Thanks ever so much. – bright Jun 30 '12 at 15:04
  • 2
    Just noticed - the images are rotated 180 degrees! The image above is also rotated from the original. Any idea why? – bright Jun 30 '12 at 15:12
  • 2
    I was able to work around this with image.RotateFlip(SD.RotateFlipType.Rotate180FlipX). Don't know why the above code causes the rotate and flip, though. – bright Jun 30 '12 at 15:22
  • Ah, right, the scan lines in a bitmap are stored upside down. Your workaround is okay. – Hans Passant Jun 30 '12 at 15:27
  • the 32-bit format of a DIB with a 40-byte header is technically RGB without alpha. Seems this gets abused as ARGB a lot, but the problem is there's no way to be sure if the 4th byte of each pixel data is alpha or just junk bytes. – Nyerguds Sep 18 '17 at 11:54
1

(I can't comment answers)

Answer of Hans Passant is good, but not correct.

Screenshot saved with this code.

You need to replace var ptr = new IntPtr((long)gch.AddrOfPinnedObject() + 40); with var ptr = new IntPtr((long)gch.AddrOfPinnedObject() + 52);

Screenshot saved with correct code.

Also you need flip image vertically

  • It looks like the offset of 40 may depend on something that changes in the dib format. I would recommend rephrasing this. Instead of "Answer of Hans Passant is good but not correct", I would say: "Using an offset of 40 (from @Hans Passant's answer) caused a weird border on my image, an offset of 52 fixed it." – Lincoln May 09 '20 at 14:40