Click here to Skip to main content
15,895,746 members
Articles / Programming Languages / C#

Image Processing for Dummies with C# and GDI+ Part 6 - The HSL color space

Rate me:
Please Sign up or sign in to vote.
4.84/5 (57 votes)
28 Jun 2004CPOL7 min read 421.3K   8K   199  
A discussion of the HSL color space, including code for a color picker and image filters
using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace CSharpFilters
{
	public class ConvMatrix
	{
		public int TopLeft = 0, TopMid = 0, TopRight = 0;
		public int MidLeft = 0, Pixel = 1, MidRight = 0;
		public int BottomLeft = 0, BottomMid = 0, BottomRight = 0;
		public int Factor = 1;
		public int Offset = 0;
		public void SetAll(int nVal)
		{
			TopLeft = TopMid = TopRight = MidLeft = Pixel = MidRight = BottomLeft = BottomMid = BottomRight = nVal;
		}
	}

	public struct FloatPoint
	{
		public double X;
		public double Y;
	}

	public class BitmapFilter
	{
		public const short EDGE_DETECT_KIRSH		= 1;
		public const short EDGE_DETECT_PREWITT		= 2;
		public const short EDGE_DETECT_SOBEL		= 3;

		public static bool Invert(Bitmap b)
		{
			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;

				int nOffset = stride - b.Width*3;
				int nWidth = b.Width * 3;
	
				for(int y=0;y<b.Height;++y)
				{
					for(int x=0; x < nWidth; ++x )
					{
						p[0] = (byte)(255-p[0]);
						++p;
					}
					p += nOffset;
				}
			}

			b.UnlockBits(bmData);

			return true;
		}

		public static bool GrayScale(Bitmap b)
		{
			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;

				int nOffset = stride - b.Width*3;

				byte red, green, blue;
	
				for(int y=0;y<b.Height;++y)
				{
					for(int x=0; x < b.Width; ++x )
					{
						blue = p[0];
						green = p[1];
						red = p[2];

						p[0] = p[1] = p[2] = (byte)(.299 * red + .587 * green + .114 * blue);

						p += 3;
					}
					p += nOffset;
				}
			}

			b.UnlockBits(bmData);

			return true;
		}

		public static bool Brightness(Bitmap b, int nBrightness)
		{
			if (nBrightness < -255 || nBrightness > 255)
				return false;

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			int nVal = 0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;

				int nOffset = stride - b.Width*3;
				int nWidth = b.Width * 3;

				for(int y=0;y<b.Height;++y)
				{
					for(int x=0; x < nWidth; ++x )
					{
						nVal = (int) (p[0] + nBrightness);
		
						if (nVal < 0) nVal = 0;
						if (nVal > 255) nVal = 255;

						p[0] = (byte)nVal;

						++p;
					}
					p += nOffset;
				}
			}

			b.UnlockBits(bmData);

			return true;
		}

		public static bool Contrast(Bitmap b, sbyte nContrast)
		{
			if (nContrast < -100) return false;
			if (nContrast >  100) return false;

			double pixel = 0, contrast = (100.0+nContrast)/100.0;

			contrast *= contrast;

			int red, green, blue;
			
			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;

				int nOffset = stride - b.Width*3;

				for(int y=0;y<b.Height;++y)
				{
					for(int x=0; x < b.Width; ++x )
					{
						blue = p[0];
						green = p[1];
						red = p[2];
				
						pixel = red/255.0;
						pixel -= 0.5;
						pixel *= contrast;
						pixel += 0.5;
						pixel *= 255;
						if (pixel < 0) pixel = 0;
						if (pixel > 255) pixel = 255;
						p[2] = (byte) pixel;

						pixel = green/255.0;
						pixel -= 0.5;
						pixel *= contrast;
						pixel += 0.5;
						pixel *= 255;
						if (pixel < 0) pixel = 0;
						if (pixel > 255) pixel = 255;
						p[1] = (byte) pixel;

						pixel = blue/255.0;
						pixel -= 0.5;
						pixel *= contrast;
						pixel += 0.5;
						pixel *= 255;
						if (pixel < 0) pixel = 0;
						if (pixel > 255) pixel = 255;
						p[0] = (byte) pixel;					

						p += 3;
					}
					p += nOffset;
				}
			}

			b.UnlockBits(bmData);

			return true;
		}
	
		public static bool Gamma(Bitmap b, double red, double green, double blue)
		{
			if (red < .2 || red > 5) return false;
			if (green < .2 || green > 5) return false;
			if (blue < .2 || blue > 5) return false;

			byte [] redGamma = new byte [256];
			byte [] greenGamma = new byte [256];
			byte [] blueGamma = new byte [256];

			for (int i = 0; i< 256; ++i)
			{
				redGamma[i] = (byte)Math.Min(255, (int)(( 255.0 * Math.Pow(i/255.0, 1.0/red)) + 0.5));
				greenGamma[i] = (byte)Math.Min(255, (int)(( 255.0 * Math.Pow(i/255.0, 1.0/green)) + 0.5));
				blueGamma[i] = (byte)Math.Min(255, (int)(( 255.0 * Math.Pow(i/255.0, 1.0/blue)) + 0.5));
			}

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;

				int nOffset = stride - b.Width*3;

				for(int y=0;y<b.Height;++y)
				{
					for(int x=0; x < b.Width; ++x )
					{
						p[2] = redGamma[ p[2] ];
						p[1] = greenGamma[ p[1] ];
						p[0] = blueGamma[ p[0] ];

						p += 3;
					}
					p += nOffset;
				}
			}

			b.UnlockBits(bmData);

			return true;
		}

		public static bool Color(Bitmap b, int red, int green, int blue)
		{
			if (red < -255 || red > 255) return false;
			if (green < -255 || green > 255) return false;
			if (blue < -255 || blue > 255) return false;

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;

				int nOffset = stride - b.Width*3;
				int nPixel;

				for(int y=0;y<b.Height;++y)
				{
					for(int x=0; x < b.Width; ++x )
					{
						nPixel = p[2] + red;
						nPixel = Math.Max(nPixel, 0);
						p[2] = (byte)Math.Min(255, nPixel);

						nPixel = p[1] + green;
						nPixel = Math.Max(nPixel, 0);
						p[1] = (byte)Math.Min(255, nPixel);

						nPixel = p[0] + blue;
						nPixel = Math.Max(nPixel, 0);
						p[0] = (byte)Math.Min(255, nPixel);

						p += 3;
					}
					p += nOffset;
				}
			}

			b.UnlockBits(bmData);

			return true;
		}

		public static bool Conv3x3(Bitmap b, ConvMatrix m)
		{
			// Avoid divide by zero errors
			if (0 == m.Factor) return false;

			Bitmap bSrc = (Bitmap)b.Clone(); 

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
			BitmapData bmSrc = bSrc.LockBits(new Rectangle(0, 0, bSrc.Width, bSrc.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			int stride2 = stride * 2;
			System.IntPtr Scan0 = bmData.Scan0;
			System.IntPtr SrcScan0 = bmSrc.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;
				byte * pSrc = (byte *)(void *)SrcScan0;

				int nOffset = stride - b.Width*3;
				int nWidth = b.Width - 2;
				int nHeight = b.Height - 2;

				int nPixel;

				for(int y=0;y < nHeight;++y)
				{
					for(int x=0; x < nWidth; ++x )
					{
						nPixel = ( ( ( (pSrc[2] * m.TopLeft) + (pSrc[5] * m.TopMid) + (pSrc[8] * m.TopRight) +
							(pSrc[2 + stride] * m.MidLeft) + (pSrc[5 + stride] * m.Pixel) + (pSrc[8 + stride] * m.MidRight) +
							(pSrc[2 + stride2] * m.BottomLeft) + (pSrc[5 + stride2] * m.BottomMid) + (pSrc[8 + stride2] * m.BottomRight)) / m.Factor) + m.Offset); 

						if (nPixel < 0) nPixel = 0;
						if (nPixel > 255) nPixel = 255;

						p[5 + stride]= (byte)nPixel;

						nPixel = ( ( ( (pSrc[1] * m.TopLeft) + (pSrc[4] * m.TopMid) + (pSrc[7] * m.TopRight) +
							(pSrc[1 + stride] * m.MidLeft) + (pSrc[4 + stride] * m.Pixel) + (pSrc[7 + stride] * m.MidRight) +
							(pSrc[1 + stride2] * m.BottomLeft) + (pSrc[4 + stride2] * m.BottomMid) + (pSrc[7 + stride2] * m.BottomRight)) / m.Factor) + m.Offset); 

						if (nPixel < 0) nPixel = 0;
						if (nPixel > 255) nPixel = 255;
							
						p[4 + stride] = (byte)nPixel;

						nPixel = ( ( ( (pSrc[0] * m.TopLeft) + (pSrc[3] * m.TopMid) + (pSrc[6] * m.TopRight) +
							(pSrc[0 + stride] * m.MidLeft) + (pSrc[3 + stride] * m.Pixel) + (pSrc[6 + stride] * m.MidRight) +
							(pSrc[0 + stride2] * m.BottomLeft) + (pSrc[3 + stride2] * m.BottomMid) + (pSrc[6 + stride2] * m.BottomRight)) / m.Factor) + m.Offset); 

						if (nPixel < 0) nPixel = 0;
						if (nPixel > 255) nPixel = 255;

						p[3 + stride] = (byte)nPixel;

						p += 3;
						pSrc += 3;
					}
					p += nOffset;
					pSrc += nOffset;
				}
			}

			b.UnlockBits(bmData);
			bSrc.UnlockBits(bmSrc);

			return true;
		}

		public static bool Smooth(Bitmap b, int nWeight /* default to 1 */)
		{
			ConvMatrix m = new ConvMatrix();
			m.SetAll(1);
			m.Pixel = nWeight;
			m.Factor = nWeight + 8;

			return  BitmapFilter.Conv3x3(b, m);
		}

		public static bool GaussianBlur(Bitmap b, int nWeight /* default to 4*/)
		{
			ConvMatrix m = new ConvMatrix();
			m.SetAll(1);
			m.Pixel = nWeight;
			m.TopMid = m.MidLeft = m.MidRight = m.BottomMid = 2;
			m.Factor = nWeight + 12;

			return  BitmapFilter.Conv3x3(b, m);
		}
		public static bool MeanRemoval(Bitmap b, int nWeight /* default to 9*/ )
		{
			ConvMatrix m = new ConvMatrix();
			m.SetAll(-1);
			m.Pixel = nWeight;
			m.Factor = nWeight - 8;

			return BitmapFilter.Conv3x3(b, m);
		}
		public static bool Sharpen(Bitmap b, int nWeight /* default to 11*/ )
		{
			ConvMatrix m = new ConvMatrix();
			m.SetAll(0);
			m.Pixel = nWeight;
			m.TopMid = m.MidLeft = m.MidRight = m.BottomMid = -2;
			m.Factor = nWeight - 8;

			return  BitmapFilter.Conv3x3(b, m);
		}
		public static bool EmbossLaplacian(Bitmap b)
		{
			ConvMatrix m = new ConvMatrix();
			m.SetAll(-1);
			m.TopMid = m.MidLeft = m.MidRight = m.BottomMid = 0;
			m.Pixel = 4;
			m.Offset = 127;

			return  BitmapFilter.Conv3x3(b, m);
		}	
		public static bool EdgeDetectQuick(Bitmap b)
		{
			ConvMatrix m = new ConvMatrix();
			m.TopLeft = m.TopMid = m.TopRight = -1;
			m.MidLeft = m.Pixel = m.MidRight = 0;
			m.BottomLeft = m.BottomMid = m.BottomRight = 1;
		
			m.Offset = 127;

			return  BitmapFilter.Conv3x3(b, m);
		}

		public static bool EdgeDetectConvolution(Bitmap b, short nType, byte nThreshold)
		{
			ConvMatrix m = new ConvMatrix();

			// I need to make a copy of this bitmap BEFORE I alter it 80)
			Bitmap bTemp = (Bitmap)b.Clone();

			switch (nType)
			{
				case EDGE_DETECT_SOBEL:
					m.SetAll(0);
					m.TopLeft = m.BottomLeft = 1;
					m.TopRight = m.BottomRight = -1;
					m.MidLeft = 2;
					m.MidRight = -2;
					m.Offset = 0;
					break;
				case EDGE_DETECT_PREWITT:
					m.SetAll(0);
					m.TopLeft = m.MidLeft = m.BottomLeft = -1;
					m.TopRight = m.MidRight = m.BottomRight = 1;
					m.Offset = 0;
					break;
				case EDGE_DETECT_KIRSH:
					m.SetAll(-3);
					m.Pixel = 0;
					m.TopLeft = m.MidLeft = m.BottomLeft = 5;
					m.Offset = 0;
					break;
			}

			BitmapFilter.Conv3x3(b, m);

			switch (nType)
			{
				case EDGE_DETECT_SOBEL:
					m.SetAll(0);
					m.TopLeft = m.TopRight = 1;
					m.BottomLeft = m.BottomRight = -1;
					m.TopMid = 2;
					m.BottomMid = -2;
					m.Offset = 0;
					break;
				case EDGE_DETECT_PREWITT:
					m.SetAll(0);
					m.BottomLeft = m.BottomMid = m.BottomRight = -1;
					m.TopLeft = m.TopMid = m.TopRight = 1;
					m.Offset = 0;
					break;
				case EDGE_DETECT_KIRSH:
					m.SetAll(-3);
					m.Pixel = 0;
					m.BottomLeft = m.BottomMid = m.BottomRight = 5;
					m.Offset = 0;
					break;
			}

			BitmapFilter.Conv3x3(bTemp, m);

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
			BitmapData bmData2 = bTemp.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;
			System.IntPtr Scan02 = bmData2.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;
				byte * p2 = (byte *)(void *)Scan02;

				int nOffset = stride - b.Width*3;
				int nWidth = b.Width * 3;

				int nPixel = 0;
	
				for(int y=0;y<b.Height;++y)
				{
					for(int x=0; x < nWidth; ++x )
					{
						nPixel = (int) Math.Sqrt((p[0]*p[0]) + (p2[0] * p2[0]));
						if (nPixel<nThreshold)nPixel = nThreshold;
						if (nPixel>255) nPixel = 255;
						p[0] = (byte) nPixel;
						++p;
						++p2;
					}
					p += nOffset;
					p2 += nOffset;
				}
			}

			b.UnlockBits(bmData);
			bTemp.UnlockBits(bmData2);

			return true;
		}
	
		public static bool EdgeDetectHorizontal(Bitmap b)
		{
			Bitmap bmTemp = (Bitmap)b.Clone();

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
			BitmapData bmData2 = bmTemp.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;
			System.IntPtr Scan02 = bmData2.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;
				byte * p2 = (byte *)(void *)Scan02;

				int nOffset = stride - b.Width*3;
				int nWidth = b.Width * 3;

				int nPixel = 0;
	
				p += stride;
				p2 += stride;

				for(int y=1;y<b.Height-1;++y)
				{
					p += 9;
					p2 += 9;

					for(int x=9; x < nWidth-9; ++x )
					{
						nPixel = ((p2 + stride - 9)[0] +
							(p2 + stride - 6)[0] +
							(p2 + stride - 3)[0] +
							(p2 + stride)[0] +
							(p2 + stride + 3)[0] +
							(p2 + stride + 6)[0] +
							(p2 + stride + 9)[0] -
							(p2 - stride - 9)[0] -
							(p2 - stride - 6)[0] -
							(p2 - stride - 3)[0] -
							(p2 - stride)[0] -
							(p2 - stride + 3)[0] -
							(p2 - stride + 6)[0] -
							(p2 - stride + 9)[0]);

						if (nPixel < 0) nPixel = 0;
						if (nPixel > 255) nPixel = 255;

						(p+stride)[0] = (byte) nPixel;
					
						++ p;
						++ p2;
					}

					p += 9 + nOffset;
					p2 += 9 + nOffset;
				}
			}

			b.UnlockBits(bmData);
			bmTemp.UnlockBits(bmData2);

			return true;
		}

		public static bool EdgeDetectVertical(Bitmap b)
		{
			Bitmap bmTemp = (Bitmap)b.Clone();

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
			BitmapData bmData2 = bmTemp.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;
			System.IntPtr Scan02 = bmData2.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;
				byte * p2 = (byte *)(void *)Scan02;

				int nOffset = stride - b.Width*3;
				int nWidth = b.Width * 3;

				int nPixel = 0;

				int nStride2 = stride *2;
				int nStride3 = stride * 3;
	
				p += nStride3;
				p2 += nStride3;

				for(int y=3;y<b.Height-3;++y)
				{
					p += 3;
					p2 += 3;

					for(int x=3; x < nWidth-3; ++x )
					{
						nPixel = ((p2 + nStride3 + 3)[0] +
							(p2 + nStride2 + 3)[0] +
							(p2 + stride + 3)[0] +
							(p2 + 3)[0] +
							(p2 - stride + 3)[0] +
							(p2 - nStride2 + 3)[0] +
							(p2 - nStride3 + 3)[0] -
							(p2 + nStride3 - 3)[0] -
							(p2 + nStride2 - 3)[0] -
							(p2 + stride - 3)[0] -
							(p2 - 3)[0] -
							(p2 - stride - 3)[0] -
							(p2 - nStride2 - 3)[0] -
							(p2 - nStride3 - 3)[0]);

						if (nPixel < 0) nPixel = 0;
						if (nPixel > 255) nPixel = 255;

						p[0] = (byte) nPixel;
					
						++ p;
						++ p2;
					}

					p += 3 + nOffset;
					p2 += 3 + nOffset;
				}
			}

			b.UnlockBits(bmData);
			bmTemp.UnlockBits(bmData2);

			return true;
		}

		public static bool EdgeDetectHomogenity(Bitmap b, byte nThreshold)
		{
			// This one works by working out the greatest difference between a pixel and it's eight neighbours.
			// The threshold allows softer edges to be forced down to black, use 0 to negate it's effect.
			Bitmap b2 = (Bitmap) b.Clone();

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
			BitmapData bmData2 = b2.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;
			System.IntPtr Scan02 = bmData2.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;
				byte * p2 = (byte *)(void *)Scan02;

				int nOffset = stride - b.Width*3;
				int nWidth = b.Width * 3;

				int nPixel = 0, nPixelMax = 0;

				p += stride;
				p2 += stride;

				for(int y=1;y<b.Height-1;++y)
				{
					p += 3;
					p2 += 3;

					for(int x=3; x < nWidth-3; ++x )
					{
						nPixelMax = Math.Abs(p2[0] - (p2+stride-3)[0]);
						nPixel = Math.Abs(p2[0] - (p2 + stride)[0]);
						if (nPixel>nPixelMax) nPixelMax = nPixel;

						nPixel = Math.Abs(p2[0] - (p2 + stride + 3)[0]);
						if (nPixel>nPixelMax) nPixelMax = nPixel;

						nPixel = Math.Abs(p2[0] - (p2 - stride)[0]);
						if (nPixel>nPixelMax) nPixelMax = nPixel;

						nPixel = Math.Abs(p2[0] - (p2 + stride)[0]);
						if (nPixel>nPixelMax) nPixelMax = nPixel;

						nPixel = Math.Abs(p2[0] - (p2 - stride - 3)[0]);
						if (nPixel>nPixelMax) nPixelMax = nPixel;

						nPixel = Math.Abs(p2[0] - (p2 - stride)[0]);
						if (nPixel>nPixelMax) nPixelMax = nPixel;

						nPixel = Math.Abs(p2[0] - (p2 - stride + 3)[0]);
						if (nPixel>nPixelMax) nPixelMax = nPixel;

						if (nPixelMax < nThreshold) nPixelMax = 0;

						p[0] = (byte) nPixelMax;

						++ p;
						++ p2;
					}

					p += 3 + nOffset;
					p2 += 3 + nOffset;
				}
			}

			b.UnlockBits(bmData);
			b2.UnlockBits(bmData2);

			return true;
            
		}
		public static bool EdgeDetectDifference(Bitmap b, byte nThreshold)
		{
			// This one works by working out the greatest difference between a pixel and it's eight neighbours.
			// The threshold allows softer edges to be forced down to black, use 0 to negate it's effect.
			Bitmap b2 = (Bitmap) b.Clone();

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
			BitmapData bmData2 = b2.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;
			System.IntPtr Scan02 = bmData2.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;
				byte * p2 = (byte *)(void *)Scan02;

				int nOffset = stride - b.Width*3;
				int nWidth = b.Width * 3;

				int nPixel = 0, nPixelMax = 0;

				p += stride;
				p2 += stride;

				for(int y=1;y<b.Height-1;++y)
				{
					p += 3;
					p2 += 3;

					for(int x=3; x < nWidth-3; ++x )
					{
						nPixelMax = Math.Abs((p2 - stride + 3)[0] - (p2+stride-3)[0]);
						nPixel = Math.Abs((p2 + stride + 3)[0] - (p2 - stride - 3)[0]);
						if (nPixel>nPixelMax) nPixelMax = nPixel;

						nPixel = Math.Abs((p2 - stride)[0] - (p2 + stride)[0]);
						if (nPixel>nPixelMax) nPixelMax = nPixel;

						nPixel = Math.Abs((p2+3)[0] - (p2 - 3)[0]);
						if (nPixel>nPixelMax) nPixelMax = nPixel;

						if (nPixelMax < nThreshold) nPixelMax = 0;

						p[0] = (byte) nPixelMax;

						++ p;
						++ p2;
					}

					p += 3 + nOffset;
					p2 += 3 + nOffset;
				}
			}

			b.UnlockBits(bmData);
			b2.UnlockBits(bmData2);

			return true;
            
		}

		public static bool EdgeEnhance(Bitmap b, byte nThreshold)
		{
			// This one works by working out the greatest difference between a nPixel and it's eight neighbours.
			// The threshold allows softer edges to be forced down to black, use 0 to negate it's effect.
			Bitmap b2 = (Bitmap) b.Clone();

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
			BitmapData bmData2 = b2.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int stride = bmData.Stride;
			System.IntPtr Scan0 = bmData.Scan0;
			System.IntPtr Scan02 = bmData2.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;
				byte * p2 = (byte *)(void *)Scan02;

				int nOffset = stride - b.Width*3;
				int nWidth = b.Width * 3;

				int nPixel = 0, nPixelMax = 0;

				p += stride;
				p2 += stride;

				for (int y = 1; y < b.Height-1; ++y)
				{
					p += 3;
					p2 += 3;

					for (int x = 3; x < nWidth-3; ++x)
					{
						nPixelMax = Math.Abs((p2 - stride + 3)[0] - (p2 + stride - 3)[0]);

						nPixel = Math.Abs((p2 + stride + 3)[0] - (p2 - stride - 3)[0]);

						if (nPixel > nPixelMax) nPixelMax = nPixel;

						nPixel = Math.Abs((p2 - stride)[0] - (p2 + stride)[0]);

						if (nPixel > nPixelMax) nPixelMax = nPixel;

						nPixel = Math.Abs((p2 + 3)[0] - (p2 - 3)[0]);

						if (nPixel > nPixelMax) nPixelMax = nPixel;

						if (nPixelMax > nThreshold && nPixelMax > p[0])
							p[0] = (byte) Math.Max(p[0], nPixelMax);

						++ p;
						++ p2;			
					}

					p += nOffset + 3;
					p2 += nOffset + 3;
				}
			}	

			b.UnlockBits(bmData);
			b2.UnlockBits(bmData2);

			return true;
		}
		public static Bitmap Resize(Bitmap b, int nWidth, int nHeight, bool bBilinear)
		{
			Bitmap bTemp = (Bitmap)b.Clone();
			b = new Bitmap(nWidth, nHeight, bTemp.PixelFormat);

			double nXFactor = (double)bTemp.Width/(double)nWidth;
			double nYFactor = (double)bTemp.Height/(double)nHeight;

			if (bBilinear)
			{
				double fraction_x, fraction_y, one_minus_x, one_minus_y;
				int ceil_x, ceil_y, floor_x, floor_y;
				Color c1 = new Color();
				Color c2 = new Color();
				Color c3 = new Color();
				Color c4 = new Color();
				byte red, green, blue;

				byte b1, b2;

				for (int x = 0; x < b.Width; ++x)
					for (int y = 0; y < b.Height; ++y)
					{
						// Setup

						floor_x = (int)Math.Floor(x * nXFactor);
						floor_y = (int)Math.Floor(y * nYFactor);
						ceil_x = floor_x + 1;
						if (ceil_x >= bTemp.Width) ceil_x = floor_x;
						ceil_y = floor_y + 1;
						if (ceil_y >= bTemp.Height) ceil_y = floor_y;
						fraction_x = x * nXFactor - floor_x;
						fraction_y = y * nYFactor - floor_y;
						one_minus_x = 1.0 - fraction_x;
						one_minus_y = 1.0 - fraction_y;

						c1 = bTemp.GetPixel(floor_x, floor_y);
						c2 = bTemp.GetPixel(ceil_x, floor_y);
						c3 = bTemp.GetPixel(floor_x, ceil_y);
						c4 = bTemp.GetPixel(ceil_x, ceil_y);

						// Blue
						b1 = (byte)(one_minus_x * c1.B + fraction_x * c2.B);

						b2 = (byte)(one_minus_x * c3.B + fraction_x * c4.B);
						
						blue = (byte)(one_minus_y * (double)(b1) + fraction_y * (double)(b2));

						// Green
						b1 = (byte)(one_minus_x * c1.G + fraction_x * c2.G);

						b2 = (byte)(one_minus_x * c3.G + fraction_x * c4.G);
						
						green = (byte)(one_minus_y * (double)(b1) + fraction_y * (double)(b2));

						// Red
						b1 = (byte)(one_minus_x * c1.R + fraction_x * c2.R);

						b2 = (byte)(one_minus_x * c3.R + fraction_x * c4.R);
						
						red = (byte)(one_minus_y * (double)(b1) + fraction_y * (double)(b2));

						b.SetPixel(x,y, System.Drawing.Color.FromArgb(255, red, green, blue));
					}
			}
			else
			{
				for (int x = 0; x < b.Width; ++x)
					for (int y = 0; y < b.Height; ++y)
						b.SetPixel(x, y, bTemp.GetPixel((int)(Math.Floor(x * nXFactor)),(int)(Math.Floor(y * nYFactor))));
			}

			return b;
		}

		public static bool OffsetFilterAbs(Bitmap b, Point[,] offset )
		{
			Bitmap bSrc = (Bitmap)b.Clone(); 

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
			BitmapData bmSrc = bSrc.LockBits(new Rectangle(0, 0, bSrc.Width, bSrc.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int scanline = bmData.Stride;

			System.IntPtr Scan0 = bmData.Scan0;
			System.IntPtr SrcScan0 = bmSrc.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;
				byte * pSrc = (byte *)(void *)SrcScan0;

				int nOffset = bmData.Stride - b.Width*3;
				int nWidth = b.Width;
				int nHeight = b.Height;

				int xOffset, yOffset;

				for(int y=0;y < nHeight;++y)
				{
					for(int x=0; x < nWidth; ++x )
					{	
						xOffset = offset[x,y].X;
						yOffset = offset[x,y].Y;
				
						if (yOffset >= 0 && yOffset < nHeight && xOffset >= 0 && xOffset < nWidth)
						{
							p[0] = pSrc[(yOffset * scanline) + (xOffset * 3)];
							p[1] = pSrc[(yOffset * scanline) + (xOffset * 3) + 1];
							p[2] = pSrc[(yOffset * scanline) + (xOffset * 3) + 2];
						}

						p += 3;
					}
					p += nOffset;
				}
			}

			b.UnlockBits(bmData);
			bSrc.UnlockBits(bmSrc);

			return true;
		}

		public static bool OffsetFilter(Bitmap b, Point[,] offset )
		{
			Bitmap bSrc = (Bitmap)b.Clone(); 

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
			BitmapData bmSrc = bSrc.LockBits(new Rectangle(0, 0, bSrc.Width, bSrc.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int scanline = bmData.Stride;

			System.IntPtr Scan0 = bmData.Scan0;
			System.IntPtr SrcScan0 = bmSrc.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;
				byte * pSrc = (byte *)(void *)SrcScan0;

				int nOffset = bmData.Stride - b.Width*3;
				int nWidth = b.Width;
				int nHeight = b.Height;

				int xOffset, yOffset;

				for(int y=0;y < nHeight;++y)
				{
					for(int x=0; x < nWidth; ++x )
					{	
						xOffset = offset[x,y].X;
						yOffset = offset[x,y].Y;

						if (y+yOffset >= 0 && y+yOffset < nHeight && x+xOffset >= 0 && x+xOffset < nWidth)
						{
							p[0] = pSrc[((y+yOffset) * scanline) + ((x+xOffset) * 3)];
							p[1] = pSrc[((y+yOffset) * scanline) + ((x+xOffset) * 3) + 1];
							p[2] = pSrc[((y+yOffset) * scanline) + ((x+xOffset) * 3) + 2];
						}

						p += 3;
					}
					p += nOffset;
				}
			}

			b.UnlockBits(bmData);
			bSrc.UnlockBits(bmSrc);

			return true;
		}

		public static bool OffsetFilterAntiAlias(Bitmap b, FloatPoint[,] fp)
		{
			Bitmap bSrc = (Bitmap)b.Clone(); 

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
			BitmapData bmSrc = bSrc.LockBits(new Rectangle(0, 0, bSrc.Width, bSrc.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int scanline = bmData.Stride;

			System.IntPtr Scan0 = bmData.Scan0;
			System.IntPtr SrcScan0 = bmSrc.Scan0;

			unsafe
			{
				byte * p = (byte *)(void *)Scan0;
				byte * pSrc = (byte *)(void *)SrcScan0;

				int nOffset = bmData.Stride - b.Width*3;
				int nWidth = b.Width;
				int nHeight = b.Height;

				double xOffset, yOffset;

				double fraction_x, fraction_y, one_minus_x, one_minus_y;
				int ceil_x, ceil_y, floor_x, floor_y;
				Byte p1, p2;

				for(int y=0;y < nHeight;++y)
				{
					for(int x=0; x < nWidth; ++x )
					{	
						xOffset = fp[x,y].X;
						yOffset = fp[x,y].Y;
				
						// Setup

						floor_x = (int)Math.Floor(xOffset);
						floor_y = (int)Math.Floor(yOffset);
						ceil_x = floor_x + 1;
						ceil_y = floor_y + 1;
						fraction_x = xOffset - floor_x;
						fraction_y = yOffset - floor_y;
						one_minus_x = 1.0 - fraction_x;
						one_minus_y = 1.0 - fraction_y;

						if (floor_y >= 0 && ceil_y < nHeight && floor_x >= 0 && ceil_x < nWidth)
						{
							// Blue

							p1 = (Byte)(one_minus_x * (double)(pSrc[floor_y * scanline + floor_x * 3]) +
								fraction_x * (double)(pSrc[floor_y * scanline + ceil_x * 3]));

							p2 = (Byte)(one_minus_x * (double)(pSrc[ceil_y * scanline + floor_x * 3]) +
								fraction_x * (double)(pSrc[ceil_y * scanline + 3 * ceil_x]));

							p[x * 3 + y*scanline] = (Byte)(one_minus_y * (double)(p1) + fraction_y * (double)(p2));
							
							// Green

							p1 = (Byte)(one_minus_x * (double)(pSrc[floor_y * scanline + floor_x * 3 + 1]) +
								fraction_x * (double)(pSrc[floor_y * scanline + ceil_x * 3 + 1]));

							p2 = (Byte)(one_minus_x * (double)(pSrc[ceil_y * scanline + floor_x * 3 + 1]) +
								fraction_x * (double)(pSrc[ceil_y * scanline + 3 * ceil_x + 1]));
			
							p[x * 3 + y*scanline + 1] = (Byte)(one_minus_y * (double)(p1) + fraction_y * (double)(p2));

							// Red

							p1 = (Byte)(one_minus_x * (double)(pSrc[floor_y * scanline + floor_x * 3 + 2]) +
								fraction_x * (double)(pSrc[floor_y * scanline + ceil_x * 3 + 2]));

							p2 = (Byte)(one_minus_x * (double)(pSrc[ceil_y * scanline + floor_x * 3 + 2]) +
								fraction_x * (double)(pSrc[ceil_y * scanline + 3 * ceil_x + 2]));
			
							p[x * 3 + y*scanline + 2] = (Byte)(one_minus_y * (double)(p1) + fraction_y * (double)(p2));
						}
					}
				}
			}

			b.UnlockBits(bmData);
			bSrc.UnlockBits(bmSrc);

			return true;
		}

		public static bool Flip(Bitmap b, bool bHorz, bool bVert)
		{
			Point [,] ptFlip = new Point[b.Width,b.Height]; 

			int nWidth = b.Width;
			int nHeight = b.Height;

			for (int x = 0; x < nWidth; ++x)
				for (int y = 0; y < nHeight; ++y)
				{
					ptFlip[x, y].X = (bHorz) ? nWidth - (x+1) : x;
					ptFlip[x,y].Y = (bVert) ? nHeight - (y + 1) : y;
				}
				
			OffsetFilterAbs(b, ptFlip);		

			return true;
		}

		public static bool RandomJitter(Bitmap b, short nDegree)
		{
			Point [,] ptRandJitter = new Point[b.Width,b.Height]; 

			int nWidth = b.Width;
			int nHeight = b.Height;

			int newX, newY;

			short nHalf = (short)Math.Floor(nDegree/2);
			Random rnd = new Random();

			for (int x = 0; x < nWidth; ++x)
				for (int y = 0; y < nHeight; ++y)
				{
					newX = rnd.Next(nDegree) - nHalf;

					if (x + newX > 0 && x + newX < nWidth)
						ptRandJitter[x, y].X = newX;
					else
						ptRandJitter[x, y].X = 0;

					newY = rnd.Next(nDegree) - nHalf;

					if (y + newY > 0 && y + newY < nWidth)
						ptRandJitter[x, y].Y = newY;
					else
						ptRandJitter[x, y].Y = 0;
				}
				
			OffsetFilter(b, ptRandJitter);		

			return true;
		}
		public static bool Swirl(Bitmap b, double fDegree, bool bSmoothing /* default fDegree to .05 */)
		{
			int nWidth = b.Width;
			int nHeight = b.Height;

			FloatPoint [,] fp = new FloatPoint[nWidth, nHeight];
			Point [,] pt = new Point[nWidth, nHeight];

			Point mid = new Point();
			mid.X = nWidth/2;
			mid.Y = nHeight/2;

			double theta, radius;
			double newX, newY;

			for (int x = 0; x < nWidth; ++x)
				for (int y = 0; y < nHeight; ++y)
				{
					int trueX = x - mid.X;
					int trueY = y - mid.Y;
					theta = Math.Atan2((trueY),(trueX));

					radius = Math.Sqrt(trueX*trueX + trueY*trueY);

					newX = mid.X + (radius * Math.Cos(theta + fDegree * radius));
					if (newX > 0 && newX < nWidth)
					{
						fp[x, y].X = newX;
						pt[x, y].X = (int)newX;
					}
					else
						fp[x, y].X = pt[x, y].X = x;

					newY = mid.Y + (radius * Math.Sin(theta + fDegree * radius));
					if (newY > 0 && newY < nHeight)
					{
						fp[x, y].Y = newY;
						pt[x, y].Y = (int)newY;
					}
					else
						fp[x, y].Y = pt[x, y].Y = y;
				}

			if(bSmoothing)
				OffsetFilterAntiAlias(b, fp);
			else
				OffsetFilterAbs(b, pt);		

			return true;
		}

		public static bool Sphere(Bitmap b, bool bSmoothing)
		{
			int nWidth = b.Width;
			int nHeight = b.Height;

			FloatPoint [,] fp = new FloatPoint[nWidth, nHeight];
			Point [,] pt = new Point[nWidth, nHeight];

			Point mid = new Point();
			mid.X = nWidth/2;
			mid.Y = nHeight/2;

			double theta, radius;
			double newX, newY;

			for (int x = 0; x < nWidth; ++x)
				for (int y = 0; y < nHeight; ++y)
				{
					int trueX = x - mid.X;
					int trueY = y - mid.Y;
					theta = Math.Atan2((trueY),(trueX));

					radius = Math.Sqrt(trueX*trueX + trueY*trueY);

					double newRadius = radius * radius/(Math.Max(mid.X, mid.Y));

					newX = mid.X + (newRadius * Math.Cos(theta));

					if (newX > 0 && newX < nWidth)
					{
						fp[x, y].X = newX;
						pt[x, y].X = (int) newX;
					}
					else
					{
						fp[x, y].X = fp[x,y].Y = 0.0;
						pt[x, y].X = pt[x,y].Y = 0;
					}

					newY = mid.Y + (newRadius * Math.Sin(theta));

					if (newY > 0 && newY < nHeight && newX > 0 && newX < nWidth)
					{
						fp[x, y].Y = newY;
						pt[x, y].Y = (int) newY;
					}
					else
					{
						fp[x, y].X = fp[x,y].Y = 0.0;
						pt[x, y].X = pt[x,y].Y = 0;
					}
				}

			if(bSmoothing)
				OffsetFilterAbs(b, pt);
			else
				OffsetFilterAntiAlias(b, fp);
	
			return true;
		}

		public static bool TimeWarp(Bitmap b, Byte factor, bool bSmoothing)
		{
			int nWidth = b.Width;
			int nHeight = b.Height;

			FloatPoint [,] fp = new FloatPoint[nWidth, nHeight];
			Point [,] pt = new Point[nWidth, nHeight];

			Point mid = new Point();
			mid.X = nWidth/2;
			mid.Y = nHeight/2;

			double theta, radius;
			double newX, newY;

			for (int x = 0; x < nWidth; ++x)
				for (int y = 0; y < nHeight; ++y)
				{
					int trueX = x - mid.X;
					int trueY = y - mid.Y;
					theta = Math.Atan2((trueY),(trueX));

					radius = Math.Sqrt(trueX*trueX + trueY*trueY);

					double newRadius = Math.Sqrt(radius) * factor;

					newX = mid.X + (newRadius * Math.Cos(theta));
					if (newX > 0 && newX < nWidth)
					{
						fp[x, y].X = newX;
						pt[x, y].X = (int) newX;
					}
					else
					{
						fp[x, y].X = 0.0;
						pt[x, y].X = 0;
					}

					newY = mid.Y + (newRadius * Math.Sin(theta));
					if (newY > 0 && newY < nHeight)
					{
						fp[x, y].Y = newY;
						pt[x, y].Y = (int) newY;
					}
					else
					{
						fp[x, y].Y = 0.0;
						pt[x, y].Y = 0;
					}
				}

			if(bSmoothing)
				OffsetFilterAbs(b, pt);
			else
				OffsetFilterAntiAlias(b, fp);
	
			return true;
		}

		public static bool Moire(Bitmap b, double fDegree)
		{
			int nWidth = b.Width;
			int nHeight = b.Height;

			Point [,] pt = new Point[nWidth, nHeight];

			Point mid = new Point();
			mid.X = nWidth/2;
			mid.Y = nHeight/2;

			double theta, radius;
			int newX, newY;

			for (int x = 0; x < nWidth; ++x)
				for (int y = 0; y < nHeight; ++y)
				{
					int trueX = x - mid.X;
					int trueY = y - mid.Y;
					theta = Math.Atan2((trueX),(trueY));

					radius = Math.Sqrt(trueX*trueX + trueY*trueY);

					newX = (int)(radius * Math.Sin(theta + fDegree * radius));
					if (newX > 0 && newX < nWidth)
					{
						pt[x, y].X = (int) newX;
					}
					else
					{
						pt[x, y].X = 0;
					}

					newY = (int)(radius * Math.Sin(theta + fDegree * radius));
					if (newY > 0 && newY < nHeight)
					{
						pt[x, y].Y = (int) newY;
					}
					else
					{
						pt[x, y].Y = 0;
					}
				}

			OffsetFilterAbs(b, pt);

			return true;
		}

		public static bool Water(Bitmap b, short nWave, bool bSmoothing)
		{
			int nWidth = b.Width;
			int nHeight = b.Height;

			FloatPoint [,] fp = new FloatPoint[nWidth, nHeight];
			Point [,] pt = new Point[nWidth, nHeight];

			Point mid = new Point();
			mid.X = nWidth/2;
			mid.Y = nHeight/2;

			double newX, newY;
			double xo, yo;

			for (int x = 0; x < nWidth; ++x)
				for (int y = 0; y < nHeight; ++y)
				{
					xo = ((double)nWave * Math.Sin(2.0 * 3.1415 * (float)y / 128.0));
					yo = ((double)nWave * Math.Cos(2.0 * 3.1415 * (float)x / 128.0));

					newX = (x + xo);
					newY = (y + yo);

					if (newX > 0 && newX < nWidth)
					{
						fp[x, y].X = newX;
						pt[x, y].X = (int) newX;
					}
					else
					{
						fp[x, y].X = 0.0;
						pt[x, y].X = 0;
					}


					if (newY > 0 && newY < nHeight)
					{
						fp[x, y].Y = newY;
						pt[x, y].Y = (int) newY;
					}
					else
					{
						fp[x, y].Y = 0.0;
						pt[x, y].Y = 0;
					}
				}

			if(bSmoothing)
				OffsetFilterAbs(b, pt);
			else
				OffsetFilterAntiAlias(b, fp);
	
			return true;
		}

		public static bool Pixelate(Bitmap b, short pixel, bool bGrid)
		{
			int nWidth = b.Width;
			int nHeight = b.Height;

			Point [,] pt = new Point[nWidth, nHeight];

			int newX, newY;

			for (int x = 0; x < nWidth; ++x)
				for (int y = 0; y < nHeight; ++y)
				{
					newX = pixel - x%pixel;

					if (bGrid && newX == pixel)
						pt[x, y].X = -x;
					else if (x + newX > 0 && x +newX < nWidth)
						pt[x, y].X = newX;
					else
						pt[x, y].X = 0;

					newY = pixel - y%pixel;

					if (bGrid && newY == pixel)
						pt[x, y].Y = -y;
					else if (y + newY > 0 && y + newY < nHeight)
						pt[x, y].Y = newY;
					else
						pt[x, y].Y = 0;
				}

			OffsetFilter(b, pt);

			return true;
		}
	}
}

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.

This article is part of the series 'Image Processing for Dummies with C# and GDI+ View All

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)
Australia Australia
Programming computers ( self taught ) since about 1984 when I bought my first Apple ][. Was working on a GUI library to interface Win32 to Python, and writing graphics filters in my spare time, and then building n-tiered apps using asp, atl and asp.net in my job at Dytech. After 4 years there, I've started working from home, at first for Code Project and now for a vet telemedicine company. I owned part of a company that sells client education software in the vet market, but we sold that and I worked for the owners for five years before leaving to get away from the travel, and spend more time with my family. I now work for a company here in Hobart, doing all sorts of Microsoft based stuff in C++ and C#, with a lot of T-SQL in the mix.

Comments and Discussions