Click here to Skip to main content
15,896,278 members
Articles / Programming Languages / C#

Managed Bitmaps

Rate me:
Please Sign up or sign in to vote.
4.78/5 (13 votes)
24 Jan 2010CPOL5 min read 31.8K   644   29  
This article presents classes that represent bitmaps in full managed code.
using System;
using System.Drawing;
using System.Drawing.Imaging;
using Pfz.Drawing.Extensions.ColorExtensions;
using Pfz.Threading;

namespace Pfz.Drawing
{
	/// <summary>
	/// This class represents a bitmap as a two dimensional array of Colors.
	/// It is capable of generating real bitmaps and extracting real-bitmap colors
	/// so you don't need to bother using unsafe code. Also, working with this
	/// ColorBitmap instead of using System.Drawing.Bitmap with GetPixel and 
	/// SetPixel is about 8 times faster.
	/// </summary>
	[Serializable]
	public sealed class ColorBitmap:
		IBitmap<Color>
	{
		#region Constructors
			#region Internal default constructor
				internal ColorBitmap()
				{
				}
			#endregion
		
			#region Create Empty
				/// <summary>
				/// Creates a new black managed-bitmap with the given width and height.
				/// </summary>
				/// <param name="width">The width of the new bitmap.</param>
				/// <param name="height">The height of the new bitmap.</param>
				public ColorBitmap(int width, int height)
				{
					PixelArray = new Color[width * height];
					Width = width;
				}
			#endregion
			#region Create from pixelArray
				/// <summary>
				/// Creates a new bitmap using the given array of colors and the given width.
				/// The array length must be a multiple of the width, as each line 
				/// "width in length" is considered to be one line.
				/// </summary>
				/// <param name="pixelArray">The array of colors that compose the bitmap.</param>
				/// <param name="width">The width of the bitmap.</param>
				public ColorBitmap(Color[] pixelArray, int width)
				{
					if (pixelArray == null)
						throw new ArgumentNullException("pixelArray");

					if (width < 1)
						throw new ArgumentOutOfRangeException("width");

					if ((pixelArray.Length % width) != 0)
						throw new ArithmeticException("pixelArray.Length must be a multiple of width.");

					PixelArray = pixelArray;
					Width = width;
				}
			#endregion
		#endregion
	
		#region Properties
			#region PixelArray
				/// <summary>
				/// Gets all the pixels of the Bitmap as a single color array.
				/// </summary>
				public Color[] PixelArray { get; internal set; }
			#endregion
			
			#region Width
				/// <summary>
				/// Gets the Width of the bitmap.
				/// </summary>
				public int Width { get; internal set; }
			#endregion
			#region Height
				/// <summary>
				/// Gets the Height of the bitmap.
				/// </summary>
				public int Height
				{
					get
					{
						return PixelArray.Length / Width;
					}
				}
			#endregion
			
			#region this[x, y]
				/// <summary>
				/// Gets or sets the pixel color at the given location.
				/// </summary>
				/// <param name="x">
				/// The x location of the pixel. This is not checked to be inside a line, so values lower than zero or higher than Width will draw on other lines.
				/// The only guarantee is that you will never access memory positions outside the ColorArray.
				/// </param>
				/// <param name="y">The y position of the pixel.</param>
				/// <returns>The color at the given coordinate.</returns>
				public Color this[int x, int y]
				{
					get
					{
						return PixelArray[y * Width + x];
					}
					set
					{
						PixelArray[y * Width + x] = value;
					}
				}
			#endregion
		#endregion
		#region Methods
			#region p_Initialize
				private void p_Initialize(Bitmap bitmap, Rectangle area)
				{
					if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
						throw new ArgumentException("bitmap.PixelFormat must be Format32bppArgb.");

					Width = area.Width;

					int count = Width * area.Height;
					PixelArray = new Color[count];

					BitmapData data = null;
					AbortSafe.Run
					(
						() => data = bitmap.LockBits(area, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb),
						() =>
						{
							unsafe
							{
								int index = -1;
								byte *scanLineBytes = (byte *)data.Scan0.ToPointer();
								for (int y = 0; y < area.Height; y++)
								{
									int *scanLine = (int *)scanLineBytes;
									
									for (int x = 0; x < area.Width; x++)
									{
										int intColor = scanLine[x];
										Color color = Color.FromArgb(intColor);

										index++;
										PixelArray[index] = color;
									}
									
									scanLineBytes += data.Stride;
								}
							}
						},
						() =>
						{
							if (data != null)
								bitmap.UnlockBits(data);
						}
					);
				}
			#endregion
			
			#region MakeGrayscale
				/// <summary>
				/// Makes the actual bitmap grayscale. Note that the bitmap is kept as 32-bit
				/// in memory, so you can add colored pixels after calling this method.
				/// </summary>
				public void MakeGrayscale()
				{
					int count = PixelArray.Length;
					for (int i=0; i<count; i++)
					{
						Color color = PixelArray[i];
						color = color.AsGrayscale();
						PixelArray[i] = color;
					}
				}
			#endregion
			
			#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] = Color.FromArgb(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].ToArgb();
									
									scanLineBytes += data.Stride;
								}
							}
						},
						() =>
						{
							if (data != null)
								destinationBitmap.UnlockBits(data);
						}
					);
				}

				/// <summary>
				/// Copies a block from this bitmap to the given bitmap.
				/// The destination bitmap can be the same, but the blocks can't intersect.
				/// </summary>
				public void CopyBlockTo(ColorBitmap destinationBitmap, Point sourceLocation, Point destinationLocation, Size blockSize)
				{
					if (ManagedBitmap.i_ValidateCopyToParameters(this, destinationBitmap, sourceLocation, destinationLocation, blockSize))
						return;
					
					if (destinationBitmap == this)
					{
						Rectangle sourceRectangle = new Rectangle(sourceLocation, blockSize);
						Rectangle destinationRectangle = new Rectangle(destinationLocation, blockSize);
						if (sourceRectangle.IntersectsWith(destinationRectangle))
							throw new ArgumentException("When copying a block to the same bitmap, the source and destination blocks can't intersect with each other.");
					}
					
					var destPixels = destinationBitmap.PixelArray;
					for (int y = 0; y < blockSize.Height; y++)
					{
						int sourceYIndex = ((sourceLocation.Y + y) * Width) + sourceLocation.X;
						int destinationYIndex = ((destinationLocation.Y + y) * Width) + destinationLocation.X;
						
						for (int x = 0; x < blockSize.Width; x++)
							destPixels[destinationYIndex + x] = PixelArray[sourceYIndex + x];
					}
				}

				/// <summary>
				/// Copies a block from this ColorBitmap to the given ArgbBitmap.
				/// </summary>
				public void CopyBlockTo(ArgbBitmap destinationBitmap, Point sourceLocation, Point destinationLocation, Size blockSize)
				{
					if (ManagedBitmap.i_ValidateCopyToParameters(this, destinationBitmap, sourceLocation, destinationLocation, blockSize))
						return;
					
					var destPixels = destinationBitmap.PixelArray;
					for (int y = 0; y < blockSize.Height; y++)
					{
						int sourceYIndex = ((sourceLocation.Y + y) * Width) + sourceLocation.X;
						int destinationYIndex = ((destinationLocation.Y + y) * Width) + destinationLocation.X;
						
						for (int x = 0; x < blockSize.Width; x++)
							destPixels[destinationYIndex + x] = PixelArray[sourceYIndex + x].ToArgb();
					}
				}

				/// <summary>
				/// Copies a block from this ColorBitmap to the given GrayscaleBitmap.
				/// </summary>
				public void CopyBlockTo(GrayscaleBitmap destinationBitmap, Point sourceLocation, Point destinationLocation, Size blockSize)
				{
					if (ManagedBitmap.i_ValidateCopyToParameters(this, destinationBitmap, sourceLocation, destinationLocation, blockSize))
						return;
					
					var destPixels = destinationBitmap.PixelArray;
					for (int y = 0; y < blockSize.Height; y++)
					{
						int sourceYIndex = ((sourceLocation.Y + y) * Width) + sourceLocation.X;
						int destinationYIndex = ((destinationLocation.Y + y) * Width) + destinationLocation.X;
						
						for (int x = 0; x < blockSize.Width; x++)
							destPixels[destinationYIndex + x] = PixelArray[sourceYIndex + x].GetGrayscaleIndex();
					}
				}
			#endregion
			
			#region CloneAsSystemBitmap
				/// <summary>
				/// Creates a new 32-bit Argb bitmap that is a copy of this bitmap.
				/// </summary>
				/// <returns>A System.Drawing.Bitmap that is a copy of this bitmap.</returns>
				public Bitmap CloneAsSystemBitmap()
				{
					Bitmap result = new Bitmap(Width, Height, PixelFormat.Format32bppArgb);
					CopyBlockTo(result, new Point(), new Point(), new Size(Width, Height));
					return result;
				}
			#endregion
			#region CloneAsColorBitmap
				/// <summary>
				/// Creates a copy of the current bitmap.
				/// </summary>
				public ColorBitmap CloneAsColorBitmap()
				{
					Color[] pixelArray = (Color[])PixelArray.Clone();
					return new ColorBitmap(pixelArray, Width);
				}
			#endregion
			#region CloneAsArgbBitmap
				/// <summary>
				/// Creates a copy of this bitmap as an ArgbBitmap.
				/// </summary>
				public ArgbBitmap CloneAsArgbBitmap()
				{
					Color[] sourcePixels = PixelArray;
					int count = sourcePixels.Length;
					
					int[] destPixels = new int[count];
					for (int i=0; i<count; i++)
						destPixels[i] = sourcePixels[i].ToArgb();
					
					ArgbBitmap result = new ArgbBitmap();
					result.PixelArray = destPixels;
					result.Width = Width;
					return result;
				}
			#endregion
			#region CloneAsGrayscaleBitmap
				/// <summary>
				/// Creates a new grayscale bitmap that is a copy of the given colorBitmap.
				/// </summary>
				public GrayscaleBitmap CloneAsGrayscaleBitmap()
				{
					Color[] sourcePixels = PixelArray;
					int count = sourcePixels.Length;
					
					byte[] destPixels = new byte[count];
					for (int i=0; i<count; i++)
						destPixels[i] = sourcePixels[i].GetGrayscaleIndex();
						
					GrayscaleBitmap result = new GrayscaleBitmap();
					result.PixelArray = destPixels;
					result.Width = Width;
					return result;
				}
			#endregion
		#endregion

		#region IBitmap Members
			Color IBitmap.this[int pixelIndex]
			{
				get
				{
					return PixelArray[pixelIndex];
				}
				set
				{
					PixelArray[pixelIndex] = value;
				}
			}
			Array IBitmap.PixelArray
			{
				get
				{
					return PixelArray;
				}
			}
			void IBitmap.CopyBlockTo(IBitmap destinationBitmap, Point sourceLocation, Point destinationLocation, Size blockSize)
			{
				if (destinationBitmap == null)
					throw new ArgumentNullException("destinationBitmap");
			
				ColorBitmap colorBitmap = destinationBitmap as ColorBitmap;
				if (colorBitmap != null)
				{
					CopyBlockTo(colorBitmap, sourceLocation, destinationLocation, blockSize);
					return;
				}
				
				ArgbBitmap argbBitmap = destinationBitmap as ArgbBitmap;
				if (argbBitmap != null)
				{
					CopyBlockTo(argbBitmap, sourceLocation, destinationLocation, blockSize);
					return;
				}
			
				GrayscaleBitmap grayscaleBitmap = destinationBitmap as GrayscaleBitmap;
				if (grayscaleBitmap != null)
				{
					CopyBlockTo(grayscaleBitmap, sourceLocation, destinationLocation, blockSize);
					return;
				}
				
				throw new ArgumentException("There is no conversion to the given destination bitmap.");
			}
		#endregion
		#region ICloneable Members
			object ICloneable.Clone()
			{
				return CloneAsColorBitmap();
			}
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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