// ========================================================== // 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"; } /// /// The Targa format of the file. /// public enum TGAFormat { /// /// Unknown Targa Image format. /// UNKNOWN = 0, /// /// Original Targa Image format. /// /// Targa Image does not have a Signature of ""TRUEVISION-XFILE"". ORIGINAL_TGA = 100, /// /// New Targa Image format /// /// Targa Image has a TargaFooter with a Signature of ""TRUEVISION-XFILE"". NEW_TGA = 200 } /// /// Indicates the type of color map, if any, included with the image file. /// public enum ColorMapType : byte { /// /// No color map was included in the file. /// NO_COLOR_MAP = 0, /// /// Color map was included in the file. /// COLOR_MAP_INCLUDED = 1 } /// /// The type of image read from the file. /// public enum ImageType : byte { /// /// No image data was found in file. /// NO_IMAGE_DATA = 0, /// /// Image is an uncompressed, indexed color-mapped image. /// UNCOMPRESSED_COLOR_MAPPED = 1, /// /// Image is an uncompressed, RGB image. /// UNCOMPRESSED_TRUE_COLOR = 2, /// /// Image is an uncompressed, Greyscale image. /// UNCOMPRESSED_BLACK_AND_WHITE = 3, /// /// Image is a compressed, indexed color-mapped image. /// RUN_LENGTH_ENCODED_COLOR_MAPPED = 9, /// /// Image is a compressed, RGB image. /// RUN_LENGTH_ENCODED_TRUE_COLOR = 10, /// /// Image is a compressed, Greyscale image. /// RUN_LENGTH_ENCODED_BLACK_AND_WHITE = 11 } /// /// The top-to-bottom ordering in which pixel data is transferred from the file to the screen. /// public enum VerticalTransferOrder { /// /// Unknown transfer order. /// UNKNOWN = -1, /// /// Transfer order of pixels is from the bottom to top. /// BOTTOM = 0, /// /// Transfer order of pixels is from the top to bottom. /// TOP = 1 } /// /// The left-to-right ordering in which pixel data is transferred from the file to the screen. /// public enum HorizontalTransferOrder { /// /// Unknown transfer order. /// UNKNOWN = -1, /// /// Transfer order of pixels is from the right to left. /// RIGHT = 0, /// /// Transfer order of pixels is from the left to right. /// LEFT = 1 } /// /// Screen destination of first pixel based on the VerticalTransferOrder and HorizontalTransferOrder. /// public enum FirstPixelDestination { /// /// Unknown first pixel destination. /// UNKNOWN = 0, /// /// First pixel destination is the top-left corner of the image. /// TOP_LEFT = 1, /// /// First pixel destination is the top-right corner of the image. /// TOP_RIGHT = 2, /// /// First pixel destination is the bottom-left corner of the image. /// BOTTOM_LEFT = 3, /// /// First pixel destination is the bottom-right corner of the image. /// BOTTOM_RIGHT = 4 } /// /// The RLE packet type used in a RLE compressed image. /// public enum RLEPacketType { /// /// A raw RLE packet type. /// RAW = 0, /// /// A run-length RLE packet type. /// RUN_LENGTH = 1 } /// /// Reads and loads a Truevision TGA Format image file. /// 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; /// /// Creates a new instance of the TargaImage object. /// public TargaImage() { this.objTargaFooter = new TargaFooter(); this.objTargaHeader = new TargaHeader(); this.objTargaExtensionArea = new TargaExtensionArea(); this.bmpTargaImage = null; this.bmpImageThumbnail = null; } /// /// Gets a TargaHeader object that holds the Targa Header information of the loaded file. /// public TargaHeader Header { get { return this.objTargaHeader; } } /// /// Gets a TargaExtensionArea object that holds the Targa Extension Area information of the loaded file. /// public TargaExtensionArea ExtensionArea { get { return this.objTargaExtensionArea; } } /// /// Gets a TargaExtensionArea object that holds the Targa Footer information of the loaded file. /// public TargaFooter Footer { get { return this.objTargaFooter; } } /// /// Gets the Targa format of the loaded file. /// public TGAFormat Format { get { return this.eTGAFormat; } } /// /// Gets a Bitmap representation of the loaded file. /// public Bitmap Image { get { return this.bmpTargaImage; } } /// /// Gets the thumbnail of the loaded file if there is one in the file. /// public Bitmap Thumbnail { get { return this.bmpImageThumbnail; } } /// /// Gets the full path and filename of the loaded file. /// public string FileName { get { return this.strFileName; } } /// /// Gets the byte offset between the beginning of one scan line and the next. Used when loading the image into the Image Bitmap. /// /// /// 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. /// public int Stride { get { return this.intStride; } } /// /// 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. /// /// /// 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. /// 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. /// /// TargaImage deconstructor. /// ~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); } } /// /// Creates a new instance of the TargaImage object with strFileName as the image loaded. /// 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'."); } /// /// Creates a new instance of the TargaImage object loading the image data from the provided stream. /// 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"); } /// /// Loads the Targa Footer information from the file. /// /// A BinaryReader that points the loaded file byte stream. 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."); } } /// /// Loads the Targa Header information from the file. /// /// A BinaryReader that points the loaded file byte stream. 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; } /// /// Loads the Targa Extension Area from the file, if it exists. /// /// A BinaryReader that points the loaded file byte stream. 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."); } } /// /// Reads the image data bytes from the file. Handles Uncompressed and RLE Compressed image data. /// Uses FirstPixelDestination to properly align the image. /// /// A BinaryReader that points the loaded file byte stream. /// An array of bytes representing the image data in the proper alignment. 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> rows = null; System.Collections.Generic.List row = null; rows = new System.Collections.Generic.List>(); row = new System.Collections.Generic.List(); // 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(); 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(); 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(); } #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 /// 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. /// /// A BinaryReader that points the loaded file byte stream. 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; } } } /// /// Gets the PixelFormat to be used by the Image based on the Targa file's attributes /// /// 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; } /// /// Loads the thumbnail of the loaded image file, if any. /// /// A BinaryReader that points the loaded file byte stream. /// A PixelFormat value indicating what pixel format to use when loading the thumbnail. 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> rows = new System.Collections.Generic.List>(); System.Collections.Generic.List row = new System.Collections.Generic.List(); 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(); } 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; } } } /// /// Clears out all objects and resources. /// 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; } /// /// Loads a Targa image file into a Bitmap object. /// /// The Targa image filename /// A Bitmap object with the Targa image loaded into it. public static Bitmap LoadTargaImage(string sFileName) { using (TargaImage ti = new TargaImage(sFileName)) { return CopyToBitmap(ti); } } /// /// Loads a stream with Targa image data into a Bitmap object. /// /// The Targa image stream /// A Bitmap object with the Targa image loaded into it. 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 /// /// Disposes all resources used by this instance of the TargaImage class. /// 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); } /// /// 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. /// /// If true dispose all resources, else dispose only release unmanaged resources. 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 } /// /// 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. /// 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 cColorMap = new List(); /// /// 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. /// public byte ImageIDLength { get { return this.bImageIDLength; } } /// /// Sets the ImageIDLength property, available only to objects in the same assembly as TargaHeader. /// /// The Image ID Length value read from the file. internal protected void SetImageIDLength(byte bImageIDLength) { this.bImageIDLength = bImageIDLength; } /// /// 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. /// public ColorMapType ColorMapType { get { return this.eColorMapType; } } /// /// Sets the ColorMapType property, available only to objects in the same assembly as TargaHeader. /// /// One of the ColorMapType enumeration values. internal protected void SetColorMapType(ColorMapType eColorMapType) { this.eColorMapType = eColorMapType; } /// /// Gets one of the ImageType enumeration values indicating the type of Targa image read from the file. /// public ImageType ImageType { get { return this.eImageType; } } /// /// Sets the ImageType property, available only to objects in the same assembly as TargaHeader. /// /// One of the ImageType enumeration values. internal protected void SetImageType(ImageType eImageType) { this.eImageType = eImageType; } /// /// Gets the index of the first color map entry. ColorMapFirstEntryIndex refers to the starting entry in loading the color map. /// public short ColorMapFirstEntryIndex { get { return this.sColorMapFirstEntryIndex; } } /// /// Sets the ColorMapFirstEntryIndex property, available only to objects in the same assembly as TargaHeader. /// /// The First Entry Index value read from the file. internal protected void SetColorMapFirstEntryIndex(short sColorMapFirstEntryIndex) { this.sColorMapFirstEntryIndex = sColorMapFirstEntryIndex; } /// /// Gets total number of color map entries included. /// public short ColorMapLength { get { return this.sColorMapLength; } } /// /// Sets the ColorMapLength property, available only to objects in the same assembly as TargaHeader. /// /// The Color Map Length value read from the file. internal protected void SetColorMapLength(short sColorMapLength) { this.sColorMapLength = sColorMapLength; } /// /// Gets the number of bits per entry in the Color Map. Typically 15, 16, 24 or 32-bit values are used. /// public byte ColorMapEntrySize { get { return this.bColorMapEntrySize; } } /// /// Sets the ColorMapEntrySize property, available only to objects in the same assembly as TargaHeader. /// /// The Color Map Entry Size value read from the file. internal protected void SetColorMapEntrySize(byte bColorMapEntrySize) { this.bColorMapEntrySize = bColorMapEntrySize; } /// /// 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). /// public short XOrigin { get { return this.sXOrigin; } } /// /// Sets the XOrigin property, available only to objects in the same assembly as TargaHeader. /// /// The X Origin value read from the file. internal protected void SetXOrigin(short sXOrigin) { this.sXOrigin = sXOrigin; } /// /// 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). /// public short YOrigin { get { return this.sYOrigin; } } /// /// Sets the YOrigin property, available only to objects in the same assembly as TargaHeader. /// /// The Y Origin value read from the file. internal protected void SetYOrigin(short sYOrigin) { this.sYOrigin = sYOrigin; } /// /// Gets the width of the image in pixels. /// public short Width { get { return this.sWidth; } } /// /// Sets the Width property, available only to objects in the same assembly as TargaHeader. /// /// The Width value read from the file. internal protected void SetWidth(short sWidth) { this.sWidth = sWidth; } /// /// Gets the height of the image in pixels. /// public short Height { get { return this.sHeight; } } /// /// Sets the Height property, available only to objects in the same assembly as TargaHeader. /// /// The Height value read from the file. internal protected void SetHeight(short sHeight) { this.sHeight = sHeight; } /// /// Gets the number of bits per pixel. This number includes /// the Attribute or Alpha channel bits. Common values are 8, 16, 24 and 32. /// public byte PixelDepth { get { return this.bPixelDepth; } } /// /// Sets the PixelDepth property, available only to objects in the same assembly as TargaHeader. /// /// The Pixel Depth value read from the file. internal protected void SetPixelDepth(byte bPixelDepth) { this.bPixelDepth = bPixelDepth; } /// /// 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. /// internal protected byte ImageDescriptor { get { return this.bImageDescriptor; } set { this.bImageDescriptor = value; } } /// /// Gets one of the FirstPixelDestination enumeration values specifying the screen destination of first pixel based on VerticalTransferOrder and HorizontalTransferOrder /// 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; } } /// /// 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. /// public VerticalTransferOrder VerticalTransferOrder { get { return this.eVerticalTransferOrder; } } /// /// Sets the VerticalTransferOrder property, available only to objects in the same assembly as TargaHeader. /// /// One of the VerticalTransferOrder enumeration values. internal protected void SetVerticalTransferOrder(VerticalTransferOrder eVerticalTransferOrder) { this.eVerticalTransferOrder = eVerticalTransferOrder; } /// /// 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. /// public HorizontalTransferOrder HorizontalTransferOrder { get { return this.eHorizontalTransferOrder; } } /// /// Sets the HorizontalTransferOrder property, available only to objects in the same assembly as TargaHeader. /// /// One of the HorizontalTransferOrder enumeration values. internal protected void SetHorizontalTransferOrder(HorizontalTransferOrder eHorizontalTransferOrder) { this.eHorizontalTransferOrder = eHorizontalTransferOrder; } /// /// Gets the number of attribute bits per pixel. /// public byte AttributeBits { get { return this.bAttributeBits; } } /// /// Sets the AttributeBits property, available only to objects in the same assembly as TargaHeader. /// /// The Attribute Bits value read from the file. internal protected void SetAttributeBits(byte bAttributeBits) { this.bAttributeBits = bAttributeBits; } /// /// Gets identifying information about the image. /// A value of zero in ImageIDLength indicates that no ImageIDValue is included with the image. /// public string ImageIDValue { get { return this.strImageIDValue; } } /// /// Sets the ImageIDValue property, available only to objects in the same assembly as TargaHeader. /// /// The Image ID value read from the file. internal protected void SetImageIDValue(string strImageIDValue) { this.strImageIDValue = strImageIDValue; } /// /// Gets the Color Map of the image, if any. The Color Map is represented by a list of System.Drawing.Color objects. /// public System.Collections.Generic.List ColorMap { get { return this.cColorMap; } } /// /// Gets the offset from the beginning of the file to the Image Data. /// 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; } } /// /// Gets the number of bytes per pixel. /// public int BytesPerPixel { get { return (int)this.bPixelDepth / 8; } } } /// /// Holds Footer infomation read from the image file. /// public class TargaFooter { private int intExtensionAreaOffset = 0; private int intDeveloperDirectoryOffset = 0; private string strSignature = string.Empty; private string strReservedCharacter = string.Empty; /// /// 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. /// public int ExtensionAreaOffset { get { return this.intExtensionAreaOffset; } } /// /// Sets the ExtensionAreaOffset property, available only to objects in the same assembly as TargaFooter. /// /// The Extension Area Offset value read from the file. internal protected void SetExtensionAreaOffset(int intExtensionAreaOffset) { this.intExtensionAreaOffset = intExtensionAreaOffset; } /// /// 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 /// public int DeveloperDirectoryOffset { get { return this.intDeveloperDirectoryOffset; } } /// /// Sets the DeveloperDirectoryOffset property, available only to objects in the same assembly as TargaFooter. /// /// The Developer Directory Offset value read from the file. internal protected void SetDeveloperDirectoryOffset(int intDeveloperDirectoryOffset) { this.intDeveloperDirectoryOffset = intDeveloperDirectoryOffset; } /// /// 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. /// public string Signature { get { return this.strSignature; } } /// /// Sets the Signature property, available only to objects in the same assembly as TargaFooter. /// /// The Signature value read from the file. internal protected void SetSignature(string strSignature) { this.strSignature = strSignature; } /// /// A New Targa format reserved character "." (period) /// public string ReservedCharacter { get { return this.strReservedCharacter; } } /// /// Sets the ReservedCharacter property, available only to objects in the same assembly as TargaFooter. /// /// The ReservedCharacter value read from the file. internal protected void SetReservedCharacter(string strReservedCharacter) { this.strReservedCharacter = strReservedCharacter; } /// /// Creates a new instance of the TargaFooter class. /// public TargaFooter() {} } /// /// This class holds all of the Extension Area properties of the Targa image. If an Extension Area exists in the file. /// 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 intScanLineTable = new List(); private System.Collections.Generic.List cColorCorrectionTable = new List(); /// /// 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 /// public int ExtensionSize { get { return this.intExtensionSize; } } /// /// Sets the ExtensionSize property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Extension Size value read from the file. internal protected void SetExtensionSize(int intExtensionSize) { this.intExtensionSize = intExtensionSize; } /// /// Gets the name of the person who created the image. /// public string AuthorName { get { return this.strAuthorName; } } /// /// Sets the AuthorName property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Author Name value read from the file. internal protected void SetAuthorName(string strAuthorName) { this.strAuthorName = strAuthorName; } /// /// Gets the comments from the author who created the image. /// public string AuthorComments { get { return this.strAuthorComments; } } /// /// Sets the AuthorComments property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Author Comments value read from the file. internal protected void SetAuthorComments(string strAuthorComments) { this.strAuthorComments = strAuthorComments; } /// /// Gets the date and time that the image was saved. /// public DateTime DateTimeStamp { get { return this.dtDateTimeStamp; } } /// /// Sets the DateTimeStamp property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Date Time Stamp value read from the file. internal protected void SetDateTimeStamp(DateTime dtDateTimeStamp) { this.dtDateTimeStamp = dtDateTimeStamp; } /// /// Gets the name or id tag which refers to the job with which the image was associated. /// public string JobName { get { return this.strJobName; } } /// /// Sets the JobName property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Job Name value read from the file. internal protected void SetJobName(string strJobName) { this.strJobName = strJobName; } /// /// Gets the job elapsed time when the image was saved. /// public TimeSpan JobTime { get { return this.dtJobTime; } } /// /// Sets the JobTime property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Job Time value read from the file. internal protected void SetJobTime(TimeSpan dtJobTime) { this.dtJobTime = dtJobTime; } /// /// Gets the Software ID. Usually used to determine and record with what program a particular image was created. /// public string SoftwareID { get { return this.strSoftwareID; } } /// /// Sets the SoftwareID property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Software ID value read from the file. internal protected void SetSoftwareID(string strSoftwareID) { this.strSoftwareID = strSoftwareID; } /// /// Gets the version of software defined by the SoftwareID. /// public string SoftwareVersion { get { return this.strSoftwareVersion; } } /// /// Sets the SoftwareVersion property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Software Version value read from the file. internal protected void SetSoftwareVersion(string strSoftwareVersion) { this.strSoftwareVersion = strSoftwareVersion; } /// /// 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". /// public Color KeyColor { get { return this.cKeyColor; } } /// /// Sets the KeyColor property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Key Color value read from the file. internal protected void SetKeyColor(Color cKeyColor) { this.cKeyColor = cKeyColor; } /// /// Gets the Pixel Ratio Numerator. /// public int PixelAspectRatioNumerator { get { return this.intPixelAspectRatioNumerator; } } /// /// Sets the PixelAspectRatioNumerator property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Pixel Aspect Ratio Numerator value read from the file. internal protected void SetPixelAspectRatioNumerator(int intPixelAspectRatioNumerator) { this.intPixelAspectRatioNumerator = intPixelAspectRatioNumerator; } /// /// Gets the Pixel Ratio Denominator. /// public int PixelAspectRatioDenominator { get { return this.intPixelAspectRatioDenominator; } } /// /// Sets the PixelAspectRatioDenominator property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Pixel Aspect Ratio Denominator value read from the file. internal protected void SetPixelAspectRatioDenominator(int intPixelAspectRatioDenominator) { this.intPixelAspectRatioDenominator = intPixelAspectRatioDenominator; } /// /// Gets the Pixel Aspect Ratio. /// public float PixelAspectRatio { get { if (this.intPixelAspectRatioDenominator > 0) { return (float)this.intPixelAspectRatioNumerator / (float)this.intPixelAspectRatioDenominator; } else return 0.0F; } } /// /// Gets the Gamma Numerator. /// public int GammaNumerator { get { return this.intGammaNumerator; } } /// /// Sets the GammaNumerator property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Gamma Numerator value read from the file. internal protected void SetGammaNumerator(int intGammaNumerator) { this.intGammaNumerator = intGammaNumerator; } /// /// Gets the Gamma Denominator. /// public int GammaDenominator { get { return this.intGammaDenominator; } } /// /// Sets the GammaDenominator property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Gamma Denominator value read from the file. internal protected void SetGammaDenominator(int intGammaDenominator) { this.intGammaDenominator = intGammaDenominator; } /// /// Gets the Gamma Ratio. /// 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; } } /// /// Gets the offset from the beginning of the file to the start of the Color Correction table. /// public int ColorCorrectionOffset { get { return this.intColorCorrectionOffset; } } /// /// Sets the ColorCorrectionOffset property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Color Correction Offset value read from the file. internal protected void SetColorCorrectionOffset(int intColorCorrectionOffset) { this.intColorCorrectionOffset = intColorCorrectionOffset; } /// /// Gets the offset from the beginning of the file to the start of the Postage Stamp image data. /// public int PostageStampOffset { get { return this.intPostageStampOffset; } } /// /// Sets the PostageStampOffset property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Postage Stamp Offset value read from the file. internal protected void SetPostageStampOffset(int intPostageStampOffset) { this.intPostageStampOffset = intPostageStampOffset; } /// /// Gets the offset from the beginning of the file to the start of the Scan Line table. /// public int ScanLineOffset { get { return this.intScanLineOffset; } } /// /// Sets the ScanLineOffset property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Scan Line Offset value read from the file. internal protected void SetScanLineOffset(int intScanLineOffset) { this.intScanLineOffset = intScanLineOffset; } /// /// 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 /// public int AttributesType { get { return this.intAttributesType; } } /// /// Sets the AttributesType property, available only to objects in the same assembly as TargaExtensionArea. /// /// The Attributes Type value read from the file. internal protected void SetAttributesType(int intAttributesType) { this.intAttributesType = intAttributesType; } /// /// 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 /// public System.Collections.Generic.List ScanLineTable { get { return this.intScanLineTable; } } /// /// 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. /// public System.Collections.Generic.List ColorCorrectionTable { get { return this.cColorCorrectionTable; } } } /// /// Utilities functions used by the TargaImage class. /// static class Utilities { /// /// Gets an int value representing the subset of bits from a single Byte. /// /// The Byte used to get the subset of bits from. /// The offset of bits starting from the right. /// The number of bits to read. /// /// An int value representing the subset of bits. /// /// /// 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) /// internal static int GetBits(byte b, int offset, int count) { return (b >> offset) & ((1 << count) - 1); } /// /// Reads ARGB values from the 16 bits of two given Bytes in a 1555 format. /// /// The first Byte. /// The Second Byte. /// A System.Drawing.Color with a ARGB values read from the two given Bytes /// /// 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 | /// 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); } /// /// Gets a 32 character binary string of the specified Int32 value. /// /// The value to get a binary string for. /// A string with the resulting binary for the supplied value. /// /// This method was used during debugging and is left here just for fun. /// 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); } /// /// Gets a 16 character binary string of the specified Int16 value. /// /// The value to get a binary string for. /// A string with the resulting binary for the supplied value. /// /// This method was used during debugging and is left here just for fun. /// 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); } } }