context: //
  //  TrueVisionTGADataFormat.cs - implements a DataFormat for manipulating images in TrueVision Targa (TGA) format
  //
  //  Author:
  //       Michael Becker <alcexhim@gmail.com>
  //
  //  Copyright (c) 2011-2020 Mike Becker's Software
  //
  //  This program is free software: you can redistribute it and/or modify
  //  it under the terms of the GNU General Public License as published by
  //  the Free Software Foundation, either version 3 of the License, or
  //  (at your option) any later version.
  //
  //  This program is distributed in the hope that it will be useful,
  //  but WITHOUT ANY WARRANTY; without even the implied warranty of
  //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  //  GNU General Public License for more details.
  //
  //  You should have received a copy of the GNU General Public License
  //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  
  using System;
  using System.Collections.Generic;
  
  using MBS.Framework.Drawing;
  using MBS.Framework.Settings;
  using UniversalEditor.IO;
  using UniversalEditor.ObjectModels.Multimedia.Picture;
  
  namespace UniversalEditor.DataFormats.Multimedia.Picture.Targa
  {
  	/// <summary>
  	/// Implements a <see cref="DataFormat" /> for manipulating images in TrueVision Targa (TGA) format.
  	/// </summary>
  	public class TrueVisionTGADataFormat : DataFormat
  	{
  		private const int HEADER_BYTE_LENGTH = 18;
  		private const int FOOTER_BYTE_LENGTH = 26;
  
  		private const int EXTENSION_AREA_AUTHOR_NAME_LENGTH = 41;
  		private const int EXTENSION_AREA_AUTHOR_COMMENTS_LENGTH = 324;
  		private const int EXTENSION_AREA_JOB_NAME_LENGTH = 41;
  		private const int EXTENSION_AREA_SOFTWARE_ID_LENGTH = 41;
  		private const int EXTENSION_AREA_SOFTWARE_VERSION_LETTER_LENGTH = 1;
  		private const int EXTENSION_AREA_COLOR_CORRECTION_TABLE_VALUE_LENGTH = 41;
  
  		public TargaExtensionArea ExtensionArea { get; } = new TargaExtensionArea();
  		public TargaFirstPixelDestination PixelOrigin { get; } = TargaFirstPixelDestination.TopLeft;
  		public TargaFormatVersion FormatVersion { get; set; } = TargaFormatVersion.Original;
  
  		private static DataFormatReference _dfr = null;
  		protected override DataFormatReference MakeReferenceInternal()
  		{
  			if (_dfr == null)
  			{
  				_dfr = base.MakeReferenceInternal();
  				_dfr.Capabilities.Add(typeof(PictureObjectModel), DataFormatCapabilities.All);
  				_dfr.ExportOptions.SettingsGroups[0].Settings.Add(new ChoiceSetting(nameof(ImageType), "Image _type", TargaImageType.UncompressedTrueColor, new ChoiceSetting.ChoiceSettingValue[]
  				{
  					new ChoiceSetting.ChoiceSettingValue("CompressedGrayscale", "Compressed grayscale", TargaImageType.CompressedGrayscale),
  					new ChoiceSetting.ChoiceSettingValue("CompressedIndexed", "Compressed indexed", TargaImageType.CompressedIndexed),
  					new ChoiceSetting.ChoiceSettingValue("CompressedTrueColor", "Compressed true color", TargaImageType.CompressedTrueColor),
  					new ChoiceSetting.ChoiceSettingValue("UncompressedGrayscale", "Uncompressed grayscale", TargaImageType.UncompressedGrayscale),
  					new ChoiceSetting.ChoiceSettingValue("UncompressedIndexed", "Uncompressed indexed", TargaImageType.UncompressedIndexed),
  					new ChoiceSetting.ChoiceSettingValue("UncompressedTrueColor", "Uncompressed true color", TargaImageType.UncompressedTrueColor)
  				}));
  				_dfr.ExportOptions.SettingsGroups[0].Settings.Add(new ChoiceSetting(nameof(PixelDepth), "Pixel _depth", (byte)32, new ChoiceSetting.ChoiceSettingValue[]
  				{
  					new ChoiceSetting.ChoiceSettingValue("8bpp", "8bpp", (byte)8),
  					new ChoiceSetting.ChoiceSettingValue("16bpp", "16bpp", (byte)16),
  					new ChoiceSetting.ChoiceSettingValue("24bpp", "24bpp", (byte)24),
  					new ChoiceSetting.ChoiceSettingValue("32bpp", "32bpp", (byte)32)
  				}));
  				_dfr.ExportOptions.SettingsGroups[0].Settings.Add(new ChoiceSetting(nameof(PixelOrigin), "Pixel o_rigin", TargaFirstPixelDestination.TopLeft, new ChoiceSetting.ChoiceSettingValue[]
  				{
  					new ChoiceSetting.ChoiceSettingValue("BottomLeft", "Bottom-left", TargaFirstPixelDestination.BottomLeft),
  					new ChoiceSetting.ChoiceSettingValue("BottomRight", "Bottom-right", TargaFirstPixelDestination.BottomRight),
  					new ChoiceSetting.ChoiceSettingValue("TopLeft", "Top-left", TargaFirstPixelDestination.TopLeft),
  					new ChoiceSetting.ChoiceSettingValue("TopRightwa78qa67Y", "Top-right", TargaFirstPixelDestination.TopRight)
  				}));
  				_dfr.ExportOptions.SettingsGroups[0].Settings.Add(new ChoiceSetting(nameof(FormatVersion), "Format _version", TargaFormatVersion.Original, new ChoiceSetting.ChoiceSettingValue[]
  				{
  					new ChoiceSetting.ChoiceSettingValue("Original", "Original (100)", TargaFormatVersion.Original),
  					new ChoiceSetting.ChoiceSettingValue("TrueVisionXFile", "TrueVision-XFile (200)", TargaFormatVersion.TrueVisionXFile)
  				}));
  				_dfr.ContentTypes.Add("image/x-targa");
  				_dfr.ContentTypes.Add("image/x-tga");
  			}
  			return _dfr;
  		}
  
  		private int GetImageDataOffset(int colorMapLength, byte imageIDLength, byte colorMapEntrySize)
  		{
  			// calculate the image data offset
  
  			// start off with the number of bytes holding the header info.
  			int intImageDataOffset = HEADER_BYTE_LENGTH;
  
  			// add the Image ID length (could be variable)
  			intImageDataOffset += imageIDLength;
  
  			// determine the number of bytes for each Color Map entry
  			int Bytes = 0;
  			switch (colorMapEntrySize)
  			{
  				case 15:
  				case 16:
  				{
  					Bytes = 2;
  					break;
  				}
  				case 24:
  				{
  					Bytes = 3;
  					break;
  				}
  				case 32:
  				{
  					Bytes = 4;
  					break;
  				}
  			}
  
  			// add the length of the color map
  			intImageDataOffset += ((int)colorMapLength * (int)Bytes);
  
  			// return result
  			return intImageDataOffset;
  		}
  		public byte PixelDepth { get; set; } = 0;
  
  
  		public int BytesPerPixel
  		{
  			get { return (int)(PixelDepth / 8); }
  			set
  			{
  				if ((value * 8) > 255)
  				{
  					throw new ArgumentOutOfRangeException("Bytes per pixel exceeds maximum allowed bits per pixel depth of 255");
  				}
  				PixelDepth = (byte)(value * 8);
  			}
  		}
  
  		public TargaImageType ImageType { get; set; } = TargaImageType.None;
  		public string ImageID { get; set; } = null;
  
  		protected override void LoadInternal(ref ObjectModel objectModel)
  		{
  			IO.Reader br = base.Accessor.Reader;
  			PictureObjectModel pic = (objectModel as PictureObjectModel);
  
  			short imageWidth = 0;
  			short imageHeight = 0;
  			int extensionAreaOffset = 0;
  			int developerDirectoryOffset = 0;
  			TargaVerticalTransferOrder verticalTransferOrder = TargaVerticalTransferOrder.Unknown;
  			TargaHorizontalTransferOrder horizontalTransferOrder = TargaHorizontalTransferOrder.Unknown;
  			int imageDataOffset = 0;
  
  			if (br.Accessor.Length < 18) throw new InvalidDataFormatException("File must be at least 18 bytes");
  
  			#region Targa Footer
  			{
  				// set the cursor at the beginning of the signature string.
  				br.Accessor.Seek((-1 * HEADER_BYTE_LENGTH), SeekOrigin.End);
  
  				// read the signature bytes and convert to ASCII string
  				string Signature = br.ReadNullTerminatedString(16);
  
  				// do we have a proper signature
  				if (Signature == "TRUEVISION-XFILE")
  				{
  					// this is a NEW targa file.
  					FormatVersion = TargaFormatVersion.TrueVisionXFile;
  
  					// set cursor to beginning of footer info
  					br.Accessor.Seek((-1 * FOOTER_BYTE_LENGTH), SeekOrigin.End);
  
  					// read the Extension Area Offset value
  					extensionAreaOffset = br.ReadInt32();
  
  					// read the Developer Directory Offset value
  					developerDirectoryOffset = br.ReadInt32();
  
  					// skip the signature we have already read it.
  					string Signature2 = br.ReadNullTerminatedString(16);
  
  					// 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.
  					FormatVersion = TargaFormatVersion.Original;
  				}
  			}
  			#endregion
  			#region Targa Header
  			{
  				// set the cursor at the beginning of the file.
  				br.Accessor.Seek(0, SeekOrigin.Begin);
  
  				// read the header properties from the file
  				byte imageIDLength = br.ReadByte();
  				bool colorMapEnabled = br.ReadBoolean();
  				ImageType = (TargaImageType)br.ReadByte();
  
  				short colorMapFirstEntryIndex = br.ReadInt16();
  				short colorMapLength = br.ReadInt16();
  				byte colorMapEntrySize = br.ReadByte();
  
  				short originX = br.ReadInt16();
  				short originY = br.ReadInt16();
  				imageWidth = br.ReadInt16();
  				imageHeight = br.ReadInt16();
  
  				pic.Width = imageWidth;
  				pic.Height = imageHeight;
  
  				PixelDepth = br.ReadByte();
  				switch (PixelDepth)
  				{
  					case 8:
  					case 16:
  					case 24:
  					case 32:
  					{
  						break;
  					}
  					default:
  					{
  						throw new InvalidOperationException("Targa image file format only supports 8, 16, 24, or 32 bit pixel depths");
  					}
  				}
  
  
  				byte ImageDescriptor = br.ReadByte();
  				int attributeBits = ImageDescriptor.GetBits(0, 4);
  
  				verticalTransferOrder = (TargaVerticalTransferOrder)ImageDescriptor.GetBits(5, 1);
  				horizontalTransferOrder = (TargaHorizontalTransferOrder)ImageDescriptor.GetBits(4, 1);
  
  				// load ImageID value if any
  				if (imageIDLength > 0)
  				{
  					ImageID = br.ReadNullTerminatedString(imageIDLength);
  				}
  
  				imageDataOffset = GetImageDataOffset(colorMapLength, imageIDLength, colorMapEntrySize);
  
  				#region Load Colormap
  				{
  					// 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 (colorMapEnabled)
  					{
  						if (ImageType == TargaImageType.UncompressedIndexed || ImageType == TargaImageType.CompressedIndexed)
  						{
  							if (colorMapLength > 0)
  							{
  								for (int i = 0; i < colorMapLength; i++)
  								{
  									pic.ColorMap.Add(DecodeColor(br, colorMapEntrySize));
  								}
  							}
  							else
  							{
  								throw new InvalidOperationException("Image Type requires a Color Map and Color Map Length is zero.");
  							}
  						}
  					}
  					else
  					{
  						if (ImageType == TargaImageType.UncompressedIndexed || ImageType == TargaImageType.CompressedIndexed)
  						{
  							throw new InvalidOperationException("Indexed image type requires a colormap and there was not a colormap included in the file.");
  						}
  					}
  				}
  				#endregion
  			}
  			#endregion
  			#region Targa extension area
  			{
  				// is there an Extension Area in file
  				if (!br.EndOfStream && (extensionAreaOffset > 0))
  				{
  					ExtensionArea.Enabled = true;
  
  					// set the cursor at the beginning of the Extension Area using ExtensionAreaOffset.
  					br.Accessor.Seek(extensionAreaOffset, SeekOrigin.Begin);
  
  					// load the extension area fields from the file
  
  					short extensionAreaSize = br.ReadInt16();
  					string authorName = br.ReadNullTerminatedString(EXTENSION_AREA_AUTHOR_NAME_LENGTH);
  					string authorComments = br.ReadNullTerminatedString(EXTENSION_AREA_AUTHOR_COMMENTS_LENGTH);
  
  
  					// get the date/time stamp of the file
  					short iMonth = br.ReadInt16();
  					short iDay = br.ReadInt16();
  					short iYear = br.ReadInt16();
  					short iHour = br.ReadInt16();
  					short iMinute = br.ReadInt16();
  					short iSecond = br.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)
  					{
  						ExtensionArea.DateCreated = dtstamp;
  					}
  
  
  					string JobName = br.ReadNullTerminatedString(EXTENSION_AREA_JOB_NAME_LENGTH);
  
  
  					// get the job time of the file
  					iHour = br.ReadInt16();
  					iMinute = br.ReadInt16();
  					iSecond = br.ReadInt16();
  					TimeSpan ts = new TimeSpan((int)iHour, (int)iMinute, (int)iSecond);
  					ExtensionArea.JobTime = ts;
  
  					string softwareID = br.ReadNullTerminatedString(EXTENSION_AREA_SOFTWARE_ID_LENGTH);
  					ExtensionArea.SoftwareID = softwareID;
  
  
  					// get the version number and letter from file
  					short sVersionNumber = br.ReadInt16();
  					float iVersionNumber = (float)sVersionNumber / 100.0F;
  
  					string strVersionLetter = br.ReadNullTerminatedString(EXTENSION_AREA_SOFTWARE_VERSION_LETTER_LENGTH);
  
  					ExtensionArea.VersionString = (iVersionNumber.ToString(@"F2") + strVersionLetter);
  
  
  					// get the color key of the file
  					int a = (int)br.ReadByte();
  					int r = (int)br.ReadByte();
  					int b = (int)br.ReadByte();
  					int g = (int)br.ReadByte();
  					ExtensionArea.ColorKey = (Color.FromRGBAInt32(r, g, b, a));
  
  
  					ExtensionArea.PixelAspectRatioNumerator = (int)br.ReadInt16();
  					ExtensionArea.PixelAspectRatioDenominator = (int)br.ReadInt16();
  					ExtensionArea.GammaNumerator = (int)br.ReadInt16();
  					ExtensionArea.GammaDenominator = (int)br.ReadInt16();
  
  					int extensionAreaColorCorrectionOffset = br.ReadInt32();
  					int extensionAreaPostageStampOffset = br.ReadInt32();
  					int extensionAreaScanLineOffset = br.ReadInt32();
  					ExtensionArea.AttributesType = (int)br.ReadByte();
  
  
  					// load Scan Line Table from file if any
  					if (extensionAreaScanLineOffset > 0)
  					{
  						br.Accessor.Seek(extensionAreaScanLineOffset, SeekOrigin.Begin);
  						for (int i = 0; i < imageHeight; i++)
  						{
  							ExtensionArea.ScanLineTable.Add(br.ReadInt32());
  						}
  					}
  
  
  					// load Color Correction Table from file if any
  					if (extensionAreaColorCorrectionOffset > 0)
  					{
  						br.Accessor.Seek(extensionAreaColorCorrectionOffset, SeekOrigin.Begin);
  						for (int i = 0; i < EXTENSION_AREA_COLOR_CORRECTION_TABLE_VALUE_LENGTH; i++)
  						{
  							a = (int)br.ReadInt16();
  							r = (int)br.ReadInt16();
  							b = (int)br.ReadInt16();
  							g = (int)br.ReadInt16();
  							ExtensionArea.ColorCorrectionTable.Add(Color.FromRGBAInt32(r, g, b, a));
  						}
  					}
  				}
  			}
  			#endregion
  			#region Targa image
  			{
  				//**************  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)
  				int intStride = (((int)imageWidth * (int)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
  				int paddingLength = intStride - ((((int)imageWidth * (int)PixelDepth) + 7) / 8);
  
  				// get the image data bytes
  				byte[] bimagedata = null;
  
  				#region Image Data Bytes
  				{
  
  					// 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;
  
  					// padding bytes
  					byte[] padding = new byte[paddingLength];
  					System.IO.MemoryStream msData = null;
  
  					// seek to the beginning of the image data using the ImageDataOffset value
  					br.Accessor.Seek(imageDataOffset, SeekOrigin.Begin);
  
  
  					// get the size in bytes of each row in the image
  					int intImageRowByteSize = (int)pic.Width * ((int)BytesPerPixel);
  
  					// get the size in bytes of the whole image
  					int intImageByteSize = intImageRowByteSize * (int)pic.Height;
  
  					List<List<byte>> rows = new List<List<byte>>();
  
  					// is this a RLE compressed image type
  					#region COMPRESSED
  					if (ImageType == TargaImageType.CompressedGrayscale || ImageType == TargaImageType.CompressedIndexed || ImageType == TargaImageType.CompressedTrueColor)
  					{
  						// 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;
  
  						List<byte> row = new List<byte>();
  
  						// keep reading until we have the all image bytes
  						while (intImageBytesRead < intImageByteSize)
  						{
  							// get the RLE packet
  							bRLEPacket = br.ReadByte();
  							intRLEPacketType = bRLEPacket.GetBits(7, 1);
  							intRLEPixelCount = bRLEPacket.GetBits(0, 7) + 1;
  
  							// check the RLE packet type
  							if ((TargaRLEPacketType)intRLEPacketType == TargaRLEPacketType.Compressed)
  							{
  								// get the pixel color data
  
gt: 								bRunLengthPixel = br.ReadBytes(BytesPerPixel);