From bedee240beca501089bf01acc95648e843a57358 Mon Sep 17 00:00:00 2001 From: Perfare Date: Sun, 27 Jun 2021 07:33:20 +0800 Subject: [PATCH] Use ImageSharp to process textures --- AssetStudioGUI/AssetStudioGUIForm.cs | 12 +- AssetStudioGUI/ExportOptions.Designer.cs | 140 +- AssetStudioGUI/ExportOptions.cs | 14 +- AssetStudioGUI/Exporter.cs | 80 +- .../Properties/Settings.Designer.cs | 14 +- AssetStudioGUI/Properties/Settings.settings | 4 +- AssetStudioGUI/app.config | 18 +- AssetStudioUtility/AssetStudioUtility.csproj | 45 +- AssetStudioUtility/ImageExtensions.cs | 39 + AssetStudioUtility/ImageFormat.cs | 10 + AssetStudioUtility/ModelConverter.cs | 38 +- AssetStudioUtility/SpriteHelper.cs | 115 +- AssetStudioUtility/TGASharpLib.cs | 5963 ----------------- AssetStudioUtility/Texture2DConverter.cs | 23 - AssetStudioUtility/Texture2DExtensions.cs | 32 +- AssetStudioUtility/app.config | 15 + AssetStudioUtility/packages.config | 12 + 17 files changed, 332 insertions(+), 6242 deletions(-) create mode 100644 AssetStudioUtility/ImageExtensions.cs create mode 100644 AssetStudioUtility/ImageFormat.cs delete mode 100644 AssetStudioUtility/TGASharpLib.cs create mode 100644 AssetStudioUtility/app.config diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index e96bc13..1321905 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -19,6 +19,7 @@ using System.Timers; using System.Windows.Forms; using static AssetStudioGUI.Studio; using Font = AssetStudio.Font; +using ImageFormat = AssetStudio.ImageFormat; using PixelFormat = System.Drawing.Imaging.PixelFormat; using Vector3 = OpenTK.Vector3; using Vector4 = OpenTK.Vector4; @@ -741,9 +742,10 @@ namespace AssetStudioGUI private void PreviewTexture2D(AssetItem assetItem, Texture2D m_Texture2D) { - var bitmap = m_Texture2D.ConvertToBitmap(true); - if (bitmap != null) + var stream = m_Texture2D.ConvertToStream(ImageFormat.Png, true); + if (stream != 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) { @@ -1151,11 +1153,11 @@ namespace AssetStudioGUI private void PreviewSprite(AssetItem assetItem, Sprite m_Sprite) { - var bitmap = m_Sprite.GetImage(); - if (bitmap != null) + var stream = m_Sprite.GetImage(ImageFormat.Png); + if (stream != null) { + var bitmap = new Bitmap(stream); assetItem.InfoText = $"Width: {bitmap.Width}\nHeight: {bitmap.Height}\n"; - PreviewTexture(bitmap); } else diff --git a/AssetStudioGUI/ExportOptions.Designer.cs b/AssetStudioGUI/ExportOptions.Designer.cs index 02d8fe7..ee9ba01 100644 --- a/AssetStudioGUI/ExportOptions.Designer.cs +++ b/AssetStudioGUI/ExportOptions.Designer.cs @@ -72,9 +72,9 @@ // // OKbutton // - this.OKbutton.Location = new System.Drawing.Point(309, 381); + this.OKbutton.Location = new System.Drawing.Point(318, 351); this.OKbutton.Name = "OKbutton"; - this.OKbutton.Size = new System.Drawing.Size(75, 23); + this.OKbutton.Size = new System.Drawing.Size(75, 21); this.OKbutton.TabIndex = 6; this.OKbutton.Text = "OK"; this.OKbutton.UseVisualStyleBackColor = true; @@ -83,9 +83,9 @@ // Cancel // this.Cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.Cancel.Location = new System.Drawing.Point(390, 381); + this.Cancel.Location = new System.Drawing.Point(399, 351); this.Cancel.Name = "Cancel"; - this.Cancel.Size = new System.Drawing.Size(75, 23); + this.Cancel.Size = new System.Drawing.Size(75, 21); this.Cancel.TabIndex = 7; this.Cancel.Text = "Cancel"; this.Cancel.UseVisualStyleBackColor = true; @@ -101,9 +101,9 @@ this.groupBox1.Controls.Add(this.convertAudio); this.groupBox1.Controls.Add(this.panel1); this.groupBox1.Controls.Add(this.converttexture); - this.groupBox1.Location = new System.Drawing.Point(12, 13); + this.groupBox1.Location = new System.Drawing.Point(12, 12); this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(232, 362); + this.groupBox1.Size = new System.Drawing.Size(232, 334); this.groupBox1.TabIndex = 9; this.groupBox1.TabStop = false; this.groupBox1.Text = "Export"; @@ -113,9 +113,9 @@ this.openAfterExport.AutoSize = true; this.openAfterExport.Checked = true; this.openAfterExport.CheckState = System.Windows.Forms.CheckState.Checked; - this.openAfterExport.Location = new System.Drawing.Point(6, 173); + this.openAfterExport.Location = new System.Drawing.Point(6, 160); this.openAfterExport.Name = "openAfterExport"; - this.openAfterExport.Size = new System.Drawing.Size(137, 17); + this.openAfterExport.Size = new System.Drawing.Size(168, 16); this.openAfterExport.TabIndex = 10; this.openAfterExport.Text = "Open folder after export"; this.openAfterExport.UseVisualStyleBackColor = true; @@ -125,9 +125,9 @@ this.restoreExtensionName.AutoSize = true; this.restoreExtensionName.Checked = true; this.restoreExtensionName.CheckState = System.Windows.Forms.CheckState.Checked; - this.restoreExtensionName.Location = new System.Drawing.Point(6, 63); + this.restoreExtensionName.Location = new System.Drawing.Point(6, 58); this.restoreExtensionName.Name = "restoreExtensionName"; - this.restoreExtensionName.Size = new System.Drawing.Size(190, 17); + this.restoreExtensionName.Size = new System.Drawing.Size(216, 16); this.restoreExtensionName.TabIndex = 9; this.restoreExtensionName.Text = "Restore TextAsset extension name"; this.restoreExtensionName.UseVisualStyleBackColor = true; @@ -141,17 +141,17 @@ "container path", "source file name", "do not group"}); - this.assetGroupOptions.Location = new System.Drawing.Point(6, 35); + this.assetGroupOptions.Location = new System.Drawing.Point(6, 32); this.assetGroupOptions.Name = "assetGroupOptions"; - this.assetGroupOptions.Size = new System.Drawing.Size(149, 21); + this.assetGroupOptions.Size = new System.Drawing.Size(149, 20); this.assetGroupOptions.TabIndex = 8; // // label6 // this.label6.AutoSize = true; - this.label6.Location = new System.Drawing.Point(6, 18); + this.label6.Location = new System.Drawing.Point(6, 17); this.label6.Name = "label6"; - this.label6.Size = new System.Drawing.Size(127, 13); + this.label6.Size = new System.Drawing.Size(149, 12); this.label6.TabIndex = 7; this.label6.Text = "Group exported assets by"; // @@ -160,9 +160,9 @@ this.convertAudio.AutoSize = true; this.convertAudio.Checked = true; this.convertAudio.CheckState = System.Windows.Forms.CheckState.Checked; - this.convertAudio.Location = new System.Drawing.Point(6, 150); + this.convertAudio.Location = new System.Drawing.Point(6, 138); this.convertAudio.Name = "convertAudio"; - this.convertAudio.Size = new System.Drawing.Size(179, 17); + this.convertAudio.Size = new System.Drawing.Size(198, 16); this.convertAudio.TabIndex = 6; this.convertAudio.Text = "Convert AudioClip to WAV(PCM)"; this.convertAudio.UseVisualStyleBackColor = true; @@ -173,51 +173,51 @@ this.panel1.Controls.Add(this.tojpg); this.panel1.Controls.Add(this.topng); this.panel1.Controls.Add(this.tobmp); - this.panel1.Location = new System.Drawing.Point(20, 111); + this.panel1.Location = new System.Drawing.Point(20, 102); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(202, 33); + this.panel1.Size = new System.Drawing.Size(202, 30); this.panel1.TabIndex = 5; // // totga // this.totga.AutoSize = true; - this.totga.Location = new System.Drawing.Point(150, 7); + this.totga.Location = new System.Drawing.Point(150, 6); this.totga.Name = "totga"; - this.totga.Size = new System.Drawing.Size(47, 17); + this.totga.Size = new System.Drawing.Size(41, 16); this.totga.TabIndex = 2; - this.totga.Text = "TGA"; + this.totga.Text = "Tga"; this.totga.UseVisualStyleBackColor = true; // // tojpg // this.tojpg.AutoSize = true; - this.tojpg.Location = new System.Drawing.Point(97, 7); + this.tojpg.Location = new System.Drawing.Point(97, 6); this.tojpg.Name = "tojpg"; - this.tojpg.Size = new System.Drawing.Size(52, 17); + this.tojpg.Size = new System.Drawing.Size(47, 16); this.tojpg.TabIndex = 4; - this.tojpg.Text = "JPEG"; + this.tojpg.Text = "Jpeg"; this.tojpg.UseVisualStyleBackColor = true; // // topng // this.topng.AutoSize = true; this.topng.Checked = true; - this.topng.Location = new System.Drawing.Point(50, 7); + this.topng.Location = new System.Drawing.Point(50, 6); this.topng.Name = "topng"; - this.topng.Size = new System.Drawing.Size(48, 17); + this.topng.Size = new System.Drawing.Size(41, 16); this.topng.TabIndex = 3; this.topng.TabStop = true; - this.topng.Text = "PNG"; + this.topng.Text = "Png"; this.topng.UseVisualStyleBackColor = true; // // tobmp // this.tobmp.AutoSize = true; - this.tobmp.Location = new System.Drawing.Point(3, 7); + this.tobmp.Location = new System.Drawing.Point(3, 6); this.tobmp.Name = "tobmp"; - this.tobmp.Size = new System.Drawing.Size(48, 17); + this.tobmp.Size = new System.Drawing.Size(41, 16); this.tobmp.TabIndex = 2; - this.tobmp.Text = "BMP"; + this.tobmp.Text = "Bmp"; this.tobmp.UseVisualStyleBackColor = true; // // converttexture @@ -225,9 +225,9 @@ this.converttexture.AutoSize = true; this.converttexture.Checked = true; this.converttexture.CheckState = System.Windows.Forms.CheckState.Checked; - this.converttexture.Location = new System.Drawing.Point(6, 87); + this.converttexture.Location = new System.Drawing.Point(6, 80); this.converttexture.Name = "converttexture"; - this.converttexture.Size = new System.Drawing.Size(116, 17); + this.converttexture.Size = new System.Drawing.Size(126, 16); this.converttexture.TabIndex = 1; this.converttexture.Text = "Convert Texture2D"; this.converttexture.UseVisualStyleBackColor = true; @@ -252,9 +252,9 @@ this.groupBox2.Controls.Add(this.castToBone); this.groupBox2.Controls.Add(this.exportAllNodes); this.groupBox2.Controls.Add(this.eulerFilter); - this.groupBox2.Location = new System.Drawing.Point(250, 13); + this.groupBox2.Location = new System.Drawing.Point(250, 12); this.groupBox2.Name = "groupBox2"; - this.groupBox2.Size = new System.Drawing.Size(214, 362); + this.groupBox2.Size = new System.Drawing.Size(224, 334); this.groupBox2.TabIndex = 11; this.groupBox2.TabStop = false; this.groupBox2.Text = "Fbx"; @@ -263,9 +263,9 @@ // this.exportAllUvsAsDiffuseMaps.AccessibleDescription = ""; this.exportAllUvsAsDiffuseMaps.AutoSize = true; - this.exportAllUvsAsDiffuseMaps.Location = new System.Drawing.Point(6, 184); + this.exportAllUvsAsDiffuseMaps.Location = new System.Drawing.Point(6, 171); this.exportAllUvsAsDiffuseMaps.Name = "exportAllUvsAsDiffuseMaps"; - this.exportAllUvsAsDiffuseMaps.Size = new System.Drawing.Size(168, 17); + this.exportAllUvsAsDiffuseMaps.Size = new System.Drawing.Size(204, 16); this.exportAllUvsAsDiffuseMaps.TabIndex = 23; this.exportAllUvsAsDiffuseMaps.Text = "Export all UVs as diffuse maps"; this.exportUvsTooltip.SetToolTip(this.exportAllUvsAsDiffuseMaps, "Unchecked: UV1 exported as normal map. Check this if your export is missing a UV " + @@ -277,9 +277,9 @@ this.exportBlendShape.AutoSize = true; this.exportBlendShape.Checked = true; this.exportBlendShape.CheckState = System.Windows.Forms.CheckState.Checked; - this.exportBlendShape.Location = new System.Drawing.Point(6, 138); + this.exportBlendShape.Location = new System.Drawing.Point(6, 127); this.exportBlendShape.Name = "exportBlendShape"; - this.exportBlendShape.Size = new System.Drawing.Size(114, 17); + this.exportBlendShape.Size = new System.Drawing.Size(126, 16); this.exportBlendShape.TabIndex = 22; this.exportBlendShape.Text = "Export blendshape"; this.exportBlendShape.UseVisualStyleBackColor = true; @@ -289,9 +289,9 @@ this.exportAnimations.AutoSize = true; this.exportAnimations.Checked = true; this.exportAnimations.CheckState = System.Windows.Forms.CheckState.Checked; - this.exportAnimations.Location = new System.Drawing.Point(6, 114); + this.exportAnimations.Location = new System.Drawing.Point(6, 105); this.exportAnimations.Name = "exportAnimations"; - this.exportAnimations.Size = new System.Drawing.Size(109, 17); + this.exportAnimations.Size = new System.Drawing.Size(126, 16); this.exportAnimations.TabIndex = 21; this.exportAnimations.Text = "Export animations"; this.exportAnimations.UseVisualStyleBackColor = true; @@ -304,9 +304,9 @@ 0, 0, 131072}); - this.scaleFactor.Location = new System.Drawing.Point(83, 237); + this.scaleFactor.Location = new System.Drawing.Point(83, 224); this.scaleFactor.Name = "scaleFactor"; - this.scaleFactor.Size = new System.Drawing.Size(60, 20); + this.scaleFactor.Size = new System.Drawing.Size(60, 21); this.scaleFactor.TabIndex = 20; this.scaleFactor.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; this.scaleFactor.Value = new decimal(new int[] { @@ -318,9 +318,9 @@ // label5 // this.label5.AutoSize = true; - this.label5.Location = new System.Drawing.Point(6, 239); + this.label5.Location = new System.Drawing.Point(6, 226); this.label5.Name = "label5"; - this.label5.Size = new System.Drawing.Size(64, 13); + this.label5.Size = new System.Drawing.Size(71, 12); this.label5.TabIndex = 19; this.label5.Text = "ScaleFactor"; // @@ -331,17 +331,17 @@ this.fbxFormat.Items.AddRange(new object[] { "Binary", "Ascii"}); - this.fbxFormat.Location = new System.Drawing.Point(77, 270); + this.fbxFormat.Location = new System.Drawing.Point(77, 254); this.fbxFormat.Name = "fbxFormat"; - this.fbxFormat.Size = new System.Drawing.Size(61, 21); + this.fbxFormat.Size = new System.Drawing.Size(61, 20); this.fbxFormat.TabIndex = 18; // // label4 // this.label4.AutoSize = true; - this.label4.Location = new System.Drawing.Point(6, 274); + this.label4.Location = new System.Drawing.Point(6, 258); this.label4.Name = "label4"; - this.label4.Size = new System.Drawing.Size(59, 13); + this.label4.Size = new System.Drawing.Size(59, 12); this.label4.TabIndex = 17; this.label4.Text = "FBXFormat"; // @@ -356,25 +356,25 @@ "7.3", "7.4", "7.5"}); - this.fbxVersion.Location = new System.Drawing.Point(77, 302); + this.fbxVersion.Location = new System.Drawing.Point(77, 284); this.fbxVersion.Name = "fbxVersion"; - this.fbxVersion.Size = new System.Drawing.Size(47, 21); + this.fbxVersion.Size = new System.Drawing.Size(47, 20); this.fbxVersion.TabIndex = 16; // // label3 // this.label3.AutoSize = true; - this.label3.Location = new System.Drawing.Point(6, 305); + this.label3.Location = new System.Drawing.Point(6, 287); this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(62, 13); + this.label3.Size = new System.Drawing.Size(65, 12); this.label3.TabIndex = 15; this.label3.Text = "FBXVersion"; // // boneSize // - this.boneSize.Location = new System.Drawing.Point(65, 208); + this.boneSize.Location = new System.Drawing.Point(65, 197); this.boneSize.Name = "boneSize"; - this.boneSize.Size = new System.Drawing.Size(46, 20); + this.boneSize.Size = new System.Drawing.Size(46, 21); this.boneSize.TabIndex = 11; this.boneSize.Value = new decimal(new int[] { 10, @@ -385,9 +385,9 @@ // label2 // this.label2.AutoSize = true; - this.label2.Location = new System.Drawing.Point(6, 210); + this.label2.Location = new System.Drawing.Point(6, 199); this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(52, 13); + this.label2.Size = new System.Drawing.Size(53, 12); this.label2.TabIndex = 10; this.label2.Text = "BoneSize"; // @@ -396,9 +396,9 @@ this.exportSkins.AutoSize = true; this.exportSkins.Checked = true; this.exportSkins.CheckState = System.Windows.Forms.CheckState.Checked; - this.exportSkins.Location = new System.Drawing.Point(6, 90); + this.exportSkins.Location = new System.Drawing.Point(6, 83); this.exportSkins.Name = "exportSkins"; - this.exportSkins.Size = new System.Drawing.Size(83, 17); + this.exportSkins.Size = new System.Drawing.Size(96, 16); this.exportSkins.TabIndex = 8; this.exportSkins.Text = "Export skins"; this.exportSkins.UseVisualStyleBackColor = true; @@ -406,9 +406,9 @@ // label1 // this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(26, 42); + this.label1.Location = new System.Drawing.Point(26, 39); this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(72, 13); + this.label1.Size = new System.Drawing.Size(95, 12); this.label1.TabIndex = 7; this.label1.Text = "FilterPrecision"; // @@ -420,9 +420,9 @@ 0, 0, 131072}); - this.filterPrecision.Location = new System.Drawing.Point(127, 40); + this.filterPrecision.Location = new System.Drawing.Point(127, 37); this.filterPrecision.Name = "filterPrecision"; - this.filterPrecision.Size = new System.Drawing.Size(51, 20); + this.filterPrecision.Size = new System.Drawing.Size(51, 21); this.filterPrecision.TabIndex = 6; this.filterPrecision.Value = new decimal(new int[] { 25, @@ -433,9 +433,9 @@ // castToBone // this.castToBone.AutoSize = true; - this.castToBone.Location = new System.Drawing.Point(6, 161); + this.castToBone.Location = new System.Drawing.Point(6, 149); this.castToBone.Name = "castToBone"; - this.castToBone.Size = new System.Drawing.Size(131, 17); + this.castToBone.Size = new System.Drawing.Size(156, 16); this.castToBone.TabIndex = 5; this.castToBone.Text = "All nodes cast to bone"; this.castToBone.UseVisualStyleBackColor = true; @@ -445,9 +445,9 @@ this.exportAllNodes.AutoSize = true; this.exportAllNodes.Checked = true; this.exportAllNodes.CheckState = System.Windows.Forms.CheckState.Checked; - this.exportAllNodes.Location = new System.Drawing.Point(6, 66); + this.exportAllNodes.Location = new System.Drawing.Point(6, 61); this.exportAllNodes.Name = "exportAllNodes"; - this.exportAllNodes.Size = new System.Drawing.Size(101, 17); + this.exportAllNodes.Size = new System.Drawing.Size(120, 16); this.exportAllNodes.TabIndex = 4; this.exportAllNodes.Text = "Export all nodes"; this.exportAllNodes.UseVisualStyleBackColor = true; @@ -457,9 +457,9 @@ this.eulerFilter.AutoSize = true; this.eulerFilter.Checked = true; this.eulerFilter.CheckState = System.Windows.Forms.CheckState.Checked; - this.eulerFilter.Location = new System.Drawing.Point(6, 22); + this.eulerFilter.Location = new System.Drawing.Point(6, 20); this.eulerFilter.Name = "eulerFilter"; - this.eulerFilter.Size = new System.Drawing.Size(72, 17); + this.eulerFilter.Size = new System.Drawing.Size(90, 16); this.eulerFilter.TabIndex = 3; this.eulerFilter.Text = "EulerFilter"; this.eulerFilter.UseVisualStyleBackColor = true; @@ -467,10 +467,10 @@ // ExportOptions // this.AcceptButton = this.OKbutton; - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.Cancel; - this.ClientSize = new System.Drawing.Size(477, 416); + this.ClientSize = new System.Drawing.Size(486, 384); this.Controls.Add(this.groupBox2); this.Controls.Add(this.groupBox1); this.Controls.Add(this.Cancel); diff --git a/AssetStudioGUI/ExportOptions.cs b/AssetStudioGUI/ExportOptions.cs index 47b7ece..117ceda 100644 --- a/AssetStudioGUI/ExportOptions.cs +++ b/AssetStudioGUI/ExportOptions.cs @@ -1,11 +1,5 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.IO; -using System.Linq; -using System.Text; +using AssetStudio; +using System; using System.Windows.Forms; namespace AssetStudioGUI @@ -19,7 +13,7 @@ namespace AssetStudioGUI restoreExtensionName.Checked = Properties.Settings.Default.restoreExtensionName; converttexture.Checked = Properties.Settings.Default.convertTexture; convertAudio.Checked = Properties.Settings.Default.convertAudio; - var str = Properties.Settings.Default.convertType; + var str = Properties.Settings.Default.convertType.ToString(); foreach (Control c in panel1.Controls) { if (c.Text == str) @@ -54,7 +48,7 @@ namespace AssetStudioGUI { if (((RadioButton)c).Checked) { - Properties.Settings.Default.convertType = c.Text; + Properties.Settings.Default.convertType = (ImageFormat)Enum.Parse(typeof(ImageFormat), c.Text); break; } } diff --git a/AssetStudioGUI/Exporter.cs b/AssetStudioGUI/Exporter.cs index b501996..5e97e4f 100644 --- a/AssetStudioGUI/Exporter.cs +++ b/AssetStudioGUI/Exporter.cs @@ -1,11 +1,9 @@ -using System.Collections.Generic; -using System.Drawing.Imaging; +using AssetStudio; +using Newtonsoft.Json; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using AssetStudio; -using Newtonsoft.Json; -using TGASharpLib; namespace AssetStudioGUI { @@ -16,40 +14,17 @@ namespace AssetStudioGUI var m_Texture2D = (Texture2D)item.Asset; if (Properties.Settings.Default.convertTexture) { - var bitmap = m_Texture2D.ConvertToBitmap(true); - if (bitmap == null) + var type = Properties.Settings.Default.convertType; + if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath)) return false; - ImageFormat format = null; - var ext = Properties.Settings.Default.convertType; - bool tga = false; - switch (ext) - { - case "BMP": - format = ImageFormat.Bmp; - break; - case "PNG": - format = ImageFormat.Png; - break; - case "JPEG": - format = ImageFormat.Jpeg; - break; - case "TGA": - tga = true; - break; - } - if (!TryExportFile(exportPath, item, "." + ext.ToLower(), out var exportFullPath)) + var stream = m_Texture2D.ConvertToStream(type, true); + if (stream == null) return false; - if (tga) + using (stream) { - var file = new TGA(bitmap); - file.Save(exportFullPath); + File.WriteAllBytes(exportFullPath, stream.ToArray()); + return true; } - else - { - bitmap.Save(exportFullPath, format); - } - bitmap.Dispose(); - return true; } else { @@ -252,40 +227,17 @@ namespace AssetStudioGUI public static bool ExportSprite(AssetItem item, string exportPath) { - ImageFormat format = null; var type = Properties.Settings.Default.convertType; - bool tga = false; - switch (type) - { - case "BMP": - format = ImageFormat.Bmp; - break; - case "PNG": - format = ImageFormat.Png; - break; - case "JPEG": - format = ImageFormat.Jpeg; - break; - case "TGA": - tga = true; - break; - } - if (!TryExportFile(exportPath, item, "." + type.ToLower(), out var exportFullPath)) + if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath)) return false; - var bitmap = ((Sprite)item.Asset).GetImage(); - if (bitmap != null) + var stream = ((Sprite)item.Asset).GetImage(type); + if (stream != null) { - if (tga) + using (stream) { - var file = new TGA(bitmap); - file.Save(exportFullPath); + File.WriteAllBytes(exportFullPath, stream.ToArray()); + return true; } - else - { - bitmap.Save(exportFullPath, format); - } - bitmap.Dispose(); - return true; } return false; } diff --git a/AssetStudioGUI/Properties/Settings.Designer.cs b/AssetStudioGUI/Properties/Settings.Designer.cs index 82393e1..2ecec40 100644 --- a/AssetStudioGUI/Properties/Settings.Designer.cs +++ b/AssetStudioGUI/Properties/Settings.Designer.cs @@ -1,10 +1,10 @@ //------------------------------------------------------------------------------ // -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 // -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 // //------------------------------------------------------------------------------ @@ -109,10 +109,10 @@ namespace AssetStudioGUI.Properties { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("PNG")] - public string convertType { + [global::System.Configuration.DefaultSettingValueAttribute("Png")] + public global::AssetStudio.ImageFormat convertType { get { - return ((string)(this["convertType"])); + return ((global::AssetStudio.ImageFormat)(this["convertType"])); } set { this["convertType"] = value; diff --git a/AssetStudioGUI/Properties/Settings.settings b/AssetStudioGUI/Properties/Settings.settings index db9a5ea..d6eb751 100644 --- a/AssetStudioGUI/Properties/Settings.settings +++ b/AssetStudioGUI/Properties/Settings.settings @@ -23,8 +23,8 @@ True - - PNG + + Png True diff --git a/AssetStudioGUI/app.config b/AssetStudioGUI/app.config index f9dbe6e..ef38ca4 100644 --- a/AssetStudioGUI/app.config +++ b/AssetStudioGUI/app.config @@ -1,7 +1,7 @@ - + - +
@@ -32,7 +32,7 @@ True - PNG + Png True @@ -75,4 +75,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/AssetStudioUtility/AssetStudioUtility.csproj b/AssetStudioUtility/AssetStudioUtility.csproj index c5d11ce..da967d6 100644 --- a/AssetStudioUtility/AssetStudioUtility.csproj +++ b/AssetStudioUtility/AssetStudioUtility.csproj @@ -37,9 +37,48 @@ ..\packages\Mono.Cecil.0.11.3\lib\net40\Mono.Cecil.dll + + ..\packages\SixLabors.Fonts.1.0.0-beta15\lib\netstandard2.0\SixLabors.Fonts.dll + + + ..\packages\SixLabors.ImageSharp.1.0.3\lib\net472\SixLabors.ImageSharp.dll + + + ..\packages\SixLabors.ImageSharp.Drawing.1.0.0-beta13\lib\net472\SixLabors.ImageSharp.Drawing.dll + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + - + + ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + True + True + + + ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + True + True + + + ..\packages\System.IO.UnmanagedMemoryStream.4.3.0\lib\net46\System.IO.UnmanagedMemoryStream.dll + True + True + + + ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.7.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll + @@ -63,11 +102,12 @@ + + - @@ -107,6 +147,7 @@ + diff --git a/AssetStudioUtility/ImageExtensions.cs b/AssetStudioUtility/ImageExtensions.cs new file mode 100644 index 0000000..7306c45 --- /dev/null +++ b/AssetStudioUtility/ImageExtensions.cs @@ -0,0 +1,39 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Tga; +using System.IO; + +namespace AssetStudio +{ + public static class ImageExtensions + { + public static MemoryStream ConvertToStream(this Image image, ImageFormat imageFormat) + { + var outputStream = new MemoryStream(); + switch (imageFormat) + { + case ImageFormat.Jpeg: + image.SaveAsJpeg(outputStream); + break; + case ImageFormat.Png: + image.SaveAsPng(outputStream); + break; + case ImageFormat.Bmp: + image.Save(outputStream, new BmpEncoder + { + BitsPerPixel = BmpBitsPerPixel.Pixel32, + SupportTransparency = true + }); + break; + case ImageFormat.Tga: + image.Save(outputStream, new TgaEncoder + { + BitsPerPixel = TgaBitsPerPixel.Pixel32, + Compression = TgaCompression.None + }); + break; + } + return outputStream; + } + } +} diff --git a/AssetStudioUtility/ImageFormat.cs b/AssetStudioUtility/ImageFormat.cs new file mode 100644 index 0000000..e2f4b8b --- /dev/null +++ b/AssetStudioUtility/ImageFormat.cs @@ -0,0 +1,10 @@ +namespace AssetStudio +{ + public enum ImageFormat + { + Jpeg, + Png, + Bmp, + Tga + } +} diff --git a/AssetStudioUtility/ModelConverter.cs b/AssetStudioUtility/ModelConverter.cs index e757dcf..2f46d8c 100644 --- a/AssetStudioUtility/ModelConverter.cs +++ b/AssetStudioUtility/ModelConverter.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; -using System.Drawing.Imaging; -using System.IO; using System.Linq; using System.Text; -using TGASharpLib; namespace AssetStudio { @@ -17,7 +14,7 @@ namespace AssetStudio public List AnimationList { get; protected set; } = new List(); public List MorphList { get; protected set; } = new List(); - private string imageFormat; + private ImageFormat imageFormat; private Avatar avatar; private HashSet animationClipHashSet = new HashSet(); private Dictionary bonePathHash = new Dictionary(); @@ -25,7 +22,7 @@ namespace AssetStudio private Dictionary transformDictionary = new Dictionary(); Dictionary morphChannelNames = new Dictionary(); - public ModelConverter(GameObject m_GameObject, string imageFormat, AnimationClip[] animationList = null) + public ModelConverter(GameObject m_GameObject, ImageFormat imageFormat, AnimationClip[] animationList = null) { this.imageFormat = imageFormat; if (m_GameObject.m_Animator != null) @@ -50,7 +47,7 @@ namespace AssetStudio ConvertAnimations(); } - public ModelConverter(string rootName, List m_GameObjects, string imageFormat, AnimationClip[] animationList = null) + public ModelConverter(string rootName, List m_GameObjects, ImageFormat imageFormat, AnimationClip[] animationList = null) { this.imageFormat = imageFormat; RootFrame = CreateFrame(rootName, Vector3.Zero, new Quaternion(0, 0, 0, 0), Vector3.One); @@ -80,7 +77,7 @@ namespace AssetStudio ConvertAnimations(); } - public ModelConverter(Animator m_Animator, string imageFormat, AnimationClip[] animationList = null) + public ModelConverter(Animator m_Animator, ImageFormat imageFormat, AnimationClip[] animationList = null) { this.imageFormat = imageFormat; InitWithAnimator(m_Animator); @@ -319,7 +316,7 @@ namespace AssetStudio } ImportedMaterial iMat = ConvertMaterial(mat); iSubmesh.Material = iMat.Name; - iSubmesh.BaseVertex = (int) mesh.m_SubMeshes[i].firstVertex; + iSubmesh.BaseVertex = (int)mesh.m_SubMeshes[i].firstVertex; //Face iSubmesh.FaceList = new List(numFaces); @@ -699,7 +696,7 @@ namespace AssetStudio texture.Dest = dest; - var ext = $".{imageFormat.ToLower()}"; + var ext = $".{imageFormat.ToString().ToLower()}"; if (textureNameDictionary.TryGetValue(m_Texture2D, out var textureName)) { texture.Name = textureName; @@ -745,30 +742,13 @@ namespace AssetStudio return; } - var bitmap = m_Texture2D.ConvertToBitmap(true); - if (bitmap != null) + var stream = m_Texture2D.ConvertToStream(imageFormat, true); + if (stream != null) { - using (var stream = new MemoryStream()) + using (stream) { - switch (imageFormat) - { - case "BMP": - bitmap.Save(stream, ImageFormat.Bmp); - break; - case "PNG": - bitmap.Save(stream, ImageFormat.Png); - break; - case "JPEG": - bitmap.Save(stream, ImageFormat.Jpeg); - break; - case "TGA": - var tga = new TGA(bitmap); - tga.Save(stream); - break; - } iTex = new ImportedTexture(stream, name); TextureList.Add(iTex); - bitmap.Dispose(); } } } diff --git a/AssetStudioUtility/SpriteHelper.cs b/AssetStudioUtility/SpriteHelper.cs index 3cd095c..891cada 100644 --- a/AssetStudioUtility/SpriteHelper.cs +++ b/AssetStudioUtility/SpriteHelper.cs @@ -1,15 +1,31 @@ -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; +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.IO; using System.Linq; +using System.Numerics; namespace AssetStudio { public static class SpriteHelper { - public static Bitmap GetImage(this Sprite m_Sprite) + public static MemoryStream GetImage(this Sprite m_Sprite, ImageFormat imageFormat) + { + var image = GetImage(m_Sprite); + if (image != null) + { + using (image) + { + return image.ConvertToStream(imageFormat); + } + } + return null; + } + + public static Image GetImage(this Sprite m_Sprite) { if (m_Sprite.m_SpriteAtlas != null && m_Sprite.m_SpriteAtlas.TryGet(out var m_SpriteAtlas)) { @@ -28,46 +44,32 @@ namespace AssetStudio return null; } - private static Bitmap CutImage(Texture2D m_Texture2D, Sprite m_Sprite, Rectf textureRect, Vector2 textureRectOffset, SpriteSettings settingsRaw) + private static Image CutImage(Texture2D m_Texture2D, Sprite m_Sprite, Rectf textureRect, Vector2 textureRectOffset, SpriteSettings settingsRaw) { - var originalImage = m_Texture2D.ConvertToBitmap(false); + var originalImage = m_Texture2D.ConvertToImage(false); if (originalImage != null) { using (originalImage) { - //var spriteImage = originalImage.Clone(textureRect, PixelFormat.Format32bppArgb); var rectf = new RectangleF(textureRect.x, textureRect.y, textureRect.width, textureRect.height); - var rect = Rectangle.Round(rectf); - if (rect.Width == 0) - { - rect.Width = 1; - } - if (rect.Height == 0) - { - rect.Height = 1; - } - 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); - } + var rect = Rectangle.Ceiling(rectf); + var spriteImage = originalImage.Clone(x => x.Crop(rect)); if (settingsRaw.packed == 1) { //RotateAndFlip switch (settingsRaw.packingRotation) { case SpritePackingRotation.kSPRFlipHorizontal: - spriteImage.RotateFlip(RotateFlipType.RotateNoneFlipX); + spriteImage.Mutate(x => x.Flip(FlipMode.Horizontal)); break; case SpritePackingRotation.kSPRFlipVertical: - spriteImage.RotateFlip(RotateFlipType.RotateNoneFlipY); + spriteImage.Mutate(x => x.Flip(FlipMode.Vertical)); break; case SpritePackingRotation.kSPRRotate180: - spriteImage.RotateFlip(RotateFlipType.Rotate180FlipNone); + spriteImage.Mutate(x => x.Rotate(180)); break; case SpritePackingRotation.kSPRRotate90: - spriteImage.RotateFlip(RotateFlipType.Rotate270FlipNone); + spriteImage.Mutate(x => x.Rotate(270)); break; } } @@ -78,41 +80,32 @@ namespace AssetStudio try { var triangles = GetTriangles(m_Sprite.m_RD); - var points = triangles.Select(x => x.Select(y => new PointF(y.X, y.Y)).ToArray()); - using (var path = new GraphicsPath()) + 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 { - foreach (var p in points) - { - path.AddPolygon(p); - } - 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; - } - } - } + matrix *= Matrix3x2.CreateTranslation(m_Sprite.m_Rect.width * 0.5f - textureRectOffset.X, m_Sprite.m_Rect.height * 0.5f - textureRectOffset.Y); } + else + { + 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() + { + AlphaCompositionMode = PixelAlphaCompositionMode.DestOut + } + }; + 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; } catch { @@ -121,7 +114,7 @@ namespace AssetStudio } //Rectangle - spriteImage.RotateFlip(RotateFlipType.RotateNoneFlipY); + spriteImage.Mutate(x => x.Flip(FlipMode.Vertical)); return spriteImage; } } diff --git a/AssetStudioUtility/TGASharpLib.cs b/AssetStudioUtility/TGASharpLib.cs deleted file mode 100644 index 7a38b65..0000000 --- a/AssetStudioUtility/TGASharpLib.cs +++ /dev/null @@ -1,5963 +0,0 @@ -/* 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/Texture2DConverter.cs b/AssetStudioUtility/Texture2DConverter.cs index b79a790..a9610ba 100644 --- a/AssetStudioUtility/Texture2DConverter.cs +++ b/AssetStudioUtility/Texture2DConverter.cs @@ -1,8 +1,5 @@ using System; -using System.Drawing; -using System.Drawing.Imaging; using System.Linq; -using System.Runtime.InteropServices; using Texture2DDecoder; namespace AssetStudio @@ -28,26 +25,6 @@ namespace AssetStudio platform = m_Texture2D.platform; } - public Bitmap ConvertToBitmap(bool flip) - { - if (image_data == null || image_data.Length == 0) - return null; - var buff = DecodeTexture2D(); - if (buff == null) - { - return null; - } - var bitmap = new Bitmap(m_Width, m_Height, PixelFormat.Format32bppArgb); - var bmpData = bitmap.LockBits(new Rectangle(0, 0, m_Width, m_Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); - Marshal.Copy(buff, 0, bmpData.Scan0, buff.Length); - bitmap.UnlockBits(bmpData); - if (flip) - { - bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY); - } - return bitmap; - } - public byte[] DecodeTexture2D() { byte[] bytes = null; diff --git a/AssetStudioUtility/Texture2DExtensions.cs b/AssetStudioUtility/Texture2DExtensions.cs index 132ca91..6301f1a 100644 --- a/AssetStudioUtility/Texture2DExtensions.cs +++ b/AssetStudioUtility/Texture2DExtensions.cs @@ -1,13 +1,39 @@ -using System.Drawing; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using System.IO; namespace AssetStudio { public static class Texture2DExtensions { - public static Bitmap ConvertToBitmap(this Texture2D m_Texture2D, bool flip) + public static Image ConvertToImage(this Texture2D m_Texture2D, bool flip) { var converter = new Texture2DConverter(m_Texture2D); - return converter.ConvertToBitmap(flip); + var bytes = converter.DecodeTexture2D(); + if (bytes != null && bytes.Length > 0) + { + var image = Image.LoadPixelData(bytes, m_Texture2D.m_Width, m_Texture2D.m_Height); + if (flip) + { + image.Mutate(x => x.Flip(FlipMode.Vertical)); + } + return image; + } + return null; + } + + public static MemoryStream ConvertToStream(this Texture2D m_Texture2D, ImageFormat imageFormat, bool flip) + { + var image = ConvertToImage(m_Texture2D, flip); + if (image != null) + { + using (image) + { + return image.ConvertToStream(imageFormat); + } + } + return null; } } } diff --git a/AssetStudioUtility/app.config b/AssetStudioUtility/app.config new file mode 100644 index 0000000..fbe4f92 --- /dev/null +++ b/AssetStudioUtility/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AssetStudioUtility/packages.config b/AssetStudioUtility/packages.config index 85c0c98..b3e8f50 100644 --- a/AssetStudioUtility/packages.config +++ b/AssetStudioUtility/packages.config @@ -1,4 +1,16 @@  + + + + + + + + + + + + \ No newline at end of file