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 = 3 * (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

    Find more about me on:
  • linkedin
  • twitter
  • youtube
This entry was posted in General Imaging, Image Processing and tagged . Bookmark the permalink.

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

  1. I have an observation and a correction:

    Observation: First, it is worth pointing out that the structure of the buffer will differ depending on the image type. IE: Grayscale, truecolor, transparent, and palette images have different byte structures which will result in a different byte array when calling _image.GetRow(). For truecolor 24-bit images each pixel will require 3 bytes. Include transparency and you will need 4 bytes per pixel resulting in a 32-bit image. For a standard 8-bit grayscale image each pixel will only require 1 byte. Standard grayscale with transparency would be 2 bytes per pixel. And a palette image could have 1, 2, 4, or even 8 pixels-per-byte depending on the palette size.

    Correction: It looks like your “GetPixel” function is flawed. Specifically your index calculation does not take into account multiple bits per pixel. The proper index calculation should be:
    int index = 3 * (y * _image.Width + x);
    Furthermore when allocating the byte array for the GetPixel function, you should not rely on _image.BytesPerLine which could vary on image type (see observation above). Instead, you should calculate the byte array based on a 24-bits-per-pixel array:
    byte[] imageBytes = new byte[ 3 * image.Height * image.Width ];

Leave a Reply to Levi Carter Cancel reply

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