In this post, Rick Strahl reviews the long-standing problems with image data retrieval from the Windows clipboard in WPF, and demonstrates robust solutions using intermediary bitmaps and pixel format fixes. He includes code samples and performance notes for developers working with UI image handling.

Retrieving Images from the Clipboard Reliably in WPF Revisited

Author: Rick Strahl
Published: February 21, 2025

Introduction

Working with images from the Windows clipboard in WPF applications has been notoriously unreliable, especially due to the behavior of the ImageSource control. The standard Clipboard.GetImage() often returns incomplete or unusable data, leading to crashes or rendering failures. In this post, Rick Strahl revisits this issue, shares new findings, and offers improved code solutions for more dependable clipboard image handling.


Image Retrieval Challenges in WPF

WPF’s clipboard API seems simple, but using Clipboard.GetImage() often causes issues:

  • Crashes on reading clipboard data.
  • Returns ImageSource objects that fail silently—data appears present but won’t render.

Alternatives like Windows.Forms.Clipboard.GetImage() are more reliable but do not support transparency, which is sometimes necessary (e.g., for PNG images).

Case Study: Markdown Monster

Rick notes image corruption problems occurring in real scenarios, such as when pasting images captured via TechSmith’s SnagIt into Markdown Monster. Instead of rendering correctly, images display broken, offset, or color-distorted previews. Inspection shows the actual bitmap data is intact—the trouble arises during the Bitmap to ImageSource conversion.

Example Visual

Bonked Image
Figure 1: Corrupted image when rendering via ImageSource.


Diagnosing The Issue

The commonly used Stack Overflow code for Bitmap-to-ImageSource conversion hardcodes PixelFormats.Bgr24. This fails intermittently when the clipboard provides images in different bit depths (e.g., 32bpp PNGs now more widely used).

Original Conversion Code

public static BitmapSource BitmapToBitmapSource(Bitmap bmp) {
   var bitmapData = bmp.LockBits(
      new Rectangle(0, 0, bmp.Width, bmp.Height),
      ImageLockMode.ReadOnly, bmp.PixelFormat);
   var bitmapSource = BitmapSource.Create(
      bitmapData.Width, bitmapData.Height,
      bmp.HorizontalResolution, bmp.VerticalResolution,
      PixelFormats.Bgr24, null,
      bitmapData.Scan0, bitmapData.Stride * bitmapData.Height, bitmapData.Stride);
   bmp.UnlockBits(bitmapData);
   return bitmapSource;
}

This fails with modern 32bpp clipboard images.


Fix: Dynamic Pixel Format Conversion

Rick provides a method to map System.Drawing and System.Windows pixel formats. The core fix is to avoid hardcoding and dynamically handle different formats:

public static System.Windows.Media.PixelFormat ConvertPixelFormat(System.Drawing.Imaging.PixelFormat systemDrawingFormat) {
   switch (systemDrawingFormat) {
      case PixelFormat.Format32bppArgb: return PixelFormats.Bgra32;
      case PixelFormat.Format32bppRgb: return PixelFormats.Bgr32;
      case PixelFormat.Format24bppRgb: return PixelFormats.Bgr24;
      case PixelFormat.Format16bppRgb565: return PixelFormats.Bgr565;
      case PixelFormat.Format16bppArgb1555: return PixelFormats.Bgr555;
      case PixelFormat.Format8bppIndexed: return PixelFormats.Gray8;
      case PixelFormat.Format1bppIndexed: return PixelFormats.BlackWhite;
      case PixelFormat.Format16bppGrayScale: return PixelFormats.Gray16;
      default: return PixelFormats.Bgr24;
   }
}

public static BitmapSource BitmapToBitmapSource(Bitmap bmp) {
   var bitmapData = bmp.LockBits(
      new Rectangle(0, 0, bmp.Width, bmp.Height),
      ImageLockMode.ReadOnly, bmp.PixelFormat);
   var pf = ConvertPixelFormat(bmp.PixelFormat);
   var bitmapSource = BitmapSource.Create(
      bitmapData.Width, bitmapData.Height,
      bmp.HorizontalResolution, bmp.VerticalResolution,
      pf, null, bitmapData.Scan0, bitmapData.Stride * bitmapData.Height, bitmapData.Stride);
   bmp.UnlockBits(bitmapData);
   return bitmapSource;
}

Now, regardless of source bitness (24bpp, 32bpp, etc.), the image renders correctly.

Visualization

Un Bonked Image
Figure 2: Correct image rendering after pixel format fix.


Safe vs. Unsafe Code Approaches

The fastest solution uses unsafe memory access (locking bitmap bits). For environments where unsafe code isn’t allowed, Rick provides a slower, safe-memory solution using MemoryStream:

public static BitmapSource BitmapToBitmapSourceSafe(Bitmap bmp) {
   using var ms = new MemoryStream();
   bmp.Save(ms, ImageFormat.Png); ms.Position = 0;
   var bitmap = new BitmapImage();
   bitmap.BeginInit();
   bitmap.CacheOption = BitmapCacheOption.OnLoad;
   bitmap.StreamSource = ms;
   bitmap.EndInit();
   bitmap.Freeze();
   return bitmap;
}
  • Unsafe version: 3-4 times faster, uses in-place memory.
  • Safe (stream) version: More portable but slower and duplicates memory.

Performance differences become critical with lots of images, such as asynchronous image lists. Example: Lots Of Images In An Image List
Figure 3: Performance matters for large async image lists.


Robust Clipboard Image Retrieval Helper

Combining these learnings, Rick offers a resilient ClipboardHelper class:

Main methods

  • GetImage() retrieves a Bitmap via the best available format (PNG if possible, else Bitmap).
  • GetImageSource() wraps GetImage() and returns an ImageSource for WPF binding.

Key retrieval logic example

public static ImageSource GetImageSource() {
   if (!Clipboard.ContainsImage()) return null;
   using (var bmp = GetImage()) {
      if (bmp == null) return null;
      return WindowUtilities.BitmapToBitmapSource(bmp);
   }
}

public static Bitmap GetImage() {
   try {
      var dataObject = Clipboard.GetDataObject();
      var formats = dataObject.GetFormats(true);
      if (formats == null || formats.Length == 0) return null;
      // Priority: PNG (supports transparency)
      if (formats.Contains("PNG")) {
         using MemoryStream ms = (MemoryStream)dataObject.GetData("PNG");
         ms.Position = 0;
         return (Bitmap)new Bitmap(ms);
      }
      if (formats.Contains("System.Drawing.Bitmap")) {
         return (Bitmap)dataObject.GetData("System.Drawing.Bitmap");
      }
      if (formats.Contains(DataFormats.Bitmap)) {
         return (Bitmap)dataObject.GetData(DataFormats.Bitmap);
      }
      // Fallback to WPF/WinForms Clipboard for non-standard formats
      BitmapSource src = null;
      for (int i = 0; i < 5; i++) {
         try {
            src = Clipboard.GetImage(); break;
         } catch { Thread.Sleep(10); }
      }
      if (src == null) {
         try {
            return System.Windows.Forms.Clipboard.GetImage() as Bitmap;
         } catch { return null; }
      }
      return WindowUtilities.BitmapSourceToBitmap(src);
   } catch { return null; }
}

Steps handled

  • Prioritizes PNG data (for transparency).
  • Tries direct Bitmap formats next.
  • Falls back to WPF/WinForms API with multiple attempts due to known clipboard timing bugs.
  • Final fallback ensures something (if not perfect) is always returned.

Working Image From Clipboard
Figure 4: Successful clipboard image with transparency preserved.


Summary

WPF Clipboard image handling is error-prone, but by prioritizing bitmap captures, translating pixel formats appropriately, and providing fallback strategies (including WinForms API use when needed), developers can ensure reliable image retrieval and rendering, even for complex PNG and 32-bit images. These helpers can be packaged into any WPF application for maximum interoperability.


See Also


If you found this post helpful, consider supporting Rick Strahl’s work!

This post appeared first on “Rick Strahl’s Blog”. Read the entire article here