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;
+ }
}
}