From a31571d1d655740beae3e6da1929db647eb765d9 Mon Sep 17 00:00:00 2001 From: Kurt Date: Mon, 1 Jan 2018 20:29:26 -0800 Subject: [PATCH] Rearranging need to make bclim code generic (bmp data -> encoded swizzle) CLIM is now --- pk3DS.Core/CTR/Images/BCLIM.cs | 593 ++++-------------- pk3DS.Core/CTR/Images/BFLIM.cs | 345 +--------- pk3DS.Core/CTR/Images/BXLIM.cs | 67 ++ pk3DS.Core/CTR/Images/CLIMHeader.cs | 31 + pk3DS.Core/CTR/Images/Coordinate.cs | 25 + pk3DS.Core/CTR/Images/FLIMHeader.cs | 30 + .../CTR/{IXLIM.cs => Images/IXLIMHeader.cs} | 6 +- pk3DS.Core/CTR/Images/PixelConverter.cs | 176 ++++++ pk3DS.Core/CTR/Images/XLIMEncoding.cs | 20 + pk3DS.Core/CTR/Images/XLIMOrientation.cs | 12 + pk3DS.Core/CTR/Images/XLIMOrienter.cs | 112 ++++ pk3DS.Core/CTR/Images/XLIMUtil.cs | 31 + pk3DS.Core/CTR/SMDH.cs | 8 +- pk3DS.Core/ImageUtil.cs | 46 +- pk3DS/Subforms/Gen6/TitleScreenEditor6.cs | 15 +- 15 files changed, 681 insertions(+), 836 deletions(-) create mode 100644 pk3DS.Core/CTR/Images/BXLIM.cs create mode 100644 pk3DS.Core/CTR/Images/CLIMHeader.cs create mode 100644 pk3DS.Core/CTR/Images/Coordinate.cs create mode 100644 pk3DS.Core/CTR/Images/FLIMHeader.cs rename pk3DS.Core/CTR/{IXLIM.cs => Images/IXLIMHeader.cs} (62%) create mode 100644 pk3DS.Core/CTR/Images/PixelConverter.cs create mode 100644 pk3DS.Core/CTR/Images/XLIMEncoding.cs create mode 100644 pk3DS.Core/CTR/Images/XLIMOrientation.cs create mode 100644 pk3DS.Core/CTR/Images/XLIMOrienter.cs create mode 100644 pk3DS.Core/CTR/Images/XLIMUtil.cs diff --git a/pk3DS.Core/CTR/Images/BCLIM.cs b/pk3DS.Core/CTR/Images/BCLIM.cs index 9a5801b..3abf345 100644 --- a/pk3DS.Core/CTR/Images/BCLIM.cs +++ b/pk3DS.Core/CTR/Images/BCLIM.cs @@ -1,22 +1,75 @@ using System; using System.Drawing; using System.IO; +using pk3DS.Core.CTR.Images; namespace pk3DS.Core.CTR { - public static class BCLIM + public class BCLIM : BXLIM { - public static void openFile(string path, bool autosave = false, bool crop = true, char format = 'X') + public BCLIM(Stream data) => ReadBCLIM(data); + public BCLIM(byte[] data) { - // Handle file - if (!File.Exists(path)) throw new Exception("Can only accept files, not folders"); - string ext = Path.GetExtension(path); - if (ext == ".png") - makeBCLIM(path, format); - else if (ext == ".bin" || ext == ".bclim") - makeBMP(path, autosave, crop); + using (var ms = new MemoryStream(data)) + ReadBCLIM(ms); + } + public BCLIM(string path) + { + var data = File.ReadAllBytes(path); + using (var ms = new MemoryStream(data)) + ReadBCLIM(ms); } + private void ReadBCLIM(Stream ms) + { + PixelData = new byte[ms.Length - FLIMHeader.SIZE]; + ms.Read(PixelData, 0, PixelData.Length); + var footer = new byte[FLIMHeader.SIZE]; + ms.Read(footer, 0, footer.Length); + Footer = footer.ToStructure(); + } + + public override uint[] GetPixels() + { + if (Format == (XLIMEncoding)7 && BitConverter.ToUInt16(PixelData, 0) == 2) // Gen6 Palette + return GetPixelsViaPalette(); + return base.GetPixels(); + } + + private uint[] GetPixelsViaPalette() + { + using (var ms = new MemoryStream(PixelData)) + using (var br = new BinaryReader(ms)) + { + if (br.ReadUInt16() != 2) return null; + + // read palette + int count = br.ReadUInt16(); + uint[] colors = new uint[count]; + for (int i = 0; i < colors.Length; i++) + colors[i] = PixelConverter.GetDecodedPixelValue(br.ReadUInt16(), XLIMEncoding.RGB565); + + // read pixels + bool half = colors.Length < 0x10; + uint[] pixels = new uint[BaseSize * BaseSize]; + for (int i = 0; i < pixels.Length; i++) + { + var b = br.ReadByte(); + if (!half) + { + pixels[i] = colors[b]; + } + else + { + pixels[i++] = colors[b & 0xF]; + pixels[i] = colors[b >> 4]; + } + } + return pixels; + } + } + + // todo: move System.Drawing utilization out, make encoding generic for bflim public static byte[] IMGToBCLIM(Image img, char fc) { Bitmap mBitmap = new Bitmap(img); @@ -80,15 +133,14 @@ public static Image makeBCLIM(string path, char fc) } public static Image makeBMP(string path, bool autosave = false, bool crop = true) { - CLIM bclim = analyze(path); + BCLIM bclim = analyze(path); if (bclim.Magic != 0x4D494C43) { System.Media.SystemSounds.Beep.Play(); return null; } - - Bitmap img = GetBCLIMImage(bclim); + Bitmap img = bclim.GetBitmap(crop); if (img == null) return null; if (crop) @@ -98,146 +150,6 @@ public static Image makeBMP(string path, bool autosave = false, bool crop = true return img; } - private static Bitmap GetBCLIMImage(CLIM bclim) - { - int f = bclim.FileFormat; - switch (f) - { - case 7 when BitConverter.ToUInt16(bclim.Data, 0) == 2: // PKM XY Format 7 (Color Palette) - return getIMG_XY7(bclim); - case 0xA: case 0xB: - return ImageUtil.GetBitmapETC(bclim); - default: - if (f > 0xD) // unsupported formats - return null; - return getIMG(bclim); - } - } - - // Bitmap Data Writing - public static Bitmap getIMG(int width, int height, byte[] bytes, int f) - { - Bitmap img = new Bitmap(width, height); - int area = img.Width * img.Height; - // Tiles Per Width - int p = gcm(img.Width, 8) / 8; - if (p == 0) p = 1; - using (Stream BitmapStream = new MemoryStream(bytes)) - using (BinaryReader br = new BinaryReader(BitmapStream)) - for (uint i = 0; i < area; i++) // for every pixel - { - uint x; - uint y; - d2xy(i % 64, out x, out y); - uint tile = i / 64; - - // Shift Tile Coordinate into Tilemap - x += (uint)(tile % p) * 8; - y += (uint)(tile / p) * 8; - - // Get Color - Color c; - switch (f) - { - case 0x0: // L8 // 8bit/1 byte - case 0x1: // A8 - case 0x2: // LA4 - c = DecodeColor(br.ReadByte(), f); - break; - case 0x3: // LA8 // 16bit/2 byte - case 0x4: // HILO8 - case 0x5: // RGB565 - case 0x8: // RGBA4444 - case 0x7: // RGBA5551 - c = DecodeColor(br.ReadUInt16(), f); - break; - case 0x6: // RGB8: // 24bit - byte[] data = br.ReadBytes(3); Array.Resize(ref data, 4); - c = DecodeColor(BitConverter.ToUInt32(data, 0), f); - break; - case 0x9: // RGBA8888 - c = DecodeColor(br.ReadUInt32(), f); - break; - case 0xC: // L4 - case 0xD: // A4 // 4bit - Do 2 pixels at a time. - uint val = br.ReadByte(); - img.SetPixel((int)x, (int)y, DecodeColor(val & 0xF, f)); // lowest bits for the low pixel - i++; x++; - c = DecodeColor(val >> 4, f); // highest bits for the high pixel - break; - default: throw new Exception("Invalid FileFormat."); - } - img.SetPixel((int)x, (int)y, c); - } - return img; - } - 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 ImageUtil.GetBitmapETC(bclim); - // New Image - int w = nlpo2(gcm(bclim.Width, 8)); - int h = nlpo2(gcm(bclim.Height, 8)); - int f = bclim.FileFormat; - int area = w * h; - if (f == 9 && area > bclim.Data.Length / 4) - { - w = gcm(bclim.Width, 8); - h = gcm(bclim.Height, 8); - } - // Build Image - return getIMG(w, h, bclim.Data, f); - } - public static Bitmap getIMG_XY7(CLIM bclim) - { - Bitmap img = new Bitmap(bclim.BaseSize, bclim.BaseSize); - using (Stream BitmapStream = new MemoryStream(bclim.Data)) - using (BinaryReader br = new BinaryReader(BitmapStream)) - { - // Fetch Color stuff. - if (br.ReadUInt16() != 2) return null; - ushort colors = br.ReadUInt16(); - Color[] ca = new Color[colors]; - for (int i = 0; i < colors; i++) - ca[i] = DecodeColor(br.ReadUInt16(), 7); - - // Coordinates - // Colors - // Tiles Per Width - int p = gcm(img.Width, 8) / 8; - if (p == 0) p = 1; - - for (uint i = 0; i < bclim.BaseSize * bclim.BaseSize; i++) // for every pixel - { - uint x; - uint y; - d2xy(i % 64, out x, out y); - uint tile = i / 64; - - // Shift Tile Coordinate into Tilemap - x += (uint)(tile % p) * 8; - y += (uint)(tile / p) * 8; - - byte val = br.ReadByte(); - if (colors <= 0x10) // Handle 2 pixels at a time - { - img.SetPixel((int)x, (int)y, ca[val >> 4]); - x++; i++; val &= 0xF; - img.SetPixel((int)x, (int)y, ca[val]); - } - else //1bpp instead of .5, handle 2 pixels at a time the same way for no reason - { - img.SetPixel((int)x, (int)y, ca[val]); - x++; i++; val = br.ReadByte(); - img.SetPixel((int)x, (int)y, ca[val]); - } - } - } - return img; - } - // BCLIM Data Writing public static int write16BitColorPalette(Bitmap img, ref MemoryStream ms) { @@ -256,10 +168,10 @@ public static int write16BitColorPalette(Bitmap img, ref MemoryStream ms) if (colors > 70) throw new Exception("Too many colors"); // Set up a new reverse image to build into. - int w = gcm(img.Width, 8); - int h = gcm(img.Height, 8); - w = Math.Max(nlpo2(w), nlpo2(h)); - h = w; + int w = XLIMUtil.gcm(img.Width, 8); + int h = XLIMUtil.gcm(img.Height, 8); + w = Math.Max(XLIMUtil.nlpo2(w), XLIMUtil.nlpo2(h)); + h = w; byte[] pixelarray = new byte[w * h]; const int colorformat = 2; @@ -267,7 +179,7 @@ public static int write16BitColorPalette(Bitmap img, ref MemoryStream ms) pcs[0] = Color.FromArgb(0, 0xFF, 0xFF, 0xFF); - int p = gcm(w, 8) / 8; + int p = XLIMUtil.gcm(w, 8) / 8; if (p == 0) p = 1; int d = 0; for (uint i = 0; i < pixelarray.Length; i++) @@ -276,7 +188,7 @@ public static int write16BitColorPalette(Bitmap img, ref MemoryStream ms) // Get Tile Coordinate uint x; uint y; - d2xy(i % 64, out x, out y); + XLIMOrienter.d2xy(i % 64, out x, out y); // Get Shift Tile uint tile = i / 64; @@ -314,12 +226,12 @@ public static int write16BitColorPalette(Bitmap img, ref MemoryStream ms) bz.Write((ushort)colorformat); bz.Write((ushort)ctr); // Write Colors for (int i = 0; i < ctr; i++) - bz.Write(GetRGBA5551(pcs[i])); // Write byte array. + bz.Write((ushort)GetRGBA5551(pcs[i])); // Write byte array. // Write Pixel Data for (uint i = 0; i < d; i++) bz.Write(pixelarray[i]); // Write Padding - while (pixelcolors.Length < nlpo2((int)pixelcolors.Length)) + while (pixelcolors.Length < XLIMUtil.nlpo2((int)pixelcolors.Length)) bz.Write((byte)0); // Copy to main CLIM. pixelcolors.Position = 0; pixelcolors.CopyTo(ms); @@ -333,6 +245,24 @@ public static void writeGeneric(int format, Bitmap img, ref MemoryStream ms, boo bz.Flush(); } + public static byte[] MakePixelData(byte[] input, ref int w, ref int h, XLIMOrientation x = XLIMOrientation.None) + { + int width = w; + w = XLIMUtil.nlpo2(w); + h = XLIMUtil.nlpo2(h); + if (!(Math.Min(w, h) < 32)) + w = h = Math.Max(w, h); // resize + + byte[] pixels = new byte[w * h * 4]; + var orienter = new XLIMOrienter(w, h, x); + for (uint i = 0; i < pixels.Length / 4; i++) + { + var c = orienter.Get(i); + var offset = c.X * 4 + c.Y * width; + Array.Copy(input, offset, pixels, i * 4, 4); + } + return pixels; + } public static byte[] getPixelData(Bitmap img, int format, bool rectangle = true) { int w = img.Width; @@ -344,25 +274,23 @@ public static byte[] getPixelData(Bitmap img, int format, bool rectangle = true) // Square Format Checks if (rectangle && Math.Min(img.Width, img.Height) < 32) { - w = nlpo2(img.Width); - h = nlpo2(img.Height); + w = XLIMUtil.nlpo2(img.Width); + h = XLIMUtil.nlpo2(img.Height); } else { - w = h = Math.Max(nlpo2(w), nlpo2(h)); // else resize + w = h = Math.Max(XLIMUtil.nlpo2(w), XLIMUtil.nlpo2(h)); // else resize } } using (MemoryStream mz = new MemoryStream()) using (BinaryWriter bz = new BinaryWriter(mz)) { - int p = gcm(w, 8) / 8; + int p = XLIMUtil.gcm(w, 8) / 8; if (p == 0) p = 1; for (uint i = 0; i < w * h; i++) { - uint x; - uint y; - d2xy(i % 64, out x, out y); + XLIMOrienter.d2xy(i % 64, out uint x, out uint y); // Get Shift Tile uint tile = i / 64; @@ -380,44 +308,44 @@ public static byte[] getPixelData(Bitmap img, int format, bool rectangle = true) switch (format) { - case 0: bz.Write(GetL8(c)); break; // L8 - case 1: bz.Write(GetA8(c)); break; // A8 - case 2: bz.Write(GetLA4(c)); break; // LA4(4) - case 3: bz.Write(GetLA8(c)); break; // LA8(8) - case 4: bz.Write(GetHILO8(c)); break; // HILO8 - case 5: bz.Write(GetRGB565(c)); break; // RGB565 + case 0: bz.Write((byte)GetL8(c)); break; // L8 + case 1: bz.Write((byte)GetA8(c)); break; // A8 + case 2: bz.Write((byte)GetLA4(c)); break; // LA4(4) + case 3: bz.Write((ushort)GetLA8(c)); break; // LA8(8) + case 4: bz.Write((ushort)GetHILO8(c)); break; // HILO8 + case 5: bz.Write((ushort)GetRGB565(c)); break; // RGB565 case 6: - { - bz.Write(c.B); - bz.Write(c.G); - bz.Write(c.R); break; - } - case 7: bz.Write(GetRGBA5551(c)); break; // RGBA5551 - case 8: bz.Write(GetRGBA4444(c)); break; // RGBA4444 - case 9: bz.Write(GetRGBA8888(c)); break; // RGBA8 + { + bz.Write(c.B); + bz.Write(c.G); + bz.Write(c.R); break; + } + case 7: bz.Write((ushort)GetRGBA5551(c)); break; // RGBA5551 + case 8: bz.Write((ushort)GetRGBA4444(c)); break; // RGBA4444 + case 9: bz.Write((uint)GetRGBA8888(c)); break; // RGBA8 case 10: throw new Exception("ETC1 not supported."); case 11: throw new Exception("ETC1A4 not supported."); case 12: - { - byte val = (byte)(GetL8(c) / 0x11); // First Pix // L4 - { c = img.GetPixel((int)x, (int)y); if (c.A == 0) c = Color.FromArgb(0, 0, 0, 0); } - val |= (byte)((GetL8(c) / 0x11) << 4); i++; - bz.Write(val); break; - } + { + byte val = (byte)(GetL8(c) / 0x11); // First Pix // L4 + { c = img.GetPixel((int)x, (int)y); if (c.A == 0) c = Color.FromArgb(0, 0, 0, 0); } + val |= (byte)((GetL8(c) / 0x11) << 4); i++; + bz.Write(val); break; + } case 13: - { - byte val = (byte)(GetA8(c) / 0x11); // First Pix // L4 - { c = img.GetPixel((int)x, (int)y); } - val |= (byte)((GetA8(c) / 0x11) << 4); i++; - bz.Write(val); break; - } + { + byte val = (byte)(GetA8(c) / 0x11); // First Pix // L4 + { c = img.GetPixel((int)x, (int)y); } + val |= (byte)((GetA8(c) / 0x11) << 4); i++; + bz.Write(val); break; + } } } if (!perfect) - while (mz.Length < nlpo2((int)mz.Length)) // pad + while (mz.Length < XLIMUtil.nlpo2((int)mz.Length)) // pad bz.Write((byte)0); return mz.ToArray(); - } + } } public static int getColorCount(Bitmap img) { @@ -437,72 +365,6 @@ public static int getColorCount(Bitmap img) return colorct; } - - internal static readonly int[] Convert5To8 = { 0x00,0x08,0x10,0x18,0x20,0x29,0x31,0x39, - 0x41,0x4A,0x52,0x5A,0x62,0x6A,0x73,0x7B, - 0x83,0x8B,0x94,0x9C,0xA4,0xAC,0xB4,0xBD, - 0xC5,0xCD,0xD5,0xDE,0xE6,0xEE,0xF6,0xFF }; - - private static Color DecodeColor(uint val, int format) - { - int alpha = 0xFF, red, green, blue; - switch (format) - { - case 0: // L8 - return Color.FromArgb(alpha, (byte)val, (byte)val, (byte)val); - case 1: // A8 - return Color.FromArgb((byte)val, alpha, alpha, alpha); - case 2: // LA4 - red = (byte)(val >> 4); - alpha = (byte)(val & 0x0F); - return Color.FromArgb(alpha, red, red, red); - case 3: // LA8 - red = (byte)(val >> 8 & 0xFF); - alpha = (byte)(val & 0xFF); - return Color.FromArgb(alpha, red, red, red); - case 4: // HILO8 - red = (byte)(val >> 8); - green = (byte)(val & 0xFF); - return Color.FromArgb(alpha, red, green, 0xFF); - case 5: // RGB565 - red = Convert5To8[(val >> 11) & 0x1F]; - green = (byte)(((val >> 5) & 0x3F) * 4); - blue = Convert5To8[val & 0x1F]; - return Color.FromArgb(alpha, red, green, blue); - case 6: // RGB8 - red = (byte)((val >> 16) & 0xFF); - green = (byte)((val >> 8) & 0xFF); - blue = (byte)(val & 0xFF); - return Color.FromArgb(alpha, red, green, blue); - case 7: // RGBA5551 - red = Convert5To8[(val >> 11) & 0x1F]; - green = Convert5To8[(val >> 6) & 0x1F]; - blue = Convert5To8[(val >> 1) & 0x1F]; - alpha = (val & 0x0001) == 1 ? 0xFF : 0x00; - return Color.FromArgb(alpha, red, green, blue); - case 8: // RGBA4444 - alpha = (byte)(0x11 * (val & 0xf)); - red = (byte)(0x11 * ((val >> 12) & 0xf)); - green = (byte)(0x11 * ((val >> 8) & 0xf)); - blue = (byte)(0x11 * ((val >> 4) & 0xf)); - return Color.FromArgb(alpha, red, green, blue); - case 9: // RGBA8888 - red = (byte)((val >> 24) & 0xFF); - green = (byte)((val >> 16) & 0xFF); - blue = (byte)((val >> 8) & 0xFF); - alpha = (byte)(val & 0xFF); - return Color.FromArgb(alpha, red, green, blue); - // case 10: - // case 11: - case 12: // L4 - return Color.FromArgb(alpha, (byte)(val * 0x11), (byte)(val * 0x11), (byte)(val * 0x11)); - case 13: // A4 - return Color.FromArgb((byte)(val * 0x11), alpha, alpha, alpha); - default: - return Color.White; - } - } - // Color Conversion internal static byte GetL8(Color c) { @@ -573,219 +435,28 @@ internal static byte convert8to5(int colorval) { byte[] Convert8to5 = { 0x00,0x08,0x10,0x18,0x20,0x29,0x31,0x39, - 0x41,0x4A,0x52,0x5A,0x62,0x6A,0x73,0x7B, - 0x83,0x8B,0x94,0x9C,0xA4,0xAC,0xB4,0xBD, - 0xC5,0xCD,0xD5,0xDE,0xE6,0xEE,0xF6,0xFF }; + 0x41,0x4A,0x52,0x5A,0x62,0x6A,0x73,0x7B, + 0x83,0x8B,0x94,0x9C,0xA4,0xAC,0xB4,0xBD, + 0xC5,0xCD,0xD5,0xDE,0xE6,0xEE,0xF6,0xFF }; byte i = 0; while (colorval > Convert8to5[i]) i++; return i; } - internal static uint DM2X(uint code) - { - return C11(code >> 0); - } - internal static uint DM2Y(uint code) - { - return C11(code >> 1); - } - internal static uint C11(uint x) - { - x &= 0x55555555; // x = -f-e -d-c -b-a -9-8 -7-6 -5-4 -3-2 -1-0 - x = (x ^ (x >> 1)) & 0x33333333; // x = --fe --dc --ba --98 --76 --54 --32 --10 - x = (x ^ (x >> 2)) & 0x0f0f0f0f; // x = ---- fedc ---- ba98 ---- 7654 ---- 3210 - x = (x ^ (x >> 4)) & 0x00ff00ff; // x = ---- ---- fedc ba98 ---- ---- 7654 3210 - x = (x ^ (x >> 8)) & 0x0000ffff; // x = ---- ---- ---- ---- fedc ba98 7654 3210 - return x; - } - - /// - /// Greatest common multiple (to round up) - /// - /// Number to round-up. - /// Multiple to round-up to. - /// Rounded up number. - internal static int gcm(int n, int m) - { - return (n + m - 1) / m * m; - } - /// - /// Next Largest Power of 2 - /// - /// Input to round up to next 2^n - /// 2^n > x && x > 2^(n-1) - internal static int nlpo2(int x) - { - x--; // comment out to always take the next biggest power of two, even if x is already a power of two - x |= x >> 1; - x |= x >> 2; - x |= x >> 4; - x |= x >> 8; - x |= x >> 16; - return x+1; - } - // Morton Translation - /// - /// Combines X/Y Coordinates to a decimal ordinate. - /// - /// - /// - /// - internal static uint xy2d(uint x, uint y) + public static BCLIM analyze(byte[] data, string shortPath) { - x &= 0x0000ffff; - y &= 0x0000ffff; - x |= x << 8; - y |= y << 8; - x &= 0x00ff00ff; - y &= 0x00ff00ff; - x |= x << 4; - y |= y << 4; - x &= 0x0f0f0f0f; - y &= 0x0f0f0f0f; - x |= x << 2; - y |= y << 2; - x &= 0x33333333; - y &= 0x33333333; - x |= x << 1; - y |= y << 1; - x &= 0x55555555; - y &= 0x55555555; - return x | (y << 1); - } - /// - /// Decimal Ordinate In to X / Y Coordinate Out - /// - /// Loop integer which will be decoded to X/Y - /// Output X coordinate - /// Output Y coordinate - internal static void d2xy(uint d, out uint x, out uint y) - { - x = d; - y = x >> 1; - x &= 0x55555555; - y &= 0x55555555; - x |= x >> 1; - y |= y >> 1; - x &= 0x33333333; - y &= 0x33333333; - x |= x >> 2; - y |= y >> 2; - x &= 0x0f0f0f0f; - y &= 0x0f0f0f0f; - x |= x >> 4; - y |= y >> 4; - x &= 0x00ff00ff; - y &= 0x00ff00ff; - x |= x >> 8; - y |= y >> 8; - x &= 0x0000ffff; - y &= 0x0000ffff; - } - - public static CLIM analyze(byte[] data, string shortPath) - { - CLIM bclim = new CLIM + BCLIM bclim = new BCLIM(data) { FileName = Path.GetFileNameWithoutExtension(shortPath), FilePath = Path.GetDirectoryName(shortPath), Extension = Path.GetExtension(shortPath) }; - byte[] byteArray = data; - using (BinaryReader br = new BinaryReader(new MemoryStream(byteArray))) - { - br.BaseStream.Seek(br.BaseStream.Length - 0x28, SeekOrigin.Begin); - bclim.Magic = br.ReadUInt32(); - - bclim.BOM = br.ReadUInt16(); - bclim.CLIMLength = br.ReadUInt32(); - bclim.TileWidth = 2 << br.ReadByte(); - bclim.TileHeight = 2 << br.ReadByte(); - bclim.totalLength = br.ReadUInt32(); - bclim.Count = br.ReadUInt32(); - - bclim.imag = br.ReadChars(4); - bclim.imagLength = br.ReadUInt32(); - bclim.Width = br.ReadUInt16(); - bclim.Height = br.ReadUInt16(); - bclim.FileFormat = br.ReadInt32(); - bclim.dataLength = br.ReadUInt32(); - - bclim.BaseSize = Math.Max(nlpo2(bclim.Width), nlpo2(bclim.Height)); - - br.BaseStream.Seek(0, SeekOrigin.Begin); - bclim.Data = br.ReadBytes((int)bclim.dataLength); - - return bclim; - } + return bclim; } - public static CLIM analyze(string path) + public static BCLIM analyze(string path) { - CLIM bclim = new CLIM - { - FileName = Path.GetFileNameWithoutExtension(path), - FilePath = Path.GetDirectoryName(path), - Extension = Path.GetExtension(path) - }; - byte[] byteArray = File.ReadAllBytes(path); - using (BinaryReader br = new BinaryReader(new MemoryStream(byteArray))) - { - br.BaseStream.Seek(br.BaseStream.Length - 0x28, SeekOrigin.Begin); - bclim.Magic = br.ReadUInt32(); - - bclim.BOM = br.ReadUInt16(); - bclim.CLIMLength = br.ReadUInt32(); - bclim.TileWidth = 2 << br.ReadByte(); - bclim.TileHeight = 2 << br.ReadByte(); - bclim.totalLength = br.ReadUInt32(); - bclim.Count = br.ReadUInt32(); - - bclim.imag = br.ReadChars(4); - bclim.imagLength = br.ReadUInt32(); - bclim.Width = br.ReadUInt16(); - bclim.Height = br.ReadUInt16(); - bclim.FileFormat = br.ReadInt32(); - bclim.dataLength = br.ReadUInt32(); - - bclim.BaseSize = Math.Max(nlpo2(bclim.Width), nlpo2(bclim.Height)); - - br.BaseStream.Seek(0, SeekOrigin.Begin); - bclim.Data = br.ReadBytes((int)bclim.dataLength); - - return bclim; - } - } - public struct CLIM : IXLIM - { - 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]] - public uint totalLength; // Total Length of file - public uint Count; // "1" , guessing it's just Count. - - public char[] imag; // imag = 0x67616D69 - public uint imagLength; // HeaderLength - 10 - 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 { get; set; } - - public int BaseSize; - - //// Contained Data - //public int ColorFormat; - //public int ColorCount; - //public Color[] Colors; - - //public int[] Pixels; - - public string FileName; - public string FilePath; - public string Extension; + byte[] data = File.ReadAllBytes(path); + return analyze(data, path); } } } \ No newline at end of file diff --git a/pk3DS.Core/CTR/Images/BFLIM.cs b/pk3DS.Core/CTR/Images/BFLIM.cs index b58d0db..1aa9838 100644 --- a/pk3DS.Core/CTR/Images/BFLIM.cs +++ b/pk3DS.Core/CTR/Images/BFLIM.cs @@ -1,21 +1,11 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Runtime.InteropServices; +using pk3DS.Core.CTR.Images; namespace pk3DS.Core.CTR { - public class BFLIM : IXLIM + public class BFLIM : BXLIM { - 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 XLIMEncoding Format { get => Footer.Format; set => Footer.Format = value; } - public BFLIM(Stream data) => ReadBFLIM(data); public BFLIM(byte[] data) { @@ -31,336 +21,11 @@ public BFLIM(string path) private void ReadBFLIM(Stream ms) { - PixelData = new byte[ms.Length - FLIM.SIZE]; + PixelData = new byte[ms.Length - FLIMHeader.SIZE]; ms.Read(PixelData, 0, PixelData.Length); - var footer = new byte[FLIM.SIZE]; + var footer = new byte[FLIMHeader.SIZE]; ms.Read(footer, 0, footer.Length); - Footer = footer.ToStructure(); - } - - /// - /// ARGB 32bpp - /// - public byte[] GetImageData(bool crop = true) - { - var orienter = new BFLIMOrienter(Footer.Width, Footer.Height, Footer.Orientation); - uint[] pixels = GetPixels(); - - if (!crop) - { - Footer.Width = (ushort)orienter.Width; - Footer.Height = (ushort)orienter.Height; - } - - // uint[] -> byte[] - byte[] array = new byte[Footer.Width * Footer.Height * 4]; - for (uint i = 0; i < pixels.Length; i++) - { - var coord = orienter.Get(i); - if (coord.X >= Footer.Width || coord.Y >= Footer.Height) - continue; - - var val = pixels[i]; - uint o = 4 * (coord.X + coord.Y * Footer.Width); - array[o + 0] = (byte)(val & 0xFF); - array[o + 1] = (byte)(val >> 8 & 0xFF); - array[o + 2] = (byte)(val >> 16 & 0xFF); - array[o + 3] = (byte)(val >> 24 & 0xFF); - } - return array; - } - public uint[] GetPixels() - { - return PixelConverter.GetPixels(PixelData, Footer.Format).ToArray(); - } - public byte[] GetPixelsRaw() - { - var pix = GetPixels(); - byte[] raw = new byte[pix.Length * 4]; - Buffer.BlockCopy(pix, 0, raw, 0, raw.Length); - return raw; - } - } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - public struct FLIM : IXLIM - { - public const int SIZE = 40; - public const string Identifier = "FLIM"; - public bool Valid => LittleEndian && Magic == 0x4D_49_4C_46; // FLIM - public bool LittleEndian => BOM == 0xFEFF; - public bool BigEndian => BOM == 0xFFFE; - - public uint Magic { get; set; } // FLIM - public ushort BOM; // 0xFFFE - public ushort HeaderLength; // always 0x14 - public int Version; - public uint TotalLength; - public uint Count; - - public uint imag; // imag = 67616D69 - public uint imagLength; // always 0x10 - public ushort Width { get; set; } - public ushort Height { get; set; } - public short Alignment; - public XLIMEncoding Format; - public BFLIMOrientation Orientation; - public uint DataSize; - } - - public enum XLIMEncoding : byte - { - L8 = 0x00, // 8 Luminance - A8 = 0x01, // 8 Alpha - LA4 = 0x02, // 8 Luminance + Alpha - LA8 = 0x03, // 16 Luminance + Alpha - HILO8 = 0x04, // 16 ? - RGB565 = 0x05, // 16 Color - RGBX8 = 0x06, // 24 Color - RGB5A1 = 0x07, // 16 Color + Alpha - RGBA4 = 0x08, // 16 Color + Alpha - RGBA8 = 0x09, // 32 Color + Alpha - ETC1 = 0x0A, // 4 Color - ETC1A4 = 0x0B, // 8 Color + Alpha - L4 = 0x0C, // 4 Luminance - A4 = 0x0D, // 4 Alpha - } - - public static class PixelConverter - { - private const int BPP_32 = 32; - private const int BPP_24 = 24; - private const int BPP_16 = 16; - private const int BPP_8 = 8; - private const int BPP_4 = 4; - - internal static readonly byte[] Convert5To8 = { - 0x00,0x08,0x10,0x18,0x20,0x29,0x31,0x39, - 0x41,0x4A,0x52,0x5A,0x62,0x6A,0x73,0x7B, - 0x83,0x8B,0x94,0x9C,0xA4,0xAC,0xB4,0xBD, - 0xC5,0xCD,0xD5,0xDE,0xE6,0xEE,0xF6,0xFF - }; - - public static IEnumerable GetPixels(byte[] raw, XLIMEncoding e) - { - int bpp = e.GetBitsPerPixel(); - if (bpp == BPP_4) - { - foreach (byte b in raw) - { - byte _0 = (byte)(b & 0xF); - byte _1 = (byte)(b >> 4); - yield return GetDecodedPixelValue(_0, e); - yield return GetDecodedPixelValue(_1, e); - } - yield break; - } - - for (int i = 0; i < raw.Length; i += bpp / 8) - { - uint val = GetEncodedPixelValue(raw, i, bpp); - yield return GetDecodedPixelValue(val, e); - } - } - - - private static uint GetDecodedPixelValue(uint val, XLIMEncoding e) - { - byte a = byte.MaxValue, r = 0, g = 0, b = 0; - switch (e) - { - case XLIMEncoding.L4: - case XLIMEncoding.L8: - { - r = g = b = (byte)val; - break; - } - case XLIMEncoding.A4: - case XLIMEncoding.A8: - { - r = g = b = 0xFF; - a = (byte)val; - break; - } - case XLIMEncoding.HILO8: - { - r = (byte)(val >> 8); - g = (byte)(val & 0xFF); - b = byte.MaxValue; - break; - } - case XLIMEncoding.LA4: - { - r = g = b = (byte)(val >> 4); - a = (byte)(val & 0x0F); - break; - } - case XLIMEncoding.LA8: - { - r = g = b = (byte)(val >> 8); - a = (byte)val; - break; - } - case XLIMEncoding.RGBX8: - { - r = (byte)(val >> 16); - g = (byte)(val >> 8); - b = (byte)(val >> 0); - break; - } - case XLIMEncoding.RGBA8: - { - r = (byte)(val >> 24); - g = (byte)(val >> 16); - b = (byte)(val >> 8); - a = (byte) val; - break; - } - case XLIMEncoding.RGBA4: - { - a = (byte)(0x11 * (val & 0xf)); - r = (byte)(0x11 * ((val >> 12) & 0xf)); - g = (byte)(0x11 * ((val >> 8) & 0xf)); - b = (byte)(0x11 * ((val >> 4) & 0xf)); - break; - } - case XLIMEncoding.RGB565: - { - r = Convert5To8[(val >> 11) & 0x1F]; - g = (byte)(((val >> 5) & 0x3F) * 4); - b = Convert5To8[val & 0x1F]; - break; - } - case XLIMEncoding.RGB5A1: - { - r = Convert5To8[(val >> 11) & 0x1F]; - g = Convert5To8[(val >> 6) & 0x1F]; - b = Convert5To8[(val >> 1) & 0x1F]; - a = (val & 1) == 1 ? byte.MaxValue : byte.MinValue; - break; - } - default: - throw new FormatException($"Unsupported {nameof(XLIMEncoding)} value = {e}"); - } - return (uint)((a << 24) | (r << 16) | (g << 8) | b); - } - - public static uint GetEncodedPixelValue(byte[] raw, int offset, int size) - { - switch (size) - { - case BPP_32: - return BitConverter.ToUInt32(raw, offset); - case BPP_24: - return BitConverter.ToUInt32(raw, offset) & 0x00FFFFFF; - case BPP_16: - return BitConverter.ToUInt16(raw, offset); - default: - return raw[offset]; - } - } - public static int GetBitsPerPixel(this XLIMEncoding e) - { - if (_32.Contains(e)) - return BPP_32; - if (_24.Contains(e)) - return BPP_24; - if (_16.Contains(e)) - return BPP_16; - if (_8.Contains(e)) - return BPP_8; - return BPP_4; - } - - private static readonly HashSet _32 = new HashSet - { - XLIMEncoding.RGBA8, - }; - private static readonly HashSet _24 = new HashSet - { - XLIMEncoding.RGBX8, - }; - private static readonly HashSet _16 = new HashSet - { - XLIMEncoding.LA8, - XLIMEncoding.HILO8, - XLIMEncoding.RGB565, - XLIMEncoding.RGB5A1, - XLIMEncoding.RGBA4, - }; - private static readonly HashSet _8 = new HashSet - { - XLIMEncoding.L8, - XLIMEncoding.A8, - XLIMEncoding.LA4, - XLIMEncoding.ETC1A4, - }; - } - - [Flags] - public enum BFLIMOrientation : byte - { - None = 0, - Rotate90 = 4, - Transpose = 8, - } - - public class BFLIMOrienter - { - readonly BFLIMOrientation _orientation; - - public uint Width { get; } - public uint Height { get; } - public uint PanelsPerWidth { get; } - - public BFLIMOrienter(int width, int height, BFLIMOrientation orientation) - { - Width = (uint)BCLIM.nlpo2(BCLIM.gcm(width, 8)); - Height = (uint)BCLIM.nlpo2(BCLIM.gcm(height, 8)); - - uint stride = orientation == BFLIMOrientation.None ? Width : Height; - PanelsPerWidth = (uint)BCLIM.gcm((int)stride, 8) / 8; - - _orientation = orientation; - } - - public Coordinate Get(uint i) - { - BCLIM.d2xy(i & 0x3F, out uint x, out uint y); - - // Shift Tile Coordinate into Tilemap - var tile = i >> 6; - x |= (tile % PanelsPerWidth) << 3; - y |= (tile / PanelsPerWidth) << 3; - - var coord = new Coordinate(x, y); - if (_orientation.HasFlag(BFLIMOrientation.Rotate90)) - coord.Rotate90(Height); - if (_orientation.HasFlag(BFLIMOrientation.Transpose)) - coord.Transpose(); - return coord; - } - } - - public struct Coordinate - { - public uint X { get; private set; } - public uint Y { get; private set; } - - public Coordinate(uint x, uint y) - { - X = x; Y = y; - } - public void Transpose() - { - var tmp = X; - X = Y; - Y = tmp; - } - public void Rotate90(uint height) - { - var tmp = X; - X = Y; - Y = height - 1 - tmp; + Footer = footer.ToStructure(); } } } diff --git a/pk3DS.Core/CTR/Images/BXLIM.cs b/pk3DS.Core/CTR/Images/BXLIM.cs new file mode 100644 index 0000000..ebd1e34 --- /dev/null +++ b/pk3DS.Core/CTR/Images/BXLIM.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; + +namespace pk3DS.Core.CTR.Images +{ + public abstract class BXLIM : IXLIMHeader + { + public byte[] PixelData; + public IXLIMHeader Footer { get; protected set; } + + 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 XLIMEncoding Format { get => Footer.Format; set => Footer.Format = value; } + public XLIMOrientation Orientation { get => Footer.Orientation; set => Footer.Orientation = value; } + public bool Valid => Footer.Valid && PixelData != null; + + public string FileName { get; set; } + public string FilePath { get; set; } + public string Extension { get; set; } + public int BaseSize => Math.Max(XLIMUtil.nlpo2(Width), XLIMUtil.nlpo2(Height)); + + /// + /// ARGB 32bpp + /// + public byte[] GetImageData(bool crop = true) + { + var orienter = new XLIMOrienter(Footer.Width, Footer.Height, Footer.Orientation); + uint[] pixels = GetPixels(); + + if (!crop) + { + Footer.Width = (ushort)orienter.Width; + Footer.Height = (ushort)orienter.Height; + } + + // uint[] -> byte[] + byte[] array = new byte[Footer.Width * Footer.Height * 4]; + for (uint i = 0; i < pixels.Length; i++) + { + var coord = orienter.Get(i); + if (coord.X >= Footer.Width || coord.Y >= Footer.Height) + continue; + + var val = pixels[i]; + uint o = 4 * (coord.X + coord.Y * Footer.Width); + array[o + 0] = (byte)(val & 0xFF); + array[o + 1] = (byte)(val >> 8 & 0xFF); + array[o + 2] = (byte)(val >> 16 & 0xFF); + array[o + 3] = (byte)(val >> 24 & 0xFF); + } + return array; + } + + public virtual uint[] GetPixels() + { + return PixelConverter.GetPixels(PixelData, Footer.Format).ToArray(); + } + public byte[] GetPixelsRaw() + { + var pix = GetPixels(); + byte[] raw = new byte[pix.Length * 4]; + Buffer.BlockCopy(pix, 0, raw, 0, raw.Length); + return raw; + } + } +} diff --git a/pk3DS.Core/CTR/Images/CLIMHeader.cs b/pk3DS.Core/CTR/Images/CLIMHeader.cs new file mode 100644 index 0000000..c28dee4 --- /dev/null +++ b/pk3DS.Core/CTR/Images/CLIMHeader.cs @@ -0,0 +1,31 @@ +using System.Runtime.InteropServices; + +namespace pk3DS.Core.CTR +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] + public struct CLIMHeader : IXLIMHeader + { + public const int SIZE = 40; + public const string Identifier = "CLIM"; + public bool Valid => LittleEndian && Magic == 0x4D_49_4C_43; // CLIM + public bool LittleEndian => BOM == 0xFEFF; + public bool BigEndian => BOM == 0xFFFE; + + public uint Magic { get; set; }// CLIM = 0x4D494C43 + public ushort BOM; // 0xFFFE + public uint HeaderLength; // always 0x14 + public byte TileWidth; // 1<<[[n]] + public byte TileHeight; // 1<<[[n]] + public uint totalLength; // Total Length of file + public uint Count; // "1" , guessing it's just Count. + + public uint imag; // imag = 0x67616D69 + public uint imagLength; // HeaderLength - 10 + public ushort Width { get; set; } // Final Dimensions + public ushort Height { get; set; } // Final Dimensions + public XLIMEncoding Format { get; set; } + public XLIMOrientation Orientation { get; set; } // unused + public short Alignment; // unused + public uint DataSize; // Pixel Data Region Length + } +} \ No newline at end of file diff --git a/pk3DS.Core/CTR/Images/Coordinate.cs b/pk3DS.Core/CTR/Images/Coordinate.cs new file mode 100644 index 0000000..c4f6fe5 --- /dev/null +++ b/pk3DS.Core/CTR/Images/Coordinate.cs @@ -0,0 +1,25 @@ +namespace pk3DS.Core.CTR +{ + public struct Coordinate + { + public uint X { get; private set; } + public uint Y { get; private set; } + + public Coordinate(uint x, uint y) + { + X = x; Y = y; + } + public void Transpose() + { + var tmp = X; + X = Y; + Y = tmp; + } + public void Rotate90(uint height) + { + var tmp = X; + X = Y; + Y = height - 1 - tmp; + } + } +} \ No newline at end of file diff --git a/pk3DS.Core/CTR/Images/FLIMHeader.cs b/pk3DS.Core/CTR/Images/FLIMHeader.cs new file mode 100644 index 0000000..d03b117 --- /dev/null +++ b/pk3DS.Core/CTR/Images/FLIMHeader.cs @@ -0,0 +1,30 @@ +using System.Runtime.InteropServices; + +namespace pk3DS.Core.CTR +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] + public struct FLIMHeader : IXLIMHeader + { + public const int SIZE = 40; + public const string Identifier = "FLIM"; + public bool Valid => LittleEndian && Magic == 0x4D_49_4C_46; // FLIM + public bool LittleEndian => BOM == 0xFEFF; + public bool BigEndian => BOM == 0xFFFE; + + public uint Magic { get; set; } // FLIM + public ushort BOM; // 0xFFFE + public ushort HeaderLength; // always 0x14 + public int Version; + public uint TotalLength; + public uint Count; + + public uint imag; // imag = 67616D69 + public uint imagLength; // always 0x10 + public ushort Width { get; set; } + public ushort Height { get; set; } + public short Alignment; + public XLIMEncoding Format { get; set; } + public XLIMOrientation Orientation { get; set; } + public uint DataSize; + } +} \ No newline at end of file diff --git a/pk3DS.Core/CTR/IXLIM.cs b/pk3DS.Core/CTR/Images/IXLIMHeader.cs similarity index 62% rename from pk3DS.Core/CTR/IXLIM.cs rename to pk3DS.Core/CTR/Images/IXLIMHeader.cs index 9147657..12f3750 100644 --- a/pk3DS.Core/CTR/IXLIM.cs +++ b/pk3DS.Core/CTR/Images/IXLIMHeader.cs @@ -3,10 +3,14 @@ /// /// and header interface. /// - public interface IXLIM + public interface IXLIMHeader { uint Magic { get; set; } ushort Width { get; set; } ushort Height { get; set; } + XLIMEncoding Format { get; set; } + XLIMOrientation Orientation { get; set; } + + bool Valid { get; } } } \ No newline at end of file diff --git a/pk3DS.Core/CTR/Images/PixelConverter.cs b/pk3DS.Core/CTR/Images/PixelConverter.cs new file mode 100644 index 0000000..64d2082 --- /dev/null +++ b/pk3DS.Core/CTR/Images/PixelConverter.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; + +namespace pk3DS.Core.CTR +{ + public static class PixelConverter + { + private const int BPP_32 = 32; + private const int BPP_24 = 24; + private const int BPP_16 = 16; + private const int BPP_8 = 8; + private const int BPP_4 = 4; + + internal static readonly byte[] Convert5To8 = { + 0x00,0x08,0x10,0x18,0x20,0x29,0x31,0x39, + 0x41,0x4A,0x52,0x5A,0x62,0x6A,0x73,0x7B, + 0x83,0x8B,0x94,0x9C,0xA4,0xAC,0xB4,0xBD, + 0xC5,0xCD,0xD5,0xDE,0xE6,0xEE,0xF6,0xFF + }; + + public static IEnumerable GetPixels(byte[] raw, XLIMEncoding e) + { + int bpp = e.GetBitsPerPixel(); + if (bpp == BPP_4) + { + foreach (byte b in raw) + { + byte _0 = (byte)(b & 0xF); + byte _1 = (byte)(b >> 4); + yield return GetDecodedPixelValue(_0, e); + yield return GetDecodedPixelValue(_1, e); + } + yield break; + } + + for (int i = 0; i < raw.Length; i += bpp / 8) + { + uint val = GetEncodedPixelValue(raw, i, bpp); + yield return GetDecodedPixelValue(val, e); + } + } + + + internal static uint GetDecodedPixelValue(uint val, XLIMEncoding e) + { + byte a = byte.MaxValue, r = 0, g = 0, b = 0; + switch (e) + { + case XLIMEncoding.L4: + case XLIMEncoding.L8: + { + r = g = b = (byte)val; + break; + } + case XLIMEncoding.A4: + case XLIMEncoding.A8: + { + r = g = b = 0xFF; + a = (byte)val; + break; + } + case XLIMEncoding.HILO8: + { + r = (byte)(val >> 8); + g = (byte)(val & 0xFF); + b = byte.MaxValue; + break; + } + case XLIMEncoding.LA4: + { + r = g = b = (byte)(val >> 4); + a = (byte)(val & 0x0F); + break; + } + case XLIMEncoding.LA8: + { + r = g = b = (byte)(val >> 8); + a = (byte)val; + break; + } + case XLIMEncoding.RGBX8: + { + r = (byte)(val >> 16); + g = (byte)(val >> 8); + b = (byte)(val >> 0); + break; + } + case XLIMEncoding.RGBA8: + { + r = (byte)(val >> 24); + g = (byte)(val >> 16); + b = (byte)(val >> 8); + a = (byte) val; + break; + } + case XLIMEncoding.RGBA4: + { + a = (byte)(0x11 * (val & 0xf)); + r = (byte)(0x11 * ((val >> 12) & 0xf)); + g = (byte)(0x11 * ((val >> 8) & 0xf)); + b = (byte)(0x11 * ((val >> 4) & 0xf)); + break; + } + case XLIMEncoding.RGB565: + { + r = Convert5To8[(val >> 11) & 0x1F]; + g = (byte)(((val >> 5) & 0x3F) * 4); + b = Convert5To8[val & 0x1F]; + break; + } + case XLIMEncoding.RGB5A1: + { + r = Convert5To8[(val >> 11) & 0x1F]; + g = Convert5To8[(val >> 6) & 0x1F]; + b = Convert5To8[(val >> 1) & 0x1F]; + a = (val & 1) == 1 ? byte.MaxValue : byte.MinValue; + break; + } + default: + throw new FormatException($"Unsupported {nameof(XLIMEncoding)} value = {e}"); + } + return (uint)((a << 24) | (r << 16) | (g << 8) | b); + } + + public static uint GetEncodedPixelValue(byte[] raw, int offset, int size) + { + switch (size) + { + case BPP_32: + return BitConverter.ToUInt32(raw, offset); + case BPP_24: + return BitConverter.ToUInt32(raw, offset) & 0x00FFFFFF; + case BPP_16: + return BitConverter.ToUInt16(raw, offset); + default: + return raw[offset]; + } + } + public static int GetBitsPerPixel(this XLIMEncoding e) + { + if (_32.Contains(e)) + return BPP_32; + if (_24.Contains(e)) + return BPP_24; + if (_16.Contains(e)) + return BPP_16; + if (_8.Contains(e)) + return BPP_8; + return BPP_4; + } + + private static readonly HashSet _32 = new HashSet + { + XLIMEncoding.RGBA8, + }; + private static readonly HashSet _24 = new HashSet + { + XLIMEncoding.RGBX8, + }; + private static readonly HashSet _16 = new HashSet + { + XLIMEncoding.LA8, + XLIMEncoding.HILO8, + XLIMEncoding.RGB565, + XLIMEncoding.RGB5A1, + XLIMEncoding.RGBA4, + }; + private static readonly HashSet _8 = new HashSet + { + XLIMEncoding.L8, + XLIMEncoding.A8, + XLIMEncoding.LA4, + XLIMEncoding.ETC1A4, + }; + } +} \ No newline at end of file diff --git a/pk3DS.Core/CTR/Images/XLIMEncoding.cs b/pk3DS.Core/CTR/Images/XLIMEncoding.cs new file mode 100644 index 0000000..f214114 --- /dev/null +++ b/pk3DS.Core/CTR/Images/XLIMEncoding.cs @@ -0,0 +1,20 @@ +namespace pk3DS.Core.CTR +{ + public enum XLIMEncoding : byte + { + L8 = 0x00, // 8 Luminance + A8 = 0x01, // 8 Alpha + LA4 = 0x02, // 8 Luminance + Alpha + LA8 = 0x03, // 16 Luminance + Alpha + HILO8 = 0x04, // 16 ? + RGB565 = 0x05, // 16 Color + RGBX8 = 0x06, // 24 Color + RGB5A1 = 0x07, // 16 Color + Alpha + RGBA4 = 0x08, // 16 Color + Alpha + RGBA8 = 0x09, // 32 Color + Alpha + ETC1 = 0x0A, // 4 Color + ETC1A4 = 0x0B, // 8 Color + Alpha + L4 = 0x0C, // 4 Luminance + A4 = 0x0D, // 4 Alpha + } +} \ No newline at end of file diff --git a/pk3DS.Core/CTR/Images/XLIMOrientation.cs b/pk3DS.Core/CTR/Images/XLIMOrientation.cs new file mode 100644 index 0000000..3426c49 --- /dev/null +++ b/pk3DS.Core/CTR/Images/XLIMOrientation.cs @@ -0,0 +1,12 @@ +using System; + +namespace pk3DS.Core.CTR +{ + [Flags] + public enum XLIMOrientation : byte + { + None = 0, + Rotate90 = 4, + Transpose = 8, + } +} \ No newline at end of file diff --git a/pk3DS.Core/CTR/Images/XLIMOrienter.cs b/pk3DS.Core/CTR/Images/XLIMOrienter.cs new file mode 100644 index 0000000..f37e0d2 --- /dev/null +++ b/pk3DS.Core/CTR/Images/XLIMOrienter.cs @@ -0,0 +1,112 @@ +using static pk3DS.Core.CTR.Images.XLIMUtil; + +namespace pk3DS.Core.CTR +{ + public class XLIMOrienter + { + readonly XLIMOrientation _orientation; + + public uint Width { get; } + public uint Height { get; } + public uint PanelsPerWidth { get; } + + public XLIMOrienter(int width, int height, XLIMOrientation orientation) + { + Width = (uint)nlpo2(gcm(width, 8)); + Height = (uint)nlpo2(gcm(height, 8)); + + uint stride = orientation == XLIMOrientation.None ? Width : Height; + PanelsPerWidth = (uint)gcm((int)stride, 8) / 8; + + _orientation = orientation; + } + + public Coordinate Get(uint i) + { + d2xy(i & 0x3F, out uint x, out uint y); + + // Shift Tile Coordinate into Tilemap + var tile = i >> 6; + x |= (tile % PanelsPerWidth) << 3; + y |= (tile / PanelsPerWidth) << 3; + + var coord = new Coordinate(x, y); + if (_orientation.HasFlag(XLIMOrientation.Rotate90)) + coord.Rotate90(Height); + if (_orientation.HasFlag(XLIMOrientation.Transpose)) + coord.Transpose(); + return coord; + } + + internal static uint C11(uint x) + { + x &= 0x55555555; // x = -f-e -d-c -b-a -9-8 -7-6 -5-4 -3-2 -1-0 + x = (x ^ (x >> 1)) & 0x33333333; // x = --fe --dc --ba --98 --76 --54 --32 --10 + x = (x ^ (x >> 2)) & 0x0f0f0f0f; // x = ---- fedc ---- ba98 ---- 7654 ---- 3210 + x = (x ^ (x >> 4)) & 0x00ff00ff; // x = ---- ---- fedc ba98 ---- ---- 7654 3210 + x = (x ^ (x >> 8)) & 0x0000ffff; // x = ---- ---- ---- ---- fedc ba98 7654 3210 + return x; + } + + // Morton Translation + + /// + /// Combines X/Y Coordinates to a decimal ordinate. + /// + /// + /// + /// + internal static uint xy2d(uint x, uint y) + { + x &= 0x0000ffff; + y &= 0x0000ffff; + x |= x << 8; + y |= y << 8; + x &= 0x00ff00ff; + y &= 0x00ff00ff; + x |= x << 4; + y |= y << 4; + x &= 0x0f0f0f0f; + y &= 0x0f0f0f0f; + x |= x << 2; + y |= y << 2; + x &= 0x33333333; + y &= 0x33333333; + x |= x << 1; + y |= y << 1; + x &= 0x55555555; + y &= 0x55555555; + return x | (y << 1); + } + + /// + /// Decimal Ordinate In to X / Y Coordinate Out + /// + /// Loop integer which will be decoded to X/Y + /// Output X coordinate + /// Output Y coordinate + internal static void d2xy(uint d, out uint x, out uint y) + { + x = d; + y = x >> 1; + x &= 0x55555555; + y &= 0x55555555; + x |= x >> 1; + y |= y >> 1; + x &= 0x33333333; + y &= 0x33333333; + x |= x >> 2; + y |= y >> 2; + x &= 0x0f0f0f0f; + y &= 0x0f0f0f0f; + x |= x >> 4; + y |= y >> 4; + x &= 0x00ff00ff; + y &= 0x00ff00ff; + x |= x >> 8; + y |= y >> 8; + x &= 0x0000ffff; + y &= 0x0000ffff; + } + } +} \ No newline at end of file diff --git a/pk3DS.Core/CTR/Images/XLIMUtil.cs b/pk3DS.Core/CTR/Images/XLIMUtil.cs new file mode 100644 index 0000000..8698bfd --- /dev/null +++ b/pk3DS.Core/CTR/Images/XLIMUtil.cs @@ -0,0 +1,31 @@ +namespace pk3DS.Core.CTR.Images +{ + public static class XLIMUtil + { + /// + /// Greatest common multiple (to round up) + /// + /// Number to round-up. + /// Multiple to round-up to. + /// Rounded up number. + internal static int gcm(int n, int m) + { + return (n + m - 1) / m * m; + } + /// + /// Next Largest Power of 2 + /// + /// Input to round up to next 2^n + /// 2^n > x && x > 2^(n-1) + internal static int nlpo2(int x) + { + x--; // comment out to always take the next biggest power of two, even if x is already a power of two + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return x + 1; + } + } +} diff --git a/pk3DS.Core/CTR/SMDH.cs b/pk3DS.Core/CTR/SMDH.cs index 7929e35..c7d4e84 100644 --- a/pk3DS.Core/CTR/SMDH.cs +++ b/pk3DS.Core/CTR/SMDH.cs @@ -147,7 +147,7 @@ public class SmallIcon // 24x24 public SmallIcon(BinaryReader br) { Bytes = br.ReadBytes(0x480); - Icon = BCLIM.getIMG(24, 24, Bytes, 0x5); + Icon = ImageUtil.GetBitmap(Bytes, 24, 24); } public void Write(BinaryWriter bw) { @@ -157,7 +157,7 @@ public bool ChangeIcon(Bitmap img) { if (img.Width != Icon.Width || img.Height != Icon.Height) return false; Icon = img; - Bytes = BCLIM.getPixelData(Icon, 0x5); + Bytes = ImageUtil.GetPixelData(Icon); return true; } } @@ -169,7 +169,7 @@ public class LargeIcon // 48x48 public LargeIcon(BinaryReader br) { Bytes = br.ReadBytes(0x1200); - Icon = BCLIM.getIMG(48, 48, Bytes, 0x5); + Icon = ImageUtil.GetBitmap(Bytes, 48, 48); } public void Write(BinaryWriter bw) { @@ -179,7 +179,7 @@ public bool ChangeIcon(Bitmap img) { if (img.Width != Icon.Width || img.Height != Icon.Height) return false; Icon = img; - Bytes = BCLIM.getPixelData(Icon, 0x5); + Bytes = ImageUtil.GetPixelData(Icon); return true; } } diff --git a/pk3DS.Core/ImageUtil.cs b/pk3DS.Core/ImageUtil.cs index a9cf65c..27de836 100644 --- a/pk3DS.Core/ImageUtil.cs +++ b/pk3DS.Core/ImageUtil.cs @@ -3,6 +3,8 @@ using System.Drawing.Imaging; using System.Runtime.InteropServices; using pk3DS.Core.CTR; +using pk3DS.Core.CTR.Images; +using static pk3DS.Core.CTR.Images.XLIMUtil; namespace pk3DS.Core { @@ -17,7 +19,7 @@ public static class ImageUtil /// Image data /// Crop the image area to the actual dimensions /// Human visible data - public static Bitmap GetBitmap(this BFLIM bflim, bool crop = true) + public static Bitmap GetBitmap(this BXLIM bflim, bool crop = true) { if (bflim.Format == XLIMEncoding.ETC1 || bflim.Format == XLIMEncoding.ETC1A4) return GetBitmapETC(bflim, crop); @@ -28,25 +30,33 @@ public static Bitmap GetBitmap(byte[] data, int width, int height, int stride = { return new Bitmap(width, height, stride, format, Marshal.UnsafeAddrOfPinnedArrayElement(data, 0)); } - - public static Bitmap GetBitmapETC(BCLIM.CLIM bclim, bool crop = true) => GetBitmapETC(bclim, bclim.Data, bclim.FileFormat == 0x0B, crop); - public static Bitmap GetBitmapETC(BFLIM bflim, bool crop = true) => GetBitmapETC(bflim, bflim.PixelData, bflim.Footer.Format == XLIMEncoding.ETC1A4, crop); - - private static Bitmap GetBitmapETC(IXLIM bflim, byte[] data, bool etc1a4, bool crop = true) + public static byte[] GetPixelData(Bitmap bitmap) { + var argbData = new byte[bitmap.Width * bitmap.Height * 4]; + var bd = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat); + Marshal.Copy(bd.Scan0, argbData, 0, bitmap.Width * bitmap.Height * 4); + bitmap.UnlockBits(bd); + return argbData; + } + + public static Bitmap GetBitmapETC(BXLIM bxlim, bool crop = true) + { + bool etc1a4 = bxlim.Footer.Format == XLIMEncoding.ETC1A4; + byte[] data = bxlim.PixelData; ETC1.CheckETC1Lib(); + try { - var width = Math.Max(BCLIM.nlpo2(bflim.Width), 16); - var height = Math.Max(BCLIM.nlpo2(bflim.Height), 16); + var width = Math.Max(nlpo2(bxlim.Width), 16); + var height = Math.Max(nlpo2(bxlim.Height), 16); Bitmap img = new Bitmap(width, height); - img = DecodeETC(bflim, img, data, etc1a4); - return crop ? CropBMP(bflim, img) : img; + img = DecodeETC(bxlim, img, data, etc1a4); + return crop ? CropBMP(bxlim, img) : img; } catch { return null; } } - public static Bitmap CropBMP(IXLIM bclim, Bitmap img) + public static Bitmap CropBMP(IXLIMHeader bclim, Bitmap img) { Rectangle cropRect = new Rectangle(0, 0, bclim.Width, bclim.Height); Bitmap src = img; @@ -62,7 +72,7 @@ public static Bitmap CropBMP(IXLIM bclim, Bitmap img) return target; } - private static Bitmap DecodeETC(IXLIM bclim, Bitmap img, byte[] textureData, bool etc1A4) + private static Bitmap DecodeETC(IXLIMHeader 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. @@ -116,10 +126,10 @@ private static Bitmap DecodeETC(IXLIM bclim, Bitmap img, byte[] textureData, boo 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) + 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(BCLIM.nlpo2(bclim.Height), 16); x++) + 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. @@ -134,10 +144,10 @@ private static Bitmap DecodeETC(IXLIM bclim, Bitmap img, byte[] textureData, boo } 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) + 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(BCLIM.nlpo2(bclim.Height), 16); x++) + 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. diff --git a/pk3DS/Subforms/Gen6/TitleScreenEditor6.cs b/pk3DS/Subforms/Gen6/TitleScreenEditor6.cs index a12931a..4e928a5 100644 --- a/pk3DS/Subforms/Gen6/TitleScreenEditor6.cs +++ b/pk3DS/Subforms/Gen6/TitleScreenEditor6.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Windows.Forms; +using pk3DS.Core; namespace pk3DS { @@ -113,18 +114,8 @@ private void changeFile(object sender, EventArgs e) // Load file byte[] data = darc.Data.Skip((int)(darc.Entries[entry].DataOffset - darc.Header.FileDataOffset)).Take((int)darc.Entries[entry].DataLength).ToArray(); - BCLIM.CLIM bclim = BCLIM.analyze(data, filename); - Image img = BCLIM.getIMG(bclim); - - Rectangle cropRect = new Rectangle(0, 0, bclim.Width, bclim.Height); - Bitmap CropBMP = new Bitmap(cropRect.Width, cropRect.Height); - using (Graphics g = Graphics.FromImage(CropBMP)) - { - g.DrawImage(img, - new Rectangle(0, 0, CropBMP.Width, CropBMP.Height), - cropRect, - GraphicsUnit.Pixel); - } + BCLIM bclim = BCLIM.analyze(data, filename); + Image CropBMP = bclim.GetBitmap(); PB_Image.Image = CropBMP; // store image locally for saving if need be