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.
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
:
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
?
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
- 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