Click here to Skip to main content
15,875,581 members
Articles / Programming Languages / C#

Managed Bitmaps 2

Rate me:
Please Sign up or sign in to vote.
4.77/5 (17 votes)
19 Mar 2010CPOL5 min read 51.4K   991   36   15
This article presents classes that represent bitmaps in full managed code.

Background

I used to manipulate images directly in memory. With C#, image manipulation, or at least pixel manipulation, is not so simple. Or you switch to unsafe code, probably having problems with untrusted environments, or you use the Bitmap's GetPixel and SetPixels, which are really slow. I then decided to create the ManagedBitmaps, which I already presented in an article. The untrusted problem is still there, as the DLL uses unsafe code, but if the DLL is trusted, everything is OK, as its users don't need to use unsafe code... but, that was not enough. The classes, as they were presented, were not helping me... so I recreated them.

Changes

Earlier, I created ColorBitmap, which had all pixels as Color type, and methods to copy to and from System.Drawing.Bitmap. ArgbBitmap which was another version of ColorBitmap. But the pixels were seen as 32-bit int values and GrayscaleBitmap, which used bytes to represent color indexes/intensity.

This worked but when I decided to create a simple life simulation program, I wanted: To use Red as Strength, Green as Speed, Blue as Resistance... and to have another characteristic in the pixel, which couldn't be the "alpha" value. To make this work, I was forced to create another copy of the bitmap, with my own data-type... and I was about to give up the ManagedBitmaps... so, I thought: Is it possible to use a "bidimensional array", unsafe code only to do the block copy to and from, and delegates to do the conversion from colors to the real data-types? And, after some tests, I discovered, it was, and the speed was still great.

So, the new class is only one: ManagedBitmap, but generic.

You can create a ManagedBitmap<byte> to represent indexed bitmaps, ManagedBitmap<Color> or, as I did, ManagedBitmap<Life>. And then, when creating system bitmaps, copying to and from them, you must simply provide a delegate to do the conversion. Fast, easy... but not enough.

One thing I didn't like in my old example is that I needed to copy data to a managed bitmap and, then, copy the data to the real bitmap. Isn't there a way to work with the bitmap directly, without using unsafe code? To be honest, that was my original idea, but for lack of tests I concluded that the switch to and from unsafe code was the real problem. But it wasn't. So, I created another class... or better, a pack of classes.

LockBitmap32 locks a 32-bit bitmap in memory and allows to access its pixels. This class uses unsafe code but, when you use it, all bound-checking is done, so there is nothing unsafe in using it. Plus two additional versions of this class, one which is read-only and another that is write-only... Plus the same classes for 8-bit and 24-bit bitmaps. In my tests, still slower than the full unsafe code, but about 35x faster than using Bitmap.GetPixel/SetPixel.

So, How do the Classes Work?

To present the classes, I will need to separate the ManagedBitmap and the "Lock" classes. So, I will start with the Lock classes.

Lock Classes

The Lock classes are, in fact, very simple. When creating them, you need to pass a System.Drawing.Bitmap as a parameter. During construction, a call to the LockBits will be made for the entire bitmap or for the Rectangle you specify, using the read/write appropriate and checking if the PixelFormat is correct. The finalizer and the Dispose will call UnlockBits in the BitmapData obtained. The indexers will simply check the boundaries and, if they are ok, get or set the value directly.

Very simple, isn't it?

Want some code? So, this is a piece of PfzDrawingSample. It will use the source-bitmap unchanged, and will always generate a new destination-bitmap with the given red, green and blue changes over the source-bitmap.

C#
using(var sourcePixels = new LockBitmapRgb24Read(fOriginalBitmap.AsSystemBitmap))
{
  using (var destPixels = new LockBitmapRgb24Write(fBitmap.AsSystemBitmap))
  {
    for (int y=0; y<height; y++)
    {
      for(int x=0; x<width; x++)
      {
        Rgb24 color = sourcePixels[x, y];

        byte r = p_Calculate(color.Red, trackBarRedValue);
        byte g = p_Calculate(color.Green, trackBarGreenValue);
        byte b = p_Calculate(color.Blue, trackBarBlueValue);

        color = new Rgb24(r, g, b);
        destPixels[x, y] = color;
      }
    }
  }
}

As the original bitmap does not change, I can use the LockRgb24Read. As the Destination bitmap is only written, but not read, I can use the LockRgb24Write. If I was planning to use grayscale/indexed bitmaps, I could use the LockIndexed8Read and LockIndexed8Write classes.

Compare with GetPixel and SetPixel:

C#
for (int y=0; y<height; y++)
{
    for(int x=0; x<width; x++)
    {
        Color color = fOriginalSystemBitmap.GetPixel(x, y);

        int r = p_Calculate(color.R, trackBarRedValue);
        int g = p_Calculate(color.G, trackBarGreenValue);
        int b = p_Calculate(color.B, trackBarBlueValue);

        fSystemBitmap.SetPixel(x, y, Color.FromArgb(r, g, b));
    }
}

Except for the two using clauses for the lock, the code is the same. But, the Bitmap GetPixel/SetPixel is really slower.

ManagedBitmap

The ManagedBitmap is dead for the original purpose but, still, it is very useful. It is faster than the Locks, but in many cases it requires copying to and from a real bitmap, so this can be a problem. I used a ManagedBitmap<Life> in my example of simple life simulation, as I needed extra information in the pixels. But, thanks to Karol Kolenda, I can say the ManagedBitmap is now really useful. If you are generating the bitmap, you can use one of the specialized ManagedBitmaps (ArgbBitmap or Indexed8Bitmap [in general, for grayscale images]). These two ManagedBitmap classes also have a property called "AsSystemBitmap". The advantage of such property is that it is a System.Drawing.Bitmap built directly from the ManagedBitmap array. So, changes to this bitmap will be reflected in the ManagedBitmap (so you can use all Graphics functions) and also changes to the pixels will automatically be reflected in such system bitmap. Good performance and the possibility to use GDI+ graphics. Is something more needed?

How do I transform a Life instance into a Color?

C#
PixelMatrix.CopyToRgb24
(
  fSystemBitmap,
  (life) => 
  {
    if (life == null)
      return new Rgb24();

    return new Rgb24((byte)life.Strength, (byte)life.Speed, (byte)life.Resistence);
  }
);

The code is small and, now, simple. I create a Rgb24 representation using the Strength as Red, Speed as Green and Resistance as Blue.

Well, that's it.

In the future, I want to create some classes to load images (be they simple .bmp or complex .jpg) directly into ManagedBitmaps, without the need to go to unmanaged code. But, I must warn you, the actual classes may get a lot of new methods and some of them may have breaking-changes.

History

  • 8th February, 2010
    • Initial version
  • 3rd March, 2010
    • Added ArgbBitmap and Indexed8Bitmap, which have a System.Drawing.Bitmap representation without the need to copy
    • Also made the Lock classes more general, so the LockBitmap32 can work with any 32-bit pixel format
  • 11th March, 2010
    • Added 24-bit bitmap support and changed the samples to use them
    • Corrected a bug in some CopyTo/CopyFrom methods, where the X index was ignored

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
GeneralRe: Why unsafe? Pin
Paulo Zemek11-Feb-10 11:36
mvaPaulo Zemek11-Feb-10 11:36 
GeneralRe: Why unsafe? Pin
Paulo Zemek11-Feb-10 14:11
mvaPaulo Zemek11-Feb-10 14:11 
GeneralRe: Why unsafe? Pin
Karol Kolenda11-Feb-10 14:19
Karol Kolenda11-Feb-10 14:19 
GeneralGood job Pin
Marcelo Ricardo de Oliveira11-Feb-10 5:42
mvaMarcelo Ricardo de Oliveira11-Feb-10 5:42 
GeneralRe: Good job Pin
Paulo Zemek11-Feb-10 5:46
mvaPaulo Zemek11-Feb-10 5:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.