How to convert Bitmap to Image

You provide a function ToBitmap in Stemmer.Cvb.Extensions.ImageExtensions, but how can I convert Bitmap to Image?

Hi @roychou

Stemmer.Cvb.Extensions.DrawingExtensions adds an extension method ToImage to the class System.Drawing.Bitmap. Is this what you are looking for?

Yes, thank you very much!

    public static Bitmap saveImage(Bitmap input)
    {
        var img = Stemmer.Cvb.Extensions.DrawingExtensions.ToImage(input);
        return Stemmer.Cvb.Extensions.ImageExtensions.ToBitmap(img);
    }

    static void Main(string[] args)
    {
        Bitmap input = new Bitmap("D:\\1.bmp");
        var output = saveImage(input);
        output.Save("D:\\2.bmp");
    }

what’s the problem with the code?

Nothing. I guess that D:\1.bmp is a monochrome image and I fear you have found a bug in the import code for monochrome bitmaps - sorry for that :frowning_face:

To fix this, please do the following:

  1. Make sure the latest build of the CVB.Net Wrappers is installed on your system (the file Stemmer.Cvb.dll in your %CVB%\Lib\Net folder should have version 1.30.0.x). If the file is older than that, please download the latest version from here: Introducing: Experimental CVB.Net, CVB++ and CVBpy API. (if you DLL version is newer than 1.30.0.x, then you don’t need to worry - you already have the patch installed…).
  2. Download this file here, unzip it and copy it to a folder on your machine: Stemmer.Cvb.Extensions.dll.zip (17.7 KB)
  3. Open a Visual Studio command prompt (any version >= 2010 will do) with admin privileges and go to the folder where you copied the downloaded DLL.
    image
  4. Execute the command
    gacutil -i Stemmer.Cvb.Extensions.dll
    The output should read Assembly successfully added to the cache

Now your code should finally work. Hint: In Visual Studio you won’t see much of a difference between the old DLL and the new DLL because Visual Studio only reports AssemblyVersions, and as the assembly interface did not change at all I have only increased the AssemblyFileVersion to 1.0.1.0.

One last thing: The line Stemmer.Cvb.Extensions.ImageExtensions.ToBitmap(img); will always yield a 32 bit Bitmap - when working with monochrome images this is probably not what you’d expect, so you might want to use one of the overloads that permits the specification of a target pixel format.

Can you give me an example for monochrome images?
I guess I should use ImageExtensions.ToBitmap Method (Image, Int32, Int32, PixelFormat, Rect, PlaneConfiguration, Double, Int32, HighBitScaleMode), but I do not have an example.

Hi @roychou,

sorry, that last sentence from my previous post was ill-conceived. The extension method ToBitmap relies on a System.Drawing.Graphics object being created for the target bitmap. However, System.Drawing.Graphics cannot be instantiated for an indexed bitmap (and that is what PixelFormat.Format8bppIndexed, the only available option that would model an 8 bit per pixel bitmap, is). In short: You cannot convert 24 an 8 bpp Bitmap using only the methods and classes exposed by System.Drawing and ToBitmap(). Or, to put it differently, ToBitmap() is currently not capable of producing an 8 bit Bitmap.

Therefore, if the objective is to save the monochrome bitmap into an 8 bit per pixel file I recommend calling Save on the Stemmer.Cvb.Image object directly rather than converting it back to a System.Drawing.Bitmap.

Do you have a method to convert Image to Array and Array to Image?If you do, please tell me!

There is no prefabricated method to do that, but for a code sample you might want to look at this post: Writing Pixel Values to an Array (the code is in the dark-themed screenshot of @Chris’s post).

Just as a side note: if you want array-like access on an Image you also can do something like this (pointer access is faster, though):

var size = new Size2D(640, 480);
var rampImage = new Image(size);

var access = rampImage.Planes[0].GetLinearAccess<byte>();
for (int y = 0; y < size.Height; y++)
{
  for (int x = 0; x < size.Width; x++)
  {
    access[x, y] = (byte)((y + x) % 255);
  }
}

But this would be much faster as much less marshaling needs to be done:

Example

Note: C# arrays are stored row-major and thus you need to access the pixels via [y, x].

var size = new Size2D(640, 480);
	
var buffer = new byte[size.Height, size.Width];
for (int y = 0; y < size.Height; y++)
{
  for (int x = 0; x < size.Width; x++)
  {
    buffer[y, x] = (byte)((y + x) % 255);
  }
}

// make a copy from the array
var image = ImageArrayExtensions.CreateFrom(buffer);

// and from image to array and back
var image2 = ImageArrayExtensions.CreateFrom(image.ToArray());

Helper

static class ImageArrayExtensions
{
  public static byte[,] ToArray(this Image image)
  {
    if (image == null)
      throw new ArgumentNullException(nameof(image));
    if (image.Planes.Count > 1 || image.Planes[0].DataType != DataTypes.Int8BppUnsigned)
      throw new ArgumentException("Only Mono8 images are supported");
		
    var array = new byte[image.Height, image.Width];
		
    image.CopyTo(array);
		
    return array;
  }
	
  public static unsafe void CopyTo(this Image image, byte[,] array)
  {
    if (image == null)
      throw new ArgumentNullException(nameof(image));
    if (image.Planes.Count > 1 || image.Planes[0].DataType != DataTypes.Int8BppUnsigned)
      throw new ArgumentException("Only Mono8 images are supported");
    if (array == null)
      throw new ArgumentNullException(nameof(array));
    if (image.Width != array.GetLength(1) || image.Height != array.GetLength(0))
      throw new ArgumentException("Image and array have different dimensions");
			
    fixed (void* pBase = &array[0, 0])
    {
      using (var target = WrappedImage.FromGreyPixels((IntPtr)pBase, 0, image.Width, image.Height, PixelDataType.UInt, 8, 1, image.Width))
      {
        image.CopyTo(target);
      }
    }
  }
	
  public static Image CreateFrom(byte[,] array)
  {
    if (array == null)
      throw new ArgumentNullException(nameof(array));

    var image = new Image(array.GetLength(1), array.GetLength(0));
		
    image.CopyFrom(array);
		
    return image;
  }

  public static unsafe void CopyFrom(this Image image, byte[,] array)
  {
    if (image == null)
      throw new ArgumentNullException(nameof(image));
    if (image.Planes.Count > 1 || image.Planes[0].DataType != DataTypes.Int8BppUnsigned)
      throw new ArgumentException("Only Mono8 images are supported");
    if (array == null)
      throw new ArgumentNullException(nameof(array));
    if (image.Width != array.GetLength(1) || image.Height != array.GetLength(0))
      throw new ArgumentException("Image and array have different dimensions");

    fixed (void* pBase = &array[0, 0])
    {
      using (var source = WrappedImage.FromGreyPixels((IntPtr)pBase, 0, image.Width, image.Height, PixelDataType.UInt, 8, 1, image.Width))
      {
        source.CopyTo(image);
      }
    }
  }
}

Hello, now I find that the method Stemmer.Cvb.Extensions.DrawingExtensions.ToImage takes a very long time, 160 milliseconds. I want to know whether the time consuming is normal. If the time is normal, do you have a faster method to perform the conversion from Bitmap to Image?

Converting a System.Drawing.Bitmap to a Stemmer.Cvb.Image requires a deep copy of the image data. How much time for this deep copy is to be considered “normal” depends on the number of pixels and the memory layout (which are unknown for me at this point for your image). To give an example: A Bitmap with 24 bits per pixel and 1280x900 pixels takes about 19 ms on my machine.

There is generally speaking no faster means than the ToImage() extension method in the sense that this method analyzes the source and target layout and then uses the fastest approach possible based on that information. So if ToImage() seems unreasonably slow on your machine we’d need to figure out why (what size is the image, what is the pixel format of the image, what kind of PC are we talking about) and eliminate the reason if possible.

To paint the full picture: There is also a ToCvbWrappedImage() extension method (likely to be renamed to ToWrappedImage() in the next release…) which does the same job as ToImage() but without the copy step. In consequence this method is always much faster, but: When using this method, you’ll have to make sure that the System.Drawing.Bitmap's pixel buffer is locked for as long as the resulting wrapped images lives, otherwise your application will run into an access violation! The pixel buffer of System.Drawing.Bitmap objects is potentially subject to relocation to a different memory block by the CLR at any time and if other components (like the WrappedImage) depend on the buffer the buffer must be locked in place by means of a LockBits() call (see here).

My image size is 2464*2056, i.e. 5 million pixels, the pixel format is PixelFormat.Format8bppIndexed, and my PC is i5-4210U.

I am not 100% sure you’re measuring the same as me? I have produced a bmp file with 2464x2056 pixels using 8 bits per pixel and the ToImage() call takes merely 14 ms. My PC is a Core i7 8700K, but I do not think the difference in hardware alone would explain a factor > 10 between your timing and mine.

This is the code I used:

    static void Main(string[] args)
    {
      var bmpFile = new Bitmap(@"D:\5mp.bmp");
      var timer = new StopWatch(StopWatchMode.SingleCPU);
      var cvbImg = bmpFile.ToImage();
      var duration = timer.TimeSpan;
      Console.WriteLine(duration);
    }

After I use your code, the consuming time is 30ms. But I use System.Diagnostics.Stopwatch, the time is 100ms. Can you tell me the difference between your Stemmer.Cvb.Utilities.StopWatch and System.Diagnostics.Stopwatch?
This is the code I used:

static void Main(string[] args)
{
   var bmpFile = new Bitmap(@"D:\1.bmp");
   Stopwatch sw = new Stopwatch();
   sw.Start();
   var cvbImg = bmpFile.ToImage();
   sw.Stop();
   TimeSpan ts2 = sw.Elapsed;
   Console.WriteLine("time{0}ms.", ts2.TotalMilliseconds);
}
1 Like

30 ms sounds a lot more plausible given the image dimensions and the CPU you are using.

I have never used System.Diagnostics.Stopwatch in the past, so I cannot comment on the accuracy of it. Stemmer.Cvb.Utilities.StopWatch internally uses the Windows SDK function QueryPerformanceCounter when in SingleCPU mode and usually achieves an accuracy level of less than one millisecond. In MultiCPU mode the accuracy is limited to 1 ms. From reading the documentation of System.Diagnostics.Stopwatch I would assume that it can reach similar levels of accuracy.

I guess the easiest way to find out which timer is more trustworthy would be to run the same operation multiple times and calculate the average.