mirror of
https://github.com/kwsch/pk3DS.git
synced 2026-10-01 21:36:43 -05:00
Rearranging
need to make bclim code generic (bmp data -> encoded swizzle) CLIM is now
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
67
pk3DS.Core/CTR/Images/BXLIM.cs
Normal file
67
pk3DS.Core/CTR/Images/BXLIM.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
pk3DS.Core/CTR/Images/CLIMHeader.cs
Normal file
31
pk3DS.Core/CTR/Images/CLIMHeader.cs
Normal 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
|
||||
}
|
||||
}
|
||||
25
pk3DS.Core/CTR/Images/Coordinate.cs
Normal file
25
pk3DS.Core/CTR/Images/Coordinate.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
pk3DS.Core/CTR/Images/FLIMHeader.cs
Normal file
30
pk3DS.Core/CTR/Images/FLIMHeader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
176
pk3DS.Core/CTR/Images/PixelConverter.cs
Normal file
176
pk3DS.Core/CTR/Images/PixelConverter.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
20
pk3DS.Core/CTR/Images/XLIMEncoding.cs
Normal file
20
pk3DS.Core/CTR/Images/XLIMEncoding.cs
Normal 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
|
||||
}
|
||||
}
|
||||
12
pk3DS.Core/CTR/Images/XLIMOrientation.cs
Normal file
12
pk3DS.Core/CTR/Images/XLIMOrientation.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace pk3DS.Core.CTR
|
||||
{
|
||||
[Flags]
|
||||
public enum XLIMOrientation : byte
|
||||
{
|
||||
None = 0,
|
||||
Rotate90 = 4,
|
||||
Transpose = 8,
|
||||
}
|
||||
}
|
||||
112
pk3DS.Core/CTR/Images/XLIMOrienter.cs
Normal file
112
pk3DS.Core/CTR/Images/XLIMOrienter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
pk3DS.Core/CTR/Images/XLIMUtil.cs
Normal file
31
pk3DS.Core/CTR/Images/XLIMUtil.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user