Click here to Skip to main content
15,867,756 members
Articles / Programming Languages / C#
Article

Managed Bitmaps

Rate me:
Please Sign up or sign in to vote.
4.78/5 (13 votes)
24 Jan 2010CPOL5 min read 31.6K   644   29   2
This article presents classes that represent bitmaps in full managed code.

Introduction

I used to manipulate bitmaps in memory directly. When I first needed to do it in C#, I was forced to use unsafe code. Not that bad, considering that I worked for years with C++, but the fact that I needed to switch to unsafe code was the problem, specially because the code may not be trusted. I don't have a real solution for the trust problem, except for the fact that only a very small DLL must be able to use unsafe code, and return everything as managed objects.

What is a Bitmap and How Managed Bitmaps Work?

A bitmap is basically a one-dimensional array that uses some calculation to be seen as a two-dimensional array. The values in such array could be color indexes (when using color palettes) or the RGB values directly, using some type of encoding. The managed bitmaps are simple classes that have a PixelArray, which is an array of bytes, for GrayscaleBitmap, of int (System.Int32) for ArgbBitmap and of Color for ColorBitmap. They also have a Width to know to calculate a X/Y coordinate. The height value is then calculated as a division of the PixelArray length by its Width. They, of course, have some additional methods. They can be converted to and from each other and System.Drawing.Bitmaps, and also can copy only "blocks" from one to another.

The Problems

This approach has three main problems:

  • I can't simply map a System.Drawing.Bitmap data as a C# array or draw managed bitmaps directly to the screen, so copying is needed. This is still faster than using GetPixel and SetPixel when many pixels must be changed, but a real problem if you only need to change some pixels.
  • The TO and FROM system bitmaps process will still be unsafe. Someone using the classes will never need to use unsafe code, but the DLL containing the classes will be marked as unsafe, having all the "untrusted problems" if not registered properly.
  • None of the GDI methods can be used on them. Some may easily be recreated, but the hardware acceleration will never be used.

So, What are the Advantages?

When creating painting programs, for example, a lot of algorithms don't use hardware acceleration, counting only on pixel information and custom made algorithms. For such algorithms, using a managed bitmap instead of a lot of pointers simplifies everything. In my case, I am working on creating some web-cam image processing by hand and I really don't want such processing to be unsafe.

Decisions Regarding the Classes

The classes are there to guarantee that its users are capable of reading or editing bitmaps at a fast speed without using unsafe code. So, to make the classes fast, the right datatypes must be used. This was one of the big challenges. Grayscale bitmaps are really easy. Its color indexes, or brightness, are simply bytes, so array of bytes are ok. But the Color and Int32 are not the same type in .NET, as it happens to be in 32-bit C++. Which one is better? Using an array of int to represent the pixels, or an array of colors? Simple. In my opinion, there is no better one. There are personal preferences and different advantages and disadvantages in each one and, so, one class that works with array of int as a 32-bit Argb and one class that works with an array of Colors was created. Three classes, but two of them are, in fact, different views of the same result. Also, one important decision is the indexer. The X and Y indexer does not do bound-checking. If you pass invalid X and Y coordinates, you can change the wrong pixel. Why I don't do bound-checking if this is a managed code? Because doing bound-checking has dramatically downgraded performance and, the real error that is accessing invalid memory areas is already checked by the .NET array. So, you are capable of affecting the wrong line giving an invalid X value, but you will never corrupt the entire application memory.

Oh, There are Three Classes. Are they Compatible?

Yes, via interfaces. Each class is sealed, so any virtual call is avoided and max speed is obtained, but they implement a common interface, named IBitmap, so one can be used in place of another. Not that it is really useful, as this will probably kill the performance and the purpose of the classes, but there is such option.

The Public Members

The most important public members are defined in the IBitmap interface and they allow the access of the PixelArray, the Width and Height properties, access to the pixel as a x/y coordinate pair and have many functions to copy from one bitmap to another and to create bitmap in the other formats (from one managed bitmap to another or even to a system bitmap). There is, of course, a constructor to create any of the managed bitmaps empty, with the specified size. That's all that's needed and any method that receives or returns a System.Drawing.Bitmap needs to use unsafe code.

The Code

Explaining all the classes is unnecessary, as I think explaining all methods, but I will explain the key methods, those that use unsafe code to the users of such methods don't need to use them:

The CopyBlockFrom and CopyBlockTo accepting a System.Drawing.Bitmap from the ArgbBitmap.

C#
#region CopyBlockFrom
	/// <summary>
	/// Copies a block from the given System.Drawing.Bitmap.
	/// </summary>
	public void CopyBlockFrom(Bitmap sourceBitmap, Point sourceLocation, 
		Point destinationLocation, Size blockSize)
	{
		if (sourceBitmap == null)
			throw new ArgumentNullException("sourceBitmap");

		if (sourceBitmap.PixelFormat != PixelFormat.Format32bppArgb)
			throw new ArgumentException
			("sourceBitmap.PixelFormat must be Format32bppArgb.");
			
		if (ManagedBitmap.i_ValidateCopyToParameters
			(sourceBitmap.Size, new Size(Width, Height), 
			sourceLocation, destinationLocation, blockSize))
			return;

		BitmapData data = null;
		AbortSafe.Run
		(
			() => data = sourceBitmap.LockBits
				(new Rectangle(sourceLocation, blockSize), 
				ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb),
			() =>
			{
				unsafe
				{
					byte *scanLineBytes = 
						(byte *)data.Scan0.ToPointer();
					int thisScanline = destinationLocation.Y * 
						Width + destinationLocation.X;
					for (int y = 0; y < blockSize.Height; y++)
					{
						int *scanLine = 
							(int *)scanLineBytes;
						for (int x = 0; 
						x < blockSize.Width; x++)
						PixelArray[thisScanline + x] = 
							scanLine[x];
						
						scanLineBytes += data.Stride;
						thisScanline += Width;
					}
				}
			},
			() =>
			{
				if (data != null)
					sourceBitmap.UnlockBits(data);
			}
		);
	}
#endregion
#region CopyBlockTo
	/// <summary>
	/// Draws this bitmap over a System.Drawing.Bitmap at the given coordinates.
	/// Note that alpha values will be copied to the destination bitmap, instead of
	/// merging the colors.
	/// </summary>
	public void CopyBlockTo(Bitmap destinationBitmap, Point sourceLocation, 
		Point destinationLocation, Size blockSize)
	{
		if (destinationBitmap == null)
			throw new ArgumentNullException("destinationBitmap");

		if (destinationBitmap.PixelFormat != PixelFormat.Format32bppArgb)
			throw new ArgumentException
			("destinationBitmap.PixelFormat must be Format32bppArgb.");
			
		if (ManagedBitmap.i_ValidateCopyToParameters
			(new Size(Width, Height), destinationBitmap.Size, 
			sourceLocation, destinationLocation, blockSize))
			return;
		
		BitmapData data = null;
		AbortSafe.Run
		(
			() => data = destinationBitmap.LockBits
				(new Rectangle(destinationLocation, blockSize), 
				ImageLockMode.ReadOnly, 
				PixelFormat.Format32bppArgb),
			() =>
			{
				unsafe
				{
					byte *scanLineBytes = 
						(byte *)data.Scan0.ToPointer();
					for (int y = 0; y < blockSize.Height; y++)
					{
						int *scanLine = 
							(int *)scanLineBytes;
						int sourceYIndex = 
							((sourceLocation.Y + y) * 
							Width) + 
							sourceLocation.X;
						
						for (int x = 0; 
						x < blockSize.Width; x++)
						scanLine[x] = 
						PixelArray[sourceYIndex + x];
						
						scanLineBytes += data.Stride;
					}
				}
			},
			() =>
			{
				if (data != null)
					destinationBitmap.UnlockBits(data);
			}
		);
	}
#endregion

The methods are unsafe but are, in fact, very small. The most bizarre aspects of the method are the calls to the AbortSafe.Run method. This method is an evolution of the pattern I presented in my article "using keyword can cause bugs". But, in fact, there is no using clause in this method, only the unabortable pattern when locking and unlocking bits.

Using the Code

There is no purpose in creating managed bitmaps if we don't use them. So, I added a sample that allows to manipulate increase or decrease RGB values of entire images. The sample can use GetPixel/SetPixel from System.Drawing.Bitmap, unsafe code and these classes. The unsafe code is certainly faster, but look at the difference from the code that uses the managed bitmaps:

C#
Color[] sourcePixels = fOriginalBitmap.PixelArray;
Color[] destPixels = fManagedBitmap.PixelArray;
int count = sourcePixels.Length;
for (int i=0; i<count; i++)
{
	Color color = sourcePixels[i];
	
	int r = p_Calculate(color.R, trackBarRedValue);
	int g = p_Calculate(color.G, trackBarGreenValue);
	int b = p_Calculate(color.B, trackBarBlueValue);
	
	destPixels[i] = Color.FromArgb(r, g, b);
}

To the code that uses unsafe code:

C#
BitmapData sourceData = null;
BitmapData destData = null;
AbortSafe.Run
(
	() =>
	{
		sourceData = fOriginalSystemBitmap.LockBits
			(new Rectangle(new Point(), fOriginalSystemBitmap.Size), 
			ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
		destData = fSystemBitmap.LockBits(new Rectangle(new Point(), 
			fSystemBitmap.Size), ImageLockMode.WriteOnly, 
			PixelFormat.Format32bppArgb);
	},
	() =>
	{
		Size size = fOriginalSystemBitmap.Size;
		unsafe
		{
			byte *sourceScanlineBytes = (byte *)sourceData.Scan0;
			byte *destScanlineBytes = (byte *)destData.Scan0;
			for(int y=0; y<size.Height; y++)
			{
				int *sourceScanline = (int *)sourceScanlineBytes;
				int *destScanline = (int *)destScanlineBytes;
			
				for(int x=0; x<size.Width; x++)
				{
					int color = sourceScanline[x];

					int r = p_Calculate
					((color>>16) & 0xFF, trackBarRedValue);
					int g = p_Calculate
					((color>>8) & 0xFF, trackBarGreenValue);
					int b = p_Calculate
					(color & 0xFF, trackBarBlueValue);
		
					color = (0xFF<<24) | 
						(r <<16) | (g<<8) | b;
					destScanline[x] = color;
				}
				
				sourceScanlineBytes += sourceData.Stride;
				destScanlineBytes += destData.Stride;
			}
		}
	},
	() =>
	{
		if (sourceData != null)
			fOriginalSystemBitmap.UnlockBits(sourceData);
		
		if (destData != null)
			fSystemBitmap.UnlockBits(destData);
	}
);

So, I hope these classes help anyone wanting to manipulate graphics without using unsafe code.

History

  • 22nd January, 2010: Initial version

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

 
SuggestionSome suggestions Pin
Ivo Smits26-Dec-11 10:07
Ivo Smits26-Dec-11 10:07 
GeneralRe: Some suggestions Pin
Paulo Zemek26-Dec-11 12:38
mvaPaulo Zemek26-Dec-11 12:38 

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.