Do it in the Buffer: Comparison of RasterImage Image Data Access Methods in .NET

There are several ways to access the data in a RasterImage. If you need to get the RGB values of each pixel, then the GetPixel() and GetRow() methods are the simplest methods to use.

Few Pixels

If you need to get just one pixel, then GetPixel() is the easiest. GetPixel() works with image data of any bits per pixel and returns a RasterColor that includes the alpha channel information for 32 and 64 bit images. The sample below uses GetPixel()to fill the buffer with the entire image (not recommended—more on that below).

private static void GetPixel(byte[] imageBytes)
{
   for (var y = 0; y < _image.Height; y++)
      for (var x = 0; x < _image.Width; x++)
      {
         RasterColor pixel = _image.GetPixel(y, x);
         if (pixel.IsPaletteIndex)
            pixel = _image.GetTrueColorValue(pixel);
         int index = y * _image.Width + x;
         imageBytes[index] = pixel.B;
         imageBytes[index + 1] = pixel.G;
         imageBytes[index + 2] = pixel.R;
      }
}

More than a Few Pixels

If you need to process more than few pixels, then GetRow() is the recommended method to get the image data into a buffer. The following example fills the buffer one row at a time.

private static void GetRow(byte[] imageBytes)
{
   _image.Access();
   try 
   {
      for (var y = 0; y < _image.Height; y++)
         _image.GetRow(y, imageBytes, y * _image.BytesPerLine, _image.BytesPerLine);
   }
   finally
   {
      _image.Release();
   }
}

As previously mentioned, it is not recommended to use GetPixel() to get many pixels unless it is absolutely required. When compared to GetRow(), GetPixel() is extremely slow. For example, using a 24-bit image that is 2448 x 2448 pixels, BGR, top-left, GetPixel() takes about 1730 ms to fill the buffer. By contrast, GetRow() takes about 5 ms to fill the same buffer from the same RasterImage.

Get Them All

Also, it is possible to call GetRow() and get more than a row at a time. This results in the fastest way to get the data into the buffer:

private static void GetAllRows(byte[] imageBytes)
{
   _image.Access();
   try
   {
      _image.GetRow(0, imageBytes, 0, imageBytes.Length);
   }
   finally
   {
      _image.Release();
   }
}

Try it at Home

The function to allocate the buffer is:

private static byte[] AllocateImageBytes(RasterImage image) => 
   new byte[ image.BytesPerLine * image.Height ];

Below are the functions I used to measure how long each function took. If you do your own timings, make sure you build in release and run unattached from the debugger.

private static TimeSpan TimeGetPixel()
{
   byte[] imageBytes = AllocateImageBytes(_image);
   // run it once to get things cached, etc...
   GetPixel(imageBytes);
   Stopwatch watch = Stopwatch.StartNew();
   for (var i = 0; i < Iterations; i++)
      GetPixelColor(imageBytes);
   watch.Stop();
   return watch.Elapsed;
}

private static TimeSpan TimeGetRow()
{
   byte[] imageBytes = AllocateImageBytes(_image);
   // run it once to get things cached, etc...
   GetRow(imageBytes);
   Stopwatch watch = Stopwatch.StartNew();
   for (var i = 0; i < Iterations; i++)
      GetRow(imageBytes);
   watch.Stop();
   return watch.Elapsed;
}

private static TimeSpan TimeGetRowAll()
{
   byte[] imageBytes = AllocateImageBytes(_image);
   // run it once to get things cached, etc...
   GetRowAll(imageBytes);
   Stopwatch watch = Stopwatch.StartNew();
   for (var i = 0; i < Iterations; i++)
      GetRowAll(imageBytes);
   watch.Stop();
   return watch.Elapsed;
}

More

There are other ways to get the image into a buffer, but they offer little benefit over GetRow(). However, they are listed below for completeness:

private static void GetRowColAll(byte[] imageBytes)
{
   _image.Access();
   try
   {
      _image.GetRowColumn(0, 0, imageBytes, 0, imageBytes.Length);
   }
   finally
   {
      _image.Release();
   }
}

private static void SaveToBuffer(byte[] imageBytes)
{
   using (var memoryStream = new MemoryStream(imageBytes))
   using (var codecs = new RasterCodecs())
   {
      // Set RAW options 
      codecs.Options.Raw.Save.Pad4 = false;
      codecs.Options.Raw.Save.ReverseBits = false;
      codecs.Save(_image, memoryStream, 0, RasterImageFormat.Raw, 24);
   }
}

About 

Developer Advocate

This entry was posted in General Imaging, Image Processing and tagged . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *