WPF vs. GDI+ Some additional notes.

In one of my previous posts WPF vs. GDI+ I wrote about performance of WPF and how to solve this problem. After some experiments in Tesseris Pro we’ve found more improvements to solution described in previous post. The main idea is that when you are converting GDI bitmap to WPF bitmap it requires memory allocation and deceases performance. And fortunately there is solution that allows to map WPF bitmap to GDI bitmap so when we are drawing on one bitmap other bitmap is changing too, because they are located in the same memory.

First we will need some API calls. You will be able to read all description in MSDN but names of the functions are more than descriptive, if you know Win API of course 😉

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr CreateFileMapping(
                IntPtr hFile, 
                IntPtr lpFileMappingAttributes, 
                uint flProtect, 
                uint dwMaximumSizeHigh,
                uint dwMaximumSizeLow,
                string lpName);

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr MapViewOfFile(
                IntPtr hFileMappingObject,
                uint dwDesiredAccess,
                uint dwFileOffsetHigh,
                uint dwFileOffsetLow,
                uint dwNumberOfBytesToMap);

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UnmapViewOfFile(IntPtr hFileMappingObject);

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr handle);

Than before creating bitmap let’s create memory mapped file as source of bitmaps:

var format = PixelFormats.Bgr32;

var pixelCount = (uint)(width * height * format.BitsPerPixel / 8);
var rowWidth = width * (format.BitsPerPixel / 8);

this.fileMapping = CreateFileMapping(
                       new IntPtr(-1), 
                       IntPtr.Zero, 
                       0x04, 
                       0, 
                       pixelCount, 
                       null);

this.mapView = MapViewOfFile(
                       fileMapping, 
                       0xF001F, 
                       0, 
                       0, 
                       pixelCount);

When we are calling CreateFileMapping with new IntPtr(-1) as first parameter windows doesn’t map some file to memory but will use system page file as source of mapping. And of course in this case we should specify size of file with width * height * format.BitsPerPixel / 8.

Now let’s create two bitmaps mapped to this mapped file:

this.bitmap = new System.Drawing.Bitmap(
              width, 
              height,
              rowWidth,
              System.Drawing.Imaging.PixelFormat.Format32bppPArgb,
              this.mapView);

this.image = (System.Windows.Interop.InteropBitmap)
  System.Windows.Interop.Imaging.CreateBitmapSourceFromMemorySection(
                                         fileMapping, 
                                         width, 
                                         height, 
                                         format, 
                                         rowWidth, 
                                         0);

Now you can in OnRender you can use following code:

protected override void OnRender(DrawingContext dc)
{
   // Ensure that bitmap is initialized and has correct size
   // Recreate bitmap ONLY when size is changed
   InitializeBitmap((int)this.width, (int)this.height);

   //TODO: Put here your drawing code

   // Invalidate and draw bitmap on WPF DrawingContext
   this.image.Invalidate();
   dc.DrawImage(
           this.image, 
           new Rect(0, 0, this.bitmap.Width, this.bitmap.Height));
}

Please note than this.image should be of type System.Windows.Interop.InteropBitmap to call Invalidate method. And don’t forget to call UnmapViewOfFile and CloseHandle.

5 Comments

  1. vb
    Posted November 23, 2016 at 17:52 | Permalink | Reply

    Great!, What did you do when the images are little blury under higher DPI

    Like

    • Posted November 23, 2016 at 20:01 | Permalink | Reply

      Good question, thank you. That retina displays may introduce a problem. Not sure, never have this issues in our product it’s very specific product. Maybe the solution will be the same as in canvas in HTML draw bigger image and than scale it by WPF size restrictions. The project we are working on is very specific enterprise solution looks like all our users have 96 DPI. But we will perform additional tests. And I’ll add test results/fix to my comments. I think it will take about a week.

      Like

    • Posted December 5, 2016 at 21:21 | Permalink | Reply

      We tested the solution with bigger picture it is working no blur on high DPI. Let me know if you need more details.

      Like

      • Vibeeshan Mahadeva
        Posted January 27, 2017 at 17:30 | Permalink

        Sorry for the late replay, Yes multiplying the size with DPI scale does the trick, thanks for the great solution GDI+ WPF.

        Like

  2. Posted January 27, 2017 at 17:32 | Permalink | Reply

    Great research well done, Your solution is much faster than WPF drawing and much simpler than using Direct2D API, touched the sweet-spot.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: