diff --git a/pk3DS.Core/CTR/BCLIM.cs b/pk3DS.Core/CTR/BCLIM.cs index 5cc4942..61dcdfb 100644 --- a/pk3DS.Core/CTR/BCLIM.cs +++ b/pk3DS.Core/CTR/BCLIM.cs @@ -2,8 +2,6 @@ using System.Drawing; using System.Drawing.Imaging; using System.IO; -using System.Runtime.InteropServices; -using pk3DS.Core.Properties; // For other projects, replace this line with the namespace that contains your ETC1Lib.dll resource. namespace pk3DS.Core.CTR { @@ -103,7 +101,7 @@ public static Image makeBMP(string path, bool autosave = false, bool crop = true // PKM XY Format 7 (Color Palette) img = getIMG_XY7(bclim); else if (f == 10 || f == 11) - img = getIMG_ETC(bclim); + img = ImageUtil.GetBitmapETC(bclim); else img = getIMG(bclim); @@ -190,7 +188,7 @@ public static Bitmap getIMG(CLIM bclim) if (bclim.FileFormat == 7 && BitConverter.ToUInt16(bclim.Data, 0) == 2) // XY7 return getIMG_XY7(bclim); if (bclim.FileFormat == 10 || bclim.FileFormat == 11) // Use ETC1 to get image instead. - return getIMG_ETC(bclim); + return ImageUtil.GetBitmapETC(bclim); // New Image int w = nlpo2(gcm(bclim.Width, 8)); int h = nlpo2(gcm(bclim.Height, 8)); @@ -251,102 +249,6 @@ public static Bitmap getIMG_XY7(CLIM bclim) } return img; } - public static Bitmap getIMG_ETC(CLIM bclim) - { - Bitmap img = new Bitmap(Math.Max(nlpo2(bclim.Width), 16), Math.Max(nlpo2(bclim.Height), 16)); - string dllpath = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) + "\\ETC1Lib.dll"; - if (!File.Exists(dllpath)) File.WriteAllBytes(dllpath, Resources.ETC1Lib); - - try - { - /* http://jul.rustedlogic.net/thread.php?id=17312 - * Much of this code is taken/modified from Tharsis. Thank you to Tharsis's creator, xdaniel. - * https://github.com/xdanieldzd/Tharsis - */ - - /* Get compressed data & handle to it */ - byte[] textureData = bclim.Data; - //textureData = switchEndianness(textureData, 0x10); - ushort[] input = new ushort[textureData.Length/sizeof (ushort)]; - Buffer.BlockCopy(textureData, 0, input, 0, textureData.Length); - GCHandle pInput = GCHandle.Alloc(input, GCHandleType.Pinned); - - /* Marshal data around, invoke ETC1.dll for conversion, etc */ - uint size1 = 0; - UInt16 w = (ushort)img.Width, h = (ushort)img.Height; - - ETC1.ConvertETC1(IntPtr.Zero, ref size1, IntPtr.Zero, w, h, bclim.FileFormat == 0xB); // true = etc1a4, false = etc1 - // System.Diagnostics.Debug.WriteLine(size1); - uint[] output = new uint[size1]; - GCHandle pOutput = GCHandle.Alloc(output, GCHandleType.Pinned); - ETC1.ConvertETC1(pOutput.AddrOfPinnedObject(), ref size1, pInput.AddrOfPinnedObject(), w, h, bclim.FileFormat == 0xB); - pOutput.Free(); - pInput.Free(); - - - /* Unscramble if needed // could probably be done in ETC1Lib.dll, it's probably pretty ugly, but whatever... */ - /* Non-square code blocks could need some cleanup, verification, etc. as well... */ - uint[] finalized = new uint[output.Length]; - - // Act if it's square because BCLIM swizzling is stupid - Buffer.BlockCopy(output, 0, finalized, 0, finalized.Length); - - byte[] tmp = new byte[finalized.Length]; - Buffer.BlockCopy(finalized, 0, tmp, 0, tmp.Length); - byte[] imgData = tmp; - - for (int i = 0; i < w; i++) - { - for (int j = 0; j < h; j++) - { - int k = (j + i*img.Height)*4; - img.SetPixel(i, j, Color.FromArgb(imgData[k + 3], imgData[k], imgData[k + 1], imgData[k + 2])); - } - } - // Image is 13 instead of 12 - // 24 34 - img.RotateFlip(RotateFlipType.Rotate90FlipX); - if (w > h) - { - // Image is now in appropriate order, but the shifting is messed up. Let's fix that. - Bitmap img2 = new Bitmap(Math.Max(nlpo2(bclim.Width), 16), Math.Max(nlpo2(bclim.Height), 16)); - for (int y = 0; y < Math.Max(nlpo2(bclim.Width), 16); y += 8) - { - for (int x = 0; x < Math.Max(nlpo2(bclim.Height), 16); x++) - { - for (int j = 0; j < 8; j++) - // Treat every 8 vertical pixels as 1 pixel for purposes of calculation, add to offset later. - { - int x1 = (x + y/8*h)%img2.Width; // Reshift x - int y1 = (x + y/8*h)/img2.Width*8; // Reshift y - img2.SetPixel(x1, y1 + j, img.GetPixel(x, y + j)); // Reswizzle - } - } - } - img = img2; - } - else if (h > w) - { - Bitmap img2 = new Bitmap(Math.Max(nlpo2(bclim.Width), 16), Math.Max(nlpo2(bclim.Height), 16)); - for (int y = 0; y < Math.Max(nlpo2(bclim.Width), 16); y += 8) - { - for (int x = 0; x < Math.Max(nlpo2(bclim.Height), 16); x++) - { - for (int j = 0; j < 8; j++) - // Treat every 8 vertical pixels as 1 pixel for purposes of calculation, add to offset later. - { - int x1 = x%img2.Width; // Reshift x - int y1 = (x + y/8*h)/img2.Width*8; // Reshift y - img2.SetPixel(x1, y1 + j, img.GetPixel(x, y + j)); // Reswizzle - } - } - } - img = img2; - } - } - catch { } - return img; - } // BCLIM Data Writing public static int write16BitColorPalette(Bitmap img, ref MemoryStream ms) @@ -865,10 +767,10 @@ public static CLIM analyze(string path) return bclim; } } - public struct CLIM + public struct CLIM : IXLIM { - public uint Magic; // CLIM = 0x4D494C43 - public UInt16 BOM; // 0xFFFE + public uint Magic { get; set; }// CLIM = 0x4D494C43 + public ushort BOM; // 0xFFFE public uint CLIMLength; // HeaderLength - 14 public int TileWidth; // 1<<[[n]] public int TileHeight; // 1<<[[n]] @@ -877,12 +779,12 @@ public struct CLIM public char[] imag; // imag = 0x67616D69 public uint imagLength; // HeaderLength - 10 - public UInt16 Width; // Final Dimensions - public UInt16 Height; // Final Dimensions + public ushort Width { get; set; } // Final Dimensions + public ushort Height { get; set; } // Final Dimensions public int FileFormat; // ?? public uint dataLength; // Pixel Data Region Length - public byte[] Data; + public byte[] Data { get; set; } public int BaseSize; diff --git a/pk3DS.Core/CTR/BFLIM.cs b/pk3DS.Core/CTR/BFLIM.cs index a6fafa3..f86c6d1 100644 --- a/pk3DS.Core/CTR/BFLIM.cs +++ b/pk3DS.Core/CTR/BFLIM.cs @@ -6,11 +6,16 @@ namespace pk3DS.Core.CTR { - public class BFLIM + public class BFLIM : IXLIM { public byte[] PixelData; public FLIM Footer; + public uint Magic { get => Footer.Magic; set => Footer.Magic = value; } + public ushort Width { get => Footer.Width; set => Footer.Width = value; } + public ushort Height { get => Footer.Height; set => Footer.Height = value; } + public BFLIMEncoding Format { get => Footer.Format; set => Footer.Format = value; } + public BFLIM(Stream data) => ReadBFLIM(data); public BFLIM(byte[] data) { @@ -77,7 +82,7 @@ public byte[] GetPixelsRaw() } } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - public struct FLIM + public struct FLIM : IXLIM { public const int SIZE = 40; public const string Identifier = "FLIM"; @@ -85,7 +90,7 @@ public struct FLIM public bool LittleEndian => BOM == 0xFEFF; public bool BigEndian => BOM == 0xFFFE; - public uint Magic; // FLIM + public uint Magic { get; set; } // FLIM public ushort BOM; // 0xFFFE public ushort HeaderLength; // always 0x14 public int Version; @@ -94,8 +99,8 @@ public struct FLIM public uint imag; // imag = 67616D69 public uint imagLength; // always 0x10 - public ushort Width; - public ushort Height; + public ushort Width { get; set; } + public ushort Height { get; set; } public short Alignment; public BFLIMEncoding Format; public BFLIMOrientation Orientation; diff --git a/pk3DS.Core/CTR/ETC1.cs b/pk3DS.Core/CTR/ETC1.cs index 2a940de..f8bb31a 100644 --- a/pk3DS.Core/CTR/ETC1.cs +++ b/pk3DS.Core/CTR/ETC1.cs @@ -1,5 +1,8 @@ using System; +using System.IO; +using System.Reflection; using System.Runtime.InteropServices; +using pk3DS.Core.Properties; namespace pk3DS.Core.CTR { @@ -7,5 +10,13 @@ internal static class ETC1 { [DllImport("ETC1Lib.dll", EntryPoint = "ConvertETC1", CallingConvention = CallingConvention.Cdecl)] public static extern void ConvertETC1(IntPtr dataOut, ref uint dataOutSize, IntPtr dataIn, ushort width, ushort height, bool alpha); + + public static void CheckETC1Lib() + { + var loc = Assembly.GetEntryAssembly()?.Location ?? typeof(ETC1).Assembly.Location; + string dllpath = Path.GetDirectoryName(loc) + "\\ETC1Lib.dll"; + if (!File.Exists(dllpath)) + File.WriteAllBytes(dllpath, Resources.ETC1Lib); + } } } \ No newline at end of file diff --git a/pk3DS.Core/CTR/IXLIM.cs b/pk3DS.Core/CTR/IXLIM.cs new file mode 100644 index 0000000..9147657 --- /dev/null +++ b/pk3DS.Core/CTR/IXLIM.cs @@ -0,0 +1,12 @@ +namespace pk3DS.Core.CTR +{ + /// + /// and header interface. + /// + public interface IXLIM + { + uint Magic { get; set; } + ushort Width { get; set; } + ushort Height { get; set; } + } +} \ No newline at end of file diff --git a/pk3DS.Core/ImageUtil.cs b/pk3DS.Core/ImageUtil.cs index e9f76cf..94e0256 100644 --- a/pk3DS.Core/ImageUtil.cs +++ b/pk3DS.Core/ImageUtil.cs @@ -1,5 +1,7 @@ -using System.Drawing; +using System; +using System.Drawing; using System.Drawing.Imaging; +using System.Runtime.InteropServices; using pk3DS.Core.CTR; namespace pk3DS.Core @@ -17,6 +19,8 @@ public static class ImageUtil /// Human visible data public static Bitmap GetBitmap(this BFLIM bflim, bool crop = true) { + if (bflim.Format == BFLIMEncoding.ETC1 || bflim.Format == BFLIMEncoding.ETC1A4) + return GetBitmapETC(bflim); var data = bflim.GetImageData(crop); return GetBitmap(data, bflim.Footer.Width, bflim.Footer.Height); } @@ -27,10 +31,116 @@ public static Bitmap GetBitmap(byte[] data, int width, int height) var bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); var ptr = bmpData.Scan0; - System.Runtime.InteropServices.Marshal.Copy(data, 0, ptr, data.Length); + Marshal.Copy(data, 0, ptr, data.Length); bmp.UnlockBits(bmpData); return bmp; } + + public static Bitmap GetBitmapETC(BCLIM.CLIM bclim) + { + ETC1.CheckETC1Lib(); + + Bitmap img = new Bitmap(Math.Max(BCLIM.nlpo2(bclim.Width), 16), Math.Max(BCLIM.nlpo2(bclim.Height), 16)); + try { return DecodeETC(bclim, img, bclim.Data, bclim.FileFormat == 0x0B); } + catch { return img; } + } + public static Bitmap GetBitmapETC(BFLIM bflim) + { + ETC1.CheckETC1Lib(); + + Bitmap img = new Bitmap(Math.Max(BCLIM.nlpo2(bflim.Width), 16), Math.Max(BCLIM.nlpo2(bflim.Height), 16)); + try { return DecodeETC(bflim, img, bflim.PixelData, bflim.Footer.Format == BFLIMEncoding.ETC1A4); } + catch { return img; } + } + + private static Bitmap DecodeETC(IXLIM bclim, Bitmap img, byte[] textureData, bool etc1A4) + { + /* http://jul.rustedlogic.net/thread.php?id=17312 + * Much of this code is taken/modified from Tharsis. Thank you to Tharsis's creator, xdaniel. + * https://github.com/xdanieldzd/Tharsis + */ + + /* Get compressed data & handle to it */ + + //textureData = switchEndianness(textureData, 0x10); + ushort[] input = new ushort[textureData.Length / sizeof(ushort)]; + Buffer.BlockCopy(textureData, 0, input, 0, textureData.Length); + GCHandle pInput = GCHandle.Alloc(input, GCHandleType.Pinned); + + /* Marshal data around, invoke ETC1.dll for conversion, etc */ + uint size1 = 0; + var w = (ushort)img.Width; + var h = (ushort)img.Height; + + ETC1.ConvertETC1(IntPtr.Zero, ref size1, IntPtr.Zero, w, h, etc1A4); // true = etc1a4, false = etc1 + uint[] output = new uint[size1]; + GCHandle pOutput = GCHandle.Alloc(output, GCHandleType.Pinned); + ETC1.ConvertETC1(pOutput.AddrOfPinnedObject(), ref size1, pInput.AddrOfPinnedObject(), w, h, etc1A4); + pOutput.Free(); + pInput.Free(); + + /* Unscramble if needed // could probably be done in ETC1Lib.dll, it's probably pretty ugly, but whatever... */ + /* Non-square code blocks could need some cleanup, verification, etc. as well... */ + uint[] finalized = new uint[output.Length]; + + // Act if it's square because BCLIM swizzling is stupid + Buffer.BlockCopy(output, 0, finalized, 0, finalized.Length); + + byte[] tmp = new byte[finalized.Length]; + Buffer.BlockCopy(finalized, 0, tmp, 0, tmp.Length); + byte[] imgData = tmp; + + for (int i = 0; i < w; i++) + { + for (int j = 0; j < h; j++) + { + int k = (j + i * img.Height) * 4; + img.SetPixel(i, j, Color.FromArgb(imgData[k + 3], imgData[k], imgData[k + 1], imgData[k + 2])); + } + } + // Image is 13 instead of 12 + // 24 34 + img.RotateFlip(RotateFlipType.Rotate90FlipX); + if (w > h) + { + // Image is now in appropriate order, but the shifting is messed up. Let's fix that. + Bitmap img2 = new Bitmap(Math.Max(BCLIM.nlpo2(bclim.Width), 16), Math.Max(BCLIM.nlpo2(bclim.Height), 16)); + for (int y = 0; y < Math.Max(BCLIM.nlpo2(bclim.Width), 16); y += 8) + { + for (int x = 0; x < Math.Max(BCLIM.nlpo2(bclim.Height), 16); x++) + { + for (int j = 0; j < 8; j++) + // Treat every 8 vertical pixels as 1 pixel for purposes of calculation, add to offset later. + { + int x1 = (x + y / 8 * h) % img2.Width; // Reshift x + int y1 = (x + y / 8 * h) / img2.Width * 8; // Reshift y + img2.SetPixel(x1, y1 + j, img.GetPixel(x, y + j)); // Reswizzle + } + } + } + img = img2; + } + else if (h > w) + { + Bitmap img2 = new Bitmap(Math.Max(BCLIM.nlpo2(bclim.Width), 16), Math.Max(BCLIM.nlpo2(bclim.Height), 16)); + for (int y = 0; y < Math.Max(BCLIM.nlpo2(bclim.Width), 16); y += 8) + { + for (int x = 0; x < Math.Max(BCLIM.nlpo2(bclim.Height), 16); x++) + { + for (int j = 0; j < 8; j++) + // Treat every 8 vertical pixels as 1 pixel for purposes of calculation, add to offset later. + { + int x1 = x % img2.Width; // Reshift x + int y1 = (x + y / 8 * h) / img2.Width * 8; // Reshift y + img2.SetPixel(x1, y1 + j, img.GetPixel(x, y + j)); // Reswizzle + } + } + } + img = img2; + } + + return img; + } } }