Image Manipulation With Unsafe Code in C#

"Unsafe" could potentially be the worst misnomer in the .NET Framework, because the word implies that somehow by using "unsafe" code you, the developer, are being a bad person! Nothing could be further from the truth. There isn't anything "wrong" or dangerous with using code blocks marked as "unsafe" and compiled accordingly with the /unsafe switch -- provided you understand what you are doing and why.

By marking a type, type member, or statement block with the unsafe keyword, you're permitted to use pointer types and perform C++ style pointer operations on memory within that scope, and to be able to do this within the managed execution framework. Unsafe code can run faster than a corresponding safe implementation.

Here is a nice, short example that comes from the book C# 4.0 in a Nutshell:

unsafe void BlueFilter (int[,] bitmap)
  {
    int length = bitmap.Length;
    fixed (int* b=bitmap)
    {
        int* p=b;
        for (int i=0, i<length; i++)
        *p++ &= 0xFF;
    }
   }

In the above case, the "safe" version would have required a nested loop with array indexing and bounds checking. Unsafe C# methods may also be faster than calling an external C function, since there is no overhead associated with leaving the managed execution environment.

The fixed statement is required to pin a managed object such as the bitmap in the code snippet above. This is necessary when using pointer operations as we don't want the garbage collector moving our objects around in memory while we're attempting to do operations on them.

Just for kicks, lets write some code that gets an image and iterates over each pixel, getting the color and then setting a color back again - a very common operation. We'll do it first with "safe" code, and then a second time with unsafe code in the form of an UnsafeBitmap class (the original version of this comes from an article by Eric Gunnerson of Microsoft).

Here is the sample code:

using System.Text;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;


namespace ImageManipulation
{
    class Program
    {
         private static Stopwatch sw;

        static void Main(string[] args)
        {
            string imagePath = System.Environment.CurrentDirectory + @"\logo1w.png";
            Bitmap img = (Bitmap)Image.FromFile(imagePath);
            var newBmp = new Bitmap(img.Width, img.Height, PixelFormat.Format32bppArgb);
             // convert the google logo to non-indexed format
        using(var gfx = Graphics.FromImage(newBmp) )
        {
            gfx.DrawImage(img, 0, 0);
        }
            sw = new Stopwatch();
             sw.Start();
            // We'll do the entire operation ten times
            for (int a = 0; a < 10; a++)
             {
                 for (int x = 0; x < newBmp.Width; x++)
                 {
                      for (int y = 0; y < newBmp.Height; y++)
                    {
                        Color c = newBmp.GetPixel(x, y);
                        newBmp.SetPixel(x, y, c);
                    }

                }
            }

            sw.Stop();
           Console.WriteLine("Bitmap: " + sw.ElapsedMilliseconds);
            UnsafeBitmap uBmp = new UnsafeBitmap(newBmp);
             sw.Reset();
             sw.Start();
            for (int b = 0; b < 10; b++)
             {
                 uBmp.LockBitmap();
                  for (int x = 0; x < newBmp.Width; x++)
                 {
                      for (int y = 0; y < newBmp.Height; y++)
                    {
                        PixelData pdata = uBmp.GetPixel(x, y);
                        uBmp.SetPixel(x, y, pdata);
                    }

                }
                uBmp.UnlockBitmap();
            }
            sw.Stop();
           Console.WriteLine("Unsafe Bitmap: " + sw.ElapsedMilliseconds);
           Console.ReadLine();
        }
    }
}

And here is the code for the UnsafeBitmap class:

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace ImageManipulation
{
    public unsafe class UnsafeBitmap
    {
        Bitmap bitmap;
      
        int width;
        BitmapData bitmapData = null;
        Byte* pBase = null;

        public UnsafeBitmap(Bitmap bitmap)
        {
            this.bitmap = new Bitmap(bitmap);
        }

        public UnsafeBitmap(int width, int height)
         {
             this.bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
         }

         public void Dispose()
        {
             bitmap.Dispose();
        }

         public Bitmap Bitmap
        {
             get
             {
                  return (bitmap);
             }
        }

        private Point PixelSize
        {
            get
            {
                GraphicsUnit unit = GraphicsUnit.Pixel;
                RectangleF bounds = bitmap.GetBounds(ref unit);
                 return new Point((int)bounds.Width, (int)bounds.Height);
             }
         }

        public void LockBitmap()
        {
            GraphicsUnit unit = GraphicsUnit.Pixel;
            RectangleF boundsF = bitmap.GetBounds(ref unit);
            Rectangle bounds = new Rectangle((int)boundsF.X,
           (int)boundsF.Y,
           (int)boundsF.Width,
           (int)boundsF.Height);
            width = (int)boundsF.Width * sizeof(PixelData);
             if (width % 4 != 0)
            {
                width = 4 * (width / 4 + 1);
            }
            bitmapData =
           bitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            pBase = (Byte*)bitmapData.Scan0.ToPointer();
         }

        public PixelData GetPixel(int x, int y)
        {
            PixelData returnValue = *PixelAt(x, y);
             return returnValue;
        }

        public void SetPixel(int x, int y, PixelData colour)
        {
            PixelData* pixel = PixelAt(x, y);
            *pixel = colour;
        }

        public void UnlockBitmap()
        {
            bitmap.UnlockBits(bitmapData);
            bitmapData = null;
            pBase = null;
        }
         public PixelData* PixelAt(int x, int y)
         {
             return (PixelData*)(pBase + y * width + x * sizeof(PixelData));
         }
    }
     public struct PixelData
    {
         public byte blue;
        public byte green;
        public byte red;
    }
}

Wnen you run the above Console Application, you will see two lines output - the elapsed milliseconds to get and to then write each pixel via managed "safe" code (ten times), and the elapsed milliseconds to get and write each pixel using "unsafe" code with pointer operations (again, the entire image ten times). There is nothing dangerous or "unsafe" in doing this as you're constraining all operations to the precise bounds of the bitmap image in memory. The "unsafe" keyword is misleading as it gives the wrong connotation. Managers and other types who are concerned are likely to misinterpret the concept and issue arbitrary rules against the use of "unsafe" anything, regardless of whether they fully understand it or not.

Depending on the speed of your CPU, etc. the unsafe version should be over eight times faster! The .NET Framework GDI+ versions of GetPixel and SetPixel are annoyingly slow, and if you need to do a lot of image manipulation, you can certainly avoid them and get a huge performance boost. Incidentally, the WPF implementation of the WriteableBitmap class is actually about as slow as GDI+, or perhaps even slower!

You can download the sample Visual Studio 2010 solution here.

By Peter Bromberg   Popularity  (3240 Views)