ASP.NET JPEG Library Example

The JPEGLIB has been downgraded to just image sizing while EXIF needs have been delegated to EXIFTOOL by Phil Harvey as this is much more reliable.

JPEGLIB now consists of this simple class and it actually supports more than JPEGs:

using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;

namespace prowaretech.com
{
	namespace JpegLib
	{
		public enum SaveImageAsType
		{
			JPEG,
			GIF,
			PNG,
			TIFF,
			WMF,
			ICON,
			BMP,
			EMF
		}
		public class ImageSizer
		{
			private int width;
			private int height;
			public ImageSizer(Stream fileStream)
			{
				Image iOriginal = new Bitmap(fileStream);
				width = iOriginal.Width;
				height = iOriginal.Height;
				iOriginal.Dispose();
			}
			public ImageSizer(string fileName)
			{
				Image iOriginal = new Bitmap(fileName);
				width = iOriginal.Width;
				height = iOriginal.Height;
				iOriginal.Dispose();
			}
			public int Width
			{
				get { return this.width; }
			}
			public int Height
			{
				get { return this.height; }
			}
		}
		public class ImageResizer
		{
			public void ResizeImage(string fileName, int newWidth, int newHeight, bool preserveImageRatio, string saveAsFileName, SaveImageAsType imageFormat)
			{
				Bitmap iOriginal = new Bitmap(fileName);

				if (preserveImageRatio)
				{
					float percentWidth = (float)newWidth / (float)iOriginal.Width;
					float percentHeight = (float)newHeight / (float)iOriginal.Height;
					float percent = percentHeight < percentWidth ? percentHeight : percentWidth;
					newWidth = (int)Math.Round(iOriginal.Width * percent, 0);
					newHeight = (int)Math.Round(iOriginal.Height * percent, 0);
				}
				resize(iOriginal, newWidth, newHeight, saveAsFileName, imageFormat);

				iOriginal.Dispose();
			}
			public void ResizeImage(Stream fileStream, int newWidth, int newHeight, bool preserveImageRatio, string saveAsFileName, SaveImageAsType imageFormat)
			{
				Bitmap iOriginal = new Bitmap(fileStream);

				if (preserveImageRatio)
				{
					float percentWidth = (float)newWidth / (float)iOriginal.Width;
					float percentHeight = (float)newHeight / (float)iOriginal.Height;
					float percent = percentHeight < percentWidth ? percentHeight : percentWidth;
					newWidth = (int)Math.Round(iOriginal.Width * percent, 0);
					newHeight = (int)Math.Round(iOriginal.Height * percent, 0);
				}
				resize(iOriginal, newWidth, newHeight, saveAsFileName, imageFormat);

				iOriginal.Dispose();
			}
			public void ResizeImage(string fileName, int newNumberOfPixels, string saveAsFileName, SaveImageAsType imageFormat)
			{
				Bitmap iOriginal = new Bitmap(fileName);

				double ratio = Math.Sqrt(newNumberOfPixels / (double)(iOriginal.Width * iOriginal.Height));
				resize(iOriginal, (int)Math.Round(iOriginal.Width * ratio, 0), (int)Math.Round(iOriginal.Height * ratio, 0), saveAsFileName, imageFormat);

				iOriginal.Dispose();
			}
			public void ResizeImage(Stream fileStream, int newNumberOfPixels, string saveAsFileName, SaveImageAsType imageFormat)
			{
				Bitmap iOriginal = new Bitmap(fileStream);

				double ratio = Math.Sqrt(newNumberOfPixels / (double)(iOriginal.Width * iOriginal.Height));
				resize(iOriginal, (int)Math.Round(iOriginal.Width * ratio, 0), (int)Math.Round(iOriginal.Height * ratio, 0), saveAsFileName, imageFormat);

				iOriginal.Dispose();
			}
			private void resize(Bitmap iOriginal, int newWidth, int newHeight, string saveAsFileName, SaveImageAsType imageType)
			{
				ImageFormat imageFormat;
				PixelFormat pixelFormat;
				switch (imageType)
				{
					case SaveImageAsType.BMP:
						imageFormat = ImageFormat.Bmp;
						pixelFormat = PixelFormat.Format24bppRgb;
						break;
					case SaveImageAsType.EMF:
						imageFormat = ImageFormat.Emf;
						pixelFormat = PixelFormat.Format24bppRgb;
						break;
					case SaveImageAsType.GIF:
						imageFormat = ImageFormat.Gif;
						pixelFormat = PixelFormat.Format8bppIndexed;
						break;
					case SaveImageAsType.ICON:
						imageFormat = ImageFormat.Icon;
						pixelFormat = PixelFormat.Format32bppArgb;
						break;
					case SaveImageAsType.JPEG:
						imageFormat = ImageFormat.Jpeg;
						pixelFormat = PixelFormat.Format24bppRgb;
						break;
					case SaveImageAsType.PNG:
						imageFormat = ImageFormat.Png;
						pixelFormat = PixelFormat.Format32bppArgb;
						break;
					case SaveImageAsType.TIFF:
						imageFormat = ImageFormat.Tiff;
						pixelFormat = PixelFormat.Format24bppRgb;
						break;
					case SaveImageAsType.WMF:
						imageFormat = ImageFormat.Wmf;
						pixelFormat = PixelFormat.Format24bppRgb;
						break;
					default:
						imageFormat = ImageFormat.Jpeg;
						pixelFormat = PixelFormat.Format24bppRgb;
						break;
				}
				Bitmap iSave = new Bitmap(newWidth, newHeight, pixelFormat);

				//Copy the original image over to the temp image
				Graphics gSave = Graphics.FromImage(iSave);
				gSave.DrawImage(iOriginal, 0, 0, newWidth, newHeight);
				gSave.Dispose();

				iSave.Save(saveAsFileName, imageFormat);
				iSave.Dispose();
			}
		}
	}
}

Using EXIFTOOL (See .NET processes for more information):

// this function returns all the exif data which is much faster than extracting the data one field at a time
string ExifExtractAllData(string imageFilePath)
{
	using (var proc = new System.Diagnostics.Process
	{
		StartInfo = new System.Diagnostics.ProcessStartInfo
		{
			FileName = "exiftool.exe", // make sure this path is found
			Arguments = '"' + imageFilePath + '"',
			UseShellExecute = false,
			RedirectStandardOutput = true,
			CreateNoWindow = true
		}
	})
	{
		proc.Start();
		if (!proc.StandardOutput.EndOfStream)
			return proc.StandardOutput.ReadToEnd();
	}
	return null;
}
// this parses the data returned by ExifExtractAllData()
// this returns an array of string values because "field" can be a wildcard,
// for example, every field beginning with Lens would be "Lens*"
public static string[] ExifExtractValue(string exifdata, string field)
{
	string ret = "\0";
	if (field.Length > 0 && field[0] == '-')
		field = field.Substring(1);
	bool multi = field.Contains('*');
	if (multi)
		field = field.Substring(0, field.IndexOf('*'));
	using (System.IO.StringReader reader = new System.IO.StringReader(exifdata))
	{
		while (true)
		{
			string line = reader.ReadLine();
			if (line == null)
				return ret.Split(new char[] { '\0' }, System.StringSplitOptions.RemoveEmptyEntries);
			int i = line.IndexOf(':');
			string f = line.Substring(0, i).Trim().Replace(" ", "");
			if (f == field || (multi && f.StartsWith(field)))
			{
				ret += (f + '|' + line.Substring(i + 1, line.Length - (i + 1)).Trim() + '\0');
				if (!multi)
					return ret.Split(new char[] { '\0' }, System.StringSplitOptions.RemoveEmptyEntries);
			}
		}
	}
}

DEPRECATED:

This library has three classes. One to determine the size of an image, one to resize an image without destroying the EXIF data and one to read the EXIF data from an image. It should work equally as well on other formats like TIFF, GIF, etc. If not wanting to compile this source, download the compiled library here: JPEGLIB.zip

The EXIF reader (ExifReader class) is modified code from "ExifLibrary for .NET" on codeproject.com. NOTE: The lastest version of this code is very fast and greatly simplified. Download the latest version of this code.

See this library in action:_exifview.aspx

<%@ Page Language="VB" %>
<script runat="server">
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs)
	If FileUpload1.HasFile Then
		Try
			Dim exif As New prowaretech.com.JpegLib.ExifReader(FileUpload1.FileContent)
			Dim s As System.Collections.DictionaryEntry
			For Each s In exif
				div1.InnerHtml &= s.Key & ": " & s.Value & "<br />"
			Next

			'Make sure directory has read/write permissions
			Dim strFileName As String = "C:\" & FileUpload1.FileName

			Dim gls As New prowaretech.com.JpegLib.ImageSizer(FileUpload1.FileContent)
			If gls.Width > 300 Or gls.Height > 300 Then
				Dim glr As New prowaretech.com.JpegLib.ImageResizer()
				glr.ResizeImage(FileUpload1.FileContent, 300, 300, True, True, strFileName)
			Else
				FileUpload1.SaveAs(strFileName)
			End If

			exif = New prowaretech.com.JpegLib.ExifReader(strFileName)
			div1.InnerHtml &= "<hr />"
			For Each s In exif
				div1.InnerHtml &= s.Key & ": " & s.Value.ToString() & "<br />"
			Next

		Catch ex As Exception
			Span1.InnerHtml = ex.Message.ToString()
		End Try
	Else
		Span1.InnerHtml = "You have not selected a file."
	End If
End Sub
</script>

<html>
	<head>
	<title></title>
	</head>
<body>

	<form id="form1" runat="server">
	<div>
	<asp:FileUpload ID="FileUpload1" runat="server" />
	<p><asp:Button ID="Button1" runat="server" Text="Upload File" OnClick="Button1_Click" /></p>
	<p><span style="color:Red;" id="Span1" runat="server"></span></p>
	</div>
	</form>
	<div id="div1" runat="server"></div>

</body>
</html>

Use csc.exe to compile this into an assembly and place in the website's bin folder. For example, change directory to

C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319

then execute

csc.exe /t:library /out:C:\JpegLib.dll /r:System.dll C:\JpegLib.cs

If not wanting to compile this source, download the compiled library here: JPEGLIB.zip

// JpegLib.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;

namespace prowaretech.com.Utils
{
	namespace JpegLib
	{
		public enum SaveImageAsType
		{
			JPEG,
			GIF,
			PNG,
			TIFF,
			WMF,
			ICON,
			BMP,
			EMF
		}
		public class ImageSizer
		{
			private int width;
			private int height;
			public ImageSizer(Stream fileStream)
			{
				Image iOriginal = new Bitmap(fileStream);
				width = iOriginal.Width;
				height = iOriginal.Height;
				iOriginal.Dispose();
			}
			public ImageSizer(string fileName)
			{
				Image iOriginal = new Bitmap(fileName);
				width = iOriginal.Width;
				height = iOriginal.Height;
				iOriginal.Dispose();
			}
			public int Width
			{
				get { return this.width; }
			}
			public int Height
			{
				get { return this.height; }
			}
		}
		public class ImageResizer
		{
			public void ResizeImage(string fileName, int newWidth, int newHeight, bool preserveImageRatio, bool preserveExifData, string saveAsFileName, SaveImageAsType imageFormat)
			{
				Bitmap iOriginal = new Bitmap(fileName);

				if (preserveImageRatio)
				{
					float percentWidth = (float)newWidth / (float)iOriginal.Width;
					float percentHeight = (float)newHeight / (float)iOriginal.Height;
					float percent = percentHeight < percentWidth ? percentHeight : percentWidth;
					newWidth = (int)Math.Round(iOriginal.Width * percent, 0);
					newHeight = (int)Math.Round(iOriginal.Height * percent, 0);
				}
				resize(iOriginal, newWidth, newHeight, preserveExifData, saveAsFileName, imageFormat);

				iOriginal.Dispose();
			}
			public void ResizeImage(Stream fileStream, int newWidth, int newHeight, bool preserveImageRatio, bool preserveExifData, string saveAsFileName, SaveImageAsType imageFormat)
			{
				Bitmap iOriginal = new Bitmap(fileStream);

				if (preserveImageRatio)
				{
					float percentWidth = (float)newWidth / (float)iOriginal.Width;
					float percentHeight = (float)newHeight / (float)iOriginal.Height;
					float percent = percentHeight < percentWidth ? percentHeight : percentWidth;
					newWidth = (int)Math.Round(iOriginal.Width * percent, 0);
					newHeight = (int)Math.Round(iOriginal.Height * percent, 0);
				}
				resize(iOriginal, newWidth, newHeight, preserveExifData, saveAsFileName, imageFormat);

				iOriginal.Dispose();
			}
			public void ResizeImage(string fileName, int newNumberOfPixels, bool preserveExifData, string saveAsFileName, SaveImageAsType imageFormat)
			{
				Bitmap iOriginal = new Bitmap(fileName);

				double ratio = Math.Sqrt(newNumberOfPixels / (double)(iOriginal.Width * iOriginal.Height));
				resize(iOriginal, (int)Math.Round(iOriginal.Width * ratio, 0), (int)Math.Round(iOriginal.Height * ratio, 0), preserveExifData, saveAsFileName, imageFormat);

				iOriginal.Dispose();
			}
			public void ResizeImage(Stream fileStream, int newNumberOfPixels, bool preserveExifData, string saveAsFileName, SaveImageAsType imageFormat)
			{
				Bitmap iOriginal = new Bitmap(fileStream);

				double ratio = Math.Sqrt(newNumberOfPixels / (double)(iOriginal.Width * iOriginal.Height));
				resize(iOriginal, (int)Math.Round(iOriginal.Width * ratio, 0), (int)Math.Round(iOriginal.Height * ratio, 0), preserveExifData, saveAsFileName, imageFormat);

				iOriginal.Dispose();
			}
			private void resize(Bitmap iOriginal, int newWidth, int newHeight, bool preserveExifData, string saveAsFileName, SaveImageAsType imageType)
			{
				ImageFormat imageFormat;
				PixelFormat pixelFormat;
				switch (imageType)
				{
					case SaveImageAsType.BMP:
						imageFormat = ImageFormat.Bmp;
						pixelFormat = PixelFormat.Format24bppRgb;
						break;
					case SaveImageAsType.EMF:
						imageFormat = ImageFormat.Emf;
						pixelFormat = PixelFormat.Format24bppRgb;
						break;
					case SaveImageAsType.GIF:
						imageFormat = ImageFormat.Gif;
						pixelFormat = PixelFormat.Format32bppArgb;
						break;
					case SaveImageAsType.ICON:
						imageFormat = ImageFormat.Icon;
						pixelFormat = PixelFormat.Format32bppArgb;
						break;
					case SaveImageAsType.JPEG:
						imageFormat = ImageFormat.Jpeg;
						pixelFormat = PixelFormat.Format24bppRgb;
						break;
					case SaveImageAsType.PNG:
						imageFormat = ImageFormat.Png;
						pixelFormat = PixelFormat.Format32bppArgb;
						break;
					case SaveImageAsType.TIFF:
						imageFormat = ImageFormat.Tiff;
						pixelFormat = PixelFormat.Format24bppRgb;
						break;
					case SaveImageAsType.WMF:
						imageFormat = ImageFormat.Wmf;
						pixelFormat = PixelFormat.Format24bppRgb;
						break;
					default:
						imageFormat = ImageFormat.Jpeg;
						pixelFormat = PixelFormat.Format24bppRgb;
						break;
				}
				Bitmap iSave = new Bitmap(newWidth, newHeight, pixelFormat);

				//Copy the original image over to the temp image
				Graphics gSave = Graphics.FromImage(iSave);
				gSave.DrawImage(iOriginal, 0, 0, newWidth, newHeight);
				gSave.Dispose();

				//Copy the original EXIF properties to the new image
				if (preserveExifData)
				{
					string tempPath = System.IO.Path.GetTempPath();
					string origFile = System.Guid.NewGuid().ToString();
					string saveFile = System.Guid.NewGuid().ToString();

					iSave.Save(tempPath + saveFile, imageFormat);
					iSave.Dispose();
					ExifFile efSave = new ExifFile(tempPath + saveFile);
					System.IO.File.Delete(tempPath + saveFile);

					iOriginal.Save(tempPath + origFile, imageFormat);
					ExifFile efOrig = new ExifFile(tempPath + origFile);
					System.IO.File.Delete(tempPath + origFile);

					efSave.SaveAs(saveAsFileName, efOrig);
				}
				else
				{
					iSave.Save(saveAsFileName, imageFormat);
					iSave.Dispose();
				}
			}
		}
		public class ExifReader : Hashtable
		{
			public ExifReader(string fileName)
			{
				ExifFile data = new ExifFile(fileName);
				build(data);
			}
			public ExifReader(Stream fileStream)
			{
				ExifFile data = new ExifFile(fileStream);
				build(data);
			}
			private void build(ExifFile data)
			{
				foreach (ExifProperty item in data.Properties.Values)
				{
					if (item.Tag == ExifTag.ShutterSpeedValue)
						this.Add(item.Name, Math.Pow(2, ((MathEx.Fraction32)item.Value).ToDouble()));
					else if (item.Tag == ExifTag.FNumber)
						this.Add(item.Name, ((MathEx.UFraction32)item.Value).ToDouble());
					else if (item.Tag == ExifTag.ApertureValue || item.Tag == ExifTag.MaxApertureValue)
						this.Add(item.Name, Math.Pow(Math.Sqrt(2), ((MathEx.UFraction32)item.Value).ToDouble()));
					else if (item.Tag == ExifTag.ExposureTime || item.Tag == ExifTag.FocalLength)
						this.Add(item.Name, ((MathEx.UFraction32)item.Value).ToDouble());
					else if (item.Tag == ExifTag.ExposureBiasValue)
						this.Add(item.Name, ((MathEx.Fraction32)item.Value).ToDouble());
					else if (item.Tag == ExifTag.MakerNote)
						this.Add(item.Name, ExifBitConverter.ToAscii((byte[])item.Value));
					else if (item.Tag == ExifTag.GPSLongitude || item.Tag == ExifTag.GPSLatitude || item.Tag == ExifTag.GPSDestLatitude || item.Tag == ExifTag.GPSDestLongitude)
					{
						MathEx.UFraction32[] array = (MathEx.UFraction32[])item.Value;
						string str = "";
						for (int i = 0; i < array.Length; i++)
							str += (str.Length == 0 ? "" : " ") + array[i].ToDouble().ToString();
						this.Add(item.Name, str);
					}
					else if (item.Tag == ExifTag.GPSTimeStamp)
					{
						MathEx.UFraction32[] array = (MathEx.UFraction32[])item.Value;
						string str = "";
						for (int i = 0; i < array.Length; i++)
							str += (str.Length == 0 ? "" : ":") + array[i].ToDouble().ToString();
						this.Add(item.Name, str);
					}
					else
					{
						if (item.Value is Byte[])
							this.Add(item.Name, ExifBitConverter.ToAscii((byte[])item.Value));
						else if (item.Value is MathEx.UFraction32[])
						{
							MathEx.UFraction32[] array = (MathEx.UFraction32[])item.Value;
							string str = "";
							for (int i = 0; i < array.Length; i++)
								str += (str.Length == 0 ? "" : " ") + array[i].ToDouble().ToString();
							this.Add(item.Name, str);
						}
						else if (item.Value is MathEx.Fraction32[])
						{
							MathEx.Fraction32[] array = (MathEx.Fraction32[])item.Value;
							string str = "";
							for (int i = 0; i < array.Length; i++)
								str += (str.Length == 0 ? "" : " ") + array[i].ToDouble().ToString();
							this.Add(item.Name, str);
						}
						else
							this.Add(item.Name, item.Value);
					}
				}
			}
		}
		/// <summary>
		/// Reads and writes Exif data contained in a JPEG/Exif file.
		/// </summary>
		internal class ExifFile
		{
			#region "Member Variables"
			private JPEGFile jpegfile;
			private JPEGSection jpegsection_app1;
			private uint makerNoteOffset;
			private long exifIFDFieldOffset, gpsIFDFieldOffset, interopIFDFieldOffset, firstIFDFieldOffset;
			private long thumbOffsetLocation, thumbSizeLocation;
			private uint thumbOffsetValue, thumbSizeValue;
			private bool makerNoteProcessed;
			#endregion

			#region "Properties"
			public JPEGFile JpegFile
			{
				get { return jpegfile; }
			}
			/// <summary>
			/// Gets the collection of Exif properties contained in the JPEG file.
			/// </summary>
			public Dictionary<ExifTag, ExifProperty> Properties { get; private set; }
			/// <summary>
			/// Gets or sets the byte-order of the Exif properties.
			/// </summary>
			public BitConverterEx.ByteOrder ByteOrder { get; set; }
			/// <summary>
			/// Gets or sets the embedded thumbnail image.
			/// </summary>
			public JPEGFile Thumbnail { get; set; }
			/// <summary>
			/// Gets or sets the Exif property with the given key.
			/// </summary>
			/// <param name="key">The Exif tag associated with the Exif property.</param>
			/// <returns></returns>
			public ExifProperty this[ExifTag key]
			{
				get
				{
					return Properties[key];
				}
				set
				{
					Properties[key] = value;
				}
			}
			#endregion

			#region "Constructors"
			/// <summary>
			/// Creates a new ExifFile from the given JPEG/Exif image file.
			/// </summary>
			/// <param name="filename">Path to the JPEJ/Exif image file.</param>
			/// <returns>An ExifFile class initialized from the specified JPEG/Exif image file.</returns>
			public ExifFile(Stream fileStream)
			{
				// Read the JPEG file and process the APP1 section
				this.Properties = new Dictionary<ExifTag, ExifProperty>();
				this.jpegfile = new JPEGFile(fileStream);
				this.ReadAPP1();

				// Process the maker note
				this.makerNoteProcessed = false;
			}
			public ExifFile(string fileName)
			{
				// Read the JPEG file and process the APP1 section
				this.Properties = new Dictionary<ExifTag, ExifProperty>();
				this.jpegfile = new JPEGFile(fileName);
				this.ReadAPP1();

				// Process the maker note
				this.makerNoteProcessed = false;
			}
			#endregion

			#region "Instance Methods"
			/// <summary>
			/// Saves the JPEG/Exif image with the given filename.
			/// </summary>
			/// <param name="filename">The complete path to the JPEG/Exif file.</param>
			public void SaveAs(string filename, ExifFile exifFileToCopy)
			{
				SaveAs(filename, false, exifFileToCopy);
			}

			/// <summary>
			/// Saves the JPEG/Exif image with the given filename.
			/// </summary>
			/// <param name="filename">The complete path to the JPEG/Exif file.</param>
			/// <param name="preserveMakerNote">Determines whether the maker note offset of
			/// the original file will be preserved.</param>
			public void SaveAs(string filename, bool preserveMakerNote, ExifFile exifFileToCopy)
			{
				WriteApp1(preserveMakerNote, exifFileToCopy);
				jpegfile.SaveAs(filename, exifFileToCopy);
			}

			/// <summary>
			/// Returns a System.Drawing.Bitmap created with image data.
			/// </summary>
			public Image ToBitmap()
			{
				return jpegfile.ToBitmap();
			}
			#endregion

			#region "Private Helper Methods"
			/// <summary>
			/// Reads the APP1 section containing Exif metadata.
			/// </summary>
			private void ReadAPP1()
			{
				// Find the APP1 section containing Exif metadata
				jpegsection_app1 = jpegfile.Sections.Find(a => (a.Marker == JPEGMarker.APP1) && (Encoding.ASCII.GetString(a.Header, 0, 6) == "Exif\0\0"));

				// If there is no APP1 section, add a new one after the last APP0 section (if any).
				if (jpegsection_app1 == null)
				{
					int insertionIndex = jpegfile.Sections.FindLastIndex(a => a.Marker == JPEGMarker.APP0);
					if (insertionIndex == -1) insertionIndex = 0;
					insertionIndex++;
					ByteOrder = BitConverterEx.ByteOrder.BigEndian;
					jpegsection_app1 = new JPEGSection(JPEGMarker.APP1);
					jpegfile.Sections.Insert(insertionIndex, jpegsection_app1);
					return;
				}

				byte[] header = jpegsection_app1.Header;
				SortedList<int, IFD> ifdqueue = new SortedList<int, IFD>();
				makerNoteOffset = 0;

				// TIFF header
				int tiffoffset = 6;
				if (header[tiffoffset] == 0x49)
					ByteOrder = BitConverterEx.ByteOrder.LittleEndian;
				else
					ByteOrder = BitConverterEx.ByteOrder.BigEndian;
				BitConverterEx conv = new BitConverterEx(ByteOrder, BitConverterEx.ByteOrder.System);

				// Offset to 0th IFD
				int ifd0offset = (int)conv.ToUInt32(header, tiffoffset + 4);
				ifdqueue.Add(ifd0offset, IFD.Zeroth);

				int thumboffset = -1;
				int thumblength = 0;
				int thumbtype = -1;
				// Read IFDs
				while (ifdqueue.Count != 0)
				{
					int ifdoffset = tiffoffset + ifdqueue.Keys[0];
					IFD currentifd = ifdqueue.Values[0];
					ifdqueue.RemoveAt(0);

					// Field count
					ushort fieldcount = conv.ToUInt16(header, ifdoffset);
					for (short i = 0; i < fieldcount; i++)
					{
						// Read field info
						int fieldoffset = ifdoffset + 2 + 12 * i;
						ushort tag = conv.ToUInt16(header, fieldoffset);
						ushort type = conv.ToUInt16(header, fieldoffset + 2);
						uint count = conv.ToUInt32(header, fieldoffset + 4);
						byte[] value = new byte[4];
						Array.Copy(header, fieldoffset + 8, value, 0, 4);

						// Fields containing offsets to other IFDs
						if (currentifd == IFD.Zeroth && tag == 0x8769)
						{
							int exififdpointer = (int)conv.ToUInt32(value, 0);
							ifdqueue.Add(exififdpointer, IFD.EXIF);
						}
						else if (currentifd == IFD.Zeroth && tag == 0x8825)
						{
							int gpsifdpointer = (int)conv.ToUInt32(value, 0);
							ifdqueue.Add(gpsifdpointer, IFD.GPS);
						}
						else if (currentifd == IFD.EXIF && tag == 0xa005)
						{
							int interopifdpointer = (int)conv.ToUInt32(value, 0);
							ifdqueue.Add(interopifdpointer, IFD.Interop);
						}

						// Save the offset to maker note data
						if (currentifd == IFD.EXIF && tag == 37500)
							makerNoteOffset = conv.ToUInt32(value, 0);

						// Calculate the bytes we need to read
						uint baselength = 0;
						if (type == 1 || type == 2 || type == 7)
							baselength = 1;
						else if (type == 3)
							baselength = 2;
						else if (type == 4 || type == 9)
							baselength = 4;
						else if (type == 5 || type == 10)
							baselength = 8;
						uint totallength = count * baselength;

						// If field value does not fit in 4 bytes
						// the value field is an offset to the actual
						// field value
						int fieldposition = 0;
						if (totallength > 4)
						{
							fieldposition = tiffoffset + (int)conv.ToUInt32(value, 0);
							value = new byte[totallength];
							Array.Copy(header, fieldposition, value, 0, totallength);
						}

						// Compressed thumbnail data
						if (currentifd == IFD.First && tag == 0x201)
						{
							thumbtype = 0;
							thumboffset = (int)conv.ToUInt32(value, 0);
						}
						else if (currentifd == IFD.First && tag == 0x202)
							thumblength = (int)conv.ToUInt32(value, 0);

						// Uncompressed thumbnail data
						if (currentifd == IFD.First && tag == 0x111)
						{
							thumbtype = 1;
							// Offset to first strip
							if (type == 3)
								thumboffset = (int)conv.ToUInt16(value, 0);
							else
								thumboffset = (int)conv.ToUInt32(value, 0);
						}
						else if (currentifd == IFD.First && tag == 0x117)
						{
							thumblength = 0;
							for (int j = 0; j < count; j++)
							{
								if (type == 3)
									thumblength += (int)conv.ToUInt16(value, 0);
								else
									thumblength += (int)conv.ToUInt32(value, 0);
							}
						}

						// Create the exif property from the interop data
						ExifProperty prop = ExifPropertyFactory.Get(tag, type, count, value, ByteOrder, currentifd);
						Properties.Add(prop.Tag, prop);
					}

					// 1st IFD pointer
					int firstifdpointer = (int)conv.ToUInt32(header, ifdoffset + 2 + 12 * fieldcount);
					if (firstifdpointer != 0)
						ifdqueue.Add(firstifdpointer, IFD.First);
					// Read thumbnail
					if (thumboffset != -1 && thumblength != 0 && Thumbnail == null)
					{
						if (thumbtype == 0)
						{
							using (MemoryStream ts = new MemoryStream(header, tiffoffset + thumboffset, thumblength))
							{
								Thumbnail = new JPEGFile(ts);
							}
						}
					}
				}
			}

			/// <summary>
			/// Replaces the contents of the APP1 section with the Exif properties.
			/// </summary>
			private void WriteApp1(bool preserveMakerNote, ExifFile exifFileToCopy)
			{
				// Zero out IFD field offsets. We will fill those as we write the IFD sections
				exifIFDFieldOffset = 0;
				gpsIFDFieldOffset = 0;
				interopIFDFieldOffset = 0;
				firstIFDFieldOffset = 0;
				// We also do not know the location of the embedded thumbnail yet
				thumbOffsetLocation = 0;
				thumbOffsetValue = 0;
				thumbSizeLocation = 0;
				thumbSizeValue = 0;

				// Which IFD sections do we have?
				Dictionary<ExifTag, ExifProperty> ifdzeroth = new Dictionary<ExifTag, ExifProperty>();
				Dictionary<ExifTag, ExifProperty> ifdexif = new Dictionary<ExifTag, ExifProperty>();
				Dictionary<ExifTag, ExifProperty> ifdgps = new Dictionary<ExifTag, ExifProperty>();
				Dictionary<ExifTag, ExifProperty> ifdinterop = new Dictionary<ExifTag, ExifProperty>();
				Dictionary<ExifTag, ExifProperty> ifdfirst = new Dictionary<ExifTag, ExifProperty>();

				Dictionary<ExifTag, ExifProperty> props = (exifFileToCopy == null) ? Properties : exifFileToCopy.Properties;
				foreach (KeyValuePair<ExifTag, ExifProperty> pair in props)
				{
					switch (pair.Value.IFD)
					{
						case IFD.Zeroth:
							ifdzeroth.Add(pair.Key, pair.Value);
							break;
						case IFD.EXIF:
							ifdexif.Add(pair.Key, pair.Value);
							break;
						case IFD.GPS:
							ifdgps.Add(pair.Key, pair.Value);
							break;
						case IFD.Interop:
							ifdinterop.Add(pair.Key, pair.Value);
							break;
						case IFD.First:
							ifdfirst.Add(pair.Key, pair.Value);
							break;
					}
				}

				// Add IFD pointers if they are missing
				// We will write the pointer values later on
				if (ifdexif.Count != 0 && !ifdzeroth.ContainsKey(ExifTag.EXIFIFDPointer))
					ifdzeroth.Add(ExifTag.EXIFIFDPointer, new ExifUInt(ExifTag.EXIFIFDPointer, 0));
				if (ifdgps.Count != 0 && !ifdzeroth.ContainsKey(ExifTag.GPSIFDPointer))
					ifdzeroth.Add(ExifTag.GPSIFDPointer, new ExifUInt(ExifTag.GPSIFDPointer, 0));
				if (ifdinterop.Count != 0 && !ifdexif.ContainsKey(ExifTag.InteroperabilityIFDPointer))
					ifdexif.Add(ExifTag.InteroperabilityIFDPointer, new ExifUInt(ExifTag.InteroperabilityIFDPointer, 0));

				// Remove IFD pointers if IFD sections are missing
				if (ifdexif.Count == 0 && ifdzeroth.ContainsKey(ExifTag.EXIFIFDPointer))
					ifdzeroth.Remove(ExifTag.EXIFIFDPointer);
				if (ifdgps.Count == 0 && ifdzeroth.ContainsKey(ExifTag.GPSIFDPointer))
					ifdzeroth.Remove(ExifTag.GPSIFDPointer);
				if (ifdinterop.Count == 0 && ifdexif.ContainsKey(ExifTag.InteroperabilityIFDPointer))
					ifdexif.Remove(ExifTag.InteroperabilityIFDPointer);

				if (ifdzeroth.Count == 0)
					throw new IFD0IsEmptyException();

				// We will need these bitconverters to write byte-ordered data
				BitConverterEx bceJPEG = new BitConverterEx(BitConverterEx.ByteOrder.System, BitConverterEx.ByteOrder.BigEndian);
				BitConverterEx bceExif = new BitConverterEx(BitConverterEx.ByteOrder.System, ByteOrder);

				// Create a memory stream to write the APP1 section to
				MemoryStream ms = new MemoryStream();

				// Exif identifer
				ms.Write(Encoding.ASCII.GetBytes("Exif\0\0"), 0, 6);

				// TIFF header
				// Byte order
				long tiffoffset = ms.Position;
				ms.Write((ByteOrder == BitConverterEx.ByteOrder.LittleEndian ? new byte[] { 0x49, 0x49 } : new byte[] { 0x4D, 0x4D }), 0, 2);
				// TIFF ID
				ms.Write(bceExif.GetBytes((ushort)42), 0, 2);
				// Offset to 0th IFD
				ms.Write(bceExif.GetBytes((uint)8), 0, 4);

				// Write IFDs
				WriteIFD(ms, ifdzeroth, IFD.Zeroth, tiffoffset, preserveMakerNote);
				uint exififdrelativeoffset = (uint)(ms.Position - tiffoffset);
				WriteIFD(ms, ifdexif, IFD.EXIF, tiffoffset, preserveMakerNote);
				uint gpsifdrelativeoffset = (uint)(ms.Position - tiffoffset);
				WriteIFD(ms, ifdgps, IFD.GPS, tiffoffset, preserveMakerNote);
				uint interopifdrelativeoffset = (uint)(ms.Position - tiffoffset);
				WriteIFD(ms, ifdinterop, IFD.Interop, tiffoffset, preserveMakerNote);
				uint firstifdrelativeoffset = (uint)(ms.Position - tiffoffset);
				WriteIFD(ms, ifdfirst, IFD.First, tiffoffset, preserveMakerNote);

				// Now that we know the location of IFDs we can go back and write IFD offsets
				if (exifIFDFieldOffset != 0)
				{
					ms.Seek(exifIFDFieldOffset, SeekOrigin.Begin);
					ms.Write(bceExif.GetBytes(exififdrelativeoffset), 0, 4);
				}
				if (gpsIFDFieldOffset != 0)
				{
					ms.Seek(gpsIFDFieldOffset, SeekOrigin.Begin);
					ms.Write(bceExif.GetBytes(gpsifdrelativeoffset), 0, 4);
				}
				if (interopIFDFieldOffset != 0)
				{
					ms.Seek(interopIFDFieldOffset, SeekOrigin.Begin);
					ms.Write(bceExif.GetBytes(interopifdrelativeoffset), 0, 4);
				}
				if (firstIFDFieldOffset != 0)
				{
					ms.Seek(firstIFDFieldOffset, SeekOrigin.Begin);
					ms.Write(bceExif.GetBytes(firstifdrelativeoffset), 0, 4);
				}
				// We can write thumbnail location now
				if (thumbOffsetLocation != 0)
				{
					ms.Seek(thumbOffsetLocation, SeekOrigin.Begin);
					ms.Write(bceExif.GetBytes(thumbOffsetValue), 0, 4);
				}
				if (thumbSizeLocation != 0)
				{
					ms.Seek(thumbSizeLocation, SeekOrigin.Begin);
					ms.Write(bceExif.GetBytes(thumbSizeValue), 0, 4);
				}

				ms.Close();

				// Return APP1 header
				jpegsection_app1.Header = ms.ToArray();
			}

			private void WriteIFD(MemoryStream stream, Dictionary<ExifTag, ExifProperty> ifd, IFD ifdtype, long tiffoffset, bool preserveMakerNote)
			{
				BitConverterEx conv = new BitConverterEx(BitConverterEx.ByteOrder.System, ByteOrder);

				// Create a queue of fields to write
				Queue<ExifProperty> fieldqueue = new Queue<ExifProperty>();
				foreach (ExifProperty prop in ifd.Values)
					if (prop.Tag != ExifTag.MakerNote)
						fieldqueue.Enqueue(prop);
				// Push the maker note data to the end
				if (ifd.ContainsKey(ExifTag.MakerNote))
					fieldqueue.Enqueue(ifd[ExifTag.MakerNote]);

				// Offset to start of field data from start of TIFF header
				uint dataoffset = (uint)(2 + ifd.Count * 12 + 4 + stream.Position - tiffoffset);
				uint currentdataoffset = dataoffset;
				long absolutedataoffset = stream.Position + (2 + ifd.Count * 12 + 4);

				bool makernotewritten = false;
				// Field count
				stream.Write(conv.GetBytes((ushort)ifd.Count), 0, 2);
				// Fields
				while (fieldqueue.Count != 0)
				{
					ExifProperty field = fieldqueue.Dequeue();
					ExifInterOperability interop = field.Interoperability;

					uint fillerbytecount = 0;

					// Try to preserve the makernote data offset
					if (!makernotewritten &&
						!makerNoteProcessed &&
						makerNoteOffset != 0 &&
						ifdtype == IFD.EXIF &&
						field.Tag != ExifTag.MakerNote &&
						interop.Data.Length > 4 &&
						currentdataoffset + interop.Data.Length > makerNoteOffset &&
						ifd.ContainsKey(ExifTag.MakerNote))
					{
						// Delay writing this field until we write makernote data
						fieldqueue.Enqueue(field);
						continue;
					}
					else if (field.Tag == ExifTag.MakerNote)
					{
						makernotewritten = true;
						// We may need to write filler bytes to preserve maker note offset
						if (preserveMakerNote && !makerNoteProcessed)
							fillerbytecount = makerNoteOffset - currentdataoffset;
						else
							fillerbytecount = 0;
					}

					// Tag
					stream.Write(conv.GetBytes(interop.TagID), 0, 2);
					// Type
					stream.Write(conv.GetBytes(interop.TypeID), 0, 2);
					// Count
					stream.Write(conv.GetBytes(interop.Count), 0, 4);
					// Field data
					byte[] data = interop.Data;
					if (ByteOrder != BitConverterEx.SystemByteOrder)
					{
						if (interop.TypeID == 1 || interop.TypeID == 3 || interop.TypeID == 4 || interop.TypeID == 9)
							Array.Reverse(data);
						else if (interop.TypeID == 5 || interop.TypeID == 10)
						{
							Array.Reverse(data, 0, 4);
							Array.Reverse(data, 4, 4);
						}
					}

					// Fields containing offsets to other IFDs
					// Just store their offets, we will write the values later on when we know the lengths of IFDs
					if (ifdtype == IFD.Zeroth && interop.TagID == 0x8769)
						exifIFDFieldOffset = stream.Position;
					else if (ifdtype == IFD.Zeroth && interop.TagID == 0x8825)
						gpsIFDFieldOffset = stream.Position;
					else if (ifdtype == IFD.EXIF && interop.TagID == 0xa005)
						interopIFDFieldOffset = stream.Position;
					else if (ifdtype == IFD.First && interop.TagID == 0x201)
						thumbOffsetLocation = stream.Position;
					else if (ifdtype == IFD.First && interop.TagID == 0x202)
						thumbSizeLocation = stream.Position;

					// Write 4 byte field value or field data
					if (data.Length <= 4)
					{
						stream.Write(data, 0, data.Length);
						for (int i = data.Length; i < 4; i++)
							stream.WriteByte(0);
					}
					else
					{
						// Pointer to data area relative to TIFF header
						stream.Write(conv.GetBytes(currentdataoffset + fillerbytecount), 0, 4);
						// Actual data
						long currentoffset = stream.Position;
						stream.Seek(absolutedataoffset, SeekOrigin.Begin);
						// Write filler bytes
						for (int i = 0; i < fillerbytecount; i++)
							stream.WriteByte(0xFF);
						stream.Write(data, 0, data.Length);
						stream.Seek(currentoffset, SeekOrigin.Begin);
						// Increment pointers
						currentdataoffset += fillerbytecount + (uint)data.Length;
						absolutedataoffset += fillerbytecount + data.Length;
					}
				}
				// Offset to 1st IFD
				// We will write zeros for now. This will be filled after we write all IFDs
				if (ifdtype == IFD.Zeroth)
					firstIFDFieldOffset = stream.Position;
				stream.Write(new byte[] { 0, 0, 0, 0 }, 0, 4);

				// Seek to end of IFD
				stream.Seek(absolutedataoffset, SeekOrigin.Begin);

				// Write thumbnail data
				if (ifdtype == IFD.First)
				{
					if (Thumbnail != null)
					{
						MemoryStream ts = new MemoryStream();
						Thumbnail.Save(ts, null);
						ts.Close();
						byte[] thumb = ts.ToArray();
						thumbOffsetValue = (uint)(stream.Position - tiffoffset);
						thumbSizeValue = (uint)thumb.Length;
						stream.Write(thumb, 0, thumb.Length);
						ts.Dispose();
					}
					else
					{
						thumbOffsetValue = 0;
						thumbSizeValue = 0;
					}
				}
			}
			#endregion

		}
		/// <summary>
		/// Contains extended Math functions.
		/// </summary>
		internal static class MathEx
		{
			/// <summary>
			/// Returns the greatest common divisor of two numbers.
			/// </summary>
			public static uint GCD(uint a, uint b)
			{
				while (b != 0)
				{
					uint rem = a % b;
					a = b;
					b = rem;
				}

				return a;
			}

			/// <summary>
			/// Represents a generic rational number represented by 32-bit signed numerator and denominator.
			/// </summary>
			public struct Fraction32 : IComparable, IFormattable, IComparable<Fraction32>, IEquatable<Fraction32>
			{
				#region Member Variables
				private bool mIsNegative;
				private int mNumerator;
				private int mDenominator;
				#endregion

				#region Constants
				private const int DefaultPrecision = 8;
				#endregion

				#region Properties
				/// <summary>
				/// Gets or sets the numerator.
				/// </summary>
				public int Numerator
				{
					get
					{
						return (mIsNegative ? -1 : 1) * mNumerator;
					}
					set
					{
						if (value < 0)
						{
							mIsNegative = true;
							mNumerator = -1 * value;
						}
						else
						{
							mIsNegative = false;
							mNumerator = value;
						}
						Reduce(ref mNumerator, ref mDenominator);
					}
				}
				/// <summary>
				/// Gets or sets the denominator.
				/// </summary>
				public int Denominator
				{
					get
					{
						return ((int)mDenominator);
					}
					set
					{
						mDenominator = System.Math.Abs(value);
						Reduce(ref mNumerator, ref mDenominator);
					}
				}

				/// <summary>
				/// Gets or sets a value determining id the fraction is a negative value.
				/// </summary>
				public bool IsNegative
				{
					get
					{
						return mIsNegative;
					}
					set
					{
						mIsNegative = value;
					}
				}
				#endregion

				#region Predefined Values
				public static readonly Fraction32 NaN = new Fraction32(0, 0);
				public static readonly Fraction32 NegativeInfinity = new Fraction32(-1, 0);
				public static readonly Fraction32 PositiveInfinity = new Fraction32(1, 0);
				#endregion

				#region Static Methods
				/// <summary>
				/// Returns a value indicating whether the specified number evaluates to a value
				/// that is not a number.
				/// </summary>
				/// <param name="f">A fraction.</param>
				/// <returns>true if f evaluates to Fraction.NaN; otherwise, false.</returns>
				public static bool IsNan(Fraction32 f)
				{
					return (f.Numerator == 0 && f.Denominator == 0);
				}
				/// <summary>
				/// Returns a value indicating whether the specified number evaluates to negative
				/// infinity.
				/// </summary>
				/// <param name="f">A fraction.</param>
				/// <returns>true if f evaluates to Fraction.NegativeInfinity; otherwise, false.</returns>
				public static bool IsNegativeInfinity(Fraction32 f)
				{
					return (f.Numerator < 0 && f.Denominator == 0);
				}
				/// <summary>
				/// Returns a value indicating whether the specified number evaluates to positive
				/// infinity.
				/// </summary>
				/// <param name="f">A fraction.</param>
				/// <returns>true if f evaluates to Fraction.PositiveInfinity; otherwise, false.</returns>
				public static bool IsPositiveInfinity(Fraction32 f)
				{
					return (f.Numerator > 0 && f.Denominator == 0);
				}
				/// <summary>
				/// Returns a value indicating whether the specified number evaluates to negative
				/// or positive infinity.
				/// </summary>
				/// <param name="f">A fraction.</param>
				/// <returns>true if f evaluates to Fraction.NegativeInfinity or Fraction.PositiveInfinity; otherwise, false.</returns>
				public static bool IsInfinity(Fraction32 f)
				{
					return (f.Denominator == 0);
				}
				/// <summary>
				/// Returns the multiplicative inverse of a given value.
				/// </summary>
				/// <param name="f">A fraction.</param>
				/// <returns>Multiplicative inverse of f.</returns>
				public static Fraction32 Inverse(Fraction32 f)
				{
					return new Fraction32(f.Denominator, f.Numerator);
				}

				/// <summary>
				/// Converts the string representation of a fraction to a fraction object.
				/// </summary>
				/// <param name="s">A string formatted as numerator/denominator</param>
				/// <returns>A fraction object converted from s.</returns>
				/// <exception cref="System.ArgumentNullException">s is null</exception>
				/// <exception cref="System.FormatException">s is not in the correct format</exception>
				/// <exception cref="System.OverflowException">
				/// s represents a number less than System.UInt32.MinValue or greater than 
				/// System.UInt32.MaxValue.
				/// </exception>
				public static Fraction32 Parse(string s)
				{
					return FromString(s);
				}

				/// <summary>
				/// Converts the string representation of a fraction to a fraction object.
				/// A return value indicates whether the conversion succeeded.
				/// </summary>
				/// <param name="s">A string formatted as numerator/denominator</param>
				/// <returns>true if s was converted successfully; otherwise, false.</returns>
				public static bool TryParse(string s, out Fraction32 f)
				{
					try
					{
						f = Parse(s);
						return true;
					}
					catch
					{
						f = new Fraction32();
						return false;
					}
				}
				#endregion

				#region Operators
				#region Arithmetic Operators
				// Multiplication
				public static Fraction32 operator *(Fraction32 f, int n)
				{
					return new Fraction32(f.Numerator * n, f.Denominator * System.Math.Abs(n));
				}
				public static Fraction32 operator *(int n, Fraction32 f)
				{
					return f * n;
				}
				public static Fraction32 operator *(Fraction32 f, float n)
				{
					return new Fraction32(((float)f) * n);
				}
				public static Fraction32 operator *(float n, Fraction32 f)
				{
					return f * n;
				}
				public static Fraction32 operator *(Fraction32 f, double n)
				{
					return new Fraction32(((double)f) * n);
				}
				public static Fraction32 operator *(double n, Fraction32 f)
				{
					return f * n;
				}
				public static Fraction32 operator *(Fraction32 f1, Fraction32 f2)
				{
					return new Fraction32(f1.Numerator * f2.Numerator, f1.Denominator * f2.Denominator);
				}
				// Divison
				public static Fraction32 operator /(Fraction32 f, int n)
				{
					return new Fraction32(f.Numerator / n, f.Denominator / System.Math.Abs(n));
				}
				public static Fraction32 operator /(Fraction32 f, float n)
				{
					return new Fraction32(((float)f) / n);
				}
				public static Fraction32 operator /(Fraction32 f, double n)
				{
					return new Fraction32(((double)f) / n);
				}
				public static Fraction32 operator /(Fraction32 f1, Fraction32 f2)
				{
					return f1 * Inverse(f2);
				}
				// Addition
				public static Fraction32 operator +(Fraction32 f, int n)
				{
					return f + new Fraction32(n, 1);
				}
				public static Fraction32 operator +(int n, Fraction32 f)
				{
					return f + n;
				}
				public static Fraction32 operator +(Fraction32 f, float n)
				{
					return new Fraction32(((float)f) + n);
				}
				public static Fraction32 operator +(float n, Fraction32 f)
				{
					return f + n;
				}
				public static Fraction32 operator +(Fraction32 f, double n)
				{
					return new Fraction32(((double)f) + n);
				}
				public static Fraction32 operator +(double n, Fraction32 f)
				{
					return f + n;
				}
				public static Fraction32 operator +(Fraction32 f1, Fraction32 f2)
				{
					int n1 = f1.Numerator, d1 = f2.Denominator;
					int n2 = f2.Numerator, d2 = f2.Denominator;

					return new Fraction32(n1 * d2 + n2 * d1, d1 * d2);
				}
				// Subtraction
				public static Fraction32 operator -(Fraction32 f, int n)
				{
					return f - new Fraction32(n, 1);
				}
				public static Fraction32 operator -(int n, Fraction32 f)
				{
					return new Fraction32(n, 1) - f;
				}
				public static Fraction32 operator -(Fraction32 f, float n)
				{
					return new Fraction32(((float)f) - n);
				}
				public static Fraction32 operator -(float n, Fraction32 f)
				{
					return new Fraction32(n) - f;
				}
				public static Fraction32 operator -(Fraction32 f, double n)
				{
					return new Fraction32(((double)f) - n);
				}
				public static Fraction32 operator -(double n, Fraction32 f)
				{
					return new Fraction32(n) - f;
				}
				public static Fraction32 operator -(Fraction32 f1, Fraction32 f2)
				{
					int n1 = f1.Numerator, d1 = f2.Denominator;
					int n2 = f2.Numerator, d2 = f2.Denominator;

					return new Fraction32(n1 * d2 - n2 * d1, d1 * d2);
				}
				// Increment
				public static Fraction32 operator ++(Fraction32 f)
				{
					return f + new Fraction32(1, 1);
				}
				// Decrement
				public static Fraction32 operator --(Fraction32 f)
				{
					return f - new Fraction32(1, 1);
				}
				#endregion
				#region Casts To Integral Types
				public static explicit operator int(Fraction32 f)
				{
					return f.Numerator / f.Denominator;
				}
				public static explicit operator float(Fraction32 f)
				{
					return ((float)f.Numerator) / ((float)f.Denominator);
				}
				public static explicit operator double(Fraction32 f)
				{
					return ((double)f.Numerator) / ((double)f.Denominator);
				}
				#endregion
				#region Comparison Operators
				public static bool operator ==(Fraction32 f1, Fraction32 f2)
				{
					return (f1.Numerator == f2.Numerator) && (f1.Denominator == f2.Denominator);
				}
				public static bool operator !=(Fraction32 f1, Fraction32 f2)
				{
					return (f1.Numerator != f2.Numerator) || (f1.Denominator != f2.Denominator);
				}
				public static bool operator <(Fraction32 f1, Fraction32 f2)
				{
					return (f1.Numerator * f2.Denominator) < (f2.Numerator * f1.Denominator);
				}
				public static bool operator >(Fraction32 f1, Fraction32 f2)
				{
					return (f1.Numerator * f2.Denominator) > (f2.Numerator * f1.Denominator);
				}
				#endregion
				#endregion

				#region Constructors
				public Fraction32(int numerator, int denominator)
				{
					mIsNegative = false;
					if (numerator < 0)
					{
						numerator = -numerator;
						mIsNegative = !mIsNegative;
					}
					if (denominator < 0)
					{
						denominator = -denominator;
						mIsNegative = !mIsNegative;
					}
					mNumerator = numerator;
					mDenominator = denominator;
					if (mDenominator != 0)
						Reduce(ref mNumerator, ref mDenominator);
				}

				public Fraction32(int numerator)
					: this(numerator, (int)1)
				{
					;
				}

				public Fraction32(Fraction32 f)
					: this(f.Numerator, f.Denominator)
				{
					;
				}

				public Fraction32(float value)
					: this((double)value)
				{
					;
				}

				public Fraction32(double value)
					: this(FromDouble(value, DefaultPrecision))
				{
					;
				}

				public Fraction32(string s)
					: this(FromString(s))
				{
					;
				}
				#endregion

				#region Instance Methods
				/// <summary>
				/// Sets the value of this instance to the fraction represented
				/// by the given numerator and denominator.
				/// </summary>
				/// <param name="numerator">The new numerator.</param>
				/// <param name="denominator">The new denominator.</param>
				public void Set(int numerator, int denominator)
				{
					mIsNegative = false;
					if (numerator < 0)
					{
						mIsNegative = !mIsNegative;
						numerator = -numerator;
					}
					if (denominator < 0)
					{
						mIsNegative = !mIsNegative;
						denominator = -denominator;
					}
					mNumerator = numerator;
					mDenominator = denominator;
					Reduce(ref mNumerator, ref mDenominator);
				}

				/// <summary>
				/// Indicates whether this instance and a specified object are equal value-wise.
				/// </summary>
				/// <param name="obj">Another object to compare to.</param>
				/// <returns>true if obj and this instance are the same type and represent 
				/// the same value; otherwise, false.</returns>
				public override bool Equals(object obj)
				{
					if (obj == null)
						return false;

					if (obj is Fraction32)
						return Equals((Fraction32)obj);
					else
						return false;
				}

				/// <summary>
				/// Indicates whether this instance and a specified object are equal value-wise.
				/// </summary>
				/// <param name="obj">Another fraction object to compare to.</param>
				/// <returns>true if obj and this instance represent the same value; 
				/// otherwise, false.</returns>
				public bool Equals(Fraction32 obj)
				{
					if (obj == null)
						return false;

					return (mIsNegative == obj.IsNegative) && (mNumerator == obj.Numerator) && (mDenominator == obj.Denominator);
				}

				/// <summary>
				/// Returns the hash code for this instance.
				/// </summary>
				/// <returns> A 32-bit signed integer that is the hash code for this instance.</returns>
				public override int GetHashCode()
				{
					return mDenominator ^ ((mIsNegative ? -1 : 1) * mNumerator);
				}

				/// <summary>
				/// Returns a string representation of the fraction.
				/// </summary>
				/// <param name="format">A numeric format string.</param>
				/// <param name="formatProvider">
				/// An System.IFormatProvider that supplies culture-specific 
				/// formatting information.
				/// </param>
				/// <returns>
				/// The string representation of the value of this instance as 
				/// specified by format and provider.
				/// </returns>
				/// <exception cref="System.FormatException">
				/// format is invalid or not supported.
				/// </exception>
				public string ToString(string format, IFormatProvider formatProvider)
				{
					StringBuilder sb = new StringBuilder();
					sb.Append(((mIsNegative ? -1 : 1) * mNumerator).ToString(format, formatProvider));
					sb.Append('/');
					sb.Append(mDenominator.ToString(format, formatProvider));
					return sb.ToString();
				}

				/// <summary>
				/// Returns a string representation of the fraction.
				/// </summary>
				/// <param name="format">A numeric format string.</param>
				/// <returns>
				/// The string representation of the value of this instance as 
				/// specified by format.
				/// </returns>
				/// <exception cref="System.FormatException">
				/// format is invalid or not supported.
				/// </exception>
				public string ToString(string format)
				{
					StringBuilder sb = new StringBuilder();
					sb.Append(((mIsNegative ? -1 : 1) * mNumerator).ToString(format));
					sb.Append('/');
					sb.Append(mDenominator.ToString(format));
					return sb.ToString();
				}

				/// <summary>
				/// Returns a string representation of the fraction.
				/// </summary>
				/// <param name="formatProvider">
				/// An System.IFormatProvider that supplies culture-specific 
				/// formatting information.
				/// </param>
				/// <returns>
				/// The string representation of the value of this instance as 
				/// specified by provider.
				/// </returns>
				public string ToString(IFormatProvider formatProvider)
				{
					StringBuilder sb = new StringBuilder();
					sb.Append(((mIsNegative ? -1 : 1) * mNumerator).ToString(formatProvider));
					sb.Append('/');
					sb.Append(mDenominator.ToString(formatProvider));
					return sb.ToString();
				}

				/// <summary>
				/// Returns a string representation of the fraction.
				/// </summary>
				/// <returns>A string formatted as numerator/denominator.</returns>
				public override string ToString()
				{
					StringBuilder sb = new StringBuilder();
					sb.Append(((mIsNegative ? -1 : 1) * mNumerator).ToString());
					if (mDenominator != 1)
					{
						sb.Append('/');
						sb.Append(mDenominator.ToString());
					}
					return sb.ToString();
				}
				public double ToDouble()
				{
					if (mDenominator == 0)
						return 0.0;
					return mNumerator / (double)mDenominator;
				}

				/// <summary>
				/// Compares this instance to a specified object and returns an indication of
				/// their relative values.
				/// </summary>
				/// <param name="obj">An object to compare, or null.</param>
				/// <returns>
				/// A signed number indicating the relative values of this instance and value.
				/// Less than zero: This instance is less than obj.
				/// Zero: This instance is equal to obj. 
				/// Greater than zero: This instance is greater than obj or obj is null.
				/// </returns>
				/// <exception cref="System.ArgumentException">obj is not a Fraction.</exception>
				public int CompareTo(object obj)
				{
					if (!(obj is Fraction32))
						throw new ArgumentException("obj must be of type Fraction", "obj");

					return CompareTo((Fraction32)obj);
				}

				/// <summary>
				/// Compares this instance to a specified object and returns an indication of
				/// their relative values.
				/// </summary>
				/// <param name="obj">An fraction to compare with this instance.</param>
				/// <returns>
				/// A signed number indicating the relative values of this instance and value.
				/// Less than zero: This instance is less than obj.
				/// Zero: This instance is equal to obj. 
				/// Greater than zero: This instance is greater than obj or obj is null.
				/// </returns>
				public int CompareTo(Fraction32 obj)
				{
					if (obj == null)
						return 1;
					if (this < obj)
						return -1;
					else if (this > obj)
						return 1;
					return 0;
				}
				#endregion

				#region Private Helper Methods
				/// <summary>
				/// Converts the given floating-point number to its rational representation.
				/// </summary>
				/// <param name="value">The floating-point number to be converted.</param>
				/// <param name="precision">Number of digits to consider.</param>
				/// <returns>The rational representation of value.</returns>
				private static Fraction32 FromDouble(double value, int precision)
				{
					if (double.IsNaN(value))
						return Fraction32.NaN;
					else if (double.IsNegativeInfinity(value))
						return Fraction32.NegativeInfinity;
					else if (double.IsPositiveInfinity(value))
						return Fraction32.PositiveInfinity;

					bool isneg = (value < 0);
					if (isneg) value = -value;

					// The precision of the resulting fraction
					int maxden = (int)System.Math.Pow(10.0, precision);

					// Input value is truncated at this many digits
					int num = (int)(value * (double)maxden);
					int den = maxden;

					// Find the GCD of numerator and denumerator
					int gcd = (int)MathEx.GCD((uint)num, (uint)den);

					// Reduce the fraction by the GCD
					num /= gcd;
					den /= gcd;

					return new Fraction32((isneg ? -1 : 1) * num, den);
				}

				/// <summary>Converts the string representation of a fraction to a Fraction type.</summary>
				/// <param name="s">The input string formatted as numerator/denominator.</param>
				/// <exception cref="System.ArgumentNullException">s is null.</exception>
				/// <exception cref="System.FormatException">s is not formatted as numerator/denominator.</exception>
				/// <exception cref="System.OverflowException">
				/// s represents numbers less than System.Int32.MinValue or greater than 
				/// System.Int32.MaxValue.
				/// </exception>
				private static Fraction32 FromString(string s)
				{
					if (s == null)
						throw new ArgumentNullException("s");

					string[] sa = s.Split('/');
					int numerator = 1;
					int denominator = 1;

					if (sa.Length == 1)
					{
						// Try to parse as int
						if (int.TryParse(sa[0], out numerator))
						{
							denominator = 1;
						}
						else
						{
							// Parse as double
							double dval = double.Parse(sa[0]);
							return FromDouble(dval, DefaultPrecision);
						}
					}
					else if (sa.Length == 2)
					{
						numerator = int.Parse(sa[0]);
						denominator = int.Parse(sa[1]);
					}
					else
						throw new FormatException("The input string must be formatted as n/d where n and d are integers");

					return new Fraction32(numerator, denominator);
				}

				/// <summary>
				/// Reduces the given numerator and denominator by dividing with their
				/// greatest common divisor.
				/// </summary>
				/// <param name="numerator">numerator to be reduced.</param>
				/// <param name="denominator">denominator to be reduced.</param>
				private static void Reduce(ref int numerator, ref int denominator)
				{
					uint gcd = MathEx.GCD((uint)numerator, (uint)denominator);
					if (gcd == 0) gcd = 1;
					numerator = numerator / (int)gcd;
					denominator = denominator / (int)gcd;
				}
				#endregion
			}

			/// <summary>
			/// Represents a generic rational number represented by 32-bit unsigned numerator and denominator.
			/// </summary>
			public struct UFraction32 : IComparable, IFormattable, IComparable<UFraction32>, IEquatable<UFraction32>
			{
				#region Member Variables
				private uint mNumerator;
				private uint mDenominator;
				#endregion

				#region Constants
				private const int DefaultPrecision = 8;
				#endregion

				#region Properties
				/// <summary>
				/// Gets or sets the numerator.
				/// </summary>
				public uint Numerator
				{
					get
					{
						return mNumerator;
					}
					set
					{
						mNumerator = value;
						Reduce(ref mNumerator, ref mDenominator);
					}
				}
				/// <summary>
				/// Gets or sets the denominator.
				/// </summary>
				public uint Denominator
				{
					get
					{
						return mDenominator;
					}
					set
					{
						mDenominator = value;
						Reduce(ref mNumerator, ref mDenominator);
					}
				}
				#endregion

				#region Predefined Values
				public static readonly UFraction32 NaN = new UFraction32(0, 0);
				public static readonly UFraction32 Infinity = new UFraction32(1, 0);
				#endregion

				#region Static Methods
				/// <summary>
				/// Returns a value indicating whether the specified number evaluates to a value
				/// that is not a number.
				/// </summary>
				/// <param name="f">A fraction.</param>
				/// <returns>true if f evaluates to Fraction.NaN; otherwise, false.</returns>
				public static bool IsNan(UFraction32 f)
				{
					return (f.Numerator == 0 && f.Denominator == 0);
				}
				/// <summary>
				/// Returns a value indicating whether the specified number evaluates to infinity.
				/// </summary>
				/// <param name="f">A fraction.</param>
				/// <returns>true if f evaluates to Fraction.Infinity; otherwise, false.</returns>
				public static bool IsInfinity(UFraction32 f)
				{
					return (f.Denominator == 0);
				}

				/// <summary>
				/// Converts the string representation of a fraction to a fraction object.
				/// </summary>
				/// <param name="s">A string formatted as numerator/denominator</param>
				/// <returns>A fraction object converted from s.</returns>
				/// <exception cref="System.ArgumentNullException">s is null</exception>
				/// <exception cref="System.FormatException">s is not in the correct format</exception>
				/// <exception cref="System.OverflowException">
				/// s represents a number less than System.UInt32.MinValue or greater than 
				/// System.UInt32.MaxValue.
				/// </exception>
				public static UFraction32 Parse(string s)
				{
					return FromString(s);
				}

				/// <summary>
				/// Converts the string representation of a fraction to a fraction object.
				/// A return value indicates whether the conversion succeeded.
				/// </summary>
				/// <param name="s">A string formatted as numerator/denominator</param>
				/// <returns>true if s was converted successfully; otherwise, false.</returns>
				public static bool TryParse(string s, out UFraction32 f)
				{
					try
					{
						f = Parse(s);
						return true;
					}
					catch
					{
						f = new UFraction32();
						return false;
					}
				}
				#endregion

				#region Operators
				#region Arithmetic Operators
				// Multiplication
				public static UFraction32 operator *(UFraction32 f, uint n)
				{
					return new UFraction32(f.Numerator * n, f.Denominator * n);
				}
				public static UFraction32 operator *(uint n, UFraction32 f)
				{
					return f * n;
				}
				public static UFraction32 operator *(UFraction32 f, float n)
				{
					return new UFraction32(((float)f) * n);
				}
				public static UFraction32 operator *(float n, UFraction32 f)
				{
					return f * n;
				}
				public static UFraction32 operator *(UFraction32 f, double n)
				{
					return new UFraction32(((double)f) * n);
				}
				public static UFraction32 operator *(double n, UFraction32 f)
				{
					return f * n;
				}
				public static UFraction32 operator *(UFraction32 f1, UFraction32 f2)
				{
					return new UFraction32(f1.Numerator * f2.Numerator, f1.Denominator * f2.Denominator);
				}
				// Divison
				public static UFraction32 operator /(UFraction32 f, uint n)
				{
					return new UFraction32(f.Numerator / n, f.Denominator / n);
				}
				public static UFraction32 operator /(UFraction32 f, float n)
				{
					return new UFraction32(((float)f) / n);
				}
				public static UFraction32 operator /(UFraction32 f, double n)
				{
					return new UFraction32(((double)f) / n);
				}
				public static UFraction32 operator /(UFraction32 f1, UFraction32 f2)
				{
					return f1 * Inverse(f2);
				}
				// Addition
				public static UFraction32 operator +(UFraction32 f, uint n)
				{
					return f + new UFraction32(n, 1);
				}
				public static UFraction32 operator +(uint n, UFraction32 f)
				{
					return f + n;
				}
				public static UFraction32 operator +(UFraction32 f, float n)
				{
					return new UFraction32(((float)f) + n);
				}
				public static UFraction32 operator +(float n, UFraction32 f)
				{
					return f + n;
				}
				public static UFraction32 operator +(UFraction32 f, double n)
				{
					return new UFraction32(((double)f) + n);
				}
				public static UFraction32 operator +(double n, UFraction32 f)
				{
					return f + n;
				}
				public static UFraction32 operator +(UFraction32 f1, UFraction32 f2)
				{
					uint n1 = f1.Numerator, d1 = f2.Denominator;
					uint n2 = f2.Numerator, d2 = f2.Denominator;

					return new UFraction32(n1 * d2 + n2 * d1, d1 * d2);
				}
				// Subtraction
				public static UFraction32 operator -(UFraction32 f, uint n)
				{
					return f - new UFraction32(n, 1);
				}
				public static UFraction32 operator -(uint n, UFraction32 f)
				{
					return new UFraction32(n, 1) - f;
				}
				public static UFraction32 operator -(UFraction32 f, float n)
				{
					return new UFraction32(((float)f) - n);
				}
				public static UFraction32 operator -(float n, UFraction32 f)
				{
					return new UFraction32(n) - f;
				}
				public static UFraction32 operator -(UFraction32 f, double n)
				{
					return new UFraction32(((double)f) - n);
				}
				public static UFraction32 operator -(double n, UFraction32 f)
				{
					return new UFraction32(n) - f;
				}
				public static UFraction32 operator -(UFraction32 f1, UFraction32 f2)
				{
					uint n1 = f1.Numerator, d1 = f2.Denominator;
					uint n2 = f2.Numerator, d2 = f2.Denominator;

					return new UFraction32(n1 * d2 - n2 * d1, d1 * d2);
				}
				// Increment
				public static UFraction32 operator ++(UFraction32 f)
				{
					return f + new UFraction32(1, 1);
				}
				// Decrement
				public static UFraction32 operator --(UFraction32 f)
				{
					return f - new UFraction32(1, 1);
				}
				#endregion
				#region Casts To Integral Types
				public static explicit operator uint(UFraction32 f)
				{
					return ((uint)f.Numerator) / ((uint)f.Denominator);
				}
				public static explicit operator float(UFraction32 f)
				{
					return ((float)f.Numerator) / ((float)f.Denominator);
				}
				public static explicit operator double(UFraction32 f)
				{
					return ((double)f.Numerator) / ((double)f.Denominator);
				}
				#endregion
				#region Comparison Operators
				public static bool operator ==(UFraction32 f1, UFraction32 f2)
				{
					return (f1.Numerator == f2.Numerator) && (f1.Denominator == f2.Denominator);
				}
				public static bool operator !=(UFraction32 f1, UFraction32 f2)
				{
					return (f1.Numerator != f2.Numerator) || (f1.Denominator != f2.Denominator);
				}
				public static bool operator <(UFraction32 f1, UFraction32 f2)
				{
					return (f1.Numerator * f2.Denominator) < (f2.Numerator * f1.Denominator);
				}
				public static bool operator >(UFraction32 f1, UFraction32 f2)
				{
					return (f1.Numerator * f2.Denominator) > (f2.Numerator * f1.Denominator);
				}
				#endregion
				#endregion

				#region Constructors
				public UFraction32(uint numerator, uint denominator)
				{
					mNumerator = numerator;
					mDenominator = denominator;
					if (mDenominator != 0)
						Reduce(ref mNumerator, ref mDenominator);
				}

				public UFraction32(uint numerator)
					: this(numerator, (uint)1)
				{
					;
				}

				public UFraction32(UFraction32 f)
					: this(f.Numerator, f.Denominator)
				{
					;
				}

				public UFraction32(float value)
					: this((double)value)
				{
					;
				}

				public UFraction32(double value)
					: this(FromDouble(value, DefaultPrecision))
				{
					;
				}

				public UFraction32(string s)
					: this(FromString(s))
				{
					;
				}
				#endregion

				#region Instance Methods
				/// <summary>
				/// Sets the value of this instance to the fraction represented
				/// by the given numerator and denominator.
				/// </summary>
				/// <param name="numerator">The new numerator.</param>
				/// <param name="denominator">The new denominator.</param>
				public void Set(uint numerator, uint denominator)
				{
					mNumerator = numerator;
					mDenominator = denominator;
					Reduce(ref mNumerator, ref mDenominator);
				}

				/// <summary>
				/// Returns the multiplicative inverse of a given value.
				/// </summary>
				/// <param name="f">A fraction.</param>
				/// <returns>Multiplicative inverse of f.</returns>
				public static UFraction32 Inverse(UFraction32 f)
				{
					return new UFraction32(f.Denominator, f.Numerator);
				}

				/// <summary>
				/// Indicates whether this instance and a specified object are equal value-wise.
				/// </summary>
				/// <param name="obj">Another object to compare to.</param>
				/// <returns>true if obj and this instance are the same type and represent 
				/// the same value; otherwise, false.</returns>
				public override bool Equals(object obj)
				{
					if (obj == null)
						return false;

					if (obj is UFraction32)
						return Equals((UFraction32)obj);
					else
						return false;
				}

				/// <summary>
				/// Indicates whether this instance and a specified object are equal value-wise.
				/// </summary>
				/// <param name="obj">Another fraction object to compare to.</param>
				/// <returns>true if obj and this instance represent the same value; 
				/// otherwise, false.</returns>
				public bool Equals(UFraction32 obj)
				{
					if (obj == null)
						return false;

					return (mNumerator == obj.Numerator) && (mDenominator == obj.Denominator);
				}

				/// <summary>
				/// Returns the hash code for this instance.
				/// </summary>
				/// <returns> A 32-bit signed integer that is the hash code for this instance.</returns>
				public override int GetHashCode()
				{
					return ((int)mDenominator) ^ ((int)mNumerator);
				}

				/// <summary>
				/// Returns a string representation of the fraction.
				/// </summary>
				/// <param name="format">A numeric format string.</param>
				/// <param name="formatProvider">
				/// An System.IFormatProvider that supplies culture-specific 
				/// formatting information.
				/// </param>
				/// <returns>
				/// The string representation of the value of this instance as 
				/// specified by format and provider.
				/// </returns>
				/// <exception cref="System.FormatException">
				/// format is invalid or not supported.
				/// </exception>
				public string ToString(string format, IFormatProvider formatProvider)
				{
					StringBuilder sb = new StringBuilder();
					sb.Append(mNumerator.ToString(format, formatProvider));
					sb.Append('/');
					sb.Append(mDenominator.ToString(format, formatProvider));
					return sb.ToString();
				}

				/// <summary>
				/// Returns a string representation of the fraction.
				/// </summary>
				/// <param name="format">A numeric format string.</param>
				/// <returns>
				/// The string representation of the value of this instance as 
				/// specified by format.
				/// </returns>
				/// <exception cref="System.FormatException">
				/// format is invalid or not supported.
				/// </exception>
				public string ToString(string format)
				{
					StringBuilder sb = new StringBuilder();
					sb.Append(mNumerator.ToString(format));
					sb.Append('/');
					sb.Append(mDenominator.ToString(format));
					return sb.ToString();
				}

				/// <summary>
				/// Returns a string representation of the fraction.
				/// </summary>
				/// <param name="formatProvider">
				/// An System.IFormatProvider that supplies culture-specific 
				/// formatting information.
				/// </param>
				/// <returns>
				/// The string representation of the value of this instance as 
				/// specified by provider.
				/// </returns>
				public string ToString(IFormatProvider formatProvider)
				{
					StringBuilder sb = new StringBuilder();
					sb.Append(mNumerator.ToString(formatProvider));
					sb.Append('/');
					sb.Append(mDenominator.ToString(formatProvider));
					return sb.ToString();
				}

				/// <summary>
				/// Returns a string representation of the fraction.
				/// </summary>
				/// <returns>A string formatted as numerator/denominator.</returns>
				public override string ToString()
				{
					StringBuilder sb = new StringBuilder();
					sb.Append(mNumerator.ToString());
					if (mDenominator != 1)
					{
						sb.Append('/');
						sb.Append(mDenominator.ToString());
					}
					return sb.ToString();
				}
				public double ToDouble()
				{
					if (mDenominator == 0)
						return 0.0;
					return mNumerator / (double)mDenominator;
				}

				/// <summary>
				/// Compares this instance to a specified object and returns an indication of
				/// their relative values.
				/// </summary>
				/// <param name="obj">An object to compare, or null.</param>
				/// <returns>
				/// A signed number indicating the relative values of this instance and value.
				/// Less than zero: This instance is less than obj.
				/// Zero: This instance is equal to obj. 
				/// Greater than zero: This instance is greater than obj or obj is null.
				/// </returns>
				/// <exception cref="System.ArgumentException">obj is not a Fraction.</exception>
				public int CompareTo(object obj)
				{
					if (!(obj is UFraction32))
						throw new ArgumentException("obj must be of type UFraction32", "obj");

					return CompareTo((UFraction32)obj);
				}

				/// <summary>
				/// Compares this instance to a specified object and returns an indication of
				/// their relative values.
				/// </summary>
				/// <param name="obj">An fraction to compare with this instance.</param>
				/// <returns>
				/// A signed number indicating the relative values of this instance and value.
				/// Less than zero: This instance is less than obj.
				/// Zero: This instance is equal to obj. 
				/// Greater than zero: This instance is greater than obj or obj is null.
				/// </returns>
				public int CompareTo(UFraction32 obj)
				{
					if (obj == null)
						return 1;
					if (this < obj)
						return -1;
					else if (this > obj)
						return 1;
					return 0;
				}
				#endregion

				#region Private Helper Methods
				/// <summary>
				/// Converts the given floating-point number to its rational representation.
				/// </summary>
				/// <param name="value">The floating-point number to be converted.</param>
				/// <param name="precision">Number of digits of value to consider.</param>
				/// <returns>The rational representation of value.</returns>
				private static UFraction32 FromDouble(double value, int precision)
				{
					if (value < 0)
						throw new ArgumentException("value cannot be negative.", "value");

					if (double.IsNaN(value))
						return UFraction32.NaN;
					else if (double.IsInfinity(value))
						return UFraction32.Infinity;

					// The precision of the resulting fraction
					uint maxden = (uint)System.Math.Pow(10.0, precision);

					// Input value is truncated at this many digits
					uint num = (uint)(value * (double)maxden);
					uint den = maxden;

					// Find the GCD of numerator and denumerator
					uint gcd = MathEx.GCD((uint)num, (uint)den);

					// Reduce the fraction by the GCD
					num /= gcd;
					den /= gcd;

					return new UFraction32(num, den);
				}

				/// <summary>Converts the string representation of a fraction to a Fraction type.</summary>
				/// <param name="s">The input string formatted as numerator/denominator.</param>
				/// <exception cref="System.ArgumentNullException">s is null.</exception>
				/// <exception cref="System.FormatException">s is not formatted as numerator/denominator.</exception>
				/// <exception cref="System.OverflowException">
				/// s represents numbers less than System.UInt32.MinValue or greater than 
				/// System.UInt32.MaxValue.
				/// </exception>
				private static UFraction32 FromString(string s)
				{
					if (s == null)
						throw new ArgumentNullException("s");

					string[] sa = s.Split('/');
					uint numerator = 1;
					uint denominator = 1;

					if (sa.Length == 1)
					{
						// Try to parse as uint
						if (uint.TryParse(sa[0], out numerator))
						{
							denominator = 1;
						}
						else
						{
							// Parse as double
							double dval = double.Parse(sa[0]);
							return FromDouble(dval, DefaultPrecision);
						}
					}
					else if (sa.Length == 2)
					{
						numerator = uint.Parse(sa[0]);
						denominator = uint.Parse(sa[1]);
					}
					else
						throw new FormatException("The input string must be formatted as n/d where n and d are integers");

					return new UFraction32(numerator, denominator);
				}

				/// <summary>
				/// Reduces the given numerator and denominator by dividing with their
				/// greatest common divisor.
				/// </summary>
				/// <param name="numerator">numerator to be reduced.</param>
				/// <param name="denominator">denominator to be reduced.</param>
				private static void Reduce(ref uint numerator, ref uint denominator)
				{
					if (denominator == 0)
						return;
					uint gcd = MathEx.GCD(numerator, denominator);
					numerator = numerator / gcd;
					denominator = denominator / gcd;
				}
				#endregion
			}

		}
		/// <summary>
		/// Represents the memory view of a JPEG section.
		/// A JPEG section is the data between markers of the JPEG file.
		/// </summary>
		internal class JPEGSection
		{
			#region "Properties"
			/// <summary>
			/// The marker byte representing the section.
			/// </summary>
			public JPEGMarker Marker { get; private set; }
			/// <summary>
			/// Section header as a byte array. This is different from the header
			/// definition in JPEG specification in that it does not include the 
			/// two byte section length.
			/// </summary>
			public byte[] Header { get; set; }
			/// <summary>
			/// For the SOS and RST markers, this contains the entropy coded data.
			/// </summary>
			public byte[] EntropyData { get; set; }
			#endregion

			#region "Constructors"
			private JPEGSection()
			{
				Header = new byte[0];
				EntropyData = new byte[0];
			}

			/// <summary>
			/// Constructs a JPEGSection represented by the marker byte and containing
			/// the given data.
			/// </summary>
			/// <param name="marker">The marker byte representing the section.</param>
			/// <param name="data">Section data.</param>
			/// <param name="entropydata">Entropy coded data.</param>
			public JPEGSection(JPEGMarker marker, byte[] data, byte[] entropydata)
			{
				Marker = marker;
				Header = data;
				EntropyData = entropydata;
			}

			/// <summary>
			/// Constructs a JPEGSection represented by the marker byte.
			/// </summary>
			/// <param name="marker">The marker byte representing the section.</param>
			public JPEGSection(JPEGMarker marker)
			{
				Marker = marker;
			}
			#endregion

			#region "Instance Methods"
			/// <summary>
			/// Returns a string representation of the current section.
			/// </summary>
			/// <returns>A System.String that represents the current section.</returns>
			public override string ToString()
			{
				return string.Format("{0} => Header: {1} bytes, Entropy Data: {2} bytes", Marker, Header.Length, EntropyData.Length);
			}
			#endregion
		}
		/// <summary>
		/// Represents a JPEG marker byte.
		/// </summary>
		internal enum JPEGMarker : byte
		{
			// Start Of Frame markers, non-differential, Huffman coding
			SOF0 = 0xc0,
			SOF1 = 0xc1,
			SOF2 = 0xc2,
			SOF3 = 0xc3,
			// Start Of Frame markers, differential, Huffman coding
			SOF5 = 0xc5,
			SOF6 = 0xc6,
			SOF7 = 0xc7,
			// Start Of Frame markers, non-differential, arithmetic coding
			JPG = 0xc8,
			SOF9 = 0xc9,
			SOF10 = 0xca,
			SOF11 = 0xcb,
			// Start Of Frame markers, differential, arithmetic coding
			SOF13 = 0xcd,
			SOF14 = 0xce,
			SOF15 = 0xcf,
			// Huffman table specification
			DHT = 0xc4,
			// Arithmetic coding conditioning specification
			DAC = 0xcc,
			// Restart interval termination
			RST0 = 0xd0,
			RST1 = 0xd1,
			RST2 = 0xd2,
			RST3 = 0xd3,
			RST4 = 0xd4,
			RST5 = 0xd5,
			RST6 = 0xd6,
			RST7 = 0xd7,
			// Other markers
			SOI = 0xd8,
			EOI = 0xd9,
			SOS = 0xda,
			DQT = 0xdb,
			DNL = 0xdc,
			DRI = 0xdd,
			DHP = 0xde,
			EXP = 0xdf,
			// application segments
			APP0 = 0xe0,
			APP1 = 0xe1,
			APP2 = 0xe2,
			APP3 = 0xe3,
			APP4 = 0xe4,
			APP5 = 0xe5,
			APP6 = 0xe6,
			APP7 = 0xe7,
			APP8 = 0xe8,
			APP9 = 0xe9,
			APP10 = 0xea,
			APP11 = 0xeb,
			APP12 = 0xec,
			APP13 = 0xed,
			APP14 = 0xee,
			APP15 = 0xef,
			// JPEG extensions
			JPG0 = 0xf0,
			JPG1 = 0xf1,
			JPG2 = 0xf2,
			JPG3 = 0xf3,
			JPG4 = 0xf4,
			JPG5 = 0xf5,
			JPG6 = 0xf6,
			JPG7 = 0xf7,
			JPG8 = 0xf8,
			JPG9 = 0xf9,
			JPG10 = 0xfa,
			JPG11 = 0xfb,
			JP1G2 = 0xfc,
			JPG13 = 0xfd,
			// Comment
			COM = 0xfe,
			// Temporary
			TEM = 0x01,
		}
		/// <summary>
		/// Represents the binary view of a JPEG file.
		/// </summary>
		internal class JPEGFile
		{
			#region "Properties"
			/// <summary>
			/// Gets or sets the sections contained in the JPEG file.
			/// </summary>
			public List<JPEGSection> Sections { get; set; }
			/// <summary>
			/// Gets or sets non-standard trailing data following the End of Image (EOI) marker.
			/// </summary>
			public byte[] TrailingData { get; set; }
			#endregion

			#region "Constructors"
			protected JPEGFile()
			{
				;
			}

			/// <summary>
			/// Constructs a JPEGFile class by reading the given stream.
			/// </summary>
			/// <param name="stream">The data stream used to load the image.</param>
			/// <exception cref="NotValidJPEGFileException"></exception>
			public JPEGFile(Stream stream)
			{
				Read(stream);
			}

			/// <summary>
			/// Constructs a JPEGFile class by reading the given JPEG file.
			/// </summary>
			/// <param name="filename">The complete path to the JPEG file.</param>
			/// <exception cref="NotValidJPEGFileException"></exception>
			public JPEGFile(string filename)
			{
				using (FileStream stream = new FileStream(filename, FileMode.Open, FileAccess.Read))
				{
					Read(stream);
					stream.Close();
					stream.Dispose();
				}
			}
			#endregion

			#region "Instance Methods"
			private bool IsMarkerInThisSections(JPEGMarker marker)
			{
				foreach (JPEGSection section in Sections)
				{
					if (section.Marker == marker)
						return true;
				}
				return false;
			}
			/// <summary>
			/// Saves the JPEG image to the given stream. The caller is responsible for
			/// disposing the stream.
			/// </summary>
			/// <param name="stream">The data stream used to save the image.</param>
			public void Save(Stream stream, ExifFile exifFileToCopy)
			{
				// Write sections
				foreach (JPEGSection section in Sections)
				{
					// Section header (including length bytes and section marker) 
					// must not exceed 64 kB.
					if (section.Header.Length + 2 + 2 > 64 * 1024)
						throw new SectionExceeds64KBException();

					// Write section marker
					stream.Write(new byte[] { 0xFF, (byte)section.Marker }, 0, 2);

					// Write section header
					if (section.Header.Length != 0)
					{
						// Header length including the length field itself
						stream.Write(BitConverterEx.BigEndian.GetBytes((ushort)(section.Header.Length + 2)), 0, 2);

						// Section header
						stream.Write(section.Header, 0, section.Header.Length);
					}

					// Write entropy coded data
					if (section.EntropyData != null && section.EntropyData.Length != 0)
						stream.Write(section.EntropyData, 0, section.EntropyData.Length);
				}
				if (exifFileToCopy == null)
				{
					// Write trailing data, if any
					if (TrailingData.Length != 0)
						stream.Write(TrailingData, 0, TrailingData.Length);
				}
				else
				{
					foreach (JPEGSection section in exifFileToCopy.JpegFile.Sections)
					{
						if (!IsMarkerInThisSections(section.Marker))
						{
							// Section header (including length bytes and section marker) 
							// must not exceed 64 kB.
							if (section.Header.Length + 2 + 2 > 64 * 1024)
								throw new SectionExceeds64KBException();

							// Write section marker
							stream.Write(new byte[] { 0xFF, (byte)section.Marker }, 0, 2);

							// Write section header
							if (section.Header.Length != 0)
							{
								// Header length including the length field itself
								stream.Write(BitConverterEx.BigEndian.GetBytes((ushort)(section.Header.Length + 2)), 0, 2);

								// Section header
								stream.Write(section.Header, 0, section.Header.Length);
							}

							// Write entropy coded data
							if (section.EntropyData != null && section.EntropyData.Length != 0)
								stream.Write(section.EntropyData, 0, section.EntropyData.Length);
						}
					}
					// Write trailing data, if any
					if (exifFileToCopy.JpegFile.TrailingData.Length != 0)
						stream.Write(exifFileToCopy.JpegFile.TrailingData, 0, exifFileToCopy.JpegFile.TrailingData.Length);
				}
			}

			/// <summary>
			/// Saves the JPEG image with the given filename.
			/// </summary>
			/// <param name="filename">The complete path to the JPEG file.</param>
			public void SaveAs(string filename, ExifFile exifFileToCopy)
			{
				using (FileStream stream = new FileStream(filename, FileMode.Create, FileAccess.Write))
				{
					Save(stream, exifFileToCopy);
					stream.Close();
				}
			}

			/// <summary>
			/// Converts the JPEGFile to a System.Drawing.Bitmap.
			/// </summary>
			/// <returns>Returns a System.Drawing.Bitmap containing image data.</returns>
			public Image ToBitmap()
			{
				Bitmap bmp;
				using (MemoryStream stream = new MemoryStream())
				{
					Save(stream, null);
					bmp = new Bitmap(stream);
					stream.Close();
				}
				return bmp;
			}
			#endregion

			#region "Private Helper Methods"
			/// <summary>
			/// Reads the given stream.
			/// </summary>
			/// <param name="stream">The data stream used to load the image.</param>
			/// <exception cref="NotValidJPEGFileException"></exception>
			private void Read(Stream stream)
			{
				Sections = new List<JPEGSection>();

				// Read the Start of Image (SOI) marker. SOI marker is represented
				// with two bytes: 0xFF, 0xD8.
				byte[] markerbytes = new byte[2];
				if (stream.Read(markerbytes, 0, 2) != 2 || markerbytes[0] != 0xFF && markerbytes[1] != 0xD8)
					throw new NotValidJPEGFileException();
				stream.Seek(0, SeekOrigin.Begin);

				byte[] twobytes = new byte[2];
				byte[] onebyte = new byte[1];
				byte[] fivebytes = new byte[5];
				stream.Read(twobytes, 0, 2); // SOI
				stream.Read(twobytes, 0, 2); // APP0
				stream.Read(twobytes, 0, 2); // APP0 Length
				stream.Seek(0, SeekOrigin.Begin);

				// Search and read sections until we reach the end of file.
				while (stream.Position != stream.Length)
				{
					// Read the next section marker. Section markers are two bytes 
					// with values 0xFF, 0x?? where ?? must not be 0x00 or 0xFF.
					if (stream.Read(markerbytes, 0, 2) != 2 || markerbytes[0] != 0xFF || markerbytes[1] == 0x00 || markerbytes[1] == 0xFF)
						throw new NotValidJPEGFileException();

					JPEGMarker marker = (JPEGMarker)markerbytes[1];

					byte[] header = new byte[0];
					// SOI, EOI and RST markers do not contain any header
					if (marker != JPEGMarker.SOI && marker != JPEGMarker.EOI && !(marker >= JPEGMarker.RST0 && marker <= JPEGMarker.RST7))
					{
						// Length of the header including the length bytes.
						// This value is a 16-bit unsigned integer 
						// in big endian byte-order.
						byte[] lengthbytes = new byte[2];
						if (stream.Read(lengthbytes, 0, 2) != 2)
							throw new NotValidJPEGFileException();
						long length = (long)BitConverterEx.BigEndian.ToUInt16(lengthbytes, 0);

						// Read section header.
						header = new byte[length - 2];
						int bytestoread = header.Length;
						while (bytestoread > 0)
						{
							int count = Math.Min(bytestoread, 4 * 1024);
							int bytesread = stream.Read(header, header.Length - bytestoread, count);
							if (bytesread == 0)
								throw new NotValidJPEGFileException();
							bytestoread -= bytesread;
						}
					}

					// Start of Scan (SOS) sections and RST sections are immediately
					// followed by entropy coded data. For that, we need to read until
					// the next section marker once we reach a SOS or RST.
					// Remeber that SOI, EOI and RST markers do not contain any header
					byte[] entropydata = new byte[0];
					if (marker == JPEGMarker.SOS || (marker >= JPEGMarker.RST0 && marker <= JPEGMarker.RST7))
					{
						long position = stream.Position;

						// Search for the next section marker
						while (true)
						{
							// Search for an 0xFF indicating start of a marker
							int nextbyte = 0;
							do
							{
								nextbyte = stream.ReadByte();
								if (nextbyte == -1)
									throw new NotValidJPEGFileException();
							} while ((byte)nextbyte != 0xFF);

							// Skip filler bytes (0xFF)
							do
							{
								nextbyte = stream.ReadByte();
								if (nextbyte == -1)
									throw new NotValidJPEGFileException();
							} while ((byte)nextbyte == 0xFF);

							// Looks like a section marker. The next byte must not be 0x00.
							if ((byte)nextbyte != 0x00)
							{
								// We reached a section marker. Calculate the
								// length of the entropy coded data.
								stream.Seek(-2, SeekOrigin.Current);
								long edlength = stream.Position - position;
								stream.Seek(-edlength, SeekOrigin.Current);

								// Read entropy coded data
								entropydata = new byte[edlength];
								int bytestoread = entropydata.Length;
								while (bytestoread > 0)
								{
									int count = Math.Min(bytestoread, 4 * 1024);
									int bytesread = stream.Read(entropydata, entropydata.Length - bytestoread, count);
									if (bytesread == 0)
										throw new NotValidJPEGFileException();
									bytestoread -= bytesread;
								}

								break;
							}
						}
					}

					// Store section.
					JPEGSection section = new JPEGSection(marker, header, entropydata);
					Sections.Add(section);

					// Some propriety formats store data past the EOI marker
					if (marker == JPEGMarker.EOI)
					{
						int bytestoread = (int)(stream.Length - stream.Position);
						TrailingData = new byte[bytestoread];
						while (bytestoread > 0)
						{
							int count = (int)Math.Min(bytestoread, 4 * 1024);
							int bytesread = stream.Read(TrailingData, TrailingData.Length - bytestoread, count);
							if (bytesread == 0)
								throw new NotValidJPEGFileException();
							bytestoread -= bytesread;
						}
					}
				}
			}
			#endregion
		}
		/// The exception that is thrown when the format of the JPEG file
		/// could not be understood.
		/// </summary>
		internal class NotValidJPEGFileException : Exception
		{
			public NotValidJPEGFileException()
				: base("Not a valid JPEG file.")
			{
				;
			}

			public NotValidJPEGFileException(string message)
				: base(message)
			{
				;
			}
		}

		/// <summary>
		/// The exception that is thrown when the length of a section exceeds 64 kB.
		/// </summary>
		internal class SectionExceeds64KBException : Exception
		{
			public SectionExceeds64KBException()
				: base("Section length exceeds 64 kB.")
			{
				;
			}

			public SectionExceeds64KBException(string message)
				: base(message)
			{
				;
			}
		}
		internal static class ExifTagFactory
		{
			#region "Static Methods"
			/// <summary>
			/// Returns the ExifTag corresponding to the given tag id.
			/// </summary>
			public static ExifTag GetExifTag(IFD ifd, ushort tagid)
			{
				return (ExifTag)(ifd + tagid);
			}

			/// <summary>
			/// Returns the tag id corresponding to the given ExifTag.
			/// </summary>
			public static ushort GetTagID(ExifTag exiftag)
			{
				IFD ifd = GetTagIFD(exiftag);
				return (ushort)((int)exiftag - (int)ifd);
			}

			/// <summary>
			/// Returns the IFD section containing the given tag.
			/// </summary>
			public static IFD GetTagIFD(ExifTag tag)
			{
				return (IFD)(((int)tag / 100000) * 100000);
			}

			/// <summary>
			/// Returns the string representation for the given exif tag.
			/// </summary>
			public static string GetTagName(ExifTag tag)
			{
				string name = Enum.GetName(typeof(ExifTag), tag);
				if (name == null)
					return "Unknown " + tag.ToString();
				else
					return name;
			}

			/// <summary>
			/// Returns the string representation for the given tag id.
			/// </summary>
			public static string GetTagName(IFD ifd, ushort tagid)
			{
				return GetTagName(GetExifTag(ifd, tagid));
			}

			/// <summary>
			/// Returns the string representation for the given exif tag including 
			/// IFD section and tag id.
			/// </summary>
			public static string GetTagLongName(ExifTag tag)
			{
				string ifdname = Enum.GetName(typeof(IFD), GetTagIFD(tag));
				string name = Enum.GetName(typeof(ExifTag), tag);
				if (name == null)
					name = "Unknown";
				string tagidname = GetTagID(tag).ToString();
				return ifdname + ": " + name + " (" + tagidname + ")";
			}
			#endregion
		}
		/// <summary>
		/// Represents the IFD section containing exif tags.
		/// </summary>
		internal enum IFD : int
		{
			Unknown = 0,
			Zeroth = 100000,
			EXIF = 200000,
			GPS = 300000,
			Interop = 400000,
			First = 500000,
			MakerNote = 600000,
		}

		/// <summary>
		/// Represents the tags associated with exif fields.
		/// </summary>
		internal enum ExifTag : int
		{
			// ****************************
			// TIFF Tags
			// ****************************
			ImageWidth = IFD.Zeroth + 256,
			ImageLength = IFD.Zeroth + 257,
			BitsPerSample = IFD.Zeroth + 258,
			Compression = IFD.Zeroth + 259,
			PhotometricInterpretation = IFD.Zeroth + 262,
			Orientation = IFD.Zeroth + 274,
			SamplesPerPixel = IFD.Zeroth + 277,
			PlanarConfiguration = IFD.Zeroth + 284,
			YCbCrSubSampling = IFD.Zeroth + 530,
			YCbCrPositioning = IFD.Zeroth + 531,
			XResolution = IFD.Zeroth + 282,
			YResolution = IFD.Zeroth + 283,
			ResolutionUnit = IFD.Zeroth + 296,
			StripOffsets = IFD.Zeroth + 273,
			RowsPerStrip = IFD.Zeroth + 278,
			StripByteCounts = IFD.Zeroth + 279,
			JPEGInterchangeFormat = IFD.Zeroth + 513,
			JPEGInterchangeFormatLength = IFD.Zeroth + 514,
			TransferFunction = IFD.Zeroth + 301,
			WhitePoint = IFD.Zeroth + 318,
			PrimaryChromaticities = IFD.Zeroth + 319,
			YCbCrCoefficients = IFD.Zeroth + 529,
			ReferenceBlackWhite = IFD.Zeroth + 532,
			DateTime = IFD.Zeroth + 306,
			ImageDescription = IFD.Zeroth + 270,
			Make = IFD.Zeroth + 271,
			Model = IFD.Zeroth + 272,
			Software = IFD.Zeroth + 305,
			Artist = IFD.Zeroth + 315,
			Copyright = IFD.Zeroth + 33432,
			EXIFIFDPointer = IFD.Zeroth + 34665,
			GPSIFDPointer = IFD.Zeroth + 34853,
			// ****************************
			// EXIF Tags
			// ****************************
			ExifVersion = IFD.EXIF + 36864,
			FlashpixVersion = IFD.EXIF + 40960,
			ColorSpace = IFD.EXIF + 40961,
			ComponentsConfiguration = IFD.EXIF + 37121,
			CompressedBitsPerPixel = IFD.EXIF + 37122,
			PixelXDimension = IFD.EXIF + 40962,
			PixelYDimension = IFD.EXIF + 40963,
			MakerNote = IFD.EXIF + 37500,
			UserComment = IFD.EXIF + 37510,
			RelatedSoundFile = IFD.EXIF + 40964,
			DateTimeOriginal = IFD.EXIF + 36867,
			DateTimeDigitized = IFD.EXIF + 36868,
			SubSecTime = IFD.EXIF + 37520,
			SubSecTimeOriginal = IFD.EXIF + 37521,
			SubSecTimeDigitized = IFD.EXIF + 37522,
			ExposureTime = IFD.EXIF + 33434,
			FNumber = IFD.EXIF + 33437,
			ExposureProgram = IFD.EXIF + 34850,
			SpectralSensitivity = IFD.EXIF + 34852,
			ISOSpeed = IFD.EXIF + 34855,
			OECF = IFD.EXIF + 34856,
			ShutterSpeedValue = IFD.EXIF + 37377,
			ApertureValue = IFD.EXIF + 37378,
			BrightnessValue = IFD.EXIF + 37379,
			ExposureBiasValue = IFD.EXIF + 37380,
			MaxApertureValue = IFD.EXIF + 37381,
			SubjectDistance = IFD.EXIF + 37382,
			MeteringMode = IFD.EXIF + 37383,
			LightSource = IFD.EXIF + 37384,
			Flash = IFD.EXIF + 37385,
			FocalLength = IFD.EXIF + 37386,
			SubjectArea = IFD.EXIF + 37396,
			FlashEnergy = IFD.EXIF + 41483,
			SpatialFrequencyResponse = IFD.EXIF + 41484,
			FocalPlaneXResolution = IFD.EXIF + 41486,
			FocalPlaneYResolution = IFD.EXIF + 41487,
			FocalPlaneResolutionUnit = IFD.EXIF + 41488,
			SubjectLocation = IFD.EXIF + 41492,
			ExposureIndex = IFD.EXIF + 41493,
			SensingMethod = IFD.EXIF + 41495,
			FileSource = IFD.EXIF + 41728,
			SceneType = IFD.EXIF + 41729,
			CFAPattern = IFD.EXIF + 41730,
			CustomRendered = IFD.EXIF + 41985,
			ExposureMode = IFD.EXIF + 41986,
			WhiteBalance = IFD.EXIF + 41987,
			DigitalZoomRatio = IFD.EXIF + 41988,
			FocalLengthIn35mmFilm = IFD.EXIF + 41989,
			SceneCaptureType = IFD.EXIF + 41990,
			GainControl = IFD.EXIF + 41991,
			Contrast = IFD.EXIF + 41992,
			Saturation = IFD.EXIF + 41993,
			Sharpness = IFD.EXIF + 41994,
			DeviceSettingDescription = IFD.EXIF + 41995,
			SubjectDistanceRange = IFD.EXIF + 41996,
			ImageUniqueID = IFD.EXIF + 42016,
			InteroperabilityIFDPointer = IFD.EXIF + 40965,
			// ****************************
			// GPS Tags
			// ****************************
			GPSVersionID = IFD.GPS + 0,
			GPSLatitudeRef = IFD.GPS + 1,
			GPSLatitude = IFD.GPS + 2,
			GPSLongitudeRef = IFD.GPS + 3,
			GPSLongitude = IFD.GPS + 4,
			GPSAltitudeRef = IFD.GPS + 5,
			GPSAltitude = IFD.GPS + 6,
			GPSTimeStamp = IFD.GPS + 7,
			GPSSatellites = IFD.GPS + 8,
			GPSStatus = IFD.GPS + 9,
			GPSMeasureMode = IFD.GPS + 10,
			GPSDOP = IFD.GPS + 11,
			GPSSpeedRef = IFD.GPS + 12,
			GPSSpeed = IFD.GPS + 13,
			GPSTrackRef = IFD.GPS + 14,
			GPSTrack = IFD.GPS + 15,
			GPSImgDirectionRef = IFD.GPS + 16,
			GPSImgDirection = IFD.GPS + 17,
			GPSMapDatum = IFD.GPS + 18,
			GPSDestLatitudeRef = IFD.GPS + 19,
			GPSDestLatitude = IFD.GPS + 20,
			GPSDestLongitudeRef = IFD.GPS + 21,
			GPSDestLongitude = IFD.GPS + 22,
			GPSDestBearingRef = IFD.GPS + 23,
			GPSDestBearing = IFD.GPS + 24,
			GPSDestDistanceRef = IFD.GPS + 25,
			GPSDestDistance = IFD.GPS + 26,
			GPSProcessingMethod = IFD.GPS + 27,
			GPSAreaInformation = IFD.GPS + 28,
			GPSDateStamp = IFD.GPS + 29,
			GPSDifferential = IFD.GPS + 30,
			// ****************************
			// InterOp Tags
			// ****************************
			InteroperabilityIndex = IFD.Interop + 1,
			InteroperabilityVersion = IFD.Interop + 2,
			// ****************************
			// First IFD TIFF Tags
			// ****************************
			ThumbnailImageWidth = IFD.First + 256,
			ThumbnailImageLength = IFD.First + 257,
			ThumbnailBitsPerSample = IFD.First + 258,
			ThumbnailCompression = IFD.First + 259,
			ThumbnailPhotometricInterpretation = IFD.First + 262,
			ThumbnailOrientation = IFD.First + 274,
			ThumbnailSamplesPerPixel = IFD.First + 277,
			ThumbnailPlanarConfiguration = IFD.First + 284,
			ThumbnailYCbCrSubSampling = IFD.First + 530,
			ThumbnailYCbCrPositioning = IFD.First + 531,
			ThumbnailXResolution = IFD.First + 282,
			ThumbnailYResolution = IFD.First + 283,
			ThumbnailResolutionUnit = IFD.First + 296,
			ThumbnailStripOffsets = IFD.First + 273,
			ThumbnailRowsPerStrip = IFD.First + 278,
			ThumbnailStripByteCounts = IFD.First + 279,
			ThumbnailJPEGInterchangeFormat = IFD.First + 513,
			ThumbnailJPEGInterchangeFormatLength = IFD.First + 514,
			ThumbnailTransferFunction = IFD.First + 301,
			ThumbnailWhitePoint = IFD.First + 318,
			ThumbnailPrimaryChromaticities = IFD.First + 319,
			ThumbnailYCbCrCoefficients = IFD.First + 529,
			ThumbnailReferenceBlackWhite = IFD.First + 532,
			ThumbnailDateTime = IFD.First + 306,
			ThumbnailImageDescription = IFD.First + 270,
			ThumbnailMake = IFD.First + 271,
			ThumbnailModel = IFD.First + 272,
			ThumbnailSoftware = IFD.First + 305,
			ThumbnailArtist = IFD.First + 315,
			ThumbnailCopyright = IFD.First + 33432,
		}
		/// <summary>
		/// Creates exif properties from interoperability parameters.
		/// </summary>
		internal static class ExifPropertyFactory
		{
			#region "Static Methods"
			/// <summary>
			/// Creates an ExifProperty from the given interoperability parameters.
			/// </summary>
			/// <param name="interOperability">Property data.</param>
			/// <param name="byteOrder">Byte order of the source data.</param>
			/// <param name="ifd">IFD section containing this propery.</param>
			/// <returns>an ExifProperty initialized from the interoperability parameters.</returns>
			public static ExifProperty Get(ExifInterOperability interOperability, BitConverterEx.ByteOrder byteOrder, IFD ifd)
			{
				return Get(interOperability.TagID, interOperability.TypeID, interOperability.Count, interOperability.Data, byteOrder, ifd);
			}

			/// <summary>
			/// Creates an ExifProperty from the given interoperability parameters.
			/// </summary>
			/// <param name="tag">The tag id of the exif property.</param>
			/// <param name="type">The type id of the exif property.</param>
			/// <param name="count">Byte or component count.</param>
			/// <param name="value">Field data as an array of bytes.</param>
			/// <param name="byteOrder">Byte order of value.</param>
			/// <param name="ifd">IFD section containing this propery.</param>
			/// <returns>an ExifProperty initialized from the interoperability parameters.</returns>
			public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value, BitConverterEx.ByteOrder byteOrder, IFD ifd)
			{
				BitConverterEx conv = new BitConverterEx(byteOrder, BitConverterEx.ByteOrder.System);

				if (ifd == IFD.Zeroth)
				{
					if (tag == 0x103) // Compression
						return new ExifEnumProperty<Compression>(ExifTag.Compression, (Compression)conv.ToUInt16(value, 0));
					else if (tag == 0x106) // PhotometricInterpretation
						return new ExifEnumProperty<PhotometricInterpretation>(ExifTag.PhotometricInterpretation, (PhotometricInterpretation)conv.ToUInt16(value, 0));
					else if (tag == 0x112) // Orientation
						return new ExifEnumProperty<Orientation>(ExifTag.Orientation, (Orientation)conv.ToUInt16(value, 0));
					else if (tag == 0x11c) // PlanarConfiguration
						return new ExifEnumProperty<PlanarConfiguration>(ExifTag.PlanarConfiguration, (PlanarConfiguration)conv.ToUInt16(value, 0));
					else if (tag == 0x213) // YCbCrPositioning
						return new ExifEnumProperty<YCbCrPositioning>(ExifTag.YCbCrPositioning, (YCbCrPositioning)conv.ToUInt16(value, 0));
					else if (tag == 0x128) // ResolutionUnit
						return new ExifEnumProperty<ResolutionUnit>(ExifTag.ResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0));
					else if (tag == 0x132) // DateTime
						return new ExifDateTime(ExifTag.DateTime, ExifBitConverter.ToDateTime(value));
				}
				else if (ifd == IFD.EXIF)
				{
					if (tag == 0x9000) // ExifVersion
						return new ExifVersion(ExifTag.ExifVersion, ExifBitConverter.ToAscii(value));
					else if (tag == 0xa000) // FlashpixVersion
						return new ExifVersion(ExifTag.FlashpixVersion, ExifBitConverter.ToAscii(value));
					else if (tag == 0xa001) // ColorSpace
						return new ExifEnumProperty<ColorSpace>(ExifTag.ColorSpace, (ColorSpace)conv.ToUInt16(value, 0));
					else if (tag == 0x9286) // UserComment
					{
						byte[] encbytes = new byte[8];
						byte[] strbytes = new byte[value.Length - 8];
						Array.Copy(value, encbytes, 8);
						Array.Copy(value, 8, strbytes, 0, value.Length - 8);
						Encoding enc = Encoding.ASCII;
						string encstr = enc.GetString(encbytes);
						if (encstr == "ASCII\0\0\0")
							enc = Encoding.ASCII;
						else if (encstr == "JIS\0\0\0\0\0")
							enc = Encoding.GetEncoding("Japanese (JIS 0208-1990 and 0212-1990)");
						else if (encstr == "Unicode\0")
							enc = Encoding.Unicode;
						else
							enc = null;

						int len = Array.IndexOf(strbytes, (byte)0);
						if (len == -1) len = strbytes.Length;
						return new ExifEncodedString(ExifTag.UserComment, (enc == null ? Encoding.ASCII.GetString(strbytes, 0, len) : enc.GetString(strbytes, 0, len)), enc);
					}
					else if (tag == 0x9003) // DateTimeOriginal
						return new ExifDateTime(ExifTag.DateTimeOriginal, ExifBitConverter.ToDateTime(value));
					else if (tag == 0x9004) // DateTimeDigitized
						return new ExifDateTime(ExifTag.DateTimeDigitized, ExifBitConverter.ToDateTime(value));
					else if (tag == 0x8822) // ExposureProgram
						return new ExifEnumProperty<ExposureProgram>(ExifTag.ExposureProgram, (ExposureProgram)conv.ToUInt16(value, 0));
					else if (tag == 0x9207) // MeteringMode
						return new ExifEnumProperty<MeteringMode>(ExifTag.MeteringMode, (MeteringMode)conv.ToUInt16(value, 0));
					else if (tag == 0x9208) // LightSource
						return new ExifEnumProperty<LightSource>(ExifTag.LightSource, (LightSource)conv.ToUInt16(value, 0));
					else if (tag == 0x9209) // Flash
						return new ExifEnumProperty<Flash>(ExifTag.Flash, (Flash)conv.ToUInt16(value, 0), true);
					else if (tag == 0x9214) // SubjectArea
					{
						if (count == 3)
							return new ExifCircularSubjectArea(ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder));
						else if (count == 4)
							return new ExifRectangularSubjectArea(ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder));
						else // count == 2
							return new ExifPointSubjectArea(ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder));
					}
					else if (tag == 0xa210) // FocalPlaneResolutionUnit
						return new ExifEnumProperty<ResolutionUnit>(ExifTag.FocalPlaneResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0), true);
					else if (tag == 0xa214) // SubjectLocation
						return new ExifPointSubjectArea(ExifTag.SubjectLocation, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder));
					else if (tag == 0xa217) // SensingMethod
						return new ExifEnumProperty<SensingMethod>(ExifTag.SensingMethod, (SensingMethod)conv.ToUInt16(value, 0), true);
					else if (tag == 0xa300) // FileSource
						return new ExifEnumProperty<FileSource>(ExifTag.FileSource, (FileSource)conv.ToUInt16(value, 0), true);
					else if (tag == 0xa301) // SceneType
						return new ExifEnumProperty<SceneType>(ExifTag.SceneType, (SceneType)conv.ToUInt16(value, 0), true);
					else if (tag == 0xa401) // CustomRendered
						return new ExifEnumProperty<CustomRendered>(ExifTag.CustomRendered, (CustomRendered)conv.ToUInt16(value, 0), true);
					else if (tag == 0xa402) // ExposureMode
						return new ExifEnumProperty<ExposureMode>(ExifTag.ExposureMode, (ExposureMode)conv.ToUInt16(value, 0), true);
					else if (tag == 0xa403) // WhiteBalance
						return new ExifEnumProperty<WhiteBalance>(ExifTag.WhiteBalance, (WhiteBalance)conv.ToUInt16(value, 0), true);
					else if (tag == 0xa406) // SceneCaptureType
						return new ExifEnumProperty<SceneCaptureType>(ExifTag.SceneCaptureType, (SceneCaptureType)conv.ToUInt16(value, 0), true);
					else if (tag == 0xa407) // GainControl
						return new ExifEnumProperty<GainControl>(ExifTag.GainControl, (GainControl)conv.ToUInt16(value, 0), true);
					else if (tag == 0xa408) // Contrast
						return new ExifEnumProperty<Contrast>(ExifTag.Contrast, (Contrast)conv.ToUInt16(value, 0), true);
					else if (tag == 0xa409) // Saturation
						return new ExifEnumProperty<Saturation>(ExifTag.Saturation, (Saturation)conv.ToUInt16(value, 0), true);
					else if (tag == 0xa40a) // Sharpness
						return new ExifEnumProperty<Sharpness>(ExifTag.Sharpness, (Sharpness)conv.ToUInt16(value, 0), true);
					else if (tag == 0xa40c) // SubjectDistanceRange
						return new ExifEnumProperty<SubjectDistanceRange>(ExifTag.SubjectDistance, (SubjectDistanceRange)conv.ToUInt16(value, 0), true);
				}
				else if (ifd == IFD.GPS)
				{
					if (tag == 0) // GPSVersionID
						return new ExifVersion(ExifTag.GPSVersionID, ExifBitConverter.ToString(value));
					else if (tag == 1) // GPSLatitudeRef
						return new ExifEnumProperty<GPSLatitudeRef>(ExifTag.GPSLatitudeRef, (GPSLatitudeRef)value[0]);
					else if (tag == 2) // GPSLatitude
						return new GPSLatitudeLongitude(ExifTag.GPSLatitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder));
					else if (tag == 3) // GPSLongitudeRef
						return new ExifEnumProperty<GPSLongitudeRef>(ExifTag.GPSLongitudeRef, (GPSLongitudeRef)value[0]);
					else if (tag == 4) // GPSLongitude
						return new GPSLatitudeLongitude(ExifTag.GPSLongitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder));
					else if (tag == 5) // GPSAltitudeRef
						return new ExifEnumProperty<GPSAltitudeRef>(ExifTag.GPSAltitudeRef, (GPSAltitudeRef)value[0]);
					else if (tag == 7) // GPSTimeStamp
						return new GPSTimeStamp(ExifTag.GPSTimeStamp, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder));
					else if (tag == 9) // GPSStatus
						return new ExifEnumProperty<GPSStatus>(ExifTag.GPSStatus, (GPSStatus)value[0]);
					else if (tag == 10) // GPSMeasureMode
						return new ExifEnumProperty<GPSMeasureMode>(ExifTag.GPSMeasureMode, (GPSMeasureMode)value[0]);
					else if (tag == 12) // GPSSpeedRef
						return new ExifEnumProperty<GPSSpeedRef>(ExifTag.GPSSpeedRef, (GPSSpeedRef)value[0]);
					else if (tag == 14) // GPSTrackRef
						return new ExifEnumProperty<GPSDirectionRef>(ExifTag.GPSTrackRef, (GPSDirectionRef)value[0]);
					else if (tag == 16) // GPSImgDirectionRef
						return new ExifEnumProperty<GPSDirectionRef>(ExifTag.GPSImgDirectionRef, (GPSDirectionRef)value[0]);
					else if (tag == 19) // GPSDestLatitudeRef
						return new ExifEnumProperty<GPSLatitudeRef>(ExifTag.GPSDestLatitudeRef, (GPSLatitudeRef)value[0]);
					else if (tag == 20) // GPSDestLatitude
						return new GPSLatitudeLongitude(ExifTag.GPSDestLatitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder));
					else if (tag == 21) // GPSDestLongitudeRef
						return new ExifEnumProperty<GPSLongitudeRef>(ExifTag.GPSDestLongitudeRef, (GPSLongitudeRef)value[0]);
					else if (tag == 22) // GPSDestLongitude
						return new GPSLatitudeLongitude(ExifTag.GPSDestLongitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder));
					else if (tag == 23) // GPSDestBearingRef
						return new ExifEnumProperty<GPSDirectionRef>(ExifTag.GPSDestBearingRef, (GPSDirectionRef)value[0]);
					else if (tag == 25) // GPSDestDistanceRef
						return new ExifEnumProperty<GPSDistanceRef>(ExifTag.GPSDestDistanceRef, (GPSDistanceRef)value[0]);
					else if (tag == 29) // GPSDate
						return new ExifDateTime(ExifTag.GPSDateStamp, ExifBitConverter.ToDateTime(value, false));
					else if (tag == 30) // GPSDifferential
						return new ExifEnumProperty<GPSDifferential>(ExifTag.GPSDifferential, (GPSDifferential)conv.ToUInt16(value, 0));
				}
				else if (ifd == IFD.Interop)
				{
					if (tag == 1) // InteroperabilityIndex
						return new ExifAscii(ExifTag.InteroperabilityIndex, ExifBitConverter.ToAscii(value));
					else if (tag == 2) // InteroperabilityVersion
						return new ExifVersion(ExifTag.InteroperabilityVersion, ExifBitConverter.ToAscii(value));
				}
				else if (ifd == IFD.First)
				{
					if (tag == 0x103) // Compression
						return new ExifEnumProperty<Compression>(ExifTag.ThumbnailCompression, (Compression)conv.ToUInt16(value, 0));
					else if (tag == 0x106) // PhotometricInterpretation
						return new ExifEnumProperty<PhotometricInterpretation>(ExifTag.ThumbnailPhotometricInterpretation, (PhotometricInterpretation)conv.ToUInt16(value, 0));
					else if (tag == 0x112) // Orientation
						return new ExifEnumProperty<Orientation>(ExifTag.ThumbnailOrientation, (Orientation)conv.ToUInt16(value, 0));
					else if (tag == 0x11c) // PlanarConfiguration
						return new ExifEnumProperty<PlanarConfiguration>(ExifTag.ThumbnailPlanarConfiguration, (PlanarConfiguration)conv.ToUInt16(value, 0));
					else if (tag == 0x213) // YCbCrPositioning
						return new ExifEnumProperty<YCbCrPositioning>(ExifTag.ThumbnailYCbCrPositioning, (YCbCrPositioning)conv.ToUInt16(value, 0));
					else if (tag == 0x128) // ResolutionUnit
						return new ExifEnumProperty<ResolutionUnit>(ExifTag.ThumbnailResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0));
					else if (tag == 0x132) // DateTime
						return new ExifDateTime(ExifTag.ThumbnailDateTime, ExifBitConverter.ToDateTime(value));
				}

				// Find the exif tag corresponding to given tag id
				ExifTag etag = ExifTagFactory.GetExifTag(ifd, tag);

				if (type == 1) // 1 = BYTE An 8-bit unsigned integer.
				{
					if (count == 1)
						return new ExifByte(etag, value[0]);
					else
						return new ExifByteArray(etag, value);
				}
				else if (type == 2) // 2 = ASCII An 8-bit byte containing one 7-bit ASCII code. 
				{
					return new ExifAscii(etag, ExifBitConverter.ToAscii(value));
				}
				else if (type == 3) // 3 = SHORT A 16-bit (2-byte) unsigned integer.
				{
					if (count == 1)
						return new ExifUShort(etag, conv.ToUInt16(value, 0));
					else
						return new ExifUShortArray(etag, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder));
				}
				else if (type == 4) // 4 = LONG A 32-bit (4-byte) unsigned integer.
				{
					if (count == 1)
						return new ExifUInt(etag, conv.ToUInt32(value, 0));
					else
						return new ExifUIntArray(etag, ExifBitConverter.ToUIntArray(value, (int)count, byteOrder));
				}
				else if (type == 5) // 5 = RATIONAL Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator.
				{
					if (count == 1)
						return new ExifURational(etag, ExifBitConverter.ToURational(value, byteOrder));
					else
						return new ExifURationalArray(etag, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder));
				}
				else if (type == 7) // 7 = UNDEFINED An 8-bit byte that can take any value depending on the field definition.
				{
					return new ExifUndefined(etag, value);
				}
				else if (type == 9) // 9 = SLONG A 32-bit (4-byte) signed integer (2's complement notation).
				{
					if (count == 1)
						return new ExifSInt(etag, conv.ToInt32(value, 0));
					else
						return new ExifSIntArray(etag, ExifBitConverter.ToSIntArray(value, (int)count, byteOrder));
				}
				else if (type == 10) // 10 = SRATIONAL Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator.
				{
					if (count == 1)
						return new ExifSRational(etag, ExifBitConverter.ToSRational(value, byteOrder));
					else
						return new ExifSRationalArray(etag, ExifBitConverter.ToSRationalArray(value, (int)count, byteOrder));
				}
				else
					throw new ArgumentException("Unknown property type.");
			}
			#endregion
		}
		/// <summary>
		/// Represents the base class for an Exif property.
		/// </summary>
		internal abstract class ExifProperty
		{
			protected ExifTag mTag;
			protected IFD mIFD;
			protected string mName;
			public ExifTag Tag { get { return mTag; } }
			public IFD IFD { get { return mIFD; } }
			public string Name
			{
				get
				{
					if (mName == null || mName.Length == 0)
						return ExifTagFactory.GetTagName(mTag);
					else
						return mName;
				}
				set
				{
					mName = value;
				}
			}
			protected abstract object _Value { get; set; }
			public object Value { get { return _Value; } set { _Value = value; } }
			public abstract ExifInterOperability Interoperability { get; }

			public ExifProperty(ExifTag tag)
			{
				mTag = tag;
				mIFD = ExifTagFactory.GetTagIFD(tag);
			}
		}

		/// <summary>
		/// Represents an 8-bit unsigned integer. (EXIF Specification: BYTE)
		/// </summary>
		internal class ExifByte : ExifProperty
		{
			protected byte mValue;
			protected override object _Value { get { return Value; } set { Value = (byte)value; } }
			public new byte Value { get { return mValue; } set { mValue = value; } }

			static public implicit operator byte(ExifByte obj) { return obj.mValue; }

			public override string ToString() { return mValue.ToString(); }

			public ExifByte(ExifTag tag, byte value)
				: base(tag)
			{
				mValue = value;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, 1, new byte[] { mValue });
				}
			}
		}

		/// <summary>
		/// Represents an array of 8-bit unsigned integers. (EXIF Specification: BYTE with count > 1)
		/// </summary>
		internal class ExifByteArray : ExifProperty
		{
			protected byte[] mValue;
			protected override object _Value { get { return Value; } set { Value = (byte[])value; } }
			public new byte[] Value { get { return mValue; } set { mValue = value; } }

			static public implicit operator byte[] (ExifByteArray obj) { return obj.mValue; }

			public override string ToString()
			{
				StringBuilder sb = new StringBuilder();
				sb.Append('[');
				foreach (byte b in mValue)
				{
					sb.Append(b);
					sb.Append(' ');
				}
				sb.Remove(sb.Length - 1, 1);
				sb.Append(']');
				return sb.ToString();
			}

			public ExifByteArray(ExifTag tag, byte[] value)
				: base(tag)
			{
				mValue = value;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, (uint)mValue.Length, mValue);
				}
			}
		}

		/// <summary>
		/// Represents an ASCII string. (EXIF Specification: ASCII)
		/// </summary>
		internal class ExifAscii : ExifProperty
		{
			protected string mValue;
			protected override object _Value { get { return Value; } set { Value = (string)value; } }
			public new string Value { get { return mValue; } set { mValue = value; } }

			static public implicit operator string(ExifAscii obj) { return obj.mValue; }

			public override string ToString() { return mValue; }

			public ExifAscii(ExifTag tag, string value)
				: base(tag)
			{
				mValue = value;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 2, (uint)mValue.Length + 1, ExifBitConverter.GetBytes(mValue, true));
				}
			}
		}

		/// <summary>
		/// Represents a 16-bit unsigned integer. (EXIF Specification: SHORT)
		/// </summary>
		internal class ExifUShort : ExifProperty
		{
			protected ushort mValue;
			protected override object _Value { get { return Value; } set { Value = (ushort)value; } }
			public new ushort Value { get { return mValue; } set { mValue = value; } }

			static public implicit operator ushort(ExifUShort obj) { return obj.mValue; }

			public override string ToString() { return mValue.ToString(); }

			public ExifUShort(ExifTag tag, ushort value)
				: base(tag)
			{
				mValue = value;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 3, 1, ExifBitConverter.GetBytes(mValue, BitConverterEx.ByteOrder.System, BitConverterEx.ByteOrder.System));
				}
			}
		}

		/// <summary>
		/// Represents an array of 16-bit unsigned integers. 
		/// (EXIF Specification: SHORT with count > 1)
		/// </summary>
		internal class ExifUShortArray : ExifProperty
		{
			protected ushort[] mValue;
			protected override object _Value { get { return Value; } set { Value = (ushort[])value; } }
			public new ushort[] Value { get { return mValue; } set { mValue = value; } }

			static public implicit operator ushort[] (ExifUShortArray obj) { return obj.mValue; }

			public override string ToString()
			{
				StringBuilder sb = new StringBuilder();
				sb.Append('[');
				foreach (ushort b in mValue)
				{
					sb.Append(b);
					sb.Append(' ');
				}
				sb.Remove(sb.Length - 1, 1);
				sb.Append(']');
				return sb.ToString();
			}

			public ExifUShortArray(ExifTag tag, ushort[] value)
				: base(tag)
			{
				mValue = value;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 3, (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.ByteOrder.System));
				}
			}
		}

		/// <summary>
		/// Represents a 32-bit unsigned integer. (EXIF Specification: LONG)
		/// </summary>
		internal class ExifUInt : ExifProperty
		{
			protected uint mValue;
			protected override object _Value { get { return Value; } set { Value = (uint)value; } }
			public new uint Value { get { return mValue; } set { mValue = value; } }

			static public implicit operator uint(ExifUInt obj) { return obj.mValue; }

			public override string ToString() { return mValue.ToString(); }

			public ExifUInt(ExifTag tag, uint value)
				: base(tag)
			{
				mValue = value;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 4, 1, ExifBitConverter.GetBytes(mValue, BitConverterEx.ByteOrder.System, BitConverterEx.ByteOrder.System));
				}
			}
		}

		/// <summary>
		/// Represents an array of 16-bit unsigned integers. 
		/// (EXIF Specification: LONG with count > 1)
		/// </summary>
		internal class ExifUIntArray : ExifProperty
		{
			protected uint[] mValue;
			protected override object _Value { get { return Value; } set { Value = (uint[])value; } }
			public new uint[] Value { get { return mValue; } set { mValue = value; } }

			static public implicit operator uint[] (ExifUIntArray obj) { return obj.mValue; }

			public override string ToString()
			{
				StringBuilder sb = new StringBuilder();
				sb.Append('[');
				foreach (uint b in mValue)
				{
					sb.Append(b);
					sb.Append(' ');
				}
				sb.Remove(sb.Length - 1, 1);
				sb.Append(']');
				return sb.ToString();
			}

			public ExifUIntArray(ExifTag tag, uint[] value)
				: base(tag)
			{
				mValue = value;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 3, (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.ByteOrder.System));
				}
			}
		}

		/// <summary>
		/// Represents a rational number defined with a 32-bit unsigned numerator 
		/// and denominator. (EXIF Specification: RATIONAL)
		/// </summary>
		internal class ExifURational : ExifProperty
		{
			protected MathEx.UFraction32 mValue;
			protected override object _Value { get { return Value; } set { Value = (MathEx.UFraction32)value; } }
			public new MathEx.UFraction32 Value { get { return mValue; } set { mValue = value; } }

			public override string ToString() { return mValue.ToString(); }
			public float ToFloat() { return (float)mValue; }

			static public explicit operator float(ExifURational obj) { return (float)obj.mValue; }

			public uint[] ToArray()
			{
				return new uint[] { mValue.Numerator, mValue.Denominator };
			}

			public ExifURational(ExifTag tag, uint numerator, uint denominator)
				: base(tag)
			{
				mValue = new MathEx.UFraction32(numerator, denominator);
			}

			public ExifURational(ExifTag tag, MathEx.UFraction32 value)
				: base(tag)
			{
				mValue = value;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 5, 1, ExifBitConverter.GetBytes(mValue, BitConverterEx.ByteOrder.System));
				}
			}
		}

		/// <summary>
		/// Represents an array of unsigned rational numbers. 
		/// (EXIF Specification: RATIONAL with count > 1)
		/// </summary>
		internal class ExifURationalArray : ExifProperty
		{
			protected MathEx.UFraction32[] mValue;
			protected override object _Value { get { return Value; } set { Value = (MathEx.UFraction32[])value; } }
			public new MathEx.UFraction32[] Value { get { return mValue; } set { mValue = value; } }

			static public explicit operator float[] (ExifURationalArray obj)
			{
				float[] result = new float[obj.mValue.Length];
				for (int i = 0; i < obj.mValue.Length; i++)
					result[i] = (float)obj.mValue[i];
				return result;
			}

			public override string ToString()
			{
				StringBuilder sb = new StringBuilder();
				sb.Append('[');
				foreach (MathEx.UFraction32 b in mValue)
				{
					sb.Append(b.ToString());
					sb.Append(' ');
				}
				sb.Remove(sb.Length - 1, 1);
				sb.Append(']');
				return sb.ToString();
			}

			public ExifURationalArray(ExifTag tag, MathEx.UFraction32[] value)
				: base(tag)
			{
				mValue = value;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 5, (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.ByteOrder.System));
				}
			}
		}

		/// <summary>
		/// Represents a byte array that can take any value. (EXIF Specification: UNDEFINED)
		/// </summary>
		internal class ExifUndefined : ExifProperty
		{
			protected byte[] mValue;
			protected override object _Value { get { return Value; } set { Value = (byte[])value; } }
			public new byte[] Value { get { return mValue; } set { mValue = value; } }

			static public implicit operator byte[] (ExifUndefined obj) { return obj.mValue; }

			public override string ToString()
			{
				StringBuilder sb = new StringBuilder();
				sb.Append('[');
				foreach (byte b in mValue)
				{
					sb.Append(b);
					sb.Append(' ');
				}
				sb.Remove(sb.Length - 1, 1);
				sb.Append(']');
				return sb.ToString();
			}

			public ExifUndefined(ExifTag tag, byte[] value)
				: base(tag)
			{
				mValue = value;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 7, (uint)mValue.Length, mValue);
				}
			}
		}

		/// <summary>
		/// Represents a 32-bit signed integer. (EXIF Specification: SLONG)
		/// </summary>
		internal class ExifSInt : ExifProperty
		{
			protected int mValue;
			protected override object _Value { get { return Value; } set { Value = (int)value; } }
			public new int Value { get { return mValue; } set { mValue = value; } }

			public override string ToString() { return mValue.ToString(); }

			static public implicit operator int(ExifSInt obj) { return obj.mValue; }

			public ExifSInt(ExifTag tag, int value)
				: base(tag)
			{
				mValue = value;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 9, 1, ExifBitConverter.GetBytes(mValue, BitConverterEx.ByteOrder.System, BitConverterEx.ByteOrder.System));
				}
			}
		}

		/// <summary>
		/// Represents an array of 32-bit signed integers. 
		/// (EXIF Specification: SLONG with count > 1)
		/// </summary>
		internal class ExifSIntArray : ExifProperty
		{
			protected int[] mValue;
			protected override object _Value { get { return Value; } set { Value = (int[])value; } }
			public new int[] Value { get { return mValue; } set { mValue = value; } }

			public override string ToString()
			{
				StringBuilder sb = new StringBuilder();
				sb.Append('[');
				foreach (int b in mValue)
				{
					sb.Append(b);
					sb.Append(' ');
				}
				sb.Remove(sb.Length - 1, 1);
				sb.Append(']');
				return sb.ToString();
			}

			static public implicit operator int[] (ExifSIntArray obj) { return obj.mValue; }

			public ExifSIntArray(ExifTag tag, int[] value)
				: base(tag)
			{
				mValue = value;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 9, (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.ByteOrder.System));
				}
			}
		}

		/// <summary>
		/// Represents a rational number defined with a 32-bit signed numerator 
		/// and denominator. (EXIF Specification: SRATIONAL)
		/// </summary>
		internal class ExifSRational : ExifProperty
		{
			protected MathEx.Fraction32 mValue;
			protected override object _Value { get { return Value; } set { Value = (MathEx.Fraction32)value; } }
			public new MathEx.Fraction32 Value { get { return mValue; } set { mValue = value; } }

			public override string ToString() { return mValue.ToString(); }
			public float ToFloat() { return (float)mValue; }

			static public explicit operator float(ExifSRational obj) { return (float)obj.mValue; }

			public int[] ToArray()
			{
				return new int[] { mValue.Numerator, mValue.Denominator };
			}

			public ExifSRational(ExifTag tag, int numerator, int denominator)
				: base(tag)
			{
				mValue = new MathEx.Fraction32(numerator, denominator);
			}

			public ExifSRational(ExifTag tag, MathEx.Fraction32 value)
				: base(tag)
			{
				mValue = value;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 10, 1, ExifBitConverter.GetBytes(mValue, BitConverterEx.ByteOrder.System));
				}
			}
		}

		/// <summary>
		/// Represents an array of signed rational numbers. 
		/// (EXIF Specification: SRATIONAL with count > 1)
		/// </summary>
		internal class ExifSRationalArray : ExifProperty
		{
			protected MathEx.Fraction32[] mValue;
			protected override object _Value { get { return Value; } set { Value = (MathEx.Fraction32[])value; } }
			public new MathEx.Fraction32[] Value { get { return mValue; } set { mValue = value; } }

			static public explicit operator float[] (ExifSRationalArray obj)
			{
				float[] result = new float[obj.mValue.Length];
				for (int i = 0; i < obj.mValue.Length; i++)
					result[i] = (float)obj.mValue[i];
				return result;
			}

			public override string ToString()
			{
				StringBuilder sb = new StringBuilder();
				sb.Append('[');
				foreach (MathEx.Fraction32 b in mValue)
				{
					sb.Append(b.ToString());
					sb.Append(' ');
				}
				sb.Remove(sb.Length - 1, 1);
				sb.Append(']');
				return sb.ToString();
			}

			public ExifSRationalArray(ExifTag tag, MathEx.Fraction32[] value)
				: base(tag)
			{
				mValue = value;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 10, (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.ByteOrder.System));
				}
			}
		}
		/// <summary>
		/// Represents interoperability data for an exif tag in the platform byte order.
		/// </summary>
		internal struct ExifInterOperability
		{
			private ushort mTagID;
			private ushort mTypeID;
			private uint mCount;
			private byte[] mData;

			/// <summary>
			/// Gets the tag ID defined in the Exif standard.
			/// </summary>
			public ushort TagID { get { return mTagID; } }
			/// <summary>
			/// Gets the type code defined in the Exif standard.
			/// <list type="bullet">
			/// <item>1 = BYTE (byte)</item>
			/// <item>2 = ASCII (byte array)</item>
			/// <item>3 = SHORT (ushort)</item>
			/// <item>4 = LONG (uint)</item>
			/// <item>5 = RATIONAL (2 x uint: numerator, denominator)</item>
			/// <item>7 = UNDEFINED (byte array)</item>
			/// <item>9 = SLONG (int)</item>
			/// <item>10 = SRATIONAL (2 x int: numerator, denominator)</item>
			/// </list>
			/// </summary>
			public ushort TypeID { get { return mTypeID; } }
			/// <summary>
			/// Gets the byte count or number of components.
			/// </summary>
			public uint Count { get { return mCount; } }
			/// <summary>
			/// Gets the field value as an array of bytes.
			/// </summary>
			public byte[] Data { get { return mData; } }
			/// <summary>
			/// Returns the string representation of this instance.
			/// </summary>
			/// <returns></returns>
			public override string ToString()
			{
				return string.Format("Tag: {0}, Type: {1}, Count: {2}, Data Length: {3}", mTagID, mTypeID, mCount, mData.Length);
			}

			public ExifInterOperability(ushort tagid, ushort typeid, uint count, byte[] data)
			{
				mTagID = tagid;
				mTypeID = typeid;
				mCount = count;
				mData = data;
			}
		}
		internal static class ExifExtensionMethods
		{
			/// <summary>
			/// Reads count bytes from the current stream into a byte array and advances
			/// the current position by count bytes.
			/// </summary>
			/// <param name="count">The number of bytes to read.</param>
			/// <returns>
			/// A byte array of given size read from the stream, or null
			/// if end of file is reached before reading count bytes.
			/// </returns>
			public static byte[] ReadBytes(FileStream stream, int count)
			{
				byte[] buffer = new byte[count];
				int offset = 0;
				int remaining = count;
				while (remaining > 0)
				{
					int read = stream.Read(buffer, offset, remaining);
					if (read <= 0)
						return null;
					remaining -= read;
					offset += read;
				}
				return buffer;
			}

			/// <summary>
			/// Writes the given byte array to the current stream and advances
			///  the current position by the length of the array.
			/// </summary>
			/// <param name="buffer">A byte array containing the data to write.</param>
			public static void WriteBytes(FileStream stream, byte[] buffer)
			{
				stream.Write(buffer, 0, buffer.Length);
			}
		}
		/// <summary>
		/// Represents an enumerated value.
		/// </summary>
		internal class ExifEnumProperty<T> : ExifProperty
		{
			protected T mValue;
			protected bool mIsBitField;
			protected override object _Value { get { return Value; } set { Value = (T)value; } }
			public new T Value { get { return mValue; } set { mValue = value; } }
			public bool IsBitField { get { return mIsBitField; } }

			static public implicit operator T(ExifEnumProperty<T> obj) { return (T)obj.mValue; }

			public override string ToString() { return mValue.ToString(); }

			public ExifEnumProperty(ExifTag tag, T value, bool isbitfield)
				: base(tag)
			{
				mValue = value;
				mIsBitField = isbitfield;
			}

			public ExifEnumProperty(ExifTag tag, T value)
				: this(tag, value, false)
			{
				;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					ushort tagid = ExifTagFactory.GetTagID(mTag);

					Type type = typeof(T);
					Type basetype = Enum.GetUnderlyingType(type);

					if (type == typeof(FileSource) || type == typeof(SceneType))
					{
						// UNDEFINED
						return new ExifInterOperability(tagid, 7, 1, new byte[] { (byte)((object)mValue) });
					}
					else if (type == typeof(GPSLatitudeRef) || type == typeof(GPSLongitudeRef) ||
						type == typeof(GPSStatus) || type == typeof(GPSMeasureMode) ||
						type == typeof(GPSSpeedRef) || type == typeof(GPSDirectionRef) ||
						type == typeof(GPSDistanceRef))
					{
						// ASCII
						return new ExifInterOperability(tagid, 2, 2, new byte[] { (byte)((object)mValue), 0 });
					}
					else if (basetype == typeof(byte))
					{
						// BYTE
						return new ExifInterOperability(tagid, 1, 1, new byte[] { (byte)((object)mValue) });
					}
					else if (basetype == typeof(ushort))
					{
						// SHORT
						return new ExifInterOperability(tagid, 3, 1, ExifBitConverter.GetBytes((ushort)((object)mValue), BitConverterEx.ByteOrder.System, BitConverterEx.ByteOrder.System));
					}
					else
						throw new UnknownEnumTypeException();
				}
			}
		}

		/// <summary>
		/// Represents an ASCII string. (EXIF Specification: UNDEFINED) Used for the UserComment field.
		/// </summary>
		internal class ExifEncodedString : ExifProperty
		{
			protected string mValue;
			private Encoding mEncoding;
			protected override object _Value { get { return Value; } set { Value = (string)value; } }
			public new string Value { get { return mValue; } set { mValue = value; } }
			public Encoding Encoding { get { return mEncoding; } set { mEncoding = value; } }

			static public implicit operator string(ExifEncodedString obj) { return obj.mValue; }

			public override string ToString() { return mValue; }

			public ExifEncodedString(ExifTag tag, string value, Encoding encoding)
				: base(tag)
			{
				mValue = value;
				mEncoding = encoding;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					string enc = "";
					if (mEncoding == null)
						enc = "\0\0\0\0\0\0\0\0";
					else if (mEncoding.EncodingName == "US-ASCII")
						enc = "ASCII\0\0\0";
					else if (mEncoding.EncodingName == "Japanese (JIS 0208-1990 and 0212-1990)")
						enc = "JIS\0\0\0\0\0";
					else if (mEncoding.EncodingName == "Unicode")
						enc = "Unicode\0";
					else
						enc = "\0\0\0\0\0\0\0\0";

					byte[] benc = Encoding.ASCII.GetBytes(enc);
					byte[] bstr = (mEncoding == null ? Encoding.ASCII.GetBytes(mValue) : mEncoding.GetBytes(mValue));
					byte[] data = new byte[benc.Length + bstr.Length];
					Array.Copy(benc, 0, data, 0, benc.Length);
					Array.Copy(bstr, 0, data, benc.Length, bstr.Length);

					return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 7, (uint)data.Length, data);
				}
			}
		}

		/// <summary>
		/// Represents an ASCII string formatted as DateTime. (EXIF Specification: ASCII) Used for the date time fields.
		/// </summary>
		internal class ExifDateTime : ExifProperty
		{
			protected DateTime mValue;
			protected override object _Value { get { return Value; } set { Value = (DateTime)value; } }
			public new DateTime Value { get { return mValue; } set { mValue = value; } }

			static public implicit operator DateTime(ExifDateTime obj) { return obj.mValue; }

			public override string ToString() { return mValue.ToString("yyyy/MM/dd HH:mm:ss"); }

			public ExifDateTime(ExifTag tag, DateTime value)
				: base(tag)
			{
				mValue = value;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 2, (uint)20, ExifBitConverter.GetBytes(mValue, true));
				}
			}
		}

		/// <summary>
		/// Represents the exif version as a 4 byte ASCII string. (EXIF Specification: UNDEFINED) 
		/// Used for the ExifVersion, FlashpixVersion, InteroperabilityVersion and GPSVersionID fields.
		/// </summary>
		internal class ExifVersion : ExifProperty
		{
			protected string mValue;
			protected override object _Value { get { return Value; } set { Value = (string)value; } }
			public new string Value { get { return mValue; } set { mValue = value.Substring(0, 4); } }

			public ExifVersion(ExifTag tag, string value)
				: base(tag)
			{
				if (value.Length > 4)
					mValue = value.Substring(0, 4);
				else if (value.Length < 4)
					mValue = value + new string(' ', 4 - value.Length);
				else
					mValue = value;
			}

			public override string ToString()
			{
				return mValue;
			}

			public override ExifInterOperability Interoperability
			{
				get
				{
					if (mTag == ExifTag.ExifVersion || mTag == ExifTag.FlashpixVersion || mTag == ExifTag.InteroperabilityVersion)
						return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 7, 4, Encoding.ASCII.GetBytes(mValue));
					else
					{
						byte[] data = new byte[4];
						for (int i = 0; i < 4; i++)
							data[i] = byte.Parse(mValue[0].ToString());
						return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 7, 4, data);
					}
				}
			}
		}

		/// <summary>
		/// Represents the location and area of the subject (EXIF Specification: 2xSHORT)
		/// The coordinate values, width, and height are expressed in relation to the 
		/// upper left as origin, prior to rotation processing as per the Rotation tag.
		/// </summary>
		internal class ExifPointSubjectArea : ExifUShortArray
		{
			protected new ushort[] Value { get { return mValue; } set { mValue = value; } }
			public ushort X { get { return mValue[0]; } set { mValue[0] = value; } }
			public ushort Y { get { return mValue[1]; } set { mValue[1] = value; } }

			public override string ToString()
			{
				StringBuilder sb = new StringBuilder();
				sb.AppendFormat("({0:d}, {1:d})", mValue[0], mValue[1]);
				return sb.ToString();
			}

			public ExifPointSubjectArea(ExifTag tag, ushort[] value)
				: base(tag, value)
			{
				;
			}
		}

		/// <summary>
		/// Represents the location and area of the subject (EXIF Specification: 3xSHORT)
		/// The coordinate values, width, and height are expressed in relation to the 
		/// upper left as origin, prior to rotation processing as per the Rotation tag.
		/// </summary>
		internal class ExifCircularSubjectArea : ExifPointSubjectArea
		{
			public ushort Diamater { get { return mValue[2]; } set { mValue[2] = value; } }

			public override string ToString()
			{
				StringBuilder sb = new StringBuilder();
				sb.AppendFormat("({0:d}, {1:d}) {2:d}", mValue[0], mValue[1], mValue[2]);
				return sb.ToString();
			}

			public ExifCircularSubjectArea(ExifTag tag, ushort[] value)
				: base(tag, value)
			{
				;
			}
		}

		/// <summary>
		/// Represents the location and area of the subject (EXIF Specification: 4xSHORT)
		/// The coordinate values, width, and height are expressed in relation to the 
		/// upper left as origin, prior to rotation processing as per the Rotation tag.
		/// </summary>
		internal class ExifRectangularSubjectArea : ExifPointSubjectArea
		{
			public ushort Width { get { return mValue[2]; } set { mValue[2] = value; } }
			public ushort Height { get { return mValue[3]; } set { mValue[3] = value; } }

			public override string ToString()
			{
				StringBuilder sb = new StringBuilder();
				sb.AppendFormat("({0:d}, {1:d}) ({2:d} x {3:d})", mValue[0], mValue[1], mValue[2], mValue[3]);
				return sb.ToString();
			}

			public ExifRectangularSubjectArea(ExifTag tag, ushort[] value)
				: base(tag, value)
			{
				;
			}
		}

		/// <summary>
		/// Represents GPS latitudes and longitudes (EXIF Specification: 3xRATIONAL)
		/// </summary>
		internal class GPSLatitudeLongitude : ExifURationalArray
		{
			protected new MathEx.UFraction32[] Value { get { return mValue; } set { mValue = value; } }
			public MathEx.UFraction32 Degrees { get { return mValue[0]; } set { mValue[0] = value; } }
			public MathEx.UFraction32 Minutes { get { return mValue[1]; } set { mValue[1] = value; } }
			public MathEx.UFraction32 Seconds { get { return mValue[2]; } set { mValue[2] = value; } }

			public static explicit operator float(GPSLatitudeLongitude obj) { return obj.ToFloat(); }
			public float ToFloat()
			{
				return (float)Degrees + ((float)Minutes) / 60.0f + ((float)Seconds) / 3600.0f;
			}

			public override string ToString()
			{
				return string.Format("{0:F2}°{1:F2}'{2:F2}\"", (float)Degrees, (float)Minutes, (float)Seconds);
			}

			public GPSLatitudeLongitude(ExifTag tag, MathEx.UFraction32[] value)
				: base(tag, value)
			{
				;
			}
		}

		/// <summary>
		/// Represents a GPS time stamp as UTC (EXIF Specification: 3xRATIONAL)
		/// </summary>
		internal class GPSTimeStamp : ExifURationalArray
		{
			protected new MathEx.UFraction32[] Value { get { return mValue; } set { mValue = value; } }
			public MathEx.UFraction32 Hour { get { return mValue[0]; } set { mValue[0] = value; } }
			public MathEx.UFraction32 Minute { get { return mValue[1]; } set { mValue[1] = value; } }
			public MathEx.UFraction32 Second { get { return mValue[2]; } set { mValue[2] = value; } }

			public override string ToString()
			{
				return string.Format("{0:F2}:{1:F2}:{2:F2}\"", (float)Hour, (float)Minute, (float)Second);
			}

			public GPSTimeStamp(ExifTag tag, MathEx.UFraction32[] value)
				: base(tag, value)
			{
				;
			}
		}
		/// <summary>
		/// The exception that is thrown when the IFD section ID could not be understood.
		/// </summary>
		internal class UnknownIFDSectionException : Exception
		{
			public UnknownIFDSectionException()
				: base("Unknown IFD section.")
			{
				;
			}

			public UnknownIFDSectionException(string message)
				: base(message)
			{
				;
			}
		}

		/// <summary>
		/// The exception that is thrown when an invalid enum type is given to an 
		/// ExifEnumProperty.
		/// </summary>
		internal class UnknownEnumTypeException : Exception
		{
			public UnknownEnumTypeException()
				: base("Unknown enum type.")
			{
				;
			}

			public UnknownEnumTypeException(string message)
				: base(message)
			{
				;
			}
		}

		/// <summary>
		/// The exception that is thrown when the 0th IFD section does not contain any fields.
		/// </summary>
		internal class IFD0IsEmptyException : Exception
		{
			public IFD0IsEmptyException()
				: base("0th IFD section cannot be empty.")
			{
				;
			}

			public IFD0IsEmptyException(string message)
				: base(message)
			{
				;
			}
		}
		internal enum Compression : ushort
		{
			Uncompressed = 1,
			JPEGCompression = 6,
		}

		internal enum PhotometricInterpretation : ushort
		{
			RGB = 2,
			YCbCr = 6,
		}

		internal enum Orientation : ushort
		{
			Normal = 1,
			MirroredVertically = 2,
			Rotated180 = 3,
			MirroredHorizontally = 4,
			RotatedLeftAndMirroredVertically = 5,
			RotatedRight = 6,
			RotatedLeft = 7,
			RotatedRightAndMirroredVertically = 8,
		}

		internal enum PlanarConfiguration : ushort
		{
			ChunkyFormat = 1,
			PlanarFormat = 2,
		}

		internal enum YCbCrPositioning : ushort
		{
			Centered = 1,
			CoSited = 2,
		}

		internal enum ResolutionUnit : ushort
		{
			Inches = 2,
			Centimeters = 3,
		}

		internal enum ColorSpace : ushort
		{
			sRGB = 1,
			Uncalibrated = 0xfff,
		}

		internal enum ExposureProgram : ushort
		{
			NotDefined = 0,
			Manual = 1,
			Normal = 2,
			AperturePriority = 3,
			ShutterPriority = 4,
			/// <summary>
			/// Biased toward depth of field.
			/// </summary>
			Creative = 5,
			/// <summary>
			/// Biased toward fast shutter speed.
			/// </summary>
			Action = 6,
			/// <summary>
			/// For closeup photos with the background out of focus.
			/// </summary>
			Portrait = 7,
			/// <summary>
			/// For landscape photos with the background in focus.
			/// </summary>
			Landscape = 8,
		}

		internal enum MeteringMode : ushort
		{
			Unknown = 0,
			Average = 1,
			CenterWeightedAverage = 2,
			Spot = 3,
			MultiSpot = 4,
			Pattern = 5,
			Partial = 6,
			Other = 255,
		}

		internal enum LightSource : ushort
		{
			Unknown = 0,
			Daylight = 1,
			Fluorescent = 2,
			Tungsten = 3,
			Flash = 4,
			FineWeather = 9,
			CloudyWeather = 10,
			Shade = 11,
			/// <summary>
			/// D 5700 – 7100K
			/// </summary>
			DaylightFluorescent = 12,
			/// <summary>
			/// N 4600 – 5400K
			/// </summary>
			DayWhiteFluorescent = 13,
			/// <summary>
			/// W 3900 – 4500K
			/// </summary>
			CoolWhiteFluorescent = 14,
			/// <summary>
			/// WW 3200 – 3700K
			/// </summary>
			WhiteFluorescent = 15,
			StandardLightA = 17,
			StandardLightB = 18,
			StandardLightC = 19,
			D55 = 20,
			D65 = 21,
			D75 = 22,
			D50 = 23,
			ISOStudioTungsten = 24,
			OtherLightSource = 255,
		}

		[Flags]
		internal enum Flash : ushort
		{
			NotFired = 0,
			Fired = 1,
			ReturnDetected = 2, // StrobeReturnLightDetected
			ReturnNotDetected = 4, // StrobeReturnLightNotDetected
			CompulsoryMode = 8, // CompulsoryFlashMode
			AutoMode = 16,
			NoFlashFunction = 32,
			RedEyeReductionMode = 64,
		}

		internal enum SensingMethod : ushort
		{
			NotDefined = 1,
			OneChipColorAreaSensor = 2,
			TwoChipColorAreaSensor = 3,
			ThreeChipColorAreaSensor = 4,
			ColorSequentialAreaSensor = 5,
			TriLinearSensor = 7,
			ColorSequentialLinearSensor = 8,
		}

		internal enum FileSource : byte // UNDEFINED
		{
			DSC = 3,
		}

		internal enum SceneType : byte // UNDEFINED
		{
			DirectlyPhotographedImage = 1,
		}

		internal enum CustomRendered : ushort
		{
			NormalProcess = 0,
			CustomProcess = 1,
		}

		internal enum ExposureMode : ushort
		{
			Auto = 0,
			Manual = 1,
			AutoBracket = 2,
		}

		internal enum WhiteBalance : ushort
		{
			Auto = 0,
			Manual = 1,
		}

		internal enum SceneCaptureType : ushort
		{
			Standard = 0,
			Landscape = 1,
			Portrait = 2,
			NightScene = 3,
		}

		internal enum GainControl : ushort
		{
			None = 0,
			LowGainUp = 1,
			HighGainUp = 2,
			LowGainDown = 3,
			HighGainDown = 4,
		}

		internal enum Contrast : ushort
		{
			Normal = 0,
			Soft = 1,
			Hard = 2,
		}

		internal enum Saturation : ushort
		{
			Normal = 0,
			Low = 1,
			High = 2,
		}

		internal enum Sharpness : ushort
		{
			Normal = 0,
			Soft = 1,
			Hard = 2,
		}

		internal enum SubjectDistanceRange : ushort
		{
			Unknown = 0,
			Macro = 1,
			CloseView = 2,
			DistantView = 3,
		}

		internal enum GPSLatitudeRef : byte // ASCII
		{
			North = 78, // 'N'
			South = 83, // 'S'
		}

		internal enum GPSLongitudeRef : byte // ASCII
		{
			West = 87, // 'W'
			East = 69, // 'E'
		}

		internal enum GPSAltitudeRef : byte
		{
			AboveSeaLevel = 0,
			BelowSeaLevel = 1,
		}

		internal enum GPSStatus : byte // ASCII
		{
			MeasurementInProgress = 65, // 'A'
			MeasurementInteroperability = 86, // 'V'
		}

		internal enum GPSMeasureMode : byte // ASCII
		{
			TwoDimensional = 50, // '2'
			ThreeDimensional = 51, // '3'
		}

		internal enum GPSSpeedRef : byte // ASCII
		{
			KilometersPerHour = 75, // 'K'
			MilesPerHour = 77, // 'M'
			Knots = 78, // 'N'
		}

		internal enum GPSDirectionRef : byte // ASCII
		{
			TrueDirection = 84, // 'T'
			MagneticDirection = 77, // 'M'
		}

		internal enum GPSDistanceRef : byte // ASCII
		{
			Kilometers = 75, // 'K'
			Miles = 77, // 'M'
			Knots = 78, // 'N'
		}

		internal enum GPSDifferential : ushort
		{
			MeasurementWithoutDifferentialCorrection = 0,
			DifferentialCorrectionApplied = 1,
		}
		/// <summary>
		/// Converts between exif data types and array of bytes.
		/// </summary>
		internal class ExifBitConverter : BitConverterEx
		{
			#region "Constructors"
			public ExifBitConverter(ByteOrder from, ByteOrder to)
				: base(from, to)
			{
				;
			}
			#endregion

			#region "Static Methods"
			/// <summary>
			/// Returns an ASCII string converted from the given byte array.
			/// </summary>
			public static string ToAscii(byte[] data, bool endatfirstnull)
			{
				int len = data.Length;
				if (endatfirstnull)
				{
					len = Array.IndexOf(data, (byte)0);
					if (len == -1) len = data.Length;
				}
				return Encoding.ASCII.GetString(data, 0, len);
			}

			/// <summary>
			/// Returns an ASCII string converted from the given byte array.
			/// </summary>
			public static string ToAscii(byte[] data)
			{
				return ToAscii(data, true);
			}

			/// <summary>
			/// Returns a string converted from the given byte array.
			/// from the numeric value of each byte.
			/// </summary>
			public static string ToString(byte[] data)
			{
				StringBuilder sb = new StringBuilder();
				foreach (byte b in data)
					sb.Append(b);
				return sb.ToString();
			}

			/// <summary>
			/// Returns a DateTime object converted from the given byte array.
			/// </summary>
			public static DateTime ToDateTime(byte[] data, bool hastime)
			{
				string str = ToAscii(data);
				if (str.Substring(0, 10) == "0000:00:00") str = "0001:01:01 " + str.Substring(11, 8);
				if (hastime)
					return DateTime.ParseExact(str, "yyyy:MM:dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
				else
					return DateTime.ParseExact(str, "yyyy:MM:dd", System.Globalization.CultureInfo.InvariantCulture);
			}

			/// <summary>
			/// Returns a DateTime object converted from the given byte array.
			/// </summary>
			public static DateTime ToDateTime(byte[] data)
			{
				return ToDateTime(data, true);
			}

			/// <summary>
			/// Returns an unsigned rational number converted from the first 
			/// eight bytes of the given byte array. The first four bytes are
			/// assumed to be the numerator and the next four bytes are the
			/// denumerator.
			/// Numbers are converted from the given byte-order to platform byte-order.
			/// </summary>
			public static MathEx.UFraction32 ToURational(byte[] data, ByteOrder frombyteorder)
			{
				byte[] num = new byte[4];
				byte[] den = new byte[4];
				Array.Copy(data, 0, num, 0, 4);
				Array.Copy(data, 4, den, 0, 4);
				return new MathEx.UFraction32(ToUInt32(num, 0, frombyteorder, ByteOrder.System), ToUInt32(den, 0, frombyteorder, ByteOrder.System));
			}

			/// <summary>
			/// Returns a signed rational number converted from the first 
			/// eight bytes of the given byte array. The first four bytes are
			/// assumed to be the numerator and the next four bytes are the
			/// denumerator.
			/// Numbers are converted from the given byte-order to platform byte-order.
			/// </summary>
			public static MathEx.Fraction32 ToSRational(byte[] data, ByteOrder frombyteorder)
			{
				byte[] num = new byte[4];
				byte[] den = new byte[4];
				Array.Copy(data, 0, num, 0, 4);
				Array.Copy(data, 4, den, 0, 4);
				return new MathEx.Fraction32(ToInt32(num, 0, frombyteorder, ByteOrder.System), ToInt32(den, 0, frombyteorder, ByteOrder.System));
			}

			/// <summary>
			/// Returns an array of 16-bit unsigned integers converted from 
			/// the given byte array.
			/// Numbers are converted from the given byte-order to platform byte-order.
			/// </summary>
			public static ushort[] ToUShortArray(byte[] data, int count, ByteOrder frombyteorder)
			{
				ushort[] numbers = new ushort[count];
				for (uint i = 0; i < count; i++)
				{
					byte[] num = new byte[2];
					Array.Copy(data, i * 2, num, 0, 2);
					numbers[i] = ToUInt16(num, 0, frombyteorder, ByteOrder.System);
				}
				return numbers;
			}

			/// <summary>
			/// Returns an array of 32-bit unsigned integers converted from 
			/// the given byte array.
			/// Numbers are converted from the given byte-order to platform byte-order.
			/// </summary>
			public static uint[] ToUIntArray(byte[] data, int count, ByteOrder frombyteorder)
			{
				uint[] numbers = new uint[count];
				for (uint i = 0; i < count; i++)
				{
					byte[] num = new byte[4];
					Array.Copy(data, i * 4, num, 0, 4);
					numbers[i] = ToUInt32(num, 0, frombyteorder, ByteOrder.System);
				}
				return numbers;
			}

			/// <summary>
			/// Returns an array of 32-bit signed integers converted from 
			/// the given byte array.
			/// Numbers are converted from the given byte-order to platform byte-order.
			/// </summary>
			public static int[] ToSIntArray(byte[] data, int count, ByteOrder byteorder)
			{
				int[] numbers = new int[count];
				for (uint i = 0; i < count; i++)
				{
					byte[] num = new byte[4];
					Array.Copy(data, i * 4, num, 0, 4);
					numbers[i] = ToInt32(num, 0, byteorder, ByteOrder.System);
				}
				return numbers;
			}

			/// <summary>
			/// Returns an array of unsigned rational numbers converted from 
			/// the given byte array.
			/// Numbers are converted from the given byte-order to platform byte-order.
			/// </summary>
			public static MathEx.UFraction32[] ToURationalArray(byte[] data, int count, ByteOrder frombyteorder)
			{
				MathEx.UFraction32[] numbers = new MathEx.UFraction32[count];
				for (uint i = 0; i < count; i++)
				{
					byte[] num = new byte[4];
					byte[] den = new byte[4];
					Array.Copy(data, i * 8, num, 0, 4);
					Array.Copy(data, i * 8 + 4, den, 0, 4);
					numbers[i].Set(ToUInt32(num, 0, frombyteorder, ByteOrder.System), ToUInt32(den, 0, frombyteorder, ByteOrder.System));
				}
				return numbers;
			}

			/// <summary>
			/// Returns an array of signed rational numbers converted from 
			/// the given byte array.
			/// Numbers are converted from the given byte-order to platform byte-order.
			/// </summary>
			public static MathEx.Fraction32[] ToSRationalArray(byte[] data, int count, ByteOrder frombyteorder)
			{
				MathEx.Fraction32[] numbers = new MathEx.Fraction32[count];
				for (uint i = 0; i < count; i++)
				{
					byte[] num = new byte[4];
					byte[] den = new byte[4];
					Array.Copy(data, i * 8, num, 0, 4);
					Array.Copy(data, i * 8 + 4, den, 0, 4);
					numbers[i].Set(ToInt32(num, 0, frombyteorder, ByteOrder.System), ToInt32(den, 0, frombyteorder, ByteOrder.System));
				}
				return numbers;
			}

			/// <summary>
			/// Converts the given ascii string to an array of bytes optionally adding a null terminator.
			/// </summary>
			public static byte[] GetBytes(string value, bool addnull)
			{
				if (addnull) value += '\0';
				return Encoding.ASCII.GetBytes(value);
			}

			/// <summary>
			/// Converts the given ascii string to an array of bytes without adding a null terminator.
			/// </summary>
			public static byte[] GetBytes(string value)
			{
				return GetBytes(value, false);
			}

			/// <summary>
			/// Converts the given datetime to an array of bytes with a null terminator.
			/// </summary>
			public static byte[] GetBytes(DateTime value, bool hastime)
			{
				string str = "";
				if (hastime)
					str = value.ToString("yyyy:MM:dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
				else
					str = value.ToString("yyyy:MM:dd", System.Globalization.CultureInfo.InvariantCulture);
				return GetBytes(str, true);
			}

			/// <summary>
			/// Converts the given unsigned rational number to an array of bytes.
			/// Numbers are converted from the platform byte-order to the given byte-order.
			/// </summary>
			public static byte[] GetBytes(MathEx.UFraction32 value, ByteOrder tobyteorder)
			{
				byte[] num = GetBytes(value.Numerator, ByteOrder.System, tobyteorder);
				byte[] den = GetBytes(value.Denominator, ByteOrder.System, tobyteorder);
				byte[] data = new byte[8];
				Array.Copy(num, 0, data, 0, 4);
				Array.Copy(den, 0, data, 4, 4);
				return data;
			}

			/// <summary>
			/// Converts the given signed rational number to an array of bytes.
			/// Numbers are converted from the platform byte-order to the given byte-order.
			/// </summary>
			public static byte[] GetBytes(MathEx.Fraction32 value, ByteOrder tobyteorder)
			{
				byte[] num = GetBytes(value.Numerator, ByteOrder.System, tobyteorder);
				byte[] den = GetBytes(value.Denominator, ByteOrder.System, tobyteorder);
				byte[] data = new byte[8];
				Array.Copy(num, 0, data, 0, 4);
				Array.Copy(den, 0, data, 4, 4);
				return data;
			}

			/// <summary>
			/// Converts the given array of 16-bit unsigned integers to an array of bytes.
			/// Numbers are converted from the platform byte-order to the given byte-order.
			/// </summary>
			public static byte[] GetBytes(ushort[] value, ByteOrder tobyteorder)
			{
				byte[] data = new byte[2 * value.Length];
				for (int i = 0; i < value.Length; i++)
				{
					byte[] num = GetBytes(value[i], ByteOrder.System, tobyteorder);
					Array.Copy(num, 0, data, i * 2, 2);
				}
				return data;
			}

			/// <summary>
			/// Converts the given array of 32-bit unsigned integers to an array of bytes.
			/// Numbers are converted from the platform byte-order to the given byte-order.
			/// </summary>
			public static byte[] GetBytes(uint[] value, ByteOrder tobyteorder)
			{
				byte[] data = new byte[4 * value.Length];
				for (int i = 0; i < value.Length; i++)
				{
					byte[] num = GetBytes(value[i], ByteOrder.System, tobyteorder);
					Array.Copy(num, 0, data, i * 4, 4);
				}
				return data;
			}

			/// <summary>
			/// Converts the given array of 32-bit signed integers to an array of bytes.
			/// Numbers are converted from the platform byte-order to the given byte-order.
			/// </summary>
			public static byte[] GetBytes(int[] value, ByteOrder tobyteorder)
			{
				byte[] data = new byte[4 * value.Length];
				for (int i = 0; i < value.Length; i++)
				{
					byte[] num = GetBytes(value[i], ByteOrder.System, tobyteorder);
					Array.Copy(num, 0, data, i * 4, 4);
				}
				return data;
			}

			/// <summary>
			/// Converts the given array of unsigned rationals to an array of bytes.
			/// Numbers are converted from the platform byte-order to the given byte-order.
			/// </summary>
			public static byte[] GetBytes(MathEx.UFraction32[] value, ByteOrder tobyteorder)
			{
				byte[] data = new byte[8 * value.Length];
				for (int i = 0; i < value.Length; i++)
				{
					byte[] num = GetBytes(value[i].Numerator, ByteOrder.System, tobyteorder);
					byte[] den = GetBytes(value[i].Denominator, ByteOrder.System, tobyteorder);
					Array.Copy(num, 0, data, i * 8, 4);
					Array.Copy(den, 0, data, i * 8 + 4, 4);
				}
				return data;
			}

			/// <summary>
			/// Converts the given array of signed rationals to an array of bytes.
			/// Numbers are converted from the platform byte-order to the given byte-order.
			/// </summary>
			public static byte[] GetBytes(MathEx.Fraction32[] value, ByteOrder tobyteorder)
			{
				byte[] data = new byte[8 * value.Length];
				for (int i = 0; i < value.Length; i++)
				{
					byte[] num = GetBytes(value[i].Numerator, ByteOrder.System, tobyteorder);
					byte[] den = GetBytes(value[i].Denominator, ByteOrder.System, tobyteorder);
					Array.Copy(num, 0, data, i * 8, 4);
					Array.Copy(den, 0, data, i * 8 + 4, 4);
				}
				return data;
			}
			#endregion
		}
		/// <summary>
		/// An endian-aware converter for converting between base data types 
		/// and an array of bytes.
		/// </summary>
		internal class BitConverterEx
		{
			#region Public Enums
			/// <summary>
			/// Represents the byte order.
			/// </summary>
			internal enum ByteOrder
			{
				System = 0,
				LittleEndian = 1,
				BigEndian = 2,
			}
			#endregion

			#region Member Variables
			private ByteOrder mFrom, mTo;
			#endregion

			#region Constructors
			public BitConverterEx(ByteOrder from, ByteOrder to)
			{
				mFrom = from;
				mTo = to;
			}
			#endregion

			#region Properties
			/// <summary>
			/// Indicates the byte order in which data is stored in this platform.
			/// </summary>
			public static ByteOrder SystemByteOrder
			{
				get
				{
					return (BitConverter.IsLittleEndian ? ByteOrder.LittleEndian : ByteOrder.BigEndian);
				}
			}
			#endregion

			#region Predefined Values
			/// <summary>
			/// Returns a bit converter that converts between little-endian and system byte-order.
			/// </summary>
			public static BitConverterEx LittleEndian
			{
				get
				{
					return new BitConverterEx(ByteOrder.LittleEndian, ByteOrder.System);
				}
			}

			/// <summary>
			/// Returns a bit converter that converts between big-endian and system byte-order.
			/// </summary>
			public static BitConverterEx BigEndian
			{
				get
				{
					return new BitConverterEx(ByteOrder.BigEndian, ByteOrder.System);
				}
			}

			/// <summary>
			/// Returns a bit converter that does not do any byte-order conversion.
			/// </summary>
			public static BitConverterEx SystemEndian
			{
				get
				{
					return new BitConverterEx(ByteOrder.System, ByteOrder.System);
				}
			}
			#endregion

			#region Static Methods
			/// <summary>
			/// Converts the given array of bytes to a Unicode character.
			/// </summary>
			public static char ToChar(byte[] value, int startIndex, ByteOrder from, ByteOrder to)
			{
				byte[] data = CheckData(value, startIndex, 2, from, to);
				return BitConverter.ToChar(data, 0);
			}

			/// <summary>
			/// Converts the given array of bytes to a 16-bit unsigned integer.
			/// </summary>
			public static ushort ToUInt16(byte[] value, int startIndex, ByteOrder from, ByteOrder to)
			{
				byte[] data = CheckData(value, startIndex, 2, from, to);
				return BitConverter.ToUInt16(data, 0);
			}

			/// <summary>
			/// Converts the given array of bytes to a 32-bit unsigned integer.
			/// </summary>
			public static uint ToUInt32(byte[] value, int startIndex, ByteOrder from, ByteOrder to)
			{
				byte[] data = CheckData(value, startIndex, 4, from, to);
				return BitConverter.ToUInt32(data, 0);
			}

			/// <summary>
			/// Converts the given array of bytes to a 64-bit unsigned integer.
			/// </summary>
			public static ulong ToUInt64(byte[] value, int startIndex, ByteOrder from, ByteOrder to)
			{
				byte[] data = CheckData(value, startIndex, 8, from, to);
				return BitConverter.ToUInt64(data, 0);
			}

			/// <summary>
			/// Converts the given array of bytes to a 16-bit signed integer.
			/// </summary>
			public static short ToInt16(byte[] value, int startIndex, ByteOrder from, ByteOrder to)
			{
				byte[] data = CheckData(value, startIndex, 2, from, to);
				return BitConverter.ToInt16(data, 0);
			}

			/// <summary>
			/// Converts the given array of bytes to a 32-bit signed integer.
			/// </summary>
			public static int ToInt32(byte[] value, int startIndex, ByteOrder from, ByteOrder to)
			{
				byte[] data = CheckData(value, startIndex, 4, from, to);
				return BitConverter.ToInt32(data, 0);
			}

			/// <summary>
			/// Converts the given array of bytes to a 64-bit signed integer.
			/// </summary>
			public static long ToInt64(byte[] value, int startIndex, ByteOrder from, ByteOrder to)
			{
				byte[] data = CheckData(value, startIndex, 8, from, to);
				return BitConverter.ToInt64(data, 0);
			}

			/// <summary>
			/// Converts the given array of bytes to a single precision floating number.
			/// </summary>
			public static float ToSingle(byte[] value, int startIndex, ByteOrder from, ByteOrder to)
			{
				byte[] data = CheckData(value, startIndex, 4, from, to);
				return BitConverter.ToSingle(data, 0);
			}

			/// <summary>
			/// Converts the given array of bytes to a double precision floating number.
			/// </summary>
			public static double ToDouble(byte[] value, int startIndex, ByteOrder from, ByteOrder to)
			{
				byte[] data = CheckData(value, startIndex, 8, from, to);
				return BitConverter.ToDouble(data, 0);
			}

			/// <summary>
			/// Converts the given 16-bit unsigned integer to an array of bytes.
			/// </summary>
			public static byte[] GetBytes(ushort value, ByteOrder from, ByteOrder to)
			{
				byte[] data = BitConverter.GetBytes(value);
				data = CheckData(data, from, to);
				return data;
			}

			/// <summary>
			/// Converts the given 32-bit unsigned integer to an array of bytes.
			/// </summary>
			public static byte[] GetBytes(uint value, ByteOrder from, ByteOrder to)
			{
				byte[] data = BitConverter.GetBytes(value);
				data = CheckData(data, from, to);
				return data;
			}

			/// <summary>
			/// Converts the given 64-bit unsigned integer to an array of bytes.
			/// </summary>
			public static byte[] GetBytes(ulong value, ByteOrder from, ByteOrder to)
			{
				byte[] data = BitConverter.GetBytes(value);
				data = CheckData(data, from, to);
				return data;
			}

			/// <summary>
			/// Converts the given 16-bit signed integer to an array of bytes.
			/// </summary>
			public static byte[] GetBytes(short value, ByteOrder from, ByteOrder to)
			{
				byte[] data = BitConverter.GetBytes(value);
				data = CheckData(data, from, to);
				return data;
			}

			/// <summary>
			/// Converts the given 32-bit signed integer to an array of bytes.
			/// </summary>
			public static byte[] GetBytes(int value, ByteOrder from, ByteOrder to)
			{
				byte[] data = BitConverter.GetBytes(value);
				data = CheckData(data, from, to);
				return data;
			}

			/// <summary>
			/// Converts the given 64-bit signed integer to an array of bytes.
			/// </summary>
			public static byte[] GetBytes(long value, ByteOrder from, ByteOrder to)
			{
				byte[] data = BitConverter.GetBytes(value);
				data = CheckData(data, from, to);
				return data;
			}

			/// <summary>
			/// Converts the given single precision floating-point number to an array of bytes.
			/// </summary>
			public static byte[] GetBytes(float value, ByteOrder from, ByteOrder to)
			{
				byte[] data = BitConverter.GetBytes(value);
				data = CheckData(data, from, to);
				return data;
			}

			/// <summary>
			/// Converts the given double precision floating-point number to an array of bytes.
			/// </summary>
			public static byte[] GetBytes(double value, ByteOrder from, ByteOrder to)
			{
				byte[] data = BitConverter.GetBytes(value);
				data = CheckData(data, from, to);
				return data;
			}
			#endregion

			#region Instance Methods
			/// <summary>
			/// Converts the given array of bytes to a 16-bit unsigned integer.
			/// </summary>
			public char ToChar(byte[] value, int startIndex)
			{
				return BitConverterEx.ToChar(value, startIndex, mFrom, mTo);
			}

			/// <summary>
			/// Converts the given array of bytes to a 16-bit unsigned integer.
			/// </summary>
			public ushort ToUInt16(byte[] value, int startIndex)
			{
				return BitConverterEx.ToUInt16(value, startIndex, mFrom, mTo);
			}

			/// <summary>
			/// Converts the given array of bytes to a 32-bit unsigned integer.
			/// </summary>
			public uint ToUInt32(byte[] value, int startIndex)
			{
				return BitConverterEx.ToUInt32(value, startIndex, mFrom, mTo);
			}

			/// <summary>
			/// Converts the given array of bytes to a 64-bit unsigned integer.
			/// </summary>
			public ulong ToUInt64(byte[] value, int startIndex)
			{
				return BitConverterEx.ToUInt64(value, startIndex, mFrom, mTo);
			}

			/// <summary>
			/// Converts the given array of bytes to a 16-bit signed integer.
			/// </summary>
			public short ToInt16(byte[] value, int startIndex)
			{
				return BitConverterEx.ToInt16(value, startIndex, mFrom, mTo);
			}

			/// <summary>
			/// Converts the given array of bytes to a 32-bit signed integer.
			/// </summary>
			public int ToInt32(byte[] value, int startIndex)
			{
				return BitConverterEx.ToInt32(value, startIndex, mFrom, mTo);
			}

			/// <summary>
			/// Converts the given array of bytes to a 64-bit signed integer.
			/// </summary>
			public long ToInt64(byte[] value, int startIndex)
			{
				return BitConverterEx.ToInt64(value, startIndex, mFrom, mTo);
			}

			/// <summary>
			/// Converts the given array of bytes to a single precision floating number.
			/// </summary>
			public float ToSingle(byte[] value, int startIndex)
			{
				return BitConverterEx.ToSingle(value, startIndex, mFrom, mTo);
			}

			/// <summary>
			/// Converts the given array of bytes to a double precision floating number.
			/// </summary>
			public double ToDouble(byte[] value, int startIndex)
			{
				return BitConverterEx.ToDouble(value, startIndex, mFrom, mTo);
			}

			/// <summary>
			/// Converts the given 16-bit unsigned integer to an array of bytes.
			/// </summary>
			public byte[] GetBytes(ushort value)
			{
				return BitConverterEx.GetBytes(value, mFrom, mTo);
			}

			/// <summary>
			/// Converts the given 32-bit unsigned integer to an array of bytes.
			/// </summary>
			public byte[] GetBytes(uint value)
			{
				return BitConverterEx.GetBytes(value, mFrom, mTo);
			}

			/// <summary>
			/// Converts the given 64-bit unsigned integer to an array of bytes.
			/// </summary>
			public byte[] GetBytes(ulong value)
			{
				return BitConverterEx.GetBytes(value, mFrom, mTo);
			}

			/// <summary>
			/// Converts the given 16-bit signed integer to an array of bytes.
			/// </summary>
			public byte[] GetBytes(short value)
			{
				return BitConverterEx.GetBytes(value, mFrom, mTo);
			}

			/// <summary>
			/// Converts the given 32-bit signed integer to an array of bytes.
			/// </summary>
			public byte[] GetBytes(int value)
			{
				return BitConverterEx.GetBytes(value, mFrom, mTo);
			}

			/// <summary>
			/// Converts the given 64-bit signed integer to an array of bytes.
			/// </summary>
			public byte[] GetBytes(long value)
			{
				return BitConverterEx.GetBytes(value, mFrom, mTo);
			}

			/// <summary>
			/// Converts the given single precision floating-point number to an array of bytes.
			/// </summary>
			public byte[] GetBytes(float value)
			{
				return BitConverterEx.GetBytes(value, mFrom, mTo);
			}

			/// <summary>
			/// Converts the given double precision floating-point number to an array of bytes.
			/// </summary>
			public byte[] GetBytes(double value)
			{
				return BitConverterEx.GetBytes(value, mFrom, mTo);
			}
			#endregion

			#region Private Helpers
			/// <summary>
			/// Reverse the array of bytes as needed.
			/// </summary>
			private static byte[] CheckData(byte[] value, int startIndex, int length, ByteOrder from, ByteOrder to)
			{
				from = CheckByteOrder(from);
				to = CheckByteOrder(to);
				byte[] data = new byte[length];
				Array.Copy(value, startIndex, data, 0, length);
				if (from != to)
					Array.Reverse(data);
				return data;
			}

			/// <summary>
			/// Reverse the array of bytes as needed.
			/// </summary>
			private static byte[] CheckData(byte[] value, ByteOrder from, ByteOrder to)
			{
				return CheckData(value, 0, value.Length, from, to);
			}

			/// <summary>
			/// Decodes the ByteOrder.System value for this platform.
			/// </summary>
			private static ByteOrder CheckByteOrder(ByteOrder order)
			{
				if (order == ByteOrder.System)
				{
					if (BitConverter.IsLittleEndian)
						return ByteOrder.LittleEndian;
					else
						return ByteOrder.BigEndian;
				}
				else
					return order;
			}
			#endregion
		}
	}
}