1

I'm looking to convert a CF_DIBV5 format from the clipboard to a BitmapSource object. Here's my code so far, based on a previous code I used to convert a normal DIB into a BitmapSource. To convert a normal DIB, I figured out that I just had to fetch the header, and prepend that to the original bytes of the bitmap.

Unfortunately, the program throws the following exception now when I tried making the code DIBV5 compatible.

No imaging component suitable to complete this operation was found.

It happens when I use the BitmapFrame's Create method. The full code can be seen below.

var infoHeader =
    ApiHelper.ByteArrayToStructure<BITMAPV5HEADER>(buffer);

var fileHeaderSize = Marshal.SizeOf(typeof(BITMAPV5HEADER));
var infoHeaderSize = infoHeader.bV5Size;
var fileSize = fileHeaderSize + infoHeader.bV5Size + infoHeader.bV5SizeImage;

var fileHeader = new BITMAPV5HEADER();
fileHeader.bV5CSType = BITMAPV5HEADER.LCS_sRGB;
fileHeader.bV5Size = (uint) fileSize;
fileHeader.bV5Reserved = 0;

byte[] fileHeaderBytes =
    ApiHelper.StructureToByteArray(fileHeader);

var bitmapStream = new MemoryStream();
bitmapStream.Write(fileHeaderBytes, 0, fileHeaderSize);
bitmapStream.Write(buffer, 0, buffer.Length);
bitmapStream.Seek(0, SeekOrigin.Begin);

BitmapFrame bitmap = BitmapFrame.Create(bitmapStream);

Any ideas of what I am doing wrong, or what I'm not doing right? I'm fumbling around in blindness, and unfortunately documentation is sparse for a C# programmer like me. I understand a bit of C++ but not enough to convert from C++ to C#.

Mathias Lykkegaard Lorenzen
  • 15,031
  • 23
  • 100
  • 187
  • The exception does not lie. The v5 format was meant to be an improvement over the BMP format, but nobody wanted it. Not even Microsoft. It is not exactly clear why this is a problem, most any program that offers the CF_DIBV5 clipboard format will also expose CF_BITMAP and/or CF_DIB if it want to be usable. – Hans Passant Apr 18 '18 at 18:39
  • Not helpful in terms of explaining the 'what's missing/not working with your above code', but on a long shot, any chance that either of these answers to [this SO question](https://stackoverflow.com/questions/14333336/how-can-i-convert-a-cf-dibv5-from-clipboard-format17-to-a-transparent-bitmap) can be adapted somehow or perhaps fill in the missing piece(s) for your situation? – Gregg L Apr 18 '18 at 19:11
  • Can you try that code: https://pastebin.com/8Q1b7Bmh I have a problem with alpha channel (transparency) which I don't understand but maybe it works enough for you. It should work from Windows 8+ – Simon Mourier Apr 23 '18 at 06:56

1 Answers1

3

Copy this image to the clipboard. You'll also need to add an Image control to your MainWindow. This took a while to figure out so I might have missed explaining to add a needed reference.

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        [DllImport("User32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool IsClipboardFormatAvailable(uint format);

        [DllImport("User32.dll", SetLastError = true)]
        private static extern IntPtr GetClipboardData(uint uFormat);

        [DllImport("User32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool OpenClipboard(IntPtr hWndNewOwner);

        [DllImport("User32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseClipboard();

        [DllImport("Kernel32.dll", SetLastError = true)]
        private static extern IntPtr GlobalLock(IntPtr hMem);

        [DllImport("Kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GlobalUnlock(IntPtr hMem);

        const uint CF_DIBV5 = 17;

        public enum BitmapCompressionMode : uint
        {
            BI_RGB = 0,
            BI_RLE8 = 1,
            BI_RLE4 = 2,
            BI_BITFIELDS = 3,
            BI_JPEG = 4,
            BI_PNG = 5
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct BITMAPV5HEADER
        {
            public uint bV5Size;
            public int bV5Width;
            public int bV5Height;
            public UInt16 bV5Planes;
            public UInt16 bV5BitCount;
            public uint bV5Compression;
            public uint bV5SizeImage;
            public int bV5XPelsPerMeter;
            public int bV5YPelsPerMeter;
            public UInt16 bV5ClrUsed;
            public UInt16 bV5ClrImportant;
            public UInt16 bV5RedMask;
            public UInt16 bV5GreenMask;
            public UInt16 bV5BlueMask;
            public UInt16 bV5AlphaMask;
            public UInt16 bV5CSType;
            public IntPtr bV5Endpoints;
            public UInt16 bV5GammaRed;
            public UInt16 bV5GammaGreen;
            public UInt16 bV5GammaBlue;
            public UInt16 bV5Intent;
            public UInt16 bV5ProfileData;
            public UInt16 bV5ProfileSize;
            public UInt16 bV5Reserved;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct RGBQUAD
        {
            public byte rgbBlue;
            public byte rgbGreen;
            public byte rgbRed;
            public byte rgbReserved;
        }

        public MainWindow()
        {
            InitializeComponent();
            AttemptConversion();
        }

        private void AttemptConversion()
        {
            WindowInteropHelper helper = new WindowInteropHelper(this);

            bool gotIt = OpenClipboard(helper.Handle);
            if (!gotIt) return;

            bool formatAvail = IsClipboardFormatAvailable(CF_DIBV5);
            if (!formatAvail) return;

            IntPtr hBitmap = IntPtr.Zero;
            hBitmap = GetClipboardData(CF_DIBV5);
            IntPtr ptr = GlobalLock(hBitmap);

            BitmapSource bmpSrc = CF_DIBV5ToBitmapSource(hBitmap);

            img.Width = (int)bmpSrc.Width;
            img.Height = (int)bmpSrc.Height;
            img.Source = bmpSrc;

            if (ptr != IntPtr.Zero) GlobalUnlock(hBitmap);
            if (gotIt) CloseClipboard();
        }

        private BitmapSource CF_DIBV5ToBitmapSource(IntPtr hBitmap)
        {
            IntPtr scan0 = IntPtr.Zero;
            var bmi = (BITMAPV5HEADER)Marshal.PtrToStructure(hBitmap, typeof(BITMAPV5HEADER));

            int stride = (int)(bmi.bV5SizeImage / bmi.bV5Height);
            long offset = bmi.bV5Size + bmi.bV5ClrUsed * Marshal.SizeOf<RGBQUAD>();
            if (bmi.bV5Compression == (uint)BitmapCompressionMode.BI_BITFIELDS)
            {
                offset += 12; //bit masks follow the header
            }
            scan0 = new IntPtr(hBitmap.ToInt64() + offset);

            BitmapSource bmpSource = BitmapSource.Create(
                bmi.bV5Width, bmi.bV5Height,
                bmi.bV5XPelsPerMeter, bmi.bV5YPelsPerMeter,
                PixelFormats.Bgra32, null,
                scan0, (int)bmi.bV5SizeImage, stride);

            return bmpSource;
        }
    }
}
Mathias Lykkegaard Lorenzen
  • 15,031
  • 23
  • 100
  • 187
Clay Ver Valen
  • 1,033
  • 6
  • 10
  • Thanks a lot! I will check if this works tomorrow and award bounty if it does. Great work! – Mathias Lykkegaard Lorenzen Apr 21 '18 at 16:55
  • On this line `scan0 = new IntPtr(hBitmap.ToInt32() + offset);` it crashes with an arithmetic overflow error. Any idea why this is happening? – Mathias Lykkegaard Lorenzen Apr 22 '18 at 16:10
  • I got rid of the error by changing to `ToInt64` instead, but now the image is turned upside down. Any ideas how to solve this? – Mathias Lykkegaard Lorenzen Apr 22 '18 at 16:13
  • Is the bV5Height for your image positive or negative? This would seem to be a top down vs. bottom up DIB issue https://stackoverflow.com/questions/662853/how-do-i-determine-if-a-dib-is-bottom-up-or-top-down https://msdn.microsoft.com/en-us/library/windows/desktop/dd407212(v=vs.85).aspx – Clay Ver Valen Apr 23 '18 at 02:05
  • I'll give it a look soon. Is there any chance you could help revising the example so that it works for both? – Mathias Lykkegaard Lorenzen Apr 23 '18 at 05:37
  • The only sample image I have is from that website I gave the link to. I’d need a sample to work with. What’s not clear from MSDN if BitmapSource.Create will work with both kinds, so there might need to either be an intermediate step of creating a BMP in a known former or if the value is negative (which I suspect yours is) reordering the bytes into the other format). – Clay Ver Valen Apr 23 '18 at 07:32
  • Thanks a lot! I awarded the bounty. If I were to put a DIBV5 into the clipboard again from a BitmapSource, how would I do that? – Mathias Lykkegaard Lorenzen Apr 23 '18 at 08:21
  • The `bV5Height` is not negative. How can I then detect the orientation? – Mathias Lykkegaard Lorenzen Apr 24 '18 at 15:00
  • Also, your example only works for 32 bit colors. Not 24 bit. – Mathias Lykkegaard Lorenzen Apr 24 '18 at 17:02
  • @MathiasLykkegaardLorenzen - The only way I know of to detect the orientations with the bV5Height. As for the 32bit vs. 24bit, I assumed that you were working with 32bit to keep the transparency. If you're not using transparency, is there a different clipboard format available for you to use? – Clay Ver Valen Apr 26 '18 at 18:24