diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs
index c8aa51fa..a667c481 100644
--- a/AssetStudioGUI/AssetStudioGUIForm.cs
+++ b/AssetStudioGUI/AssetStudioGUIForm.cs
@@ -763,10 +763,9 @@ private void PreviewAsset(AssetItem assetItem)
private void PreviewTexture2D(AssetItem assetItem, Texture2D m_Texture2D)
{
- var stream = m_Texture2D.ConvertToStream(ImageFormat.Png, true);
- if (stream != null)
+ var bitmap = m_Texture2D.ConvertToBitmap(true);
+ if (bitmap != null)
{
- var bitmap = new Bitmap(stream);
assetItem.InfoText = $"Width: {m_Texture2D.m_Width}\nHeight: {m_Texture2D.m_Height}\nFormat: {m_Texture2D.m_TextureFormat}";
switch (m_Texture2D.m_TextureSettings.m_FilterMode)
{
@@ -1174,10 +1173,9 @@ private void PreviewMesh(Mesh m_Mesh)
private void PreviewSprite(AssetItem assetItem, Sprite m_Sprite)
{
- var stream = m_Sprite.GetImage(ImageFormat.Png);
- if (stream != null)
+ var bitmap = m_Sprite.GetImage();
+ if (bitmap != null)
{
- var bitmap = new Bitmap(stream);
assetItem.InfoText = $"Width: {bitmap.Width}\nHeight: {bitmap.Height}\n";
PreviewTexture(bitmap);
}
diff --git a/AssetStudioUtility/AssetStudioUtility.csproj b/AssetStudioUtility/AssetStudioUtility.csproj
index 1ef222f2..932e8ea8 100644
--- a/AssetStudioUtility/AssetStudioUtility.csproj
+++ b/AssetStudioUtility/AssetStudioUtility.csproj
@@ -15,7 +15,7 @@
-
+
diff --git a/AssetStudioUtility/ImageExtensions.cs b/AssetStudioUtility/ImageExtensions.cs
index 7306c453..5862511b 100644
--- a/AssetStudioUtility/ImageExtensions.cs
+++ b/AssetStudioUtility/ImageExtensions.cs
@@ -1,38 +1,31 @@
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.Formats.Bmp;
-using SixLabors.ImageSharp.Formats.Tga;
+using System.Drawing;
using System.IO;
+using TGASharpLib;
namespace AssetStudio
{
public static class ImageExtensions
{
- public static MemoryStream ConvertToStream(this Image image, ImageFormat imageFormat)
+ public static MemoryStream ConvertToStream(this Bitmap image, ImageFormat imageFormat)
{
var outputStream = new MemoryStream();
switch (imageFormat)
{
case ImageFormat.Jpeg:
- image.SaveAsJpeg(outputStream);
+ image.Save(outputStream, System.Drawing.Imaging.ImageFormat.Jpeg);
break;
case ImageFormat.Png:
- image.SaveAsPng(outputStream);
+ image.Save(outputStream, System.Drawing.Imaging.ImageFormat.Png);
break;
case ImageFormat.Bmp:
- image.Save(outputStream, new BmpEncoder
- {
- BitsPerPixel = BmpBitsPerPixel.Pixel32,
- SupportTransparency = true
- });
+ image.Save(outputStream, System.Drawing.Imaging.ImageFormat.Bmp);
break;
case ImageFormat.Tga:
- image.Save(outputStream, new TgaEncoder
- {
- BitsPerPixel = TgaBitsPerPixel.Pixel32,
- Compression = TgaCompression.None
- });
+ var tga = new TGA(image);
+ tga.Save(outputStream);
break;
}
+ image.Dispose();
return outputStream;
}
}
diff --git a/AssetStudioUtility/SpriteHelper.cs b/AssetStudioUtility/SpriteHelper.cs
index 98322f48..85ddb3c6 100644
--- a/AssetStudioUtility/SpriteHelper.cs
+++ b/AssetStudioUtility/SpriteHelper.cs
@@ -1,13 +1,10 @@
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.Drawing;
-using SixLabors.ImageSharp.Drawing.Processing;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
using System;
-using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Numerics;
namespace AssetStudio
{
@@ -26,7 +23,7 @@ public static MemoryStream GetImage(this Sprite m_Sprite, ImageFormat imageForma
return null;
}
- public static Image GetImage(this Sprite m_Sprite)
+ public static Bitmap GetImage(this Sprite m_Sprite)
{
if (m_Sprite.m_SpriteAtlas != null && m_Sprite.m_SpriteAtlas.TryGet(out var m_SpriteAtlas))
{
@@ -45,9 +42,9 @@ public static Image GetImage(this Sprite m_Sprite)
return null;
}
- private static Image CutImage(Texture2D m_Texture2D, Sprite m_Sprite, Rectf textureRect, Vector2 textureRectOffset, SpriteSettings settingsRaw)
+ private static Bitmap CutImage(Texture2D m_Texture2D, Sprite m_Sprite, Rectf textureRect, Vector2 textureRectOffset, SpriteSettings settingsRaw)
{
- var originalImage = m_Texture2D.ConvertToImage(false);
+ var originalImage = m_Texture2D.ConvertToBitmap(false);
if (originalImage != null)
{
using (originalImage)
@@ -59,23 +56,29 @@ private static Image CutImage(Texture2D m_Texture2D, Sprite m_Sprite, Rectf text
rectRight = Math.Min(rectRight, m_Texture2D.m_Width);
rectBottom = Math.Min(rectBottom, m_Texture2D.m_Height);
var rect = new Rectangle(rectX, rectY, rectRight - rectX, rectBottom - rectY);
- var spriteImage = originalImage.Clone(x => x.Crop(rect));
+
+ var spriteImage = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb);
+ var destRect = new Rectangle(0, 0, rect.Width, rect.Height);
+ using (var graphic = Graphics.FromImage(spriteImage))
+ {
+ graphic.DrawImage(originalImage, destRect, rect, GraphicsUnit.Pixel);
+ }
if (settingsRaw.packed == 1)
{
//RotateAndFlip
switch (settingsRaw.packingRotation)
{
case SpritePackingRotation.kSPRFlipHorizontal:
- spriteImage.Mutate(x => x.Flip(FlipMode.Horizontal));
+ spriteImage.RotateFlip(RotateFlipType.RotateNoneFlipX);
break;
case SpritePackingRotation.kSPRFlipVertical:
- spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));
+ spriteImage.RotateFlip(RotateFlipType.RotateNoneFlipY);
break;
case SpritePackingRotation.kSPRRotate180:
- spriteImage.Mutate(x => x.Rotate(180));
+ spriteImage.RotateFlip(RotateFlipType.Rotate180FlipNone);
break;
case SpritePackingRotation.kSPRRotate90:
- spriteImage.Mutate(x => x.Rotate(270));
+ spriteImage.RotateFlip(RotateFlipType.Rotate270FlipNone);
break;
}
}
@@ -86,32 +89,41 @@ private static Image CutImage(Texture2D m_Texture2D, Sprite m_Sprite, Rectf text
try
{
var triangles = GetTriangles(m_Sprite.m_RD);
- var polygons = triangles.Select(x => new Polygon(new LinearLineSegment(x.Select(y => new PointF(y.X, y.Y)).ToArray()))).ToArray();
- IPathCollection path = new PathCollection(polygons);
- var matrix = Matrix3x2.CreateScale(m_Sprite.m_PixelsToUnits);
- var version = m_Sprite.version;
- if (version[0] < 5
- || (version[0] == 5 && version[1] < 4)
- || (version[0] == 5 && version[1] == 4 && version[2] <= 1)) //5.4.1p3 down
- {
- matrix *= Matrix3x2.CreateTranslation(m_Sprite.m_Rect.width * 0.5f - textureRectOffset.X, m_Sprite.m_Rect.height * 0.5f - textureRectOffset.Y);
- }
- else
+ var points = triangles.Select(x => x.Select(y => new PointF(y.X, y.Y)).ToArray());
+ using (var path = new GraphicsPath())
{
- matrix *= Matrix3x2.CreateTranslation(m_Sprite.m_Rect.width * m_Sprite.m_Pivot.X - textureRectOffset.X, m_Sprite.m_Rect.height * m_Sprite.m_Pivot.Y - textureRectOffset.Y);
- }
- path = path.Transform(matrix);
- var options = new DrawingOptions
- {
- GraphicsOptions = new GraphicsOptions()
+ foreach (var p in points)
{
- AlphaCompositionMode = PixelAlphaCompositionMode.DestOut
+ path.AddPolygon(p);
}
- };
- var rectP = new RectangularPolygon(0, 0, rect.Width, rect.Height);
- spriteImage.Mutate(x => x.Fill(options, SixLabors.ImageSharp.Color.Red, rectP.Clip(path)));
- spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));
- return spriteImage;
+ using (var matr = new Matrix())
+ {
+ var version = m_Sprite.version;
+ if (version[0] < 5
+ || (version[0] == 5 && version[1] < 4)
+ || (version[0] == 5 && version[1] == 4 && version[2] <= 1)) //5.4.1p3 down
+ {
+ matr.Translate(m_Sprite.m_Rect.width * 0.5f - textureRectOffset.X, m_Sprite.m_Rect.height * 0.5f - textureRectOffset.Y);
+ }
+ else
+ {
+ matr.Translate(m_Sprite.m_Rect.width * m_Sprite.m_Pivot.X - textureRectOffset.X, m_Sprite.m_Rect.height * m_Sprite.m_Pivot.Y - textureRectOffset.Y);
+ }
+ matr.Scale(m_Sprite.m_PixelsToUnits, m_Sprite.m_PixelsToUnits);
+ path.Transform(matr);
+ var bitmap = new Bitmap(rect.Width, rect.Height);
+ using (var graphic = Graphics.FromImage(bitmap))
+ {
+ using (var brush = new TextureBrush(spriteImage))
+ {
+ graphic.FillPath(brush, path);
+ bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
+ spriteImage.Dispose();
+ return bitmap;
+ }
+ }
+ }
+ }
}
catch
{
@@ -120,7 +132,7 @@ private static Image CutImage(Texture2D m_Texture2D, Sprite m_Sprite, Rectf text
}
//Rectangle
- spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));
+ spriteImage.RotateFlip(RotateFlipType.RotateNoneFlipY);
return spriteImage;
}
}
diff --git a/AssetStudioUtility/TGASharpLib.cs b/AssetStudioUtility/TGASharpLib.cs
new file mode 100644
index 00000000..7a38b65a
--- /dev/null
+++ b/AssetStudioUtility/TGASharpLib.cs
@@ -0,0 +1,5963 @@
+/* MIT License
+ Copyright (c) 2017 TGASharpLib
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace TGASharpLib
+{
+ #region Enums
+ ///
+ /// The first 128 Color Map Type codes are reserved for use by Truevision,
+ /// while the second set of 128 Color Map Type codes(128 to 255) may be used for
+ /// developer applications.
+ /// True-Color images do not normally make use of the color map field, but some current
+ /// applications store palette information or developer-defined information in this field.
+ /// It is best to check Field 3, Image Type, to make sure you have a file which can use the
+ /// data stored in the Color Map Field.
+ /// Otherwise ignore the information. When saving or creating files for True-Color
+ /// images do not use this field and set it to Zero to ensure compatibility. Please refer
+ /// to the Developer Area specification for methods of storing developer defined information.
+ ///
+ public enum TgaColorMapType : byte
+ {
+ NoColorMap = 0,
+ ColorMap = 1,
+ Truevision_2,
+ Truevision_3,
+ Truevision_4,
+ Truevision_5,
+ Truevision_6,
+ Truevision_7,
+ Truevision_8,
+ Truevision_9,
+ Truevision_10,
+ Truevision_11,
+ Truevision_12,
+ Truevision_13,
+ Truevision_14,
+ Truevision_15,
+ Truevision_16,
+ Truevision_17,
+ Truevision_18,
+ Truevision_19,
+ Truevision_20,
+ Truevision_21,
+ Truevision_22,
+ Truevision_23,
+ Truevision_24,
+ Truevision_25,
+ Truevision_26,
+ Truevision_27,
+ Truevision_28,
+ Truevision_29,
+ Truevision_30,
+ Truevision_31,
+ Truevision_32,
+ Truevision_33,
+ Truevision_34,
+ Truevision_35,
+ Truevision_36,
+ Truevision_37,
+ Truevision_38,
+ Truevision_39,
+ Truevision_40,
+ Truevision_41,
+ Truevision_42,
+ Truevision_43,
+ Truevision_44,
+ Truevision_45,
+ Truevision_46,
+ Truevision_47,
+ Truevision_48,
+ Truevision_49,
+ Truevision_50,
+ Truevision_51,
+ Truevision_52,
+ Truevision_53,
+ Truevision_54,
+ Truevision_55,
+ Truevision_56,
+ Truevision_57,
+ Truevision_58,
+ Truevision_59,
+ Truevision_60,
+ Truevision_61,
+ Truevision_62,
+ Truevision_63,
+ Truevision_64,
+ Truevision_65,
+ Truevision_66,
+ Truevision_67,
+ Truevision_68,
+ Truevision_69,
+ Truevision_70,
+ Truevision_71,
+ Truevision_72,
+ Truevision_73,
+ Truevision_74,
+ Truevision_75,
+ Truevision_76,
+ Truevision_77,
+ Truevision_78,
+ Truevision_79,
+ Truevision_80,
+ Truevision_81,
+ Truevision_82,
+ Truevision_83,
+ Truevision_84,
+ Truevision_85,
+ Truevision_86,
+ Truevision_87,
+ Truevision_88,
+ Truevision_89,
+ Truevision_90,
+ Truevision_91,
+ Truevision_92,
+ Truevision_93,
+ Truevision_94,
+ Truevision_95,
+ Truevision_96,
+ Truevision_97,
+ Truevision_98,
+ Truevision_99,
+ Truevision_100,
+ Truevision_101,
+ Truevision_102,
+ Truevision_103,
+ Truevision_104,
+ Truevision_105,
+ Truevision_106,
+ Truevision_107,
+ Truevision_108,
+ Truevision_109,
+ Truevision_110,
+ Truevision_111,
+ Truevision_112,
+ Truevision_113,
+ Truevision_114,
+ Truevision_115,
+ Truevision_116,
+ Truevision_117,
+ Truevision_118,
+ Truevision_119,
+ Truevision_120,
+ Truevision_121,
+ Truevision_122,
+ Truevision_123,
+ Truevision_124,
+ Truevision_125,
+ Truevision_126,
+ Truevision_127,
+ Other_128,
+ Other_129,
+ Other_130,
+ Other_131,
+ Other_132,
+ Other_133,
+ Other_134,
+ Other_135,
+ Other_136,
+ Other_137,
+ Other_138,
+ Other_139,
+ Other_140,
+ Other_141,
+ Other_142,
+ Other_143,
+ Other_144,
+ Other_145,
+ Other_146,
+ Other_147,
+ Other_148,
+ Other_149,
+ Other_150,
+ Other_151,
+ Other_152,
+ Other_153,
+ Other_154,
+ Other_155,
+ Other_156,
+ Other_157,
+ Other_158,
+ Other_159,
+ Other_160,
+ Other_161,
+ Other_162,
+ Other_163,
+ Other_164,
+ Other_165,
+ Other_166,
+ Other_167,
+ Other_168,
+ Other_169,
+ Other_170,
+ Other_171,
+ Other_172,
+ Other_173,
+ Other_174,
+ Other_175,
+ Other_176,
+ Other_177,
+ Other_178,
+ Other_179,
+ Other_180,
+ Other_181,
+ Other_182,
+ Other_183,
+ Other_184,
+ Other_185,
+ Other_186,
+ Other_187,
+ Other_188,
+ Other_189,
+ Other_190,
+ Other_191,
+ Other_192,
+ Other_193,
+ Other_194,
+ Other_195,
+ Other_196,
+ Other_197,
+ Other_198,
+ Other_199,
+ Other_200,
+ Other_201,
+ Other_202,
+ Other_203,
+ Other_204,
+ Other_205,
+ Other_206,
+ Other_207,
+ Other_208,
+ Other_209,
+ Other_210,
+ Other_211,
+ Other_212,
+ Other_213,
+ Other_214,
+ Other_215,
+ Other_216,
+ Other_217,
+ Other_218,
+ Other_219,
+ Other_220,
+ Other_221,
+ Other_222,
+ Other_223,
+ Other_224,
+ Other_225,
+ Other_226,
+ Other_227,
+ Other_228,
+ Other_229,
+ Other_230,
+ Other_231,
+ Other_232,
+ Other_233,
+ Other_234,
+ Other_235,
+ Other_236,
+ Other_237,
+ Other_238,
+ Other_239,
+ Other_240,
+ Other_241,
+ Other_242,
+ Other_243,
+ Other_244,
+ Other_245,
+ Other_246,
+ Other_247,
+ Other_248,
+ Other_249,
+ Other_250,
+ Other_251,
+ Other_252,
+ Other_253,
+ Other_254,
+ Other_255
+ }
+
+ ///
+ /// Establishes the number of bits per entry. Typically 15, 16, 24 or 32-bit values are used.
+ /// When working with VDA or VDA/D cards it is preferred that you select 16 bits(5 bits
+ /// per primary with 1 bit to select interrupt control) and set the 16th bit to 0 so that the
+ /// interrupt bit is disabled. Even if this field is set to 15 bits(5 bits per primary) you
+ /// must still parse the color map data 16 bits at a time and ignore the 16th bit.
+ /// When working with a TARGA M8 card you would select 24 bits (8 bits per primary)
+ /// since the color map is defined as 256 entries of 24 bit color values.
+ /// When working with a TrueVista card(ATVista or NuVista) you would select 24-bit(8 bits per
+ /// primary) or 32-bit(8 bits per primary including Alpha channel) depending on your
+ /// application’s use of look-up tables. It is suggested that when working with 16-bit and
+ /// 32-bit color images, you store them as True-Color images and do not use the color map
+ /// field to store look-up tables. Please refer to the TGA Extensions for fields better suited
+ /// to storing look-up table information.
+ ///
+ public enum TgaColorMapEntrySize : byte
+ {
+ Other = 0,
+ X1R5G5B5 = 15,
+ A1R5G5B5 = 16,
+ R8G8B8 = 24,
+ A8R8G8B8 = 32
+ }
+
+ ///
+ /// Truevision has currently defined seven image types:
+ /// 0 - No Image Data Included;
+ /// 1 - Uncompressed, Color-mapped Image;
+ /// 2 - Uncompressed, True-color Image;
+ /// 3 - Uncompressed, Black-and-white Image;
+ /// 9 - Run-length encoded, Color-mapped Image;
+ /// 10 - Run-length encoded, True-color Image;
+ /// 11 - Run-length encoded, Black-and-white Image.
+ /// Image Data Type codes 0 to 127 are reserved for use by Truevision for general applications.
+ /// Image Data Type codes 128 to 255 may be used for developer applications.
+ ///
+ public enum TgaImageType : byte
+ {
+ NoImageData = 0,
+ Uncompressed_ColorMapped = 1,
+ Uncompressed_TrueColor,
+ Uncompressed_BlackWhite,
+ _Truevision_4,
+ _Truevision_5,
+ _Truevision_6,
+ _Truevision_7,
+ _Truevision_8,
+ RLE_ColorMapped = 9,
+ RLE_TrueColor,
+ RLE_BlackWhite,
+ _Truevision_12,
+ _Truevision_13,
+ _Truevision_14,
+ _Truevision_15,
+ _Truevision_16,
+ _Truevision_17,
+ _Truevision_18,
+ _Truevision_19,
+ _Truevision_20,
+ _Truevision_21,
+ _Truevision_22,
+ _Truevision_23,
+ _Truevision_24,
+ _Truevision_25,
+ _Truevision_26,
+ _Truevision_27,
+ _Truevision_28,
+ _Truevision_29,
+ _Truevision_30,
+ _Truevision_31,
+ _Truevision_32,
+ _Truevision_33,
+ _Truevision_34,
+ _Truevision_35,
+ _Truevision_36,
+ _Truevision_37,
+ _Truevision_38,
+ _Truevision_39,
+ _Truevision_40,
+ _Truevision_41,
+ _Truevision_42,
+ _Truevision_43,
+ _Truevision_44,
+ _Truevision_45,
+ _Truevision_46,
+ _Truevision_47,
+ _Truevision_48,
+ _Truevision_49,
+ _Truevision_50,
+ _Truevision_51,
+ _Truevision_52,
+ _Truevision_53,
+ _Truevision_54,
+ _Truevision_55,
+ _Truevision_56,
+ _Truevision_57,
+ _Truevision_58,
+ _Truevision_59,
+ _Truevision_60,
+ _Truevision_61,
+ _Truevision_62,
+ _Truevision_63,
+ _Truevision_64,
+ _Truevision_65,
+ _Truevision_66,
+ _Truevision_67,
+ _Truevision_68,
+ _Truevision_69,
+ _Truevision_70,
+ _Truevision_71,
+ _Truevision_72,
+ _Truevision_73,
+ _Truevision_74,
+ _Truevision_75,
+ _Truevision_76,
+ _Truevision_77,
+ _Truevision_78,
+ _Truevision_79,
+ _Truevision_80,
+ _Truevision_81,
+ _Truevision_82,
+ _Truevision_83,
+ _Truevision_84,
+ _Truevision_85,
+ _Truevision_86,
+ _Truevision_87,
+ _Truevision_88,
+ _Truevision_89,
+ _Truevision_90,
+ _Truevision_91,
+ _Truevision_92,
+ _Truevision_93,
+ _Truevision_94,
+ _Truevision_95,
+ _Truevision_96,
+ _Truevision_97,
+ _Truevision_98,
+ _Truevision_99,
+ _Truevision_100,
+ _Truevision_101,
+ _Truevision_102,
+ _Truevision_103,
+ _Truevision_104,
+ _Truevision_105,
+ _Truevision_106,
+ _Truevision_107,
+ _Truevision_108,
+ _Truevision_109,
+ _Truevision_110,
+ _Truevision_111,
+ _Truevision_112,
+ _Truevision_113,
+ _Truevision_114,
+ _Truevision_115,
+ _Truevision_116,
+ _Truevision_117,
+ _Truevision_118,
+ _Truevision_119,
+ _Truevision_120,
+ _Truevision_121,
+ _Truevision_122,
+ _Truevision_123,
+ _Truevision_124,
+ _Truevision_125,
+ _Truevision_126,
+ _Truevision_127,
+ _Other_128,
+ _Other_129,
+ _Other_130,
+ _Other_131,
+ _Other_132,
+ _Other_133,
+ _Other_134,
+ _Other_135,
+ _Other_136,
+ _Other_137,
+ _Other_138,
+ _Other_139,
+ _Other_140,
+ _Other_141,
+ _Other_142,
+ _Other_143,
+ _Other_144,
+ _Other_145,
+ _Other_146,
+ _Other_147,
+ _Other_148,
+ _Other_149,
+ _Other_150,
+ _Other_151,
+ _Other_152,
+ _Other_153,
+ _Other_154,
+ _Other_155,
+ _Other_156,
+ _Other_157,
+ _Other_158,
+ _Other_159,
+ _Other_160,
+ _Other_161,
+ _Other_162,
+ _Other_163,
+ _Other_164,
+ _Other_165,
+ _Other_166,
+ _Other_167,
+ _Other_168,
+ _Other_169,
+ _Other_170,
+ _Other_171,
+ _Other_172,
+ _Other_173,
+ _Other_174,
+ _Other_175,
+ _Other_176,
+ _Other_177,
+ _Other_178,
+ _Other_179,
+ _Other_180,
+ _Other_181,
+ _Other_182,
+ _Other_183,
+ _Other_184,
+ _Other_185,
+ _Other_186,
+ _Other_187,
+ _Other_188,
+ _Other_189,
+ _Other_190,
+ _Other_191,
+ _Other_192,
+ _Other_193,
+ _Other_194,
+ _Other_195,
+ _Other_196,
+ _Other_197,
+ _Other_198,
+ _Other_199,
+ _Other_200,
+ _Other_201,
+ _Other_202,
+ _Other_203,
+ _Other_204,
+ _Other_205,
+ _Other_206,
+ _Other_207,
+ _Other_208,
+ _Other_209,
+ _Other_210,
+ _Other_211,
+ _Other_212,
+ _Other_213,
+ _Other_214,
+ _Other_215,
+ _Other_216,
+ _Other_217,
+ _Other_218,
+ _Other_219,
+ _Other_220,
+ _Other_221,
+ _Other_222,
+ _Other_223,
+ _Other_224,
+ _Other_225,
+ _Other_226,
+ _Other_227,
+ _Other_228,
+ _Other_229,
+ _Other_230,
+ _Other_231,
+ _Other_232,
+ _Other_233,
+ _Other_234,
+ _Other_235,
+ _Other_236,
+ _Other_237,
+ _Other_238,
+ _Other_239,
+ _Other_240,
+ _Other_241,
+ _Other_242,
+ _Other_243,
+ _Other_244,
+ _Other_245,
+ _Other_246,
+ _Other_247,
+ _Other_248,
+ _Other_249,
+ _Other_250,
+ _Other_251,
+ _Other_252,
+ _Other_253,
+ _Other_254,
+ _Other_255
+ }
+
+ ///
+ /// Number of bits per pixel. This number includes the Attribute or Alpha channel bits.
+ /// Common values are 8, 16, 24 and 32 but other pixel depths could be used.
+ ///
+ public enum TgaPixelDepth : byte
+ {
+ Other = 0,
+ Bpp8 = 8,
+ Bpp16 = 16,
+ Bpp24 = 24,
+ Bpp32 = 32
+ }
+
+ ///
+ /// Used to indicate the order in which pixel data is transferred from the file to the screen.
+ /// (Bit 4 (bit 0 in enum) is for left-to-right ordering and bit 5 (bit 1 in enum) is for
+ /// topto-bottom ordering as shown below.)
+ ///
+ public enum TgaImgOrigin : byte
+ {
+ BottomLeft = 0,
+ BottomRight,
+ TopLeft,
+ TopRight
+ }
+
+ ///
+ /// Contains a value which specifies the type of Alpha channel
+ /// data contained in the file. Value Meaning:
+ /// 0: no Alpha data included (bits 3-0 of field 5.6 should also be set to zero)
+ /// 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
+ /// Pre-multiplied Alpha Example: Suppose the Alpha channel data is being used to specify the
+ /// opacity of each pixel(for use when the image is overlayed on another image), where 0 indicates
+ /// that the pixel is completely transparent and a value of 1 indicates that the pixel is
+ /// completely opaque(assume all component values have been normalized).
+ /// A quadruple(a, r, g, b) of( 0.5, 1, 0, 0) would indicate that the pixel is pure red with a
+ /// transparency of one-half. For numerous reasons(including image compositing) is is better to
+ /// pre-multiply the individual color components with the value in the Alpha channel.
+ /// A pre-multiplication of the above would produce a quadruple(0.5, 0.5, 0, 0).
+ /// A value of 3 in the Attributes Type Field(field 23) would indicate that the color components
+ /// of the pixel have already been scaled by the value in the Alpha channel.
+ ///
+ public enum TgaAttrType : byte
+ {
+ NoAlpha = 0,
+ UndefinedAlphaCanBeIgnored,
+ UndefinedAlphaButShouldBeRetained,
+ UsefulAlpha,
+ PreMultipliedAlpha,
+ _Reserved_5,
+ _Reserved_6,
+ _Reserved_7,
+ _Reserved_8,
+ _Reserved_9,
+ _Reserved_10,
+ _Reserved_11,
+ _Reserved_12,
+ _Reserved_13,
+ _Reserved_14,
+ _Reserved_15,
+ _Reserved_16,
+ _Reserved_17,
+ _Reserved_18,
+ _Reserved_19,
+ _Reserved_20,
+ _Reserved_21,
+ _Reserved_22,
+ _Reserved_23,
+ _Reserved_24,
+ _Reserved_25,
+ _Reserved_26,
+ _Reserved_27,
+ _Reserved_28,
+ _Reserved_29,
+ _Reserved_30,
+ _Reserved_31,
+ _Reserved_32,
+ _Reserved_33,
+ _Reserved_34,
+ _Reserved_35,
+ _Reserved_36,
+ _Reserved_37,
+ _Reserved_38,
+ _Reserved_39,
+ _Reserved_40,
+ _Reserved_41,
+ _Reserved_42,
+ _Reserved_43,
+ _Reserved_44,
+ _Reserved_45,
+ _Reserved_46,
+ _Reserved_47,
+ _Reserved_48,
+ _Reserved_49,
+ _Reserved_50,
+ _Reserved_51,
+ _Reserved_52,
+ _Reserved_53,
+ _Reserved_54,
+ _Reserved_55,
+ _Reserved_56,
+ _Reserved_57,
+ _Reserved_58,
+ _Reserved_59,
+ _Reserved_60,
+ _Reserved_61,
+ _Reserved_62,
+ _Reserved_63,
+ _Reserved_64,
+ _Reserved_65,
+ _Reserved_66,
+ _Reserved_67,
+ _Reserved_68,
+ _Reserved_69,
+ _Reserved_70,
+ _Reserved_71,
+ _Reserved_72,
+ _Reserved_73,
+ _Reserved_74,
+ _Reserved_75,
+ _Reserved_76,
+ _Reserved_77,
+ _Reserved_78,
+ _Reserved_79,
+ _Reserved_80,
+ _Reserved_81,
+ _Reserved_82,
+ _Reserved_83,
+ _Reserved_84,
+ _Reserved_85,
+ _Reserved_86,
+ _Reserved_87,
+ _Reserved_88,
+ _Reserved_89,
+ _Reserved_90,
+ _Reserved_91,
+ _Reserved_92,
+ _Reserved_93,
+ _Reserved_94,
+ _Reserved_95,
+ _Reserved_96,
+ _Reserved_97,
+ _Reserved_98,
+ _Reserved_99,
+ _Reserved_100,
+ _Reserved_101,
+ _Reserved_102,
+ _Reserved_103,
+ _Reserved_104,
+ _Reserved_105,
+ _Reserved_106,
+ _Reserved_107,
+ _Reserved_108,
+ _Reserved_109,
+ _Reserved_110,
+ _Reserved_111,
+ _Reserved_112,
+ _Reserved_113,
+ _Reserved_114,
+ _Reserved_115,
+ _Reserved_116,
+ _Reserved_117,
+ _Reserved_118,
+ _Reserved_119,
+ _Reserved_120,
+ _Reserved_121,
+ _Reserved_122,
+ _Reserved_123,
+ _Reserved_124,
+ _Reserved_125,
+ _Reserved_126,
+ _Reserved_127,
+ _UnAssigned_128,
+ _UnAssigned_129,
+ _UnAssigned_130,
+ _UnAssigned_131,
+ _UnAssigned_132,
+ _UnAssigned_133,
+ _UnAssigned_134,
+ _UnAssigned_135,
+ _UnAssigned_136,
+ _UnAssigned_137,
+ _UnAssigned_138,
+ _UnAssigned_139,
+ _UnAssigned_140,
+ _UnAssigned_141,
+ _UnAssigned_142,
+ _UnAssigned_143,
+ _UnAssigned_144,
+ _UnAssigned_145,
+ _UnAssigned_146,
+ _UnAssigned_147,
+ _UnAssigned_148,
+ _UnAssigned_149,
+ _UnAssigned_150,
+ _UnAssigned_151,
+ _UnAssigned_152,
+ _UnAssigned_153,
+ _UnAssigned_154,
+ _UnAssigned_155,
+ _UnAssigned_156,
+ _UnAssigned_157,
+ _UnAssigned_158,
+ _UnAssigned_159,
+ _UnAssigned_160,
+ _UnAssigned_161,
+ _UnAssigned_162,
+ _UnAssigned_163,
+ _UnAssigned_164,
+ _UnAssigned_165,
+ _UnAssigned_166,
+ _UnAssigned_167,
+ _UnAssigned_168,
+ _UnAssigned_169,
+ _UnAssigned_170,
+ _UnAssigned_171,
+ _UnAssigned_172,
+ _UnAssigned_173,
+ _UnAssigned_174,
+ _UnAssigned_175,
+ _UnAssigned_176,
+ _UnAssigned_177,
+ _UnAssigned_178,
+ _UnAssigned_179,
+ _UnAssigned_180,
+ _UnAssigned_181,
+ _UnAssigned_182,
+ _UnAssigned_183,
+ _UnAssigned_184,
+ _UnAssigned_185,
+ _UnAssigned_186,
+ _UnAssigned_187,
+ _UnAssigned_188,
+ _UnAssigned_189,
+ _UnAssigned_190,
+ _UnAssigned_191,
+ _UnAssigned_192,
+ _UnAssigned_193,
+ _UnAssigned_194,
+ _UnAssigned_195,
+ _UnAssigned_196,
+ _UnAssigned_197,
+ _UnAssigned_198,
+ _UnAssigned_199,
+ _UnAssigned_200,
+ _UnAssigned_201,
+ _UnAssigned_202,
+ _UnAssigned_203,
+ _UnAssigned_204,
+ _UnAssigned_205,
+ _UnAssigned_206,
+ _UnAssigned_207,
+ _UnAssigned_208,
+ _UnAssigned_209,
+ _UnAssigned_210,
+ _UnAssigned_211,
+ _UnAssigned_212,
+ _UnAssigned_213,
+ _UnAssigned_214,
+ _UnAssigned_215,
+ _UnAssigned_216,
+ _UnAssigned_217,
+ _UnAssigned_218,
+ _UnAssigned_219,
+ _UnAssigned_220,
+ _UnAssigned_221,
+ _UnAssigned_222,
+ _UnAssigned_223,
+ _UnAssigned_224,
+ _UnAssigned_225,
+ _UnAssigned_226,
+ _UnAssigned_227,
+ _UnAssigned_228,
+ _UnAssigned_229,
+ _UnAssigned_230,
+ _UnAssigned_231,
+ _UnAssigned_232,
+ _UnAssigned_233,
+ _UnAssigned_234,
+ _UnAssigned_235,
+ _UnAssigned_236,
+ _UnAssigned_237,
+ _UnAssigned_238,
+ _UnAssigned_239,
+ _UnAssigned_240,
+ _UnAssigned_241,
+ _UnAssigned_242,
+ _UnAssigned_243,
+ _UnAssigned_244,
+ _UnAssigned_245,
+ _UnAssigned_246,
+ _UnAssigned_247,
+ _UnAssigned_248,
+ _UnAssigned_249,
+ _UnAssigned_250,
+ _UnAssigned_251,
+ _UnAssigned_252,
+ _UnAssigned_253,
+ _UnAssigned_254,
+ _UnAssigned_255
+ }
+ #endregion
+
+ #region Classes
+ public class TgaColorKey : ICloneable
+ {
+ byte a = 0;
+ byte r = 0;
+ byte g = 0;
+ byte b = 0;
+
+ public TgaColorKey()
+ {
+ }
+
+ ///
+ /// Make from ARGB bytes.
+ ///
+ /// Alpha value.
+ /// Red value.
+ /// Green value.
+ /// Blue value.
+ public TgaColorKey(byte A, byte R, byte G, byte B)
+ {
+ a = A;
+ r = R;
+ g = G;
+ b = B;
+ }
+
+ ///
+ /// Make from ARGB bytes.
+ ///
+ /// Array of bytes(byte[4]).
+ public TgaColorKey(byte[] Bytes)
+ {
+ if (Bytes == null)
+ throw new ArgumentNullException(nameof(Bytes) + " = null!");
+ if (Bytes.Length != Size)
+ throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!");
+
+ Color color = Color.FromArgb(BitConverter.ToInt32(Bytes, 0));
+ a = color.A;
+ r = color.R;
+ g = color.G;
+ b = color.B;
+ }
+
+ ///
+ /// Make from .
+ ///
+ /// 32bit ARGB integer color value.
+ public TgaColorKey(int ARGB)
+ {
+ Color ColorARGB = Color.FromArgb(ARGB);
+ a = ColorARGB.A;
+ r = ColorARGB.R;
+ g = ColorARGB.G;
+ b = ColorARGB.B;
+ }
+
+ ///
+ /// Make from .
+ ///
+ /// GDI+ value.
+ public TgaColorKey(Color color)
+ {
+ a = color.A;
+ r = color.R;
+ g = color.G;
+ b = color.B;
+ }
+
+ ///
+ /// Gets or sets alpha color value.
+ ///
+ public byte A
+ {
+ get { return a; }
+ set { a = value; }
+ }
+
+ ///
+ /// Gets or sets red color value.
+ ///
+ public byte R
+ {
+ get { return r; }
+ set { r = value; }
+ }
+
+ ///
+ /// Gets or sets green color value.
+ ///
+ public byte G
+ {
+ get { return g; }
+ set { g = value; }
+ }
+
+ ///
+ /// Gets or sets blue color value.
+ ///
+ public byte B
+ {
+ get { return b; }
+ set { b = value; }
+ }
+
+ ///
+ /// Gets TGA Field size in bytes.
+ ///
+ public const int Size = 4;
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Copy of
+ public TgaColorKey Clone()
+ {
+ return new TgaColorKey(a, r, g, b);
+ }
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Copy of
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaColorKey) ? Equals((TgaColorKey)obj) : false);
+ }
+
+ public bool Equals(TgaColorKey item)
+ {
+ return (a == item.a && r == item.r && g == item.g && b == item.b);
+ }
+
+ public static bool operator ==(TgaColorKey item1, TgaColorKey item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaColorKey item1, TgaColorKey item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public override int GetHashCode()
+ {
+ return ToInt().GetHashCode();
+ }
+
+ ///
+ /// Gets like string.
+ ///
+ /// String in ARGB format.
+ public override string ToString()
+ {
+ return String.Format("{0}={1}, {2}={3}, {4}={5}, {6}={7}",
+ nameof(A), a, nameof(R), r, nameof(G), g, nameof(B), b);
+ }
+
+ ///
+ /// Convert to byte array.
+ ///
+ /// Byte array with length = 4.
+ public byte[] ToBytes()
+ {
+ return BitConverter.GetBytes(ToInt());
+ }
+
+ ///
+ /// Gets like GDI+ .
+ ///
+ /// value of .
+ public Color ToColor()
+ {
+ return Color.FromArgb(a, r, g, b);
+ }
+
+ ///
+ /// Gets like ARGB .
+ ///
+ /// ARGB value of .
+ public int ToInt()
+ {
+ return ToColor().ToArgb();
+ }
+ }
+
+ ///
+ /// This field (5 bytes) and its sub-fields describe the color map (if any) used for the image.
+ /// If the Color Map Type field is set to zero, indicating that no color map exists, then
+ /// these 5 bytes should be set to zero. These bytes always must be written to the file.
+ ///
+ public class TgaColorMapSpec : ICloneable
+ {
+ ushort firstEntryIndex = 0;
+ ushort colorMapLength = 0;
+ TgaColorMapEntrySize colorMapEntrySize = TgaColorMapEntrySize.Other;
+
+ ///
+ /// Make new .
+ ///
+ public TgaColorMapSpec()
+ {
+ }
+
+ ///
+ /// Make from bytes.
+ ///
+ /// Array of bytes(byte[5]).
+ public TgaColorMapSpec(byte[] Bytes)
+ {
+ if (Bytes == null)
+ throw new ArgumentNullException(nameof(Bytes) + " = null!");
+ if (Bytes.Length != Size)
+ throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!");
+
+ firstEntryIndex = BitConverter.ToUInt16(Bytes, 0);
+ colorMapLength = BitConverter.ToUInt16(Bytes, 2);
+ colorMapEntrySize = (TgaColorMapEntrySize)Bytes[4];
+ }
+
+ ///
+ /// Field 4.1 (2 bytes):
+ /// Index of the first color map entry. Index refers to the starting entry in loading
+ /// the color map.
+ /// Example: If you would have 1024 entries in the entire color map but you only
+ /// need to store 72 of those entries, this field allows you to start in the middle of
+ /// the color-map (e.g., position 342).
+ ///
+ public ushort FirstEntryIndex
+ {
+ get { return firstEntryIndex; }
+ set { firstEntryIndex = value; }
+ }
+
+ ///
+ /// Field 4.2 (2 bytes):
+ /// Total number of color map entries included.
+ ///
+ public ushort ColorMapLength
+ {
+ get { return colorMapLength; }
+ set { colorMapLength = value; }
+ }
+
+ ///
+ /// Field 4.3 (1 byte):
+ /// Establishes the number of bits per entry. Typically 15, 16, 24 or 32-bit values are used.
+ /// When working with VDA or VDA/D cards it is preferred that you select 16 bits(5 bits
+ /// per primary with 1 bit to select interrupt control) and set the 16th bit to 0 so that the
+ /// interrupt bit is disabled. Even if this field is set to 15 bits(5 bits per primary) you
+ /// must still parse the color map data 16 bits at a time and ignore the 16th bit.
+ /// When working with a TARGA M8 card you would select 24 bits (8 bits per primary)
+ /// since the color map is defined as 256 entries of 24 bit color values.
+ /// When working with a TrueVista card(ATVista or NuVista) you would select 24-bit(8 bits per
+ /// primary) or 32-bit(8 bits per primary including Alpha channel) depending on your
+ /// application’s use of look-up tables. It is suggested that when working with 16-bit and
+ /// 32-bit color images, you store them as True-Color images and do not use the color map
+ /// field to store look-up tables. Please refer to the TGA Extensions for fields better suited
+ /// to storing look-up table information.
+ ///
+ public TgaColorMapEntrySize ColorMapEntrySize
+ {
+ get { return colorMapEntrySize; }
+ set { colorMapEntrySize = value; }
+ }
+
+ ///
+ /// Gets TGA Field size in bytes.
+ ///
+ public const int Size = 5;
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Copy of
+ public TgaColorMapSpec Clone()
+ {
+ return new TgaColorMapSpec(ToBytes());
+ }
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Copy of
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaColorMapSpec) ? Equals((TgaColorMapSpec)obj) : false);
+ }
+
+ public bool Equals(TgaColorMapSpec item)
+ {
+ return (firstEntryIndex == item.firstEntryIndex &&
+ colorMapLength == item.colorMapLength &&
+ colorMapEntrySize == item.colorMapEntrySize);
+ }
+
+ public static bool operator ==(TgaColorMapSpec item1, TgaColorMapSpec item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaColorMapSpec item1, TgaColorMapSpec item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (firstEntryIndex << 16 | colorMapLength).GetHashCode() ^ colorMapEntrySize.GetHashCode();
+ }
+ }
+
+ public override string ToString()
+ {
+ return String.Format("{0}={1}, {2}={3}, {4}={5}", nameof(FirstEntryIndex), FirstEntryIndex,
+ nameof(ColorMapLength), ColorMapLength, nameof(ColorMapEntrySize), ColorMapEntrySize);
+ }
+
+ ///
+ /// Convert ColorMapSpec to byte array.
+ ///
+ /// Byte array with length = 5.
+ public byte[] ToBytes()
+ {
+ return BitConverterExt.ToBytes(firstEntryIndex, colorMapLength, (byte)colorMapEntrySize);
+ }
+ }
+
+ public class TgaComment : ICloneable
+ {
+ const int StrNLen = 80; //80 ASCII chars + 1 '\0' = 81 per SrtN!
+ string origString = String.Empty;
+ char blankSpaceChar = TgaString.DefaultBlankSpaceChar;
+
+ public TgaComment()
+ {
+ }
+
+ public TgaComment(string Str, char BlankSpaceChar = '\0')
+ {
+ if (Str == null)
+ throw new ArgumentNullException(nameof(Str) + " = null!");
+
+ origString = Str;
+ blankSpaceChar = BlankSpaceChar;
+ }
+
+ public TgaComment(byte[] Bytes)
+ {
+ if (Bytes == null)
+ throw new ArgumentNullException(nameof(Bytes) + " = null!");
+ if (Bytes.Length != Size)
+ throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!");
+
+ string s = Encoding.ASCII.GetString(Bytes, 0, StrNLen);
+ s += Encoding.ASCII.GetString(Bytes, 81, StrNLen);
+ s += Encoding.ASCII.GetString(Bytes, 162, StrNLen);
+ s += Encoding.ASCII.GetString(Bytes, 243, StrNLen);
+
+ switch (s[s.Length - 1])
+ {
+ case '\0':
+ case ' ':
+ blankSpaceChar = s[s.Length - 1];
+ origString = s.TrimEnd(new char[] { s[s.Length - 1] });
+ break;
+ default:
+ origString = s;
+ break;
+ }
+ }
+
+ ///
+ /// Gets TGA Field size in bytes.
+ ///
+ public const int Size = 81 * 4;
+
+ public string OriginalString
+ {
+ get { return origString; }
+ set { origString = value; }
+ }
+
+ public char BlankSpaceChar
+ {
+ get { return blankSpaceChar; }
+ set { blankSpaceChar = value; }
+ }
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Copy of
+ public TgaComment Clone()
+ {
+ return new TgaComment(origString, blankSpaceChar);
+ }
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Copy of
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaComment) ? Equals((TgaComment)obj) : false);
+ }
+
+ public bool Equals(TgaComment item)
+ {
+ return (origString == item.origString && blankSpaceChar == item.blankSpaceChar);
+ }
+
+ public static bool operator ==(TgaComment item1, TgaComment item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaComment item1, TgaComment item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public override int GetHashCode()
+ {
+ return origString.GetHashCode() ^ blankSpaceChar.GetHashCode();
+ }
+
+ ///
+ /// Get ASCII-Like string with string-terminators, example: "Line1 \0\0 Line2 \0\0\0".
+ ///
+ /// String with replaced string-terminators to "\0".
+ public override string ToString()
+ {
+ return Encoding.ASCII.GetString(ToBytes()).Replace("\0", @"\0");
+ }
+
+ ///
+ /// Get ASCII-Like string to first string-terminator, example:
+ /// "Some string \0 Some Data \0" - > "Some string".
+ ///
+ /// String to first string-terminator.
+ public string GetString()
+ {
+ String Str = Encoding.ASCII.GetString(ToBytes());
+ for (int i = 1; i < 4; i++)
+ Str = Str.Insert((StrNLen + 1) * i + i - 1, "\n");
+ return Str.Replace("\0", String.Empty).TrimEnd(new char[] { '\n' });
+ }
+
+ ///
+ /// Convert to byte array.
+ ///
+ /// Byte array, every byte is ASCII symbol.
+ public byte[] ToBytes()
+ {
+ return ToBytes(origString, blankSpaceChar);
+ }
+
+ ///
+ /// Convert to byte array.
+ ///
+ /// Input string.
+ /// Char for filling blank space in string.
+ /// Byte array, every byte is ASCII symbol.
+ public static byte[] ToBytes(string Str, char BlankSpaceChar = '\0')
+ {
+ char[] C = new char[81 * 4];
+
+ for (int i = 0; i < C.Length; i++)
+ {
+ if ((i + 82) % 81 == 0)
+ C[i] = TgaString.DefaultEndingChar;
+ else
+ {
+ int Index = i - i / 81;
+ C[i] = (Index < Str.Length ? Str[Index] : BlankSpaceChar);
+ }
+ }
+ return Encoding.ASCII.GetBytes(C);
+ }
+ }
+
+ public class TgaDateTime : ICloneable
+ {
+ ushort month = 0;
+ ushort day = 0;
+ ushort year = 0;
+ ushort hour = 0;
+ ushort minute = 0;
+ ushort second = 0;
+
+ ///
+ /// Make empty .
+ ///
+ public TgaDateTime()
+ {
+ }
+
+ ///
+ /// Make from .
+ ///
+ /// Some variable.
+ public TgaDateTime(DateTime DateAndTime)
+ {
+ month = (ushort)DateAndTime.Month;
+ day = (ushort)DateAndTime.Day;
+ year = (ushort)DateAndTime.Year;
+ hour = (ushort)DateAndTime.Hour;
+ minute = (ushort)DateAndTime.Minute;
+ second = (ushort)DateAndTime.Second;
+ }
+
+ ///
+ /// Make from ushort values.
+ ///
+ /// Month (1 - 12).
+ /// Day (1 - 31).
+ /// Year (4 digit, ie. 1989).
+ /// Hour (0 - 23).
+ /// Minute (0 - 59).
+ /// Second (0 - 59).
+ public TgaDateTime(ushort Month, ushort Day, ushort Year, ushort Hour, ushort Minute, ushort Second)
+ {
+ month = Month;
+ day = Day;
+ year = Year;
+ hour = Hour;
+ minute = Minute;
+ second = Second;
+ }
+
+ ///
+ /// Make from bytes.
+ ///
+ /// Array of bytes(byte[12]).
+ public TgaDateTime(byte[] Bytes)
+ {
+ if (Bytes == null)
+ throw new ArgumentNullException(nameof(Bytes) + " = null!");
+ else if (Bytes.Length != Size)
+ throw new ArgumentOutOfRangeException(nameof(Bytes) + " must be equal " + Size + "!");
+
+ month = BitConverter.ToUInt16(Bytes, 0);
+ day = BitConverter.ToUInt16(Bytes, 2);
+ year = BitConverter.ToUInt16(Bytes, 4);
+ hour = BitConverter.ToUInt16(Bytes, 6);
+ minute = BitConverter.ToUInt16(Bytes, 8);
+ second = BitConverter.ToUInt16(Bytes, 10);
+ }
+
+ ///
+ /// Gets or Sets month (1 - 12).
+ ///
+ public ushort Month
+ {
+ get { return month; }
+ set { month = value; }
+ }
+
+ ///
+ /// Gets or Sets day (1 - 31).
+ ///
+ public ushort Day
+ {
+ get { return day; }
+ set { day = value; }
+ }
+
+ ///
+ /// Gets or Sets year (4 digit, ie. 1989).
+ ///
+ public ushort Year
+ {
+ get { return year; }
+ set { year = value; }
+ }
+
+ ///
+ /// Gets or Sets hour (0 - 23).
+ ///
+ public ushort Hour
+ {
+ get { return hour; }
+ set { hour = value; }
+ }
+
+ ///
+ /// Gets or Sets minute (0 - 59).
+ ///
+ public ushort Minute
+ {
+ get { return minute; }
+ set { minute = value; }
+ }
+
+ ///
+ /// Gets or Sets second (0 - 59).
+ ///
+ public ushort Second
+ {
+ get { return second; }
+ set { second = value; }
+ }
+
+ ///
+ /// Gets TGA Field size in bytes.
+ ///
+ public const int Size = 12;
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Copy of
+ public TgaDateTime Clone()
+ {
+ return new TgaDateTime(month, day, year, hour, minute, second);
+ }
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Copy of
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaDateTime) ? Equals((TgaDateTime)obj) : false);
+ }
+
+ public bool Equals(TgaDateTime item)
+ {
+ return (
+ month == item.month &&
+ day == item.day &&
+ year == item.year &&
+ hour == item.hour &&
+ minute == item.minute &&
+ second == item.second);
+ }
+
+ public static bool operator ==(TgaDateTime item1, TgaDateTime item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaDateTime item1, TgaDateTime item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 17;
+ hash = hash * 23 + (month << 16 | hour).GetHashCode();
+ hash = hash * 23 + (day << 16 | minute).GetHashCode();
+ hash = hash * 23 + (year << 16 | second).GetHashCode();
+ return hash;
+ }
+ }
+
+ ///
+ /// Gets like string.
+ ///
+ /// String in "1990.01.23 1:02:03" format.
+ public override string ToString()
+ {
+ return String.Format("{0:D4}.{1:D2}.{2:D2} {3}:{4:D2}:{5:D2}", year, month, day, hour, minute, second);
+ }
+
+ ///
+ /// Convert to byte array.
+ ///
+ /// Byte array with length = 12.
+ public byte[] ToBytes()
+ {
+ return BitConverterExt.ToBytes(month, day, year, hour, minute, second);
+ }
+
+ ///
+ /// Gets like .
+ ///
+ /// value of .
+ public DateTime ToDateTime()
+ {
+ return new DateTime(year, month, day, hour, minute, second);
+ }
+ }
+
+ public class TgaDevEntry : ICloneable
+ {
+ // Directory
+ ushort fieldTag = 0;
+ uint fieldFileOffset = 0;
+ // Field
+ byte[] data = null;
+
+ ///
+ /// Make empty .
+ ///
+ public TgaDevEntry()
+ {
+ }
+
+ ///
+ /// Make from other .
+ ///
+ /// Some variable.
+ public TgaDevEntry(TgaDevEntry Entry)
+ {
+ if (Entry == null)
+ throw new ArgumentNullException();
+
+ fieldTag = Entry.fieldTag;
+ fieldFileOffset = Entry.fieldFileOffset;
+ data = BitConverterExt.ToBytes(Entry.data);
+ }
+
+ ///
+ /// Make from , and .
+ ///
+ /// TAG ID (0 - 65535). See .
+ /// TAG file offset in bytes. See .
+ /// This is DevEntry Field Data. See .
+ public TgaDevEntry(ushort Tag, uint Offset, byte[] Data = null)
+ {
+ fieldTag = Tag;
+ fieldFileOffset = Offset;
+ data = Data;
+ }
+
+ ///
+ /// Make from bytes.
+ ///
+ /// Array of bytes(byte[6] or bigger, if exist).
+ public TgaDevEntry(byte[] Bytes)
+ {
+ if (Bytes == null)
+ throw new ArgumentNullException(nameof(Bytes) + " = null!");
+ else if (Bytes.Length < 6)
+ throw new ArgumentOutOfRangeException(nameof(Bytes) + " must be >= 6!");
+
+ fieldTag = BitConverter.ToUInt16(Bytes, 0);
+ fieldFileOffset = BitConverter.ToUInt32(Bytes, 2);
+
+ if (Bytes.Length > 6)
+ data = BitConverterExt.GetElements(Bytes, 6, Bytes.Length - 6);
+ }
+
+ ///
+ /// Each TAG is a value in the range of 0 to 65535. Values from 0 - 32767 are available for developer use,
+ /// while values from 32768 - 65535 are reserved for Truevision.
+ ///
+ public ushort Tag
+ {
+ get { return fieldTag; }
+ set { fieldTag = value; }
+ }
+
+ ///
+ /// This OFFSET is a number of bytes from the beginning of the file to the start of the field
+ /// referenced by the tag.
+ ///
+ public uint Offset
+ {
+ get { return fieldFileOffset; }
+ set { fieldFileOffset = value; }
+ }
+
+ ///
+ /// Field DATA.
+ /// Although the size and format of the actual Developer Area fields are totally up to the developer,
+ /// please define your formats to address future considerations you might have concerning your fields.
+ /// This means that if you anticipate changing a field, build flexibility into the format to make these
+ /// changes easy on other developers.Major changes to an existing TAG’s definition should never happen.
+ ///
+ public byte[] Data
+ {
+ get { return data; }
+ set { data = value; }
+ }
+
+ ///
+ /// The FIELD SIZE is a number of bytes in the field. Same like: ,
+ /// if is null, return -1.
+ ///
+ public int FieldSize
+ {
+ get
+ {
+ if (Data == null)
+ return -1;
+
+ return Data.Length;
+ }
+ }
+
+ ///
+ /// Gets TGA size in bytes (Always constant and equal 10!).
+ /// It is not ! It is just size of entry sizeof(ushort + uint + uint).
+ ///
+ public const int Size = 10;
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Copy of
+ public TgaDevEntry Clone()
+ {
+ return new TgaDevEntry(this);
+ }
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Copy of
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaDevEntry) ? Equals((TgaDevEntry)obj) : false);
+ }
+
+ public bool Equals(TgaDevEntry item)
+ {
+ return (fieldTag == item.fieldTag &&
+ fieldFileOffset == item.fieldFileOffset &&
+ BitConverterExt.IsArraysEqual(data, item.data));
+ }
+
+ public static bool operator ==(TgaDevEntry item1, TgaDevEntry item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaDevEntry item1, TgaDevEntry item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 17;
+ hash = hash * 23 + fieldTag.GetHashCode();
+ hash = hash * 23 + fieldFileOffset.GetHashCode();
+
+ if (data != null)
+ for (int i = 0; i < data.Length; i++)
+ hash = hash * 23 + data[i].GetHashCode();
+
+ return hash;
+ }
+ }
+
+ ///
+ /// Gets like string.
+ ///
+ /// String in "Tag={0}, Offset={1}, FieldSize={2}" format.
+ public override string ToString()
+ {
+ return String.Format("{0}={1}, {1}={2}, {3}={4}", nameof(Tag), fieldTag,
+ nameof(Offset), fieldFileOffset, nameof(FieldSize), FieldSize);
+ }
+
+ ///
+ /// Convert to byte array. (Not include !).
+ ///
+ /// Byte array with length = 10.
+ public byte[] ToBytes()
+ {
+ return BitConverterExt.ToBytes(fieldTag, fieldFileOffset, (data == null ? 0 : data.Length));
+ }
+ } //Not full ToBytes()
+
+ public class TgaFraction : ICloneable
+ {
+ ushort numerator = 0;
+ ushort denominator = 0;
+
+ ///
+ /// Make from and .
+ ///
+ /// Numerator value.
+ /// Denominator value.
+ public TgaFraction(ushort Numerator = 0, ushort Denominator = 0)
+ {
+ numerator = Numerator;
+ denominator = Denominator;
+ }
+
+ ///
+ /// Make from bytes.
+ ///
+ /// Array of bytes(byte[4]).
+ public TgaFraction(byte[] Bytes)
+ {
+ if (Bytes == null)
+ throw new ArgumentNullException(nameof(Bytes) + " = null!");
+ if (Bytes.Length != Size)
+ throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!");
+
+ numerator = BitConverter.ToUInt16(Bytes, 0);
+ denominator = BitConverter.ToUInt16(Bytes, 2);
+ }
+
+ ///
+ /// Gets or sets numerator value.
+ ///
+ public ushort Numerator
+ {
+ get { return numerator; }
+ set { numerator = value; }
+ }
+
+ ///
+ /// Gets or sets denominator value.
+ ///
+ public ushort Denominator
+ {
+ get { return denominator; }
+ set { denominator = value; }
+ }
+
+ ///
+ /// Get aspect ratio = / .
+ ///
+ public float AspectRatio
+ {
+ get
+ {
+ if (numerator == denominator)
+ return 1f;
+
+ return numerator / (float)denominator;
+ }
+ }
+
+ ///
+ /// Gets Empty , all values are 0.
+ ///
+ public static readonly TgaFraction Empty = new TgaFraction();
+
+ ///
+ /// Gets One , all values are 1 (ones, 1 / 1 = 1).
+ ///
+ public static readonly TgaFraction One = new TgaFraction(1, 1);
+
+ ///
+ /// Gets TGA Field size in bytes.
+ ///
+ public const int Size = 4;
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Copy of
+ public TgaFraction Clone()
+ {
+ return new TgaFraction(numerator, denominator);
+ }
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Copy of
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaFraction) ? Equals((TgaFraction)obj) : false);
+ }
+
+ public bool Equals(TgaFraction item)
+ {
+ return (numerator == item.numerator && denominator == item.denominator);
+ }
+
+ public static bool operator ==(TgaFraction item1, TgaFraction item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaFraction item1, TgaFraction item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public override int GetHashCode()
+ {
+ return (numerator << 16 | denominator).GetHashCode();
+ }
+
+ ///
+ /// Gets like string.
+ ///
+ /// String in "Numerator=1, Denominator=2" format.
+ public override string ToString()
+ {
+ return String.Format("{0}={1}, {2}={3}", nameof(Numerator), numerator,
+ nameof(Denominator), denominator);
+ }
+
+ ///
+ /// Convert to byte array.
+ ///
+ /// Byte array with length = 4.
+ public byte[] ToBytes()
+ {
+ return BitConverterExt.ToBytes(numerator, denominator);
+ }
+ }
+
+ ///
+ /// Contains image origin bits and alpha channel bits(or number of overlay bits)
+ ///
+ public class TgaImageDescriptor : ICloneable
+ {
+ TgaImgOrigin imageOrigin = 0; //bits 5-4
+ byte alphaChannelBits = 0; //bits 3-0
+
+ ///
+ /// Make empty .
+ ///
+ public TgaImageDescriptor()
+ {
+ }
+
+ ///
+ /// Make from bytes.
+ ///
+ /// ImageDescriptor byte with reserved 7-6 bits, bits 5-4 used for
+ /// , 3-0 used as alpha channel bits or number of overlay bits.
+ public TgaImageDescriptor(byte b)
+ {
+ imageOrigin = (TgaImgOrigin)((b & 0x30) >> 4);
+ alphaChannelBits = (byte)(b & 0x0F);
+ }
+
+ ///
+ /// Gets or Sets Image Origin bits (select from enum only, don'n use 5-4 bits!).
+ ///
+ public TgaImgOrigin ImageOrigin
+ {
+ get { return imageOrigin; }
+ set { imageOrigin = value; }
+ }
+
+ ///
+ /// Gets or Sets alpha channel bits or number of overlay bits.
+ ///
+ public byte AlphaChannelBits
+ {
+ get { return alphaChannelBits; }
+ set { alphaChannelBits = value; }
+ }
+
+ ///
+ /// Gets TGA Field size in bytes.
+ ///
+ public const int Size = 1;
+
+ ///
+ /// Make full copy of .
+ ///
+ /// Full independent copy of .
+ public TgaImageDescriptor Clone()
+ {
+ return new TgaImageDescriptor(ToByte());
+ }
+
+ ///
+ /// Make full copy of .
+ ///
+ /// Full independent copy of .
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaImageDescriptor) ? Equals((TgaImageDescriptor)obj) : false);
+ }
+
+ public bool Equals(TgaImageDescriptor item)
+ {
+ return (imageOrigin == item.imageOrigin && alphaChannelBits == item.alphaChannelBits);
+ }
+
+ public static bool operator ==(TgaImageDescriptor item1, TgaImageDescriptor item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaImageDescriptor item1, TgaImageDescriptor item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return ((int)ImageOrigin << 4 | alphaChannelBits).GetHashCode();
+ }
+ }
+
+ public override string ToString()
+ {
+ return String.Format("{0}={1}, {2}={3}, ImageDescriptor_AsByte={4}", nameof(ImageOrigin),
+ imageOrigin, nameof(AlphaChannelBits), alphaChannelBits, ToByte());
+ }
+
+ ///
+ /// Gets ImageDescriptor byte.
+ ///
+ /// ImageDescriptor byte with reserved 7-6 bits, bits 5-4 used for imageOrigin,
+ /// 3-0 used as alpha channel bits or number of overlay bits.
+ public byte ToByte()
+ {
+ return (byte)(((int)imageOrigin << 4) | alphaChannelBits);
+ }
+ }
+
+ ///
+ /// Image Specification - Field 5 (10 bytes):
+ /// This field and its sub-fields describe the image screen location, size and pixel depth.
+ /// These information is always written to the file.
+ ///
+ public class TgaImageSpec : ICloneable
+ {
+ ushort x_Origin = 0;
+ ushort y_Origin = 0;
+ ushort imageWidth = 0;
+ ushort imageHeight = 0;
+ TgaPixelDepth pixelDepth = TgaPixelDepth.Other;
+ TgaImageDescriptor imageDescriptor = new TgaImageDescriptor();
+
+ public TgaImageSpec()
+ {
+ }
+
+ ///
+ /// Make ImageSpec from values.
+ ///
+ /// These specify 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).
+ /// These 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).
+ /// This field specifies the width of the image in pixels.
+ /// This field specifies the height of the image in pixels.
+ /// This field indicates the number of bits per pixel. This number
+ /// includes the Attribute or Alpha channel bits. Common values are 8, 16, 24 and 32 but
+ /// other pixel depths could be used.
+ /// Contains image origin bits and alpha channel bits
+ /// (or number of overlay bits).
+ public TgaImageSpec(ushort X_Origin, ushort Y_Origin, ushort ImageWidth, ushort ImageHeight,
+ TgaPixelDepth PixelDepth, TgaImageDescriptor ImageDescriptor)
+ {
+ x_Origin = X_Origin;
+ y_Origin = Y_Origin;
+ imageWidth = ImageWidth;
+ imageHeight = ImageHeight;
+ pixelDepth = PixelDepth;
+ imageDescriptor = ImageDescriptor;
+ }
+
+ ///
+ /// Make ImageSpec from bytes.
+ ///
+ /// Array of bytes(byte[10]).
+ public TgaImageSpec(byte[] Bytes)
+ {
+ if (Bytes == null)
+ throw new ArgumentNullException(nameof(Bytes) + " = null!");
+ if (Bytes.Length != Size)
+ throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!");
+
+ x_Origin = BitConverter.ToUInt16(Bytes, 0);
+ y_Origin = BitConverter.ToUInt16(Bytes, 2);
+ imageWidth = BitConverter.ToUInt16(Bytes, 4);
+ imageHeight = BitConverter.ToUInt16(Bytes, 6);
+ pixelDepth = (TgaPixelDepth)Bytes[8];
+ imageDescriptor = new TgaImageDescriptor(Bytes[9]);
+ }
+
+ ///
+ /// These specify 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 ushort X_Origin
+ {
+ get { return x_Origin; }
+ set { x_Origin = value; }
+ }
+
+ ///
+ /// These 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 ushort Y_Origin
+ {
+ get { return y_Origin; }
+ set { y_Origin = value; }
+ }
+
+ ///
+ /// This field specifies the width of the image in pixels.
+ ///
+ public ushort ImageWidth
+ {
+ get { return imageWidth; }
+ set { imageWidth = value; }
+ }
+
+ ///
+ /// This field specifies the height of the image in pixels.
+ ///
+ public ushort ImageHeight
+ {
+ get { return imageHeight; }
+ set { imageHeight = value; }
+ }
+
+ ///
+ /// This field indicates the number of bits per pixel. This number includes the Attribute or
+ /// Alpha channel bits. Common values are 8, 16, 24 and 32 but other pixel depths could be used.
+ ///
+ public TgaPixelDepth PixelDepth
+ {
+ get { return pixelDepth; }
+ set { pixelDepth = value; }
+ }
+
+ ///
+ /// Contains image origin bits and alpha channel bits(or number of overlay bits).
+ ///
+ public TgaImageDescriptor ImageDescriptor
+ {
+ get { return imageDescriptor; }
+ set { imageDescriptor = value; }
+ }
+
+ ///
+ /// Gets TGA Field size in bytes.
+ ///
+ public const int Size = 10;
+
+ ///
+ /// Make full copy of .
+ ///
+ ///
+ public TgaImageSpec Clone()
+ {
+ return new TgaImageSpec(ToBytes());
+ }
+
+ ///
+ /// Make full copy of .
+ ///
+ ///
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaImageSpec) ? Equals((TgaImageSpec)obj) : false);
+ }
+
+ public bool Equals(TgaImageSpec item)
+ {
+ return (
+ x_Origin == item.x_Origin &&
+ y_Origin == item.y_Origin &&
+ imageWidth == item.imageWidth &&
+ imageHeight == item.imageHeight &&
+ pixelDepth == item.pixelDepth &&
+ imageDescriptor == item.imageDescriptor);
+ }
+
+ public static bool operator ==(TgaImageSpec item1, TgaImageSpec item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaImageSpec item1, TgaImageSpec item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 17;
+ hash = hash * 23 + x_Origin.GetHashCode();
+ hash = hash * 23 + y_Origin.GetHashCode();
+ hash = hash * 23 + imageWidth.GetHashCode();
+ hash = hash * 23 + imageHeight.GetHashCode();
+ hash = hash * 23 + pixelDepth.GetHashCode();
+
+ if (imageDescriptor != null)
+ hash = hash * 23 + imageDescriptor.GetHashCode();
+
+ return hash;
+ }
+ }
+
+ public override string ToString()
+ {
+ return String.Format("{0}={1}, {2}={3}, {4}={5}, {6}={7}, {8}={9}, {10}={11}",
+ nameof(X_Origin), x_Origin,
+ nameof(Y_Origin), y_Origin,
+ nameof(ImageWidth), imageWidth,
+ nameof(ImageHeight), imageHeight,
+ nameof(PixelDepth), pixelDepth,
+ nameof(ImageDescriptor), imageDescriptor);
+ }
+
+ ///
+ /// Convert to byte array.
+ ///
+ /// Byte array with length = 10.
+ public byte[] ToBytes()
+ {
+ return BitConverterExt.ToBytes(x_Origin, y_Origin, imageWidth, imageHeight,
+ (byte)pixelDepth, (imageDescriptor == null ? byte.MinValue : imageDescriptor.ToByte()));
+ }
+ }
+
+ ///
+ /// Postage Stamp Image (MaxSize 64x64, uncompressed, PixelDepth like in full image).
+ ///
+ public class TgaPostageStampImage : ICloneable
+ {
+ byte width = 0;
+ byte height = 0;
+ byte[] data = null;
+
+ public TgaPostageStampImage()
+ {
+ }
+
+ ///
+ /// Make from bytes array.
+ ///
+ /// Bytes array, first 2 bytes are and ,
+ /// next bytes - image data.
+ public TgaPostageStampImage(byte[] Bytes)
+ {
+ if (Bytes == null)
+ throw new ArgumentNullException(nameof(Bytes) + " = null!");
+ if (Bytes.Length < 2)
+ throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be >= " + 2 + "!");
+
+ width = Bytes[0];
+ height = Bytes[1];
+
+ if (Bytes.Length > 2)
+ data = BitConverterExt.GetElements(Bytes, 2, Bytes.Length - 2);
+ }
+
+ ///
+ /// Make from bytes and size.
+ ///
+ /// Image Width.
+ /// Image Height.
+ /// Postage Stamp Image Data.
+ public TgaPostageStampImage(byte Width, byte Height, byte[] Bytes)
+ {
+ if (Bytes == null)
+ throw new ArgumentNullException(nameof(Bytes) + " = null!");
+
+ width = Width;
+ height = Height;
+ data = Bytes;
+ }
+
+ ///
+ /// Postage Stamp Image Data
+ ///
+ public byte[] Data
+ {
+ get { return data; }
+ set { data = value; }
+ }
+
+ ///
+ /// Postage Stamp Image Width (maximum = 64).
+ ///
+ public byte Width
+ {
+ get { return width; }
+ set { width = value; }
+ }
+
+ ///
+ /// Postage Stamp Image Height (maximum = 64).
+ ///
+ public byte Height
+ {
+ get { return height; }
+ set { height = value; }
+ }
+
+ ///
+ /// Make full copy of .
+ ///
+ /// Full independent copy of .
+ public TgaPostageStampImage Clone()
+ {
+ return new TgaPostageStampImage(width, height, BitConverterExt.ToBytes(data));
+ }
+
+ ///
+ /// Make full copy of .
+ ///
+ /// Full independent copy of .
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaPostageStampImage) ? Equals((TgaPostageStampImage)obj) : false);
+ }
+
+ public bool Equals(TgaPostageStampImage item)
+ {
+ return width == item.width && height == item.height && BitConverterExt.IsArraysEqual(data, item.data);
+ }
+
+ public static bool operator ==(TgaPostageStampImage item1, TgaPostageStampImage item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaPostageStampImage item1, TgaPostageStampImage item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 27;
+ hash = (13 * hash) + width.GetHashCode();
+ hash = (13 * hash) + height.GetHashCode();
+ if (data != null)
+ for (int i = 0; i < data.Length; i++)
+ hash = (13 * hash) + data[i].GetHashCode();
+ return hash;
+ }
+ }
+
+ public override string ToString()
+ {
+ return String.Format("{0}={1}, {2}={3}, DataLength={4}",
+ nameof(Width), width, nameof(Height), height, (data == null ? -1 : data.Length));
+ }
+
+ ///
+ /// Convert to byte array.
+ ///
+ /// Byte array.
+ public byte[] ToBytes()
+ {
+ return BitConverterExt.ToBytes(width, height, data);
+ }
+ }
+
+ public class TgaSoftVersion : ICloneable
+ {
+ ushort versionNumber = 0;
+ char versionLetter = ' ';
+
+ ///
+ /// Gets Empty , = ' ' (space).
+ ///
+ public TgaSoftVersion()
+ {
+ }
+
+ ///
+ /// Make from string.
+ ///
+ /// Input string, example: "123d".
+ public TgaSoftVersion(string Str)
+ {
+ if (Str == null)
+ throw new ArgumentNullException();
+ if (Str.Length < 3 || Str.Length > 4)
+ throw new ArgumentOutOfRangeException(nameof(Str.Length) + " must be equal 3 or 4!");
+
+ bool Res = ushort.TryParse(Str.Substring(0, 3), out versionNumber);
+ if (Res && Str.Length == 4)
+ versionLetter = Str[3];
+ }
+
+ ///
+ /// Make from bytes.
+ ///
+ /// Bytes array (byte[3]).
+ public TgaSoftVersion(byte[] Bytes)
+ {
+ if (Bytes == null)
+ throw new ArgumentNullException(nameof(Bytes) + " = null!");
+ if (Bytes.Length != Size)
+ throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!");
+
+ versionNumber = BitConverter.ToUInt16(Bytes, 0);
+ versionLetter = Encoding.ASCII.GetString(Bytes, 2, 1)[0];
+ }
+
+ public TgaSoftVersion(ushort VersionNumber, char VersionLetter = ' ')
+ {
+ versionNumber = VersionNumber;
+ versionLetter = VersionLetter;
+ }
+
+ public ushort VersionNumber
+ {
+ get { return versionNumber; }
+ set { versionNumber = value; }
+ }
+
+ public char VersionLetter
+ {
+ get { return versionLetter; }
+ set { versionLetter = value; }
+ }
+
+ ///
+ /// Gets TGA Field size in bytes.
+ ///
+ public const int Size = 3;
+
+ ///
+ /// Make full copy of .
+ ///
+ ///
+ public TgaSoftVersion Clone()
+ {
+ return new TgaSoftVersion(versionNumber, versionLetter);
+ }
+
+ ///
+ /// Make full copy of .
+ ///
+ ///
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaSoftVersion) ? Equals((TgaSoftVersion)obj) : false);
+ }
+
+ public bool Equals(TgaSoftVersion item)
+ {
+ return (versionNumber == item.versionNumber && versionLetter == item.versionLetter);
+ }
+
+ public static bool operator ==(TgaSoftVersion item1, TgaSoftVersion item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaSoftVersion item1, TgaSoftVersion item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public override int GetHashCode()
+ {
+ return versionNumber.GetHashCode() ^ versionLetter.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return (versionNumber.ToString("000") + versionLetter).TrimEnd(new char[] { ' ', '\0' });
+ }
+
+ ///
+ /// Convert to byte array.
+ ///
+ /// Byte array, (2 bytes) and
+ /// (ASCII symbol).
+ public byte[] ToBytes()
+ {
+ return ToBytes(versionNumber, versionLetter);
+ }
+
+ ///
+ /// Convert to byte array.
+ ///
+ /// Set 123 for 1.23 version.
+ /// Version letter, example: for 'a' - "1.23a".
+ /// Byte array, (2 bytes) and (ASCII symbol).
+ public static byte[] ToBytes(ushort VersionNumber, char VersionLetter = ' ')
+ {
+ return BitConverterExt.ToBytes(VersionNumber, Encoding.ASCII.GetBytes(VersionLetter.ToString()));
+ }
+ }
+
+ ///
+ /// Use it for working with ASCII strings in TGA files.
+ ///
+ public class TgaString : ICloneable
+ {
+ public const string XFileSignatuteConst = "TRUEVISION-XFILE";
+ public const string DotSymbolConst = ".";
+
+ string origString = String.Empty;
+ int length = 0;
+ char blankSpaceChar = DefaultBlankSpaceChar;
+ bool useEnding = false;
+
+ public TgaString(bool UseEnding = false)
+ {
+ useEnding = UseEnding;
+ }
+
+ public TgaString(byte[] Bytes, bool UseEnding = false)
+ {
+ if (Bytes == null)
+ throw new ArgumentNullException(nameof(Bytes) + " = null!");
+
+ length = Bytes.Length;
+ useEnding = UseEnding;
+ string s = Encoding.ASCII.GetString(Bytes, 0, Bytes.Length - (useEnding ? 1 : 0));
+
+ if (s.Length > 0)
+ switch (s[s.Length - 1])
+ {
+ case '\0':
+ case ' ':
+ blankSpaceChar = s[s.Length - 1];
+ origString = s.TrimEnd(new char[] { s[s.Length - 1] });
+ break;
+ default:
+ origString = s;
+ break;
+ }
+ }
+
+ public TgaString(int Length, bool UseEnding = false)
+ {
+ length = Length;
+ useEnding = UseEnding;
+ }
+
+ public TgaString(string Str, int Length, bool UseEnding = false, char BlankSpaceChar = '\0')
+ {
+ if (Str == null)
+ throw new ArgumentNullException(nameof(Str) + " = null!");
+
+ origString = Str;
+ length = Length;
+ blankSpaceChar = BlankSpaceChar;
+ useEnding = UseEnding;
+ }
+
+ public string OriginalString
+ {
+ get { return origString; }
+ set { origString = value; }
+ }
+
+ public int Length
+ {
+ get { return length; }
+ set { length = value; }
+ }
+
+ public char BlankSpaceChar
+ {
+ get { return blankSpaceChar; }
+ set { blankSpaceChar = value; }
+ }
+
+ public bool UseEndingChar
+ {
+ get { return useEnding; }
+ set { useEnding = value; }
+ }
+
+ ///
+ /// Gets ending char, default '\0'.
+ ///
+ public static readonly char DefaultEndingChar = '\0';
+
+ ///
+ /// Gets blank space char, value = '\0'.
+ ///
+ public static readonly char DefaultBlankSpaceChar = '\0';
+
+ ///
+ /// Gets Empty .
+ ///
+ public static readonly TgaString Empty = new TgaString();
+
+ ///
+ /// Gets with = '\0' and = true.
+ ///
+ public static readonly TgaString ZeroTerminator = new TgaString(true);
+
+ ///
+ /// Gets "." with dot (period) symbol.
+ ///
+ public static readonly TgaString DotSymbol = new TgaString(DotSymbolConst, DotSymbolConst.Length);
+
+ ///
+ /// Gets "TRUEVISION-XFILE" (TGA File Format Version 2.0 signatute).
+ ///
+ public static readonly TgaString XFileSignatute = new TgaString(XFileSignatuteConst, XFileSignatuteConst.Length);
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Copy of
+ public TgaString Clone()
+ {
+ return new TgaString(origString, length, useEnding, blankSpaceChar);
+ }
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Copy of
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaString) ? Equals((TgaString)obj) : false);
+ }
+
+ public bool Equals(TgaString item)
+ {
+ return (
+ origString == item.origString &&
+ length == item.length &&
+ blankSpaceChar == item.blankSpaceChar &&
+ useEnding == item.useEnding);
+ }
+
+ public static bool operator ==(TgaString item1, TgaString item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaString item1, TgaString item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public static TgaString operator +(TgaString item1, TgaString item2)
+ {
+ if (ReferenceEquals(item1, null) || ReferenceEquals(item2, null))
+ throw new ArgumentNullException();
+
+ return new TgaString(BitConverterExt.ToBytes(item1.ToBytes(), item2.ToBytes()));
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 17;
+ hash = hash * 23 + origString.GetHashCode();
+ hash = hash * 23 + length.GetHashCode();
+ hash = hash * 23 + blankSpaceChar.GetHashCode();
+ hash = hash * 23 + useEnding.GetHashCode();
+ return hash;
+ }
+ }
+
+ ///
+ /// Get ASCII-Like string with string-terminators, example: "Some string\0\0\0\0\0".
+ ///
+ /// String with replaced string-terminators to "\0".
+ public override string ToString()
+ {
+ return Encoding.ASCII.GetString(ToBytes()).Replace("\0", @"\0");
+ }
+
+ ///
+ /// Get ASCII-Like string to first string-terminator, example:
+ /// "Some string \0 Some Data \0" - > "Some string".
+ ///
+ /// String to first string-terminator.
+ public string GetString()
+ {
+ String Str = Encoding.ASCII.GetString(ToBytes());
+ int EndIndex = Str.IndexOf('\0');
+ if (EndIndex != -1)
+ Str = Str.Substring(0, EndIndex);
+ return Str;
+ }
+
+ ///
+ /// Convert to byte array.
+ ///
+ /// Byte array, every byte is ASCII symbol.
+ public byte[] ToBytes()
+ {
+ return ToBytes(origString, length, useEnding, blankSpaceChar);
+ }
+
+ ///
+ /// Convert to byte array.
+ ///
+ /// Input string.
+ /// Length of output ASCII string with Ending char (if used).
+ /// Add to string or not?
+ /// Char for filling blank space in string. If this char is '-' (only for example!),
+ /// for string "ABC" with = 7, with = true,
+ /// is '\0', result string is "ABC---\0".
+ /// Byte array, every byte is ASCII symbol.
+ public static byte[] ToBytes(string str, int Length, bool UseEnding = true, char BlankSpaceChar = '\0')
+ {
+ char[] C = new char[Math.Max(Length, (UseEnding ? 1 : 0))];
+
+ for (int i = 0; i < C.Length; i++)
+ C[i] = (i < str.Length ? str[i] : BlankSpaceChar);
+
+ if (UseEnding)
+ C[C.Length - 1] = DefaultEndingChar;
+
+ return Encoding.ASCII.GetBytes(C);
+ }
+ }
+
+ public class TgaTime : ICloneable
+ {
+ ushort hours = 0;
+ ushort minutes = 0;
+ ushort seconds = 0;
+
+ ///
+ /// Make empty .
+ ///
+ public TgaTime()
+ {
+ }
+
+ ///
+ /// Make from .
+ ///
+ /// Some variable.
+ public TgaTime(TimeSpan Time)
+ {
+ hours = (ushort)Time.TotalHours;
+ minutes = (ushort)Time.Minutes;
+ seconds = (ushort)Time.Seconds;
+ }
+
+ ///
+ /// Make from ushort values.
+ ///
+ /// Hour (0 - 65535).
+ /// Minute (0 - 59).
+ /// Second (0 - 59).
+ public TgaTime(ushort Hours, ushort Minutes, ushort Seconds)
+ {
+ hours = Hours;
+ minutes = Minutes;
+ seconds = Seconds;
+ }
+
+ ///
+ /// Make from bytes.
+ ///
+ /// Array of bytes(byte[6]).
+ public TgaTime(byte[] Bytes)
+ {
+ if (Bytes == null)
+ throw new ArgumentNullException(nameof(Bytes) + " = null!");
+ else if (Bytes.Length != Size)
+ throw new ArgumentOutOfRangeException(nameof(Bytes) + " must be equal " + Size + "!");
+
+ hours = BitConverter.ToUInt16(Bytes, 0);
+ minutes = BitConverter.ToUInt16(Bytes, 2);
+ seconds = BitConverter.ToUInt16(Bytes, 4);
+ }
+
+ ///
+ /// Gets or Sets hour (0 - 65535).
+ ///
+ public ushort Hours
+ {
+ get { return hours; }
+ set { hours = value; }
+ }
+
+ ///
+ /// Gets or Sets minute (0 - 59).
+ ///
+ public ushort Minutes
+ {
+ get { return minutes; }
+ set { minutes = value; }
+ }
+
+ ///
+ /// Gets or Sets second (0 - 59).
+ ///
+ public ushort Seconds
+ {
+ get { return seconds; }
+ set { seconds = value; }
+ }
+
+ ///
+ /// Gets TGA Field size in bytes.
+ ///
+ public const int Size = 6;
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Copy of
+ public TgaTime Clone()
+ {
+ return new TgaTime(hours, minutes, seconds);
+ }
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Copy of
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaTime) ? Equals((TgaTime)obj) : false);
+ }
+
+ public bool Equals(TgaTime item)
+ {
+ return (hours == item.hours && minutes == item.minutes && seconds == item.seconds);
+ }
+
+ public static bool operator ==(TgaTime item1, TgaTime item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaTime item1, TgaTime item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 17;
+ hash = hash * 23 + hours.GetHashCode();
+ hash = hash * 23 + (minutes << 16 | seconds).GetHashCode();
+ return hash;
+ }
+ }
+
+ ///
+ /// Gets like string.
+ ///
+ /// String in "H:M:S" format.
+ public override string ToString()
+ {
+ return String.Format("{0}:{1}:{2}", hours, minutes, seconds);
+ }
+
+ ///
+ /// Convert to byte array.
+ ///
+ /// Byte array with length = 6.
+ public byte[] ToBytes()
+ {
+ return BitConverterExt.ToBytes(hours, minutes, seconds);
+ }
+
+ ///
+ /// Gets like .
+ ///
+ /// value of .
+ public TimeSpan ToTimeSpan()
+ {
+ return new TimeSpan(hours, minutes, seconds);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// File Header Area (18 bytes)
+ ///
+ public class TgaHeader : ICloneable
+ {
+ byte idLength = 0;
+ TgaColorMapType colorMapType = TgaColorMapType.NoColorMap;
+ TgaImageType imageType = TgaImageType.NoImageData;
+ TgaColorMapSpec colorMapSpec = new TgaColorMapSpec();
+ TgaImageSpec imageSpec = new TgaImageSpec();
+
+ ///
+ /// Make empty .
+ ///
+ public TgaHeader()
+ {
+ }
+
+ ///
+ /// Make from bytes.
+ ///
+ /// Bytes array (byte[18]).
+ public TgaHeader(byte[] Bytes)
+ {
+ if (Bytes == null)
+ throw new ArgumentNullException(nameof(Bytes) + " = null!");
+ if (Bytes.Length != Size)
+ throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!");
+
+ idLength = Bytes[0];
+ colorMapType = (TgaColorMapType)Bytes[1];
+ imageType = (TgaImageType)Bytes[2];
+ colorMapSpec = new TgaColorMapSpec(BitConverterExt.GetElements(Bytes, 3, TgaColorMapSpec.Size));
+ imageSpec = new TgaImageSpec(BitConverterExt.GetElements(Bytes, 8, TgaImageSpec.Size));
+ }
+
+ ///
+ /// ID Length - Field 1 (1 byte):
+ /// This field identifies the number of bytes contained in the Field.
+ /// The maximum number of characters is 255. A value of zero indicates that no Image ID
+ /// field is included with the image.
+ ///
+ public byte IDLength
+ {
+ get { return idLength; }
+ set { idLength = value; }
+ }
+
+ ///
+ /// Color Map Type - Field 2 (1 byte):
+ /// This field indicates the type of color map (if any) included with the image.
+ /// There are currently 2 defined values for this field:
+ /// 0 - indicates that no color-map data is included with this image;
+ /// 1 - indicates that a color-map is included with this image.
+ ///
+ public TgaColorMapType ColorMapType
+ {
+ get { return colorMapType; }
+ set { colorMapType = value; }
+ }
+
+ ///
+ /// Image Type - Field 3 (1 byte):
+ /// The TGA File Format can be used to store Pseudo-Color, True-Color and Direct-Color images
+ /// of various pixel depths.
+ ///
+ public TgaImageType ImageType
+ {
+ get { return imageType; }
+ set { imageType = value; }
+ }
+
+ ///
+ /// Color Map Specification - Field 4 (5 bytes):
+ /// This field and its sub-fields describe the color map (if any) used for the image.
+ /// If the Color Map Type field is set to zero, indicating that no color map exists, then
+ /// these 5 bytes should be set to zero. These bytes always must be written to the file.
+ ///
+ public TgaColorMapSpec ColorMapSpec
+ {
+ get { return colorMapSpec; }
+ set { colorMapSpec = value; }
+ }
+
+ ///
+ /// Image Specification - Field 5 (10 bytes):
+ /// This field and its sub-fields describe the image screen location, size and pixel depth.
+ /// These information is always written to the file.
+ ///
+ public TgaImageSpec ImageSpec
+ {
+ get { return imageSpec; }
+ set { imageSpec = value; }
+ }
+
+ ///
+ /// Gets TGA Header Section size in bytes.
+ ///
+ public const int Size = 18;
+
+ ///
+ /// Make full copy of .
+ ///
+ /// Full independent copy of .
+ public TgaHeader Clone()
+ {
+ return new TgaHeader(ToBytes());
+ }
+
+ ///
+ /// Make full copy of .
+ ///
+ /// Full independent copy of .
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaHeader) ? Equals((TgaHeader)obj) : false);
+ }
+
+ public bool Equals(TgaHeader item)
+ {
+ return (idLength == item.idLength &&
+ colorMapType == item.colorMapType &&
+ imageType == item.imageType &&
+ colorMapSpec == item.colorMapSpec &&
+ imageSpec == item.imageSpec);
+ }
+
+ public static bool operator ==(TgaHeader item1, TgaHeader item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaHeader item1, TgaHeader item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 17;
+ hash = hash * 23 + (idLength << 24 | (byte)colorMapType << 8 | (byte)imageType).GetHashCode();
+
+ if (colorMapSpec != null)
+ hash = hash * 23 + colorMapSpec.GetHashCode();
+
+ if (imageSpec != null)
+ hash = hash * 23 + imageSpec.GetHashCode();
+
+ return hash;
+ }
+ }
+
+ public override string ToString()
+ {
+ return String.Format("{0}={1}, {2}={3}, {4}={5}, {6}={7}, {8}={9}",
+ nameof(IDLength), idLength,
+ nameof(ColorMapType), colorMapType,
+ nameof(ImageType), imageType,
+ nameof(ColorMapSpec), colorMapSpec,
+ nameof(ImageSpec), imageSpec);
+ }
+
+ ///
+ /// Convert to byte array.
+ ///
+ /// Byte array with size equal .
+ public byte[] ToBytes()
+ {
+ return BitConverterExt.ToBytes(idLength, (byte)colorMapType, (byte)imageType,
+ (colorMapSpec == null ? new byte[TgaColorMapSpec.Size] : colorMapSpec.ToBytes()),
+ (imageSpec == null ? new byte[TgaImageSpec.Size] : imageSpec.ToBytes()));
+ }
+ }
+
+ ///
+ /// Image Or ColorMap Area
+ ///
+ public class TgaImgOrColMap : ICloneable
+ {
+ TgaString imageID = null;
+ byte[] colorMapData = null;
+ byte[] imageData = null;
+
+ ///
+ /// Make empty .
+ ///
+ public TgaImgOrColMap()
+ {
+ }
+
+ ///
+ /// Make from arrays.
+ ///
+ /// This optional field contains identifying information about the image.
+ /// The maximum length for this field is 255 bytes. Refer to
+ /// for the length of this field. If field 1 is set to Zero indicating that no Image ID exists
+ /// then these bytes are not written to the file.
+ /// Color Map Data, see description.
+ /// Image Data, see description.
+ public TgaImgOrColMap(TgaString ImageID, byte[] ColorMapData, byte[] ImageData)
+ {
+ imageID = ImageID;
+ colorMapData = ColorMapData;
+ imageData = ImageData;
+ }
+
+ ///
+ /// Image ID - Field 6 (variable):
+ /// This optional field contains identifying information about the image. The maximum length
+ /// for this field is 255 bytes. Refer to for the length of this
+ /// field. If field 1 is set to Zero indicating that no Image ID exists then these bytes are not
+ /// written to the file. Can have text inside (ASCII).
+ ///
+ public TgaString ImageID
+ {
+ get { return imageID; }
+ set { imageID = value; }
+ }
+
+ ///
+ /// Color Map Data - Field 7 (variable):
+ /// If the Color Map Type(field 2) field is set to zero indicating that no Color-Map
+ /// exists then this field will not be present (i.e., no bytes written to the file).
+ /// This variable-length field contains the actual color map information (LUT data).
+ /// Field 4.3 specifies the width in bits of each color map entry while Field 4.2 specifies
+ /// the number of color map entries in this field. These two fields together are used to
+ /// determine the number of bytes contained in field 7.
+ /// Each color map entry is stored using an integral number of bytes.The RGB specification
+ /// for each color map entry is stored in successive bit-fields in the multi-byte entries.
+ /// Each color bit-field is assumed to be MIN(Field4.3/3, 8) bits in length. If Field 4.3
+ /// contains 24, then each color specification is 8 bits in length; if Field 4.3 contains 32,
+ /// then each color specification is also 8 bits (32/3 gives 10, but 8 is smaller).
+ /// Unused bit(s) in the multi-byte entries are assumed to specify attribute bits. The
+ /// attribute bit field is often called the Alpha Channel, Overlay Bit(s) or Interrupt Bit(s).
+ /// For the TARGA M-8, ATVista and NuVista, the number of bits in a color map specification is
+ /// 24 (or 32). The red, green, and blue components are each represented by one byte.
+ ///
+ public byte[] ColorMapData
+ {
+ get { return colorMapData; }
+ set { colorMapData = value; }
+ }
+
+ ///
+ /// Image Data - Field 8 (variable):
+ /// This field contains (Width)x(Height) pixels. Each pixel specifies image data in one
+ /// of the following formats:
+ /// a single color-map index for Pseudo-Color;
+ /// Attribute, Red, Green and Blue ordered data for True-Color;
+ /// and independent color-map indices for Direct-Color.
+ /// The values for Width and Height are specified in Fields 5.3 and 5.4 respectively.
+ /// The number of attribute and color-definition bits for each pixel are defined in Fields 5.6
+ /// and 5.5, respectively.Each pixel is stored as an integral number of bytes.
+ ///
+ public byte[] ImageData
+ {
+ get { return imageData; }
+ set { imageData = value; }
+ }
+
+ ///
+ /// Make full copy of .
+ ///
+ /// Full independed copy of .
+ public TgaImgOrColMap Clone()
+ {
+ return new TgaImgOrColMap(
+ (imageID == null ? null : imageID.Clone()),
+ (colorMapData == null ? null : (byte[])colorMapData.Clone()),
+ (imageData == null ? null : (byte[])imageData.Clone()));
+ }
+
+ ///
+ /// Make full copy of .
+ ///
+ /// Full independed copy of .
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaImgOrColMap) ? Equals((TgaImgOrColMap)obj) : false);
+ }
+
+ public bool Equals(TgaImgOrColMap item)
+ {
+ return imageID == item.imageID &&
+ BitConverterExt.IsArraysEqual(colorMapData, item.colorMapData) &&
+ BitConverterExt.IsArraysEqual(imageData, item.imageData);
+ }
+
+ public static bool operator ==(TgaImgOrColMap item1, TgaImgOrColMap item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaImgOrColMap item1, TgaImgOrColMap item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 27;
+
+ if (imageID != null)
+ hash = (13 * hash) + imageID.GetHashCode();
+ if (colorMapData != null)
+ for (int i = 0; i < colorMapData.Length; i++)
+ hash = (13 * hash) + colorMapData[i].GetHashCode();
+ if (imageData != null)
+ for (int i = 0; i < imageData.Length; i++)
+ hash = (13 * hash) + imageData[i].GetHashCode();
+
+ return hash;
+ }
+ }
+ } //No ToBytes()
+
+ ///
+ /// Developer Area
+ /// //?
+ public class TgaDevArea : ICloneable
+ {
+ List entries = new List();
+
+ public TgaDevArea()
+ {
+ }
+
+ public TgaDevArea(List Entries)
+ {
+ if (Entries == null)
+ throw new ArgumentNullException(nameof(Entries) + " = null!");
+
+ entries = Entries;
+ }
+
+ ///
+ /// Developer Data - Field 9 (variable):
+ ///
+ public List Entries
+ {
+ get { return entries; }
+ set { entries = value; }
+ }
+
+ public int Count
+ {
+ get { return entries.Count; }
+ }
+
+ public TgaDevEntry this[int index]
+ {
+ get { return entries[index]; }
+ set { entries[index] = value; }
+ }
+
+ ///
+ /// Make full copy of .
+ ///
+ /// Full independent copy of .
+ public TgaDevArea Clone()
+ {
+ if (entries == null)
+ return new TgaDevArea(null);
+
+ List L = new List();
+ for (int i = 0; i < entries.Count; i++)
+ L.Add(entries[i].Clone());
+
+ return new TgaDevArea(L);
+ }
+
+ ///
+ /// Make full copy of .
+ ///
+ /// Full independent copy of .
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaDevArea) ? Equals((TgaDevArea)obj) : false);
+ }
+
+ public bool Equals(TgaDevArea item)
+ {
+ return BitConverterExt.IsListsEqual(entries, item.entries);
+ }
+
+ public static bool operator ==(TgaDevArea item1, TgaDevArea item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaDevArea item1, TgaDevArea item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 27;
+ if (entries != null)
+ for (int i = 0; i < entries.Count; i++)
+ hash = (13 * hash) + entries[i].GetHashCode();
+ return hash;
+ }
+ }
+
+ ///
+ /// Convert (without Fields Data, only Directory!) to byte array.
+ ///
+ /// Byte array, Len = (NUMBER_OF_TAGS_IN_THE_DIRECTORY * 10) + 2 bytes in size.
+ /// The "+ 2" includes the 2 bytes for the number of tags in the directory.
+ public byte[] ToBytes()
+ {
+ if (entries == null)
+ throw new Exception(nameof(Entries) + " = null!");
+
+ ushort NumberOfEntries = (ushort)Math.Min(ushort.MaxValue, entries.Count);
+ List DevDir = new List(BitConverter.GetBytes(NumberOfEntries));
+
+ for (int i = 0; i < entries.Count; i++)
+ {
+ DevDir.AddRange(BitConverter.GetBytes(entries[i].Tag));
+ DevDir.AddRange(BitConverter.GetBytes(entries[i].Offset));
+ DevDir.AddRange(BitConverter.GetBytes(entries[i].FieldSize));
+ }
+
+ return DevDir.ToArray();
+ }
+ } //Not full ToBytes()
+
+ ///
+ /// Extension Area
+ ///
+ public class TgaExtArea : ICloneable
+ {
+ public const int MinSize = 495; //bytes
+
+ ushort extensionSize = MinSize;
+ TgaString authorName = new TgaString(41, true);
+ TgaComment authorComments = new TgaComment();
+ TgaDateTime dateTimeStamp = new TgaDateTime();
+ TgaString jobNameOrID = new TgaString(41, true);
+ TgaTime jobTime = new TgaTime();
+ TgaString softwareID = new TgaString(41, true);
+ TgaSoftVersion softVersion = new TgaSoftVersion();
+ TgaColorKey keyColor = new TgaColorKey();
+ TgaFraction pixelAspectRatio = TgaFraction.Empty;
+ TgaFraction gammaValue = TgaFraction.Empty;
+ uint colorCorrectionOffset = 0;
+ uint postageStampOffset = 0;
+ uint scanLineOffset = 0;
+ TgaAttrType attributesType = TgaAttrType.NoAlpha;
+ uint[] scanLineTable = null;
+ TgaPostageStampImage postageStampImage = null;
+ ushort[] colorCorrectionTable = null;
+ byte[] otherDataInExtensionArea = null;
+
+ public TgaExtArea()
+ {
+ }
+
+ ///
+ /// Make from bytes. Warning: ,
+ /// , not included,
+ /// because thea are can be not in the Extension Area of TGA file!
+ ///
+ /// Bytes of .
+ /// Scan Line Table.
+ /// Postage Stamp Image.
+ /// Color Correction Table.
+ public TgaExtArea(byte[] Bytes, uint[] SLT = null, TgaPostageStampImage PostImg = null, ushort[] CCT = null)
+ {
+ if (Bytes == null)
+ throw new ArgumentNullException(nameof(Bytes) + " = null!");
+ if (Bytes.Length < MinSize)
+ throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be >= " + MinSize + "!");
+
+ extensionSize = BitConverter.ToUInt16(Bytes, 0);
+ authorName = new TgaString(BitConverterExt.GetElements(Bytes, 2, 41), true);
+ authorComments = new TgaComment(BitConverterExt.GetElements(Bytes, 43, TgaComment.Size));
+ dateTimeStamp = new TgaDateTime(BitConverterExt.GetElements(Bytes, 367, TgaDateTime.Size));
+ jobNameOrID = new TgaString(BitConverterExt.GetElements(Bytes, 379, 41), true);
+ jobTime = new TgaTime(BitConverterExt.GetElements(Bytes, 420, TgaTime.Size));
+ softwareID = new TgaString(BitConverterExt.GetElements(Bytes, 426, 41), true);
+ softVersion = new TgaSoftVersion(BitConverterExt.GetElements(Bytes, 467, TgaSoftVersion.Size));
+ keyColor = new TgaColorKey(BitConverterExt.GetElements(Bytes, 470, TgaColorKey.Size));
+ pixelAspectRatio = new TgaFraction(BitConverterExt.GetElements(Bytes, 474, TgaFraction.Size));
+ gammaValue = new TgaFraction(BitConverterExt.GetElements(Bytes, 478, TgaFraction.Size));
+ colorCorrectionOffset = BitConverter.ToUInt32(Bytes, 482);
+ postageStampOffset = BitConverter.ToUInt32(Bytes, 486);
+ scanLineOffset = BitConverter.ToUInt32(Bytes, 490);
+ attributesType = (TgaAttrType)Bytes[494];
+
+ if (extensionSize > MinSize)
+ otherDataInExtensionArea = BitConverterExt.GetElements(Bytes, 495, Bytes.Length - MinSize);
+
+ scanLineTable = SLT;
+ postageStampImage = PostImg;
+ colorCorrectionTable = CCT;
+ }
+
+ #region Properties
+ ///
+ /// Extension Size - Field 10 (2 Bytes):
+ /// This field is a SHORT field which specifies the number of BYTES in the fixedlength portion of
+ /// the Extension Area. For Version 2.0 of the TGA File Format, this number should be set to 495.
+ /// If the number found in this field is not 495, then the file will be assumed to be of a
+ /// version other than 2.0. If it ever becomes necessary to alter this number, the change
+ /// will be controlled by Truevision, and will be accompanied by a revision to the TGA File
+ /// Format with an accompanying change in the version number.
+ ///
+ public ushort ExtensionSize
+ {
+ get { return extensionSize; }
+ set { extensionSize = value; }
+ }
+
+ ///
+ /// Author Name - Field 11 (41 Bytes):
+ /// Bytes 2-42 - This field is an ASCII field of 41 bytes where the last byte must be a null
+ /// (binary zero). This gives a total of 40 ASCII characters for the name. If the field is used,
+ /// it should contain the name of the person who created the image (author). If the field is not
+ /// used, you may fill it with nulls or a series of blanks(spaces) terminated by a null.
+ /// The 41st byte must always be a null.
+ ///
+ public TgaString AuthorName
+ {
+ get { return authorName; }
+ set { authorName = value; }
+ }
+
+ ///
+ /// Author Comments - Field 12 (324 Bytes):
+ /// Bytes 43-366 - This is an ASCII field consisting of 324 bytes which are organized as four lines
+ /// of 80 characters, each followed by a null terminator.This field is provided, in addition to the
+ /// original IMAGE ID field(in the original TGA format), because it was determined that a few
+ /// developers had used the IMAGE ID field for their own purposes.This field gives the developer
+ /// four lines of 80 characters each, to use as an Author Comment area. Each line is fixed to 81
+ /// bytes which makes access to the four lines easy.Each line must be terminated by a null.
+ /// If you do not use all 80 available characters in the line, place the null after the last
+ /// character and blank or null fill the rest of the line. The 81st byte of each of the four
+ /// lines must be null.
+ ///
+ public TgaComment AuthorComments
+ {
+ get { return authorComments; }
+ set { authorComments = value; }
+ }
+
+ ///
+ /// Date/Time Stamp - Field 13 (12 Bytes):
+ /// Bytes 367-378 - This field contains a series of 6 SHORT values which define the integer
+ /// value for the date and time that the image was saved. This data is formatted as follows:
+ /// SHORT 0: Month(1 - 12)
+ /// SHORT 1: Day(1 - 31)
+ /// SHORT 2: Year(4 digit, ie. 1989)
+ /// SHORT 3: Hour(0 - 23)
+ /// SHORT 4: Minute(0 - 59)
+ /// SHORT 5: Second(0 - 59)
+ /// Even though operating systems typically time- and date-stamp files, this feature is
+ /// provided because the operating system may change the time and date stamp if the file is
+ /// copied. By using this area, you are guaranteed an unmodified region for date and time
+ /// recording. If the fields are not used, you should fill them with binary zeros (0).
+ ///
+ public TgaDateTime DateTimeStamp
+ {
+ get { return dateTimeStamp; }
+ set { dateTimeStamp = value; }
+ }
+
+ ///
+ /// Job Name/ID - Field 14 (41 Bytes):
+ /// Bytes 379-419 - This field is an ASCII field of 41 bytes where the last byte must be
+ /// a binary zero. This gives a total of 40 ASCII characters for the job name or the ID.
+ /// If the field is used, it should contain a name or id tag which refers to the job with
+ /// which the image was associated.This allows production companies (and others) to tie
+ /// images with jobs by using this field as a job name (i.e., CITY BANK) or job id number
+ /// (i.e., CITY023). If the field is not used, you may fill it with a null terminated series
+ /// of blanks (spaces) or nulls. In any case, the 41st byte must be a null.
+ ///
+ public TgaString JobNameOrID
+ {
+ get { return jobNameOrID; }
+ set { jobNameOrID = value; }
+ }
+
+ ///
+ /// Job Time - Field 15 (6 Bytes):
+ /// Bytes 420-425 - This field contains a series of 3 SHORT values which define the integer
+ /// value for the job elapsed time when the image was saved.This data is formatted as follows:
+ /// SHORT 0: Hours(0 - 65535)
+ /// SHORT 1: Minutes(0 - 59)
+ /// SHORT 2: Seconds(0 - 59)
+ /// The purpose of this field is to allow production houses (and others) to keep a running total
+ /// of the amount of time invested in a particular image. This may be useful for billing, costing,
+ /// and time estimating. If the fields are not used, you should fill them with binary zeros (0).
+ ///
+ public TgaTime JobTime
+ {
+ get { return jobTime; }
+ set { jobTime = value; }
+ }
+
+ ///
+ /// Software ID - Field 16 (41 Bytes):
+ /// Bytes 426-466 - This field is an ASCII field of 41 bytes where the last byte must be
+ /// a binary zero (null). This gives a total of 40 ASCII characters for the Software ID.
+ /// The purpose of this field is to allow software to determine and record with what program
+ /// a particular image was created.If the field is not used, you may fill it with a
+ /// null terminated series of blanks (spaces) or nulls. The 41st byte must always be a null.
+ ///
+ public TgaString SoftwareID
+ {
+ get { return softwareID; }
+ set { softwareID = value; }
+ }
+
+ ///
+ /// Software Version - Field 17 (3 Bytes):
+ /// Bytes 467-469 - This field consists of two sub-fields, a SHORT and an ASCII BYTE.
+ /// The purpose of this field is to define the version of software defined by the
+ /// “Software ID” field above. The SHORT contains the version number as a binary
+ /// integer times 100.
+ /// Therefore, software version 4.17 would be the integer value 417.This allows for
+ /// two decimal positions of sub-version.The ASCII BYTE supports developers who also
+ /// tag a release letter to the end. For example, if the version number is 1.17b, then
+ /// the SHORT would contain 117. and the ASCII BYTE would contain “b”.
+ /// The organization is as follows:
+ /// SHORT (Bytes 0 - 1): Version Number * 100
+ /// BYTE(Byte 2): Version Letter
+ /// If you do not use this field, set the SHORT to binary zero, and the BYTE to a space(“ “)
+ ///
+ public TgaSoftVersion SoftVersion
+ {
+ get { return softVersion; }
+ set { softVersion = value; }
+ }
+
+ ///
+ /// Key Color - Field 18 (4 Bytes):
+ /// Bytes 470-473 - This field contains a long value which is the key color in effect at
+ /// the time the image is saved. The format is in A:R:G:B where ‘A’ (most significant byte)
+ /// is the alpha channel key color(if you don’t have an alpha channel in your application,
+ /// keep this byte zero [0]).
+ /// The Key Color can be thought of as the ‘background color’ or ‘transparent color’.
+ /// This is the color of the ‘non image’ area of the screen, and the same color that the
+ /// screen would be cleared to if erased in the application. If you don’t use this field,
+ /// set it to all zeros (0). Setting the field to all zeros is the same as selecting a key
+ /// color of black.
+ /// A good example of a key color is the ‘transparent color’ used in TIPS™ for WINDOW loading/saving.
+ ///
+ public TgaColorKey KeyColor
+ {
+ get { return keyColor; }
+ set { keyColor = value; }
+ }
+
+ ///
+ /// Pixel Aspect Ratio - Field 19 (4 Bytes):
+ /// Bytes 474-477 - This field contains two SHORT sub-fields, which when taken together
+ /// specify a pixel size ratio.The format is as follows:
+ /// SHORT 0: Pixel Ratio Numerator(pixel width)
+ /// SHORT 1: Pixel Ratio Denominator(pixel height)
+ /// These sub-fields may be used to determine the aspect ratio of a pixel. This is useful when
+ /// it is important to preserve the proper aspect ratio of the saved image. If the two values
+ /// are set to the same non-zero value, then the image is composed of square pixels. A zero
+ /// in the second sub-field (denominator) indicates that no pixel aspect ratio is specified.
+ ///
+ public TgaFraction PixelAspectRatio
+ {
+ get { return pixelAspectRatio; }
+ set { pixelAspectRatio = value; }
+ }
+
+ ///
+ /// Gamma Value - Field 20 (4 Bytes):
+ /// Bytes 478-481 - This field contains two SHORT sub-fields, which when taken together in a ratio,
+ /// provide a fractional gamma value.The format is as follows:
+ /// SHORT 0: Gamma Numerator
+ /// SHORT 1: Gamma Denominator
+ /// The resulting value should be in the range of 0.0 to 10.0, with only one decimal place of
+ /// precision necessary. An uncorrected image (an image with no gamma) should have the value 1.0 as
+ /// the result.This may be accomplished by placing thesame, non-zero values in both positions
+ /// (i.e., 1/1). If you decide to totally ignore this field, please set the denominator (the second
+ /// SHORT) to the value zero. This will indicate that the Gamma Value field is not being used.
+ ///
+ public TgaFraction GammaValue
+ {
+ get { return gammaValue; }
+ set { gammaValue = value; }
+ }
+
+ ///
+ /// Color Correction Offset - Field 21 (4 Bytes):
+ /// Bytes 482-485 - This field is a 4-byte field containing a single offset value. This is an offset
+ /// from the beginning of the file to the start of the Color Correction table. This table may be
+ /// written anywhere between the end of the Image Data field (field 8) and the start of the TGA
+ /// File Footer. If the image has no Color Correction Table or if the Gamma Value setting is
+ /// sufficient, set this value to zero and do not write a Correction Table anywhere.
+ ///
+ public uint ColorCorrectionTableOffset
+ {
+ get { return colorCorrectionOffset; }
+ set { colorCorrectionOffset = value; }
+ }
+
+ ///
+ /// Postage Stamp Offset - Field 22 (4 Bytes):
+ /// Bytes 486-489 - This field is a 4-byte field containing a single offset value. This is an offset
+ /// from the beginning of the file to the start of the Postage Stamp Image. The Postage Stamp Image
+ /// must be written after Field 25 (Scan Line Table) but before the start of the TGA File Footer.
+ /// If no postage stamp is stored, set this field to the value zero (0).
+ ///
+ public uint PostageStampOffset
+ {
+ get { return postageStampOffset; }
+ set { postageStampOffset = value; }
+ }
+
+ ///
+ /// Scan Line Offset - Field 23 (4 Bytes):
+ /// Bytes 490-493 - This field is a 4-byte field containing a single offset value. This is an
+ /// offset from the beginning of the file to the start of the Scan Line Table.
+ ///
+ public uint ScanLineOffset
+ {
+ get { return scanLineOffset; }
+ set { scanLineOffset = value; }
+ }
+
+ ///
+ /// Attributes Type - Field 24 (1 Byte):
+ /// Byte 494 - This single byte field contains a value which specifies the type of Alpha channel
+ /// data contained in the file. Value Meaning:
+ /// 0: no Alpha data included (bits 3-0 of field 5.6 should also be set to zero)
+ /// 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
+ /// Pre-multiplied Alpha Example: Suppose the Alpha channel data is being used to specify the
+ /// opacity of each pixel(for use when the image is overlayed on another image), where 0 indicates
+ /// that the pixel is completely transparent and a value of 1 indicates that the pixel is
+ /// completely opaque(assume all component values have been normalized).
+ /// A quadruple(a, r, g, b) of( 0.5, 1, 0, 0) would indicate that the pixel is pure red with a
+ /// transparency of one-half. For numerous reasons(including image compositing) is is better to
+ /// pre-multiply the individual color components with the value in the Alpha channel.
+ /// A pre-multiplication of the above would produce a quadruple(0.5, 0.5, 0, 0).
+ /// A value of 3 in the Attributes Type Field(field 23) would indicate that the color components
+ /// of the pixel have already been scaled by the value in the Alpha channel.
+ ///
+ public TgaAttrType AttributesType
+ {
+ get { return attributesType; }
+ set { attributesType = value; }
+ }
+
+ ///
+ /// Scan Line Table - Field 25 (Variable):
+ /// This information is provided, at the developers’ request, for two purposes:
+ /// 1) To make random access of compressed images easy.
+ /// 2) To allow “giant picture” access in smaller “chunks”.
+ /// This table should contain a series of 4-byte offsets.Each offset you write should point to the
+ /// start of the next scan line, in the order that the image was saved (i.e., top down or bottom up).
+ /// The offset should be from the start of the file.Therefore, you will have a four byte value for
+ /// each scan line in your image. This means that if your image is 768 pixels tall, you will have 768,
+ /// 4-byte offset pointers (for a total of 3072 bytes). This size is not extreme, and thus this table
+ /// can be built and maintained in memory, and then written out at the proper time.
+ ///
+ public uint[] ScanLineTable
+ {
+ get { return scanLineTable; }
+ set { scanLineTable = value; }
+ }
+
+ ///
+ /// Postage Stamp Image - Field 26 (Variable):
+ /// The Postage Stamp area is a smaller representation of the original image. This is useful for
+ /// “browsing” a collection of image files. If your application can deal with a postage stamp image,
+ /// it is recommended that you create one using sub-sampling techniques to create the best
+ /// representation possible. The postage stamp image must be stored in the same format as the normal
+ /// image specified in the file, but without any compression. The first byte of the postage stamp
+ /// image specifies the X size of the stamp in pixels, the second byte of the stamp image specifies the
+ /// Y size of the stamp in pixels. Truevision does not recommend stamps larger than 64x64 pixels, and
+ /// suggests that any stamps stored larger be clipped. Obviously, the storage of the postage stamp
+ /// relies heavily on the storage of the image. The two images are stored using the same format under
+ /// the assumption that if you can read the image, then you can read the postage stamp. If the original
+ /// image is color mapped, DO NOT average the postage stamp, as you will create new colors not in your map.
+ ///
+ public TgaPostageStampImage PostageStampImage
+ {
+ get { return postageStampImage; }
+ set { postageStampImage = value; }
+ }
+
+ ///
+ /// Color Correction Table - Field 27 (2K Bytes):
+ /// The Color Correction Table is a block of 256 x 4 SHORT values, where each set of
+ /// four contiguous values are the desired A:R:G:B correction for that entry. This
+ /// allows the user to store a correction table for image remapping or LUT driving.
+ /// Since each color in the block is a SHORT, the maximum value for a color gun (i.e.,
+ /// A, R, G, B) is 65535, and the minimum value is zero.Therefore, BLACK maps to
+ /// 0, 0, 0, 0 and WHITE maps to 65535, 65535, 65535, 65535.
+ ///
+ public ushort[] ColorCorrectionTable
+ {
+ get { return colorCorrectionTable; }
+ set { colorCorrectionTable = value; }
+ }
+
+ ///
+ /// Other Data In Extension Area (if > 495).
+ ///
+ public byte[] OtherDataInExtensionArea
+ {
+ get { return otherDataInExtensionArea; }
+ set { otherDataInExtensionArea = value; }
+ }
+ #endregion
+
+ ///
+ /// Make full copy of .
+ ///
+ /// Full independent copy of .
+ public TgaExtArea Clone()
+ {
+ TgaExtArea NewExtArea = new TgaExtArea();
+ NewExtArea.extensionSize = extensionSize;
+ NewExtArea.authorName = authorName.Clone();
+ NewExtArea.authorComments = authorComments.Clone();
+ NewExtArea.dateTimeStamp = dateTimeStamp.Clone();
+ NewExtArea.jobNameOrID = jobNameOrID.Clone();
+ NewExtArea.jobTime = jobTime.Clone();
+ NewExtArea.softwareID = softwareID.Clone();
+ NewExtArea.softVersion = softVersion.Clone();
+ NewExtArea.keyColor = keyColor.Clone();
+ NewExtArea.pixelAspectRatio = pixelAspectRatio.Clone();
+ NewExtArea.gammaValue = gammaValue.Clone();
+ NewExtArea.colorCorrectionOffset = colorCorrectionOffset;
+ NewExtArea.postageStampOffset = postageStampOffset;
+ NewExtArea.scanLineOffset = scanLineOffset;
+ NewExtArea.attributesType = attributesType;
+
+ if (scanLineTable != null)
+ NewExtArea.scanLineTable = (uint[])scanLineTable.Clone();
+ if (postageStampImage != null)
+ NewExtArea.postageStampImage = new TgaPostageStampImage(postageStampImage.ToBytes());
+ if (colorCorrectionTable != null)
+ NewExtArea.colorCorrectionTable = (ushort[])colorCorrectionTable.Clone();
+
+ if (otherDataInExtensionArea != null)
+ NewExtArea.otherDataInExtensionArea = (byte[])otherDataInExtensionArea.Clone();
+
+ return NewExtArea;
+ }
+
+ ///
+ /// Make full copy of .
+ ///
+ /// Full independent copy of .
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaExtArea) ? Equals((TgaExtArea)obj) : false);
+ }
+
+ public bool Equals(TgaExtArea item)
+ {
+ return (extensionSize == item.extensionSize &&
+ authorName == item.authorName &&
+ authorComments == item.authorComments &&
+ dateTimeStamp == item.dateTimeStamp &&
+ jobNameOrID == item.jobNameOrID &&
+ jobTime == item.jobTime &&
+ softwareID == item.softwareID &&
+ softVersion == item.softVersion &&
+ keyColor == item.keyColor &&
+ pixelAspectRatio == item.pixelAspectRatio &&
+ gammaValue == item.gammaValue &&
+ colorCorrectionOffset == item.colorCorrectionOffset &&
+ postageStampOffset == item.postageStampOffset &&
+ scanLineOffset == item.scanLineOffset &&
+ attributesType == item.attributesType &&
+
+ BitConverterExt.IsArraysEqual(scanLineTable, item.scanLineTable) &&
+ postageStampImage == item.postageStampImage &&
+ BitConverterExt.IsArraysEqual(colorCorrectionTable, item.colorCorrectionTable) &&
+
+ BitConverterExt.IsArraysEqual(otherDataInExtensionArea, item.otherDataInExtensionArea));
+ }
+
+ public static bool operator ==(TgaExtArea item1, TgaExtArea item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaExtArea item1, TgaExtArea item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 27;
+ hash = (13 * hash) + extensionSize.GetHashCode();
+ hash = (13 * hash) + authorName.GetHashCode();
+ hash = (13 * hash) + authorComments.GetHashCode();
+ hash = (13 * hash) + dateTimeStamp.GetHashCode();
+ hash = (13 * hash) + jobNameOrID.GetHashCode();
+ hash = (13 * hash) + jobTime.GetHashCode();
+ hash = (13 * hash) + softwareID.GetHashCode();
+ hash = (13 * hash) + softVersion.GetHashCode();
+ hash = (13 * hash) + keyColor.GetHashCode();
+ hash = (13 * hash) + pixelAspectRatio.GetHashCode();
+ hash = (13 * hash) + gammaValue.GetHashCode();
+ hash = (13 * hash) + colorCorrectionOffset.GetHashCode();
+ hash = (13 * hash) + postageStampOffset.GetHashCode();
+ hash = (13 * hash) + scanLineOffset.GetHashCode();
+ hash = (13 * hash) + attributesType.GetHashCode();
+
+ if (scanLineTable != null)
+ for (int i = 0; i < scanLineTable.Length; i++)
+ hash = (13 * hash) + scanLineTable[i].GetHashCode();
+
+ if (postageStampImage != null)
+ hash = (13 * hash) + postageStampImage.GetHashCode();
+
+ if (colorCorrectionTable != null)
+ for (int i = 0; i < colorCorrectionTable.Length; i++)
+ hash = (13 * hash) + colorCorrectionTable[i].GetHashCode();
+
+ if (otherDataInExtensionArea != null)
+ for (int i = 0; i < otherDataInExtensionArea.Length; i++)
+ hash = (13 * hash) + otherDataInExtensionArea[i].GetHashCode();
+
+ return hash;
+ }
+ }
+
+ ///
+ /// Convert to byte array. Warning: ,
+ /// , not included,
+ /// because thea are can be not in the Extension Area of TGA file!
+ ///
+ /// Byte array.
+ public byte[] ToBytes()
+ {
+ #region Exceptions check
+ if (authorName == null)
+ authorName = new TgaString(41, true);
+
+ if (authorComments == null)
+ authorComments = new TgaComment();
+
+ if (dateTimeStamp == null)
+ dateTimeStamp = new TgaDateTime(DateTime.UtcNow);
+
+ if (jobNameOrID == null)
+ jobNameOrID = new TgaString(41, true);
+
+ if (jobTime == null)
+ jobTime = new TgaTime();
+
+ if (softwareID == null)
+ softwareID = new TgaString(41, true);
+
+ if (softVersion == null)
+ softVersion = new TgaSoftVersion();
+
+ if (keyColor == null)
+ keyColor = new TgaColorKey();
+
+ if (pixelAspectRatio == null)
+ pixelAspectRatio = new TgaFraction();
+
+ if (gammaValue == null)
+ gammaValue = new TgaFraction();
+ #endregion
+
+ return BitConverterExt.ToBytes(
+ extensionSize,
+ authorName.ToBytes(),
+ authorComments.ToBytes(),
+ dateTimeStamp.ToBytes(),
+ jobNameOrID.ToBytes(),
+ jobTime.ToBytes(),
+ softwareID.ToBytes(),
+ softVersion.ToBytes(),
+ keyColor.ToBytes(),
+ pixelAspectRatio.ToBytes(),
+ gammaValue.ToBytes(),
+ colorCorrectionOffset,
+ postageStampOffset,
+ scanLineOffset,
+ (byte)attributesType,
+ otherDataInExtensionArea);
+ }
+ } //Not full ToBytes()
+
+ ///
+ /// File Footer Area
+ ///
+ public class TgaFooter : ICloneable
+ {
+ uint extAreaOffset = 0;
+ uint devDirOffset = 0;
+ TgaString signature = TgaString.XFileSignatute;
+ TgaString reservedChar = TgaString.DotSymbol;
+ TgaString zeroStrTerminator = TgaString.ZeroTerminator;
+
+ ///
+ /// Make NewXFile format TGA Footer with = 0 and
+ /// = 0.
+ ///
+ public TgaFooter()
+ {
+ }
+
+ ///
+ /// Make from values.
+ ///
+ /// Extension Area Offset, offset from the beginning of the file.
+ /// Developer Directory Offset, offset from the beginning of the file.
+ /// New TGA format signature.
+ /// Reserved Character - ASCII character “.” (period).
+ /// Binary Zero Terminator, a binary zero which acts as a final terminator.
+ public TgaFooter(uint ExtOff, uint DevDirOff, TgaString Sign, TgaString ReservChr, TgaString Termin)
+ {
+ extAreaOffset = ExtOff;
+ devDirOffset = DevDirOff;
+ signature = Sign;
+ reservedChar = ReservChr;
+ zeroStrTerminator = Termin;
+ }
+
+ ///
+ /// Make from bytes (if signature is right).
+ ///
+ /// Bytes array (byte[26]).
+ public TgaFooter(byte[] Bytes)
+ {
+ if (Bytes == null)
+ throw new ArgumentNullException(nameof(Bytes) + " = null!");
+ if (Bytes.Length != Size)
+ throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!");
+
+ extAreaOffset = BitConverter.ToUInt32(Bytes, 0);
+ devDirOffset = BitConverter.ToUInt32(Bytes, 4);
+ signature = new TgaString(BitConverterExt.GetElements(Bytes, 8, TgaString.XFileSignatuteConst.Length));
+ reservedChar = new TgaString(new byte[] { Bytes[24] });
+ zeroStrTerminator = new TgaString(new byte[] { Bytes[25] });
+ }
+
+ ///
+ /// Byte 0-3 - Extension Area Offset - Field 28
+ /// The first four bytes (bytes 0-3, the first LONG) of the TGA File Footer contain an
+ /// offset from the beginning of the file to the start of the Extension Area. Simply
+ /// SEEK to this location to position to the start of the Extension Area. If the
+ /// Extension Area Offset is zero, no Extension Area exists in the file.
+ ///
+ public uint ExtensionAreaOffset
+ {
+ get { return extAreaOffset; }
+ set { extAreaOffset = value; }
+ }
+
+ ///
+ /// Byte 4-7 - Developer Directory Offset - Field 29
+ /// The next four bytes(bytes 4-7, the second LONG) contain an offset from the
+ /// beginning of the file to the start of the Developer Directory. If the Developer
+ /// Directory Offset is zero, then the Developer Area does not exist.
+ ///
+ public uint DeveloperDirectoryOffset
+ {
+ get { return devDirOffset; }
+ set { devDirOffset = value; }
+ }
+
+ ///
+ /// Byte 8-23 - Signature - Field 30
+ /// This string is exactly 16 bytes long and is formatted exactly as shown below
+ /// capital letters), with a hyphen between “TRUEVISION” and “XFILE.” If the
+ /// signature is detected, the file is assumed to be of the New TGA format and MAY,
+ /// therefore, contain the Developer Area and/or the Extension Area fields.If the
+ /// signature is not found, then the file is assumed to be in the Original TGA format.
+ ///
+ public TgaString Signature
+ {
+ get { return signature; }
+ set { signature = value; }
+ }
+
+ ///
+ /// Byte 24 - Reserved Character - Field 31
+ /// Byte 24 is an ASCII character “.” (period). This character MUST BE a period or
+ /// the file is not considered a proper TGA file.
+ ///
+ public TgaString ReservedCharacter
+ {
+ get { return reservedChar; }
+ set { reservedChar = value; }
+ }
+
+ ///
+ /// Byte 25 - Binary Zero String Terminator - Field 32
+ /// Byte 25 is a binary zero which acts as a final terminator and allows the entire TGA
+ /// File Footer to be read and utilized as a “C” string.
+ ///
+ public TgaString BinaryZeroStringTerminator
+ {
+ get { return zeroStrTerminator; }
+ set { zeroStrTerminator = value; }
+ }
+
+ ///
+ /// Make full copy of .
+ ///
+ ///
+ public TgaFooter Clone()
+ {
+ return new TgaFooter(extAreaOffset, devDirOffset, signature.Clone(),
+ reservedChar.Clone(), zeroStrTerminator.Clone());
+ }
+
+ ///
+ /// Make full copy of .
+ ///
+ ///
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ ///
+ /// Gets TGA Footer Section size in bytes.
+ ///
+ public const int Size = 26;
+
+ public override bool Equals(object obj)
+ {
+ return ((obj is TgaFooter) ? Equals((TgaFooter)obj) : false);
+ }
+
+ public bool Equals(TgaFooter item)
+ {
+ return (extAreaOffset == item.extAreaOffset &&
+ devDirOffset == item.devDirOffset &&
+ signature == item.signature &&
+ reservedChar == item.reservedChar &&
+ zeroStrTerminator == item.zeroStrTerminator);
+ }
+
+ public static bool operator ==(TgaFooter item1, TgaFooter item2)
+ {
+ if (ReferenceEquals(item1, null))
+ return ReferenceEquals(item2, null);
+
+ if (ReferenceEquals(item2, null))
+ return ReferenceEquals(item1, null);
+
+ return item1.Equals(item2);
+ }
+
+ public static bool operator !=(TgaFooter item1, TgaFooter item2)
+ {
+ return !(item1 == item2);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 17;
+ hash = hash * 23 + extAreaOffset.GetHashCode();
+ hash = hash * 23 + devDirOffset.GetHashCode();
+
+ if (signature != null)
+ hash = hash * 23 + signature.GetHashCode();
+
+ if (reservedChar != null)
+ hash = hash * 23 + reservedChar.GetHashCode();
+
+ if (zeroStrTerminator != null)
+ hash = hash * 23 + zeroStrTerminator.GetHashCode();
+
+ return hash;
+ }
+ }
+
+ public override string ToString()
+ {
+ return String.Format("{0}={1}, {2}={3}, FullSignature={4}",
+ nameof(ExtensionAreaOffset), extAreaOffset, nameof(DeveloperDirectoryOffset), devDirOffset,
+ (signature + reservedChar + zeroStrTerminator).ToString());
+ }
+
+ ///
+ /// Convert to byte array.
+ ///
+ /// Byte array with size equal .
+ public byte[] ToBytes()
+ {
+ return BitConverterExt.ToBytes(extAreaOffset, devDirOffset,
+ signature.ToBytes(), reservedChar.ToBytes(), zeroStrTerminator.ToBytes());
+ }
+
+ ///
+ /// Is footer is real footer of TGA File Format Version 2.0?
+ /// Checking by .
+ ///
+ public bool IsFooterCorrect
+ {
+ get { return signature == TgaString.XFileSignatute; }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Simplify ByteConversion operations, like concatination of byte arrays, comparing and other.
+ ///
+ public static class BitConverterExt
+ {
+ ///
+ /// Combine byte, byte[], (u)short, (u)int, (u)long values to byte[] array.
+ ///
+ /// Array of byte, byte[], (u)short, (u)int, (u)long values.
+ /// Array of bytes, null when some object is null.
+ public static byte[] ToBytes(params object[] obj)
+ {
+ if (obj == null)
+ return null;
+
+ List BytesList = new List();
+
+ for (int i = 0; i < obj.Length; i++)
+ {
+ if (obj[i] == null)
+ continue;
+ else if (obj[i] is byte)
+ BytesList.Add((byte)obj[i]);
+ else if (obj[i] is byte[])
+ BytesList.AddRange((byte[])obj[i]);
+ else if (obj[i] is short)
+ BytesList.AddRange(BitConverter.GetBytes((short)obj[i]));
+ else if (obj[i] is ushort)
+ BytesList.AddRange(BitConverter.GetBytes((ushort)obj[i]));
+ else if (obj[i] is int)
+ BytesList.AddRange(BitConverter.GetBytes((int)obj[i]));
+ else if (obj[i] is uint)
+ BytesList.AddRange(BitConverter.GetBytes((uint)obj[i]));
+ else if (obj[i] is long)
+ BytesList.AddRange(BitConverter.GetBytes((long)obj[i]));
+ else if (obj[i] is ulong)
+ BytesList.AddRange(BitConverter.GetBytes((ulong)obj[i]));
+ }
+ return BytesList.ToArray();
+ }
+
+ ///
+ /// Copies a range of elements from an Array starting at the specified source index.
+ /// The length and the index are specified as 32-bit integers.
+ ///
+ /// The that contains the data to copy.
+ /// A 32-bit integer that represents the index in the
+ /// at which copying begins.
+ /// A 32-bit integer that represents the number of elements to copy.
+ ///
+ public static T[] GetElements(T[] SrcArray, int Offset, int Count)
+ {
+ if (SrcArray == null)
+ throw new ArgumentNullException(nameof(SrcArray) + " is null!");
+
+ if (Offset >= SrcArray.Length || Offset < 0)
+ throw new ArgumentOutOfRangeException(nameof(Offset) + " has wrong value!");
+
+ if (Count <= 0 || Offset + Count > SrcArray.Length)
+ throw new ArgumentOutOfRangeException(nameof(Count) + " has wrong value!");
+
+ T[] Buff = new T[Count];
+ Array.Copy(SrcArray, Offset, Buff, 0, Buff.Length);
+ return Buff;
+ }
+
+ ///
+ /// Compare N-dimensional Arrays.
+ ///
+ /// Arrays Type.
+ /// First Array.
+ /// Second Array.
+ /// True, if Arrays are equal.
+ public static bool IsArraysEqual(T[] item1, T[] item2)
+ {
+ if (ReferenceEquals(item1, item2))
+ return true;
+
+ if (item1 == null || item2 == null)
+ return false;
+
+ if (item1.Length != item2.Length)
+ return false;
+
+ EqualityComparer comparer = EqualityComparer.Default;
+ for (int i = 0; i < item1.Length; i++)
+ if (!comparer.Equals(item1[i], item2[i]))
+ return false;
+ return true;
+ }
+
+ ///
+ /// Compare Lists.
+ ///
+ /// List Type.
+ /// First List.
+ /// Second List.
+ /// True, if Lists are equal.
+ public static bool IsListsEqual(List item1, List item2)
+ {
+ if (ReferenceEquals(item1, item2))
+ return true;
+
+ if (item1 == null || item2 == null)
+ return false;
+
+ if (item1.Count != item2.Count)
+ return false;
+
+ for (int i = 0; i < item1.Count; i++)
+ if (!item1[i].Equals(item2[i]))
+ return false;
+ return true;
+ }
+
+ ///
+ /// Compare elements in one Array with different offsets.
+ ///
+ /// Array type.
+ /// Some Array.
+ /// First offset.
+ /// Second offset.
+ /// Elements count which must be compared.
+ ///
+ public static bool IsElementsEqual(T[] Arr, int Offset1, int Offset2, int Count)
+ {
+ if (Arr == null)
+ throw new ArgumentNullException(nameof(Arr) + " is null!");
+
+ if (Offset1 >= Arr.Length || Offset1 < 0)
+ throw new ArgumentOutOfRangeException(nameof(Offset1) + " has wrong value!");
+
+ if (Offset2 >= Arr.Length || Offset2 < 0)
+ throw new ArgumentOutOfRangeException(nameof(Offset2) + " has wrong value!");
+
+ if (Count <= 0 || Offset1 + Count > Arr.Length || Offset2 + Count > Arr.Length)
+ throw new ArgumentOutOfRangeException(nameof(Count) + " has wrong value!");
+
+ if (Offset1 == Offset2)
+ return true;
+
+ for (int i = 0; i < Count; i++)
+ if (!Arr[Offset1 + i].Equals(Arr[Offset2 + i]))
+ return false;
+
+ return true;
+ }
+ }
+ #endregion
+
+ public class TGA : ICloneable
+ {
+ public TgaHeader Header = new TgaHeader();
+ public TgaImgOrColMap ImageOrColorMapArea = new TgaImgOrColMap();
+ public TgaDevArea DevArea = null;
+ public TgaExtArea ExtArea = null;
+ public TgaFooter Footer = null;
+
+ #region TGA Creation, Loading, Saving (all are public, have reference to private metods).
+ ///
+ /// Create new empty istance.
+ ///
+ public TGA()
+ {
+ }
+
+ ///
+ /// Create instance with some params. If it must have ColorMap,
+ /// check all ColorMap fields and settings after.
+ ///
+ /// Image Width.
+ /// Image Height.
+ /// Image Pixel Depth (bits / pixel), set ColorMap bpp after, if needed!
+ /// Image Type (is RLE compressed, ColorMapped or GrayScaled).
+ /// Set numder of Attrbute bits (Alpha channel bits), default: 0, 1, 8.
+ /// Use new 2.0 TGA XFile format?
+ public TGA(ushort Width, ushort Height, TgaPixelDepth PixDepth = TgaPixelDepth.Bpp24,
+ TgaImageType ImgType = TgaImageType.Uncompressed_TrueColor, byte AttrBits = 0, bool NewFormat = true)
+ {
+ if (Width <= 0 || Height <= 0 || PixDepth == TgaPixelDepth.Other)
+ {
+ Width = Height = 0;
+ PixDepth = TgaPixelDepth.Other;
+ ImgType = TgaImageType.NoImageData;
+ AttrBits = 0;
+ }
+ else
+ {
+ int BytesPerPixel = (int)Math.Ceiling((double)PixDepth / 8.0);
+ ImageOrColorMapArea.ImageData = new byte[Width * Height * BytesPerPixel];
+
+ if (ImgType == TgaImageType.Uncompressed_ColorMapped || ImgType == TgaImageType.RLE_ColorMapped)
+ {
+ Header.ColorMapType = TgaColorMapType.ColorMap;
+ Header.ColorMapSpec.FirstEntryIndex = 0;
+ Header.ColorMapSpec.ColorMapEntrySize = (TgaColorMapEntrySize)Math.Ceiling((double)PixDepth / 8);
+ }
+ }
+
+ Header.ImageType = ImgType;
+ Header.ImageSpec.ImageWidth = Width;
+ Header.ImageSpec.ImageHeight = Height;
+ Header.ImageSpec.PixelDepth = PixDepth;
+ Header.ImageSpec.ImageDescriptor.AlphaChannelBits = AttrBits;
+
+ if (NewFormat)
+ {
+ Footer = new TgaFooter();
+ ExtArea = new TgaExtArea();
+ ExtArea.DateTimeStamp = new TgaDateTime(DateTime.UtcNow);
+ ExtArea.AttributesType = (AttrBits > 0 ? TgaAttrType.UsefulAlpha : TgaAttrType.NoAlpha);
+ }
+ }
+
+ ///
+ /// Make from some instance.
+ /// Equal to function.
+ ///
+ /// Original instance.
+ public TGA(TGA tga)
+ {
+ Header = tga.Header.Clone();
+ ImageOrColorMapArea = tga.ImageOrColorMapArea.Clone();
+ DevArea = tga.DevArea.Clone();
+ ExtArea = tga.ExtArea.Clone();
+ Footer = tga.Footer.Clone();
+ }
+
+ ///
+ /// Load from file.
+ ///
+ /// Full path to TGA file.
+ /// Loaded file.
+ public TGA(string filename)
+ {
+ LoadFunc(filename);
+ }
+
+ ///
+ /// Make from bytes array.
+ ///
+ /// Bytes array (same like TGA File).
+ public TGA(byte[] bytes)
+ {
+ LoadFunc(bytes);
+ }
+
+ ///
+ /// Make from .
+ /// For file opening better use .
+ ///
+ /// Some stream. You can use a lot of Stream types, but Stream must support:
+ /// and .
+ public TGA(Stream stream)
+ {
+ LoadFunc(stream);
+ }
+
+ ///
+ /// Make from .
+ ///
+ /// Input Bitmap, supported a lot of bitmaps types: 8/15/16/24/32 Bpp's.
+ /// Use RLE Compression?
+ /// Use new 2.0 TGA XFile format?
+ /// Is Color Map Entry size equal 15 or 16 Bpp, else - 24 or 32.
+ public TGA(Bitmap bmp, bool UseRLE = false, bool NewFormat = true, bool ColorMap2BytesEntry = false)
+ {
+ LoadFunc(bmp, UseRLE, NewFormat, ColorMap2BytesEntry);
+ }
+
+ ///
+ /// Load from file.
+ ///
+ /// Full path to TGA file.
+ /// Loaded file.
+ public static TGA FromFile(string filename)
+ {
+ return new TGA(filename);
+ }
+
+ ///
+ /// Make from bytes array.
+ ///
+ /// Bytes array (same like TGA File).
+ public static TGA FromBytes(byte[] bytes)
+ {
+ return new TGA(bytes);
+ }
+
+ ///
+ /// Make from .
+ /// For file opening better use .
+ ///
+ /// Some stream. You can use a lot of Stream types, but Stream must support:
+ /// and .
+ public static TGA FromStream(Stream stream)
+ {
+ return new TGA(stream);
+ }
+
+ ///
+ /// Make from .
+ ///
+ /// Input Bitmap, supported a lot of bitmaps types: 8/15/16/24/32 Bpp's.
+ /// Use RLE Compression?
+ /// Use new 2.0 TGA XFile format?
+ /// Is Color Map Entry size equal 15 or 16 Bpp, else - 24 or 32.
+ public static TGA FromBitmap(Bitmap bmp, bool UseRLE = false,
+ bool NewFormat = true, bool ColorMap2BytesEntry = false)
+ {
+ return new TGA(bmp, UseRLE, NewFormat, ColorMap2BytesEntry);
+ }
+
+ ///
+ /// Save to file.
+ ///
+ /// Full path to file.
+ /// Return "true", if all done or "false", if failed.
+ public bool Save(string filename)
+ {
+ try
+ {
+ bool Result = false;
+ using (FileStream Fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None))
+ {
+ using (MemoryStream Ms = new MemoryStream())
+ {
+ Result = SaveFunc(Ms);
+ Ms.WriteTo(Fs);
+ Fs.Flush();
+ }
+ }
+ return Result;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Save to .
+ ///
+ /// Some stream, it must support: .
+ /// Return "true", if all done or "false", if failed.
+ public bool Save(Stream stream)
+ {
+ return SaveFunc(stream);
+ }
+ #endregion
+
+ ///
+ /// Gets or Sets Image Width (see ).
+ ///
+ public ushort Width
+ {
+ get { return Header.ImageSpec.ImageWidth; }
+ set { Header.ImageSpec.ImageWidth = value; }
+ }
+
+ ///
+ /// Gets or Sets Image Height (see ).
+ ///
+ public ushort Height
+ {
+ get { return Header.ImageSpec.ImageHeight; }
+ set { Header.ImageSpec.ImageHeight = value; }
+ }
+
+ ///
+ /// Gets or Sets image Size.
+ ///
+ public Size Size
+ {
+ get { return new Size(Header.ImageSpec.ImageWidth, Header.ImageSpec.ImageHeight); }
+ set
+ {
+ Header.ImageSpec.ImageWidth = (ushort)value.Width;
+ Header.ImageSpec.ImageHeight = (ushort)value.Height;
+ }
+ }
+
+ ///
+ /// Make full independed copy of .
+ ///
+ /// Full independed copy of .
+ public TGA Clone()
+ {
+ return new TGA(this);
+ }
+
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ ///
+ /// Flip directions, for more info see .
+ ///
+ /// Flip horizontal.
+ /// Flip vertical.
+ public void Flip(bool Horizontal = false, bool Vertical = false)
+ {
+ int NewOrigin = (int)Header.ImageSpec.ImageDescriptor.ImageOrigin;
+ NewOrigin = NewOrigin ^ ((Vertical ? 0x20 : 0) | (Horizontal ? 0x10 : 0));
+ Header.ImageSpec.ImageDescriptor.ImageOrigin = (TgaImgOrigin)NewOrigin;
+ }
+
+ ///
+ /// Get information from TGA image.
+ ///
+ /// MultiLine string with info fields (one per line).
+ public string GetInfo()
+ {
+ StringBuilder SB = new StringBuilder();
+
+ SB.AppendLine("Header:");
+ SB.AppendLine("\tID Length = " + Header.IDLength);
+ SB.AppendLine("\tImage Type = " + Header.ImageType);
+ SB.AppendLine("\tHeader -> ImageSpec:");
+ SB.AppendLine("\t\tImage Width = " + Header.ImageSpec.ImageWidth);
+ SB.AppendLine("\t\tImage Height = " + Header.ImageSpec.ImageHeight);
+ SB.AppendLine("\t\tPixel Depth = " + Header.ImageSpec.PixelDepth);
+ SB.AppendLine("\t\tImage Descriptor (AsByte) = " + Header.ImageSpec.ImageDescriptor.ToByte());
+ SB.AppendLine("\t\tImage Descriptor -> AttributeBits = " + Header.ImageSpec.ImageDescriptor.AlphaChannelBits);
+ SB.AppendLine("\t\tImage Descriptor -> ImageOrigin = " + Header.ImageSpec.ImageDescriptor.ImageOrigin);
+ SB.AppendLine("\t\tX_Origin = " + Header.ImageSpec.X_Origin);
+ SB.AppendLine("\t\tY_Origin = " + Header.ImageSpec.Y_Origin);
+ SB.AppendLine("\tColorMap Type = " + Header.ColorMapType);
+ SB.AppendLine("\tHeader -> ColorMapSpec:");
+ SB.AppendLine("\t\tColorMap Entry Size = " + Header.ColorMapSpec.ColorMapEntrySize);
+ SB.AppendLine("\t\tColorMap Length = " + Header.ColorMapSpec.ColorMapLength);
+ SB.AppendLine("\t\tFirstEntry Index = " + Header.ColorMapSpec.FirstEntryIndex);
+
+ SB.AppendLine("\nImage / Color Map Area:");
+ if (Header.IDLength > 0 && ImageOrColorMapArea.ImageID != null)
+ SB.AppendLine("\tImage ID = \"" + ImageOrColorMapArea.ImageID.GetString() + "\"");
+ else
+ SB.AppendLine("\tImage ID = null");
+
+ if (ImageOrColorMapArea.ImageData != null)
+ SB.AppendLine("\tImage Data Length = " + ImageOrColorMapArea.ImageData.Length);
+ else
+ SB.AppendLine("\tImage Data = null");
+
+ if (ImageOrColorMapArea.ColorMapData != null)
+ SB.AppendLine("\tColorMap Data Length = " + ImageOrColorMapArea.ColorMapData.Length);
+ else
+ SB.AppendLine("\tColorMap Data = null");
+
+ SB.AppendLine("\nDevelopers Area:");
+ if (DevArea != null)
+ SB.AppendLine("\tCount = " + DevArea.Count);
+ else
+ SB.AppendLine("\tDevArea = null");
+
+ SB.AppendLine("\nExtension Area:");
+ if (ExtArea != null)
+ {
+ SB.AppendLine("\tExtension Size = " + ExtArea.ExtensionSize);
+ SB.AppendLine("\tAuthor Name = \"" + ExtArea.AuthorName.GetString() + "\"");
+ SB.AppendLine("\tAuthor Comments = \"" + ExtArea.AuthorComments.GetString() + "\"");
+ SB.AppendLine("\tDate / Time Stamp = " + ExtArea.DateTimeStamp);
+ SB.AppendLine("\tJob Name / ID = \"" + ExtArea.JobNameOrID.GetString() + "\"");
+ SB.AppendLine("\tJob Time = " + ExtArea.JobTime);
+ SB.AppendLine("\tSoftware ID = \"" + ExtArea.SoftwareID.GetString() + "\"");
+ SB.AppendLine("\tSoftware Version = \"" + ExtArea.SoftVersion + "\"");
+ SB.AppendLine("\tKey Color = " + ExtArea.KeyColor);
+ SB.AppendLine("\tPixel Aspect Ratio = " + ExtArea.PixelAspectRatio);
+ SB.AppendLine("\tGamma Value = " + ExtArea.GammaValue);
+ SB.AppendLine("\tColor Correction Table Offset = " + ExtArea.ColorCorrectionTableOffset);
+ SB.AppendLine("\tPostage Stamp Offset = " + ExtArea.PostageStampOffset);
+ SB.AppendLine("\tScan Line Offset = " + ExtArea.ScanLineOffset);
+ SB.AppendLine("\tAttributes Type = " + ExtArea.AttributesType);
+
+ if (ExtArea.ScanLineTable != null)
+ SB.AppendLine("\tScan Line Table = " + ExtArea.ScanLineTable.Length);
+ else
+ SB.AppendLine("\tScan Line Table = null");
+
+ if (ExtArea.PostageStampImage != null)
+ SB.AppendLine("\tPostage Stamp Image: " + ExtArea.PostageStampImage.ToString());
+ else
+ SB.AppendLine("\tPostage Stamp Image = null");
+
+ SB.AppendLine("\tColor Correction Table = " + (ExtArea.ColorCorrectionTable != null));
+ }
+ else
+ SB.AppendLine("\tExtArea = null");
+
+ SB.AppendLine("\nFooter:");
+ if (Footer != null)
+ {
+ SB.AppendLine("\tExtension Area Offset = " + Footer.ExtensionAreaOffset);
+ SB.AppendLine("\tDeveloper Directory Offset = " + Footer.DeveloperDirectoryOffset);
+ SB.AppendLine("\tSignature (Full) = \"" + Footer.Signature.ToString() +
+ Footer.ReservedCharacter.ToString() + Footer.BinaryZeroStringTerminator.ToString() + "\"");
+ }
+ else
+ SB.AppendLine("\tFooter = null");
+
+ return SB.ToString();
+ }
+
+ ///
+ /// Check and update all fields with data length and offsets.
+ ///
+ /// Return "true", if all OK or "false", if checking failed.
+ public bool CheckAndUpdateOffsets(out string ErrorStr)
+ {
+ ErrorStr = String.Empty;
+
+ if (Header == null)
+ {
+ ErrorStr = "Header = null";
+ return false;
+ }
+
+ if (ImageOrColorMapArea == null)
+ {
+ ErrorStr = "ImageOrColorMapArea = null";
+ return false;
+ }
+
+ uint Offset = TgaHeader.Size; // Virtual Offset
+
+ #region Header
+ if (ImageOrColorMapArea.ImageID != null)
+ {
+ int StrMaxLen = 255;
+ if (ImageOrColorMapArea.ImageID.UseEndingChar)
+ StrMaxLen--;
+
+ Header.IDLength = (byte)Math.Min(ImageOrColorMapArea.ImageID.OriginalString.Length, StrMaxLen);
+ ImageOrColorMapArea.ImageID.Length = Header.IDLength;
+ Offset += Header.IDLength;
+ }
+ else
+ Header.IDLength = 0;
+ #endregion
+
+ #region ColorMap
+ if (Header.ColorMapType != TgaColorMapType.NoColorMap)
+ {
+ if (Header.ColorMapSpec == null)
+ {
+ ErrorStr = "Header.ColorMapSpec = null";
+ return false;
+ }
+
+ if (Header.ColorMapSpec.ColorMapLength == 0)
+ {
+ ErrorStr = "Header.ColorMapSpec.ColorMapLength = 0";
+ return false;
+ }
+
+ if (ImageOrColorMapArea.ColorMapData == null)
+ {
+ ErrorStr = "ImageOrColorMapArea.ColorMapData = null";
+ return false;
+ }
+
+ int CmBytesPerPixel = (int)Math.Ceiling((double)Header.ColorMapSpec.ColorMapEntrySize / 8.0);
+ int LenBytes = Header.ColorMapSpec.ColorMapLength * CmBytesPerPixel;
+
+ if (LenBytes != ImageOrColorMapArea.ColorMapData.Length)
+ {
+ ErrorStr = "ImageOrColorMapArea.ColorMapData.Length has wrong size!";
+ return false;
+ }
+
+ Offset += (uint)ImageOrColorMapArea.ColorMapData.Length;
+ }
+ #endregion
+
+ #region Image Data
+ int BytesPerPixel = 0;
+ if (Header.ImageType != TgaImageType.NoImageData)
+ {
+ if (Header.ImageSpec == null)
+ {
+ ErrorStr = "Header.ImageSpec = null";
+ return false;
+ }
+
+ if (Header.ImageSpec.ImageWidth == 0 || Header.ImageSpec.ImageHeight == 0)
+ {
+ ErrorStr = "Header.ImageSpec.ImageWidth = 0 or Header.ImageSpec.ImageHeight = 0";
+ return false;
+ }
+
+ if (ImageOrColorMapArea.ImageData == null)
+ {
+ ErrorStr = "ImageOrColorMapArea.ImageData = null";
+ return false;
+ }
+
+ BytesPerPixel = (int)Math.Ceiling((double)Header.ImageSpec.PixelDepth / 8.0);
+ if (Width * Height * BytesPerPixel != ImageOrColorMapArea.ImageData.Length)
+ {
+ ErrorStr = "ImageOrColorMapArea.ImageData.Length has wrong size!";
+ return false;
+ }
+
+ if (Header.ImageType >= TgaImageType.RLE_ColorMapped &&
+ Header.ImageType <= TgaImageType.RLE_BlackWhite)
+ {
+ byte[] RLE = RLE_Encode(ImageOrColorMapArea.ImageData, Width, Height);
+ if (RLE == null)
+ {
+ ErrorStr = "RLE Compressing error! Check Image Data size.";
+ return false;
+ }
+
+ Offset += (uint)RLE.Length;
+ RLE = null;
+ }
+ else
+ Offset += (uint)ImageOrColorMapArea.ImageData.Length;
+ }
+ #endregion
+
+ #region Footer, DevArea, ExtArea
+ if (Footer != null)
+ {
+ #region DevArea
+ if (DevArea != null)
+ {
+ int DevAreaCount = DevArea.Count;
+ for (int i = 0; i < DevAreaCount; i++)
+ if (DevArea[i] == null || DevArea[i].FieldSize <= 0) //Del Empty Entries
+ {
+ DevArea.Entries.RemoveAt(i);
+ DevAreaCount--;
+ i--;
+ }
+
+ if (DevArea.Count <= 0)
+ Footer.DeveloperDirectoryOffset = 0;
+
+ if (DevArea.Count > 2)
+ {
+ DevArea.Entries.Sort((a, b) => { return a.Tag.CompareTo(b.Tag); });
+ for (int i = 0; i < DevArea.Count - 1; i++)
+ if (DevArea[i].Tag == DevArea[i + 1].Tag)
+ {
+ ErrorStr = "DevArea Enties has same Tags!";
+ return false;
+ }
+ }
+
+ for (int i = 0; i < DevArea.Count; i++)
+ {
+ DevArea[i].Offset = Offset;
+ Offset += (uint)DevArea[i].FieldSize;
+ }
+
+ Footer.DeveloperDirectoryOffset = Offset;
+ Offset += (uint)(DevArea.Count * 10 + 2);
+ }
+ else
+ Footer.DeveloperDirectoryOffset = 0;
+ #endregion
+
+ #region ExtArea
+ if (ExtArea != null)
+ {
+ ExtArea.ExtensionSize = TgaExtArea.MinSize;
+ if (ExtArea.OtherDataInExtensionArea != null)
+ ExtArea.ExtensionSize += (ushort)ExtArea.OtherDataInExtensionArea.Length;
+
+ ExtArea.DateTimeStamp = new TgaDateTime(DateTime.UtcNow);
+
+ Footer.ExtensionAreaOffset = Offset;
+ Offset += ExtArea.ExtensionSize;
+
+ #region ScanLineTable
+ if (ExtArea.ScanLineTable == null)
+ ExtArea.ScanLineOffset = 0;
+ else
+ {
+ if (ExtArea.ScanLineTable.Length != Height)
+ {
+ ErrorStr = "ExtArea.ScanLineTable.Length != Height";
+ return false;
+ }
+
+ ExtArea.ScanLineOffset = Offset;
+ Offset += (uint)(ExtArea.ScanLineTable.Length * 4);
+ }
+ #endregion
+
+ #region PostageStampImage
+ if (ExtArea.PostageStampImage == null)
+ ExtArea.PostageStampOffset = 0;
+ else
+ {
+ if (ExtArea.PostageStampImage.Width == 0 || ExtArea.PostageStampImage.Height == 0)
+ {
+ ErrorStr = "ExtArea.PostageStampImage Width or Height is equal 0!";
+ return false;
+ }
+
+ if (ExtArea.PostageStampImage.Data == null)
+ {
+ ErrorStr = "ExtArea.PostageStampImage.Data == null";
+ return false;
+ }
+
+ int PImgSB = ExtArea.PostageStampImage.Width * ExtArea.PostageStampImage.Height * BytesPerPixel;
+ if (Header.ImageType != TgaImageType.NoImageData &&
+ ExtArea.PostageStampImage.Data.Length != PImgSB)
+ {
+ ErrorStr = "ExtArea.PostageStampImage.Data.Length is wrong!";
+ return false;
+ }
+
+
+ ExtArea.PostageStampOffset = Offset;
+ Offset += (uint)(ExtArea.PostageStampImage.Data.Length);
+ }
+ #endregion
+
+ #region ColorCorrectionTable
+ if (ExtArea.ColorCorrectionTable == null)
+ ExtArea.ColorCorrectionTableOffset = 0;
+ else
+ {
+ if (ExtArea.ColorCorrectionTable.Length != 1024)
+ {
+ ErrorStr = "ExtArea.ColorCorrectionTable.Length != 256 * 4";
+ return false;
+ }
+
+ ExtArea.ColorCorrectionTableOffset = Offset;
+ Offset += (uint)(ExtArea.ColorCorrectionTable.Length * 2);
+ }
+ #endregion
+ }
+ else
+ Footer.ExtensionAreaOffset = 0;
+ #endregion
+
+ #region Footer
+ if (Footer.ToBytes().Length != TgaFooter.Size)
+ {
+ ErrorStr = "Footer.Length is wrong!";
+ return false;
+ }
+
+ Offset += TgaFooter.Size;
+ #endregion
+ }
+ #endregion
+
+ return true;
+ }
+
+ #region Convert
+ ///
+ /// Convert to .
+ ///
+ /// Force use alpha channel.
+ /// Bitmap or null, on error.
+ public Bitmap ToBitmap(bool ForceUseAlpha = false)
+ {
+ return ToBitmapFunc(ForceUseAlpha, false);
+ }
+
+ ///
+ /// Convert to bytes array.
+ ///
+ /// Bytes array, (equal to saved file, but in memory) or null (on error).
+ public byte[] ToBytes()
+ {
+ try
+ {
+ byte[] Bytes;
+ using (MemoryStream ms = new MemoryStream())
+ {
+ Save(ms);
+ Bytes = ms.ToArray();
+ ms.Flush();
+ }
+ return Bytes;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Convert TGA Image to new XFile format (v2.0).
+ ///
+ public void ToNewFormat()
+ {
+ if (Footer == null)
+ Footer = new TgaFooter();
+
+ if (ExtArea == null)
+ {
+ ExtArea = new TgaExtArea();
+
+ ExtArea.DateTimeStamp = new TgaDateTime(DateTime.UtcNow);
+
+ if (Header.ImageSpec.ImageDescriptor.AlphaChannelBits > 0)
+ ExtArea.AttributesType = TgaAttrType.UsefulAlpha;
+ else
+ ExtArea.AttributesType = TgaAttrType.NoAlpha;
+ }
+ }
+ #endregion
+
+ #region Private functions
+ bool LoadFunc(string filename)
+ {
+ if (!File.Exists(filename))
+ throw new FileNotFoundException("File: \"" + filename + "\" not found!");
+
+ try
+ {
+ using (FileStream FS = new FileStream(filename, FileMode.Open))
+ return LoadFunc(FS);
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ bool LoadFunc(byte[] bytes)
+ {
+ if (bytes == null)
+ throw new ArgumentNullException();
+
+ try
+ {
+ using (MemoryStream FS = new MemoryStream(bytes, false))
+ return LoadFunc(FS);
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ bool LoadFunc(Stream stream)
+ {
+ if (stream == null)
+ throw new ArgumentNullException();
+ if (!(stream.CanRead && stream.CanSeek))
+ throw new FileLoadException("Stream reading or seeking is not avaiable!");
+
+ try
+ {
+ stream.Seek(0, SeekOrigin.Begin);
+ BinaryReader Br = new BinaryReader(stream);
+
+ Header = new TgaHeader(Br.ReadBytes(TgaHeader.Size));
+
+ if (Header.IDLength > 0)
+ ImageOrColorMapArea.ImageID = new TgaString(Br.ReadBytes(Header.IDLength));
+
+ if (Header.ColorMapSpec.ColorMapLength > 0)
+ {
+ int CmBytesPerPixel = (int)Math.Ceiling((double)Header.ColorMapSpec.ColorMapEntrySize / 8.0);
+ int LenBytes = Header.ColorMapSpec.ColorMapLength * CmBytesPerPixel;
+ ImageOrColorMapArea.ColorMapData = Br.ReadBytes(LenBytes);
+ }
+
+ #region Read Image Data
+ int BytesPerPixel = (int)Math.Ceiling((double)Header.ImageSpec.PixelDepth / 8.0);
+ if (Header.ImageType != TgaImageType.NoImageData)
+ {
+ int ImageDataSize = Width * Height * BytesPerPixel;
+ switch (Header.ImageType)
+ {
+ case TgaImageType.RLE_ColorMapped:
+ case TgaImageType.RLE_TrueColor:
+ case TgaImageType.RLE_BlackWhite:
+
+ int DataOffset = 0;
+ byte PacketInfo;
+ int PacketCount;
+ byte[] RLE_Bytes, RLE_Part;
+ ImageOrColorMapArea.ImageData = new byte[ImageDataSize];
+
+ do
+ {
+ PacketInfo = Br.ReadByte(); //1 type bit and 7 count bits. Len = Count + 1.
+ PacketCount = (PacketInfo & 127) + 1;
+
+ if (PacketInfo >= 128) // bit7 = 1, RLE
+ {
+ RLE_Bytes = new byte[PacketCount * BytesPerPixel];
+ RLE_Part = Br.ReadBytes(BytesPerPixel);
+ for (int i = 0; i < RLE_Bytes.Length; i++)
+ RLE_Bytes[i] = RLE_Part[i % BytesPerPixel];
+ }
+ else // RAW format
+ RLE_Bytes = Br.ReadBytes(PacketCount * BytesPerPixel);
+
+ Buffer.BlockCopy(RLE_Bytes, 0, ImageOrColorMapArea.ImageData, DataOffset, RLE_Bytes.Length);
+ DataOffset += RLE_Bytes.Length;
+ }
+ while (DataOffset < ImageDataSize);
+ RLE_Bytes = null;
+ break;
+
+ case TgaImageType.Uncompressed_ColorMapped:
+ case TgaImageType.Uncompressed_TrueColor:
+ case TgaImageType.Uncompressed_BlackWhite:
+ ImageOrColorMapArea.ImageData = Br.ReadBytes(ImageDataSize);
+ break;
+ }
+ }
+ #endregion
+
+ #region Try parse Footer
+ stream.Seek(-TgaFooter.Size, SeekOrigin.End);
+ uint FooterOffset = (uint)stream.Position;
+ TgaFooter MbFooter = new TgaFooter(Br.ReadBytes(TgaFooter.Size));
+ if (MbFooter.IsFooterCorrect)
+ {
+ Footer = MbFooter;
+ uint DevDirOffset = Footer.DeveloperDirectoryOffset;
+ uint ExtAreaOffset = Footer.ExtensionAreaOffset;
+
+ #region If Dev Area exist, read it.
+ if (DevDirOffset != 0)
+ {
+ stream.Seek(DevDirOffset, SeekOrigin.Begin);
+ DevArea = new TgaDevArea();
+ uint NumberOfTags = Br.ReadUInt16();
+
+ ushort[] Tags = new ushort[NumberOfTags];
+ uint[] TagOffsets = new uint[NumberOfTags];
+ uint[] TagSizes = new uint[NumberOfTags];
+
+ for (int i = 0; i < NumberOfTags; i++)
+ {
+ Tags[i] = Br.ReadUInt16();
+ TagOffsets[i] = Br.ReadUInt32();
+ TagSizes[i] = Br.ReadUInt32();
+ }
+
+ for (int i = 0; i < NumberOfTags; i++)
+ {
+ stream.Seek(TagOffsets[i], SeekOrigin.Begin);
+ var Ent = new TgaDevEntry(Tags[i], TagOffsets[i], Br.ReadBytes((int)TagSizes[i]));
+ DevArea.Entries.Add(Ent);
+ }
+
+ Tags = null;
+ TagOffsets = null;
+ TagSizes = null;
+ }
+ #endregion
+
+ #region If Ext Area exist, read it.
+ if (ExtAreaOffset != 0)
+ {
+ stream.Seek(ExtAreaOffset, SeekOrigin.Begin);
+ ushort ExtAreaSize = Math.Max((ushort)TgaExtArea.MinSize, Br.ReadUInt16());
+ stream.Seek(ExtAreaOffset, SeekOrigin.Begin);
+ ExtArea = new TgaExtArea(Br.ReadBytes(ExtAreaSize));
+
+ if (ExtArea.ScanLineOffset > 0)
+ {
+ stream.Seek(ExtArea.ScanLineOffset, SeekOrigin.Begin);
+ ExtArea.ScanLineTable = new uint[Height];
+ for (int i = 0; i < ExtArea.ScanLineTable.Length; i++)
+ ExtArea.ScanLineTable[i] = Br.ReadUInt32();
+ }
+
+ if (ExtArea.PostageStampOffset > 0)
+ {
+ stream.Seek(ExtArea.PostageStampOffset, SeekOrigin.Begin);
+ byte W = Br.ReadByte();
+ byte H = Br.ReadByte();
+ int ImgDataSize = W * H * BytesPerPixel;
+ if (ImgDataSize > 0)
+ ExtArea.PostageStampImage = new TgaPostageStampImage(W, H, Br.ReadBytes(ImgDataSize));
+ }
+
+ if (ExtArea.ColorCorrectionTableOffset > 0)
+ {
+ stream.Seek(ExtArea.ColorCorrectionTableOffset, SeekOrigin.Begin);
+ ExtArea.ColorCorrectionTable = new ushort[256 * 4];
+ for (int i = 0; i < ExtArea.ColorCorrectionTable.Length; i++)
+ ExtArea.ColorCorrectionTable[i] = Br.ReadUInt16();
+ }
+ }
+ #endregion
+ }
+ #endregion
+
+ Br.Close();
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ bool LoadFunc(Bitmap bmp, bool UseRLE = false, bool NewFormat = true, bool ColorMap2BytesEntry = false)
+ {
+ if (bmp == null)
+ throw new ArgumentNullException();
+
+ try
+ {
+ Header.ImageSpec.ImageWidth = (ushort)bmp.Width;
+ Header.ImageSpec.ImageHeight = (ushort)bmp.Height;
+ Header.ImageSpec.ImageDescriptor.ImageOrigin = TgaImgOrigin.TopLeft;
+
+ switch (bmp.PixelFormat)
+ {
+ case PixelFormat.Indexed:
+ case PixelFormat.Gdi:
+ case PixelFormat.Alpha:
+ case PixelFormat.Undefined:
+ case PixelFormat.PAlpha:
+ case PixelFormat.Extended:
+ case PixelFormat.Max:
+ case PixelFormat.Canonical:
+ case PixelFormat.Format16bppRgb565:
+ default:
+ throw new FormatException(nameof(PixelFormat) + " is not supported!");
+
+ case PixelFormat.Format1bppIndexed:
+ case PixelFormat.Format4bppIndexed:
+ case PixelFormat.Format8bppIndexed:
+ case PixelFormat.Format16bppGrayScale:
+ case PixelFormat.Format16bppRgb555:
+ case PixelFormat.Format16bppArgb1555:
+ case PixelFormat.Format24bppRgb:
+ case PixelFormat.Format32bppRgb:
+ case PixelFormat.Format32bppArgb:
+ case PixelFormat.Format32bppPArgb:
+ case PixelFormat.Format48bppRgb:
+ case PixelFormat.Format64bppArgb:
+ case PixelFormat.Format64bppPArgb:
+
+ int bpp = Math.Max(8, Image.GetPixelFormatSize(bmp.PixelFormat));
+ int BytesPP = bpp / 8;
+
+ if (bmp.PixelFormat == PixelFormat.Format16bppRgb555)
+ bpp = 15;
+
+ bool IsAlpha = Image.IsAlphaPixelFormat(bmp.PixelFormat);
+ bool IsPreAlpha = IsAlpha && bmp.PixelFormat.ToString().EndsWith("PArgb");
+ bool IsColorMapped = bmp.PixelFormat.ToString().EndsWith("Indexed");
+
+ Header.ImageSpec.PixelDepth = (TgaPixelDepth)(BytesPP * 8);
+
+ if (IsAlpha)
+ {
+ Header.ImageSpec.ImageDescriptor.AlphaChannelBits = (byte)(BytesPP * 2);
+
+ if (bmp.PixelFormat == PixelFormat.Format16bppArgb1555)
+ Header.ImageSpec.ImageDescriptor.AlphaChannelBits = 1;
+ }
+
+ #region ColorMap
+ bool IsGrayImage = (bmp.PixelFormat == PixelFormat.Format16bppGrayScale | IsColorMapped);
+
+ if (IsColorMapped && bmp.Palette != null)
+ {
+ Color[] Colors = bmp.Palette.Entries;
+
+ #region Analyze ColorMapType
+ int AlphaSum = 0;
+ bool ColorMapUseAlpha = false;
+
+ for (int i = 0; i < Colors.Length; i++)
+ {
+ IsGrayImage &= (Colors[i].R == Colors[i].G && Colors[i].G == Colors[i].B);
+ ColorMapUseAlpha |= (Colors[i].A < 248);
+ AlphaSum |= Colors[i].A;
+ }
+ ColorMapUseAlpha &= (AlphaSum > 0);
+
+ int CMapBpp = (ColorMap2BytesEntry ? 15 : 24) + (ColorMapUseAlpha ? (ColorMap2BytesEntry ? 1 : 8) : 0);
+ int CMBytesPP = (int)Math.Ceiling(CMapBpp / 8.0);
+ #endregion
+
+ Header.ColorMapSpec.ColorMapLength = Math.Min((ushort)Colors.Length, ushort.MaxValue);
+ Header.ColorMapSpec.ColorMapEntrySize = (TgaColorMapEntrySize)CMapBpp;
+ ImageOrColorMapArea.ColorMapData = new byte[Header.ColorMapSpec.ColorMapLength * CMBytesPP];
+
+ byte[] CMapEntry = new byte[CMBytesPP];
+
+ const float To5Bit = 32f / 256f; // Scale value from 8 to 5 bits.
+ for (int i = 0; i < Colors.Length; i++)
+ {
+ switch (Header.ColorMapSpec.ColorMapEntrySize)
+ {
+ case TgaColorMapEntrySize.A1R5G5B5:
+ case TgaColorMapEntrySize.X1R5G5B5:
+ int R = (int)(Colors[i].R * To5Bit);
+ int G = (int)(Colors[i].G * To5Bit) << 5;
+ int B = (int)(Colors[i].B * To5Bit) << 10;
+ int A = 0;
+
+ if (Header.ColorMapSpec.ColorMapEntrySize == TgaColorMapEntrySize.A1R5G5B5)
+ A = ((Colors[i].A & 0x80) << 15);
+
+ CMapEntry = BitConverter.GetBytes(A | R | G | B);
+ break;
+
+ case TgaColorMapEntrySize.R8G8B8:
+ CMapEntry[0] = Colors[i].B;
+ CMapEntry[1] = Colors[i].G;
+ CMapEntry[2] = Colors[i].R;
+ break;
+
+ case TgaColorMapEntrySize.A8R8G8B8:
+ CMapEntry[0] = Colors[i].B;
+ CMapEntry[1] = Colors[i].G;
+ CMapEntry[2] = Colors[i].R;
+ CMapEntry[3] = Colors[i].A;
+ break;
+
+ case TgaColorMapEntrySize.Other:
+ default:
+ break;
+ }
+
+ Buffer.BlockCopy(CMapEntry, 0, ImageOrColorMapArea.ColorMapData, i * CMBytesPP, CMBytesPP);
+ }
+ }
+ #endregion
+
+ #region ImageType
+ if (UseRLE)
+ {
+ if (IsGrayImage)
+ Header.ImageType = TgaImageType.RLE_BlackWhite;
+ else if (IsColorMapped)
+ Header.ImageType = TgaImageType.RLE_ColorMapped;
+ else
+ Header.ImageType = TgaImageType.RLE_TrueColor;
+ }
+ else
+ {
+ if (IsGrayImage)
+ Header.ImageType = TgaImageType.Uncompressed_BlackWhite;
+ else if (IsColorMapped)
+ Header.ImageType = TgaImageType.Uncompressed_ColorMapped;
+ else
+ Header.ImageType = TgaImageType.Uncompressed_TrueColor;
+ }
+
+ Header.ColorMapType = (IsColorMapped ? TgaColorMapType.ColorMap : TgaColorMapType.NoColorMap);
+ #endregion
+
+ #region NewFormat
+ if (NewFormat)
+ {
+ Footer = new TgaFooter();
+ ExtArea = new TgaExtArea();
+ ExtArea.DateTimeStamp = new TgaDateTime(DateTime.UtcNow);
+
+ if (IsAlpha)
+ {
+ ExtArea.AttributesType = TgaAttrType.UsefulAlpha;
+
+ if (IsPreAlpha)
+ ExtArea.AttributesType = TgaAttrType.PreMultipliedAlpha;
+ }
+ else
+ {
+ ExtArea.AttributesType = TgaAttrType.NoAlpha;
+
+ if (Header.ImageSpec.ImageDescriptor.AlphaChannelBits > 0)
+ ExtArea.AttributesType = TgaAttrType.UndefinedAlphaButShouldBeRetained;
+ }
+ }
+ #endregion
+
+ #region Bitmap width is aligned by 32 bits = 4 bytes! Delete it.
+ int StrideBytes = bmp.Width * BytesPP;
+ int PaddingBytes = (int)Math.Ceiling(StrideBytes / 4.0) * 4 - StrideBytes;
+
+ byte[] ImageData = new byte[(StrideBytes + PaddingBytes) * bmp.Height];
+
+ Rectangle Re = new Rectangle(0, 0, bmp.Width, bmp.Height);
+ BitmapData BmpData = bmp.LockBits(Re, ImageLockMode.ReadOnly, bmp.PixelFormat);
+ Marshal.Copy(BmpData.Scan0, ImageData, 0, ImageData.Length);
+ bmp.UnlockBits(BmpData);
+ BmpData = null;
+
+ if (PaddingBytes > 0) //Need delete bytes align
+ {
+ ImageOrColorMapArea.ImageData = new byte[StrideBytes * bmp.Height];
+ for (int i = 0; i < bmp.Height; i++)
+ Buffer.BlockCopy(ImageData, i * (StrideBytes + PaddingBytes),
+ ImageOrColorMapArea.ImageData, i * StrideBytes, StrideBytes);
+ }
+ else
+ ImageOrColorMapArea.ImageData = ImageData;
+
+ ImageData = null;
+
+ // Not official supported, but works (tested on 16bpp GrayScale test images)!
+ if (bmp.PixelFormat == PixelFormat.Format16bppGrayScale)
+ {
+ for (long i = 0; i < ImageOrColorMapArea.ImageData.Length; i++)
+ ImageOrColorMapArea.ImageData[i] ^= byte.MaxValue;
+ }
+ #endregion
+
+ break;
+ }
+
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ bool SaveFunc(Stream stream)
+ {
+ try
+ {
+ if (stream == null)
+ throw new ArgumentNullException();
+ if (!(stream.CanWrite && stream.CanSeek))
+ throw new FileLoadException("Stream writing or seeking is not avaiable!");
+
+ string CheckResult;
+ if (!CheckAndUpdateOffsets(out CheckResult))
+ return false;
+
+ BinaryWriter Bw = new BinaryWriter(stream);
+ Bw.Write(Header.ToBytes());
+
+ if (ImageOrColorMapArea.ImageID != null)
+ Bw.Write(ImageOrColorMapArea.ImageID.ToBytes());
+
+ if (Header.ColorMapType != TgaColorMapType.NoColorMap)
+ Bw.Write(ImageOrColorMapArea.ColorMapData);
+
+ #region ImageData
+ if (Header.ImageType != TgaImageType.NoImageData)
+ {
+ if (Header.ImageType >= TgaImageType.RLE_ColorMapped &&
+ Header.ImageType <= TgaImageType.RLE_BlackWhite)
+ Bw.Write(RLE_Encode(ImageOrColorMapArea.ImageData, Width, Height));
+ else
+ Bw.Write(ImageOrColorMapArea.ImageData);
+ }
+ #endregion
+
+ #region Footer
+ if (Footer != null)
+ {
+ #region DevArea
+ if (DevArea != null)
+ {
+ for (int i = 0; i < DevArea.Count; i++)
+ Bw.Write(DevArea[i].Data);
+
+ Bw.Write((ushort)DevArea.Count);
+
+ for (int i = 0; i < DevArea.Count; i++)
+ {
+ Bw.Write(DevArea[i].Tag);
+ Bw.Write(DevArea[i].Offset);
+ Bw.Write(DevArea[i].FieldSize);
+ }
+ }
+ #endregion
+
+ #region ExtArea
+ if (ExtArea != null)
+ {
+ Bw.Write(ExtArea.ToBytes());
+
+ if (ExtArea.ScanLineTable != null)
+ for (int i = 0; i < ExtArea.ScanLineTable.Length; i++)
+ Bw.Write(ExtArea.ScanLineTable[i]);
+
+ if (ExtArea.PostageStampImage != null)
+ Bw.Write(ExtArea.PostageStampImage.ToBytes());
+
+ if (ExtArea.ColorCorrectionTable != null)
+ for (int i = 0; i < ExtArea.ColorCorrectionTable.Length; i++)
+ Bw.Write(ExtArea.ColorCorrectionTable[i]);
+ }
+ #endregion
+
+ Bw.Write(Footer.ToBytes());
+ }
+ #endregion
+
+ Bw.Flush();
+ stream.Flush();
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Encode image with RLE compression (used RLE per line)!
+ ///
+ /// Image data, bytes array with size = Width * Height * BytesPerPixel.
+ /// Image Width, must be > 0.
+ /// Image Height, must be > 0.
+ /// Bytes array with RLE compressed image data.
+ byte[] RLE_Encode(byte[] ImageData, int Width, int Height)
+ {
+ if (ImageData == null)
+ throw new ArgumentNullException(nameof(ImageData) + "in null!");
+
+ if (Width <= 0 || Height <= 0)
+ throw new ArgumentOutOfRangeException(nameof(Width) + " and " + nameof(Height) + " must be > 0!");
+
+ int Bpp = ImageData.Length / Width / Height; // Bytes per pixel
+ int ScanLineSize = Width * Bpp;
+
+ if (ScanLineSize * Height != ImageData.Length)
+ throw new ArgumentOutOfRangeException("ImageData has wrong Length!");
+
+ try
+ {
+ int Count = 0;
+ int Pos = 0;
+ bool IsRLE = false;
+ List Encoded = new List();
+ byte[] RowData = new byte[ScanLineSize];
+
+ for (int y = 0; y < Height; y++)
+ {
+ Pos = 0;
+ Buffer.BlockCopy(ImageData, y * ScanLineSize, RowData, 0, ScanLineSize);
+
+ while (Pos < ScanLineSize)
+ {
+ if (Pos >= ScanLineSize - Bpp)
+ {
+ Encoded.Add(0);
+ Encoded.AddRange(BitConverterExt.GetElements(RowData, Pos, Bpp));
+ Pos += Bpp;
+ break;
+ }
+
+ Count = 0; //1
+ IsRLE = BitConverterExt.IsElementsEqual(RowData, Pos, Pos + Bpp, Bpp);
+
+ for (int i = Pos + Bpp; i < Math.Min(Pos + 128 * Bpp, ScanLineSize) - Bpp; i += Bpp)
+ {
+ if (IsRLE ^ BitConverterExt.IsElementsEqual(RowData, (IsRLE ? Pos : i), i + Bpp, Bpp))
+ {
+ //Count--;
+ break;
+ }
+ else
+ Count++;
+ }
+
+ int CountBpp = (Count + 1) * Bpp;
+ Encoded.Add((byte)(IsRLE ? Count | 128 : Count));
+ Encoded.AddRange(BitConverterExt.GetElements(RowData, Pos, (IsRLE ? Bpp : CountBpp)));
+ Pos += CountBpp;
+ }
+ }
+
+ return Encoded.ToArray();
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Convert to .
+ ///
+ /// Force use alpha channel.
+ /// Get Postage Stamp Image (Thumb) or get main image?
+ /// Bitmap or null, on error.
+ Bitmap ToBitmapFunc(bool ForceUseAlpha = false, bool PostageStampImage = false)
+ {
+ try
+ {
+ #region UseAlpha?
+ bool UseAlpha = true;
+ if (ExtArea != null)
+ {
+ switch (ExtArea.AttributesType)
+ {
+ case TgaAttrType.NoAlpha:
+ case TgaAttrType.UndefinedAlphaCanBeIgnored:
+ case TgaAttrType.UndefinedAlphaButShouldBeRetained:
+ UseAlpha = false;
+ break;
+ case TgaAttrType.UsefulAlpha:
+ case TgaAttrType.PreMultipliedAlpha:
+ default:
+ break;
+ }
+ }
+ UseAlpha = (Header.ImageSpec.ImageDescriptor.AlphaChannelBits > 0 && UseAlpha) | ForceUseAlpha;
+ #endregion
+
+ #region IsGrayImage
+ bool IsGrayImage = Header.ImageType == TgaImageType.RLE_BlackWhite ||
+ Header.ImageType == TgaImageType.Uncompressed_BlackWhite;
+ #endregion
+
+ #region Get PixelFormat
+ PixelFormat PixFormat = PixelFormat.Format24bppRgb;
+
+ switch (Header.ImageSpec.PixelDepth)
+ {
+ case TgaPixelDepth.Bpp8:
+ PixFormat = PixelFormat.Format8bppIndexed;
+ break;
+
+ case TgaPixelDepth.Bpp16:
+ if (IsGrayImage)
+ PixFormat = PixelFormat.Format16bppGrayScale;
+ else
+ PixFormat = (UseAlpha ? PixelFormat.Format16bppArgb1555 : PixelFormat.Format16bppRgb555);
+ break;
+
+ case TgaPixelDepth.Bpp24:
+ PixFormat = PixelFormat.Format24bppRgb;
+ break;
+
+ case TgaPixelDepth.Bpp32:
+ if (UseAlpha)
+ {
+ var f = Footer;
+ if (ExtArea?.AttributesType == TgaAttrType.PreMultipliedAlpha)
+ PixFormat = PixelFormat.Format32bppPArgb;
+ else
+ PixFormat = PixelFormat.Format32bppArgb;
+ }
+ else
+ PixFormat = PixelFormat.Format32bppRgb;
+ break;
+
+ default:
+ PixFormat = PixelFormat.Undefined;
+ break;
+ }
+ #endregion
+
+ ushort BMP_Width = (PostageStampImage ? ExtArea.PostageStampImage.Width : Width);
+ ushort BMP_Height = (PostageStampImage ? ExtArea.PostageStampImage.Height : Height);
+ Bitmap BMP = new Bitmap(BMP_Width, BMP_Height, PixFormat);
+
+ #region ColorMap and GrayPalette
+ if (Header.ColorMapType == TgaColorMapType.ColorMap &&
+ (Header.ImageType == TgaImageType.RLE_ColorMapped ||
+ Header.ImageType == TgaImageType.Uncompressed_ColorMapped))
+ {
+
+ ColorPalette ColorMap = BMP.Palette;
+ Color[] CMapColors = ColorMap.Entries;
+
+ switch (Header.ColorMapSpec.ColorMapEntrySize)
+ {
+ case TgaColorMapEntrySize.X1R5G5B5:
+ case TgaColorMapEntrySize.A1R5G5B5:
+ const float To8Bit = 255f / 31f; // Scale value from 5 to 8 bits.
+ for (int i = 0; i < Math.Min(CMapColors.Length, Header.ColorMapSpec.ColorMapLength); i++)
+ {
+ ushort A1R5G5B5 = BitConverter.ToUInt16(ImageOrColorMapArea.ColorMapData, i * 2);
+ int A = (UseAlpha ? (A1R5G5B5 & 0x8000) >> 15 : 1) * 255; // (0 or 1) * 255
+ int R = (int)(((A1R5G5B5 & 0x7C00) >> 10) * To8Bit);
+ int G = (int)(((A1R5G5B5 & 0x3E0) >> 5) * To8Bit);
+ int B = (int)((A1R5G5B5 & 0x1F) * To8Bit);
+ CMapColors[i] = Color.FromArgb(A, R, G, B);
+ }
+ break;
+
+ case TgaColorMapEntrySize.R8G8B8:
+ for (int i = 0; i < Math.Min(CMapColors.Length, Header.ColorMapSpec.ColorMapLength); i++)
+ {
+ int Index = i * 3; //RGB = 3 bytes
+ int R = ImageOrColorMapArea.ColorMapData[Index + 2];
+ int G = ImageOrColorMapArea.ColorMapData[Index + 1];
+ int B = ImageOrColorMapArea.ColorMapData[Index];
+ CMapColors[i] = Color.FromArgb(R, G, B);
+ }
+ break;
+
+ case TgaColorMapEntrySize.A8R8G8B8:
+ for (int i = 0; i < Math.Min(CMapColors.Length, Header.ColorMapSpec.ColorMapLength); i++)
+ {
+ int ARGB = BitConverter.ToInt32(ImageOrColorMapArea.ColorMapData, i * 4);
+ CMapColors[i] = Color.FromArgb(UseAlpha ? ARGB | (0xFF << 24) : ARGB);
+ }
+ break;
+
+ default:
+ ColorMap = null;
+ break;
+ }
+
+ if (ColorMap != null)
+ BMP.Palette = ColorMap;
+ }
+
+ if (PixFormat == PixelFormat.Format8bppIndexed && IsGrayImage)
+ {
+ ColorPalette GrayPalette = BMP.Palette;
+ Color[] GrayColors = GrayPalette.Entries;
+ for (int i = 0; i < GrayColors.Length; i++)
+ GrayColors[i] = Color.FromArgb(i, i, i);
+ BMP.Palette = GrayPalette;
+ }
+ #endregion
+
+ #region Bitmap width must by aligned (align value = 32 bits = 4 bytes)!
+ byte[] ImageData;
+ int BytesPerPixel = (int)Math.Ceiling((double)Header.ImageSpec.PixelDepth / 8.0);
+ int StrideBytes = BMP.Width * BytesPerPixel;
+ int PaddingBytes = (int)Math.Ceiling(StrideBytes / 4.0) * 4 - StrideBytes;
+
+ if (PaddingBytes > 0) //Need bytes align
+ {
+ ImageData = new byte[(StrideBytes + PaddingBytes) * BMP.Height];
+ for (int i = 0; i < BMP.Height; i++)
+ Buffer.BlockCopy(PostageStampImage ? ExtArea.PostageStampImage.Data :
+ ImageOrColorMapArea.ImageData, i * StrideBytes, ImageData,
+ i * (StrideBytes + PaddingBytes), StrideBytes);
+ }
+ else
+ ImageData = BitConverterExt.ToBytes(PostageStampImage ? ExtArea.PostageStampImage.Data :
+ ImageOrColorMapArea.ImageData);
+
+ // Not official supported, but works (tested on 2 test images)!
+ if (PixFormat == PixelFormat.Format16bppGrayScale)
+ {
+ for (long i = 0; i < ImageData.Length; i++)
+ ImageData[i] ^= byte.MaxValue;
+ }
+ #endregion
+
+ Rectangle Re = new Rectangle(0, 0, BMP.Width, BMP.Height);
+ BitmapData BmpData = BMP.LockBits(Re, ImageLockMode.WriteOnly, BMP.PixelFormat);
+ Marshal.Copy(ImageData, 0, BmpData.Scan0, ImageData.Length);
+ BMP.UnlockBits(BmpData);
+ ImageData = null;
+ BmpData = null;
+
+ if (ExtArea != null && ExtArea.KeyColor.ToInt() != 0)
+ BMP.MakeTransparent(ExtArea.KeyColor.ToColor());
+
+ #region Flip Image
+ switch (Header.ImageSpec.ImageDescriptor.ImageOrigin)
+ {
+ case TgaImgOrigin.BottomLeft:
+ BMP.RotateFlip(RotateFlipType.RotateNoneFlipY);
+ break;
+ case TgaImgOrigin.BottomRight:
+ BMP.RotateFlip(RotateFlipType.RotateNoneFlipXY);
+ break;
+ case TgaImgOrigin.TopLeft:
+ default:
+ break;
+ case TgaImgOrigin.TopRight:
+ BMP.RotateFlip(RotateFlipType.RotateNoneFlipX);
+ break;
+ }
+ #endregion
+
+ return BMP;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+ #endregion
+
+ #region Explicit
+ public static explicit operator Bitmap(TGA tga)
+ {
+ return tga.ToBitmap();
+ }
+
+ public static explicit operator TGA(Bitmap bmp)
+ {
+ return FromBitmap(bmp);
+ }
+ #endregion
+
+ #region PostageStamp Image
+ ///
+ /// Convert to .
+ ///
+ /// Force use alpha channel.
+ /// Bitmap or null.
+ public Bitmap GetPostageStampImage(bool ForceUseAlpha = false)
+ {
+ if (ExtArea == null || ExtArea.PostageStampImage == null || ExtArea.PostageStampImage.Data == null ||
+ ExtArea.PostageStampImage.Width <= 0 || ExtArea.PostageStampImage.Height <= 0)
+ return null;
+
+ return ToBitmapFunc(ForceUseAlpha, true);
+ }
+
+ ///
+ /// Update Postage Stamp Image or set it.
+ ///
+ public void UpdatePostageStampImage()
+ {
+ if (Header.ImageType == TgaImageType.NoImageData)
+ {
+ if (ExtArea != null)
+ ExtArea.PostageStampImage = null;
+ return;
+ }
+
+ ToNewFormat();
+ if (ExtArea.PostageStampImage == null)
+ ExtArea.PostageStampImage = new TgaPostageStampImage();
+
+ int PS_Width = Header.ImageSpec.ImageWidth;
+ int PS_Height = Header.ImageSpec.ImageHeight;
+
+ if (Width > 64 || Height > 64)
+ {
+ float AspectRatio = Width / (float)Height;
+ PS_Width = (byte)(64f * (AspectRatio < 1f ? AspectRatio : 1f));
+ PS_Height = (byte)(64f / (AspectRatio > 1f ? AspectRatio : 1f));
+ }
+ PS_Width = Math.Max(PS_Width, 4);
+ PS_Height = Math.Max(PS_Height, 4);
+
+ ExtArea.PostageStampImage.Width = (byte)PS_Width;
+ ExtArea.PostageStampImage.Height = (byte)PS_Height;
+
+ int BytesPerPixel = (int)Math.Ceiling((double)Header.ImageSpec.PixelDepth / 8.0);
+ ExtArea.PostageStampImage.Data = new byte[PS_Width * PS_Height * BytesPerPixel];
+
+ float WidthCoef = Width / (float)PS_Width;
+ float HeightCoef = Height / (float)PS_Height;
+
+ for (int y = 0; y < PS_Height; y++)
+ {
+ int Y_Offset = (int)(y * HeightCoef) * Width * BytesPerPixel;
+ int y_Offset = y * PS_Width * BytesPerPixel;
+
+ for (int x = 0; x < PS_Width; x++)
+ {
+ Buffer.BlockCopy(ImageOrColorMapArea.ImageData, Y_Offset + (int)(x * WidthCoef) * BytesPerPixel,
+ ExtArea.PostageStampImage.Data, y_Offset + x * BytesPerPixel, BytesPerPixel);
+ }
+ }
+ }
+
+ public void DeletePostageStampImage()
+ {
+ if (ExtArea != null)
+ ExtArea.PostageStampImage = null;
+ }
+ #endregion
+ }
+}
diff --git a/AssetStudioUtility/Texture2DExtensions.cs b/AssetStudioUtility/Texture2DExtensions.cs
index 6301f1af..bc24c78d 100644
--- a/AssetStudioUtility/Texture2DExtensions.cs
+++ b/AssetStudioUtility/Texture2DExtensions.cs
@@ -1,31 +1,34 @@
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Runtime.InteropServices;
using System.IO;
namespace AssetStudio
{
public static class Texture2DExtensions
{
- public static Image ConvertToImage(this Texture2D m_Texture2D, bool flip)
+ public static Bitmap ConvertToBitmap(this Texture2D m_Texture2D, bool flip)
{
var converter = new Texture2DConverter(m_Texture2D);
var bytes = converter.DecodeTexture2D();
if (bytes != null && bytes.Length > 0)
{
- var image = Image.LoadPixelData(bytes, m_Texture2D.m_Width, m_Texture2D.m_Height);
+ var bitmap = new Bitmap(m_Texture2D.m_Width, m_Texture2D.m_Height, PixelFormat.Format32bppArgb);
+ var bmpData = bitmap.LockBits(new Rectangle(0, 0, m_Texture2D.m_Width, m_Texture2D.m_Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
+ Marshal.Copy(bytes, 0, bmpData.Scan0, bytes.Length);
+ bitmap.UnlockBits(bmpData);
if (flip)
{
- image.Mutate(x => x.Flip(FlipMode.Vertical));
+ bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
}
- return image;
+ return bitmap;
}
return null;
}
public static MemoryStream ConvertToStream(this Texture2D m_Texture2D, ImageFormat imageFormat, bool flip)
{
- var image = ConvertToImage(m_Texture2D, flip);
+ var image = ConvertToBitmap(m_Texture2D, flip);
if (image != null)
{
using (image)