/* 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 OMI.Workers; using OMI; namespace PckStudio.Core.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(FileStream 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, ByteOrder.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)); } } }