commit fd52928ff9c32ef43be852840196726d0b20f7ca Author: Hipstercat Date: Mon Mar 9 15:38:36 2020 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..611428f --- /dev/null +++ b/.gitignore @@ -0,0 +1,353 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/GiantsMapTextureChanger.sln b/GiantsMapTextureChanger.sln new file mode 100644 index 0000000..3374586 --- /dev/null +++ b/GiantsMapTextureChanger.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.572 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GiantsMapTextureChanger", "GiantsMapTextureChanger\GiantsMapTextureChanger.csproj", "{06A0FCD6-9A1C-40BD-AF5C-546DE39CF4F0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {06A0FCD6-9A1C-40BD-AF5C-546DE39CF4F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06A0FCD6-9A1C-40BD-AF5C-546DE39CF4F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06A0FCD6-9A1C-40BD-AF5C-546DE39CF4F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {06A0FCD6-9A1C-40BD-AF5C-546DE39CF4F0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {279855BA-1FFC-423C-A703-A10DDF62260C} + EndGlobalSection +EndGlobal diff --git a/GiantsMapTextureChanger/App.config b/GiantsMapTextureChanger/App.config new file mode 100644 index 0000000..2d2a12d --- /dev/null +++ b/GiantsMapTextureChanger/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/GiantsMapTextureChanger/ChangeTextureForm.Designer.cs b/GiantsMapTextureChanger/ChangeTextureForm.Designer.cs new file mode 100644 index 0000000..3c1cd67 --- /dev/null +++ b/GiantsMapTextureChanger/ChangeTextureForm.Designer.cs @@ -0,0 +1,291 @@ +namespace GiantsMapTextureChanger +{ + partial class ChangeTextureForm + { + /// + /// Variable nécessaire au concepteur. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Nettoyage des ressources utilisées. + /// + /// true si les ressources managées doivent être supprimées ; sinon, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Code généré par le Concepteur Windows Form + + /// + /// Méthode requise pour la prise en charge du concepteur - ne modifiez pas + /// le contenu de cette méthode avec l'éditeur de code. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ChangeTextureForm)); + this.mapFileDialog = new System.Windows.Forms.OpenFileDialog(); + this.Separator1 = new System.Windows.Forms.Label(); + this.button1 = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.texturesinmapList = new System.Windows.Forms.ListBox(); + this.exportButton = new System.Windows.Forms.Button(); + this.status = new System.Windows.Forms.StatusStrip(); + this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel(); + this.gameFileDialog = new System.Windows.Forms.OpenFileDialog(); + this.textureList = new System.Windows.Forms.ComboBox(); + this.ThumbImage = new System.Windows.Forms.PictureBox(); + this.saveTextureDialog = new System.Windows.Forms.SaveFileDialog(); + this.label2 = new System.Windows.Forms.Label(); + this.button2 = new System.Windows.Forms.Button(); + this.button3 = new System.Windows.Forms.Button(); + this.textureAddDialog = new System.Windows.Forms.OpenFileDialog(); + this.saveMapDialog = new System.Windows.Forms.SaveFileDialog(); + this.label3 = new System.Windows.Forms.Label(); + this.openmapButton = new System.Windows.Forms.Button(); + this.status.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.ThumbImage)).BeginInit(); + this.SuspendLayout(); + // + // mapFileDialog + // + this.mapFileDialog.Filter = "Giants map file (*.gck)|*.gck"; + this.mapFileDialog.Title = "Enter path to the Giants map you want to modify"; + // + // Separator1 + // + this.Separator1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Right))); + this.Separator1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.Separator1.Location = new System.Drawing.Point(261, 15); + this.Separator1.Name = "Separator1"; + this.Separator1.Size = new System.Drawing.Size(2, 485); + this.Separator1.TabIndex = 1; + // + // button1 + // + this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.button1.Enabled = false; + this.button1.Location = new System.Drawing.Point(19, 52); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(220, 39); + this.button1.TabIndex = 2; + this.button1.Text = "Add texture to map"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 186); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(129, 20); + this.label1.TabIndex = 3; + this.label1.Text = "Possible textures"; + // + // texturesinmapList + // + this.texturesinmapList.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Right))); + this.texturesinmapList.FormattingEnabled = true; + this.texturesinmapList.ItemHeight = 20; + this.texturesinmapList.Location = new System.Drawing.Point(294, 52); + this.texturesinmapList.Name = "texturesinmapList"; + this.texturesinmapList.Size = new System.Drawing.Size(341, 384); + this.texturesinmapList.TabIndex = 4; + this.texturesinmapList.SelectedIndexChanged += new System.EventHandler(this.texturesinmapList_SelectedIndexChanged); + // + // exportButton + // + this.exportButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.exportButton.Enabled = false; + this.exportButton.Location = new System.Drawing.Point(16, 254); + this.exportButton.Name = "exportButton"; + this.exportButton.Size = new System.Drawing.Size(220, 38); + this.exportButton.TabIndex = 5; + this.exportButton.Text = "Export texture..."; + this.exportButton.UseVisualStyleBackColor = true; + this.exportButton.Click += new System.EventHandler(this.exportButton_Click); + // + // status + // + this.status.ImageScalingSize = new System.Drawing.Size(24, 24); + this.status.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.toolStripStatusLabel1}); + this.status.Location = new System.Drawing.Point(0, 514); + this.status.Name = "status"; + this.status.Size = new System.Drawing.Size(679, 22); + this.status.TabIndex = 6; + // + // toolStripStatusLabel1 + // + this.toolStripStatusLabel1.Name = "toolStripStatusLabel1"; + this.toolStripStatusLabel1.Size = new System.Drawing.Size(0, 17); + // + // gameFileDialog + // + this.gameFileDialog.Filter = "Giants.exe (Giants.exe)|Giants.exe"; + this.gameFileDialog.Title = "Enter path to Giants.exe"; + // + // textureList + // + this.textureList.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.textureList.Enabled = false; + this.textureList.FormattingEnabled = true; + this.textureList.Location = new System.Drawing.Point(16, 209); + this.textureList.Name = "textureList"; + this.textureList.Size = new System.Drawing.Size(220, 28); + this.textureList.TabIndex = 7; + this.textureList.SelectedIndexChanged += new System.EventHandler(this.textureList_SelectedIndexChanged); + // + // ThumbImage + // + this.ThumbImage.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.ThumbImage.Location = new System.Drawing.Point(16, 298); + this.ThumbImage.MinimumSize = new System.Drawing.Size(220, 201); + this.ThumbImage.Name = "ThumbImage"; + this.ThumbImage.Size = new System.Drawing.Size(220, 201); + this.ThumbImage.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; + this.ThumbImage.TabIndex = 8; + this.ThumbImage.TabStop = false; + // + // saveTextureDialog + // + this.saveTextureDialog.Filter = "Texture file|*.tga"; + this.saveTextureDialog.Title = "Save texture file"; + // + // label2 + // + this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Right))); + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(290, 10); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(121, 20); + this.label2.TabIndex = 9; + this.label2.Text = "Textures in map"; + // + // button2 + // + this.button2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.button2.Enabled = false; + this.button2.Location = new System.Drawing.Point(294, 442); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(341, 48); + this.button2.TabIndex = 10; + this.button2.Text = "Delete texture"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // button3 + // + this.button3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.button3.Enabled = false; + this.button3.Location = new System.Drawing.Point(19, 97); + this.button3.Name = "button3"; + this.button3.Size = new System.Drawing.Size(220, 39); + this.button3.TabIndex = 11; + this.button3.Text = "Save map as"; + this.button3.UseVisualStyleBackColor = true; + this.button3.Click += new System.EventHandler(this.button3_Click); + // + // textureAddDialog + // + this.textureAddDialog.Filter = "Texture file|*.tga"; + this.textureAddDialog.Title = "Select a texture to add to map"; + // + // saveMapDialog + // + this.saveMapDialog.Filter = "Map|*.gck"; + // + // label3 + // + this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label3.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.label3.Location = new System.Drawing.Point(19, 164); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(220, 2); + this.label3.TabIndex = 12; + // + // openmapButton + // + this.openmapButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.openmapButton.Enabled = false; + this.openmapButton.Location = new System.Drawing.Point(19, 10); + this.openmapButton.Name = "openmapButton"; + this.openmapButton.Size = new System.Drawing.Size(220, 39); + this.openmapButton.TabIndex = 13; + this.openmapButton.Text = "Open map"; + this.openmapButton.UseVisualStyleBackColor = true; + this.openmapButton.Click += new System.EventHandler(this.openmapButton_Click); + // + // ChangeTextureForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.ClientSize = new System.Drawing.Size(679, 536); + this.Controls.Add(this.openmapButton); + this.Controls.Add(this.label3); + this.Controls.Add(this.button3); + this.Controls.Add(this.button2); + this.Controls.Add(this.label2); + this.Controls.Add(this.ThumbImage); + this.Controls.Add(this.textureList); + this.Controls.Add(this.status); + this.Controls.Add(this.exportButton); + this.Controls.Add(this.texturesinmapList); + this.Controls.Add(this.label1); + this.Controls.Add(this.button1); + this.Controls.Add(this.Separator1); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MinimumSize = new System.Drawing.Size(701, 592); + this.Name = "ChangeTextureForm"; + this.Text = " Giants map retexturer"; + this.Load += new System.EventHandler(this.ChangeTextureForm_Load); + this.Shown += new System.EventHandler(this.ChangeTextureForm_Shown); + this.status.ResumeLayout(false); + this.status.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.ThumbImage)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.OpenFileDialog mapFileDialog; + private System.Windows.Forms.Label Separator1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.ListBox texturesinmapList; + private System.Windows.Forms.Button exportButton; + private System.Windows.Forms.StatusStrip status; + private System.Windows.Forms.OpenFileDialog gameFileDialog; + private System.Windows.Forms.ComboBox textureList; + private System.Windows.Forms.PictureBox ThumbImage; + private System.Windows.Forms.SaveFileDialog saveTextureDialog; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.Button button3; + private System.Windows.Forms.OpenFileDialog textureAddDialog; + private System.Windows.Forms.SaveFileDialog saveMapDialog; + private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel1; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Button openmapButton; + } +} + diff --git a/GiantsMapTextureChanger/ChangeTextureForm.cs b/GiantsMapTextureChanger/ChangeTextureForm.cs new file mode 100644 index 0000000..1344e7d --- /dev/null +++ b/GiantsMapTextureChanger/ChangeTextureForm.cs @@ -0,0 +1,383 @@ +using System; +using System.Windows.Forms; +using System.IO.Compression; +using System.IO; +using System.Drawing; +using System.Collections.Generic; +using System.Linq; +using TGASharpLib; +using System.Text; + +namespace GiantsMapTextureChanger +{ + public partial class ChangeTextureForm : Form + { + public ChangeTextureForm() + { + InitializeComponent(); + } + + public string map_filename { get; private set; } + public string giantsPath { get; private set; } + public Dictionary all_textures { get; private set; } + public Dictionary map_textures { get; private set; } + + private void ChangeTextureForm_Load(object sender, EventArgs e) + { + } + private Dictionary ReadAllTextures() + { + Dictionary all = new Dictionary(); + + string bin_path = Path.GetDirectoryName(this.giantsPath) + "\\Bin"; + var all_files_in_bin = Directory.EnumerateFiles(bin_path); + // MessageBox.Show(all_files_in_bin.Count() + " files in Bin"); + foreach (string currentFile in all_files_in_bin) + { + if (currentFile.EndsWith(".gzp")) + { + // it's a .gzp + toolStripStatusLabel1.Text = "Reading file " + currentFile; + Application.DoEvents(); + Dictionary textures_in_gzp = ReadTexturesInGzp(currentFile); + textures_in_gzp.ToList().ForEach(x => { + if (!all.ContainsKey(x.Key)) + { + all.Add(x.Key, x.Value); + } + }); + } + } + + return all; + } + private Dictionary ReadTexturesInGzp(string gzp_file) + { + Dictionary all = new Dictionary(); + + using (BinaryReader reader = new BinaryReader(File.Open(gzp_file, FileMode.Open))) + { + int checksum = BitConverter.ToInt32(reader.ReadBytes(4), 0); + if (checksum != 0x6608F101) + { + throw new Exception("GZP checksum invalid: " + checksum); + } + + int meta_info_offset = BitConverter.ToInt32(reader.ReadBytes(4), 0); + reader.BaseStream.Seek(meta_info_offset, SeekOrigin.Begin); + + int unk = BitConverter.ToInt32(reader.ReadBytes(4), 0); + int entries_count = BitConverter.ToInt32(reader.ReadBytes(4), 0); + + // MessageBox.Show(entries_count + " files in " + gzp_file); + + for (int i=0; i ReadMapTextures(string map_file) + { + Dictionary res = new Dictionary(); + + // read all files in gck file + ZipArchive zip = ZipFile.OpenRead(map_file); + foreach (ZipArchiveEntry file in zip.Entries) + { + if(file.Name.EndsWith(".tga")) + { + Stream rs = file.Open(); + byte[] tgaData = new byte[file.Length]; + rs.Read(tgaData, 0, (int)file.Length); + TGA t = TGA.FromBytes(tgaData); + + Image img = (Image)t; + res.Add(file.Name, img); + rs.Close(); + } + } + + zip.Dispose(); + + return res; + } + + private byte[] DecompressBytes(byte[] compressed_bytes, int original_size) + { + int i = 0; + int j = 0; + int dec_byte = 0; + int dec_bits = 8; + int buff_start = 0xFEE; + + byte[] res = new byte[original_size]; + + if (original_size == 0) + { + return res; + } + + while (j < original_size) + { + if (dec_bits == 8) + { + dec_byte = compressed_bytes[i]; + i++; + dec_bits = 0; + } + if ((dec_byte >> dec_bits & 1) == 0) + { + int dec_pos = ((compressed_bytes[i] + ((compressed_bytes[i + 1] & 0xF0) << 4) - buff_start - j) & 0xFFF) - 0x1000 + j; + int dec_len = (compressed_bytes[i + 1] & 0xF) + 3; + i += 2; + while (dec_len > 0) + { + if (dec_pos >= 0) + { + res[j] = res[dec_pos]; + } else + { + res[j] = 32; + } + j++; + dec_pos++; + dec_len--; + } + } else + { + res[j] = compressed_bytes[i]; + i++; + j++; + } + dec_bits++; + } + return res; + } + + private Image TGAToImage(byte[] data) + { + TGA tga = TGA.FromBytes(data); + return (Image)tga; + } + + private void textureList_SelectedIndexChanged(object sender, EventArgs e) + { + // texturesinmapList.SelectedIndex = -1; + string selected = textureList.Text; + if (this.all_textures.ContainsKey(selected)) { + ThumbImage.Image = this.all_textures[selected]; + ThumbImage.Tag = selected; + } + } + + private void exportButton_Click(object sender, EventArgs e) + { + saveTextureDialog.FileName = (string)ThumbImage.Tag; + saveTextureDialog.ShowDialog(); + // If the file name is not an empty string open it for saving. + if (saveTextureDialog.FileName != "") + { + // Saves the Image via a FileStream created by the OpenFile method. + System.IO.FileStream fs = (System.IO.FileStream)saveTextureDialog.OpenFile(); + + TGA t = (TGA)ThumbImage.Image; + t.Save(fs); + fs.Close(); + toolStripStatusLabel1.Text = "Saved texture to "+ saveTextureDialog.FileName; + } + else + { + toolStripStatusLabel1.Text = "Error: you did not choose a file to save texture"; + } + } + + private void texturesinmapList_SelectedIndexChanged(object sender, EventArgs e) + { + // textureList.SelectedIndex = -1; + string selected = texturesinmapList.Text; + if (this.map_textures.ContainsKey(selected)) + { + ThumbImage.Image = this.map_textures[selected]; + ThumbImage.Tag = selected; + } + } + + private void button2_Click(object sender, EventArgs e) + { + string selected = texturesinmapList.Text; + texturesinmapList.Items.Remove(selected); + map_textures.Remove(selected); + } + + private void button1_Click(object sender, EventArgs e) + { + textureAddDialog.ShowDialog(); + if (textureAddDialog.FileName != "") + { + Image img = (Image)new TGA(textureAddDialog.FileName); + string name = Path.GetFileName(textureAddDialog.FileName); + map_textures.Add(name, img); + texturesinmapList.Items.Add(name); + } + } + + private void button3_Click(object sender, EventArgs e) + { + saveMapDialog.ShowDialog(); + toolStripStatusLabel1.Text = "Saving map to: " + saveMapDialog.FileName; + if (saveMapDialog.FileName != "") + { + if (this.map_filename != saveMapDialog.FileName) { + File.Copy(this.map_filename, saveMapDialog.FileName, true); + } + ZipArchive map = ZipFile.Open(saveMapDialog.FileName, ZipArchiveMode.Update); + for(int i = map.Entries.Count-1;i>=0;i--) + { + ZipArchiveEntry entry = map.Entries[i]; + if (entry.Name.EndsWith(".tga")) + { + entry.Delete(); + } + } + + foreach(string texture_name in this.map_textures.Keys) + { + TGA texture = (TGA)map_textures[texture_name]; + + ZipArchiveEntry newentry = map.CreateEntry(texture_name); + using (StreamWriter writer = new StreamWriter(newentry.Open())) + { + texture.Save(writer.BaseStream); + } + } + toolStripStatusLabel1.Text = "Saved map to: " + Path.GetFileName(saveMapDialog.FileName); + map.Dispose(); + } + } + + private void ChangeTextureForm_Shown(object sender, EventArgs e) + { + // Get Giants.exe location + object install_dir = Microsoft.Win32.Registry.GetValue(@"HKEY_CURRENT_USER\Software\PlanetMoon\Giants", "DestDir", ""); + if (install_dir != null) { + this.giantsPath = install_dir.ToString(); + this.giantsPath += "\\Giants.exe"; + } + if (this.giantsPath == null || !File.Exists(this.giantsPath)) + { + if (gameFileDialog.ShowDialog() != DialogResult.OK) + { + Application.Exit(); + return; + } + this.giantsPath = gameFileDialog.FileName; + } + + // Get map location + if (mapFileDialog.ShowDialog() != DialogResult.OK) + { + Application.Exit(); + return; + } + + this.map_filename = mapFileDialog.FileName; + + toolStripStatusLabel1.Text = "Reading files..."; + Application.DoEvents(); + + this.all_textures = ReadAllTextures(); + foreach (string i in all_textures.Keys) + { + textureList.Items.Add(i); + } + + toolStripStatusLabel1.Text = "Reading map file " + this.map_filename; + Application.DoEvents(); + this.map_textures = ReadMapTextures(this.map_filename); ; + foreach (string i in map_textures.Keys) + { + texturesinmapList.Items.Add(i); + } + + this.Activate(); + toolStripStatusLabel1.Text = "Done"; + + button1.Enabled = true; + button3.Enabled = true; + textureList.Enabled = true; + exportButton.Enabled = true; + button2.Enabled = true; + openmapButton.Enabled = true; + } + + private void openmapButton_Click(object sender, EventArgs e) + { + button1.Enabled = false; + button3.Enabled = false; + textureList.Enabled = false; + exportButton.Enabled = false; + button2.Enabled = false; + openmapButton.Enabled = false; + + Application.DoEvents(); + + + // Get map location + if (mapFileDialog.ShowDialog() != DialogResult.OK) + { + Application.Exit(); + return; + } + + this.map_filename = mapFileDialog.FileName; + + toolStripStatusLabel1.Text = "Reading map file " + this.map_filename; + Application.DoEvents(); + this.map_textures = ReadMapTextures(this.map_filename); + + texturesinmapList.Items.Clear(); + foreach (string i in map_textures.Keys) + { + texturesinmapList.Items.Add(i); + } + + this.Activate(); + toolStripStatusLabel1.Text = "Done"; + + button1.Enabled = true; + button3.Enabled = true; + textureList.Enabled = true; + exportButton.Enabled = true; + button2.Enabled = true; + openmapButton.Enabled = true; + } + } +} diff --git a/GiantsMapTextureChanger/ChangeTextureForm.resx b/GiantsMapTextureChanger/ChangeTextureForm.resx new file mode 100644 index 0000000..e4966ec --- /dev/null +++ b/GiantsMapTextureChanger/ChangeTextureForm.resx @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 196, 17 + + + 306, 17 + + + 494, 17 + + + 705, 17 + + + 911, 17 + + + + + AAABAAEAICAAAAAAAACoCAAAFgAAACgAAAAgAAAAQAAAAAEACAAAAAAAgAQAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA+v//APb//wDp//8A5v//AN///wDX//8A1v//AMz//wDK//8AyP//ALf//wCZ+v8AzfT/ALvq + +QC/6/cAffH2AHrp8wDO4e4Aw//tAKHR7QCu6OkAfNrnAI3M5gCc9uQAmu/jAJ+64wCjveEAmeDdAIrW + 2wChv9cAeLPWAH2x1QCL3tQAq93TAJHS0wBDrM4AaLnKAF2HyAB9uMcAoeHGAH+kxAB9mMMAdsHBALTw + vwBDk78Akci8AGqPtgBrmrUASpa1AEKGswBeoLIAMYWxAFp8rABBjakAe6+oAH6gogBfe6IAVIqhAEh1 + oQAAAKAAVJOeAAYcngAAIpwA//8AAEh5mQA/aZgAL1uXAAUYlwBTbZYARISUAD53lAAPK5QAMWqTABEk + kwALIZMAFy6SABMlkgAnZZEAJ0eRACQ5kQAxWpAAP26PAChGjgAAB44APnaMADVUjAAkO4sAAAqLAHeg + igA4VYkAGjeJAHOniAAxUYgAK2WHAA0whgAAAIYAZJKFACA5hAALHIQAg62DADdsggA8W4IAapSBAC5I + gQBki38AIix/ACNWewA+bXoAV3h5AB9VeAAZOHgAGDB4AB5MdwAQGXcAAAB3ACNMdgBDYnUAHCl0AA8k + dABMcXMAIz1yABoxcgARL3IAGThxAAAAcQAQInAAJ0NvAAcSbwBojG4AJEpuAAAPbgAAAG4AGSdtAAAH + awAAGGkAX4BnABAgZwALFGcAChNnAAAQZgAAEWUAHz1kABQyZABHY2MAGTpjAAwcYwAAAGAADhZfAAAA + XwAcLV4AAAxeAA8kXQA6XVwADylbAAAAWwARIFoAEBxaAAAAWgAjLFkAGydZAAIHWQAADVgAFyZVAB1B + VAAQKVQADydTAAAAUgAAAFAAFSlOAAAATgA4UUsAAABLAAwVSQANE0kACxlIABIiRgANH0YAAwtGABsv + RQAFDUQAGiRDAAAAQwAIEUIAAANCAAAAQQAACUAAAABAAAYRPwAABD8AABU9AAAAPQAAADsACiQ6AAAA + OgAAADgABRU3AAAANgAeKTUALTs0AAAANAAAADMAAAAxAAAAMAAAAC8AAAAuAAAALQAAACwAJjMrAAAA + KwACCCoAAAAqAAAAKQAAACgABA0nAAAAJwAAACYAAQElAAAAJAAAAiMAAAAjAAAAIgAAASEAAAAhAAAA + IAAAAB8AAAAeAAAAHQAAABwAAAAbAAAAGQAAABgAAAAXAAAAFgAAABUAAAAUAAAAEwAAABIAAAARAAAA + EAAAAA8AAAAOAAAADQAAAAwAAAALAAAACgAAAAkAAAAIAAAABwAAAAYAAAAFAAAABAAAAAMAAAACAAAA + AQD///8A0tKvxOvGtcGztaR4mWk3Lis4FQgIBQcTLC6BiGQ3Z5DS0q/E68bGtcGztaR4mWk3LisuFQgI + BQcTLC4uLi4ukIWFREtNTWNiaFtmV0xTT1B1YlBaRU9CbU9PSH9IPS6ZuLi4uLi4uLi4uLi4uLi4uLi4 + uLi4uLi4uLi4aoc9Lpn7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7sz0udfv7+/v7+/v7+/v7+/v7 + +/v7+/v7+/v7+/v7+/utYS5/+/v7+/v7+/v75OTZ0tLS0tnr8/Pk+/v7+/v766wuLpj7+/v7+/v70rit + po5eQTtCUXmv2evz6/Pz6/P7pC5ctPPz6+vz8+R5QSURDAsLBQcFARMgUrjS6/v7+/s9LlzS8/Pr2evS + NAslMzIxO0ImFAcDAwEBB2+95Ov70iguiOv7++vzNBElcbTZ8/vz0qesoUUBAQUNBy6XuORDLC6I6/v7 + 8yQQOuTk5Ovz8/Pr6+TZvYYbAQ8OCBNYPCguiKTz+/s2DCTz8/Pz+/v7+/v7+/vz5Kc1AQ4ILngnLC6I + pPv70hYM0vvz+/v7+/v7+/v7+/v7+6wgAQgVZy4uLojz+/tuGUn7+/v7+/v7+/v7+/v7+/v762AHAQgh + Li5nkPv7+zMj2fv7+/v7+/v78/P7+/v7+/vrpyYCFRguiDek+/v7MzPz+/v7+/v7++Tk8+vk6+vk5NJy + FAciKGRkHOT7+/s0LfP7+/v7+/v70p15oIWWjnlaOwMBBAcTLCgh+/v7+04t2fv7+/v7+/vSQiAvNSop + HhIEAxcaFCcwIEf7+/v7mja4+/v7+/v7+8h8yHuTb72DoY2scz5zoV9z2fv7+/u9JJfk+/v7+/v72TDk + lgcp5C8iBLhzE3ODEyu9+/v78/MkYsjz+/v7+/vrIPv7CJj7LyBpyHcTjXMjpLj7+/v7+3cxvdL7+/v7 + +/vW+/vS6/vZyL3SWjhrhm960vv7+/v75DJ0yNn7+/v7+/v7+/v7+/vZ0o8BCB14Ihb7+/v7+/v7tFWt + 0tn7+/v7+/v7+/v7+9LZFQEMLmQTpfv7+/v7+/vztF6P0tnr8/v7+/v78+vZ5EIBFScVXIL7+/v7+/v7 + +/vzpWV/yPPZ0tnr6+TIyNJWAQELHCvB8/v7+/v7+/v7+/vzsGxSVr3IvdLZ0r2NOQEBAQxGvvv7+/v7 + +/v7+/v7+/v72TMWbWZiaGJoURUBAQIXcfv7+/v7+/v7+/v7+/v7+/vz+8g9ExUICAUBARIpmvv78/v7 + +/v7+/v7+/v7+/v7+/v7+/PktIJVdY645PP7+/v7+/v7+/v7+/v7+/v7+/v7+/v7++vZ0tnZ2dnr+/v7 + +/v7+/v7+/v7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA + + + \ No newline at end of file diff --git a/GiantsMapTextureChanger/Giants.ico b/GiantsMapTextureChanger/Giants.ico new file mode 100644 index 0000000..262c602 Binary files /dev/null and b/GiantsMapTextureChanger/Giants.ico differ diff --git a/GiantsMapTextureChanger/GiantsMapTextureChanger.csproj b/GiantsMapTextureChanger/GiantsMapTextureChanger.csproj new file mode 100644 index 0000000..a866401 --- /dev/null +++ b/GiantsMapTextureChanger/GiantsMapTextureChanger.csproj @@ -0,0 +1,123 @@ + + + + + Debug + AnyCPU + {06A0FCD6-9A1C-40BD-AF5C-546DE39CF4F0} + WinExe + GiantsMapTextureChanger + Giants Map Texture Changer + v4.6 + 512 + true + true + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + Giants.ico + + + + + + + + + + + + + + + + + + + Form + + + ChangeTextureForm.cs + + + + + + ChangeTextureForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + + + + False + Microsoft .NET Framework 4.6.1 %28x86 et x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + \ No newline at end of file diff --git a/GiantsMapTextureChanger/Program.cs b/GiantsMapTextureChanger/Program.cs new file mode 100644 index 0000000..c2e097d --- /dev/null +++ b/GiantsMapTextureChanger/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace GiantsMapTextureChanger +{ + static class Program + { + /// + /// Point d'entrée principal de l'application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new ChangeTextureForm()); + } + } +} diff --git a/GiantsMapTextureChanger/Properties/AssemblyInfo.cs b/GiantsMapTextureChanger/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4044fb2 --- /dev/null +++ b/GiantsMapTextureChanger/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Les informations générales relatives à un assembly dépendent de +// l'ensemble d'attributs suivant. Changez les valeurs de ces attributs pour modifier les informations +// associées à un assembly. +[assembly: AssemblyTitle("Giants Map Texture Changer")] +[assembly: AssemblyDescription("Allows to change the textures inside a map")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Amazed")] +[assembly: AssemblyProduct("Giants Map Texture Changer")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// L'affectation de la valeur false à ComVisible rend les types invisibles dans cet assembly +// aux composants COM. Si vous devez accéder à un type dans cet assembly à partir de +// COM, affectez la valeur true à l'attribut ComVisible sur ce type. +[assembly: ComVisible(false)] + +// Le GUID suivant est pour l'ID de la typelib si ce projet est exposé à COM +[assembly: Guid("06a0fcd6-9a1c-40bd-af5c-546de39cf4f0")] + +// Les informations de version pour un assembly se composent des quatre valeurs suivantes : +// +// Version principale +// Version secondaire +// Numéro de build +// Révision +// +// Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de build et de révision par défaut +// en utilisant '*', comme indiqué ci-dessous : +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/GiantsMapTextureChanger/Properties/Resources.Designer.cs b/GiantsMapTextureChanger/Properties/Resources.Designer.cs new file mode 100644 index 0000000..6d87f2b --- /dev/null +++ b/GiantsMapTextureChanger/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// Ce code a été généré par un outil. +// Version du runtime :4.0.30319.42000 +// +// Les modifications apportées à ce fichier peuvent provoquer un comportement incorrect et seront perdues si +// le code est régénéré. +// +//------------------------------------------------------------------------------ + +namespace GiantsMapTextureChanger.Properties { + using System; + + + /// + /// Une classe de ressource fortement typée destinée, entre autres, à la consultation des chaînes localisées. + /// + // Cette classe a été générée automatiquement par la classe StronglyTypedResourceBuilder + // à l'aide d'un outil, tel que ResGen ou Visual Studio. + // Pour ajouter ou supprimer un membre, modifiez votre fichier .ResX, puis réexécutez ResGen + // avec l'option /str ou régénérez votre projet VS. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Retourne l'instance ResourceManager mise en cache utilisée par cette classe. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GiantsMapTextureChanger.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Remplace la propriété CurrentUICulture du thread actuel pour toutes + /// les recherches de ressources à l'aide de cette classe de ressource fortement typée. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/GiantsMapTextureChanger/Properties/Resources.resx b/GiantsMapTextureChanger/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/GiantsMapTextureChanger/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/GiantsMapTextureChanger/Properties/Settings.Designer.cs b/GiantsMapTextureChanger/Properties/Settings.Designer.cs new file mode 100644 index 0000000..aba98f2 --- /dev/null +++ b/GiantsMapTextureChanger/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// Ce code a été généré par un outil. +// Version du runtime :4.0.30319.42000 +// +// Les modifications apportées à ce fichier peuvent provoquer un comportement incorrect et seront perdues si +// le code est régénéré. +// +//------------------------------------------------------------------------------ + +namespace GiantsMapTextureChanger.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/GiantsMapTextureChanger/Properties/Settings.settings b/GiantsMapTextureChanger/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/GiantsMapTextureChanger/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/GiantsMapTextureChanger/TGASharpLib.cs b/GiantsMapTextureChanger/TGASharpLib.cs new file mode 100644 index 0000000..7a38b65 --- /dev/null +++ b/GiantsMapTextureChanger/TGASharpLib.cs @@ -0,0 +1,5963 @@ +/* MIT License + Copyright (c) 2017 TGASharpLib + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Runtime.InteropServices; + +namespace TGASharpLib +{ + #region Enums + /// + /// The first 128 Color Map Type codes are reserved for use by Truevision, + /// while the second set of 128 Color Map Type codes(128 to 255) may be used for + /// developer applications. + /// True-Color images do not normally make use of the color map field, but some current + /// applications store palette information or developer-defined information in this field. + /// It is best to check Field 3, Image Type, to make sure you have a file which can use the + /// data stored in the Color Map Field. + /// Otherwise ignore the information. When saving or creating files for True-Color + /// images do not use this field and set it to Zero to ensure compatibility. Please refer + /// to the Developer Area specification for methods of storing developer defined information. + /// + public enum TgaColorMapType : byte + { + NoColorMap = 0, + ColorMap = 1, + Truevision_2, + Truevision_3, + Truevision_4, + Truevision_5, + Truevision_6, + Truevision_7, + Truevision_8, + Truevision_9, + Truevision_10, + Truevision_11, + Truevision_12, + Truevision_13, + Truevision_14, + Truevision_15, + Truevision_16, + Truevision_17, + Truevision_18, + Truevision_19, + Truevision_20, + Truevision_21, + Truevision_22, + Truevision_23, + Truevision_24, + Truevision_25, + Truevision_26, + Truevision_27, + Truevision_28, + Truevision_29, + Truevision_30, + Truevision_31, + Truevision_32, + Truevision_33, + Truevision_34, + Truevision_35, + Truevision_36, + Truevision_37, + Truevision_38, + Truevision_39, + Truevision_40, + Truevision_41, + Truevision_42, + Truevision_43, + Truevision_44, + Truevision_45, + Truevision_46, + Truevision_47, + Truevision_48, + Truevision_49, + Truevision_50, + Truevision_51, + Truevision_52, + Truevision_53, + Truevision_54, + Truevision_55, + Truevision_56, + Truevision_57, + Truevision_58, + Truevision_59, + Truevision_60, + Truevision_61, + Truevision_62, + Truevision_63, + Truevision_64, + Truevision_65, + Truevision_66, + Truevision_67, + Truevision_68, + Truevision_69, + Truevision_70, + Truevision_71, + Truevision_72, + Truevision_73, + Truevision_74, + Truevision_75, + Truevision_76, + Truevision_77, + Truevision_78, + Truevision_79, + Truevision_80, + Truevision_81, + Truevision_82, + Truevision_83, + Truevision_84, + Truevision_85, + Truevision_86, + Truevision_87, + Truevision_88, + Truevision_89, + Truevision_90, + Truevision_91, + Truevision_92, + Truevision_93, + Truevision_94, + Truevision_95, + Truevision_96, + Truevision_97, + Truevision_98, + Truevision_99, + Truevision_100, + Truevision_101, + Truevision_102, + Truevision_103, + Truevision_104, + Truevision_105, + Truevision_106, + Truevision_107, + Truevision_108, + Truevision_109, + Truevision_110, + Truevision_111, + Truevision_112, + Truevision_113, + Truevision_114, + Truevision_115, + Truevision_116, + Truevision_117, + Truevision_118, + Truevision_119, + Truevision_120, + Truevision_121, + Truevision_122, + Truevision_123, + Truevision_124, + Truevision_125, + Truevision_126, + Truevision_127, + Other_128, + Other_129, + Other_130, + Other_131, + Other_132, + Other_133, + Other_134, + Other_135, + Other_136, + Other_137, + Other_138, + Other_139, + Other_140, + Other_141, + Other_142, + Other_143, + Other_144, + Other_145, + Other_146, + Other_147, + Other_148, + Other_149, + Other_150, + Other_151, + Other_152, + Other_153, + Other_154, + Other_155, + Other_156, + Other_157, + Other_158, + Other_159, + Other_160, + Other_161, + Other_162, + Other_163, + Other_164, + Other_165, + Other_166, + Other_167, + Other_168, + Other_169, + Other_170, + Other_171, + Other_172, + Other_173, + Other_174, + Other_175, + Other_176, + Other_177, + Other_178, + Other_179, + Other_180, + Other_181, + Other_182, + Other_183, + Other_184, + Other_185, + Other_186, + Other_187, + Other_188, + Other_189, + Other_190, + Other_191, + Other_192, + Other_193, + Other_194, + Other_195, + Other_196, + Other_197, + Other_198, + Other_199, + Other_200, + Other_201, + Other_202, + Other_203, + Other_204, + Other_205, + Other_206, + Other_207, + Other_208, + Other_209, + Other_210, + Other_211, + Other_212, + Other_213, + Other_214, + Other_215, + Other_216, + Other_217, + Other_218, + Other_219, + Other_220, + Other_221, + Other_222, + Other_223, + Other_224, + Other_225, + Other_226, + Other_227, + Other_228, + Other_229, + Other_230, + Other_231, + Other_232, + Other_233, + Other_234, + Other_235, + Other_236, + Other_237, + Other_238, + Other_239, + Other_240, + Other_241, + Other_242, + Other_243, + Other_244, + Other_245, + Other_246, + Other_247, + Other_248, + Other_249, + Other_250, + Other_251, + Other_252, + Other_253, + Other_254, + Other_255 + } + + /// + /// Establishes the number of bits per entry. Typically 15, 16, 24 or 32-bit values are used. + /// When working with VDA or VDA/D cards it is preferred that you select 16 bits(5 bits + /// per primary with 1 bit to select interrupt control) and set the 16th bit to 0 so that the + /// interrupt bit is disabled. Even if this field is set to 15 bits(5 bits per primary) you + /// must still parse the color map data 16 bits at a time and ignore the 16th bit. + /// When working with a TARGA M8 card you would select 24 bits (8 bits per primary) + /// since the color map is defined as 256 entries of 24 bit color values. + /// When working with a TrueVista card(ATVista or NuVista) you would select 24-bit(8 bits per + /// primary) or 32-bit(8 bits per primary including Alpha channel) depending on your + /// application’s use of look-up tables. It is suggested that when working with 16-bit and + /// 32-bit color images, you store them as True-Color images and do not use the color map + /// field to store look-up tables. Please refer to the TGA Extensions for fields better suited + /// to storing look-up table information. + /// + public enum TgaColorMapEntrySize : byte + { + Other = 0, + X1R5G5B5 = 15, + A1R5G5B5 = 16, + R8G8B8 = 24, + A8R8G8B8 = 32 + } + + /// + /// Truevision has currently defined seven image types: + /// 0 - No Image Data Included; + /// 1 - Uncompressed, Color-mapped Image; + /// 2 - Uncompressed, True-color Image; + /// 3 - Uncompressed, Black-and-white Image; + /// 9 - Run-length encoded, Color-mapped Image; + /// 10 - Run-length encoded, True-color Image; + /// 11 - Run-length encoded, Black-and-white Image. + /// Image Data Type codes 0 to 127 are reserved for use by Truevision for general applications. + /// Image Data Type codes 128 to 255 may be used for developer applications. + /// + public enum TgaImageType : byte + { + NoImageData = 0, + Uncompressed_ColorMapped = 1, + Uncompressed_TrueColor, + Uncompressed_BlackWhite, + _Truevision_4, + _Truevision_5, + _Truevision_6, + _Truevision_7, + _Truevision_8, + RLE_ColorMapped = 9, + RLE_TrueColor, + RLE_BlackWhite, + _Truevision_12, + _Truevision_13, + _Truevision_14, + _Truevision_15, + _Truevision_16, + _Truevision_17, + _Truevision_18, + _Truevision_19, + _Truevision_20, + _Truevision_21, + _Truevision_22, + _Truevision_23, + _Truevision_24, + _Truevision_25, + _Truevision_26, + _Truevision_27, + _Truevision_28, + _Truevision_29, + _Truevision_30, + _Truevision_31, + _Truevision_32, + _Truevision_33, + _Truevision_34, + _Truevision_35, + _Truevision_36, + _Truevision_37, + _Truevision_38, + _Truevision_39, + _Truevision_40, + _Truevision_41, + _Truevision_42, + _Truevision_43, + _Truevision_44, + _Truevision_45, + _Truevision_46, + _Truevision_47, + _Truevision_48, + _Truevision_49, + _Truevision_50, + _Truevision_51, + _Truevision_52, + _Truevision_53, + _Truevision_54, + _Truevision_55, + _Truevision_56, + _Truevision_57, + _Truevision_58, + _Truevision_59, + _Truevision_60, + _Truevision_61, + _Truevision_62, + _Truevision_63, + _Truevision_64, + _Truevision_65, + _Truevision_66, + _Truevision_67, + _Truevision_68, + _Truevision_69, + _Truevision_70, + _Truevision_71, + _Truevision_72, + _Truevision_73, + _Truevision_74, + _Truevision_75, + _Truevision_76, + _Truevision_77, + _Truevision_78, + _Truevision_79, + _Truevision_80, + _Truevision_81, + _Truevision_82, + _Truevision_83, + _Truevision_84, + _Truevision_85, + _Truevision_86, + _Truevision_87, + _Truevision_88, + _Truevision_89, + _Truevision_90, + _Truevision_91, + _Truevision_92, + _Truevision_93, + _Truevision_94, + _Truevision_95, + _Truevision_96, + _Truevision_97, + _Truevision_98, + _Truevision_99, + _Truevision_100, + _Truevision_101, + _Truevision_102, + _Truevision_103, + _Truevision_104, + _Truevision_105, + _Truevision_106, + _Truevision_107, + _Truevision_108, + _Truevision_109, + _Truevision_110, + _Truevision_111, + _Truevision_112, + _Truevision_113, + _Truevision_114, + _Truevision_115, + _Truevision_116, + _Truevision_117, + _Truevision_118, + _Truevision_119, + _Truevision_120, + _Truevision_121, + _Truevision_122, + _Truevision_123, + _Truevision_124, + _Truevision_125, + _Truevision_126, + _Truevision_127, + _Other_128, + _Other_129, + _Other_130, + _Other_131, + _Other_132, + _Other_133, + _Other_134, + _Other_135, + _Other_136, + _Other_137, + _Other_138, + _Other_139, + _Other_140, + _Other_141, + _Other_142, + _Other_143, + _Other_144, + _Other_145, + _Other_146, + _Other_147, + _Other_148, + _Other_149, + _Other_150, + _Other_151, + _Other_152, + _Other_153, + _Other_154, + _Other_155, + _Other_156, + _Other_157, + _Other_158, + _Other_159, + _Other_160, + _Other_161, + _Other_162, + _Other_163, + _Other_164, + _Other_165, + _Other_166, + _Other_167, + _Other_168, + _Other_169, + _Other_170, + _Other_171, + _Other_172, + _Other_173, + _Other_174, + _Other_175, + _Other_176, + _Other_177, + _Other_178, + _Other_179, + _Other_180, + _Other_181, + _Other_182, + _Other_183, + _Other_184, + _Other_185, + _Other_186, + _Other_187, + _Other_188, + _Other_189, + _Other_190, + _Other_191, + _Other_192, + _Other_193, + _Other_194, + _Other_195, + _Other_196, + _Other_197, + _Other_198, + _Other_199, + _Other_200, + _Other_201, + _Other_202, + _Other_203, + _Other_204, + _Other_205, + _Other_206, + _Other_207, + _Other_208, + _Other_209, + _Other_210, + _Other_211, + _Other_212, + _Other_213, + _Other_214, + _Other_215, + _Other_216, + _Other_217, + _Other_218, + _Other_219, + _Other_220, + _Other_221, + _Other_222, + _Other_223, + _Other_224, + _Other_225, + _Other_226, + _Other_227, + _Other_228, + _Other_229, + _Other_230, + _Other_231, + _Other_232, + _Other_233, + _Other_234, + _Other_235, + _Other_236, + _Other_237, + _Other_238, + _Other_239, + _Other_240, + _Other_241, + _Other_242, + _Other_243, + _Other_244, + _Other_245, + _Other_246, + _Other_247, + _Other_248, + _Other_249, + _Other_250, + _Other_251, + _Other_252, + _Other_253, + _Other_254, + _Other_255 + } + + /// + /// Number of bits per pixel. This number includes the Attribute or Alpha channel bits. + /// Common values are 8, 16, 24 and 32 but other pixel depths could be used. + /// + public enum TgaPixelDepth : byte + { + Other = 0, + Bpp8 = 8, + Bpp16 = 16, + Bpp24 = 24, + Bpp32 = 32 + } + + /// + /// Used to indicate the order in which pixel data is transferred from the file to the screen. + /// (Bit 4 (bit 0 in enum) is for left-to-right ordering and bit 5 (bit 1 in enum) is for + /// topto-bottom ordering as shown below.) + /// + public enum TgaImgOrigin : byte + { + BottomLeft = 0, + BottomRight, + TopLeft, + TopRight + } + + /// + /// Contains a value which specifies the type of Alpha channel + /// data contained in the file. Value Meaning: + /// 0: no Alpha data included (bits 3-0 of field 5.6 should also be set to zero) + /// 1: undefined data in the Alpha field, can be ignored + /// 2: undefined data in the Alpha field, but should be retained + /// 3: useful Alpha channel data is present + /// 4: pre-multiplied Alpha(see description below) + /// 5 -127: RESERVED + /// 128-255: Un-assigned + /// Pre-multiplied Alpha Example: Suppose the Alpha channel data is being used to specify the + /// opacity of each pixel(for use when the image is overlayed on another image), where 0 indicates + /// that the pixel is completely transparent and a value of 1 indicates that the pixel is + /// completely opaque(assume all component values have been normalized). + /// A quadruple(a, r, g, b) of( 0.5, 1, 0, 0) would indicate that the pixel is pure red with a + /// transparency of one-half. For numerous reasons(including image compositing) is is better to + /// pre-multiply the individual color components with the value in the Alpha channel. + /// A pre-multiplication of the above would produce a quadruple(0.5, 0.5, 0, 0). + /// A value of 3 in the Attributes Type Field(field 23) would indicate that the color components + /// of the pixel have already been scaled by the value in the Alpha channel. + /// + public enum TgaAttrType : byte + { + NoAlpha = 0, + UndefinedAlphaCanBeIgnored, + UndefinedAlphaButShouldBeRetained, + UsefulAlpha, + PreMultipliedAlpha, + _Reserved_5, + _Reserved_6, + _Reserved_7, + _Reserved_8, + _Reserved_9, + _Reserved_10, + _Reserved_11, + _Reserved_12, + _Reserved_13, + _Reserved_14, + _Reserved_15, + _Reserved_16, + _Reserved_17, + _Reserved_18, + _Reserved_19, + _Reserved_20, + _Reserved_21, + _Reserved_22, + _Reserved_23, + _Reserved_24, + _Reserved_25, + _Reserved_26, + _Reserved_27, + _Reserved_28, + _Reserved_29, + _Reserved_30, + _Reserved_31, + _Reserved_32, + _Reserved_33, + _Reserved_34, + _Reserved_35, + _Reserved_36, + _Reserved_37, + _Reserved_38, + _Reserved_39, + _Reserved_40, + _Reserved_41, + _Reserved_42, + _Reserved_43, + _Reserved_44, + _Reserved_45, + _Reserved_46, + _Reserved_47, + _Reserved_48, + _Reserved_49, + _Reserved_50, + _Reserved_51, + _Reserved_52, + _Reserved_53, + _Reserved_54, + _Reserved_55, + _Reserved_56, + _Reserved_57, + _Reserved_58, + _Reserved_59, + _Reserved_60, + _Reserved_61, + _Reserved_62, + _Reserved_63, + _Reserved_64, + _Reserved_65, + _Reserved_66, + _Reserved_67, + _Reserved_68, + _Reserved_69, + _Reserved_70, + _Reserved_71, + _Reserved_72, + _Reserved_73, + _Reserved_74, + _Reserved_75, + _Reserved_76, + _Reserved_77, + _Reserved_78, + _Reserved_79, + _Reserved_80, + _Reserved_81, + _Reserved_82, + _Reserved_83, + _Reserved_84, + _Reserved_85, + _Reserved_86, + _Reserved_87, + _Reserved_88, + _Reserved_89, + _Reserved_90, + _Reserved_91, + _Reserved_92, + _Reserved_93, + _Reserved_94, + _Reserved_95, + _Reserved_96, + _Reserved_97, + _Reserved_98, + _Reserved_99, + _Reserved_100, + _Reserved_101, + _Reserved_102, + _Reserved_103, + _Reserved_104, + _Reserved_105, + _Reserved_106, + _Reserved_107, + _Reserved_108, + _Reserved_109, + _Reserved_110, + _Reserved_111, + _Reserved_112, + _Reserved_113, + _Reserved_114, + _Reserved_115, + _Reserved_116, + _Reserved_117, + _Reserved_118, + _Reserved_119, + _Reserved_120, + _Reserved_121, + _Reserved_122, + _Reserved_123, + _Reserved_124, + _Reserved_125, + _Reserved_126, + _Reserved_127, + _UnAssigned_128, + _UnAssigned_129, + _UnAssigned_130, + _UnAssigned_131, + _UnAssigned_132, + _UnAssigned_133, + _UnAssigned_134, + _UnAssigned_135, + _UnAssigned_136, + _UnAssigned_137, + _UnAssigned_138, + _UnAssigned_139, + _UnAssigned_140, + _UnAssigned_141, + _UnAssigned_142, + _UnAssigned_143, + _UnAssigned_144, + _UnAssigned_145, + _UnAssigned_146, + _UnAssigned_147, + _UnAssigned_148, + _UnAssigned_149, + _UnAssigned_150, + _UnAssigned_151, + _UnAssigned_152, + _UnAssigned_153, + _UnAssigned_154, + _UnAssigned_155, + _UnAssigned_156, + _UnAssigned_157, + _UnAssigned_158, + _UnAssigned_159, + _UnAssigned_160, + _UnAssigned_161, + _UnAssigned_162, + _UnAssigned_163, + _UnAssigned_164, + _UnAssigned_165, + _UnAssigned_166, + _UnAssigned_167, + _UnAssigned_168, + _UnAssigned_169, + _UnAssigned_170, + _UnAssigned_171, + _UnAssigned_172, + _UnAssigned_173, + _UnAssigned_174, + _UnAssigned_175, + _UnAssigned_176, + _UnAssigned_177, + _UnAssigned_178, + _UnAssigned_179, + _UnAssigned_180, + _UnAssigned_181, + _UnAssigned_182, + _UnAssigned_183, + _UnAssigned_184, + _UnAssigned_185, + _UnAssigned_186, + _UnAssigned_187, + _UnAssigned_188, + _UnAssigned_189, + _UnAssigned_190, + _UnAssigned_191, + _UnAssigned_192, + _UnAssigned_193, + _UnAssigned_194, + _UnAssigned_195, + _UnAssigned_196, + _UnAssigned_197, + _UnAssigned_198, + _UnAssigned_199, + _UnAssigned_200, + _UnAssigned_201, + _UnAssigned_202, + _UnAssigned_203, + _UnAssigned_204, + _UnAssigned_205, + _UnAssigned_206, + _UnAssigned_207, + _UnAssigned_208, + _UnAssigned_209, + _UnAssigned_210, + _UnAssigned_211, + _UnAssigned_212, + _UnAssigned_213, + _UnAssigned_214, + _UnAssigned_215, + _UnAssigned_216, + _UnAssigned_217, + _UnAssigned_218, + _UnAssigned_219, + _UnAssigned_220, + _UnAssigned_221, + _UnAssigned_222, + _UnAssigned_223, + _UnAssigned_224, + _UnAssigned_225, + _UnAssigned_226, + _UnAssigned_227, + _UnAssigned_228, + _UnAssigned_229, + _UnAssigned_230, + _UnAssigned_231, + _UnAssigned_232, + _UnAssigned_233, + _UnAssigned_234, + _UnAssigned_235, + _UnAssigned_236, + _UnAssigned_237, + _UnAssigned_238, + _UnAssigned_239, + _UnAssigned_240, + _UnAssigned_241, + _UnAssigned_242, + _UnAssigned_243, + _UnAssigned_244, + _UnAssigned_245, + _UnAssigned_246, + _UnAssigned_247, + _UnAssigned_248, + _UnAssigned_249, + _UnAssigned_250, + _UnAssigned_251, + _UnAssigned_252, + _UnAssigned_253, + _UnAssigned_254, + _UnAssigned_255 + } + #endregion + + #region Classes + public class TgaColorKey : ICloneable + { + byte a = 0; + byte r = 0; + byte g = 0; + byte b = 0; + + public TgaColorKey() + { + } + + /// + /// Make from ARGB bytes. + /// + /// Alpha value. + /// Red value. + /// Green value. + /// Blue value. + public TgaColorKey(byte A, byte R, byte G, byte B) + { + a = A; + r = R; + g = G; + b = B; + } + + /// + /// Make from ARGB bytes. + /// + /// Array of bytes(byte[4]). + public TgaColorKey(byte[] Bytes) + { + if (Bytes == null) + throw new ArgumentNullException(nameof(Bytes) + " = null!"); + if (Bytes.Length != Size) + throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!"); + + Color color = Color.FromArgb(BitConverter.ToInt32(Bytes, 0)); + a = color.A; + r = color.R; + g = color.G; + b = color.B; + } + + /// + /// Make from . + /// + /// 32bit ARGB integer color value. + public TgaColorKey(int ARGB) + { + Color ColorARGB = Color.FromArgb(ARGB); + a = ColorARGB.A; + r = ColorARGB.R; + g = ColorARGB.G; + b = ColorARGB.B; + } + + /// + /// Make from . + /// + /// GDI+ value. + public TgaColorKey(Color color) + { + a = color.A; + r = color.R; + g = color.G; + b = color.B; + } + + /// + /// Gets or sets alpha color value. + /// + public byte A + { + get { return a; } + set { a = value; } + } + + /// + /// Gets or sets red color value. + /// + public byte R + { + get { return r; } + set { r = value; } + } + + /// + /// Gets or sets green color value. + /// + public byte G + { + get { return g; } + set { g = value; } + } + + /// + /// Gets or sets blue color value. + /// + public byte B + { + get { return b; } + set { b = value; } + } + + /// + /// Gets TGA Field size in bytes. + /// + public const int Size = 4; + + /// + /// Make full independed copy of . + /// + /// Copy of + public TgaColorKey Clone() + { + return new TgaColorKey(a, r, g, b); + } + + /// + /// Make full independed copy of . + /// + /// Copy of + object ICloneable.Clone() + { + return Clone(); + } + + public override bool Equals(object obj) + { + return ((obj is TgaColorKey) ? Equals((TgaColorKey)obj) : false); + } + + public bool Equals(TgaColorKey item) + { + return (a == item.a && r == item.r && g == item.g && b == item.b); + } + + public static bool operator ==(TgaColorKey item1, TgaColorKey item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaColorKey item1, TgaColorKey item2) + { + return !(item1 == item2); + } + + public override int GetHashCode() + { + return ToInt().GetHashCode(); + } + + /// + /// Gets like string. + /// + /// String in ARGB format. + public override string ToString() + { + return String.Format("{0}={1}, {2}={3}, {4}={5}, {6}={7}", + nameof(A), a, nameof(R), r, nameof(G), g, nameof(B), b); + } + + /// + /// Convert to byte array. + /// + /// Byte array with length = 4. + public byte[] ToBytes() + { + return BitConverter.GetBytes(ToInt()); + } + + /// + /// Gets like GDI+ . + /// + /// value of . + public Color ToColor() + { + return Color.FromArgb(a, r, g, b); + } + + /// + /// Gets like ARGB . + /// + /// ARGB value of . + public int ToInt() + { + return ToColor().ToArgb(); + } + } + + /// + /// This field (5 bytes) and its sub-fields describe the color map (if any) used for the image. + /// If the Color Map Type field is set to zero, indicating that no color map exists, then + /// these 5 bytes should be set to zero. These bytes always must be written to the file. + /// + public class TgaColorMapSpec : ICloneable + { + ushort firstEntryIndex = 0; + ushort colorMapLength = 0; + TgaColorMapEntrySize colorMapEntrySize = TgaColorMapEntrySize.Other; + + /// + /// Make new . + /// + public TgaColorMapSpec() + { + } + + /// + /// Make from bytes. + /// + /// Array of bytes(byte[5]). + public TgaColorMapSpec(byte[] Bytes) + { + if (Bytes == null) + throw new ArgumentNullException(nameof(Bytes) + " = null!"); + if (Bytes.Length != Size) + throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!"); + + firstEntryIndex = BitConverter.ToUInt16(Bytes, 0); + colorMapLength = BitConverter.ToUInt16(Bytes, 2); + colorMapEntrySize = (TgaColorMapEntrySize)Bytes[4]; + } + + /// + /// Field 4.1 (2 bytes): + /// Index of the first color map entry. Index refers to the starting entry in loading + /// the color map. + /// Example: If you would have 1024 entries in the entire color map but you only + /// need to store 72 of those entries, this field allows you to start in the middle of + /// the color-map (e.g., position 342). + /// + public ushort FirstEntryIndex + { + get { return firstEntryIndex; } + set { firstEntryIndex = value; } + } + + /// + /// Field 4.2 (2 bytes): + /// Total number of color map entries included. + /// + public ushort ColorMapLength + { + get { return colorMapLength; } + set { colorMapLength = value; } + } + + /// + /// Field 4.3 (1 byte): + /// Establishes the number of bits per entry. Typically 15, 16, 24 or 32-bit values are used. + /// When working with VDA or VDA/D cards it is preferred that you select 16 bits(5 bits + /// per primary with 1 bit to select interrupt control) and set the 16th bit to 0 so that the + /// interrupt bit is disabled. Even if this field is set to 15 bits(5 bits per primary) you + /// must still parse the color map data 16 bits at a time and ignore the 16th bit. + /// When working with a TARGA M8 card you would select 24 bits (8 bits per primary) + /// since the color map is defined as 256 entries of 24 bit color values. + /// When working with a TrueVista card(ATVista or NuVista) you would select 24-bit(8 bits per + /// primary) or 32-bit(8 bits per primary including Alpha channel) depending on your + /// application’s use of look-up tables. It is suggested that when working with 16-bit and + /// 32-bit color images, you store them as True-Color images and do not use the color map + /// field to store look-up tables. Please refer to the TGA Extensions for fields better suited + /// to storing look-up table information. + /// + public TgaColorMapEntrySize ColorMapEntrySize + { + get { return colorMapEntrySize; } + set { colorMapEntrySize = value; } + } + + /// + /// Gets TGA Field size in bytes. + /// + public const int Size = 5; + + /// + /// Make full independed copy of . + /// + /// Copy of + public TgaColorMapSpec Clone() + { + return new TgaColorMapSpec(ToBytes()); + } + + /// + /// Make full independed copy of . + /// + /// Copy of + object ICloneable.Clone() + { + return Clone(); + } + + public override bool Equals(object obj) + { + return ((obj is TgaColorMapSpec) ? Equals((TgaColorMapSpec)obj) : false); + } + + public bool Equals(TgaColorMapSpec item) + { + return (firstEntryIndex == item.firstEntryIndex && + colorMapLength == item.colorMapLength && + colorMapEntrySize == item.colorMapEntrySize); + } + + public static bool operator ==(TgaColorMapSpec item1, TgaColorMapSpec item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaColorMapSpec item1, TgaColorMapSpec item2) + { + return !(item1 == item2); + } + + public override int GetHashCode() + { + unchecked + { + return (firstEntryIndex << 16 | colorMapLength).GetHashCode() ^ colorMapEntrySize.GetHashCode(); + } + } + + public override string ToString() + { + return String.Format("{0}={1}, {2}={3}, {4}={5}", nameof(FirstEntryIndex), FirstEntryIndex, + nameof(ColorMapLength), ColorMapLength, nameof(ColorMapEntrySize), ColorMapEntrySize); + } + + /// + /// Convert ColorMapSpec to byte array. + /// + /// Byte array with length = 5. + public byte[] ToBytes() + { + return BitConverterExt.ToBytes(firstEntryIndex, colorMapLength, (byte)colorMapEntrySize); + } + } + + public class TgaComment : ICloneable + { + const int StrNLen = 80; //80 ASCII chars + 1 '\0' = 81 per SrtN! + string origString = String.Empty; + char blankSpaceChar = TgaString.DefaultBlankSpaceChar; + + public TgaComment() + { + } + + public TgaComment(string Str, char BlankSpaceChar = '\0') + { + if (Str == null) + throw new ArgumentNullException(nameof(Str) + " = null!"); + + origString = Str; + blankSpaceChar = BlankSpaceChar; + } + + public TgaComment(byte[] Bytes) + { + if (Bytes == null) + throw new ArgumentNullException(nameof(Bytes) + " = null!"); + if (Bytes.Length != Size) + throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!"); + + string s = Encoding.ASCII.GetString(Bytes, 0, StrNLen); + s += Encoding.ASCII.GetString(Bytes, 81, StrNLen); + s += Encoding.ASCII.GetString(Bytes, 162, StrNLen); + s += Encoding.ASCII.GetString(Bytes, 243, StrNLen); + + switch (s[s.Length - 1]) + { + case '\0': + case ' ': + blankSpaceChar = s[s.Length - 1]; + origString = s.TrimEnd(new char[] { s[s.Length - 1] }); + break; + default: + origString = s; + break; + } + } + + /// + /// Gets TGA Field size in bytes. + /// + public const int Size = 81 * 4; + + public string OriginalString + { + get { return origString; } + set { origString = value; } + } + + public char BlankSpaceChar + { + get { return blankSpaceChar; } + set { blankSpaceChar = value; } + } + + /// + /// Make full independed copy of . + /// + /// Copy of + public TgaComment Clone() + { + return new TgaComment(origString, blankSpaceChar); + } + + /// + /// Make full independed copy of . + /// + /// Copy of + object ICloneable.Clone() + { + return Clone(); + } + + public override bool Equals(object obj) + { + return ((obj is TgaComment) ? Equals((TgaComment)obj) : false); + } + + public bool Equals(TgaComment item) + { + return (origString == item.origString && blankSpaceChar == item.blankSpaceChar); + } + + public static bool operator ==(TgaComment item1, TgaComment item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaComment item1, TgaComment item2) + { + return !(item1 == item2); + } + + public override int GetHashCode() + { + return origString.GetHashCode() ^ blankSpaceChar.GetHashCode(); + } + + /// + /// Get ASCII-Like string with string-terminators, example: "Line1 \0\0 Line2 \0\0\0". + /// + /// String with replaced string-terminators to "\0". + public override string ToString() + { + return Encoding.ASCII.GetString(ToBytes()).Replace("\0", @"\0"); + } + + /// + /// Get ASCII-Like string to first string-terminator, example: + /// "Some string \0 Some Data \0" - > "Some string". + /// + /// String to first string-terminator. + public string GetString() + { + String Str = Encoding.ASCII.GetString(ToBytes()); + for (int i = 1; i < 4; i++) + Str = Str.Insert((StrNLen + 1) * i + i - 1, "\n"); + return Str.Replace("\0", String.Empty).TrimEnd(new char[] { '\n' }); + } + + /// + /// Convert to byte array. + /// + /// Byte array, every byte is ASCII symbol. + public byte[] ToBytes() + { + return ToBytes(origString, blankSpaceChar); + } + + /// + /// Convert to byte array. + /// + /// Input string. + /// Char for filling blank space in string. + /// Byte array, every byte is ASCII symbol. + public static byte[] ToBytes(string Str, char BlankSpaceChar = '\0') + { + char[] C = new char[81 * 4]; + + for (int i = 0; i < C.Length; i++) + { + if ((i + 82) % 81 == 0) + C[i] = TgaString.DefaultEndingChar; + else + { + int Index = i - i / 81; + C[i] = (Index < Str.Length ? Str[Index] : BlankSpaceChar); + } + } + return Encoding.ASCII.GetBytes(C); + } + } + + public class TgaDateTime : ICloneable + { + ushort month = 0; + ushort day = 0; + ushort year = 0; + ushort hour = 0; + ushort minute = 0; + ushort second = 0; + + /// + /// Make empty . + /// + public TgaDateTime() + { + } + + /// + /// Make from . + /// + /// Some variable. + public TgaDateTime(DateTime DateAndTime) + { + month = (ushort)DateAndTime.Month; + day = (ushort)DateAndTime.Day; + year = (ushort)DateAndTime.Year; + hour = (ushort)DateAndTime.Hour; + minute = (ushort)DateAndTime.Minute; + second = (ushort)DateAndTime.Second; + } + + /// + /// Make from ushort values. + /// + /// Month (1 - 12). + /// Day (1 - 31). + /// Year (4 digit, ie. 1989). + /// Hour (0 - 23). + /// Minute (0 - 59). + /// Second (0 - 59). + public TgaDateTime(ushort Month, ushort Day, ushort Year, ushort Hour, ushort Minute, ushort Second) + { + month = Month; + day = Day; + year = Year; + hour = Hour; + minute = Minute; + second = Second; + } + + /// + /// Make from bytes. + /// + /// Array of bytes(byte[12]). + public TgaDateTime(byte[] Bytes) + { + if (Bytes == null) + throw new ArgumentNullException(nameof(Bytes) + " = null!"); + else if (Bytes.Length != Size) + throw new ArgumentOutOfRangeException(nameof(Bytes) + " must be equal " + Size + "!"); + + month = BitConverter.ToUInt16(Bytes, 0); + day = BitConverter.ToUInt16(Bytes, 2); + year = BitConverter.ToUInt16(Bytes, 4); + hour = BitConverter.ToUInt16(Bytes, 6); + minute = BitConverter.ToUInt16(Bytes, 8); + second = BitConverter.ToUInt16(Bytes, 10); + } + + /// + /// Gets or Sets month (1 - 12). + /// + public ushort Month + { + get { return month; } + set { month = value; } + } + + /// + /// Gets or Sets day (1 - 31). + /// + public ushort Day + { + get { return day; } + set { day = value; } + } + + /// + /// Gets or Sets year (4 digit, ie. 1989). + /// + public ushort Year + { + get { return year; } + set { year = value; } + } + + /// + /// Gets or Sets hour (0 - 23). + /// + public ushort Hour + { + get { return hour; } + set { hour = value; } + } + + /// + /// Gets or Sets minute (0 - 59). + /// + public ushort Minute + { + get { return minute; } + set { minute = value; } + } + + /// + /// Gets or Sets second (0 - 59). + /// + public ushort Second + { + get { return second; } + set { second = value; } + } + + /// + /// Gets TGA Field size in bytes. + /// + public const int Size = 12; + + /// + /// Make full independed copy of . + /// + /// Copy of + public TgaDateTime Clone() + { + return new TgaDateTime(month, day, year, hour, minute, second); + } + + /// + /// Make full independed copy of . + /// + /// Copy of + object ICloneable.Clone() + { + return Clone(); + } + + public override bool Equals(object obj) + { + return ((obj is TgaDateTime) ? Equals((TgaDateTime)obj) : false); + } + + public bool Equals(TgaDateTime item) + { + return ( + month == item.month && + day == item.day && + year == item.year && + hour == item.hour && + minute == item.minute && + second == item.second); + } + + public static bool operator ==(TgaDateTime item1, TgaDateTime item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaDateTime item1, TgaDateTime item2) + { + return !(item1 == item2); + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 23 + (month << 16 | hour).GetHashCode(); + hash = hash * 23 + (day << 16 | minute).GetHashCode(); + hash = hash * 23 + (year << 16 | second).GetHashCode(); + return hash; + } + } + + /// + /// Gets like string. + /// + /// String in "1990.01.23 1:02:03" format. + public override string ToString() + { + return String.Format("{0:D4}.{1:D2}.{2:D2} {3}:{4:D2}:{5:D2}", year, month, day, hour, minute, second); + } + + /// + /// Convert to byte array. + /// + /// Byte array with length = 12. + public byte[] ToBytes() + { + return BitConverterExt.ToBytes(month, day, year, hour, minute, second); + } + + /// + /// Gets like . + /// + /// value of . + public DateTime ToDateTime() + { + return new DateTime(year, month, day, hour, minute, second); + } + } + + public class TgaDevEntry : ICloneable + { + // Directory + ushort fieldTag = 0; + uint fieldFileOffset = 0; + // Field + byte[] data = null; + + /// + /// Make empty . + /// + public TgaDevEntry() + { + } + + /// + /// Make from other . + /// + /// Some variable. + public TgaDevEntry(TgaDevEntry Entry) + { + if (Entry == null) + throw new ArgumentNullException(); + + fieldTag = Entry.fieldTag; + fieldFileOffset = Entry.fieldFileOffset; + data = BitConverterExt.ToBytes(Entry.data); + } + + /// + /// Make from , and . + /// + /// TAG ID (0 - 65535). See . + /// TAG file offset in bytes. See . + /// This is DevEntry Field Data. See . + public TgaDevEntry(ushort Tag, uint Offset, byte[] Data = null) + { + fieldTag = Tag; + fieldFileOffset = Offset; + data = Data; + } + + /// + /// Make from bytes. + /// + /// Array of bytes(byte[6] or bigger, if exist). + public TgaDevEntry(byte[] Bytes) + { + if (Bytes == null) + throw new ArgumentNullException(nameof(Bytes) + " = null!"); + else if (Bytes.Length < 6) + throw new ArgumentOutOfRangeException(nameof(Bytes) + " must be >= 6!"); + + fieldTag = BitConverter.ToUInt16(Bytes, 0); + fieldFileOffset = BitConverter.ToUInt32(Bytes, 2); + + if (Bytes.Length > 6) + data = BitConverterExt.GetElements(Bytes, 6, Bytes.Length - 6); + } + + /// + /// Each TAG is a value in the range of 0 to 65535. Values from 0 - 32767 are available for developer use, + /// while values from 32768 - 65535 are reserved for Truevision. + /// + public ushort Tag + { + get { return fieldTag; } + set { fieldTag = value; } + } + + /// + /// This OFFSET is a number of bytes from the beginning of the file to the start of the field + /// referenced by the tag. + /// + public uint Offset + { + get { return fieldFileOffset; } + set { fieldFileOffset = value; } + } + + /// + /// Field DATA. + /// Although the size and format of the actual Developer Area fields are totally up to the developer, + /// please define your formats to address future considerations you might have concerning your fields. + /// This means that if you anticipate changing a field, build flexibility into the format to make these + /// changes easy on other developers.Major changes to an existing TAG’s definition should never happen. + /// + public byte[] Data + { + get { return data; } + set { data = value; } + } + + /// + /// The FIELD SIZE is a number of bytes in the field. Same like: , + /// if is null, return -1. + /// + public int FieldSize + { + get + { + if (Data == null) + return -1; + + return Data.Length; + } + } + + /// + /// Gets TGA size in bytes (Always constant and equal 10!). + /// It is not ! It is just size of entry sizeof(ushort + uint + uint). + /// + public const int Size = 10; + + /// + /// Make full independed copy of . + /// + /// Copy of + public TgaDevEntry Clone() + { + return new TgaDevEntry(this); + } + + /// + /// Make full independed copy of . + /// + /// Copy of + object ICloneable.Clone() + { + return Clone(); + } + + public override bool Equals(object obj) + { + return ((obj is TgaDevEntry) ? Equals((TgaDevEntry)obj) : false); + } + + public bool Equals(TgaDevEntry item) + { + return (fieldTag == item.fieldTag && + fieldFileOffset == item.fieldFileOffset && + BitConverterExt.IsArraysEqual(data, item.data)); + } + + public static bool operator ==(TgaDevEntry item1, TgaDevEntry item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaDevEntry item1, TgaDevEntry item2) + { + return !(item1 == item2); + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 23 + fieldTag.GetHashCode(); + hash = hash * 23 + fieldFileOffset.GetHashCode(); + + if (data != null) + for (int i = 0; i < data.Length; i++) + hash = hash * 23 + data[i].GetHashCode(); + + return hash; + } + } + + /// + /// Gets like string. + /// + /// String in "Tag={0}, Offset={1}, FieldSize={2}" format. + public override string ToString() + { + return String.Format("{0}={1}, {1}={2}, {3}={4}", nameof(Tag), fieldTag, + nameof(Offset), fieldFileOffset, nameof(FieldSize), FieldSize); + } + + /// + /// Convert to byte array. (Not include !). + /// + /// Byte array with length = 10. + public byte[] ToBytes() + { + return BitConverterExt.ToBytes(fieldTag, fieldFileOffset, (data == null ? 0 : data.Length)); + } + } //Not full ToBytes() + + public class TgaFraction : ICloneable + { + ushort numerator = 0; + ushort denominator = 0; + + /// + /// Make from and . + /// + /// Numerator value. + /// Denominator value. + public TgaFraction(ushort Numerator = 0, ushort Denominator = 0) + { + numerator = Numerator; + denominator = Denominator; + } + + /// + /// Make from bytes. + /// + /// Array of bytes(byte[4]). + public TgaFraction(byte[] Bytes) + { + if (Bytes == null) + throw new ArgumentNullException(nameof(Bytes) + " = null!"); + if (Bytes.Length != Size) + throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!"); + + numerator = BitConverter.ToUInt16(Bytes, 0); + denominator = BitConverter.ToUInt16(Bytes, 2); + } + + /// + /// Gets or sets numerator value. + /// + public ushort Numerator + { + get { return numerator; } + set { numerator = value; } + } + + /// + /// Gets or sets denominator value. + /// + public ushort Denominator + { + get { return denominator; } + set { denominator = value; } + } + + /// + /// Get aspect ratio = / . + /// + public float AspectRatio + { + get + { + if (numerator == denominator) + return 1f; + + return numerator / (float)denominator; + } + } + + /// + /// Gets Empty , all values are 0. + /// + public static readonly TgaFraction Empty = new TgaFraction(); + + /// + /// Gets One , all values are 1 (ones, 1 / 1 = 1). + /// + public static readonly TgaFraction One = new TgaFraction(1, 1); + + /// + /// Gets TGA Field size in bytes. + /// + public const int Size = 4; + + /// + /// Make full independed copy of . + /// + /// Copy of + public TgaFraction Clone() + { + return new TgaFraction(numerator, denominator); + } + + /// + /// Make full independed copy of . + /// + /// Copy of + object ICloneable.Clone() + { + return Clone(); + } + + public override bool Equals(object obj) + { + return ((obj is TgaFraction) ? Equals((TgaFraction)obj) : false); + } + + public bool Equals(TgaFraction item) + { + return (numerator == item.numerator && denominator == item.denominator); + } + + public static bool operator ==(TgaFraction item1, TgaFraction item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaFraction item1, TgaFraction item2) + { + return !(item1 == item2); + } + + public override int GetHashCode() + { + return (numerator << 16 | denominator).GetHashCode(); + } + + /// + /// Gets like string. + /// + /// String in "Numerator=1, Denominator=2" format. + public override string ToString() + { + return String.Format("{0}={1}, {2}={3}", nameof(Numerator), numerator, + nameof(Denominator), denominator); + } + + /// + /// Convert to byte array. + /// + /// Byte array with length = 4. + public byte[] ToBytes() + { + return BitConverterExt.ToBytes(numerator, denominator); + } + } + + /// + /// Contains image origin bits and alpha channel bits(or number of overlay bits) + /// + public class TgaImageDescriptor : ICloneable + { + TgaImgOrigin imageOrigin = 0; //bits 5-4 + byte alphaChannelBits = 0; //bits 3-0 + + /// + /// Make empty . + /// + public TgaImageDescriptor() + { + } + + /// + /// Make from bytes. + /// + /// ImageDescriptor byte with reserved 7-6 bits, bits 5-4 used for + /// , 3-0 used as alpha channel bits or number of overlay bits. + public TgaImageDescriptor(byte b) + { + imageOrigin = (TgaImgOrigin)((b & 0x30) >> 4); + alphaChannelBits = (byte)(b & 0x0F); + } + + /// + /// Gets or Sets Image Origin bits (select from enum only, don'n use 5-4 bits!). + /// + public TgaImgOrigin ImageOrigin + { + get { return imageOrigin; } + set { imageOrigin = value; } + } + + /// + /// Gets or Sets alpha channel bits or number of overlay bits. + /// + public byte AlphaChannelBits + { + get { return alphaChannelBits; } + set { alphaChannelBits = value; } + } + + /// + /// Gets TGA Field size in bytes. + /// + public const int Size = 1; + + /// + /// Make full copy of . + /// + /// Full independent copy of . + public TgaImageDescriptor Clone() + { + return new TgaImageDescriptor(ToByte()); + } + + /// + /// Make full copy of . + /// + /// Full independent copy of . + object ICloneable.Clone() + { + return Clone(); + } + + public override bool Equals(object obj) + { + return ((obj is TgaImageDescriptor) ? Equals((TgaImageDescriptor)obj) : false); + } + + public bool Equals(TgaImageDescriptor item) + { + return (imageOrigin == item.imageOrigin && alphaChannelBits == item.alphaChannelBits); + } + + public static bool operator ==(TgaImageDescriptor item1, TgaImageDescriptor item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaImageDescriptor item1, TgaImageDescriptor item2) + { + return !(item1 == item2); + } + + public override int GetHashCode() + { + unchecked + { + return ((int)ImageOrigin << 4 | alphaChannelBits).GetHashCode(); + } + } + + public override string ToString() + { + return String.Format("{0}={1}, {2}={3}, ImageDescriptor_AsByte={4}", nameof(ImageOrigin), + imageOrigin, nameof(AlphaChannelBits), alphaChannelBits, ToByte()); + } + + /// + /// Gets ImageDescriptor byte. + /// + /// ImageDescriptor byte with reserved 7-6 bits, bits 5-4 used for imageOrigin, + /// 3-0 used as alpha channel bits or number of overlay bits. + public byte ToByte() + { + return (byte)(((int)imageOrigin << 4) | alphaChannelBits); + } + } + + /// + /// Image Specification - Field 5 (10 bytes): + /// This field and its sub-fields describe the image screen location, size and pixel depth. + /// These information is always written to the file. + /// + public class TgaImageSpec : ICloneable + { + ushort x_Origin = 0; + ushort y_Origin = 0; + ushort imageWidth = 0; + ushort imageHeight = 0; + TgaPixelDepth pixelDepth = TgaPixelDepth.Other; + TgaImageDescriptor imageDescriptor = new TgaImageDescriptor(); + + public TgaImageSpec() + { + } + + /// + /// Make ImageSpec from values. + /// + /// These specify the absolute horizontal coordinate for the lower + /// left corner of the image as it is positioned on a display device having an origin at + /// the lower left of the screen(e.g., the TARGA series). + /// These specify the absolute vertical coordinate for the lower + /// left corner of the image as it is positioned on a display device having an origin at + /// the lower left of the screen(e.g., the TARGA series). + /// This field specifies the width of the image in pixels. + /// This field specifies the height of the image in pixels. + /// This field indicates the number of bits per pixel. This number + /// includes the Attribute or Alpha channel bits. Common values are 8, 16, 24 and 32 but + /// other pixel depths could be used. + /// Contains image origin bits and alpha channel bits + /// (or number of overlay bits). + public TgaImageSpec(ushort X_Origin, ushort Y_Origin, ushort ImageWidth, ushort ImageHeight, + TgaPixelDepth PixelDepth, TgaImageDescriptor ImageDescriptor) + { + x_Origin = X_Origin; + y_Origin = Y_Origin; + imageWidth = ImageWidth; + imageHeight = ImageHeight; + pixelDepth = PixelDepth; + imageDescriptor = ImageDescriptor; + } + + /// + /// Make ImageSpec from bytes. + /// + /// Array of bytes(byte[10]). + public TgaImageSpec(byte[] Bytes) + { + if (Bytes == null) + throw new ArgumentNullException(nameof(Bytes) + " = null!"); + if (Bytes.Length != Size) + throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!"); + + x_Origin = BitConverter.ToUInt16(Bytes, 0); + y_Origin = BitConverter.ToUInt16(Bytes, 2); + imageWidth = BitConverter.ToUInt16(Bytes, 4); + imageHeight = BitConverter.ToUInt16(Bytes, 6); + pixelDepth = (TgaPixelDepth)Bytes[8]; + imageDescriptor = new TgaImageDescriptor(Bytes[9]); + } + + /// + /// These specify the absolute horizontal coordinate for the lower left corner of the image + /// as it is positioned on a display device having an origin at the lower left of the + /// screen(e.g., the TARGA series). + /// + public ushort X_Origin + { + get { return x_Origin; } + set { x_Origin = value; } + } + + /// + /// These specify the absolute vertical coordinate for the lower left corner of the image + /// as it is positioned on a display device having an origin at the lower left of the + /// screen(e.g., the TARGA series). + /// + public ushort Y_Origin + { + get { return y_Origin; } + set { y_Origin = value; } + } + + /// + /// This field specifies the width of the image in pixels. + /// + public ushort ImageWidth + { + get { return imageWidth; } + set { imageWidth = value; } + } + + /// + /// This field specifies the height of the image in pixels. + /// + public ushort ImageHeight + { + get { return imageHeight; } + set { imageHeight = value; } + } + + /// + /// This field indicates the number of bits per pixel. This number includes the Attribute or + /// Alpha channel bits. Common values are 8, 16, 24 and 32 but other pixel depths could be used. + /// + public TgaPixelDepth PixelDepth + { + get { return pixelDepth; } + set { pixelDepth = value; } + } + + /// + /// Contains image origin bits and alpha channel bits(or number of overlay bits). + /// + public TgaImageDescriptor ImageDescriptor + { + get { return imageDescriptor; } + set { imageDescriptor = value; } + } + + /// + /// Gets TGA Field size in bytes. + /// + public const int Size = 10; + + /// + /// Make full copy of . + /// + /// + public TgaImageSpec Clone() + { + return new TgaImageSpec(ToBytes()); + } + + /// + /// Make full copy of . + /// + /// + object ICloneable.Clone() + { + return Clone(); + } + + public override bool Equals(object obj) + { + return ((obj is TgaImageSpec) ? Equals((TgaImageSpec)obj) : false); + } + + public bool Equals(TgaImageSpec item) + { + return ( + x_Origin == item.x_Origin && + y_Origin == item.y_Origin && + imageWidth == item.imageWidth && + imageHeight == item.imageHeight && + pixelDepth == item.pixelDepth && + imageDescriptor == item.imageDescriptor); + } + + public static bool operator ==(TgaImageSpec item1, TgaImageSpec item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaImageSpec item1, TgaImageSpec item2) + { + return !(item1 == item2); + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 23 + x_Origin.GetHashCode(); + hash = hash * 23 + y_Origin.GetHashCode(); + hash = hash * 23 + imageWidth.GetHashCode(); + hash = hash * 23 + imageHeight.GetHashCode(); + hash = hash * 23 + pixelDepth.GetHashCode(); + + if (imageDescriptor != null) + hash = hash * 23 + imageDescriptor.GetHashCode(); + + return hash; + } + } + + public override string ToString() + { + return String.Format("{0}={1}, {2}={3}, {4}={5}, {6}={7}, {8}={9}, {10}={11}", + nameof(X_Origin), x_Origin, + nameof(Y_Origin), y_Origin, + nameof(ImageWidth), imageWidth, + nameof(ImageHeight), imageHeight, + nameof(PixelDepth), pixelDepth, + nameof(ImageDescriptor), imageDescriptor); + } + + /// + /// Convert to byte array. + /// + /// Byte array with length = 10. + public byte[] ToBytes() + { + return BitConverterExt.ToBytes(x_Origin, y_Origin, imageWidth, imageHeight, + (byte)pixelDepth, (imageDescriptor == null ? byte.MinValue : imageDescriptor.ToByte())); + } + } + + /// + /// Postage Stamp Image (MaxSize 64x64, uncompressed, PixelDepth like in full image). + /// + public class TgaPostageStampImage : ICloneable + { + byte width = 0; + byte height = 0; + byte[] data = null; + + public TgaPostageStampImage() + { + } + + /// + /// Make from bytes array. + /// + /// Bytes array, first 2 bytes are and , + /// next bytes - image data. + public TgaPostageStampImage(byte[] Bytes) + { + if (Bytes == null) + throw new ArgumentNullException(nameof(Bytes) + " = null!"); + if (Bytes.Length < 2) + throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be >= " + 2 + "!"); + + width = Bytes[0]; + height = Bytes[1]; + + if (Bytes.Length > 2) + data = BitConverterExt.GetElements(Bytes, 2, Bytes.Length - 2); + } + + /// + /// Make from bytes and size. + /// + /// Image Width. + /// Image Height. + /// Postage Stamp Image Data. + public TgaPostageStampImage(byte Width, byte Height, byte[] Bytes) + { + if (Bytes == null) + throw new ArgumentNullException(nameof(Bytes) + " = null!"); + + width = Width; + height = Height; + data = Bytes; + } + + /// + /// Postage Stamp Image Data + /// + public byte[] Data + { + get { return data; } + set { data = value; } + } + + /// + /// Postage Stamp Image Width (maximum = 64). + /// + public byte Width + { + get { return width; } + set { width = value; } + } + + /// + /// Postage Stamp Image Height (maximum = 64). + /// + public byte Height + { + get { return height; } + set { height = value; } + } + + /// + /// Make full copy of . + /// + /// Full independent copy of . + public TgaPostageStampImage Clone() + { + return new TgaPostageStampImage(width, height, BitConverterExt.ToBytes(data)); + } + + /// + /// Make full copy of . + /// + /// Full independent copy of . + object ICloneable.Clone() + { + return Clone(); + } + + public override bool Equals(object obj) + { + return ((obj is TgaPostageStampImage) ? Equals((TgaPostageStampImage)obj) : false); + } + + public bool Equals(TgaPostageStampImage item) + { + return width == item.width && height == item.height && BitConverterExt.IsArraysEqual(data, item.data); + } + + public static bool operator ==(TgaPostageStampImage item1, TgaPostageStampImage item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaPostageStampImage item1, TgaPostageStampImage item2) + { + return !(item1 == item2); + } + + public override int GetHashCode() + { + unchecked + { + int hash = 27; + hash = (13 * hash) + width.GetHashCode(); + hash = (13 * hash) + height.GetHashCode(); + if (data != null) + for (int i = 0; i < data.Length; i++) + hash = (13 * hash) + data[i].GetHashCode(); + return hash; + } + } + + public override string ToString() + { + return String.Format("{0}={1}, {2}={3}, DataLength={4}", + nameof(Width), width, nameof(Height), height, (data == null ? -1 : data.Length)); + } + + /// + /// Convert to byte array. + /// + /// Byte array. + public byte[] ToBytes() + { + return BitConverterExt.ToBytes(width, height, data); + } + } + + public class TgaSoftVersion : ICloneable + { + ushort versionNumber = 0; + char versionLetter = ' '; + + /// + /// Gets Empty , = ' ' (space). + /// + public TgaSoftVersion() + { + } + + /// + /// Make from string. + /// + /// Input string, example: "123d". + public TgaSoftVersion(string Str) + { + if (Str == null) + throw new ArgumentNullException(); + if (Str.Length < 3 || Str.Length > 4) + throw new ArgumentOutOfRangeException(nameof(Str.Length) + " must be equal 3 or 4!"); + + bool Res = ushort.TryParse(Str.Substring(0, 3), out versionNumber); + if (Res && Str.Length == 4) + versionLetter = Str[3]; + } + + /// + /// Make from bytes. + /// + /// Bytes array (byte[3]). + public TgaSoftVersion(byte[] Bytes) + { + if (Bytes == null) + throw new ArgumentNullException(nameof(Bytes) + " = null!"); + if (Bytes.Length != Size) + throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!"); + + versionNumber = BitConverter.ToUInt16(Bytes, 0); + versionLetter = Encoding.ASCII.GetString(Bytes, 2, 1)[0]; + } + + public TgaSoftVersion(ushort VersionNumber, char VersionLetter = ' ') + { + versionNumber = VersionNumber; + versionLetter = VersionLetter; + } + + public ushort VersionNumber + { + get { return versionNumber; } + set { versionNumber = value; } + } + + public char VersionLetter + { + get { return versionLetter; } + set { versionLetter = value; } + } + + /// + /// Gets TGA Field size in bytes. + /// + public const int Size = 3; + + /// + /// Make full copy of . + /// + /// + public TgaSoftVersion Clone() + { + return new TgaSoftVersion(versionNumber, versionLetter); + } + + /// + /// Make full copy of . + /// + /// + object ICloneable.Clone() + { + return Clone(); + } + + public override bool Equals(object obj) + { + return ((obj is TgaSoftVersion) ? Equals((TgaSoftVersion)obj) : false); + } + + public bool Equals(TgaSoftVersion item) + { + return (versionNumber == item.versionNumber && versionLetter == item.versionLetter); + } + + public static bool operator ==(TgaSoftVersion item1, TgaSoftVersion item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaSoftVersion item1, TgaSoftVersion item2) + { + return !(item1 == item2); + } + + public override int GetHashCode() + { + return versionNumber.GetHashCode() ^ versionLetter.GetHashCode(); + } + + public override string ToString() + { + return (versionNumber.ToString("000") + versionLetter).TrimEnd(new char[] { ' ', '\0' }); + } + + /// + /// Convert to byte array. + /// + /// Byte array, (2 bytes) and + /// (ASCII symbol). + public byte[] ToBytes() + { + return ToBytes(versionNumber, versionLetter); + } + + /// + /// Convert to byte array. + /// + /// Set 123 for 1.23 version. + /// Version letter, example: for 'a' - "1.23a". + /// Byte array, (2 bytes) and (ASCII symbol). + public static byte[] ToBytes(ushort VersionNumber, char VersionLetter = ' ') + { + return BitConverterExt.ToBytes(VersionNumber, Encoding.ASCII.GetBytes(VersionLetter.ToString())); + } + } + + /// + /// Use it for working with ASCII strings in TGA files. + /// + public class TgaString : ICloneable + { + public const string XFileSignatuteConst = "TRUEVISION-XFILE"; + public const string DotSymbolConst = "."; + + string origString = String.Empty; + int length = 0; + char blankSpaceChar = DefaultBlankSpaceChar; + bool useEnding = false; + + public TgaString(bool UseEnding = false) + { + useEnding = UseEnding; + } + + public TgaString(byte[] Bytes, bool UseEnding = false) + { + if (Bytes == null) + throw new ArgumentNullException(nameof(Bytes) + " = null!"); + + length = Bytes.Length; + useEnding = UseEnding; + string s = Encoding.ASCII.GetString(Bytes, 0, Bytes.Length - (useEnding ? 1 : 0)); + + if (s.Length > 0) + switch (s[s.Length - 1]) + { + case '\0': + case ' ': + blankSpaceChar = s[s.Length - 1]; + origString = s.TrimEnd(new char[] { s[s.Length - 1] }); + break; + default: + origString = s; + break; + } + } + + public TgaString(int Length, bool UseEnding = false) + { + length = Length; + useEnding = UseEnding; + } + + public TgaString(string Str, int Length, bool UseEnding = false, char BlankSpaceChar = '\0') + { + if (Str == null) + throw new ArgumentNullException(nameof(Str) + " = null!"); + + origString = Str; + length = Length; + blankSpaceChar = BlankSpaceChar; + useEnding = UseEnding; + } + + public string OriginalString + { + get { return origString; } + set { origString = value; } + } + + public int Length + { + get { return length; } + set { length = value; } + } + + public char BlankSpaceChar + { + get { return blankSpaceChar; } + set { blankSpaceChar = value; } + } + + public bool UseEndingChar + { + get { return useEnding; } + set { useEnding = value; } + } + + /// + /// Gets ending char, default '\0'. + /// + public static readonly char DefaultEndingChar = '\0'; + + /// + /// Gets blank space char, value = '\0'. + /// + public static readonly char DefaultBlankSpaceChar = '\0'; + + /// + /// Gets Empty . + /// + public static readonly TgaString Empty = new TgaString(); + + /// + /// Gets with = '\0' and = true. + /// + public static readonly TgaString ZeroTerminator = new TgaString(true); + + /// + /// Gets "." with dot (period) symbol. + /// + public static readonly TgaString DotSymbol = new TgaString(DotSymbolConst, DotSymbolConst.Length); + + /// + /// Gets "TRUEVISION-XFILE" (TGA File Format Version 2.0 signatute). + /// + public static readonly TgaString XFileSignatute = new TgaString(XFileSignatuteConst, XFileSignatuteConst.Length); + + /// + /// Make full independed copy of . + /// + /// Copy of + public TgaString Clone() + { + return new TgaString(origString, length, useEnding, blankSpaceChar); + } + + /// + /// Make full independed copy of . + /// + /// Copy of + object ICloneable.Clone() + { + return Clone(); + } + + public override bool Equals(object obj) + { + return ((obj is TgaString) ? Equals((TgaString)obj) : false); + } + + public bool Equals(TgaString item) + { + return ( + origString == item.origString && + length == item.length && + blankSpaceChar == item.blankSpaceChar && + useEnding == item.useEnding); + } + + public static bool operator ==(TgaString item1, TgaString item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaString item1, TgaString item2) + { + return !(item1 == item2); + } + + public static TgaString operator +(TgaString item1, TgaString item2) + { + if (ReferenceEquals(item1, null) || ReferenceEquals(item2, null)) + throw new ArgumentNullException(); + + return new TgaString(BitConverterExt.ToBytes(item1.ToBytes(), item2.ToBytes())); + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 23 + origString.GetHashCode(); + hash = hash * 23 + length.GetHashCode(); + hash = hash * 23 + blankSpaceChar.GetHashCode(); + hash = hash * 23 + useEnding.GetHashCode(); + return hash; + } + } + + /// + /// Get ASCII-Like string with string-terminators, example: "Some string\0\0\0\0\0". + /// + /// String with replaced string-terminators to "\0". + public override string ToString() + { + return Encoding.ASCII.GetString(ToBytes()).Replace("\0", @"\0"); + } + + /// + /// Get ASCII-Like string to first string-terminator, example: + /// "Some string \0 Some Data \0" - > "Some string". + /// + /// String to first string-terminator. + public string GetString() + { + String Str = Encoding.ASCII.GetString(ToBytes()); + int EndIndex = Str.IndexOf('\0'); + if (EndIndex != -1) + Str = Str.Substring(0, EndIndex); + return Str; + } + + /// + /// Convert to byte array. + /// + /// Byte array, every byte is ASCII symbol. + public byte[] ToBytes() + { + return ToBytes(origString, length, useEnding, blankSpaceChar); + } + + /// + /// Convert to byte array. + /// + /// Input string. + /// Length of output ASCII string with Ending char (if used). + /// Add to string or not? + /// Char for filling blank space in string. If this char is '-' (only for example!), + /// for string "ABC" with = 7, with = true, + /// is '\0', result string is "ABC---\0". + /// Byte array, every byte is ASCII symbol. + public static byte[] ToBytes(string str, int Length, bool UseEnding = true, char BlankSpaceChar = '\0') + { + char[] C = new char[Math.Max(Length, (UseEnding ? 1 : 0))]; + + for (int i = 0; i < C.Length; i++) + C[i] = (i < str.Length ? str[i] : BlankSpaceChar); + + if (UseEnding) + C[C.Length - 1] = DefaultEndingChar; + + return Encoding.ASCII.GetBytes(C); + } + } + + public class TgaTime : ICloneable + { + ushort hours = 0; + ushort minutes = 0; + ushort seconds = 0; + + /// + /// Make empty . + /// + public TgaTime() + { + } + + /// + /// Make from . + /// + /// Some variable. + public TgaTime(TimeSpan Time) + { + hours = (ushort)Time.TotalHours; + minutes = (ushort)Time.Minutes; + seconds = (ushort)Time.Seconds; + } + + /// + /// Make from ushort values. + /// + /// Hour (0 - 65535). + /// Minute (0 - 59). + /// Second (0 - 59). + public TgaTime(ushort Hours, ushort Minutes, ushort Seconds) + { + hours = Hours; + minutes = Minutes; + seconds = Seconds; + } + + /// + /// Make from bytes. + /// + /// Array of bytes(byte[6]). + public TgaTime(byte[] Bytes) + { + if (Bytes == null) + throw new ArgumentNullException(nameof(Bytes) + " = null!"); + else if (Bytes.Length != Size) + throw new ArgumentOutOfRangeException(nameof(Bytes) + " must be equal " + Size + "!"); + + hours = BitConverter.ToUInt16(Bytes, 0); + minutes = BitConverter.ToUInt16(Bytes, 2); + seconds = BitConverter.ToUInt16(Bytes, 4); + } + + /// + /// Gets or Sets hour (0 - 65535). + /// + public ushort Hours + { + get { return hours; } + set { hours = value; } + } + + /// + /// Gets or Sets minute (0 - 59). + /// + public ushort Minutes + { + get { return minutes; } + set { minutes = value; } + } + + /// + /// Gets or Sets second (0 - 59). + /// + public ushort Seconds + { + get { return seconds; } + set { seconds = value; } + } + + /// + /// Gets TGA Field size in bytes. + /// + public const int Size = 6; + + /// + /// Make full independed copy of . + /// + /// Copy of + public TgaTime Clone() + { + return new TgaTime(hours, minutes, seconds); + } + + /// + /// Make full independed copy of . + /// + /// Copy of + object ICloneable.Clone() + { + return Clone(); + } + + public override bool Equals(object obj) + { + return ((obj is TgaTime) ? Equals((TgaTime)obj) : false); + } + + public bool Equals(TgaTime item) + { + return (hours == item.hours && minutes == item.minutes && seconds == item.seconds); + } + + public static bool operator ==(TgaTime item1, TgaTime item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaTime item1, TgaTime item2) + { + return !(item1 == item2); + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 23 + hours.GetHashCode(); + hash = hash * 23 + (minutes << 16 | seconds).GetHashCode(); + return hash; + } + } + + /// + /// Gets like string. + /// + /// String in "H:M:S" format. + public override string ToString() + { + return String.Format("{0}:{1}:{2}", hours, minutes, seconds); + } + + /// + /// Convert to byte array. + /// + /// Byte array with length = 6. + public byte[] ToBytes() + { + return BitConverterExt.ToBytes(hours, minutes, seconds); + } + + /// + /// Gets like . + /// + /// value of . + public TimeSpan ToTimeSpan() + { + return new TimeSpan(hours, minutes, seconds); + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// File Header Area (18 bytes) + /// + public class TgaHeader : ICloneable + { + byte idLength = 0; + TgaColorMapType colorMapType = TgaColorMapType.NoColorMap; + TgaImageType imageType = TgaImageType.NoImageData; + TgaColorMapSpec colorMapSpec = new TgaColorMapSpec(); + TgaImageSpec imageSpec = new TgaImageSpec(); + + /// + /// Make empty . + /// + public TgaHeader() + { + } + + /// + /// Make from bytes. + /// + /// Bytes array (byte[18]). + public TgaHeader(byte[] Bytes) + { + if (Bytes == null) + throw new ArgumentNullException(nameof(Bytes) + " = null!"); + if (Bytes.Length != Size) + throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!"); + + idLength = Bytes[0]; + colorMapType = (TgaColorMapType)Bytes[1]; + imageType = (TgaImageType)Bytes[2]; + colorMapSpec = new TgaColorMapSpec(BitConverterExt.GetElements(Bytes, 3, TgaColorMapSpec.Size)); + imageSpec = new TgaImageSpec(BitConverterExt.GetElements(Bytes, 8, TgaImageSpec.Size)); + } + + /// + /// ID Length - Field 1 (1 byte): + /// This field identifies the number of bytes contained in the Field. + /// The maximum number of characters is 255. A value of zero indicates that no Image ID + /// field is included with the image. + /// + public byte IDLength + { + get { return idLength; } + set { idLength = value; } + } + + /// + /// Color Map Type - Field 2 (1 byte): + /// This field indicates the type of color map (if any) included with the image. + /// There are currently 2 defined values for this field: + /// 0 - indicates that no color-map data is included with this image; + /// 1 - indicates that a color-map is included with this image. + /// + public TgaColorMapType ColorMapType + { + get { return colorMapType; } + set { colorMapType = value; } + } + + /// + /// Image Type - Field 3 (1 byte): + /// The TGA File Format can be used to store Pseudo-Color, True-Color and Direct-Color images + /// of various pixel depths. + /// + public TgaImageType ImageType + { + get { return imageType; } + set { imageType = value; } + } + + /// + /// Color Map Specification - Field 4 (5 bytes): + /// This field and its sub-fields describe the color map (if any) used for the image. + /// If the Color Map Type field is set to zero, indicating that no color map exists, then + /// these 5 bytes should be set to zero. These bytes always must be written to the file. + /// + public TgaColorMapSpec ColorMapSpec + { + get { return colorMapSpec; } + set { colorMapSpec = value; } + } + + /// + /// Image Specification - Field 5 (10 bytes): + /// This field and its sub-fields describe the image screen location, size and pixel depth. + /// These information is always written to the file. + /// + public TgaImageSpec ImageSpec + { + get { return imageSpec; } + set { imageSpec = value; } + } + + /// + /// Gets TGA Header Section size in bytes. + /// + public const int Size = 18; + + /// + /// Make full copy of . + /// + /// Full independent copy of . + public TgaHeader Clone() + { + return new TgaHeader(ToBytes()); + } + + /// + /// Make full copy of . + /// + /// Full independent copy of . + object ICloneable.Clone() + { + return Clone(); + } + + public override bool Equals(object obj) + { + return ((obj is TgaHeader) ? Equals((TgaHeader)obj) : false); + } + + public bool Equals(TgaHeader item) + { + return (idLength == item.idLength && + colorMapType == item.colorMapType && + imageType == item.imageType && + colorMapSpec == item.colorMapSpec && + imageSpec == item.imageSpec); + } + + public static bool operator ==(TgaHeader item1, TgaHeader item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaHeader item1, TgaHeader item2) + { + return !(item1 == item2); + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 23 + (idLength << 24 | (byte)colorMapType << 8 | (byte)imageType).GetHashCode(); + + if (colorMapSpec != null) + hash = hash * 23 + colorMapSpec.GetHashCode(); + + if (imageSpec != null) + hash = hash * 23 + imageSpec.GetHashCode(); + + return hash; + } + } + + public override string ToString() + { + return String.Format("{0}={1}, {2}={3}, {4}={5}, {6}={7}, {8}={9}", + nameof(IDLength), idLength, + nameof(ColorMapType), colorMapType, + nameof(ImageType), imageType, + nameof(ColorMapSpec), colorMapSpec, + nameof(ImageSpec), imageSpec); + } + + /// + /// Convert to byte array. + /// + /// Byte array with size equal . + public byte[] ToBytes() + { + return BitConverterExt.ToBytes(idLength, (byte)colorMapType, (byte)imageType, + (colorMapSpec == null ? new byte[TgaColorMapSpec.Size] : colorMapSpec.ToBytes()), + (imageSpec == null ? new byte[TgaImageSpec.Size] : imageSpec.ToBytes())); + } + } + + /// + /// Image Or ColorMap Area + /// + public class TgaImgOrColMap : ICloneable + { + TgaString imageID = null; + byte[] colorMapData = null; + byte[] imageData = null; + + /// + /// Make empty . + /// + public TgaImgOrColMap() + { + } + + /// + /// Make from arrays. + /// + /// This optional field contains identifying information about the image. + /// The maximum length for this field is 255 bytes. Refer to + /// for the length of this field. If field 1 is set to Zero indicating that no Image ID exists + /// then these bytes are not written to the file. + /// Color Map Data, see description. + /// Image Data, see description. + public TgaImgOrColMap(TgaString ImageID, byte[] ColorMapData, byte[] ImageData) + { + imageID = ImageID; + colorMapData = ColorMapData; + imageData = ImageData; + } + + /// + /// Image ID - Field 6 (variable): + /// This optional field contains identifying information about the image. The maximum length + /// for this field is 255 bytes. Refer to for the length of this + /// field. If field 1 is set to Zero indicating that no Image ID exists then these bytes are not + /// written to the file. Can have text inside (ASCII). + /// + public TgaString ImageID + { + get { return imageID; } + set { imageID = value; } + } + + /// + /// Color Map Data - Field 7 (variable): + /// If the Color Map Type(field 2) field is set to zero indicating that no Color-Map + /// exists then this field will not be present (i.e., no bytes written to the file). + /// This variable-length field contains the actual color map information (LUT data). + /// Field 4.3 specifies the width in bits of each color map entry while Field 4.2 specifies + /// the number of color map entries in this field. These two fields together are used to + /// determine the number of bytes contained in field 7. + /// Each color map entry is stored using an integral number of bytes.The RGB specification + /// for each color map entry is stored in successive bit-fields in the multi-byte entries. + /// Each color bit-field is assumed to be MIN(Field4.3/3, 8) bits in length. If Field 4.3 + /// contains 24, then each color specification is 8 bits in length; if Field 4.3 contains 32, + /// then each color specification is also 8 bits (32/3 gives 10, but 8 is smaller). + /// Unused bit(s) in the multi-byte entries are assumed to specify attribute bits. The + /// attribute bit field is often called the Alpha Channel, Overlay Bit(s) or Interrupt Bit(s). + /// For the TARGA M-8, ATVista and NuVista, the number of bits in a color map specification is + /// 24 (or 32). The red, green, and blue components are each represented by one byte. + /// + public byte[] ColorMapData + { + get { return colorMapData; } + set { colorMapData = value; } + } + + /// + /// Image Data - Field 8 (variable): + /// This field contains (Width)x(Height) pixels. Each pixel specifies image data in one + /// of the following formats: + /// a single color-map index for Pseudo-Color; + /// Attribute, Red, Green and Blue ordered data for True-Color; + /// and independent color-map indices for Direct-Color. + /// The values for Width and Height are specified in Fields 5.3 and 5.4 respectively. + /// The number of attribute and color-definition bits for each pixel are defined in Fields 5.6 + /// and 5.5, respectively.Each pixel is stored as an integral number of bytes. + /// + public byte[] ImageData + { + get { return imageData; } + set { imageData = value; } + } + + /// + /// Make full copy of . + /// + /// Full independed copy of . + public TgaImgOrColMap Clone() + { + return new TgaImgOrColMap( + (imageID == null ? null : imageID.Clone()), + (colorMapData == null ? null : (byte[])colorMapData.Clone()), + (imageData == null ? null : (byte[])imageData.Clone())); + } + + /// + /// Make full copy of . + /// + /// Full independed copy of . + object ICloneable.Clone() + { + return Clone(); + } + + public override bool Equals(object obj) + { + return ((obj is TgaImgOrColMap) ? Equals((TgaImgOrColMap)obj) : false); + } + + public bool Equals(TgaImgOrColMap item) + { + return imageID == item.imageID && + BitConverterExt.IsArraysEqual(colorMapData, item.colorMapData) && + BitConverterExt.IsArraysEqual(imageData, item.imageData); + } + + public static bool operator ==(TgaImgOrColMap item1, TgaImgOrColMap item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaImgOrColMap item1, TgaImgOrColMap item2) + { + return !(item1 == item2); + } + + public override int GetHashCode() + { + unchecked + { + int hash = 27; + + if (imageID != null) + hash = (13 * hash) + imageID.GetHashCode(); + if (colorMapData != null) + for (int i = 0; i < colorMapData.Length; i++) + hash = (13 * hash) + colorMapData[i].GetHashCode(); + if (imageData != null) + for (int i = 0; i < imageData.Length; i++) + hash = (13 * hash) + imageData[i].GetHashCode(); + + return hash; + } + } + } //No ToBytes() + + /// + /// Developer Area + /// //? + public class TgaDevArea : ICloneable + { + List entries = new List(); + + public TgaDevArea() + { + } + + public TgaDevArea(List Entries) + { + if (Entries == null) + throw new ArgumentNullException(nameof(Entries) + " = null!"); + + entries = Entries; + } + + /// + /// Developer Data - Field 9 (variable): + /// + public List Entries + { + get { return entries; } + set { entries = value; } + } + + public int Count + { + get { return entries.Count; } + } + + public TgaDevEntry this[int index] + { + get { return entries[index]; } + set { entries[index] = value; } + } + + /// + /// Make full copy of . + /// + /// Full independent copy of . + public TgaDevArea Clone() + { + if (entries == null) + return new TgaDevArea(null); + + List L = new List(); + for (int i = 0; i < entries.Count; i++) + L.Add(entries[i].Clone()); + + return new TgaDevArea(L); + } + + /// + /// Make full copy of . + /// + /// Full independent copy of . + object ICloneable.Clone() + { + return Clone(); + } + + public override bool Equals(object obj) + { + return ((obj is TgaDevArea) ? Equals((TgaDevArea)obj) : false); + } + + public bool Equals(TgaDevArea item) + { + return BitConverterExt.IsListsEqual(entries, item.entries); + } + + public static bool operator ==(TgaDevArea item1, TgaDevArea item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaDevArea item1, TgaDevArea item2) + { + return !(item1 == item2); + } + + public override int GetHashCode() + { + unchecked + { + int hash = 27; + if (entries != null) + for (int i = 0; i < entries.Count; i++) + hash = (13 * hash) + entries[i].GetHashCode(); + return hash; + } + } + + /// + /// Convert (without Fields Data, only Directory!) to byte array. + /// + /// Byte array, Len = (NUMBER_OF_TAGS_IN_THE_DIRECTORY * 10) + 2 bytes in size. + /// The "+ 2" includes the 2 bytes for the number of tags in the directory. + public byte[] ToBytes() + { + if (entries == null) + throw new Exception(nameof(Entries) + " = null!"); + + ushort NumberOfEntries = (ushort)Math.Min(ushort.MaxValue, entries.Count); + List DevDir = new List(BitConverter.GetBytes(NumberOfEntries)); + + for (int i = 0; i < entries.Count; i++) + { + DevDir.AddRange(BitConverter.GetBytes(entries[i].Tag)); + DevDir.AddRange(BitConverter.GetBytes(entries[i].Offset)); + DevDir.AddRange(BitConverter.GetBytes(entries[i].FieldSize)); + } + + return DevDir.ToArray(); + } + } //Not full ToBytes() + + /// + /// Extension Area + /// + public class TgaExtArea : ICloneable + { + public const int MinSize = 495; //bytes + + ushort extensionSize = MinSize; + TgaString authorName = new TgaString(41, true); + TgaComment authorComments = new TgaComment(); + TgaDateTime dateTimeStamp = new TgaDateTime(); + TgaString jobNameOrID = new TgaString(41, true); + TgaTime jobTime = new TgaTime(); + TgaString softwareID = new TgaString(41, true); + TgaSoftVersion softVersion = new TgaSoftVersion(); + TgaColorKey keyColor = new TgaColorKey(); + TgaFraction pixelAspectRatio = TgaFraction.Empty; + TgaFraction gammaValue = TgaFraction.Empty; + uint colorCorrectionOffset = 0; + uint postageStampOffset = 0; + uint scanLineOffset = 0; + TgaAttrType attributesType = TgaAttrType.NoAlpha; + uint[] scanLineTable = null; + TgaPostageStampImage postageStampImage = null; + ushort[] colorCorrectionTable = null; + byte[] otherDataInExtensionArea = null; + + public TgaExtArea() + { + } + + /// + /// Make from bytes. Warning: , + /// , not included, + /// because thea are can be not in the Extension Area of TGA file! + /// + /// Bytes of . + /// Scan Line Table. + /// Postage Stamp Image. + /// Color Correction Table. + public TgaExtArea(byte[] Bytes, uint[] SLT = null, TgaPostageStampImage PostImg = null, ushort[] CCT = null) + { + if (Bytes == null) + throw new ArgumentNullException(nameof(Bytes) + " = null!"); + if (Bytes.Length < MinSize) + throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be >= " + MinSize + "!"); + + extensionSize = BitConverter.ToUInt16(Bytes, 0); + authorName = new TgaString(BitConverterExt.GetElements(Bytes, 2, 41), true); + authorComments = new TgaComment(BitConverterExt.GetElements(Bytes, 43, TgaComment.Size)); + dateTimeStamp = new TgaDateTime(BitConverterExt.GetElements(Bytes, 367, TgaDateTime.Size)); + jobNameOrID = new TgaString(BitConverterExt.GetElements(Bytes, 379, 41), true); + jobTime = new TgaTime(BitConverterExt.GetElements(Bytes, 420, TgaTime.Size)); + softwareID = new TgaString(BitConverterExt.GetElements(Bytes, 426, 41), true); + softVersion = new TgaSoftVersion(BitConverterExt.GetElements(Bytes, 467, TgaSoftVersion.Size)); + keyColor = new TgaColorKey(BitConverterExt.GetElements(Bytes, 470, TgaColorKey.Size)); + pixelAspectRatio = new TgaFraction(BitConverterExt.GetElements(Bytes, 474, TgaFraction.Size)); + gammaValue = new TgaFraction(BitConverterExt.GetElements(Bytes, 478, TgaFraction.Size)); + colorCorrectionOffset = BitConverter.ToUInt32(Bytes, 482); + postageStampOffset = BitConverter.ToUInt32(Bytes, 486); + scanLineOffset = BitConverter.ToUInt32(Bytes, 490); + attributesType = (TgaAttrType)Bytes[494]; + + if (extensionSize > MinSize) + otherDataInExtensionArea = BitConverterExt.GetElements(Bytes, 495, Bytes.Length - MinSize); + + scanLineTable = SLT; + postageStampImage = PostImg; + colorCorrectionTable = CCT; + } + + #region Properties + /// + /// Extension Size - Field 10 (2 Bytes): + /// This field is a SHORT field which specifies the number of BYTES in the fixedlength portion of + /// the Extension Area. For Version 2.0 of the TGA File Format, this number should be set to 495. + /// If the number found in this field is not 495, then the file will be assumed to be of a + /// version other than 2.0. If it ever becomes necessary to alter this number, the change + /// will be controlled by Truevision, and will be accompanied by a revision to the TGA File + /// Format with an accompanying change in the version number. + /// + public ushort ExtensionSize + { + get { return extensionSize; } + set { extensionSize = value; } + } + + /// + /// Author Name - Field 11 (41 Bytes): + /// Bytes 2-42 - This field is an ASCII field of 41 bytes where the last byte must be a null + /// (binary zero). This gives a total of 40 ASCII characters for the name. If the field is used, + /// it should contain the name of the person who created the image (author). If the field is not + /// used, you may fill it with nulls or a series of blanks(spaces) terminated by a null. + /// The 41st byte must always be a null. + /// + public TgaString AuthorName + { + get { return authorName; } + set { authorName = value; } + } + + /// + /// Author Comments - Field 12 (324 Bytes): + /// Bytes 43-366 - This is an ASCII field consisting of 324 bytes which are organized as four lines + /// of 80 characters, each followed by a null terminator.This field is provided, in addition to the + /// original IMAGE ID field(in the original TGA format), because it was determined that a few + /// developers had used the IMAGE ID field for their own purposes.This field gives the developer + /// four lines of 80 characters each, to use as an Author Comment area. Each line is fixed to 81 + /// bytes which makes access to the four lines easy.Each line must be terminated by a null. + /// If you do not use all 80 available characters in the line, place the null after the last + /// character and blank or null fill the rest of the line. The 81st byte of each of the four + /// lines must be null. + /// + public TgaComment AuthorComments + { + get { return authorComments; } + set { authorComments = value; } + } + + /// + /// Date/Time Stamp - Field 13 (12 Bytes): + /// Bytes 367-378 - This field contains a series of 6 SHORT values which define the integer + /// value for the date and time that the image was saved. This data is formatted as follows: + /// SHORT 0: Month(1 - 12) + /// SHORT 1: Day(1 - 31) + /// SHORT 2: Year(4 digit, ie. 1989) + /// SHORT 3: Hour(0 - 23) + /// SHORT 4: Minute(0 - 59) + /// SHORT 5: Second(0 - 59) + /// Even though operating systems typically time- and date-stamp files, this feature is + /// provided because the operating system may change the time and date stamp if the file is + /// copied. By using this area, you are guaranteed an unmodified region for date and time + /// recording. If the fields are not used, you should fill them with binary zeros (0). + /// + public TgaDateTime DateTimeStamp + { + get { return dateTimeStamp; } + set { dateTimeStamp = value; } + } + + /// + /// Job Name/ID - Field 14 (41 Bytes): + /// Bytes 379-419 - This field is an ASCII field of 41 bytes where the last byte must be + /// a binary zero. This gives a total of 40 ASCII characters for the job name or the ID. + /// If the field is used, it should contain a name or id tag which refers to the job with + /// which the image was associated.This allows production companies (and others) to tie + /// images with jobs by using this field as a job name (i.e., CITY BANK) or job id number + /// (i.e., CITY023). If the field is not used, you may fill it with a null terminated series + /// of blanks (spaces) or nulls. In any case, the 41st byte must be a null. + /// + public TgaString JobNameOrID + { + get { return jobNameOrID; } + set { jobNameOrID = value; } + } + + /// + /// Job Time - Field 15 (6 Bytes): + /// Bytes 420-425 - This field contains a series of 3 SHORT values which define the integer + /// value for the job elapsed time when the image was saved.This data is formatted as follows: + /// SHORT 0: Hours(0 - 65535) + /// SHORT 1: Minutes(0 - 59) + /// SHORT 2: Seconds(0 - 59) + /// The purpose of this field is to allow production houses (and others) to keep a running total + /// of the amount of time invested in a particular image. This may be useful for billing, costing, + /// and time estimating. If the fields are not used, you should fill them with binary zeros (0). + /// + public TgaTime JobTime + { + get { return jobTime; } + set { jobTime = value; } + } + + /// + /// Software ID - Field 16 (41 Bytes): + /// Bytes 426-466 - This field is an ASCII field of 41 bytes where the last byte must be + /// a binary zero (null). This gives a total of 40 ASCII characters for the Software ID. + /// The purpose of this field is to allow software to determine and record with what program + /// a particular image was created.If the field is not used, you may fill it with a + /// null terminated series of blanks (spaces) or nulls. The 41st byte must always be a null. + /// + public TgaString SoftwareID + { + get { return softwareID; } + set { softwareID = value; } + } + + /// + /// Software Version - Field 17 (3 Bytes): + /// Bytes 467-469 - This field consists of two sub-fields, a SHORT and an ASCII BYTE. + /// The purpose of this field is to define the version of software defined by the + /// “Software ID” field above. The SHORT contains the version number as a binary + /// integer times 100. + /// Therefore, software version 4.17 would be the integer value 417.This allows for + /// two decimal positions of sub-version.The ASCII BYTE supports developers who also + /// tag a release letter to the end. For example, if the version number is 1.17b, then + /// the SHORT would contain 117. and the ASCII BYTE would contain “b”. + /// The organization is as follows: + /// SHORT (Bytes 0 - 1): Version Number * 100 + /// BYTE(Byte 2): Version Letter + /// If you do not use this field, set the SHORT to binary zero, and the BYTE to a space(“ “) + /// + public TgaSoftVersion SoftVersion + { + get { return softVersion; } + set { softVersion = value; } + } + + /// + /// Key Color - Field 18 (4 Bytes): + /// Bytes 470-473 - This field contains a long value which is the key color in effect at + /// the time the image is saved. The format is in A:R:G:B where ‘A’ (most significant byte) + /// is the alpha channel key color(if you don’t have an alpha channel in your application, + /// keep this byte zero [0]). + /// The Key Color can be thought of as the ‘background color’ or ‘transparent color’. + /// This is the color of the ‘non image’ area of the screen, and the same color that the + /// screen would be cleared to if erased in the application. If you don’t use this field, + /// set it to all zeros (0). Setting the field to all zeros is the same as selecting a key + /// color of black. + /// A good example of a key color is the ‘transparent color’ used in TIPS™ for WINDOW loading/saving. + /// + public TgaColorKey KeyColor + { + get { return keyColor; } + set { keyColor = value; } + } + + /// + /// Pixel Aspect Ratio - Field 19 (4 Bytes): + /// Bytes 474-477 - This field contains two SHORT sub-fields, which when taken together + /// specify a pixel size ratio.The format is as follows: + /// SHORT 0: Pixel Ratio Numerator(pixel width) + /// SHORT 1: Pixel Ratio Denominator(pixel height) + /// These sub-fields may be used to determine the aspect ratio of a pixel. This is useful when + /// it is important to preserve the proper aspect ratio of the saved image. If the two values + /// are set to the same non-zero value, then the image is composed of square pixels. A zero + /// in the second sub-field (denominator) indicates that no pixel aspect ratio is specified. + /// + public TgaFraction PixelAspectRatio + { + get { return pixelAspectRatio; } + set { pixelAspectRatio = value; } + } + + /// + /// Gamma Value - Field 20 (4 Bytes): + /// Bytes 478-481 - This field contains two SHORT sub-fields, which when taken together in a ratio, + /// provide a fractional gamma value.The format is as follows: + /// SHORT 0: Gamma Numerator + /// SHORT 1: Gamma Denominator + /// The resulting value should be in the range of 0.0 to 10.0, with only one decimal place of + /// precision necessary. An uncorrected image (an image with no gamma) should have the value 1.0 as + /// the result.This may be accomplished by placing thesame, non-zero values in both positions + /// (i.e., 1/1). If you decide to totally ignore this field, please set the denominator (the second + /// SHORT) to the value zero. This will indicate that the Gamma Value field is not being used. + /// + public TgaFraction GammaValue + { + get { return gammaValue; } + set { gammaValue = value; } + } + + /// + /// Color Correction Offset - Field 21 (4 Bytes): + /// Bytes 482-485 - This field is a 4-byte field containing a single offset value. This is an offset + /// from the beginning of the file to the start of the Color Correction table. This table may be + /// written anywhere between the end of the Image Data field (field 8) and the start of the TGA + /// File Footer. If the image has no Color Correction Table or if the Gamma Value setting is + /// sufficient, set this value to zero and do not write a Correction Table anywhere. + /// + public uint ColorCorrectionTableOffset + { + get { return colorCorrectionOffset; } + set { colorCorrectionOffset = value; } + } + + /// + /// Postage Stamp Offset - Field 22 (4 Bytes): + /// Bytes 486-489 - This field is a 4-byte field containing a single offset value. This is an offset + /// from the beginning of the file to the start of the Postage Stamp Image. The Postage Stamp Image + /// must be written after Field 25 (Scan Line Table) but before the start of the TGA File Footer. + /// If no postage stamp is stored, set this field to the value zero (0). + /// + public uint PostageStampOffset + { + get { return postageStampOffset; } + set { postageStampOffset = value; } + } + + /// + /// Scan Line Offset - Field 23 (4 Bytes): + /// Bytes 490-493 - This field is a 4-byte field containing a single offset value. This is an + /// offset from the beginning of the file to the start of the Scan Line Table. + /// + public uint ScanLineOffset + { + get { return scanLineOffset; } + set { scanLineOffset = value; } + } + + /// + /// Attributes Type - Field 24 (1 Byte): + /// Byte 494 - This single byte field contains a value which specifies the type of Alpha channel + /// data contained in the file. Value Meaning: + /// 0: no Alpha data included (bits 3-0 of field 5.6 should also be set to zero) + /// 1: undefined data in the Alpha field, can be ignored + /// 2: undefined data in the Alpha field, but should be retained + /// 3: useful Alpha channel data is present + /// 4: pre-multiplied Alpha(see description below) + /// 5 -127: RESERVED + /// 128-255: Un-assigned + /// Pre-multiplied Alpha Example: Suppose the Alpha channel data is being used to specify the + /// opacity of each pixel(for use when the image is overlayed on another image), where 0 indicates + /// that the pixel is completely transparent and a value of 1 indicates that the pixel is + /// completely opaque(assume all component values have been normalized). + /// A quadruple(a, r, g, b) of( 0.5, 1, 0, 0) would indicate that the pixel is pure red with a + /// transparency of one-half. For numerous reasons(including image compositing) is is better to + /// pre-multiply the individual color components with the value in the Alpha channel. + /// A pre-multiplication of the above would produce a quadruple(0.5, 0.5, 0, 0). + /// A value of 3 in the Attributes Type Field(field 23) would indicate that the color components + /// of the pixel have already been scaled by the value in the Alpha channel. + /// + public TgaAttrType AttributesType + { + get { return attributesType; } + set { attributesType = value; } + } + + /// + /// Scan Line Table - Field 25 (Variable): + /// This information is provided, at the developers’ request, for two purposes: + /// 1) To make random access of compressed images easy. + /// 2) To allow “giant picture” access in smaller “chunks”. + /// This table should contain a series of 4-byte offsets.Each offset you write should point to the + /// start of the next scan line, in the order that the image was saved (i.e., top down or bottom up). + /// The offset should be from the start of the file.Therefore, you will have a four byte value for + /// each scan line in your image. This means that if your image is 768 pixels tall, you will have 768, + /// 4-byte offset pointers (for a total of 3072 bytes). This size is not extreme, and thus this table + /// can be built and maintained in memory, and then written out at the proper time. + /// + public uint[] ScanLineTable + { + get { return scanLineTable; } + set { scanLineTable = value; } + } + + /// + /// Postage Stamp Image - Field 26 (Variable): + /// The Postage Stamp area is a smaller representation of the original image. This is useful for + /// “browsing” a collection of image files. If your application can deal with a postage stamp image, + /// it is recommended that you create one using sub-sampling techniques to create the best + /// representation possible. The postage stamp image must be stored in the same format as the normal + /// image specified in the file, but without any compression. The first byte of the postage stamp + /// image specifies the X size of the stamp in pixels, the second byte of the stamp image specifies the + /// Y size of the stamp in pixels. Truevision does not recommend stamps larger than 64x64 pixels, and + /// suggests that any stamps stored larger be clipped. Obviously, the storage of the postage stamp + /// relies heavily on the storage of the image. The two images are stored using the same format under + /// the assumption that if you can read the image, then you can read the postage stamp. If the original + /// image is color mapped, DO NOT average the postage stamp, as you will create new colors not in your map. + /// + public TgaPostageStampImage PostageStampImage + { + get { return postageStampImage; } + set { postageStampImage = value; } + } + + /// + /// Color Correction Table - Field 27 (2K Bytes): + /// The Color Correction Table is a block of 256 x 4 SHORT values, where each set of + /// four contiguous values are the desired A:R:G:B correction for that entry. This + /// allows the user to store a correction table for image remapping or LUT driving. + /// Since each color in the block is a SHORT, the maximum value for a color gun (i.e., + /// A, R, G, B) is 65535, and the minimum value is zero.Therefore, BLACK maps to + /// 0, 0, 0, 0 and WHITE maps to 65535, 65535, 65535, 65535. + /// + public ushort[] ColorCorrectionTable + { + get { return colorCorrectionTable; } + set { colorCorrectionTable = value; } + } + + /// + /// Other Data In Extension Area (if > 495). + /// + public byte[] OtherDataInExtensionArea + { + get { return otherDataInExtensionArea; } + set { otherDataInExtensionArea = value; } + } + #endregion + + /// + /// Make full copy of . + /// + /// Full independent copy of . + public TgaExtArea Clone() + { + TgaExtArea NewExtArea = new TgaExtArea(); + NewExtArea.extensionSize = extensionSize; + NewExtArea.authorName = authorName.Clone(); + NewExtArea.authorComments = authorComments.Clone(); + NewExtArea.dateTimeStamp = dateTimeStamp.Clone(); + NewExtArea.jobNameOrID = jobNameOrID.Clone(); + NewExtArea.jobTime = jobTime.Clone(); + NewExtArea.softwareID = softwareID.Clone(); + NewExtArea.softVersion = softVersion.Clone(); + NewExtArea.keyColor = keyColor.Clone(); + NewExtArea.pixelAspectRatio = pixelAspectRatio.Clone(); + NewExtArea.gammaValue = gammaValue.Clone(); + NewExtArea.colorCorrectionOffset = colorCorrectionOffset; + NewExtArea.postageStampOffset = postageStampOffset; + NewExtArea.scanLineOffset = scanLineOffset; + NewExtArea.attributesType = attributesType; + + if (scanLineTable != null) + NewExtArea.scanLineTable = (uint[])scanLineTable.Clone(); + if (postageStampImage != null) + NewExtArea.postageStampImage = new TgaPostageStampImage(postageStampImage.ToBytes()); + if (colorCorrectionTable != null) + NewExtArea.colorCorrectionTable = (ushort[])colorCorrectionTable.Clone(); + + if (otherDataInExtensionArea != null) + NewExtArea.otherDataInExtensionArea = (byte[])otherDataInExtensionArea.Clone(); + + return NewExtArea; + } + + /// + /// Make full copy of . + /// + /// Full independent copy of . + object ICloneable.Clone() + { + return Clone(); + } + + public override bool Equals(object obj) + { + return ((obj is TgaExtArea) ? Equals((TgaExtArea)obj) : false); + } + + public bool Equals(TgaExtArea item) + { + return (extensionSize == item.extensionSize && + authorName == item.authorName && + authorComments == item.authorComments && + dateTimeStamp == item.dateTimeStamp && + jobNameOrID == item.jobNameOrID && + jobTime == item.jobTime && + softwareID == item.softwareID && + softVersion == item.softVersion && + keyColor == item.keyColor && + pixelAspectRatio == item.pixelAspectRatio && + gammaValue == item.gammaValue && + colorCorrectionOffset == item.colorCorrectionOffset && + postageStampOffset == item.postageStampOffset && + scanLineOffset == item.scanLineOffset && + attributesType == item.attributesType && + + BitConverterExt.IsArraysEqual(scanLineTable, item.scanLineTable) && + postageStampImage == item.postageStampImage && + BitConverterExt.IsArraysEqual(colorCorrectionTable, item.colorCorrectionTable) && + + BitConverterExt.IsArraysEqual(otherDataInExtensionArea, item.otherDataInExtensionArea)); + } + + public static bool operator ==(TgaExtArea item1, TgaExtArea item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaExtArea item1, TgaExtArea item2) + { + return !(item1 == item2); + } + + public override int GetHashCode() + { + unchecked + { + int hash = 27; + hash = (13 * hash) + extensionSize.GetHashCode(); + hash = (13 * hash) + authorName.GetHashCode(); + hash = (13 * hash) + authorComments.GetHashCode(); + hash = (13 * hash) + dateTimeStamp.GetHashCode(); + hash = (13 * hash) + jobNameOrID.GetHashCode(); + hash = (13 * hash) + jobTime.GetHashCode(); + hash = (13 * hash) + softwareID.GetHashCode(); + hash = (13 * hash) + softVersion.GetHashCode(); + hash = (13 * hash) + keyColor.GetHashCode(); + hash = (13 * hash) + pixelAspectRatio.GetHashCode(); + hash = (13 * hash) + gammaValue.GetHashCode(); + hash = (13 * hash) + colorCorrectionOffset.GetHashCode(); + hash = (13 * hash) + postageStampOffset.GetHashCode(); + hash = (13 * hash) + scanLineOffset.GetHashCode(); + hash = (13 * hash) + attributesType.GetHashCode(); + + if (scanLineTable != null) + for (int i = 0; i < scanLineTable.Length; i++) + hash = (13 * hash) + scanLineTable[i].GetHashCode(); + + if (postageStampImage != null) + hash = (13 * hash) + postageStampImage.GetHashCode(); + + if (colorCorrectionTable != null) + for (int i = 0; i < colorCorrectionTable.Length; i++) + hash = (13 * hash) + colorCorrectionTable[i].GetHashCode(); + + if (otherDataInExtensionArea != null) + for (int i = 0; i < otherDataInExtensionArea.Length; i++) + hash = (13 * hash) + otherDataInExtensionArea[i].GetHashCode(); + + return hash; + } + } + + /// + /// Convert to byte array. Warning: , + /// , not included, + /// because thea are can be not in the Extension Area of TGA file! + /// + /// Byte array. + public byte[] ToBytes() + { + #region Exceptions check + if (authorName == null) + authorName = new TgaString(41, true); + + if (authorComments == null) + authorComments = new TgaComment(); + + if (dateTimeStamp == null) + dateTimeStamp = new TgaDateTime(DateTime.UtcNow); + + if (jobNameOrID == null) + jobNameOrID = new TgaString(41, true); + + if (jobTime == null) + jobTime = new TgaTime(); + + if (softwareID == null) + softwareID = new TgaString(41, true); + + if (softVersion == null) + softVersion = new TgaSoftVersion(); + + if (keyColor == null) + keyColor = new TgaColorKey(); + + if (pixelAspectRatio == null) + pixelAspectRatio = new TgaFraction(); + + if (gammaValue == null) + gammaValue = new TgaFraction(); + #endregion + + return BitConverterExt.ToBytes( + extensionSize, + authorName.ToBytes(), + authorComments.ToBytes(), + dateTimeStamp.ToBytes(), + jobNameOrID.ToBytes(), + jobTime.ToBytes(), + softwareID.ToBytes(), + softVersion.ToBytes(), + keyColor.ToBytes(), + pixelAspectRatio.ToBytes(), + gammaValue.ToBytes(), + colorCorrectionOffset, + postageStampOffset, + scanLineOffset, + (byte)attributesType, + otherDataInExtensionArea); + } + } //Not full ToBytes() + + /// + /// File Footer Area + /// + public class TgaFooter : ICloneable + { + uint extAreaOffset = 0; + uint devDirOffset = 0; + TgaString signature = TgaString.XFileSignatute; + TgaString reservedChar = TgaString.DotSymbol; + TgaString zeroStrTerminator = TgaString.ZeroTerminator; + + /// + /// Make NewXFile format TGA Footer with = 0 and + /// = 0. + /// + public TgaFooter() + { + } + + /// + /// Make from values. + /// + /// Extension Area Offset, offset from the beginning of the file. + /// Developer Directory Offset, offset from the beginning of the file. + /// New TGA format signature. + /// Reserved Character - ASCII character “.” (period). + /// Binary Zero Terminator, a binary zero which acts as a final terminator. + public TgaFooter(uint ExtOff, uint DevDirOff, TgaString Sign, TgaString ReservChr, TgaString Termin) + { + extAreaOffset = ExtOff; + devDirOffset = DevDirOff; + signature = Sign; + reservedChar = ReservChr; + zeroStrTerminator = Termin; + } + + /// + /// Make from bytes (if signature is right). + /// + /// Bytes array (byte[26]). + public TgaFooter(byte[] Bytes) + { + if (Bytes == null) + throw new ArgumentNullException(nameof(Bytes) + " = null!"); + if (Bytes.Length != Size) + throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!"); + + extAreaOffset = BitConverter.ToUInt32(Bytes, 0); + devDirOffset = BitConverter.ToUInt32(Bytes, 4); + signature = new TgaString(BitConverterExt.GetElements(Bytes, 8, TgaString.XFileSignatuteConst.Length)); + reservedChar = new TgaString(new byte[] { Bytes[24] }); + zeroStrTerminator = new TgaString(new byte[] { Bytes[25] }); + } + + /// + /// Byte 0-3 - Extension Area Offset - Field 28 + /// The first four bytes (bytes 0-3, the first LONG) of the TGA File Footer contain an + /// offset from the beginning of the file to the start of the Extension Area. Simply + /// SEEK to this location to position to the start of the Extension Area. If the + /// Extension Area Offset is zero, no Extension Area exists in the file. + /// + public uint ExtensionAreaOffset + { + get { return extAreaOffset; } + set { extAreaOffset = value; } + } + + /// + /// Byte 4-7 - Developer Directory Offset - Field 29 + /// The next four bytes(bytes 4-7, the second LONG) contain an offset from the + /// beginning of the file to the start of the Developer Directory. If the Developer + /// Directory Offset is zero, then the Developer Area does not exist. + /// + public uint DeveloperDirectoryOffset + { + get { return devDirOffset; } + set { devDirOffset = value; } + } + + /// + /// Byte 8-23 - Signature - Field 30 + /// This string is exactly 16 bytes long and is formatted exactly as shown below + /// capital letters), with a hyphen between “TRUEVISION” and “XFILE.” If the + /// signature is detected, the file is assumed to be of the New TGA format and MAY, + /// therefore, contain the Developer Area and/or the Extension Area fields.If the + /// signature is not found, then the file is assumed to be in the Original TGA format. + /// + public TgaString Signature + { + get { return signature; } + set { signature = value; } + } + + /// + /// Byte 24 - Reserved Character - Field 31 + /// Byte 24 is an ASCII character “.” (period). This character MUST BE a period or + /// the file is not considered a proper TGA file. + /// + public TgaString ReservedCharacter + { + get { return reservedChar; } + set { reservedChar = value; } + } + + /// + /// Byte 25 - Binary Zero String Terminator - Field 32 + /// Byte 25 is a binary zero which acts as a final terminator and allows the entire TGA + /// File Footer to be read and utilized as a “C” string. + /// + public TgaString BinaryZeroStringTerminator + { + get { return zeroStrTerminator; } + set { zeroStrTerminator = value; } + } + + /// + /// Make full copy of . + /// + /// + public TgaFooter Clone() + { + return new TgaFooter(extAreaOffset, devDirOffset, signature.Clone(), + reservedChar.Clone(), zeroStrTerminator.Clone()); + } + + /// + /// Make full copy of . + /// + /// + object ICloneable.Clone() + { + return Clone(); + } + + /// + /// Gets TGA Footer Section size in bytes. + /// + public const int Size = 26; + + public override bool Equals(object obj) + { + return ((obj is TgaFooter) ? Equals((TgaFooter)obj) : false); + } + + public bool Equals(TgaFooter item) + { + return (extAreaOffset == item.extAreaOffset && + devDirOffset == item.devDirOffset && + signature == item.signature && + reservedChar == item.reservedChar && + zeroStrTerminator == item.zeroStrTerminator); + } + + public static bool operator ==(TgaFooter item1, TgaFooter item2) + { + if (ReferenceEquals(item1, null)) + return ReferenceEquals(item2, null); + + if (ReferenceEquals(item2, null)) + return ReferenceEquals(item1, null); + + return item1.Equals(item2); + } + + public static bool operator !=(TgaFooter item1, TgaFooter item2) + { + return !(item1 == item2); + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 23 + extAreaOffset.GetHashCode(); + hash = hash * 23 + devDirOffset.GetHashCode(); + + if (signature != null) + hash = hash * 23 + signature.GetHashCode(); + + if (reservedChar != null) + hash = hash * 23 + reservedChar.GetHashCode(); + + if (zeroStrTerminator != null) + hash = hash * 23 + zeroStrTerminator.GetHashCode(); + + return hash; + } + } + + public override string ToString() + { + return String.Format("{0}={1}, {2}={3}, FullSignature={4}", + nameof(ExtensionAreaOffset), extAreaOffset, nameof(DeveloperDirectoryOffset), devDirOffset, + (signature + reservedChar + zeroStrTerminator).ToString()); + } + + /// + /// Convert to byte array. + /// + /// Byte array with size equal . + public byte[] ToBytes() + { + return BitConverterExt.ToBytes(extAreaOffset, devDirOffset, + signature.ToBytes(), reservedChar.ToBytes(), zeroStrTerminator.ToBytes()); + } + + /// + /// Is footer is real footer of TGA File Format Version 2.0? + /// Checking by . + /// + public bool IsFooterCorrect + { + get { return signature == TgaString.XFileSignatute; } + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + + /// + /// Simplify ByteConversion operations, like concatination of byte arrays, comparing and other. + /// + public static class BitConverterExt + { + /// + /// Combine byte, byte[], (u)short, (u)int, (u)long values to byte[] array. + /// + /// Array of byte, byte[], (u)short, (u)int, (u)long values. + /// Array of bytes, null when some object is null. + public static byte[] ToBytes(params object[] obj) + { + if (obj == null) + return null; + + List BytesList = new List(); + + for (int i = 0; i < obj.Length; i++) + { + if (obj[i] == null) + continue; + else if (obj[i] is byte) + BytesList.Add((byte)obj[i]); + else if (obj[i] is byte[]) + BytesList.AddRange((byte[])obj[i]); + else if (obj[i] is short) + BytesList.AddRange(BitConverter.GetBytes((short)obj[i])); + else if (obj[i] is ushort) + BytesList.AddRange(BitConverter.GetBytes((ushort)obj[i])); + else if (obj[i] is int) + BytesList.AddRange(BitConverter.GetBytes((int)obj[i])); + else if (obj[i] is uint) + BytesList.AddRange(BitConverter.GetBytes((uint)obj[i])); + else if (obj[i] is long) + BytesList.AddRange(BitConverter.GetBytes((long)obj[i])); + else if (obj[i] is ulong) + BytesList.AddRange(BitConverter.GetBytes((ulong)obj[i])); + } + return BytesList.ToArray(); + } + + /// + /// Copies a range of elements from an Array starting at the specified source index. + /// The length and the index are specified as 32-bit integers. + /// + /// The that contains the data to copy. + /// A 32-bit integer that represents the index in the + /// at which copying begins. + /// A 32-bit integer that represents the number of elements to copy. + /// + public static T[] GetElements(T[] SrcArray, int Offset, int Count) + { + if (SrcArray == null) + throw new ArgumentNullException(nameof(SrcArray) + " is null!"); + + if (Offset >= SrcArray.Length || Offset < 0) + throw new ArgumentOutOfRangeException(nameof(Offset) + " has wrong value!"); + + if (Count <= 0 || Offset + Count > SrcArray.Length) + throw new ArgumentOutOfRangeException(nameof(Count) + " has wrong value!"); + + T[] Buff = new T[Count]; + Array.Copy(SrcArray, Offset, Buff, 0, Buff.Length); + return Buff; + } + + /// + /// Compare N-dimensional Arrays. + /// + /// Arrays Type. + /// First Array. + /// Second Array. + /// True, if Arrays are equal. + public static bool IsArraysEqual(T[] item1, T[] item2) + { + if (ReferenceEquals(item1, item2)) + return true; + + if (item1 == null || item2 == null) + return false; + + if (item1.Length != item2.Length) + return false; + + EqualityComparer comparer = EqualityComparer.Default; + for (int i = 0; i < item1.Length; i++) + if (!comparer.Equals(item1[i], item2[i])) + return false; + return true; + } + + /// + /// Compare Lists. + /// + /// List Type. + /// First List. + /// Second List. + /// True, if Lists are equal. + public static bool IsListsEqual(List item1, List item2) + { + if (ReferenceEquals(item1, item2)) + return true; + + if (item1 == null || item2 == null) + return false; + + if (item1.Count != item2.Count) + return false; + + for (int i = 0; i < item1.Count; i++) + if (!item1[i].Equals(item2[i])) + return false; + return true; + } + + /// + /// Compare elements in one Array with different offsets. + /// + /// Array type. + /// Some Array. + /// First offset. + /// Second offset. + /// Elements count which must be compared. + /// + public static bool IsElementsEqual(T[] Arr, int Offset1, int Offset2, int Count) + { + if (Arr == null) + throw new ArgumentNullException(nameof(Arr) + " is null!"); + + if (Offset1 >= Arr.Length || Offset1 < 0) + throw new ArgumentOutOfRangeException(nameof(Offset1) + " has wrong value!"); + + if (Offset2 >= Arr.Length || Offset2 < 0) + throw new ArgumentOutOfRangeException(nameof(Offset2) + " has wrong value!"); + + if (Count <= 0 || Offset1 + Count > Arr.Length || Offset2 + Count > Arr.Length) + throw new ArgumentOutOfRangeException(nameof(Count) + " has wrong value!"); + + if (Offset1 == Offset2) + return true; + + for (int i = 0; i < Count; i++) + if (!Arr[Offset1 + i].Equals(Arr[Offset2 + i])) + return false; + + return true; + } + } + #endregion + + public class TGA : ICloneable + { + public TgaHeader Header = new TgaHeader(); + public TgaImgOrColMap ImageOrColorMapArea = new TgaImgOrColMap(); + public TgaDevArea DevArea = null; + public TgaExtArea ExtArea = null; + public TgaFooter Footer = null; + + #region TGA Creation, Loading, Saving (all are public, have reference to private metods). + /// + /// Create new empty istance. + /// + public TGA() + { + } + + /// + /// Create instance with some params. If it must have ColorMap, + /// check all ColorMap fields and settings after. + /// + /// Image Width. + /// Image Height. + /// Image Pixel Depth (bits / pixel), set ColorMap bpp after, if needed! + /// Image Type (is RLE compressed, ColorMapped or GrayScaled). + /// Set numder of Attrbute bits (Alpha channel bits), default: 0, 1, 8. + /// Use new 2.0 TGA XFile format? + public TGA(ushort Width, ushort Height, TgaPixelDepth PixDepth = TgaPixelDepth.Bpp24, + TgaImageType ImgType = TgaImageType.Uncompressed_TrueColor, byte AttrBits = 0, bool NewFormat = true) + { + if (Width <= 0 || Height <= 0 || PixDepth == TgaPixelDepth.Other) + { + Width = Height = 0; + PixDepth = TgaPixelDepth.Other; + ImgType = TgaImageType.NoImageData; + AttrBits = 0; + } + else + { + int BytesPerPixel = (int)Math.Ceiling((double)PixDepth / 8.0); + ImageOrColorMapArea.ImageData = new byte[Width * Height * BytesPerPixel]; + + if (ImgType == TgaImageType.Uncompressed_ColorMapped || ImgType == TgaImageType.RLE_ColorMapped) + { + Header.ColorMapType = TgaColorMapType.ColorMap; + Header.ColorMapSpec.FirstEntryIndex = 0; + Header.ColorMapSpec.ColorMapEntrySize = (TgaColorMapEntrySize)Math.Ceiling((double)PixDepth / 8); + } + } + + Header.ImageType = ImgType; + Header.ImageSpec.ImageWidth = Width; + Header.ImageSpec.ImageHeight = Height; + Header.ImageSpec.PixelDepth = PixDepth; + Header.ImageSpec.ImageDescriptor.AlphaChannelBits = AttrBits; + + if (NewFormat) + { + Footer = new TgaFooter(); + ExtArea = new TgaExtArea(); + ExtArea.DateTimeStamp = new TgaDateTime(DateTime.UtcNow); + ExtArea.AttributesType = (AttrBits > 0 ? TgaAttrType.UsefulAlpha : TgaAttrType.NoAlpha); + } + } + + /// + /// Make from some instance. + /// Equal to function. + /// + /// Original instance. + public TGA(TGA tga) + { + Header = tga.Header.Clone(); + ImageOrColorMapArea = tga.ImageOrColorMapArea.Clone(); + DevArea = tga.DevArea.Clone(); + ExtArea = tga.ExtArea.Clone(); + Footer = tga.Footer.Clone(); + } + + /// + /// Load from file. + /// + /// Full path to TGA file. + /// Loaded file. + public TGA(string filename) + { + LoadFunc(filename); + } + + /// + /// Make from bytes array. + /// + /// Bytes array (same like TGA File). + public TGA(byte[] bytes) + { + LoadFunc(bytes); + } + + /// + /// Make from . + /// For file opening better use . + /// + /// Some stream. You can use a lot of Stream types, but Stream must support: + /// and . + public TGA(Stream stream) + { + LoadFunc(stream); + } + + /// + /// Make from . + /// + /// Input Bitmap, supported a lot of bitmaps types: 8/15/16/24/32 Bpp's. + /// Use RLE Compression? + /// Use new 2.0 TGA XFile format? + /// Is Color Map Entry size equal 15 or 16 Bpp, else - 24 or 32. + public TGA(Bitmap bmp, bool UseRLE = false, bool NewFormat = true, bool ColorMap2BytesEntry = false) + { + LoadFunc(bmp, UseRLE, NewFormat, ColorMap2BytesEntry); + } + + /// + /// Load from file. + /// + /// Full path to TGA file. + /// Loaded file. + public static TGA FromFile(string filename) + { + return new TGA(filename); + } + + /// + /// Make from bytes array. + /// + /// Bytes array (same like TGA File). + public static TGA FromBytes(byte[] bytes) + { + return new TGA(bytes); + } + + /// + /// Make from . + /// For file opening better use . + /// + /// Some stream. You can use a lot of Stream types, but Stream must support: + /// and . + public static TGA FromStream(Stream stream) + { + return new TGA(stream); + } + + /// + /// Make from . + /// + /// Input Bitmap, supported a lot of bitmaps types: 8/15/16/24/32 Bpp's. + /// Use RLE Compression? + /// Use new 2.0 TGA XFile format? + /// Is Color Map Entry size equal 15 or 16 Bpp, else - 24 or 32. + public static TGA FromBitmap(Bitmap bmp, bool UseRLE = false, + bool NewFormat = true, bool ColorMap2BytesEntry = false) + { + return new TGA(bmp, UseRLE, NewFormat, ColorMap2BytesEntry); + } + + /// + /// Save to file. + /// + /// Full path to file. + /// Return "true", if all done or "false", if failed. + public bool Save(string filename) + { + try + { + bool Result = false; + using (FileStream Fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None)) + { + using (MemoryStream Ms = new MemoryStream()) + { + Result = SaveFunc(Ms); + Ms.WriteTo(Fs); + Fs.Flush(); + } + } + return Result; + } + catch + { + return false; + } + } + + /// + /// Save to . + /// + /// Some stream, it must support: . + /// Return "true", if all done or "false", if failed. + public bool Save(Stream stream) + { + return SaveFunc(stream); + } + #endregion + + /// + /// Gets or Sets Image Width (see ). + /// + public ushort Width + { + get { return Header.ImageSpec.ImageWidth; } + set { Header.ImageSpec.ImageWidth = value; } + } + + /// + /// Gets or Sets Image Height (see ). + /// + public ushort Height + { + get { return Header.ImageSpec.ImageHeight; } + set { Header.ImageSpec.ImageHeight = value; } + } + + /// + /// Gets or Sets image Size. + /// + public Size Size + { + get { return new Size(Header.ImageSpec.ImageWidth, Header.ImageSpec.ImageHeight); } + set + { + Header.ImageSpec.ImageWidth = (ushort)value.Width; + Header.ImageSpec.ImageHeight = (ushort)value.Height; + } + } + + /// + /// Make full independed copy of . + /// + /// Full independed copy of . + public TGA Clone() + { + return new TGA(this); + } + + object ICloneable.Clone() + { + return Clone(); + } + + /// + /// Flip directions, for more info see . + /// + /// Flip horizontal. + /// Flip vertical. + public void Flip(bool Horizontal = false, bool Vertical = false) + { + int NewOrigin = (int)Header.ImageSpec.ImageDescriptor.ImageOrigin; + NewOrigin = NewOrigin ^ ((Vertical ? 0x20 : 0) | (Horizontal ? 0x10 : 0)); + Header.ImageSpec.ImageDescriptor.ImageOrigin = (TgaImgOrigin)NewOrigin; + } + + /// + /// Get information from TGA image. + /// + /// MultiLine string with info fields (one per line). + public string GetInfo() + { + StringBuilder SB = new StringBuilder(); + + SB.AppendLine("Header:"); + SB.AppendLine("\tID Length = " + Header.IDLength); + SB.AppendLine("\tImage Type = " + Header.ImageType); + SB.AppendLine("\tHeader -> ImageSpec:"); + SB.AppendLine("\t\tImage Width = " + Header.ImageSpec.ImageWidth); + SB.AppendLine("\t\tImage Height = " + Header.ImageSpec.ImageHeight); + SB.AppendLine("\t\tPixel Depth = " + Header.ImageSpec.PixelDepth); + SB.AppendLine("\t\tImage Descriptor (AsByte) = " + Header.ImageSpec.ImageDescriptor.ToByte()); + SB.AppendLine("\t\tImage Descriptor -> AttributeBits = " + Header.ImageSpec.ImageDescriptor.AlphaChannelBits); + SB.AppendLine("\t\tImage Descriptor -> ImageOrigin = " + Header.ImageSpec.ImageDescriptor.ImageOrigin); + SB.AppendLine("\t\tX_Origin = " + Header.ImageSpec.X_Origin); + SB.AppendLine("\t\tY_Origin = " + Header.ImageSpec.Y_Origin); + SB.AppendLine("\tColorMap Type = " + Header.ColorMapType); + SB.AppendLine("\tHeader -> ColorMapSpec:"); + SB.AppendLine("\t\tColorMap Entry Size = " + Header.ColorMapSpec.ColorMapEntrySize); + SB.AppendLine("\t\tColorMap Length = " + Header.ColorMapSpec.ColorMapLength); + SB.AppendLine("\t\tFirstEntry Index = " + Header.ColorMapSpec.FirstEntryIndex); + + SB.AppendLine("\nImage / Color Map Area:"); + if (Header.IDLength > 0 && ImageOrColorMapArea.ImageID != null) + SB.AppendLine("\tImage ID = \"" + ImageOrColorMapArea.ImageID.GetString() + "\""); + else + SB.AppendLine("\tImage ID = null"); + + if (ImageOrColorMapArea.ImageData != null) + SB.AppendLine("\tImage Data Length = " + ImageOrColorMapArea.ImageData.Length); + else + SB.AppendLine("\tImage Data = null"); + + if (ImageOrColorMapArea.ColorMapData != null) + SB.AppendLine("\tColorMap Data Length = " + ImageOrColorMapArea.ColorMapData.Length); + else + SB.AppendLine("\tColorMap Data = null"); + + SB.AppendLine("\nDevelopers Area:"); + if (DevArea != null) + SB.AppendLine("\tCount = " + DevArea.Count); + else + SB.AppendLine("\tDevArea = null"); + + SB.AppendLine("\nExtension Area:"); + if (ExtArea != null) + { + SB.AppendLine("\tExtension Size = " + ExtArea.ExtensionSize); + SB.AppendLine("\tAuthor Name = \"" + ExtArea.AuthorName.GetString() + "\""); + SB.AppendLine("\tAuthor Comments = \"" + ExtArea.AuthorComments.GetString() + "\""); + SB.AppendLine("\tDate / Time Stamp = " + ExtArea.DateTimeStamp); + SB.AppendLine("\tJob Name / ID = \"" + ExtArea.JobNameOrID.GetString() + "\""); + SB.AppendLine("\tJob Time = " + ExtArea.JobTime); + SB.AppendLine("\tSoftware ID = \"" + ExtArea.SoftwareID.GetString() + "\""); + SB.AppendLine("\tSoftware Version = \"" + ExtArea.SoftVersion + "\""); + SB.AppendLine("\tKey Color = " + ExtArea.KeyColor); + SB.AppendLine("\tPixel Aspect Ratio = " + ExtArea.PixelAspectRatio); + SB.AppendLine("\tGamma Value = " + ExtArea.GammaValue); + SB.AppendLine("\tColor Correction Table Offset = " + ExtArea.ColorCorrectionTableOffset); + SB.AppendLine("\tPostage Stamp Offset = " + ExtArea.PostageStampOffset); + SB.AppendLine("\tScan Line Offset = " + ExtArea.ScanLineOffset); + SB.AppendLine("\tAttributes Type = " + ExtArea.AttributesType); + + if (ExtArea.ScanLineTable != null) + SB.AppendLine("\tScan Line Table = " + ExtArea.ScanLineTable.Length); + else + SB.AppendLine("\tScan Line Table = null"); + + if (ExtArea.PostageStampImage != null) + SB.AppendLine("\tPostage Stamp Image: " + ExtArea.PostageStampImage.ToString()); + else + SB.AppendLine("\tPostage Stamp Image = null"); + + SB.AppendLine("\tColor Correction Table = " + (ExtArea.ColorCorrectionTable != null)); + } + else + SB.AppendLine("\tExtArea = null"); + + SB.AppendLine("\nFooter:"); + if (Footer != null) + { + SB.AppendLine("\tExtension Area Offset = " + Footer.ExtensionAreaOffset); + SB.AppendLine("\tDeveloper Directory Offset = " + Footer.DeveloperDirectoryOffset); + SB.AppendLine("\tSignature (Full) = \"" + Footer.Signature.ToString() + + Footer.ReservedCharacter.ToString() + Footer.BinaryZeroStringTerminator.ToString() + "\""); + } + else + SB.AppendLine("\tFooter = null"); + + return SB.ToString(); + } + + /// + /// Check and update all fields with data length and offsets. + /// + /// Return "true", if all OK or "false", if checking failed. + public bool CheckAndUpdateOffsets(out string ErrorStr) + { + ErrorStr = String.Empty; + + if (Header == null) + { + ErrorStr = "Header = null"; + return false; + } + + if (ImageOrColorMapArea == null) + { + ErrorStr = "ImageOrColorMapArea = null"; + return false; + } + + uint Offset = TgaHeader.Size; // Virtual Offset + + #region Header + if (ImageOrColorMapArea.ImageID != null) + { + int StrMaxLen = 255; + if (ImageOrColorMapArea.ImageID.UseEndingChar) + StrMaxLen--; + + Header.IDLength = (byte)Math.Min(ImageOrColorMapArea.ImageID.OriginalString.Length, StrMaxLen); + ImageOrColorMapArea.ImageID.Length = Header.IDLength; + Offset += Header.IDLength; + } + else + Header.IDLength = 0; + #endregion + + #region ColorMap + if (Header.ColorMapType != TgaColorMapType.NoColorMap) + { + if (Header.ColorMapSpec == null) + { + ErrorStr = "Header.ColorMapSpec = null"; + return false; + } + + if (Header.ColorMapSpec.ColorMapLength == 0) + { + ErrorStr = "Header.ColorMapSpec.ColorMapLength = 0"; + return false; + } + + if (ImageOrColorMapArea.ColorMapData == null) + { + ErrorStr = "ImageOrColorMapArea.ColorMapData = null"; + return false; + } + + int CmBytesPerPixel = (int)Math.Ceiling((double)Header.ColorMapSpec.ColorMapEntrySize / 8.0); + int LenBytes = Header.ColorMapSpec.ColorMapLength * CmBytesPerPixel; + + if (LenBytes != ImageOrColorMapArea.ColorMapData.Length) + { + ErrorStr = "ImageOrColorMapArea.ColorMapData.Length has wrong size!"; + return false; + } + + Offset += (uint)ImageOrColorMapArea.ColorMapData.Length; + } + #endregion + + #region Image Data + int BytesPerPixel = 0; + if (Header.ImageType != TgaImageType.NoImageData) + { + if (Header.ImageSpec == null) + { + ErrorStr = "Header.ImageSpec = null"; + return false; + } + + if (Header.ImageSpec.ImageWidth == 0 || Header.ImageSpec.ImageHeight == 0) + { + ErrorStr = "Header.ImageSpec.ImageWidth = 0 or Header.ImageSpec.ImageHeight = 0"; + return false; + } + + if (ImageOrColorMapArea.ImageData == null) + { + ErrorStr = "ImageOrColorMapArea.ImageData = null"; + return false; + } + + BytesPerPixel = (int)Math.Ceiling((double)Header.ImageSpec.PixelDepth / 8.0); + if (Width * Height * BytesPerPixel != ImageOrColorMapArea.ImageData.Length) + { + ErrorStr = "ImageOrColorMapArea.ImageData.Length has wrong size!"; + return false; + } + + if (Header.ImageType >= TgaImageType.RLE_ColorMapped && + Header.ImageType <= TgaImageType.RLE_BlackWhite) + { + byte[] RLE = RLE_Encode(ImageOrColorMapArea.ImageData, Width, Height); + if (RLE == null) + { + ErrorStr = "RLE Compressing error! Check Image Data size."; + return false; + } + + Offset += (uint)RLE.Length; + RLE = null; + } + else + Offset += (uint)ImageOrColorMapArea.ImageData.Length; + } + #endregion + + #region Footer, DevArea, ExtArea + if (Footer != null) + { + #region DevArea + if (DevArea != null) + { + int DevAreaCount = DevArea.Count; + for (int i = 0; i < DevAreaCount; i++) + if (DevArea[i] == null || DevArea[i].FieldSize <= 0) //Del Empty Entries + { + DevArea.Entries.RemoveAt(i); + DevAreaCount--; + i--; + } + + if (DevArea.Count <= 0) + Footer.DeveloperDirectoryOffset = 0; + + if (DevArea.Count > 2) + { + DevArea.Entries.Sort((a, b) => { return a.Tag.CompareTo(b.Tag); }); + for (int i = 0; i < DevArea.Count - 1; i++) + if (DevArea[i].Tag == DevArea[i + 1].Tag) + { + ErrorStr = "DevArea Enties has same Tags!"; + return false; + } + } + + for (int i = 0; i < DevArea.Count; i++) + { + DevArea[i].Offset = Offset; + Offset += (uint)DevArea[i].FieldSize; + } + + Footer.DeveloperDirectoryOffset = Offset; + Offset += (uint)(DevArea.Count * 10 + 2); + } + else + Footer.DeveloperDirectoryOffset = 0; + #endregion + + #region ExtArea + if (ExtArea != null) + { + ExtArea.ExtensionSize = TgaExtArea.MinSize; + if (ExtArea.OtherDataInExtensionArea != null) + ExtArea.ExtensionSize += (ushort)ExtArea.OtherDataInExtensionArea.Length; + + ExtArea.DateTimeStamp = new TgaDateTime(DateTime.UtcNow); + + Footer.ExtensionAreaOffset = Offset; + Offset += ExtArea.ExtensionSize; + + #region ScanLineTable + if (ExtArea.ScanLineTable == null) + ExtArea.ScanLineOffset = 0; + else + { + if (ExtArea.ScanLineTable.Length != Height) + { + ErrorStr = "ExtArea.ScanLineTable.Length != Height"; + return false; + } + + ExtArea.ScanLineOffset = Offset; + Offset += (uint)(ExtArea.ScanLineTable.Length * 4); + } + #endregion + + #region PostageStampImage + if (ExtArea.PostageStampImage == null) + ExtArea.PostageStampOffset = 0; + else + { + if (ExtArea.PostageStampImage.Width == 0 || ExtArea.PostageStampImage.Height == 0) + { + ErrorStr = "ExtArea.PostageStampImage Width or Height is equal 0!"; + return false; + } + + if (ExtArea.PostageStampImage.Data == null) + { + ErrorStr = "ExtArea.PostageStampImage.Data == null"; + return false; + } + + int PImgSB = ExtArea.PostageStampImage.Width * ExtArea.PostageStampImage.Height * BytesPerPixel; + if (Header.ImageType != TgaImageType.NoImageData && + ExtArea.PostageStampImage.Data.Length != PImgSB) + { + ErrorStr = "ExtArea.PostageStampImage.Data.Length is wrong!"; + return false; + } + + + ExtArea.PostageStampOffset = Offset; + Offset += (uint)(ExtArea.PostageStampImage.Data.Length); + } + #endregion + + #region ColorCorrectionTable + if (ExtArea.ColorCorrectionTable == null) + ExtArea.ColorCorrectionTableOffset = 0; + else + { + if (ExtArea.ColorCorrectionTable.Length != 1024) + { + ErrorStr = "ExtArea.ColorCorrectionTable.Length != 256 * 4"; + return false; + } + + ExtArea.ColorCorrectionTableOffset = Offset; + Offset += (uint)(ExtArea.ColorCorrectionTable.Length * 2); + } + #endregion + } + else + Footer.ExtensionAreaOffset = 0; + #endregion + + #region Footer + if (Footer.ToBytes().Length != TgaFooter.Size) + { + ErrorStr = "Footer.Length is wrong!"; + return false; + } + + Offset += TgaFooter.Size; + #endregion + } + #endregion + + return true; + } + + #region Convert + /// + /// Convert to . + /// + /// Force use alpha channel. + /// Bitmap or null, on error. + public Bitmap ToBitmap(bool ForceUseAlpha = false) + { + return ToBitmapFunc(ForceUseAlpha, false); + } + + /// + /// Convert to bytes array. + /// + /// Bytes array, (equal to saved file, but in memory) or null (on error). + public byte[] ToBytes() + { + try + { + byte[] Bytes; + using (MemoryStream ms = new MemoryStream()) + { + Save(ms); + Bytes = ms.ToArray(); + ms.Flush(); + } + return Bytes; + } + catch + { + return null; + } + } + + /// + /// Convert TGA Image to new XFile format (v2.0). + /// + public void ToNewFormat() + { + if (Footer == null) + Footer = new TgaFooter(); + + if (ExtArea == null) + { + ExtArea = new TgaExtArea(); + + ExtArea.DateTimeStamp = new TgaDateTime(DateTime.UtcNow); + + if (Header.ImageSpec.ImageDescriptor.AlphaChannelBits > 0) + ExtArea.AttributesType = TgaAttrType.UsefulAlpha; + else + ExtArea.AttributesType = TgaAttrType.NoAlpha; + } + } + #endregion + + #region Private functions + bool LoadFunc(string filename) + { + if (!File.Exists(filename)) + throw new FileNotFoundException("File: \"" + filename + "\" not found!"); + + try + { + using (FileStream FS = new FileStream(filename, FileMode.Open)) + return LoadFunc(FS); + } + catch + { + return false; + } + } + + bool LoadFunc(byte[] bytes) + { + if (bytes == null) + throw new ArgumentNullException(); + + try + { + using (MemoryStream FS = new MemoryStream(bytes, false)) + return LoadFunc(FS); + } + catch + { + return false; + } + } + + bool LoadFunc(Stream stream) + { + if (stream == null) + throw new ArgumentNullException(); + if (!(stream.CanRead && stream.CanSeek)) + throw new FileLoadException("Stream reading or seeking is not avaiable!"); + + try + { + stream.Seek(0, SeekOrigin.Begin); + BinaryReader Br = new BinaryReader(stream); + + Header = new TgaHeader(Br.ReadBytes(TgaHeader.Size)); + + if (Header.IDLength > 0) + ImageOrColorMapArea.ImageID = new TgaString(Br.ReadBytes(Header.IDLength)); + + if (Header.ColorMapSpec.ColorMapLength > 0) + { + int CmBytesPerPixel = (int)Math.Ceiling((double)Header.ColorMapSpec.ColorMapEntrySize / 8.0); + int LenBytes = Header.ColorMapSpec.ColorMapLength * CmBytesPerPixel; + ImageOrColorMapArea.ColorMapData = Br.ReadBytes(LenBytes); + } + + #region Read Image Data + int BytesPerPixel = (int)Math.Ceiling((double)Header.ImageSpec.PixelDepth / 8.0); + if (Header.ImageType != TgaImageType.NoImageData) + { + int ImageDataSize = Width * Height * BytesPerPixel; + switch (Header.ImageType) + { + case TgaImageType.RLE_ColorMapped: + case TgaImageType.RLE_TrueColor: + case TgaImageType.RLE_BlackWhite: + + int DataOffset = 0; + byte PacketInfo; + int PacketCount; + byte[] RLE_Bytes, RLE_Part; + ImageOrColorMapArea.ImageData = new byte[ImageDataSize]; + + do + { + PacketInfo = Br.ReadByte(); //1 type bit and 7 count bits. Len = Count + 1. + PacketCount = (PacketInfo & 127) + 1; + + if (PacketInfo >= 128) // bit7 = 1, RLE + { + RLE_Bytes = new byte[PacketCount * BytesPerPixel]; + RLE_Part = Br.ReadBytes(BytesPerPixel); + for (int i = 0; i < RLE_Bytes.Length; i++) + RLE_Bytes[i] = RLE_Part[i % BytesPerPixel]; + } + else // RAW format + RLE_Bytes = Br.ReadBytes(PacketCount * BytesPerPixel); + + Buffer.BlockCopy(RLE_Bytes, 0, ImageOrColorMapArea.ImageData, DataOffset, RLE_Bytes.Length); + DataOffset += RLE_Bytes.Length; + } + while (DataOffset < ImageDataSize); + RLE_Bytes = null; + break; + + case TgaImageType.Uncompressed_ColorMapped: + case TgaImageType.Uncompressed_TrueColor: + case TgaImageType.Uncompressed_BlackWhite: + ImageOrColorMapArea.ImageData = Br.ReadBytes(ImageDataSize); + break; + } + } + #endregion + + #region Try parse Footer + stream.Seek(-TgaFooter.Size, SeekOrigin.End); + uint FooterOffset = (uint)stream.Position; + TgaFooter MbFooter = new TgaFooter(Br.ReadBytes(TgaFooter.Size)); + if (MbFooter.IsFooterCorrect) + { + Footer = MbFooter; + uint DevDirOffset = Footer.DeveloperDirectoryOffset; + uint ExtAreaOffset = Footer.ExtensionAreaOffset; + + #region If Dev Area exist, read it. + if (DevDirOffset != 0) + { + stream.Seek(DevDirOffset, SeekOrigin.Begin); + DevArea = new TgaDevArea(); + uint NumberOfTags = Br.ReadUInt16(); + + ushort[] Tags = new ushort[NumberOfTags]; + uint[] TagOffsets = new uint[NumberOfTags]; + uint[] TagSizes = new uint[NumberOfTags]; + + for (int i = 0; i < NumberOfTags; i++) + { + Tags[i] = Br.ReadUInt16(); + TagOffsets[i] = Br.ReadUInt32(); + TagSizes[i] = Br.ReadUInt32(); + } + + for (int i = 0; i < NumberOfTags; i++) + { + stream.Seek(TagOffsets[i], SeekOrigin.Begin); + var Ent = new TgaDevEntry(Tags[i], TagOffsets[i], Br.ReadBytes((int)TagSizes[i])); + DevArea.Entries.Add(Ent); + } + + Tags = null; + TagOffsets = null; + TagSizes = null; + } + #endregion + + #region If Ext Area exist, read it. + if (ExtAreaOffset != 0) + { + stream.Seek(ExtAreaOffset, SeekOrigin.Begin); + ushort ExtAreaSize = Math.Max((ushort)TgaExtArea.MinSize, Br.ReadUInt16()); + stream.Seek(ExtAreaOffset, SeekOrigin.Begin); + ExtArea = new TgaExtArea(Br.ReadBytes(ExtAreaSize)); + + if (ExtArea.ScanLineOffset > 0) + { + stream.Seek(ExtArea.ScanLineOffset, SeekOrigin.Begin); + ExtArea.ScanLineTable = new uint[Height]; + for (int i = 0; i < ExtArea.ScanLineTable.Length; i++) + ExtArea.ScanLineTable[i] = Br.ReadUInt32(); + } + + if (ExtArea.PostageStampOffset > 0) + { + stream.Seek(ExtArea.PostageStampOffset, SeekOrigin.Begin); + byte W = Br.ReadByte(); + byte H = Br.ReadByte(); + int ImgDataSize = W * H * BytesPerPixel; + if (ImgDataSize > 0) + ExtArea.PostageStampImage = new TgaPostageStampImage(W, H, Br.ReadBytes(ImgDataSize)); + } + + if (ExtArea.ColorCorrectionTableOffset > 0) + { + stream.Seek(ExtArea.ColorCorrectionTableOffset, SeekOrigin.Begin); + ExtArea.ColorCorrectionTable = new ushort[256 * 4]; + for (int i = 0; i < ExtArea.ColorCorrectionTable.Length; i++) + ExtArea.ColorCorrectionTable[i] = Br.ReadUInt16(); + } + } + #endregion + } + #endregion + + Br.Close(); + return true; + } + catch + { + return false; + } + } + + bool LoadFunc(Bitmap bmp, bool UseRLE = false, bool NewFormat = true, bool ColorMap2BytesEntry = false) + { + if (bmp == null) + throw new ArgumentNullException(); + + try + { + Header.ImageSpec.ImageWidth = (ushort)bmp.Width; + Header.ImageSpec.ImageHeight = (ushort)bmp.Height; + Header.ImageSpec.ImageDescriptor.ImageOrigin = TgaImgOrigin.TopLeft; + + switch (bmp.PixelFormat) + { + case PixelFormat.Indexed: + case PixelFormat.Gdi: + case PixelFormat.Alpha: + case PixelFormat.Undefined: + case PixelFormat.PAlpha: + case PixelFormat.Extended: + case PixelFormat.Max: + case PixelFormat.Canonical: + case PixelFormat.Format16bppRgb565: + default: + throw new FormatException(nameof(PixelFormat) + " is not supported!"); + + case PixelFormat.Format1bppIndexed: + case PixelFormat.Format4bppIndexed: + case PixelFormat.Format8bppIndexed: + case PixelFormat.Format16bppGrayScale: + case PixelFormat.Format16bppRgb555: + case PixelFormat.Format16bppArgb1555: + case PixelFormat.Format24bppRgb: + case PixelFormat.Format32bppRgb: + case PixelFormat.Format32bppArgb: + case PixelFormat.Format32bppPArgb: + case PixelFormat.Format48bppRgb: + case PixelFormat.Format64bppArgb: + case PixelFormat.Format64bppPArgb: + + int bpp = Math.Max(8, Image.GetPixelFormatSize(bmp.PixelFormat)); + int BytesPP = bpp / 8; + + if (bmp.PixelFormat == PixelFormat.Format16bppRgb555) + bpp = 15; + + bool IsAlpha = Image.IsAlphaPixelFormat(bmp.PixelFormat); + bool IsPreAlpha = IsAlpha && bmp.PixelFormat.ToString().EndsWith("PArgb"); + bool IsColorMapped = bmp.PixelFormat.ToString().EndsWith("Indexed"); + + Header.ImageSpec.PixelDepth = (TgaPixelDepth)(BytesPP * 8); + + if (IsAlpha) + { + Header.ImageSpec.ImageDescriptor.AlphaChannelBits = (byte)(BytesPP * 2); + + if (bmp.PixelFormat == PixelFormat.Format16bppArgb1555) + Header.ImageSpec.ImageDescriptor.AlphaChannelBits = 1; + } + + #region ColorMap + bool IsGrayImage = (bmp.PixelFormat == PixelFormat.Format16bppGrayScale | IsColorMapped); + + if (IsColorMapped && bmp.Palette != null) + { + Color[] Colors = bmp.Palette.Entries; + + #region Analyze ColorMapType + int AlphaSum = 0; + bool ColorMapUseAlpha = false; + + for (int i = 0; i < Colors.Length; i++) + { + IsGrayImage &= (Colors[i].R == Colors[i].G && Colors[i].G == Colors[i].B); + ColorMapUseAlpha |= (Colors[i].A < 248); + AlphaSum |= Colors[i].A; + } + ColorMapUseAlpha &= (AlphaSum > 0); + + int CMapBpp = (ColorMap2BytesEntry ? 15 : 24) + (ColorMapUseAlpha ? (ColorMap2BytesEntry ? 1 : 8) : 0); + int CMBytesPP = (int)Math.Ceiling(CMapBpp / 8.0); + #endregion + + Header.ColorMapSpec.ColorMapLength = Math.Min((ushort)Colors.Length, ushort.MaxValue); + Header.ColorMapSpec.ColorMapEntrySize = (TgaColorMapEntrySize)CMapBpp; + ImageOrColorMapArea.ColorMapData = new byte[Header.ColorMapSpec.ColorMapLength * CMBytesPP]; + + byte[] CMapEntry = new byte[CMBytesPP]; + + const float To5Bit = 32f / 256f; // Scale value from 8 to 5 bits. + for (int i = 0; i < Colors.Length; i++) + { + switch (Header.ColorMapSpec.ColorMapEntrySize) + { + case TgaColorMapEntrySize.A1R5G5B5: + case TgaColorMapEntrySize.X1R5G5B5: + int R = (int)(Colors[i].R * To5Bit); + int G = (int)(Colors[i].G * To5Bit) << 5; + int B = (int)(Colors[i].B * To5Bit) << 10; + int A = 0; + + if (Header.ColorMapSpec.ColorMapEntrySize == TgaColorMapEntrySize.A1R5G5B5) + A = ((Colors[i].A & 0x80) << 15); + + CMapEntry = BitConverter.GetBytes(A | R | G | B); + break; + + case TgaColorMapEntrySize.R8G8B8: + CMapEntry[0] = Colors[i].B; + CMapEntry[1] = Colors[i].G; + CMapEntry[2] = Colors[i].R; + break; + + case TgaColorMapEntrySize.A8R8G8B8: + CMapEntry[0] = Colors[i].B; + CMapEntry[1] = Colors[i].G; + CMapEntry[2] = Colors[i].R; + CMapEntry[3] = Colors[i].A; + break; + + case TgaColorMapEntrySize.Other: + default: + break; + } + + Buffer.BlockCopy(CMapEntry, 0, ImageOrColorMapArea.ColorMapData, i * CMBytesPP, CMBytesPP); + } + } + #endregion + + #region ImageType + if (UseRLE) + { + if (IsGrayImage) + Header.ImageType = TgaImageType.RLE_BlackWhite; + else if (IsColorMapped) + Header.ImageType = TgaImageType.RLE_ColorMapped; + else + Header.ImageType = TgaImageType.RLE_TrueColor; + } + else + { + if (IsGrayImage) + Header.ImageType = TgaImageType.Uncompressed_BlackWhite; + else if (IsColorMapped) + Header.ImageType = TgaImageType.Uncompressed_ColorMapped; + else + Header.ImageType = TgaImageType.Uncompressed_TrueColor; + } + + Header.ColorMapType = (IsColorMapped ? TgaColorMapType.ColorMap : TgaColorMapType.NoColorMap); + #endregion + + #region NewFormat + if (NewFormat) + { + Footer = new TgaFooter(); + ExtArea = new TgaExtArea(); + ExtArea.DateTimeStamp = new TgaDateTime(DateTime.UtcNow); + + if (IsAlpha) + { + ExtArea.AttributesType = TgaAttrType.UsefulAlpha; + + if (IsPreAlpha) + ExtArea.AttributesType = TgaAttrType.PreMultipliedAlpha; + } + else + { + ExtArea.AttributesType = TgaAttrType.NoAlpha; + + if (Header.ImageSpec.ImageDescriptor.AlphaChannelBits > 0) + ExtArea.AttributesType = TgaAttrType.UndefinedAlphaButShouldBeRetained; + } + } + #endregion + + #region Bitmap width is aligned by 32 bits = 4 bytes! Delete it. + int StrideBytes = bmp.Width * BytesPP; + int PaddingBytes = (int)Math.Ceiling(StrideBytes / 4.0) * 4 - StrideBytes; + + byte[] ImageData = new byte[(StrideBytes + PaddingBytes) * bmp.Height]; + + Rectangle Re = new Rectangle(0, 0, bmp.Width, bmp.Height); + BitmapData BmpData = bmp.LockBits(Re, ImageLockMode.ReadOnly, bmp.PixelFormat); + Marshal.Copy(BmpData.Scan0, ImageData, 0, ImageData.Length); + bmp.UnlockBits(BmpData); + BmpData = null; + + if (PaddingBytes > 0) //Need delete bytes align + { + ImageOrColorMapArea.ImageData = new byte[StrideBytes * bmp.Height]; + for (int i = 0; i < bmp.Height; i++) + Buffer.BlockCopy(ImageData, i * (StrideBytes + PaddingBytes), + ImageOrColorMapArea.ImageData, i * StrideBytes, StrideBytes); + } + else + ImageOrColorMapArea.ImageData = ImageData; + + ImageData = null; + + // Not official supported, but works (tested on 16bpp GrayScale test images)! + if (bmp.PixelFormat == PixelFormat.Format16bppGrayScale) + { + for (long i = 0; i < ImageOrColorMapArea.ImageData.Length; i++) + ImageOrColorMapArea.ImageData[i] ^= byte.MaxValue; + } + #endregion + + break; + } + + return true; + } + catch + { + return false; + } + } + + bool SaveFunc(Stream stream) + { + try + { + if (stream == null) + throw new ArgumentNullException(); + if (!(stream.CanWrite && stream.CanSeek)) + throw new FileLoadException("Stream writing or seeking is not avaiable!"); + + string CheckResult; + if (!CheckAndUpdateOffsets(out CheckResult)) + return false; + + BinaryWriter Bw = new BinaryWriter(stream); + Bw.Write(Header.ToBytes()); + + if (ImageOrColorMapArea.ImageID != null) + Bw.Write(ImageOrColorMapArea.ImageID.ToBytes()); + + if (Header.ColorMapType != TgaColorMapType.NoColorMap) + Bw.Write(ImageOrColorMapArea.ColorMapData); + + #region ImageData + if (Header.ImageType != TgaImageType.NoImageData) + { + if (Header.ImageType >= TgaImageType.RLE_ColorMapped && + Header.ImageType <= TgaImageType.RLE_BlackWhite) + Bw.Write(RLE_Encode(ImageOrColorMapArea.ImageData, Width, Height)); + else + Bw.Write(ImageOrColorMapArea.ImageData); + } + #endregion + + #region Footer + if (Footer != null) + { + #region DevArea + if (DevArea != null) + { + for (int i = 0; i < DevArea.Count; i++) + Bw.Write(DevArea[i].Data); + + Bw.Write((ushort)DevArea.Count); + + for (int i = 0; i < DevArea.Count; i++) + { + Bw.Write(DevArea[i].Tag); + Bw.Write(DevArea[i].Offset); + Bw.Write(DevArea[i].FieldSize); + } + } + #endregion + + #region ExtArea + if (ExtArea != null) + { + Bw.Write(ExtArea.ToBytes()); + + if (ExtArea.ScanLineTable != null) + for (int i = 0; i < ExtArea.ScanLineTable.Length; i++) + Bw.Write(ExtArea.ScanLineTable[i]); + + if (ExtArea.PostageStampImage != null) + Bw.Write(ExtArea.PostageStampImage.ToBytes()); + + if (ExtArea.ColorCorrectionTable != null) + for (int i = 0; i < ExtArea.ColorCorrectionTable.Length; i++) + Bw.Write(ExtArea.ColorCorrectionTable[i]); + } + #endregion + + Bw.Write(Footer.ToBytes()); + } + #endregion + + Bw.Flush(); + stream.Flush(); + return true; + } + catch + { + return false; + } + } + + /// + /// Encode image with RLE compression (used RLE per line)! + /// + /// Image data, bytes array with size = Width * Height * BytesPerPixel. + /// Image Width, must be > 0. + /// Image Height, must be > 0. + /// Bytes array with RLE compressed image data. + byte[] RLE_Encode(byte[] ImageData, int Width, int Height) + { + if (ImageData == null) + throw new ArgumentNullException(nameof(ImageData) + "in null!"); + + if (Width <= 0 || Height <= 0) + throw new ArgumentOutOfRangeException(nameof(Width) + " and " + nameof(Height) + " must be > 0!"); + + int Bpp = ImageData.Length / Width / Height; // Bytes per pixel + int ScanLineSize = Width * Bpp; + + if (ScanLineSize * Height != ImageData.Length) + throw new ArgumentOutOfRangeException("ImageData has wrong Length!"); + + try + { + int Count = 0; + int Pos = 0; + bool IsRLE = false; + List Encoded = new List(); + byte[] RowData = new byte[ScanLineSize]; + + for (int y = 0; y < Height; y++) + { + Pos = 0; + Buffer.BlockCopy(ImageData, y * ScanLineSize, RowData, 0, ScanLineSize); + + while (Pos < ScanLineSize) + { + if (Pos >= ScanLineSize - Bpp) + { + Encoded.Add(0); + Encoded.AddRange(BitConverterExt.GetElements(RowData, Pos, Bpp)); + Pos += Bpp; + break; + } + + Count = 0; //1 + IsRLE = BitConverterExt.IsElementsEqual(RowData, Pos, Pos + Bpp, Bpp); + + for (int i = Pos + Bpp; i < Math.Min(Pos + 128 * Bpp, ScanLineSize) - Bpp; i += Bpp) + { + if (IsRLE ^ BitConverterExt.IsElementsEqual(RowData, (IsRLE ? Pos : i), i + Bpp, Bpp)) + { + //Count--; + break; + } + else + Count++; + } + + int CountBpp = (Count + 1) * Bpp; + Encoded.Add((byte)(IsRLE ? Count | 128 : Count)); + Encoded.AddRange(BitConverterExt.GetElements(RowData, Pos, (IsRLE ? Bpp : CountBpp))); + Pos += CountBpp; + } + } + + return Encoded.ToArray(); + } + catch + { + return null; + } + } + + /// + /// Convert to . + /// + /// Force use alpha channel. + /// Get Postage Stamp Image (Thumb) or get main image? + /// Bitmap or null, on error. + Bitmap ToBitmapFunc(bool ForceUseAlpha = false, bool PostageStampImage = false) + { + try + { + #region UseAlpha? + bool UseAlpha = true; + if (ExtArea != null) + { + switch (ExtArea.AttributesType) + { + case TgaAttrType.NoAlpha: + case TgaAttrType.UndefinedAlphaCanBeIgnored: + case TgaAttrType.UndefinedAlphaButShouldBeRetained: + UseAlpha = false; + break; + case TgaAttrType.UsefulAlpha: + case TgaAttrType.PreMultipliedAlpha: + default: + break; + } + } + UseAlpha = (Header.ImageSpec.ImageDescriptor.AlphaChannelBits > 0 && UseAlpha) | ForceUseAlpha; + #endregion + + #region IsGrayImage + bool IsGrayImage = Header.ImageType == TgaImageType.RLE_BlackWhite || + Header.ImageType == TgaImageType.Uncompressed_BlackWhite; + #endregion + + #region Get PixelFormat + PixelFormat PixFormat = PixelFormat.Format24bppRgb; + + switch (Header.ImageSpec.PixelDepth) + { + case TgaPixelDepth.Bpp8: + PixFormat = PixelFormat.Format8bppIndexed; + break; + + case TgaPixelDepth.Bpp16: + if (IsGrayImage) + PixFormat = PixelFormat.Format16bppGrayScale; + else + PixFormat = (UseAlpha ? PixelFormat.Format16bppArgb1555 : PixelFormat.Format16bppRgb555); + break; + + case TgaPixelDepth.Bpp24: + PixFormat = PixelFormat.Format24bppRgb; + break; + + case TgaPixelDepth.Bpp32: + if (UseAlpha) + { + var f = Footer; + if (ExtArea?.AttributesType == TgaAttrType.PreMultipliedAlpha) + PixFormat = PixelFormat.Format32bppPArgb; + else + PixFormat = PixelFormat.Format32bppArgb; + } + else + PixFormat = PixelFormat.Format32bppRgb; + break; + + default: + PixFormat = PixelFormat.Undefined; + break; + } + #endregion + + ushort BMP_Width = (PostageStampImage ? ExtArea.PostageStampImage.Width : Width); + ushort BMP_Height = (PostageStampImage ? ExtArea.PostageStampImage.Height : Height); + Bitmap BMP = new Bitmap(BMP_Width, BMP_Height, PixFormat); + + #region ColorMap and GrayPalette + if (Header.ColorMapType == TgaColorMapType.ColorMap && + (Header.ImageType == TgaImageType.RLE_ColorMapped || + Header.ImageType == TgaImageType.Uncompressed_ColorMapped)) + { + + ColorPalette ColorMap = BMP.Palette; + Color[] CMapColors = ColorMap.Entries; + + switch (Header.ColorMapSpec.ColorMapEntrySize) + { + case TgaColorMapEntrySize.X1R5G5B5: + case TgaColorMapEntrySize.A1R5G5B5: + const float To8Bit = 255f / 31f; // Scale value from 5 to 8 bits. + for (int i = 0; i < Math.Min(CMapColors.Length, Header.ColorMapSpec.ColorMapLength); i++) + { + ushort A1R5G5B5 = BitConverter.ToUInt16(ImageOrColorMapArea.ColorMapData, i * 2); + int A = (UseAlpha ? (A1R5G5B5 & 0x8000) >> 15 : 1) * 255; // (0 or 1) * 255 + int R = (int)(((A1R5G5B5 & 0x7C00) >> 10) * To8Bit); + int G = (int)(((A1R5G5B5 & 0x3E0) >> 5) * To8Bit); + int B = (int)((A1R5G5B5 & 0x1F) * To8Bit); + CMapColors[i] = Color.FromArgb(A, R, G, B); + } + break; + + case TgaColorMapEntrySize.R8G8B8: + for (int i = 0; i < Math.Min(CMapColors.Length, Header.ColorMapSpec.ColorMapLength); i++) + { + int Index = i * 3; //RGB = 3 bytes + int R = ImageOrColorMapArea.ColorMapData[Index + 2]; + int G = ImageOrColorMapArea.ColorMapData[Index + 1]; + int B = ImageOrColorMapArea.ColorMapData[Index]; + CMapColors[i] = Color.FromArgb(R, G, B); + } + break; + + case TgaColorMapEntrySize.A8R8G8B8: + for (int i = 0; i < Math.Min(CMapColors.Length, Header.ColorMapSpec.ColorMapLength); i++) + { + int ARGB = BitConverter.ToInt32(ImageOrColorMapArea.ColorMapData, i * 4); + CMapColors[i] = Color.FromArgb(UseAlpha ? ARGB | (0xFF << 24) : ARGB); + } + break; + + default: + ColorMap = null; + break; + } + + if (ColorMap != null) + BMP.Palette = ColorMap; + } + + if (PixFormat == PixelFormat.Format8bppIndexed && IsGrayImage) + { + ColorPalette GrayPalette = BMP.Palette; + Color[] GrayColors = GrayPalette.Entries; + for (int i = 0; i < GrayColors.Length; i++) + GrayColors[i] = Color.FromArgb(i, i, i); + BMP.Palette = GrayPalette; + } + #endregion + + #region Bitmap width must by aligned (align value = 32 bits = 4 bytes)! + byte[] ImageData; + int BytesPerPixel = (int)Math.Ceiling((double)Header.ImageSpec.PixelDepth / 8.0); + int StrideBytes = BMP.Width * BytesPerPixel; + int PaddingBytes = (int)Math.Ceiling(StrideBytes / 4.0) * 4 - StrideBytes; + + if (PaddingBytes > 0) //Need bytes align + { + ImageData = new byte[(StrideBytes + PaddingBytes) * BMP.Height]; + for (int i = 0; i < BMP.Height; i++) + Buffer.BlockCopy(PostageStampImage ? ExtArea.PostageStampImage.Data : + ImageOrColorMapArea.ImageData, i * StrideBytes, ImageData, + i * (StrideBytes + PaddingBytes), StrideBytes); + } + else + ImageData = BitConverterExt.ToBytes(PostageStampImage ? ExtArea.PostageStampImage.Data : + ImageOrColorMapArea.ImageData); + + // Not official supported, but works (tested on 2 test images)! + if (PixFormat == PixelFormat.Format16bppGrayScale) + { + for (long i = 0; i < ImageData.Length; i++) + ImageData[i] ^= byte.MaxValue; + } + #endregion + + Rectangle Re = new Rectangle(0, 0, BMP.Width, BMP.Height); + BitmapData BmpData = BMP.LockBits(Re, ImageLockMode.WriteOnly, BMP.PixelFormat); + Marshal.Copy(ImageData, 0, BmpData.Scan0, ImageData.Length); + BMP.UnlockBits(BmpData); + ImageData = null; + BmpData = null; + + if (ExtArea != null && ExtArea.KeyColor.ToInt() != 0) + BMP.MakeTransparent(ExtArea.KeyColor.ToColor()); + + #region Flip Image + switch (Header.ImageSpec.ImageDescriptor.ImageOrigin) + { + case TgaImgOrigin.BottomLeft: + BMP.RotateFlip(RotateFlipType.RotateNoneFlipY); + break; + case TgaImgOrigin.BottomRight: + BMP.RotateFlip(RotateFlipType.RotateNoneFlipXY); + break; + case TgaImgOrigin.TopLeft: + default: + break; + case TgaImgOrigin.TopRight: + BMP.RotateFlip(RotateFlipType.RotateNoneFlipX); + break; + } + #endregion + + return BMP; + } + catch + { + return null; + } + } + #endregion + + #region Explicit + public static explicit operator Bitmap(TGA tga) + { + return tga.ToBitmap(); + } + + public static explicit operator TGA(Bitmap bmp) + { + return FromBitmap(bmp); + } + #endregion + + #region PostageStamp Image + /// + /// Convert to . + /// + /// Force use alpha channel. + /// Bitmap or null. + public Bitmap GetPostageStampImage(bool ForceUseAlpha = false) + { + if (ExtArea == null || ExtArea.PostageStampImage == null || ExtArea.PostageStampImage.Data == null || + ExtArea.PostageStampImage.Width <= 0 || ExtArea.PostageStampImage.Height <= 0) + return null; + + return ToBitmapFunc(ForceUseAlpha, true); + } + + /// + /// Update Postage Stamp Image or set it. + /// + public void UpdatePostageStampImage() + { + if (Header.ImageType == TgaImageType.NoImageData) + { + if (ExtArea != null) + ExtArea.PostageStampImage = null; + return; + } + + ToNewFormat(); + if (ExtArea.PostageStampImage == null) + ExtArea.PostageStampImage = new TgaPostageStampImage(); + + int PS_Width = Header.ImageSpec.ImageWidth; + int PS_Height = Header.ImageSpec.ImageHeight; + + if (Width > 64 || Height > 64) + { + float AspectRatio = Width / (float)Height; + PS_Width = (byte)(64f * (AspectRatio < 1f ? AspectRatio : 1f)); + PS_Height = (byte)(64f / (AspectRatio > 1f ? AspectRatio : 1f)); + } + PS_Width = Math.Max(PS_Width, 4); + PS_Height = Math.Max(PS_Height, 4); + + ExtArea.PostageStampImage.Width = (byte)PS_Width; + ExtArea.PostageStampImage.Height = (byte)PS_Height; + + int BytesPerPixel = (int)Math.Ceiling((double)Header.ImageSpec.PixelDepth / 8.0); + ExtArea.PostageStampImage.Data = new byte[PS_Width * PS_Height * BytesPerPixel]; + + float WidthCoef = Width / (float)PS_Width; + float HeightCoef = Height / (float)PS_Height; + + for (int y = 0; y < PS_Height; y++) + { + int Y_Offset = (int)(y * HeightCoef) * Width * BytesPerPixel; + int y_Offset = y * PS_Width * BytesPerPixel; + + for (int x = 0; x < PS_Width; x++) + { + Buffer.BlockCopy(ImageOrColorMapArea.ImageData, Y_Offset + (int)(x * WidthCoef) * BytesPerPixel, + ExtArea.PostageStampImage.Data, y_Offset + x * BytesPerPixel, BytesPerPixel); + } + } + } + + public void DeletePostageStampImage() + { + if (ExtArea != null) + ExtArea.PostageStampImage = null; + } + #endregion + } +}