Migrate out etc handling

currently only works for bclim, dunno if I want to try and fix the
swizzling / standardize the reading
basically just outputs scrambled ETC1 images rather than throwing an
exception
This commit is contained in:
Kurt
2017-12-31 13:50:37 -08:00
parent 71b6d95ec5
commit abef306ffc
5 changed files with 153 additions and 113 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}
}
}

12
pk3DS.Core/CTR/IXLIM.cs Normal file
View File

@@ -0,0 +1,12 @@
namespace pk3DS.Core.CTR
{
/// <summary>
/// <see cref="BCLIM"/> and <see cref="BFLIM"/> header interface.
/// </summary>
public interface IXLIM
{
uint Magic { get; set; }
ushort Width { get; set; }
ushort Height { get; set; }
}
}

View File

@@ -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
/// <returns>Human visible data</returns>
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;
}
}
}