From ae7503761522c7e7110a9d24a9fc762d915112eb Mon Sep 17 00:00:00 2001 From: Scuff Date: Thu, 5 Sep 2019 19:54:58 +0200 Subject: [PATCH] Added support for R8 and RG88 Increased version --- RePKG.Tests/TexDecompilingTestsBase.cs | 2 +- RePKG.Tests/Texb2DecompilingTests.cs | 9 +++ RePKG/Helper.cs | 86 ++++++++++++++++++++++++-- RePKG/Properties/AssemblyInfo.cs | 4 +- RePKG/Texture/Tex.cs | 50 ++++++++++----- RePKG/Texture/TexLoader.cs | 6 ++ 6 files changed, 132 insertions(+), 25 deletions(-) diff --git a/RePKG.Tests/TexDecompilingTestsBase.cs b/RePKG.Tests/TexDecompilingTestsBase.cs index 7121426..1d20252 100644 --- a/RePKG.Tests/TexDecompilingTestsBase.cs +++ b/RePKG.Tests/TexDecompilingTestsBase.cs @@ -24,7 +24,7 @@ protected void SetUp() Directory.CreateDirectory($"{BasePath}\\{ValidatedDirectoryName}\\"); } - protected void Test(string name, bool validateBytes = false, Action validateTex = null) + protected void Test(string name, bool validateBytes = true, Action validateTex = null) { var texture = TexLoader.LoadTex(LoadTestFile(name)); var bytes = texture.Decompile(); diff --git a/RePKG.Tests/Texb2DecompilingTests.cs b/RePKG.Tests/Texb2DecompilingTests.cs index e202bfd..7a90587 100644 --- a/RePKG.Tests/Texb2DecompilingTests.cs +++ b/RePKG.Tests/Texb2DecompilingTests.cs @@ -9,5 +9,14 @@ public class Texb2DecompilingTests : TexDecompilingTestsBase [Test] public void V2_ARGB8888() => Test(nameof(V2_ARGB8888)); + + [Test] + public void V2_R8() => Test(nameof(V2_R8)); + + [Test] + public void V2_RG88() => Test(nameof(V2_RG88)); + + [Test] + public void V2_ARGB8888N() => Test(nameof(V2_ARGB8888N)); } } \ No newline at end of file diff --git a/RePKG/Helper.cs b/RePKG/Helper.cs index 81b8e9f..0500ca3 100644 --- a/RePKG/Helper.cs +++ b/RePKG/Helper.cs @@ -17,20 +17,94 @@ public static IEnumerable GetPropertyKeysForDynamic(dynamic dynamicToGet var toReturn = new List(); foreach (var key in values.Keys) { - toReturn.Add(key); + toReturn.Add(key); } + return toReturn; } + public static unsafe void CopyRawR8PixelsIntoBitmap(byte[] data, int textureWidth, Bitmap processedBitmap) + { + var dataStride = textureWidth; + + var bitmapData = processedBitmap.LockBits( + new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), ImageLockMode.ReadWrite, + processedBitmap.PixelFormat); + + var bytesPerPixel = Image.GetPixelFormatSize(processedBitmap.PixelFormat) / 8; + var heightInPixels = bitmapData.Height; + var widthInBytes = bitmapData.Width * bytesPerPixel; + var ptrFirstPixel = (byte*) bitmapData.Scan0; + + Parallel.For(0, heightInPixels, y => + { + var currentLine = ptrFirstPixel + y * bitmapData.Stride; + var currentLineData = y * dataStride; + + for (var x = 0; x < widthInBytes; x += bytesPerPixel) + { + var dataX = x / bytesPerPixel; + var p = data[currentLineData + dataX]; + + currentLine[x] = p; + currentLine[x + 1] = p; + currentLine[x + 2] = p; + + currentLine[x + 3] = 0xFF; + } + }); + + processedBitmap.UnlockBits(bitmapData); + } + + public static unsafe void CopyRawRG88PixelsIntoBitmap(byte[] data, int textureWidth, Bitmap processedBitmap) + { + var dataStride = textureWidth * 2; + + var bitmapData = processedBitmap.LockBits( + new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), ImageLockMode.ReadWrite, + processedBitmap.PixelFormat); + + var bytesPerPixel = Image.GetPixelFormatSize(processedBitmap.PixelFormat) / 8; + var heightInPixels = bitmapData.Height; + var widthInBytes = bitmapData.Width * bytesPerPixel; + var ptrFirstPixel = (byte*) bitmapData.Scan0; + + Parallel.For(0, heightInPixels, y => + { + var currentLine = ptrFirstPixel + y * bitmapData.Stride; + var currentLineData = y * dataStride; + + for (var x = 0; x < widthInBytes; x += bytesPerPixel) + { + var dataX = (x / bytesPerPixel) * 2; + var p = data[currentLineData + dataX + 1]; + + currentLine[x] = p; + currentLine[x + 1] = p; + currentLine[x + 2] = p; + + currentLine[x + 3] = 0xFF; + } + }); + + processedBitmap.UnlockBits(bitmapData); + } + // source: http://csharpexamples.com/fast-image-processing-c/ - public static unsafe void CopyRawPixelsIntoBitmap(byte[] data, int dataStride, Bitmap processedBitmap, bool invertedColorOrder) + public static unsafe void CopyRawPixelsIntoBitmap(byte[] data, int textureWidth, Bitmap processedBitmap, + bool invertedColorOrder) { - var bitmapData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), ImageLockMode.ReadWrite, processedBitmap.PixelFormat); - + var dataStride = textureWidth * 4; + + var bitmapData = processedBitmap.LockBits( + new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), ImageLockMode.ReadWrite, + processedBitmap.PixelFormat); + var bytesPerPixel = Image.GetPixelFormatSize(processedBitmap.PixelFormat) / 8; var heightInPixels = bitmapData.Height; var widthInBytes = bitmapData.Width * bytesPerPixel; - var ptrFirstPixel = (byte*)bitmapData.Scan0; + var ptrFirstPixel = (byte*) bitmapData.Scan0; if (invertedColorOrder) { @@ -81,4 +155,4 @@ public static string GetExtension(FreeImageFormat format) return $".{str.ToLower()}"; } } -} +} \ No newline at end of file diff --git a/RePKG/Properties/AssemblyInfo.cs b/RePKG/Properties/AssemblyInfo.cs index b88abb2..f58dee8 100644 --- a/RePKG/Properties/AssemblyInfo.cs +++ b/RePKG/Properties/AssemblyInfo.cs @@ -32,7 +32,7 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.1.6.0")] -[assembly: AssemblyFileVersion("0.1.6.0")] +[assembly: AssemblyVersion("0.1.7.0")] +[assembly: AssemblyFileVersion("0.1.7.0")] [assembly: NeutralResourcesLanguage("en")] diff --git a/RePKG/Texture/Tex.cs b/RePKG/Texture/Tex.cs index 1265e8f..bdc191e 100644 --- a/RePKG/Texture/Tex.cs +++ b/RePKG/Texture/Tex.cs @@ -27,7 +27,7 @@ public class Tex public readonly List Mipmaps; public bool IsGif => (Flags & TexFlags.IsGif) == TexFlags.IsGif; - + public Tex() { Format = TexFormat.ARGB8888; @@ -44,31 +44,44 @@ public byte[] Decompile() if (ImageFormat != FreeImageFormat.FIF_UNKNOWN) return bytes; - + + var width = ImageWidth; + var textureWidth = Mipmaps[0].Width; + var height = ImageHeight; + var bitmap = new Bitmap(width, height); + switch (Format) { case TexFormat.DXT5: bytes = DXT.DecompressImage(Mipmaps[0].Width, Mipmaps[0].Height, bytes, DXT.DXTFlags.DXT5); + Helper.CopyRawPixelsIntoBitmap(bytes, textureWidth, bitmap, true); break; + case TexFormat.DXT3: bytes = DXT.DecompressImage(Mipmaps[0].Width, Mipmaps[0].Height, bytes, DXT.DXTFlags.DXT3); + Helper.CopyRawPixelsIntoBitmap(bytes, textureWidth, bitmap, true); break; + case TexFormat.DXT1: bytes = DXT.DecompressImage(Mipmaps[0].Width, Mipmaps[0].Height, bytes, DXT.DXTFlags.DXT1); + Helper.CopyRawPixelsIntoBitmap(bytes, textureWidth, bitmap, true); + break; + + case TexFormat.R8: + Helper.CopyRawR8PixelsIntoBitmap(bytes, textureWidth, bitmap); + break; + + case TexFormat.RG8: + Helper.CopyRawRG88PixelsIntoBitmap(bytes, textureWidth, bitmap); break; + case TexFormat.ARGB8888: + Helper.CopyRawPixelsIntoBitmap(bytes, textureWidth, bitmap, true); break; default: - throw new NotImplementedException($"Format: \"{Format.ToString()}\" ({(int)Format})"); + throw new NotImplementedException($"Format: \"{Format.ToString()}\" ({(int) Format})"); } - var width = ImageWidth; - var textureWidth = Mipmaps[0].Width; - var height = ImageHeight; - var bitmap = new Bitmap(width, height); - - Helper.CopyRawPixelsIntoBitmap(bytes, textureWidth * 4, bitmap, true); - var stream = new MemoryStream(); bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png); bitmap.Dispose(); @@ -81,7 +94,9 @@ public byte[] Decompile() public void DecompileAndSave(string path, bool overwrite) { if (!overwrite && File.Exists(path + Helper.GetExtension( - ImageFormat == FreeImageFormat.FIF_UNKNOWN ? FreeImageFormat.FIF_PNG : ImageFormat))) + ImageFormat == FreeImageFormat.FIF_UNKNOWN + ? FreeImageFormat.FIF_PNG + : ImageFormat))) return; var bytes = Decompile(); @@ -100,7 +115,7 @@ public void SaveFormatInfo(string path, bool overwrite) return; var format = Format.ToString().Split('.').Last().ToLower(); - + // ReSharper disable LocalizableElement File.WriteAllText(path, $"{{\r\n\t\"format\" : \"{format}\"\r\n}}"); } @@ -112,22 +127,25 @@ public enum TexMipmapVersion Version2, Version1 } - + // ReSharper disable InconsistentNaming public enum TexFormat { ARGB8888, DXT5, DXT3, - DXT1 + DXT1, + R8, + RG8, } - + [Flags] public enum TexFlags { NoInterpolation = 1, ClampUVs = 2, IsGif = 4, + // Placeholders Unk3 = 8, Unk4 = 16, @@ -135,4 +153,4 @@ public enum TexFlags Unk6 = 64, Unk7 = 128, } -} +} \ No newline at end of file diff --git a/RePKG/Texture/TexLoader.cs b/RePKG/Texture/TexLoader.cs index 096ab01..586495f 100644 --- a/RePKG/Texture/TexLoader.cs +++ b/RePKG/Texture/TexLoader.cs @@ -51,6 +51,12 @@ public static Tex LoadTex(byte[] bytes, int maxMipmapsToLoad = -1) case 7: tex.Format = TexFormat.DXT1; break; + case 8: + tex.Format = TexFormat.RG8; + break; + case 9: + tex.Format = TexFormat.R8; + break; default: throw new Exception( $"Unknown tex format id: {tex.FormatId} for {tex.TextureContainerMagic}");