From f645914ee6c29dc74f38b1a0548439363eb609f7 Mon Sep 17 00:00:00 2001
From: Miku-666 <74728189+NessieHax@users.noreply.github.com>
Date: Sun, 24 Mar 2024 14:48:49 +0100
Subject: [PATCH] Add Tga file support (#25)
* Add minimal tga image loader
* Update TGA class ,add support for importing tga texture files and generate mipmap from tga images
* Add copyright and resource links
* Partial Add SaveImage
* Move TGAReader/Writer outside of TGA.cs
* Add check to return early when `DataTypeCode` is set to NO_DATA
* Add support for loading ExtensionData
* Change PNG signature check more clear
* Remove unnecessary using statements
* Move Debug stuff into Debug methods and call LoadImage before LoadFooter
* Update TGAReader
* Update TGA Reader/Writer
* Remove TGATimeSpan and use TimeSpan and DateTime instead
* Update TGAHeader member varible name and type
* Update TGA- Reader/Writer Constructor to not accept boolean flag 'useLittleEndian'
* TGAReader - Optimized 'TGA_HandleRGB'
* TGAReader - Update and rename 'TGA_HandleRLE_RGB'
* TGAWriter - Remove RLE RGB from switch statement
* TGA - Add TGA.FromFile, Move TGADataTypeCode to its own file
* Move Header, Footer and ExtentionData to there own cs file
* Move Tga files into IO folder
* Changed visibility of tga related classes to internal
* TGA - Only support reading/writting of raw RGB 32 bit images
* Update IO/TGA classes
---
PCK-Studio/Classes/IO/TGA/TGADataTypeCode.cs | 60 +++++
PCK-Studio/Classes/IO/TGA/TGADeserializer.cs | 33 +++
PCK-Studio/Classes/IO/TGA/TGAException.cs | 25 ++
PCK-Studio/Classes/IO/TGA/TGAExtentionData.cs | 63 +++++
PCK-Studio/Classes/IO/TGA/TGAFileData.cs | 41 +++
PCK-Studio/Classes/IO/TGA/TGAFooter.cs | 27 ++
PCK-Studio/Classes/IO/TGA/TGAHeader.cs | 37 +++
PCK-Studio/Classes/IO/TGA/TGAReader.cs | 241 ++++++++++++++++++
PCK-Studio/Classes/IO/TGA/TGASerializer.cs | 32 +++
PCK-Studio/Classes/IO/TGA/TGAWriter.cs | 143 +++++++++++
.../Extensions/PckFileDataExtensions.cs | 6 +-
PCK-Studio/MainForm.cs | 14 +-
PCK-Studio/PckStudio.csproj | 12 +-
13 files changed, 721 insertions(+), 13 deletions(-)
create mode 100644 PCK-Studio/Classes/IO/TGA/TGADataTypeCode.cs
create mode 100644 PCK-Studio/Classes/IO/TGA/TGADeserializer.cs
create mode 100644 PCK-Studio/Classes/IO/TGA/TGAException.cs
create mode 100644 PCK-Studio/Classes/IO/TGA/TGAExtentionData.cs
create mode 100644 PCK-Studio/Classes/IO/TGA/TGAFileData.cs
create mode 100644 PCK-Studio/Classes/IO/TGA/TGAFooter.cs
create mode 100644 PCK-Studio/Classes/IO/TGA/TGAHeader.cs
create mode 100644 PCK-Studio/Classes/IO/TGA/TGAReader.cs
create mode 100644 PCK-Studio/Classes/IO/TGA/TGASerializer.cs
create mode 100644 PCK-Studio/Classes/IO/TGA/TGAWriter.cs
diff --git a/PCK-Studio/Classes/IO/TGA/TGADataTypeCode.cs b/PCK-Studio/Classes/IO/TGA/TGADataTypeCode.cs
new file mode 100644
index 00000000..60456312
--- /dev/null
+++ b/PCK-Studio/Classes/IO/TGA/TGADataTypeCode.cs
@@ -0,0 +1,60 @@
+/* Copyright (c) 2022-present miku-666
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1.The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+**/
+
+namespace PckStudio.IO.TGA
+{
+ internal enum TGADataTypeCode : byte
+ {
+ ///
+ /// No image data included.
+ ///
+ NO_DATA = 0,
+ ///
+ /// Uncompressed, color-mapped images.
+ ///
+ COLORMAPPED = 1,
+ ///
+ /// Uncompressed, RGB images.
+ ///
+ RGB = 2,
+ ///
+ /// Uncompressed, black and white images.
+ ///
+ BLACK_WHITE = 3,
+ ///
+ /// Runlength encoded color-mapped images.
+ ///
+ RLE_COLORMAPPED = 9,
+ ///
+ /// Runlength encoded RGB images.
+ ///
+ RLE_RGB = 10,
+ ///
+ /// Compressed, black and white images.
+ ///
+ COMPRESSED_BLACK_WHITE = 11,
+ ///
+ /// Compressed color-mapped data, using Huffman, Delta, and runlength encoding.
+ ///
+ COMPRESSED_RLE_COLORMAPPED = 32,
+ ///
+ /// Compressed color-mapped data, using Huffman, Delta, and runlength encoding. 4-pass quadtree-type process.
+ ///
+ COMPRESSED_RLE_COLORMAPPED_4 = 33,
+ }
+}
\ No newline at end of file
diff --git a/PCK-Studio/Classes/IO/TGA/TGADeserializer.cs b/PCK-Studio/Classes/IO/TGA/TGADeserializer.cs
new file mode 100644
index 00000000..0382e16e
--- /dev/null
+++ b/PCK-Studio/Classes/IO/TGA/TGADeserializer.cs
@@ -0,0 +1,33 @@
+/* Copyright (c) 2022-present miku-666
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1.The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+**/
+using System.IO;
+using System.Drawing;
+
+namespace PckStudio.IO.TGA
+{
+ internal static class TGADeserializer
+ {
+ private static TGAReader reader = new TGAReader();
+
+ public static Image DeserializeFromStream(Stream stream)
+ {
+ TGAFileData tgaImg = reader.FromStream(stream);
+ return tgaImg.Bitmap;
+ }
+ }
+}
\ No newline at end of file
diff --git a/PCK-Studio/Classes/IO/TGA/TGAException.cs b/PCK-Studio/Classes/IO/TGA/TGAException.cs
new file mode 100644
index 00000000..9f961579
--- /dev/null
+++ b/PCK-Studio/Classes/IO/TGA/TGAException.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace PckStudio.IO.TGA
+{
+ [Serializable]
+ internal class TGAException : Exception
+ {
+ public TGAException()
+ {
+ }
+
+ public TGAException(string message) : base(message)
+ {
+ }
+
+ public TGAException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected TGAException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/PCK-Studio/Classes/IO/TGA/TGAExtentionData.cs b/PCK-Studio/Classes/IO/TGA/TGAExtentionData.cs
new file mode 100644
index 00000000..55485144
--- /dev/null
+++ b/PCK-Studio/Classes/IO/TGA/TGAExtentionData.cs
@@ -0,0 +1,63 @@
+/* Copyright (c) 2022-present miku-666
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1.The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+**/
+using System;
+using System.Windows.Forms;
+
+namespace PckStudio.IO.TGA
+{
+ internal struct TGAExtentionData
+ {
+ public const short ExtensionSize = 0x1EF;
+ public string AuthorName;
+ public string AuthorComment;
+ public DateTime TimeStamp;
+ public string JobID;
+ public TimeSpan JobTime;
+ public string SoftwareID;
+ public byte[] SoftwareVersion;
+ public int KeyColor;
+ public int PixelAspectRatio;
+ public int GammaValue;
+ public int ColorCorrectionOffset;
+ public int PostageStampOffset;
+ public int ScanLineOffset;
+ public byte AttributesType;
+
+ public static TGAExtentionData Create()
+ {
+ var extensionData = new TGAExtentionData();
+ extensionData.AuthorName = "";
+ extensionData.AuthorComment = "";
+ extensionData.AuthorComment = "";
+ extensionData.TimeStamp = DateTime.Now;
+ extensionData.JobID = "";
+ extensionData.JobTime = new TimeSpan(extensionData.TimeStamp.Hour, extensionData.TimeStamp.Minute, extensionData.TimeStamp.Second);
+ extensionData.SoftwareID = Application.ProductName;
+ Version.TryParse(Application.ProductVersion, out Version currentVersion);
+ extensionData.SoftwareVersion = [(byte)currentVersion.Major, (byte)currentVersion.Minor, (byte)currentVersion.Build];
+ extensionData.KeyColor = 0;
+ extensionData.PixelAspectRatio = 0;
+ extensionData.GammaValue = 0;
+ extensionData.ColorCorrectionOffset = 0;
+ extensionData.PostageStampOffset = 0;
+ extensionData.ScanLineOffset = 0;
+ extensionData.AttributesType = 3;
+ return extensionData;
+ }
+ }
+}
\ No newline at end of file
diff --git a/PCK-Studio/Classes/IO/TGA/TGAFileData.cs b/PCK-Studio/Classes/IO/TGA/TGAFileData.cs
new file mode 100644
index 00000000..f4a12859
--- /dev/null
+++ b/PCK-Studio/Classes/IO/TGA/TGAFileData.cs
@@ -0,0 +1,41 @@
+/* Copyright (c) 2022-present miku-666
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1.The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+**/
+using System.IO;
+using System.Drawing;
+using System;
+
+namespace PckStudio.IO.TGA
+{
+ internal class TGAFileData
+ {
+ public TGAFileData(TGAHeader header, Image bitmap, TGAFooter footer, TGAExtentionData extentionData)
+ {
+ if (bitmap.Width != header.Width || bitmap.Height != header.Height)
+ throw new InvalidDataException("Header resolution doesn't match Image resolution");
+ Header = header;
+ Bitmap = bitmap;
+ Footer = footer;
+ ExtentionData = extentionData;
+ }
+
+ public readonly TGAHeader Header;
+ public readonly Image Bitmap;
+ public readonly TGAFooter Footer;
+ public readonly TGAExtentionData ExtentionData;
+ }
+}
\ No newline at end of file
diff --git a/PCK-Studio/Classes/IO/TGA/TGAFooter.cs b/PCK-Studio/Classes/IO/TGA/TGAFooter.cs
new file mode 100644
index 00000000..cfae98c8
--- /dev/null
+++ b/PCK-Studio/Classes/IO/TGA/TGAFooter.cs
@@ -0,0 +1,27 @@
+/* Copyright (c) 2022-present miku-666
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1.The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+**/
+
+namespace PckStudio.IO.TGA
+{
+ internal struct TGAFooter
+ {
+ internal const string Signature = "TRUEVISION-XFILE";
+ public int ExtensionDataOffset;
+ public int DeveloperAreaDataOffset;
+ }
+}
\ No newline at end of file
diff --git a/PCK-Studio/Classes/IO/TGA/TGAHeader.cs b/PCK-Studio/Classes/IO/TGA/TGAHeader.cs
new file mode 100644
index 00000000..ce967b22
--- /dev/null
+++ b/PCK-Studio/Classes/IO/TGA/TGAHeader.cs
@@ -0,0 +1,37 @@
+/* Copyright (c) 2022-present miku-666
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1.The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+**/
+
+namespace PckStudio.IO.TGA
+{
+ ///
+ /// Resources:
+ ///
+ ///
+ ///
+ internal struct TGAHeader
+ {
+ public byte[] Id;
+ public TGADataTypeCode DataTypeCode;
+ public (byte Type, short Origin/*Offset*/, short Length, byte Depth) Colormap;
+ public (short X, short Y) Origin;
+ public short Width;
+ public short Height;
+ public byte BitsPerPixel;
+ public byte ImageDescriptor;
+ }
+}
\ No newline at end of file
diff --git a/PCK-Studio/Classes/IO/TGA/TGAReader.cs b/PCK-Studio/Classes/IO/TGA/TGAReader.cs
new file mode 100644
index 00000000..112d2fc7
--- /dev/null
+++ b/PCK-Studio/Classes/IO/TGA/TGAReader.cs
@@ -0,0 +1,241 @@
+/* Copyright (c) 2022-present miku-666
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1.The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+**/
+using System;
+using System.IO;
+using System.Text;
+using System.Drawing;
+using System.Diagnostics;
+using System.Drawing.Imaging;
+using System.Runtime.InteropServices;
+using System.Collections.Generic;
+using OMI.Workers;
+using OMI;
+
+namespace PckStudio.IO.TGA
+{
+ internal class TGAReader : IDataFormatReader, IDataFormatReader
+ {
+ object IDataFormatReader.FromStream(Stream stream) => FromStream(stream);
+
+ object IDataFormatReader.FromFile(string filename) => FromFile(filename);
+
+ public TGAFileData FromFile(string filename)
+ {
+ if (File.Exists(filename))
+ {
+ using( var fs = File.OpenRead(filename) )
+ {
+ return FromStream(fs);
+ }
+ }
+ throw new FileNotFoundException(filename);
+ }
+
+ public TGAFileData FromStream(Stream stream)
+ {
+ using var reader = new EndiannessAwareBinaryReader(stream, Encoding.ASCII, leaveOpen: true, Endianness.LittleEndian);
+ TGAHeader header = LoadHeader(reader);
+ Image image = LoadImage(reader, header);
+ TGAFooter footer = LoadFooter(reader);
+ TGAExtentionData extentionData = LoadExtentionData(reader, footer);
+ return new TGAFileData(header, image, footer, extentionData);
+ }
+
+ private static void TGA_HandleRGB(EndiannessAwareBinaryReader reader, TGAHeader header, BitmapData bitmapData)
+ {
+ int bytesPerPixel = header.BitsPerPixel / 8;
+
+ byte[] data = reader.ReadBytes(header.Height * header.Width * bytesPerPixel);
+ Marshal.Copy(data, 0, bitmapData.Scan0, data.Length);
+ }
+
+ private static void TGA_HandleNoData(EndiannessAwareBinaryReader _, TGAHeader header, BitmapData bitmapData)
+ {
+ Random r = new Random();
+ byte[] bytes = new byte[bitmapData.Width * bitmapData.Height * 4];
+ r.NextBytes(bytes);
+ Marshal.Copy(bytes, 0, bitmapData.Scan0, bytes.Length);
+ }
+
+ private static TGAHeader LoadHeader(EndiannessAwareBinaryReader reader)
+ {
+ var header = new TGAHeader();
+ byte[] bytes = reader.ReadBytes(3);
+ (var headerIdLength, header.Colormap.Type, header.DataTypeCode) = (bytes[0], bytes[1], (TGADataTypeCode)bytes[2]);
+ header.Colormap.Origin = reader.ReadInt16();
+ header.Colormap.Length = reader.ReadInt16();
+ header.Colormap.Depth = reader.ReadByte();
+ header.Origin.X = reader.ReadInt16();
+ header.Origin.Y = reader.ReadInt16();
+ header.Width = reader.ReadInt16();
+ header.Height = reader.ReadInt16();
+ header.BitsPerPixel = reader.ReadByte();
+ header.ImageDescriptor = reader.ReadByte();
+ header.Id = reader.ReadBytes(headerIdLength);
+ DebugLogHeader(header);
+ return header;
+ }
+
+ private static PixelFormat GetPixelFormat(int bytesPerPixel)
+ {
+ return bytesPerPixel switch
+ {
+ 2 => PixelFormat.Format16bppArgb1555,
+ 3 => PixelFormat.Format24bppRgb,
+ 4 => PixelFormat.Format32bppArgb,
+ _ => throw new NotSupportedException(nameof(bytesPerPixel))
+ };
+ }
+
+ private static Image LoadImage(EndiannessAwareBinaryReader reader, TGAHeader header)
+ {
+ if (header.DataTypeCode != TGADataTypeCode.RGB)
+ throw new NotSupportedException(nameof(header.DataTypeCode));
+
+ Bitmap bitmap = new Bitmap(header.Width, header.Height);
+ BitmapData bitmapData = bitmap.LockBits(
+ new Rectangle(0, 0, header.Width, header.Height),
+ ImageLockMode.WriteOnly,
+ GetPixelFormat(header.BitsPerPixel >> 3));
+
+ if (header.DataTypeCode == TGADataTypeCode.NO_DATA)
+ {
+ TGA_HandleNoData(reader, header, bitmapData);
+ bitmap.UnlockBits(bitmapData);
+ return bitmap;
+ }
+
+ TGA_HandleRGB(reader, header, bitmapData);
+ bitmap.UnlockBits(bitmapData);
+ bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
+ return bitmap;
+ }
+
+ private static TGAFooter LoadFooter(EndiannessAwareBinaryReader reader)
+ {
+ long origin = reader.BaseStream.Position;
+ reader.BaseStream.Seek(-26, SeekOrigin.End);
+
+ TGAFooter footer = new TGAFooter();
+
+ footer.ExtensionDataOffset = reader.ReadInt32(); // optional
+ footer.DeveloperAreaDataOffset = reader.ReadInt32(); // optional
+ string signature = reader.ReadString(16);
+ Debug.Assert(signature.Equals(TGAFooter.Signature) || reader.ReadInt16() != 0x002E,
+ "Footer signature invalid");
+ reader.BaseStream.Seek(origin, SeekOrigin.Begin);
+ DebugLogFooter(footer);
+ return footer;
+ }
+
+ private static TGAExtentionData LoadExtentionData(EndiannessAwareBinaryReader reader, TGAFooter footer)
+ {
+ if (footer.ExtensionDataOffset > 0)
+ {
+ reader.BaseStream.Seek(footer.ExtensionDataOffset, SeekOrigin.Begin);
+ if (reader.ReadInt16() == TGAExtentionData.ExtensionSize)
+ {
+ TGAExtentionData extentionData = new TGAExtentionData();
+ extentionData.AuthorName = reader.ReadString(41);
+ extentionData.AuthorComment = reader.ReadString(324);
+ short month = reader.ReadInt16();
+ short day = reader.ReadInt16();
+ short year = reader.ReadInt16();
+ short hour = reader.ReadInt16();
+ short minute = reader.ReadInt16();
+ short second = reader.ReadInt16();
+ extentionData.TimeStamp = new DateTime(year, month, day, hour, minute, second);
+ extentionData.JobID = reader.ReadString(41);
+ extentionData.JobTime = new TimeSpan(
+ hours: reader.ReadInt16(),
+ minutes: reader.ReadInt16(),
+ seconds: reader.ReadInt16()
+ );
+ extentionData.SoftwareID = reader.ReadString(41);
+ extentionData.SoftwareVersion = reader.ReadBytes(3);
+ extentionData.KeyColor = reader.ReadInt32();
+ extentionData.PixelAspectRatio = reader.ReadInt32();
+ extentionData.GammaValue = reader.ReadInt32();
+ extentionData.ColorCorrectionOffset = reader.ReadInt32();
+ extentionData.PostageStampOffset = reader.ReadInt32();
+ extentionData.ScanLineOffset = reader.ReadInt32();
+ extentionData.AttributesType = reader.ReadByte();
+ DebugLogExtentionData(extentionData);
+ return extentionData;
+ }
+ }
+ return default;
+ }
+
+ [Conditional("DEBUG")]
+ [DebuggerHidden]
+ [DebuggerStepThrough]
+ private static void DebugLogExtentionData(TGAExtentionData extentionData)
+ {
+ Debug.WriteLine("-------Extention Data-------", category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Author Name: {0}", args: extentionData.AuthorName), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Author Comment: {0}", args: extentionData.AuthorComment), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Time Stamp: {0}", args: extentionData.TimeStamp), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Job ID: {0}", args: extentionData.JobID), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Job Time: {0}", args: extentionData.JobTime), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("SoftwareID: {0}", args: extentionData.SoftwareID), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Software Version: {0}.{1}.{2}", extentionData.SoftwareVersion[0], extentionData.SoftwareVersion[1], extentionData.SoftwareVersion[2]), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Key Color: {0}", args: extentionData.KeyColor), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Pixel Aspect Ratio: {0}", args: extentionData.PixelAspectRatio), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Gamma Value: {0}", args: extentionData.GammaValue), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Color Correction Offset: {0}", args: extentionData.ColorCorrectionOffset), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Postage Stamp Offset: {0}", args: extentionData.PostageStampOffset), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Scan Line Offset: {0}", args: extentionData.ScanLineOffset), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Attributes Type: {0}", args: extentionData.AttributesType), category: nameof(TGAReader));
+ Debug.WriteLine("----------------------------", category: nameof(TGAReader));
+ }
+
+ [Conditional("DEBUG")]
+ [DebuggerHidden]
+ [DebuggerStepThrough]
+ private static void DebugLogHeader(TGAHeader header)
+ {
+ Debug.WriteLine("------Header Data------", category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("ID length: {0}", args: header.Id.Length), category: nameof(TGAReader));
+ Debug.WriteLineIf(header.Id.Length > 0, $"ID: {header.Id}", category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Colourmap type: {0}", args: header.Colormap.Type), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Image type: {0}", args: header.DataTypeCode), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Colour map offset: {0}", args: header.Colormap.Origin), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Colour map length: {0}", args: header.Colormap.Length), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Colour map depth: {0}", args: header.Colormap.Depth), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("X origin: {0}", args: header.Origin.X), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Y origin: {0}", args: header.Origin.Y), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Width: {0}", args: header.Width), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Height: {0}", args: header.Height), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Bits per pixel: {0}", args: header.BitsPerPixel), category: nameof(TGAReader));
+ Debug.WriteLine(string.Format("Descriptor: {0}", args: header.ImageDescriptor), category: nameof(TGAReader));
+ Debug.WriteLine("-----------------------", category: nameof(TGAReader));
+ }
+
+ [Conditional("DEBUG")]
+ [DebuggerHidden]
+ [DebuggerStepThrough]
+ private static void DebugLogFooter(TGAFooter footer)
+ {
+ Debug.WriteLine("-------Footer Data-------", category: nameof(TGAReader));
+ Debug.WriteLine($"Extension Data Offset: {footer.ExtensionDataOffset:x}", category: nameof(TGAReader));
+ Debug.WriteLine($"Developer Area Data Offset: {footer.DeveloperAreaDataOffset:x}", category: nameof(TGAReader));
+ Debug.WriteLine("-----------------------", category: nameof(TGAReader));
+ }
+ }
+}
\ No newline at end of file
diff --git a/PCK-Studio/Classes/IO/TGA/TGASerializer.cs b/PCK-Studio/Classes/IO/TGA/TGASerializer.cs
new file mode 100644
index 00000000..ce018242
--- /dev/null
+++ b/PCK-Studio/Classes/IO/TGA/TGASerializer.cs
@@ -0,0 +1,32 @@
+/* Copyright (c) 2022-present miku-666
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1.The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+**/
+using System.IO;
+using System.Drawing;
+
+namespace PckStudio.IO.TGA
+{
+ internal static class TGASerializer
+ {
+ private static TGAWriter writer = new TGAWriter();
+
+ public static void SerializeToStream(ref Stream stream, Image image)
+ {
+ writer.WriteToStream(stream, image);
+ }
+ }
+}
\ No newline at end of file
diff --git a/PCK-Studio/Classes/IO/TGA/TGAWriter.cs b/PCK-Studio/Classes/IO/TGA/TGAWriter.cs
new file mode 100644
index 00000000..ff0b5475
--- /dev/null
+++ b/PCK-Studio/Classes/IO/TGA/TGAWriter.cs
@@ -0,0 +1,143 @@
+/* Copyright (c) 2022-present miku-666
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1.The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+**/
+using System;
+using System.IO;
+using System.Drawing;
+using System.Diagnostics;
+using System.Drawing.Imaging;
+using System.Runtime.InteropServices;
+using System.Text;
+using OMI;
+using System.Windows.Forms;
+using DiscordRPC;
+
+namespace PckStudio.IO.TGA
+{
+ internal class TGAWriter
+ {
+ private Bitmap _bitmap;
+ private int extensionDataOffset = 0;
+
+ public TGAWriter()
+ {
+ }
+
+ private void WriteHeader(EndiannessAwareBinaryWriter writer)
+ {
+ writer.Write(new byte[]
+ {
+ 0, // header.Id.Length
+ 0, // colormap type
+ (byte)TGADataTypeCode.RGB
+ });
+ writer.Write(0); // Colormap.Origin
+ writer.Write(0); // Colormap.Length
+ writer.Write(0); // Colormap.Depth
+ writer.Write(0); // Origin.X
+ writer.Write(0); // Origin.Y
+ writer.Write(_bitmap.Width);
+ writer.Write(_bitmap.Height);
+ writer.Write(32); // BitsPerPixel
+ writer.Write(8); // ImageDescriptor
+ }
+
+ private void WriteImage(EndiannessAwareBinaryWriter writer)
+ {
+ _bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
+ BitmapData bitmapData = _bitmap.LockBits(
+ new Rectangle(0, 0, _bitmap.Width, _bitmap.Height),
+ ImageLockMode.ReadOnly,
+ PixelFormat.Format32bppArgb);
+
+ byte[] buffer = new byte[_bitmap.Width * _bitmap.Height * 4];
+ Marshal.Copy(bitmapData.Scan0, buffer, 0, _bitmap.Width * _bitmap.Height * 4);
+ writer.Write(buffer);
+ }
+
+ private void WriteFooter(EndiannessAwareBinaryWriter writer)
+ {
+ writer.Write(extensionDataOffset); // extensionDataOffset
+ writer.Write(0); // developerAreaDataOffset
+ writer.WriteString(TGAFooter.Signature);
+ writer.Write((byte)0x2E);
+ writer.Write((byte)0x00);
+ }
+
+ private void WriteExtensionData(EndiannessAwareBinaryWriter writer)
+ {
+ extensionDataOffset = Convert.ToInt32(writer.BaseStream.Position);
+ TGAExtentionData extentionData = TGAExtentionData.Create();
+ writer.Write(TGAExtentionData.ExtensionSize);
+ // Author Name
+ writer.WriteString(extentionData.AuthorName, 41);
+ // Author Comment
+ writer.WriteString(extentionData.AuthorComment, 324);
+ // Timestamp
+ writer.Write((short)extentionData.TimeStamp.Month);
+ writer.Write((short)extentionData.TimeStamp.Day);
+ writer.Write((short)extentionData.TimeStamp.Year);
+ writer.Write((short)extentionData.TimeStamp.Hour);
+ writer.Write((short)extentionData.TimeStamp.Minute);
+ writer.Write((short)extentionData.TimeStamp.Second);
+ // Job id
+ writer.WriteString(extentionData.JobID, 41);
+ // Job time
+ writer.Write((short)extentionData.JobTime.Hours);
+ writer.Write((short)extentionData.JobTime.Minutes);
+ writer.Write((short)extentionData.JobTime.Seconds);
+ // Software Id
+ writer.WriteString(extentionData.SoftwareID, 41);
+ // Software version
+ writer.Write(extentionData.SoftwareVersion, 0, 3);
+ // Key color
+ writer.Write(extentionData.KeyColor);
+ // Pixel aspect ratio
+ writer.Write(extentionData.PixelAspectRatio);
+ // Gamma value
+ writer.Write(extentionData.GammaValue);
+ // Color correction offset
+ writer.Write(extentionData.ColorCorrectionOffset);
+ // Postage stamp offset
+ writer.Write(extentionData.PostageStampOffset);
+ // Scan line offset
+ writer.Write(extentionData.ScanLineOffset);
+ // Attributes type
+ writer.Write(extentionData.AttributesType);
+ }
+
+ public void WriteToStream(Stream stream, Image image)
+ {
+ _bitmap = new Bitmap(image);
+ using (var writer = new EndiannessAwareBinaryWriter(stream, Encoding.ASCII, leaveOpen: true, Endianness.LittleEndian))
+ {
+ WriteHeader(writer);
+ WriteImage(writer);
+ WriteExtensionData(writer);
+ WriteFooter(writer);
+ }
+ }
+
+ public void WriteToFile(string filename, Image image)
+ {
+ using (var fs = File.OpenWrite(filename))
+ {
+ WriteToStream(fs, image);
+ }
+ }
+ }
+}
diff --git a/PCK-Studio/Extensions/PckFileDataExtensions.cs b/PCK-Studio/Extensions/PckFileDataExtensions.cs
index 9488fdcb..7427a0d6 100644
--- a/PCK-Studio/Extensions/PckFileDataExtensions.cs
+++ b/PCK-Studio/Extensions/PckFileDataExtensions.cs
@@ -9,6 +9,7 @@ using System.Text;
using System.Threading.Tasks;
using OMI.Formats.Pck;
using OMI.Workers;
+using PckStudio.IO.TGA;
namespace PckStudio.Extensions
{
@@ -30,7 +31,10 @@ namespace PckStudio.Extensions
{
try
{
- return Image.FromStream(stream);
+ if (Path.GetExtension(file.Filename) == ".tga")
+ return TGADeserializer.DeserializeFromStream(stream);
+ else
+ return Image.FromStream(stream);
}
catch(Exception ex)
{
diff --git a/PCK-Studio/MainForm.cs b/PCK-Studio/MainForm.cs
index d8fb5d75..b8d13042 100644
--- a/PCK-Studio/MainForm.cs
+++ b/PCK-Studio/MainForm.cs
@@ -571,11 +571,7 @@ namespace PckStudio
case PckFileType.CapeFile:
case PckFileType.TextureFile:
{
- // TODO: Add tga support
- if (Path.GetExtension(file.Filename) == ".tga") break;
- using MemoryStream stream = new MemoryStream(file.Data);
-
- var img = Image.FromStream(stream);
+ Image img = file.GetTexture();
if (img.RawFormat != ImageFormat.Jpeg || img.RawFormat != ImageFormat.Png)
{
@@ -595,7 +591,6 @@ namespace PckStudio
Debug.WriteLine(string.Format("An error occured of type: {0} with message: {1}", ex.GetType(), ex.Message), "Exception");
}
-
if ((file.Filename.StartsWith("res/textures/blocks/") || file.Filename.StartsWith("res/textures/items/")) &&
file.Filetype == PckFileType.TextureFile
&& !file.IsMipmappedFile())
@@ -1953,7 +1948,7 @@ namespace PckStudio
private void addTextureToolStripMenuItem_Click(object sender, EventArgs e)
{
using OpenFileDialog fileDialog = new OpenFileDialog();
- fileDialog.Filter = "Texture File(*.png;*.tga)|*.png;*.tga";
+ fileDialog.Filter = "Texture File(*.png,*.tga)|*.png;*.tga";
if (fileDialog.ShowDialog() == DialogResult.OK)
{
using TextPrompt renamePrompt = new TextPrompt(Path.GetFileName(fileDialog.FileName));
@@ -1997,9 +1992,6 @@ namespace PckStudio
string textureExtension = Path.GetExtension(file.Filename);
- // TGA is not yet supported
- if (textureExtension == ".tga") return;
-
using NumericPrompt numericPrompt = new NumericPrompt(0);
numericPrompt.Minimum = 1;
numericPrompt.Maximum = 4; // 5 is the presumed max MipMap level
@@ -2018,7 +2010,7 @@ namespace PckStudio
PckFileData MipMappedFile = new PckFileData(mippedPath, PckFileType.TextureFile);
- Image originalTexture = Image.FromStream(new MemoryStream(file.Data));
+ Image originalTexture = file.GetTexture();
int NewWidth = Math.Max(originalTexture.Width / (int)Math.Pow(2, i - 1), 1);
int NewHeight = Math.Max(originalTexture.Height / (int)Math.Pow(2, i - 1), 1);
diff --git a/PCK-Studio/PckStudio.csproj b/PCK-Studio/PckStudio.csproj
index 112dceb3..47a63e0b 100644
--- a/PCK-Studio/PckStudio.csproj
+++ b/PCK-Studio/PckStudio.csproj
@@ -133,11 +133,14 @@
+
+
+
@@ -245,7 +248,14 @@
WiiUPanel.cs
-
+
+
+
+
+
+
+
+
Form