Rearranging

need to make bclim code generic (bmp data -> encoded swizzle)
CLIM is now
This commit is contained in:
Kurt
2018-01-01 20:29:26 -08:00
parent eb69d46880
commit a31571d1d6
15 changed files with 681 additions and 836 deletions

View File

@@ -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<CLIMHeader>();
}
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;
}
/// <summary>
/// Greatest common multiple (to round up)
/// </summary>
/// <param name="n">Number to round-up.</param>
/// <param name="m">Multiple to round-up to.</param>
/// <returns>Rounded up number.</returns>
internal static int gcm(int n, int m)
{
return (n + m - 1) / m * m;
}
/// <summary>
/// Next Largest Power of 2
/// </summary>
/// <param name="x">Input to round up to next 2^n</param>
/// <returns>2^n > x && x > 2^(n-1) </returns>
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
/// <summary>
/// Combines X/Y Coordinates to a decimal ordinate.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Decimal Ordinate In to X / Y Coordinate Out
/// </summary>
/// <param name="d">Loop integer which will be decoded to X/Y</param>
/// <param name="x">Output X coordinate</param>
/// <param name="y">Output Y coordinate</param>
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);
}
}
}

View File

@@ -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<FLIM>();
}
/// <summary>
/// ARGB 32bpp
/// </summary>
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<uint> 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<XLIMEncoding> _32 = new HashSet<XLIMEncoding>
{
XLIMEncoding.RGBA8,
};
private static readonly HashSet<XLIMEncoding> _24 = new HashSet<XLIMEncoding>
{
XLIMEncoding.RGBX8,
};
private static readonly HashSet<XLIMEncoding> _16 = new HashSet<XLIMEncoding>
{
XLIMEncoding.LA8,
XLIMEncoding.HILO8,
XLIMEncoding.RGB565,
XLIMEncoding.RGB5A1,
XLIMEncoding.RGBA4,
};
private static readonly HashSet<XLIMEncoding> _8 = new HashSet<XLIMEncoding>
{
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<FLIMHeader>();
}
}
}

View File

@@ -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));
/// <summary>
/// ARGB 32bpp
/// </summary>
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;
}
}
}

View File

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

View File

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

View File

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

View File

@@ -3,10 +3,14 @@
/// <summary>
/// <see cref="BCLIM"/> and <see cref="BFLIM"/> header interface.
/// </summary>
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; }
}
}

View File

@@ -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<uint> 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<XLIMEncoding> _32 = new HashSet<XLIMEncoding>
{
XLIMEncoding.RGBA8,
};
private static readonly HashSet<XLIMEncoding> _24 = new HashSet<XLIMEncoding>
{
XLIMEncoding.RGBX8,
};
private static readonly HashSet<XLIMEncoding> _16 = new HashSet<XLIMEncoding>
{
XLIMEncoding.LA8,
XLIMEncoding.HILO8,
XLIMEncoding.RGB565,
XLIMEncoding.RGB5A1,
XLIMEncoding.RGBA4,
};
private static readonly HashSet<XLIMEncoding> _8 = new HashSet<XLIMEncoding>
{
XLIMEncoding.L8,
XLIMEncoding.A8,
XLIMEncoding.LA4,
XLIMEncoding.ETC1A4,
};
}
}

View File

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

View File

@@ -0,0 +1,12 @@
using System;
namespace pk3DS.Core.CTR
{
[Flags]
public enum XLIMOrientation : byte
{
None = 0,
Rotate90 = 4,
Transpose = 8,
}
}

View File

@@ -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
/// <summary>
/// Combines X/Y Coordinates to a decimal ordinate.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Decimal Ordinate In to X / Y Coordinate Out
/// </summary>
/// <param name="d">Loop integer which will be decoded to X/Y</param>
/// <param name="x">Output X coordinate</param>
/// <param name="y">Output Y coordinate</param>
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;
}
}
}

View File

@@ -0,0 +1,31 @@
namespace pk3DS.Core.CTR.Images
{
public static class XLIMUtil
{
/// <summary>
/// Greatest common multiple (to round up)
/// </summary>
/// <param name="n">Number to round-up.</param>
/// <param name="m">Multiple to round-up to.</param>
/// <returns>Rounded up number.</returns>
internal static int gcm(int n, int m)
{
return (n + m - 1) / m * m;
}
/// <summary>
/// Next Largest Power of 2
/// </summary>
/// <param name="x">Input to round up to next 2^n</param>
/// <returns>2^n > x && x > 2^(n-1) </returns>
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;
}
}
}

View File

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

View File

@@ -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
/// <param name="bflim">Image data</param>
/// <param name="crop">Crop the image area to the actual dimensions</param>
/// <returns>Human visible data</returns>
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.

View File

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