// ==========================================================
// TargaImage
//
// Design and implementation by
// - David Polomis (paloma_sw@cox.net)
//
//
// This source code, along with any associated files, is licensed under
// The Code Project Open License (CPOL) 1.02
// A copy of this license can be found in the CPOL.html file 
// which was downloaded with this source code
// or at http://www.codeproject.com/info/cpol10.aspx
//
// 
// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
// WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS
// FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR
// NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
// OF THE COVERED CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE
// DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY
// OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING,
// REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN
// ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS
// AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
//
// Use at your own risk!
//
// ==========================================================


using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace Paloma
{
    internal static class TargaConstants
    {
        // constant byte lengths for various fields in the Targa format
        internal const int HeaderByteLength = 18;
        internal const int FooterByteLength = 26;
        internal const int FooterSignatureOffsetFromEnd = 18;
        internal const int FooterSignatureByteLength = 16;
        internal const int FooterReservedCharByteLength = 1;
        internal const int ExtensionAreaAuthorNameByteLength = 41;
        internal const int ExtensionAreaAuthorCommentsByteLength = 324;
        internal const int ExtensionAreaJobNameByteLength = 41;
        internal const int ExtensionAreaSoftwareIDByteLength = 41;
        internal const int ExtensionAreaSoftwareVersionLetterByteLength = 1;
        internal const int ExtensionAreaColorCorrectionTableValueLength = 256;
        internal const string TargaFooterASCIISignature = "TRUEVISION-XFILE";
    }


    /// <summary>
    /// The Targa format of the file.
    /// </summary>
    public enum TGAFormat
    {
        /// <summary>
        /// Unknown Targa Image format.
        /// </summary>
        UNKNOWN = 0,

        /// <summary>
        /// Original Targa Image format.
        /// </summary>
        /// <remarks>Targa Image does not have a Signature of ""TRUEVISION-XFILE"".</remarks>
        ORIGINAL_TGA = 100,

        /// <summary>
        /// New Targa Image format
        /// </summary>
        /// <remarks>Targa Image has a TargaFooter with a Signature of ""TRUEVISION-XFILE"".</remarks>
        NEW_TGA = 200
    }


    /// <summary>
    /// Indicates the type of color map, if any, included with the image file. 
    /// </summary>
    public enum ColorMapType : byte
    {
        /// <summary>
        /// No color map was included in the file.
        /// </summary>
        NO_COLOR_MAP = 0,

        /// <summary>
        /// Color map was included in the file.
        /// </summary>
        COLOR_MAP_INCLUDED = 1
    }


    /// <summary>
    /// The type of image read from the file.
    /// </summary>
    public enum ImageType : byte
    {
        /// <summary>
        /// No image data was found in file.
        /// </summary>
        NO_IMAGE_DATA = 0,

        /// <summary>
        /// Image is an uncompressed, indexed color-mapped image.
        /// </summary>
        UNCOMPRESSED_COLOR_MAPPED = 1,

        /// <summary>
        /// Image is an uncompressed, RGB image.
        /// </summary>
        UNCOMPRESSED_TRUE_COLOR = 2,

        /// <summary>
        /// Image is an uncompressed, Greyscale image.
        /// </summary>
        UNCOMPRESSED_BLACK_AND_WHITE = 3,

        /// <summary>
        /// Image is a compressed, indexed color-mapped image.
        /// </summary>
        RUN_LENGTH_ENCODED_COLOR_MAPPED = 9,

        /// <summary>
        /// Image is a compressed, RGB image.
        /// </summary>
        RUN_LENGTH_ENCODED_TRUE_COLOR = 10,

        /// <summary>
        /// Image is a compressed, Greyscale image.
        /// </summary>
        RUN_LENGTH_ENCODED_BLACK_AND_WHITE = 11
    }


    /// <summary>
    /// The top-to-bottom ordering in which pixel data is transferred from the file to the screen.
    /// </summary>
    public enum VerticalTransferOrder 
    {
        /// <summary>
        /// Unknown transfer order.
        /// </summary>
        UNKNOWN = -1,

        /// <summary>
        /// Transfer order of pixels is from the bottom to top.
        /// </summary>
        BOTTOM = 0,

        /// <summary>
        /// Transfer order of pixels is from the top to bottom.
        /// </summary>
        TOP = 1
    }


    /// <summary>
    /// The left-to-right ordering in which pixel data is transferred from the file to the screen.
    /// </summary>
    public enum HorizontalTransferOrder 
    {
        /// <summary>
        /// Unknown transfer order.
        /// </summary>
        UNKNOWN = -1,

        /// <summary>
        /// Transfer order of pixels is from the right to left.
        /// </summary>
        RIGHT = 0,

        /// <summary>
        /// Transfer order of pixels is from the left to right.
        /// </summary>
        LEFT = 1
    }


    /// <summary>
    /// Screen destination of first pixel based on the VerticalTransferOrder and HorizontalTransferOrder.
    /// </summary>
    public enum FirstPixelDestination 
    {
        /// <summary>
        /// Unknown first pixel destination.
        /// </summary>
        UNKNOWN = 0,

        /// <summary>
        /// First pixel destination is the top-left corner of the image.
        /// </summary>
        TOP_LEFT = 1,

        /// <summary>
        /// First pixel destination is the top-right corner of the image.
        /// </summary>
        TOP_RIGHT = 2,

        /// <summary>
        /// First pixel destination is the bottom-left corner of the image.
        /// </summary>
        BOTTOM_LEFT = 3,

        /// <summary>
        /// First pixel destination is the bottom-right corner of the image.
        /// </summary>
        BOTTOM_RIGHT = 4
    }


    /// <summary>
    /// The RLE packet type used in a RLE compressed image.
    /// </summary>
    public enum RLEPacketType 
    {
        /// <summary>
        /// A raw RLE packet type.
        /// </summary>
        RAW = 0,

        /// <summary>
        /// A run-length RLE packet type.
        /// </summary>
        RUN_LENGTH = 1
    }


    /// <summary>
    /// Reads and loads a Truevision TGA Format image file.
    /// </summary>
    public class TargaImage : IDisposable

    {
        private TargaHeader objTargaHeader = null;
        private TargaExtensionArea objTargaExtensionArea = null;
        private TargaFooter objTargaFooter = null;
        private Bitmap bmpTargaImage = null;
        private Bitmap bmpImageThumbnail = null;
        private TGAFormat eTGAFormat = TGAFormat.UNKNOWN;
        private string strFileName = string.Empty;
        private int intStride = 0;
        private int intPadding = 0;
        private GCHandle ImageByteHandle;
        private GCHandle ThumbnailByteHandle;
                        

        // Track whether Dispose has been called.
        private bool disposed = false;


        /// <summary>
        /// Creates a new instance of the TargaImage object.
        /// </summary>
        public TargaImage()
        {
            this.objTargaFooter = new TargaFooter();
            this.objTargaHeader = new TargaHeader();
            this.objTargaExtensionArea = new TargaExtensionArea();
            this.bmpTargaImage = null;
            this.bmpImageThumbnail = null;
        }


        /// <summary>
        /// Gets a TargaHeader object that holds the Targa Header information of the loaded file.
        /// </summary>
        public TargaHeader Header
        {
            get { return this.objTargaHeader; }
        }


        /// <summary>
        /// Gets a TargaExtensionArea object that holds the Targa Extension Area information of the loaded file.
        /// </summary>
        public TargaExtensionArea ExtensionArea
        {
            get { return this.objTargaExtensionArea; }
        }


        /// <summary>
        /// Gets a TargaExtensionArea object that holds the Targa Footer information of the loaded file.
        /// </summary>
        public TargaFooter Footer
        {
            get { return this.objTargaFooter; }
        }


        /// <summary>
        /// Gets the Targa format of the loaded file.
        /// </summary>
        public TGAFormat Format
        {
            get { return this.eTGAFormat; }
        }


        /// <summary>
        /// Gets a Bitmap representation of the loaded file.
        /// </summary>
        public Bitmap Image
        {
            get { return this.bmpTargaImage; }
        }

        /// <summary>
        /// Gets the thumbnail of the loaded file if there is one in the file.
        /// </summary>
        public Bitmap Thumbnail
        {
            get { return this.bmpImageThumbnail; }
        }

        /// <summary>
        /// Gets the full path and filename of the loaded file.
        /// </summary>
        public string FileName
        {
            get { return this.strFileName; }
        }


        /// <summary>
        /// Gets the byte offset between the beginning of one scan line and the next. Used when loading the image into the Image Bitmap.
        /// </summary>
        /// <remarks>
        /// The memory allocated for Microsoft Bitmaps must be aligned on a 32bit boundary.
        /// The stride refers to the number of bytes allocated for one scanline of the bitmap.
        /// </remarks>
        public int Stride
        {
            get { return this.intStride; }
        }


        /// <summary>
        /// Gets the number of bytes used to pad each scan line to meet the Stride value. Used when loading the image into the Image Bitmap.
        /// </summary>
        /// <remarks>
        /// The memory allocated for Microsoft Bitmaps must be aligned on a 32bit boundary.
        /// The stride refers to the number of bytes allocated for one scanline of the bitmap.
        /// In your loop, you copy the pixels one scanline at a time and take into 
        /// consideration the amount of padding that occurs due to memory alignment.
        /// </remarks>
        public int Padding
        {
            get { return this.intPadding; }
        }


        // Use C# destructor syntax for finalization code.
        // This destructor will run only if the Dispose method 
        // does not get called.
        // It gives your base class the opportunity to finalize.
        // Do not provide destructors in types derived from this class.
        /// <summary>
        /// TargaImage deconstructor.
        /// </summary>
        ~TargaImage()      
        {
           // Do not re-create Dispose clean-up code here.
           // Calling Dispose(false) is optimal in terms of
           // readability and maintainability.
           Dispose(false);
        }

        public bool IsSupportedTGA(Stream ImageStream)
        {
            using (BinaryReader binReader = new BinaryReader(ImageStream, Encoding.Default, true))
            {
                this.LoadTGAFooterInfo(binReader);
               return this.LoadTGAHeaderInfo(binReader);
            }
        }

        /// <summary>
        /// Creates a new instance of the TargaImage object with strFileName as the image loaded.
        /// </summary>
        public TargaImage(string strFileName) : this()
        {
            // make sure we have a .tga file
            if (System.IO.Path.GetExtension(strFileName).ToLower() == ".tga")
            {
                // make sure the file exists
                if (System.IO.File.Exists(strFileName) == true)
                {
                    this.strFileName = strFileName;
                    MemoryStream filestream = null;
                    BinaryReader binReader = null;
                    byte[] filebytes = null;

                    // load the file as an array of bytes
                    filebytes = System.IO.File.ReadAllBytes(this.strFileName);
                    if (filebytes != null && filebytes.Length > 0)
                    {
                        // create a seekable memory stream of the file bytes
                        using (filestream = new MemoryStream(filebytes))
                        {
                            if (filestream != null && filestream.Length > 0 && filestream.CanSeek == true)
                            {
                                // create a BinaryReader used to read the Targa file
                                using (binReader = new BinaryReader(filestream))
                                {
                                    this.LoadTGAFooterInfo(binReader);
                                    this.LoadTGAHeaderInfo(binReader);
                                    this.LoadTGAExtensionArea(binReader);
                                    this.LoadTGAImage(binReader);
                                }
                            }
                            else
                                throw new Exception(@"Error loading file, could not read file from disk.");
                        
                        }

                    }
                    else
                        throw new Exception(@"Error loading file, could not read file from disk.");

                }
                else
                    throw new Exception(@"Error loading file, could not find file '" + strFileName + "' on disk.");

            }
            else
                throw new Exception(@"Error loading file, file '" + strFileName + "' must have an extension of '.tga'.");

            
        }


        /// <summary>
        /// Creates a new instance of the TargaImage object loading the image data from the provided stream.
        /// </summary>
        public TargaImage(Stream ImageStream)
            : this()
        {
            if (ImageStream != null && ImageStream.Length > 0 && ImageStream.CanSeek == true)
            {
                // create a BinaryReader used to read the Targa file
                using (BinaryReader binReader = new BinaryReader(ImageStream))
                {
                    this.LoadTGAFooterInfo(binReader);
                    this.LoadTGAHeaderInfo(binReader);
                    this.LoadTGAExtensionArea(binReader);
                    this.LoadTGAImage(binReader);
                }
            }
            else
                throw new ArgumentException(@"Error loading image, Null, zero length or non-seekable stream provided.", "ImageStream");


        }


        /// <summary>
        /// Loads the Targa Footer information from the file.
        /// </summary>
        /// <param name="binReader">A BinaryReader that points the loaded file byte stream.</param>
        private void LoadTGAFooterInfo(BinaryReader binReader)
        {

                if (binReader != null && binReader.BaseStream != null && binReader.BaseStream.Length > 0 && binReader.BaseStream.CanSeek == true)
                {
                    
                    try
                    {            
                        // set the cursor at the beginning of the signature string.
                        binReader.BaseStream.Seek((TargaConstants.FooterSignatureOffsetFromEnd * -1), SeekOrigin.End);

                        // read the signature bytes and convert to ascii string
                        string Signature = System.Text.Encoding.ASCII.GetString(binReader.ReadBytes(TargaConstants.FooterSignatureByteLength)).TrimEnd('\0');

                        // do we have a proper signature
                        if (string.Compare(Signature, TargaConstants.TargaFooterASCIISignature) == 0)
                        {
                            // this is a NEW targa file.
                            // create the footer
                            this.eTGAFormat = TGAFormat.NEW_TGA;

                            // set cursor to beginning of footer info
                            binReader.BaseStream.Seek((TargaConstants.FooterByteLength * -1), SeekOrigin.End);

                            // read the Extension Area Offset value
                            int ExtOffset = binReader.ReadInt32();

                            // read the Developer Directory Offset value
                            int DevDirOff = binReader.ReadInt32();

                            // skip the signature we have already read it.
                            binReader.ReadBytes(TargaConstants.FooterSignatureByteLength);

                            // read the reserved character
                            string ResChar = System.Text.Encoding.ASCII.GetString(binReader.ReadBytes(TargaConstants.FooterReservedCharByteLength)).TrimEnd('\0');

                            // set all values to our TargaFooter class
                            this.objTargaFooter.SetExtensionAreaOffset(ExtOffset);
                            this.objTargaFooter.SetDeveloperDirectoryOffset(DevDirOff);
                            this.objTargaFooter.SetSignature(Signature);
                            this.objTargaFooter.SetReservedCharacter(ResChar);
                        }
                        else
                        {
                            // this is not an ORIGINAL targa file.
                            this.eTGAFormat = TGAFormat.ORIGINAL_TGA;
                        }
                    }
                    catch ( Exception ex )
                    {
                        // clear all 
                        this.ClearAll();
                        throw ex;
                    }
                }
                else
                {
                    this.ClearAll();
                    throw new Exception(@"Error loading file, could not read file from disk.");
                }

           
        }


        /// <summary>
        /// Loads the Targa Header information from the file.
        /// </summary>
        /// <param name="binReader">A BinaryReader that points the loaded file byte stream.</param>
        private bool LoadTGAHeaderInfo(BinaryReader binReader)
        {

            if (binReader != null && binReader.BaseStream != null && binReader.BaseStream.Length > 0 && binReader.BaseStream.CanSeek == true)
            {
                try
                {
                    // set the cursor at the beginning of the file.
                    binReader.BaseStream.Seek(0, SeekOrigin.Begin);

                    // read the header properties from the file
                    this.objTargaHeader.SetImageIDLength(binReader.ReadByte());
                    this.objTargaHeader.SetColorMapType((ColorMapType)binReader.ReadByte());
                    this.objTargaHeader.SetImageType((ImageType)binReader.ReadByte());

                    this.objTargaHeader.SetColorMapFirstEntryIndex(binReader.ReadInt16());
                    this.objTargaHeader.SetColorMapLength(binReader.ReadInt16());
                    this.objTargaHeader.SetColorMapEntrySize(binReader.ReadByte());

                    this.objTargaHeader.SetXOrigin(binReader.ReadInt16());
                    this.objTargaHeader.SetYOrigin(binReader.ReadInt16());
                    this.objTargaHeader.SetWidth(binReader.ReadInt16());
                    this.objTargaHeader.SetHeight(binReader.ReadInt16());

                    byte pixeldepth = binReader.ReadByte();
                    switch (pixeldepth)
                    {
                        case 8:
                        case 16:
                        case 24:
                        case 32:
                            this.objTargaHeader.SetPixelDepth(pixeldepth);
                            break;

                        default:
                            this.ClearAll();
                            return false;
                        //    throw new Exception("Targa Image only supports 8, 16, 24, or 32 bit pixel depths.");
                    }
                    

                    byte ImageDescriptor = binReader.ReadByte();
                    this.objTargaHeader.SetAttributeBits((byte)Utilities.GetBits(ImageDescriptor, 0, 4));

                    this.objTargaHeader.SetVerticalTransferOrder((VerticalTransferOrder)Utilities.GetBits(ImageDescriptor, 5, 1));
                    this.objTargaHeader.SetHorizontalTransferOrder((HorizontalTransferOrder)Utilities.GetBits(ImageDescriptor, 4, 1));

                    // load ImageID value if any
                    if (this.objTargaHeader.ImageIDLength > 0)
                    {
                        byte[] ImageIDValueBytes = binReader.ReadBytes(this.objTargaHeader.ImageIDLength);
                        this.objTargaHeader.SetImageIDValue(System.Text.Encoding.ASCII.GetString(ImageIDValueBytes).TrimEnd('\0'));
                    }
                }
                catch (Exception ex)
                {
                    this.ClearAll();
                    throw ex;
                }


                // load color map if it's included and/or needed
                // Only needed for UNCOMPRESSED_COLOR_MAPPED and RUN_LENGTH_ENCODED_COLOR_MAPPED
                // image types. If color map is included for other file types we can ignore it.
                if (this.objTargaHeader.ColorMapType == ColorMapType.COLOR_MAP_INCLUDED)
                {
                    if (this.objTargaHeader.ImageType == ImageType.UNCOMPRESSED_COLOR_MAPPED || 
                        this.objTargaHeader.ImageType == ImageType.RUN_LENGTH_ENCODED_COLOR_MAPPED)
                    {
                        if (this.objTargaHeader.ColorMapLength > 0)
                        {
                            try
                            {
                                for (int i = 0; i < this.objTargaHeader.ColorMapLength; i++)
                                {
                                    int a = 0;
                                    int r = 0;
                                    int g = 0;
                                    int b = 0;

                                    // load each color map entry based on the ColorMapEntrySize value
                                    switch (this.objTargaHeader.ColorMapEntrySize)
                                    {
                                        case 15:
                                            byte[] color15 = binReader.ReadBytes(2);
                                            // remember that the bytes are stored in reverse oreder
                                            this.objTargaHeader.ColorMap.Add(Utilities.GetColorFrom2Bytes(color15[1], color15[0]));
                                            break;
                                        case 16:
                                            byte[] color16 = binReader.ReadBytes(2);
                                            // remember that the bytes are stored in reverse oreder
                                            this.objTargaHeader.ColorMap.Add(Utilities.GetColorFrom2Bytes(color16[1], color16[0]));
                                            break;
                                        case 24:
                                            b = Convert.ToInt32(binReader.ReadByte());
                                            g = Convert.ToInt32(binReader.ReadByte());
                                            r = Convert.ToInt32(binReader.ReadByte());
                                            this.objTargaHeader.ColorMap.Add(System.Drawing.Color.FromArgb(r, g, b));
                                            break;
                                        case 32:
                                            a = Convert.ToInt32(binReader.ReadByte());
                                            b = Convert.ToInt32(binReader.ReadByte());
                                            g = Convert.ToInt32(binReader.ReadByte());
                                            r = Convert.ToInt32(binReader.ReadByte());
                                            this.objTargaHeader.ColorMap.Add(System.Drawing.Color.FromArgb(a, r, g, b));
                                            break;
                                        default:
                                            this.ClearAll();
                                            throw new Exception("TargaImage only supports ColorMap Entry Sizes of 15, 16, 24 or 32 bits.");
                                            
                                    }


                                }
                            }
                            catch (Exception ex)
                            {
                                this.ClearAll();
                                throw ex;
                            }

                            

                        }
                        else
                        {
                            this.ClearAll();
                            throw new Exception("Image Type requires a Color Map and Color Map Length is zero.");
                        }
                    }


                }
                else
                {
                    if (this.objTargaHeader.ImageType == ImageType.UNCOMPRESSED_COLOR_MAPPED || 
                        this.objTargaHeader.ImageType == ImageType.RUN_LENGTH_ENCODED_COLOR_MAPPED)
                    {
                        this.ClearAll();
                        throw new Exception("Image Type requires a Color Map and there was not a Color Map included in the file.");
                    }
                }


            }
            else
            {
                this.ClearAll();
                throw new Exception(@"Error loading file, could not read file from disk.");
            }

            return true;
        }


        /// <summary>
        /// Loads the Targa Extension Area from the file, if it exists.
        /// </summary>
        /// <param name="binReader">A BinaryReader that points the loaded file byte stream.</param>
        private void LoadTGAExtensionArea(BinaryReader binReader)
        {

            if (binReader != null && binReader.BaseStream != null && binReader.BaseStream.Length > 0 && binReader.BaseStream.CanSeek == true)
            {
                // is there an Extension Area in file
                if (this.objTargaFooter.ExtensionAreaOffset > 0)
                {
                    try
                    {
                        // set the cursor at the beginning of the Extension Area using ExtensionAreaOffset.
                        binReader.BaseStream.Seek(this.objTargaFooter.ExtensionAreaOffset, SeekOrigin.Begin);

                        // load the extension area fields from the file

                        this.objTargaExtensionArea.SetExtensionSize((int)(binReader.ReadInt16()));
                        this.objTargaExtensionArea.SetAuthorName(System.Text.Encoding.ASCII.GetString(binReader.ReadBytes(TargaConstants.ExtensionAreaAuthorNameByteLength)).TrimEnd('\0'));
                        this.objTargaExtensionArea.SetAuthorComments(System.Text.Encoding.ASCII.GetString(binReader.ReadBytes(TargaConstants.ExtensionAreaAuthorCommentsByteLength)).TrimEnd('\0'));


                        // get the date/time stamp of the file
                        Int16 iMonth = binReader.ReadInt16();
                        Int16 iDay = binReader.ReadInt16();
                        Int16 iYear = binReader.ReadInt16();
                        Int16 iHour = binReader.ReadInt16();
                        Int16 iMinute = binReader.ReadInt16();
                        Int16 iSecond = binReader.ReadInt16();
                        DateTime dtstamp;
                        string strStamp = iMonth.ToString() + @"/" + iDay.ToString() + @"/" + iYear.ToString() + @" ";
                        strStamp += iHour.ToString() + @":" + iMinute.ToString() + @":" + iSecond.ToString();
                        if (DateTime.TryParse(strStamp, out dtstamp) == true)
                            this.objTargaExtensionArea.SetDateTimeStamp(dtstamp);


                        this.objTargaExtensionArea.SetJobName(System.Text.Encoding.ASCII.GetString(binReader.ReadBytes(TargaConstants.ExtensionAreaJobNameByteLength)).TrimEnd('\0'));


                        // get the job time of the file
                        iHour = binReader.ReadInt16();
                        iMinute = binReader.ReadInt16();
                        iSecond = binReader.ReadInt16();
                        TimeSpan ts = new TimeSpan((int)iHour, (int)iMinute, (int)iSecond);
                        this.objTargaExtensionArea.SetJobTime(ts);


                        this.objTargaExtensionArea.SetSoftwareID(System.Text.Encoding.ASCII.GetString(binReader.ReadBytes(TargaConstants.ExtensionAreaSoftwareIDByteLength)).TrimEnd('\0'));


                        // get the version number and letter from file
                        float iVersionNumber = (float)binReader.ReadInt16() / 100.0F;
                        string strVersionLetter = System.Text.Encoding.ASCII.GetString(binReader.ReadBytes(TargaConstants.ExtensionAreaSoftwareVersionLetterByteLength)).TrimEnd('\0');
                        
                        
                        this.objTargaExtensionArea.SetSoftwareID(iVersionNumber.ToString(@"F2") + strVersionLetter);


                        // get the color key of the file
                        int a = (int)binReader.ReadByte();
                        int r = (int)binReader.ReadByte();
                        int b = (int)binReader.ReadByte();
                        int g = (int)binReader.ReadByte();
                        this.objTargaExtensionArea.SetKeyColor(Color.FromArgb(a, r, g, b));


                        this.objTargaExtensionArea.SetPixelAspectRatioNumerator((int)binReader.ReadInt16());
                        this.objTargaExtensionArea.SetPixelAspectRatioDenominator((int)binReader.ReadInt16());
                        this.objTargaExtensionArea.SetGammaNumerator((int)binReader.ReadInt16());
                        this.objTargaExtensionArea.SetGammaDenominator((int)binReader.ReadInt16());
                        this.objTargaExtensionArea.SetColorCorrectionOffset(binReader.ReadInt32());
                        this.objTargaExtensionArea.SetPostageStampOffset(binReader.ReadInt32());
                        this.objTargaExtensionArea.SetScanLineOffset(binReader.ReadInt32());
                        this.objTargaExtensionArea.SetAttributesType((int)binReader.ReadByte());


                        // load Scan Line Table from file if any
                        if (this.objTargaExtensionArea.ScanLineOffset > 0)
                        {
                            binReader.BaseStream.Seek(this.objTargaExtensionArea.ScanLineOffset, SeekOrigin.Begin);
                            for (int i = 0; i < this.objTargaHeader.Height; i++)
                            {
                                this.objTargaExtensionArea.ScanLineTable.Add(binReader.ReadInt32());
                            }
                        }


                        // load Color Correction Table from file if any
                        if (this.objTargaExtensionArea.ColorCorrectionOffset > 0)
                        {
                            binReader.BaseStream.Seek(this.objTargaExtensionArea.ColorCorrectionOffset, SeekOrigin.Begin);
                            for (int i = 0; i < TargaConstants.ExtensionAreaColorCorrectionTableValueLength; i++)
                            {
                                a = (int)binReader.ReadInt16();
                                r = (int)binReader.ReadInt16();
                                b = (int)binReader.ReadInt16();
                                g = (int)binReader.ReadInt16();
                                this.objTargaExtensionArea.ColorCorrectionTable.Add(Color.FromArgb(a, r, g, b));
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        this.ClearAll();
                        throw ex;
                    }
                }
            }
            else
            {
                this.ClearAll();
                throw new Exception(@"Error loading file, could not read file from disk.");
            }
        }

        /// <summary>
        /// Reads the image data bytes from the file. Handles Uncompressed and RLE Compressed image data. 
        /// Uses FirstPixelDestination to properly align the image.
        /// </summary>
        /// <param name="binReader">A BinaryReader that points the loaded file byte stream.</param>
        /// <returns>An array of bytes representing the image data in the proper alignment.</returns>
        private byte[] LoadImageBytes(BinaryReader binReader)
        {

            // read the image data into a byte array
            // take into account stride has to be a multiple of 4
            // use padding to make sure multiple of 4    
        
            byte[] data = null;
            if (binReader != null && binReader.BaseStream != null && binReader.BaseStream.Length > 0 && binReader.BaseStream.CanSeek == true)
            {
                if (this.objTargaHeader.ImageDataOffset > 0)
                {
                    // padding bytes
                    byte[] padding = new byte[this.intPadding];
                    MemoryStream msData = null;
                    System.Collections.Generic.List<System.Collections.Generic.List<byte>> rows = null;
                    System.Collections.Generic.List<byte> row = null;
                    rows = new System.Collections.Generic.List<System.Collections.Generic.List<byte>>();
                    row = new System.Collections.Generic.List<byte>();

                                       
                    // seek to the beginning of the image data using the ImageDataOffset value
                    binReader.BaseStream.Seek(this.objTargaHeader.ImageDataOffset, SeekOrigin.Begin);
                   
                    
                    // get the size in bytes of each row in the image
                    int intImageRowByteSize = (int)this.objTargaHeader.Width * ((int)this.objTargaHeader.BytesPerPixel);

                    // get the size in bytes of the whole image
                    int intImageByteSize = intImageRowByteSize * (int)this.objTargaHeader.Height;
                                    
                    // is this a RLE compressed image type
                    if (this.objTargaHeader.ImageType == ImageType.RUN_LENGTH_ENCODED_BLACK_AND_WHITE ||
                       this.objTargaHeader.ImageType == ImageType.RUN_LENGTH_ENCODED_COLOR_MAPPED ||
                       this.objTargaHeader.ImageType == ImageType.RUN_LENGTH_ENCODED_TRUE_COLOR)
                    {

                        #region COMPRESSED
                        
                            // RLE Packet info
                            byte bRLEPacket = 0;
                            int intRLEPacketType = -1;
                            int intRLEPixelCount = 0;
                            byte[] bRunLengthPixel = null;

                            // used to keep track of bytes read
                            int intImageBytesRead = 0;
                            int intImageRowBytesRead = 0;

                            // keep reading until we have the all image bytes
                            while (intImageBytesRead < intImageByteSize)
                            {
                                // get the RLE packet
                                bRLEPacket = binReader.ReadByte();
                                intRLEPacketType = Utilities.GetBits(bRLEPacket, 7, 1);
                                intRLEPixelCount = Utilities.GetBits(bRLEPacket, 0, 7) + 1;

                                // check the RLE packet type
                                if ((RLEPacketType)intRLEPacketType == RLEPacketType.RUN_LENGTH)
                                {
                                    // get the pixel color data
                                    bRunLengthPixel = binReader.ReadBytes((int)this.objTargaHeader.BytesPerPixel);

                                    // add the number of pixels specified using the read pixel color
                                    for (int i = 0; i < intRLEPixelCount; i++)
                                    {
                                        foreach (byte b in bRunLengthPixel)
                                            row.Add(b);

                                        // increment the byte counts
                                        intImageRowBytesRead += bRunLengthPixel.Length;
                                        intImageBytesRead += bRunLengthPixel.Length;

                                        // if we have read a full image row
                                        // add the row to the row list and clear it
                                        // restart row byte count
                                        if (intImageRowBytesRead == intImageRowByteSize)
                                        {
                                            rows.Add(row);
                                            row = null;
                                            row = new System.Collections.Generic.List<byte>();
                                            intImageRowBytesRead = 0;
                                            
                                        }
                                    }

                                }

                                else if ((RLEPacketType)intRLEPacketType == RLEPacketType.RAW)
                                {
                                    // get the number of bytes to read based on the read pixel count
                                    int intBytesToRead = intRLEPixelCount * (int)this.objTargaHeader.BytesPerPixel;

                                    // read each byte
                                    for (int i = 0;i < intBytesToRead;i++)
                                    {
                                        row.Add(binReader.ReadByte());

                                        // increment the byte counts
                                        intImageBytesRead++;
                                        intImageRowBytesRead++;

                                        // if we have read a full image row
                                        // add the row to the row list and clear it
                                        // restart row byte count
                                        if (intImageRowBytesRead == intImageRowByteSize)
                                        {
                                            rows.Add(row);
                                            row = null;
                                            row = new System.Collections.Generic.List<byte>();
                                            intImageRowBytesRead = 0;
                                        }

                                    }

                                }
                            }

                        #endregion

                    }

                    else
                    {
                        #region NON-COMPRESSED

                        // loop through each row in the image
                        for (int i = 0; i < (int)this.objTargaHeader.Height; i++)
                        {
                            // loop through each byte in the row
                            for (int j = 0; j < intImageRowByteSize; j++)
                            {
                                // add the byte to the row
                                row.Add(binReader.ReadByte());
                            }

                            // add row to the list of rows
                            rows.Add(row);                            
                            // create a new row
                            row = null;
                            row = new System.Collections.Generic.List<byte>();
                        }

                        
                        #endregion
                    }

                    // flag that states whether or not to reverse the location of all rows.
                    bool blnRowsReverse = false;

                    // flag that states whether or not to reverse the bytes in each row.
                    bool blnEachRowReverse = false;

                    // use FirstPixelDestination to determine the alignment of the 
                    // image data byte
                    switch (this.objTargaHeader.FirstPixelDestination)
                    {
                        case FirstPixelDestination.TOP_LEFT:
                            blnRowsReverse = false;
                            blnEachRowReverse = true;
                            break;

                        case FirstPixelDestination.TOP_RIGHT:
                            blnRowsReverse = false;
                            blnEachRowReverse = false;
                            break;

                        case FirstPixelDestination.BOTTOM_LEFT:
                            blnRowsReverse = true;
                            blnEachRowReverse = true;
                            break;

                        case FirstPixelDestination.BOTTOM_RIGHT:
                        case FirstPixelDestination.UNKNOWN:
                            blnRowsReverse = true;
                            blnEachRowReverse = false;

                            break;
                    }

                    // write the bytes from each row into a memory stream and get the 
                    // resulting byte array
                    using (msData = new MemoryStream())
                    {

                        // do we reverse the rows in the row list.
                        if (blnRowsReverse == true)
                            rows.Reverse();

                        // go through each row
                        for (int i = 0; i < rows.Count; i++)
                        {
                            // do we reverse the bytes in the row
                            if (blnEachRowReverse == true)
                                rows[i].Reverse();

                            // get the byte array for the row
                            byte[] brow = rows[i].ToArray();

                            // write the row bytes and padding bytes to the memory streem
                            msData.Write(brow, 0, brow.Length);
                            msData.Write(padding, 0, padding.Length);
                        }
                        // get the image byte array
                        data = msData.ToArray(); 
                        
                    }

                    // clear our row arrays
                    if (rows != null)
                    {
                        for(int i =0; i<rows.Count;i++){                            
                            rows[i].Clear();
                            rows[i] = null;
                        }
                        rows.Clear();
                        rows = null;
                    }
                    if (rows != null)
                    {
                        row.Clear();
                        row = null;
                    }

                }
                else
                {
                    this.ClearAll();
                    throw new Exception(@"Error loading file, No image data in file.");
                }
            }
            else
            {
                this.ClearAll();
                throw new Exception(@"Error loading file, could not read file from disk.");
            }

            // return the image byte array
            return data;

        }

        /// <summary>
        /// Reads the image data bytes from the file and loads them into the Image Bitmap object.
        /// Also loads the color map, if any, into the Image Bitmap.
        /// </summary>
        /// <param name="binReader">A BinaryReader that points the loaded file byte stream.</param>
        private void LoadTGAImage(BinaryReader binReader)
        {

            // make sure we don't have a phantom Bitmap
            if (this.bmpTargaImage != null)
            {
                this.bmpTargaImage.Dispose();
            }

            // make sure we don't have a phantom Thumbnail
            if (this.bmpImageThumbnail != null)
            {
                this.bmpImageThumbnail.Dispose();
            }

            //**************  NOTE  *******************
            // The memory allocated for Microsoft Bitmaps must be aligned on a 32bit boundary.
            // The stride refers to the number of bytes allocated for one scanline of the bitmap.
            // In your loop, you copy the pixels one scanline at a time and take into
            // consideration the amount of padding that occurs due to memory alignment.
            // calculate the stride, in bytes, of the image (32bit aligned width of each image row)
            this.intStride = (((int)this.objTargaHeader.Width * (int)this.objTargaHeader.PixelDepth + 31) & ~31) >> 3; // width in bytes

            // calculate the padding, in bytes, of the image 
            // number of bytes to add to make each row a 32bit aligned row
            // padding in bytes
            this.intPadding = this.intStride - ((((int)this.objTargaHeader.Width * (int)this.objTargaHeader.PixelDepth) + 7) / 8);


            // get the Pixel format to use with the Bitmap object
            PixelFormat pf = this.GetPixelFormat();

            // get the image data bytes
            byte[] bimagedata = this.LoadImageBytes(binReader);

            // since the Bitmap constructor requires a poiter to an array of image bytes
            // we have to pin down the memory used by the byte array and use the pointer 
            // of this pinned memory to create the Bitmap.
            // This tells the Garbage Collector to leave the memory alone and DO NOT touch it.
            this.ImageByteHandle = GCHandle.Alloc(bimagedata, GCHandleType.Pinned);



            // create a Bitmap object using the image Width, Height,
            // Stride, PixelFormat and the pointer to the pinned byte array.
            this.bmpTargaImage = new Bitmap((int)this.objTargaHeader.Width,
                                            (int)this.objTargaHeader.Height,
                                            this.intStride,
                                            pf,
                                            this.ImageByteHandle.AddrOfPinnedObject());

            // lets free the pinned bytes
            if (this.ImageByteHandle != null && this.ImageByteHandle.IsAllocated)
                this.ImageByteHandle.Free();


            // load the thumbnail if any.
            this.LoadThumbnail(binReader, pf);



            // load the color map into the Bitmap, if it exists
            if (this.objTargaHeader.ColorMap.Count > 0)
            {
                // get the Bitmap's current palette
                ColorPalette pal = this.bmpTargaImage.Palette;

                // loop trough each color in the loaded file's color map
                for (int i = 0; i < this.objTargaHeader.ColorMap.Count; i++)
                {
                    // is the AttributesType 0 or 1 bit
                    bool forceopaque = false;

                    if (this.Format == TGAFormat.NEW_TGA && this.objTargaFooter.ExtensionAreaOffset > 0)
                    {
                        if (this.objTargaExtensionArea.AttributesType == 0 || this.objTargaExtensionArea.AttributesType == 1)
                            forceopaque = true;
                    }
                    else if (this.Header.AttributeBits == 0 || this.Header.AttributeBits == 1)
                            forceopaque = true;
                    
                    if (forceopaque)
                        // use 255 for alpha ( 255 = opaque/visible ) so we can see the image
                        pal.Entries[i] = Color.FromArgb(255, this.objTargaHeader.ColorMap[i].R, this.objTargaHeader.ColorMap[i].G, this.objTargaHeader.ColorMap[i].B);

                    else
                        // use whatever value is there
                        pal.Entries[i] = this.objTargaHeader.ColorMap[i];

                }

                // set the new palette back to the Bitmap object
                this.bmpTargaImage.Palette = pal;

                // set the palette to the thumbnail also, if there is one
                if (this.bmpImageThumbnail != null)
                {
                    this.bmpImageThumbnail.Palette = pal;
                }

                pal = null;
            }
            else
            { // no color map


                // check to see if this is a Black and White (Greyscale)
                if (this.objTargaHeader.PixelDepth == 8 && (this.objTargaHeader.ImageType == ImageType.UNCOMPRESSED_BLACK_AND_WHITE ||
                    this.objTargaHeader.ImageType == ImageType.RUN_LENGTH_ENCODED_BLACK_AND_WHITE))
                {
                    // get the current palette
                    ColorPalette pal = this.bmpTargaImage.Palette;

                    // create the Greyscale palette
                    for (int i = 0; i < 256; i++)
                    {
                        pal.Entries[i] = Color.FromArgb(i, i, i);
                    }

                    // set the new palette back to the Bitmap object
                    this.bmpTargaImage.Palette = pal;

                    // set the palette to the thumbnail also, if there is one
                    if (this.bmpImageThumbnail != null)
                    {
                        this.bmpImageThumbnail.Palette = pal;
                    }
                    pal = null;
                }
                

            }


            
        }

        /// <summary>
        /// Gets the PixelFormat to be used by the Image based on the Targa file's attributes
        /// </summary>
        /// <returns></returns>
        private PixelFormat GetPixelFormat()
        {
            
            PixelFormat pfTargaPixelFormat = PixelFormat.Undefined;

            // first off what is our Pixel Depth (bits per pixel)
            switch (this.objTargaHeader.PixelDepth)
            {
                case 8:
                    pfTargaPixelFormat = PixelFormat.Format8bppIndexed;
                    break;
            
                case 16:
                    // if this is a new tga file and we have an extension area, we'll determine the alpha based on 
                    // the extension area Attributes 
                    if (this.Format == TGAFormat.NEW_TGA && this.objTargaFooter.ExtensionAreaOffset > 0)
                    {
                        switch (this.objTargaExtensionArea.AttributesType)
                        {
                            case 0:
                            case 1:
                            case 2: // no alpha data
                                pfTargaPixelFormat = PixelFormat.Format16bppRgb555;
                                break;

                            case 3: // useful alpha data
                                pfTargaPixelFormat = PixelFormat.Format16bppArgb1555;
                                break;
                        }
                    }
                    else
                    {
                        // just a regular tga, determine the alpha based on the Header Attributes
                        if (this.Header.AttributeBits == 0)
                            pfTargaPixelFormat = PixelFormat.Format16bppRgb555;
                        if (this.Header.AttributeBits == 1)
                            pfTargaPixelFormat = PixelFormat.Format16bppArgb1555;
                    }

                    break;

                case 24:
                    pfTargaPixelFormat = PixelFormat.Format24bppRgb;
                    break;

                case 32:
                    //PixelFormat.Format32bppArgb
                    //PixelFormat.Format32bppPArgb
                    //PixelFormat.Format32bppRgb
                    if (this.Format == TGAFormat.NEW_TGA)
                    {
                        switch (this.objTargaExtensionArea.AttributesType)
                        {
                            case 0: // No Alpha Data included
                                pfTargaPixelFormat = PixelFormat.Format32bppRgb;
                                break;

                            case 1: // Undefined data, can be ignored
                            case 2: // Undefined data, should be retained
                            case 3: // Non-premultiplied Alpha
                                pfTargaPixelFormat = PixelFormat.Format32bppArgb;
                                break;

                            case 4: // Premultiplied Alpha
                                pfTargaPixelFormat = PixelFormat.Format32bppPArgb;
                                break;
                        }
                    }
                    else
                    {
                        // Normally it is better to keep the alpha, non premultiplied
                        pfTargaPixelFormat = PixelFormat.Format32bppArgb;
                        break;
                    }
                    break;
                
            }


            return pfTargaPixelFormat;
        }

        
        /// <summary>
        /// Loads the thumbnail of the loaded image file, if any.
        /// </summary>
        /// <param name="binReader">A BinaryReader that points the loaded file byte stream.</param>
        /// <param name="pfPixelFormat">A PixelFormat value indicating what pixel format to use when loading the thumbnail.</param>
        private void LoadThumbnail(BinaryReader binReader, PixelFormat pfPixelFormat)
        {

            // read the Thumbnail image data into a byte array
            // take into account stride has to be a multiple of 4
            // use padding to make sure multiple of 4    

            byte[] data = null;
            if (binReader != null && binReader.BaseStream != null && binReader.BaseStream.Length > 0 && binReader.BaseStream.CanSeek == true)
            {
                if (this.ExtensionArea.PostageStampOffset > 0)
                {

                    // seek to the beginning of the image data using the ImageDataOffset value
                    binReader.BaseStream.Seek(this.ExtensionArea.PostageStampOffset, SeekOrigin.Begin);

                    int iWidth = (int)binReader.ReadByte();
                    int iHeight = (int)binReader.ReadByte();

                    int iStride = ((iWidth * (int)this.objTargaHeader.PixelDepth + 31) & ~31) >> 3; // width in bytes
                    int iPadding = iStride - (((iWidth * (int)this.objTargaHeader.PixelDepth) + 7) / 8);

                    System.Collections.Generic.List<System.Collections.Generic.List<byte>> rows = new System.Collections.Generic.List<System.Collections.Generic.List<byte>>();
                    System.Collections.Generic.List<byte> row = new System.Collections.Generic.List<byte>();




                    byte[] padding = new byte[iPadding];
                    MemoryStream msData = null;
                    bool blnEachRowReverse = false;
                    bool blnRowsReverse = false;

                    
                    using (msData = new MemoryStream())
                    {
                        // get the size in bytes of each row in the image
                        int intImageRowByteSize = iWidth * ((int)this.objTargaHeader.PixelDepth / 8);

                        // get the size in bytes of the whole image
                        int intImageByteSize = intImageRowByteSize * iHeight;

                        // thumbnails are never compressed
                        for (int i = 0; i < iHeight; i++)
                        {
                            for (int j = 0; j < intImageRowByteSize; j++)
                            {
                                row.Add(binReader.ReadByte());
                            }
                            rows.Add(row);
                            row = null;
                            row = new System.Collections.Generic.List<byte>();
                        }
                        
                        switch (this.objTargaHeader.FirstPixelDestination)
                        {
                            case FirstPixelDestination.TOP_LEFT:
                                break;

                            case FirstPixelDestination.TOP_RIGHT:
                                blnRowsReverse = false;
                                blnEachRowReverse = false;
                                break;

                            case FirstPixelDestination.BOTTOM_LEFT:
                                break;

                            case FirstPixelDestination.BOTTOM_RIGHT:
                            case FirstPixelDestination.UNKNOWN:
                                blnRowsReverse = true;
                                blnEachRowReverse = false;

                                break;
                        }

                        if (blnRowsReverse == true)
                            rows.Reverse();

                        for (int i = 0; i < rows.Count; i++)
                        {
                            if (blnEachRowReverse == true)
                                rows[i].Reverse();

                            byte[] brow = rows[i].ToArray();
                            msData.Write(brow, 0, brow.Length);
                            msData.Write(padding, 0, padding.Length);
                        }
                        data = msData.ToArray();
                    }

                    if (data != null && data.Length > 0)
                    {
                        this.ThumbnailByteHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
                        this.bmpImageThumbnail = new Bitmap(iWidth, iHeight, iStride, pfPixelFormat,
                                                        this.ThumbnailByteHandle.AddrOfPinnedObject());

                        if (this.ThumbnailByteHandle != null && this.ThumbnailByteHandle.IsAllocated)
                            this.ThumbnailByteHandle.Free();
                    }


                    // clear our row arrays
                    if (rows != null)
                    {
                        for (int i = 0; i < rows.Count; i++)
                        {
                            rows[i].Clear();
                            rows[i] = null;
                        }
                        rows.Clear();
                        rows = null;
                    }
                    if (rows != null)
                    {
                        row.Clear();
                        row = null;
                    }

                }
                else
                {
                    if (this.bmpImageThumbnail != null)
                    {
                        this.bmpImageThumbnail.Dispose();
                        this.bmpImageThumbnail = null;
                    }
                }
            }
            else
            {
                if (this.bmpImageThumbnail != null)
                {
                    this.bmpImageThumbnail.Dispose();
                    this.bmpImageThumbnail = null;
                }
            }

        }

        /// <summary>
        /// Clears out all objects and resources.
        /// </summary>
        private void ClearAll()
        {
            if (this.bmpTargaImage != null)
            {
                this.bmpTargaImage.Dispose();
                this.bmpTargaImage = null;
            }

            if (this.bmpImageThumbnail != null)
            {
                this.bmpImageThumbnail.Dispose();
                this.bmpImageThumbnail = null;
            }

            if (this.ImageByteHandle != null && this.ImageByteHandle.IsAllocated)
                this.ImageByteHandle.Free();


            if (this.ThumbnailByteHandle != null && this.ThumbnailByteHandle.IsAllocated)
                this.ThumbnailByteHandle.Free();

            this.objTargaHeader = new TargaHeader();
            this.objTargaExtensionArea = new TargaExtensionArea();
            this.objTargaFooter = new TargaFooter();
            this.eTGAFormat = TGAFormat.UNKNOWN;
            this.intStride = 0;
            this.intPadding = 0;            
            this.strFileName = string.Empty;
        
        }

        /// <summary>
        /// Loads a Targa image file into a Bitmap object.
        /// </summary>
        /// <param name="sFileName">The Targa image filename</param>
        /// <returns>A Bitmap object with the Targa image loaded into it.</returns>
        public static Bitmap LoadTargaImage(string sFileName)
        {
            using (TargaImage ti = new TargaImage(sFileName))
            {
                return CopyToBitmap(ti);
            }
        }

        /// <summary>
        /// Loads a stream with Targa image data into a Bitmap object.
        /// </summary>
        /// <param name="sFileName">The Targa image stream</param>
        /// <returns>A Bitmap object with the Targa image loaded into it.</returns>
        public static Bitmap LoadTargaImage(Stream ImageStream)
        {
            using (TargaImage ti = new TargaImage(ImageStream))
            {
                return CopyToBitmap(ti);
            }
        }

        private static Bitmap CopyToBitmap(TargaImage ti)
        {
            Bitmap b = null;
            if (ti.Image.PixelFormat == PixelFormat.Format8bppIndexed)
            {
                b = (Bitmap)ti.Image.Clone();
            }
            else
            {
                b = new Bitmap(ti.Image.Width, ti.Image.Height, ti.Image.PixelFormat);
                using (Graphics g = Graphics.FromImage(b))
                {
                    g.DrawImage(ti.Image, 0, 0, new Rectangle(0, 0, b.Width, b.Height), GraphicsUnit.Pixel);
                }
            }
            return b;
        }


        #region IDisposable Members

        /// <summary>
        /// Disposes all resources used by this instance of the TargaImage class.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            // Take yourself off the Finalization queue 
            // to prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);

        }


        /// <summary>
        /// Dispose(bool disposing) executes in two distinct scenarios.
        /// If disposing equals true, the method has been called directly
        /// or indirectly by a user's code. Managed and unmanaged resources
        /// can be disposed.
        /// If disposing equals false, the method has been called by the 
        /// runtime from inside the finalizer and you should not reference 
        /// other objects. Only unmanaged resources can be disposed.
        /// </summary>
        /// <param name="disposing">If true dispose all resources, else dispose only release unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.
                    if (this.bmpTargaImage != null)
                    {
                        this.bmpTargaImage.Dispose();
                    }

                    if (this.bmpImageThumbnail != null)
                    {
                        this.bmpImageThumbnail.Dispose();
                    }

                    if (this.ImageByteHandle != null)
                    {
                        if (this.ImageByteHandle.IsAllocated)
                        {
                            this.ImageByteHandle.Free();
                        }
                       
                    }

                    if (this.ThumbnailByteHandle != null)
                    {
                        if (this.ThumbnailByteHandle.IsAllocated)
                        {
                            this.ThumbnailByteHandle.Free();
                        }

                    }

                    if (this.objTargaHeader != null)
                    {
                        objTargaHeader.ColorMap.Clear();
                        objTargaHeader = null;
                    }
                    if (this.objTargaExtensionArea != null)
                    {
                        objTargaExtensionArea.ColorCorrectionTable.Clear();
                        objTargaExtensionArea.ScanLineTable.Clear();
                        objTargaExtensionArea = null;
                    }

                    objTargaFooter = null;

                }
                // Release unmanaged resources. If disposing is false, 
                // only the following code is executed.
                // ** release unmanged resources here **

                // Note that this is not thread safe.
                // Another thread could start disposing the object
                // after the managed resources are disposed,
                // but before the disposed flag is set to true.
                // If thread safety is necessary, it must be
                // implemented by the client.

            }
            disposed = true;
        }


        #endregion
    }


    /// <summary>
    /// This class holds all of the header properties of a Targa image. 
    /// This includes the TGA File Header section the ImageID and the Color Map.
    /// </summary>
    public class TargaHeader
    {
        private byte bImageIDLength = 0;
        private ColorMapType eColorMapType = ColorMapType.NO_COLOR_MAP;
        private ImageType eImageType = ImageType.NO_IMAGE_DATA;
        private short sColorMapFirstEntryIndex = 0;
        private short sColorMapLength = 0;
        private byte bColorMapEntrySize = 0;
        private short sXOrigin = 0;
        private short sYOrigin = 0;
        private short sWidth = 0;
        private short sHeight = 0;
        private byte bPixelDepth = 0;
        private byte bImageDescriptor = 0;
        private VerticalTransferOrder eVerticalTransferOrder = VerticalTransferOrder.UNKNOWN;
        private HorizontalTransferOrder eHorizontalTransferOrder = HorizontalTransferOrder.UNKNOWN;
        private byte bAttributeBits = 0;
        private string strImageIDValue = string.Empty;
        private System.Collections.Generic.List<System.Drawing.Color> cColorMap = new List<System.Drawing.Color>();
        
        /// <summary>
        /// Gets the number of bytes contained the ImageIDValue property. The maximum
        /// number of characters is 255. A value of zero indicates that no ImageIDValue is included with the
        /// image.
        /// </summary>
        public byte ImageIDLength
        {
            get { return this.bImageIDLength; }
        }

        /// <summary>
        /// Sets the ImageIDLength property, available only to objects in the same assembly as TargaHeader.
        /// </summary>
        /// <param name="bImageIDLength">The Image ID Length value read from the file.</param>
        internal protected void SetImageIDLength(byte bImageIDLength)
        {
            this.bImageIDLength = bImageIDLength;
        }

        /// <summary>
        /// Gets the type of color map (if any) included with the image. There are currently 2
        /// defined values for this field:
        /// NO_COLOR_MAP - indicates that no color-map data is included with this image.
        /// COLOR_MAP_INCLUDED - indicates that a color-map is included with this image.
        /// </summary>
        public ColorMapType ColorMapType
        {
            get { return this.eColorMapType; }
        }

        /// <summary>
        /// Sets the ColorMapType property, available only to objects in the same assembly as TargaHeader.
        /// </summary>
        /// <param name="eColorMapType">One of the ColorMapType enumeration values.</param>
        internal protected void SetColorMapType(ColorMapType eColorMapType)
        {
            this.eColorMapType = eColorMapType;
        }

        /// <summary>
        /// Gets one of the ImageType enumeration values indicating the type of Targa image read from the file.
        /// </summary>
        public ImageType ImageType
        {
            get { return this.eImageType; }
        }

        /// <summary>
        /// Sets the ImageType property, available only to objects in the same assembly as TargaHeader.
        /// </summary>
        /// <param name="eImageType">One of the ImageType enumeration values.</param>
        internal protected void SetImageType(ImageType eImageType)
        {
            this.eImageType = eImageType;
        }

        /// <summary>
        /// Gets the index of the first color map entry. ColorMapFirstEntryIndex refers to the starting entry in loading the color map.
        /// </summary>
        public short ColorMapFirstEntryIndex
        {
            get { return this.sColorMapFirstEntryIndex; }
        }

        /// <summary>
        /// Sets the ColorMapFirstEntryIndex property, available only to objects in the same assembly as TargaHeader.
        /// </summary>
        /// <param name="sColorMapFirstEntryIndex">The First Entry Index value read from the file.</param>
        internal protected void SetColorMapFirstEntryIndex(short sColorMapFirstEntryIndex)
        {
            this.sColorMapFirstEntryIndex = sColorMapFirstEntryIndex;
        }

        /// <summary>
        /// Gets total number of color map entries included.
        /// </summary>
        public short ColorMapLength
        {
            get { return this.sColorMapLength; }
        }

        /// <summary>
        /// Sets the ColorMapLength property, available only to objects in the same assembly as TargaHeader.
        /// </summary>
        /// <param name="sColorMapLength">The Color Map Length value read from the file.</param>
        internal protected void SetColorMapLength(short sColorMapLength)
        {
            this.sColorMapLength = sColorMapLength;
        }

        /// <summary>
        /// Gets the number of bits per entry in the Color Map. Typically 15, 16, 24 or 32-bit values are used.
        /// </summary>
        public byte ColorMapEntrySize
        {
            get { return this.bColorMapEntrySize; }
        }

        /// <summary>
        /// Sets the ColorMapEntrySize property, available only to objects in the same assembly as TargaHeader.
        /// </summary>
        /// <param name="bColorMapEntrySize">The Color Map Entry Size value read from the file.</param>
        internal protected void SetColorMapEntrySize(byte bColorMapEntrySize)
        {
            this.bColorMapEntrySize = bColorMapEntrySize;
        }

        /// <summary>
        /// Gets the absolute horizontal coordinate for the lower
        /// left corner of the image as it is positioned on a display device having
        /// an origin at the lower left of the screen (e.g., the TARGA series).
        /// </summary>
        public short XOrigin
        {
            get { return this.sXOrigin; }
        }

        /// <summary>
        /// Sets the XOrigin property, available only to objects in the same assembly as TargaHeader.
        /// </summary>
        /// <param name="sXOrigin">The X Origin value read from the file.</param>
        internal protected void SetXOrigin(short sXOrigin)
        {
            this.sXOrigin = sXOrigin;
        }

        /// <summary>
        /// These bytes specify the absolute vertical coordinate for the lower left
        /// corner of the image as it is positioned on a display device having an
        /// origin at the lower left of the screen (e.g., the TARGA series).
        /// </summary>
        public short YOrigin
        {
            get { return this.sYOrigin; }
        }

        /// <summary>
        /// Sets the YOrigin property, available only to objects in the same assembly as TargaHeader.
        /// </summary>
        /// <param name="sYOrigin">The Y Origin value read from the file.</param>
        internal protected void SetYOrigin(short sYOrigin)
        {
            this.sYOrigin = sYOrigin;
        }

        /// <summary>
        /// Gets the width of the image in pixels.
        /// </summary>
        public short Width
        {
            get { return this.sWidth; }
        }

        /// <summary>
        /// Sets the Width property, available only to objects in the same assembly as TargaHeader.
        /// </summary>
        /// <param name="sWidth">The Width value read from the file.</param>
        internal protected void SetWidth(short sWidth)
        {
            this.sWidth = sWidth;
        }

        /// <summary>
        /// Gets the height of the image in pixels.
        /// </summary>
        public short Height
        {
            get { return this.sHeight; }
        }

        /// <summary>
        /// Sets the Height property, available only to objects in the same assembly as TargaHeader.
        /// </summary>
        /// <param name="sHeight">The Height value read from the file.</param>
        internal protected void SetHeight(short sHeight)
        {
            this.sHeight = sHeight;
        }

        /// <summary>
        /// Gets the number of bits per pixel. This number includes
        /// the Attribute or Alpha channel bits. Common values are 8, 16, 24 and 32.
        /// </summary>
        public byte PixelDepth
        {
            get { return this.bPixelDepth; }
        }

        /// <summary>
        /// Sets the PixelDepth property, available only to objects in the same assembly as TargaHeader.
        /// </summary>
        /// <param name="bPixelDepth">The Pixel Depth value read from the file.</param>
        internal protected void SetPixelDepth(byte bPixelDepth)
        {
            this.bPixelDepth = bPixelDepth;
        }

        /// <summary>
        /// Gets or Sets the ImageDescriptor property. The ImageDescriptor is the byte that holds the 
        /// Image Origin and Attribute Bits values.
        /// Available only to objects in the same assembly as TargaHeader.
        /// </summary>
        internal protected byte ImageDescriptor
        {
            get { return this.bImageDescriptor; }
            set { this.bImageDescriptor = value; }
        }

        /// <summary>
        /// Gets one of the FirstPixelDestination enumeration values specifying the screen destination of first pixel based on VerticalTransferOrder and HorizontalTransferOrder
        /// </summary>
        public FirstPixelDestination FirstPixelDestination
        {
            get 
            {

                if (this.eVerticalTransferOrder == VerticalTransferOrder.UNKNOWN || this.eHorizontalTransferOrder == HorizontalTransferOrder.UNKNOWN)
                     return FirstPixelDestination.UNKNOWN; 
                else if (this.eVerticalTransferOrder == VerticalTransferOrder.BOTTOM && this.eHorizontalTransferOrder == HorizontalTransferOrder.LEFT)
                    return FirstPixelDestination.BOTTOM_LEFT;
                else if (this.eVerticalTransferOrder == VerticalTransferOrder.BOTTOM && this.eHorizontalTransferOrder == HorizontalTransferOrder.RIGHT)
                    return FirstPixelDestination.BOTTOM_RIGHT;
                else if (this.eVerticalTransferOrder == VerticalTransferOrder.TOP && this.eHorizontalTransferOrder == HorizontalTransferOrder.LEFT)
                    return FirstPixelDestination.TOP_LEFT;
                else 
                    return FirstPixelDestination.TOP_RIGHT;
               
            }
        }


        /// <summary>
        /// Gets one of the VerticalTransferOrder enumeration values specifying the top-to-bottom ordering in which pixel data is transferred from the file to the screen.
        /// </summary>
        public VerticalTransferOrder VerticalTransferOrder
        {
            get { return this.eVerticalTransferOrder; }
        }

        /// <summary>
        /// Sets the VerticalTransferOrder property, available only to objects in the same assembly as TargaHeader.
        /// </summary>
        /// <param name="eVerticalTransferOrder">One of the VerticalTransferOrder enumeration values.</param>
        internal protected void SetVerticalTransferOrder(VerticalTransferOrder eVerticalTransferOrder)
        {
            this.eVerticalTransferOrder = eVerticalTransferOrder;
        }

        /// <summary>
        /// Gets one of the HorizontalTransferOrder enumeration values specifying the left-to-right ordering in which pixel data is transferred from the file to the screen.
        /// </summary>
        public HorizontalTransferOrder HorizontalTransferOrder
        {
            get { return this.eHorizontalTransferOrder; }
        }

        /// <summary>
        /// Sets the HorizontalTransferOrder property, available only to objects in the same assembly as TargaHeader.
        /// </summary>
        /// <param name="eHorizontalTransferOrder">One of the HorizontalTransferOrder enumeration values.</param>
        internal protected void SetHorizontalTransferOrder(HorizontalTransferOrder eHorizontalTransferOrder)
        {
            this.eHorizontalTransferOrder = eHorizontalTransferOrder;
        }

        /// <summary>
        /// Gets the number of attribute bits per pixel.
        /// </summary>
        public byte AttributeBits
        {
            get { return this.bAttributeBits; }
        }

        /// <summary>
        /// Sets the AttributeBits property, available only to objects in the same assembly as TargaHeader.
        /// </summary>
        /// <param name="bAttributeBits">The Attribute Bits value read from the file.</param>
        internal protected void SetAttributeBits(byte bAttributeBits)
        {
            this.bAttributeBits = bAttributeBits;
        }

        /// <summary>
        /// Gets identifying information about the image. 
        /// A value of zero in ImageIDLength indicates that no ImageIDValue is included with the image.
        /// </summary>
        public string ImageIDValue
        {
            get { return this.strImageIDValue; }
        }

        /// <summary>
        /// Sets the ImageIDValue property, available only to objects in the same assembly as TargaHeader.
        /// </summary>
        /// <param name="strImageIDValue">The Image ID value read from the file.</param>
        internal protected void SetImageIDValue(string strImageIDValue)
        {
            this.strImageIDValue = strImageIDValue;
        }

        /// <summary>
        /// Gets the Color Map of the image, if any. The Color Map is represented by a list of System.Drawing.Color objects.
        /// </summary>
        public System.Collections.Generic.List<System.Drawing.Color> ColorMap
        {
            get { return this.cColorMap; }
        }

        /// <summary>
        /// Gets the offset from the beginning of the file to the Image Data.
        /// </summary>
        public int ImageDataOffset
        {
            get 
            {
                // calculate the image data offset

                // start off with the number of bytes holding the header info.
                int intImageDataOffset = TargaConstants.HeaderByteLength;

                // add the Image ID length (could be variable)
                intImageDataOffset += this.bImageIDLength;

                // determine the number of bytes for each Color Map entry
                int Bytes = 0;
                switch (this.bColorMapEntrySize)
                {
                    case 15:
                        Bytes = 2;
                        break;
                    case 16:
                        Bytes = 2;
                        break;
                    case 24:
                        Bytes = 3;
                        break;
                    case 32:
                        Bytes = 4;
                        break;
                }

                // add the length of the color map
                intImageDataOffset += ((int)this.sColorMapLength * (int)Bytes);

                // return result
                return intImageDataOffset; 
            }
        }

        /// <summary>
        /// Gets the number of bytes per pixel.
        /// </summary>
        public int BytesPerPixel
        {
            get
            {
                return (int)this.bPixelDepth / 8;
            }
        }
    }


    /// <summary>
    /// Holds Footer infomation read from the image file.
    /// </summary>
    public class TargaFooter
    {
        private int intExtensionAreaOffset = 0;
        private int intDeveloperDirectoryOffset = 0;
        private string strSignature = string.Empty;
        private string strReservedCharacter = string.Empty;
        
        /// <summary>
        /// Gets the offset from the beginning of the file to the start of the Extension Area. 
        /// If the ExtensionAreaOffset is zero, no Extension Area exists in the file.
        /// </summary>
        public int ExtensionAreaOffset
        {
            get { return this.intExtensionAreaOffset; }
        }

        /// <summary>
        /// Sets the ExtensionAreaOffset property, available only to objects in the same assembly as TargaFooter.
        /// </summary>
        /// <param name="intExtensionAreaOffset">The Extension Area Offset value read from the file.</param>
        internal protected void SetExtensionAreaOffset(int intExtensionAreaOffset)
        {
            this.intExtensionAreaOffset = intExtensionAreaOffset;
        }

        /// <summary>
        /// Gets the offset from the beginning of the file to the start of the Developer Area.
        /// If the DeveloperDirectoryOffset is zero, then the Developer Area does not exist
        /// </summary>
        public int DeveloperDirectoryOffset
        {
            get { return this.intDeveloperDirectoryOffset; }
        }

        /// <summary>
        /// Sets the DeveloperDirectoryOffset property, available only to objects in the same assembly as TargaFooter.
        /// </summary>
        /// <param name="intDeveloperDirectoryOffset">The Developer Directory Offset value read from the file.</param>
        internal protected void SetDeveloperDirectoryOffset(int intDeveloperDirectoryOffset)
        {
            this.intDeveloperDirectoryOffset = intDeveloperDirectoryOffset;
        }

        /// <summary>
        /// This string is formatted exactly as "TRUEVISION-XFILE" (no quotes). If the
        /// signature is detected, the file is assumed to be a New TGA format and MAY,
        /// therefore, contain the Developer Area and/or the Extension Areas. If the
        /// signature is not found, then the file is assumed to be an Original TGA format.
        /// </summary>
        public string Signature
        {
            get { return this.strSignature; }
        }

        /// <summary>
        /// Sets the Signature property, available only to objects in the same assembly as TargaFooter.
        /// </summary>
        /// <param name="strSignature">The Signature value read from the file.</param>
        internal protected void SetSignature(string strSignature)
        {
            this.strSignature = strSignature;
        }

        /// <summary>
        /// A New Targa format reserved character "." (period)
        /// </summary>
        public string ReservedCharacter
        {
            get { return this.strReservedCharacter; }
        }

        /// <summary>
        /// Sets the ReservedCharacter property, available only to objects in the same assembly as TargaFooter.
        /// </summary>
        /// <param name="strReservedCharacter">The ReservedCharacter value read from the file.</param>
        internal protected void SetReservedCharacter(string strReservedCharacter)
        {
            this.strReservedCharacter = strReservedCharacter;
        }

        /// <summary>
        /// Creates a new instance of the TargaFooter class.
        /// </summary>
        public TargaFooter()
        {}
        

    }


    /// <summary>
    /// This class holds all of the Extension Area properties of the Targa image. If an Extension Area exists in the file.
    /// </summary>
    public class TargaExtensionArea
    {
        int intExtensionSize = 0;
        string strAuthorName = string.Empty;
        string strAuthorComments = string.Empty;
        DateTime dtDateTimeStamp = DateTime.Now;
        string strJobName = string.Empty;
        TimeSpan dtJobTime = TimeSpan.Zero;
        string strSoftwareID = string.Empty;
        string strSoftwareVersion = string.Empty;
        Color cKeyColor = Color.Empty;
        int intPixelAspectRatioNumerator = 0;
        int intPixelAspectRatioDenominator = 0;
        int intGammaNumerator = 0;
        int intGammaDenominator = 0;
        int intColorCorrectionOffset = 0;
        int intPostageStampOffset = 0;
        int intScanLineOffset = 0;
        int intAttributesType = 0;
        private System.Collections.Generic.List<int> intScanLineTable = new List<int>();
        private System.Collections.Generic.List<System.Drawing.Color> cColorCorrectionTable = new List<System.Drawing.Color>();

        /// <summary>
        /// Gets the number of Bytes in the fixed-length portion of the ExtensionArea. 
        /// For Version 2.0 of the TGA File Format, this number should be set to 495
        /// </summary>
        public int ExtensionSize
        {
            get { return this.intExtensionSize; }
        }

        /// <summary>
        /// Sets the ExtensionSize property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="intExtensionSize">The Extension Size value read from the file.</param>
        internal protected void SetExtensionSize(int intExtensionSize)
        {
            this.intExtensionSize = intExtensionSize;
        }

        /// <summary>
        /// Gets the name of the person who created the image.
        /// </summary>
        public string AuthorName
        {
            get { return this.strAuthorName; }
        }

        /// <summary>
        /// Sets the AuthorName property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="strAuthorName">The Author Name value read from the file.</param>
        internal protected void SetAuthorName(string strAuthorName)
        {
            this.strAuthorName = strAuthorName;
        }

        /// <summary>
        /// Gets the comments from the author who created the image.
        /// </summary>
        public string AuthorComments
        {
            get { return this.strAuthorComments; }
        }

        /// <summary>
        /// Sets the AuthorComments property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="strAuthorComments">The Author Comments value read from the file.</param>
        internal protected void SetAuthorComments(string strAuthorComments)
        {
            this.strAuthorComments = strAuthorComments;
        }

        /// <summary>
        /// Gets the date and time that the image was saved.
        /// </summary>
        public DateTime DateTimeStamp
        {
            get { return this.dtDateTimeStamp; }
        }

        /// <summary>
        /// Sets the DateTimeStamp property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="dtDateTimeStamp">The Date Time Stamp value read from the file.</param>
        internal protected void SetDateTimeStamp(DateTime dtDateTimeStamp)
        {
            this.dtDateTimeStamp = dtDateTimeStamp;
        }

        /// <summary>
        /// Gets the name or id tag which refers to the job with which the image was associated.
        /// </summary>
        public string JobName
        {
            get { return this.strJobName; }
        }

        /// <summary>
        /// Sets the JobName property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="strJobName">The Job Name value read from the file.</param>
        internal protected void SetJobName(string strJobName)
        {
            this.strJobName = strJobName;
        }

        /// <summary>
        /// Gets the job elapsed time when the image was saved.
        /// </summary>
        public TimeSpan JobTime
        {
            get { return this.dtJobTime; }
        }

        /// <summary>
        /// Sets the JobTime property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="dtJobTime">The Job Time value read from the file.</param>
        internal protected void SetJobTime(TimeSpan dtJobTime)
        {
            this.dtJobTime = dtJobTime;
        }

        /// <summary>
        /// Gets the Software ID. Usually used to determine and record with what program a particular image was created.
        /// </summary>
        public string SoftwareID
        {
            get { return this.strSoftwareID; }
        }

        /// <summary>
        /// Sets the SoftwareID property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="strSoftwareID">The Software ID value read from the file.</param>
        internal protected void SetSoftwareID(string strSoftwareID)
        {
            this.strSoftwareID = strSoftwareID;
        }

        /// <summary>
        /// Gets the version of software defined by the SoftwareID.
        /// </summary>
        public string SoftwareVersion
        {
            get { return this.strSoftwareVersion; }
        }

        /// <summary>
        /// Sets the SoftwareVersion property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="strSoftwareVersion">The Software Version value read from the file.</param>
        internal protected void SetSoftwareVersion(string strSoftwareVersion)
        {
            this.strSoftwareVersion = strSoftwareVersion;
        }

        /// <summary>
        /// Gets the key color in effect at the time the image is saved.
        /// The Key Color can be thought of as the "background color" or "transparent color".
        /// </summary>
        public Color KeyColor
        {
            get { return this.cKeyColor; }
        }

        /// <summary>
        /// Sets the KeyColor property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="cKeyColor">The Key Color value read from the file.</param>
        internal protected void SetKeyColor(Color cKeyColor)
        {
            this.cKeyColor = cKeyColor;
        }

        /// <summary>
        /// Gets the Pixel Ratio Numerator.
        /// </summary>
        public int PixelAspectRatioNumerator
        {
            get { return this.intPixelAspectRatioNumerator; }
        }

        /// <summary>
        /// Sets the PixelAspectRatioNumerator property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="intPixelAspectRatioNumerator">The Pixel Aspect Ratio Numerator value read from the file.</param>
        internal protected void SetPixelAspectRatioNumerator(int intPixelAspectRatioNumerator)
        {
            this.intPixelAspectRatioNumerator = intPixelAspectRatioNumerator;
        }

        /// <summary>
        /// Gets the Pixel Ratio Denominator.
        /// </summary>
        public int PixelAspectRatioDenominator
        {
            get { return this.intPixelAspectRatioDenominator; }
        }

        /// <summary>
        /// Sets the PixelAspectRatioDenominator property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="intPixelAspectRatioDenominator">The Pixel Aspect Ratio Denominator value read from the file.</param>
        internal protected void SetPixelAspectRatioDenominator(int intPixelAspectRatioDenominator)
        {
            this.intPixelAspectRatioDenominator = intPixelAspectRatioDenominator;
        }

        /// <summary>
        /// Gets the Pixel Aspect Ratio.
        /// </summary>
        public float PixelAspectRatio
        {
            get 
            {
                if (this.intPixelAspectRatioDenominator > 0)
                {
                    return (float)this.intPixelAspectRatioNumerator / (float)this.intPixelAspectRatioDenominator;
                }
                else
                    return 0.0F; 
            }
        }

        /// <summary>
        /// Gets the Gamma Numerator.
        /// </summary>
        public int GammaNumerator
        {
            get { return this.intGammaNumerator; }
        }

        /// <summary>
        /// Sets the GammaNumerator property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="intGammaNumerator">The Gamma Numerator value read from the file.</param>
        internal protected void SetGammaNumerator(int intGammaNumerator)
        {
            this.intGammaNumerator = intGammaNumerator;
        }

        /// <summary>
        /// Gets the Gamma Denominator.
        /// </summary>
        public int GammaDenominator
        {
            get { return this.intGammaDenominator; }
        }

        /// <summary>
        /// Sets the GammaDenominator property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="intGammaDenominator">The Gamma Denominator value read from the file.</param>
        internal protected void SetGammaDenominator(int intGammaDenominator)
        {
            this.intGammaDenominator = intGammaDenominator;
        }

        /// <summary>
        /// Gets the Gamma Ratio.
        /// </summary>
        public float GammaRatio
        {
            get
            {
                if (this.intGammaDenominator > 0)
                {
                    float ratio = (float)this.intGammaNumerator / (float)this.intGammaDenominator;
                    return (float)Math.Round(ratio, 1);
                }
                else
                    return 1.0F;
            }
        }

        /// <summary>
        /// Gets the offset from the beginning of the file to the start of the Color Correction table.
        /// </summary>
        public int ColorCorrectionOffset
        {
            get { return this.intColorCorrectionOffset; }
        }

        /// <summary>
        /// Sets the ColorCorrectionOffset property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="intColorCorrectionOffset">The Color Correction Offset value read from the file.</param>
        internal protected void SetColorCorrectionOffset(int intColorCorrectionOffset)
        {
            this.intColorCorrectionOffset = intColorCorrectionOffset;
        }

        /// <summary>
        /// Gets the offset from the beginning of the file to the start of the Postage Stamp image data.
        /// </summary>
        public int PostageStampOffset
        {
            get { return this.intPostageStampOffset; }
        }

        /// <summary>
        /// Sets the PostageStampOffset property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="intPostageStampOffset">The Postage Stamp Offset value read from the file.</param>
        internal protected void SetPostageStampOffset(int intPostageStampOffset)
        {
            this.intPostageStampOffset = intPostageStampOffset;
        }

        /// <summary>
        /// Gets the offset from the beginning of the file to the start of the Scan Line table.
        /// </summary>
        public int ScanLineOffset
        {
            get { return this.intScanLineOffset; }
        }

        /// <summary>
        /// Sets the ScanLineOffset property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="intScanLineOffset">The Scan Line Offset value read from the file.</param>
        internal protected void SetScanLineOffset(int intScanLineOffset)
        {
            this.intScanLineOffset = intScanLineOffset;
        }

        /// <summary>
        /// Gets the type of Alpha channel data contained in the file.
        /// 0: No Alpha data included.
        /// 1: Undefined data in the Alpha field, can be ignored
        /// 2: Undefined data in the Alpha field, but should be retained
        /// 3: Useful Alpha channel data is present
        /// 4: Pre-multiplied Alpha (see description below)
        /// 5-127: RESERVED
        /// 128-255: Un-assigned
        /// </summary>
        public int AttributesType
        {
            get { return this.intAttributesType; }
        }

        /// <summary>
        /// Sets the AttributesType property, available only to objects in the same assembly as TargaExtensionArea.
        /// </summary>
        /// <param name="intAttributesType">The Attributes Type value read from the file.</param>
        internal protected void SetAttributesType(int intAttributesType)
        {
            this.intAttributesType = intAttributesType;
        }

        /// <summary>
        /// Gets a list of offsets from the beginning of the file that point to the start of the next scan line, 
        /// in the order that the image was saved 
        /// </summary>
        public System.Collections.Generic.List<int> ScanLineTable
        {
            get { return this.intScanLineTable; }
        }

        /// <summary>
        /// Gets a list of Colors where each Color value is the desired Color correction for that entry.
        /// This allows the user to store a correction table for image remapping or LUT driving.
        /// </summary>
        public System.Collections.Generic.List<System.Drawing.Color> ColorCorrectionTable
        {
            get { return this.cColorCorrectionTable; }
        }

    }
    

    /// <summary>
    /// Utilities functions used by the TargaImage class.
    /// </summary>
    static class Utilities
    {

        /// <summary>
        /// Gets an int value representing the subset of bits from a single Byte.
        /// </summary>
        /// <param name="b">The Byte used to get the subset of bits from.</param>
        /// <param name="offset">The offset of bits starting from the right.</param>
        /// <param name="count">The number of bits to read.</param>
        /// <returns>
        /// An int value representing the subset of bits.
        /// </returns>
        /// <remarks>
        /// Given -> b = 00110101 
        /// A call to GetBits(b, 2, 4)
        /// GetBits looks at the following bits in the byte -> 00{1101}00
        /// Returns 1101 as an int (13)
        /// </remarks>
        internal static int GetBits(byte b, int offset, int count)
        {
            return (b >> offset) & ((1 << count) - 1);
        }

        /// <summary>
        /// Reads ARGB values from the 16 bits of two given Bytes in a 1555 format.
        /// </summary>
        /// <param name="one">The first Byte.</param>
        /// <param name="two">The Second Byte.</param>
        /// <returns>A System.Drawing.Color with a ARGB values read from the two given Bytes</returns>
        /// <remarks>
        /// Gets the ARGB values from the 16 bits in the two bytes based on the below diagram
        /// |   BYTE 1   |  BYTE 2   |
        /// | A RRRRR GG | GGG BBBBB |
        /// </remarks>
        internal static Color GetColorFrom2Bytes(byte one, byte two)
        {
            // get the 5 bits used for the RED value from the first byte
            int r1 = Utilities.GetBits(one, 2, 5);
            int r = r1 << 3;

            // get the two high order bits for GREEN from the from the first byte
            int bit = Utilities.GetBits(one, 0, 2);
            // shift bits to the high order
            int g1 = bit << 6;

            // get the 3 low order bits for GREEN from the from the second byte
            bit = Utilities.GetBits(two, 5, 3);
            // shift the low order bits
            int g2 = bit << 3;
            // add the shifted values together to get the full GREEN value
            int g = g1 + g2;

            // get the 5 bits used for the BLUE value from the second byte
            int b1 = Utilities.GetBits(two, 0, 5);
            int b = b1 << 3;

            // get the 1 bit used for the ALPHA value from the first byte
            int a1 = Utilities.GetBits(one, 7, 1);
            int a = a1 * 255;

            // return the resulting Color
            return Color.FromArgb(a, r, g, b);
        }

        /// <summary>
        /// Gets a 32 character binary string of the specified Int32 value.
        /// </summary>
        /// <param name="n">The value to get a binary string for.</param>
        /// <returns>A string with the resulting binary for the supplied value.</returns>
        /// <remarks>
        /// This method was used during debugging and is left here just for fun.
        /// </remarks>
        internal static string GetIntBinaryString(Int32 n)
        {
            char[] b = new char[32];
            int pos = 31;
            int i = 0;

            while (i < 32)
            {
                if ((n & (1 << i)) != 0)
                {
                    b[pos] = '1';
                }
                else
                {
                    b[pos] = '0';
                }
                pos--;
                i++;
            }
            return new string(b);
        }

        /// <summary>
        /// Gets a 16 character binary string of the specified Int16 value.
        /// </summary>
        /// <param name="n">The value to get a binary string for.</param>
        /// <returns>A string with the resulting binary for the supplied value.</returns>
        /// <remarks>
        /// This method was used during debugging and is left here just for fun.
        /// </remarks>
        internal static string GetInt16BinaryString(Int16 n)
        {
            char[] b = new char[16];
            int pos = 15;
            int i = 0;

            while (i < 16)
            {
                if ((n & (1 << i)) != 0)
                {
                    b[pos] = '1';
                }
                else
                {
                    b[pos] = '0';
                }
                pos--;
                i++;
            }
            return new string(b);
        }

    }
}
