mirror of
https://github.com/kwsch/pk3DS.git
synced 2026-04-19 23:17:34 -05:00
174 lines
6.8 KiB
C#
174 lines
6.8 KiB
C#
using System;
|
|
using System.Drawing;
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Image Utility class using <see cref="System.Drawing"/>.
|
|
/// </summary>
|
|
public static class ImageUtil
|
|
{
|
|
/// <summary>
|
|
/// Converts a <see cref="BFLIM"/> to <see cref="Bitmap"/> via the 32bit/pixel data.
|
|
/// </summary>
|
|
/// <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 BXLIM bflim, bool crop = true)
|
|
{
|
|
if (bflim.Format is XLIMEncoding.ETC1 or XLIMEncoding.ETC1A4)
|
|
return GetBitmapETC(bflim, crop);
|
|
var data = bflim.GetImageData(crop);
|
|
return GetBitmap(data, bflim.Footer.Width, bflim.Footer.Height);
|
|
}
|
|
|
|
public static Bitmap GetBitmap(byte[] data, int width, int height, PixelFormat format = PixelFormat.Format32bppArgb)
|
|
{
|
|
var bmp = new Bitmap(width, height, format);
|
|
var bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, format);
|
|
var ptr = bmpData.Scan0;
|
|
Marshal.Copy(data, 0, ptr, data.Length);
|
|
bmp.UnlockBits(bmpData);
|
|
return bmp;
|
|
}
|
|
|
|
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(NextLargestPow2(bxlim.Width), 16);
|
|
var height = Math.Max(NextLargestPow2(bxlim.Height), 16);
|
|
var img = new Bitmap(width, height);
|
|
img = DecodeETC(bxlim, img, data, etc1a4);
|
|
return crop ? CropBMP(bxlim, img) : img;
|
|
}
|
|
catch { return null; }
|
|
}
|
|
|
|
public static Bitmap CropBMP(IXLIMHeader bclim, Bitmap img)
|
|
{
|
|
var cropRect = new Rectangle(0, 0, bclim.Width, bclim.Height);
|
|
var src = img;
|
|
var target = new Bitmap(cropRect.Width, cropRect.Height);
|
|
|
|
using (Graphics g = Graphics.FromImage(target))
|
|
{
|
|
g.DrawImage(src, new Rectangle(0, 0, target.Width, target.Height),
|
|
cropRect,
|
|
GraphicsUnit.Pixel);
|
|
}
|
|
Console.WriteLine($"Returning cropped {target.Width}");
|
|
return target;
|
|
}
|
|
|
|
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.
|
|
* https://github.com/xdanieldzd/Tharsis
|
|
*/
|
|
|
|
/* Get compressed data & handle to it */
|
|
|
|
//textureData = switchEndianness(textureData, 0x10);
|
|
ushort[] input = new ushort[textureData.Length / sizeof(ushort)];
|
|
Buffer.BlockCopy(textureData, 0, input, 0, textureData.Length);
|
|
GCHandle pInput = GCHandle.Alloc(input, GCHandleType.Pinned);
|
|
|
|
/* Marshal data around, invoke ETC1.dll for conversion, etc */
|
|
uint size1 = 0;
|
|
var w = (ushort)img.Width;
|
|
var h = (ushort)img.Height;
|
|
|
|
ETC1.ConvertETC1(IntPtr.Zero, ref size1, IntPtr.Zero, w, h, etc1A4); // true = etc1a4, false = etc1
|
|
uint[] output = new uint[size1];
|
|
GCHandle pOutput = GCHandle.Alloc(output, GCHandleType.Pinned);
|
|
ETC1.ConvertETC1(pOutput.AddrOfPinnedObject(), ref size1, pInput.AddrOfPinnedObject(), w, h, etc1A4);
|
|
pOutput.Free();
|
|
pInput.Free();
|
|
|
|
/* Unscramble if needed // could probably be done in ETC1Lib.dll, it's probably pretty ugly, but whatever... */
|
|
/* Non-square code blocks could need some cleanup, verification, etc. as well... */
|
|
uint[] finalized = new uint[output.Length];
|
|
|
|
// Act if it's square because BCLIM swizzling is stupid
|
|
Array.Copy(output, 0, finalized, 0, finalized.Length);
|
|
|
|
byte[] tmp = new byte[finalized.Length];
|
|
Buffer.BlockCopy(finalized, 0, tmp, 0, tmp.Length);
|
|
byte[] imgData = tmp;
|
|
|
|
for (int i = 0; i < w; i++)
|
|
{
|
|
for (int j = 0; j < h; j++)
|
|
{
|
|
int k = (j + (i * img.Height)) * 4;
|
|
img.SetPixel(i, j, Color.FromArgb(imgData[k + 3], imgData[k], imgData[k + 1], imgData[k + 2]));
|
|
}
|
|
}
|
|
if (bclim is BFLIM)
|
|
return img;
|
|
|
|
// Image is 13 instead of 12
|
|
// 24 34
|
|
img.RotateFlip(RotateFlipType.Rotate90FlipX);
|
|
if (w > h)
|
|
{
|
|
// Image is now in appropriate order, but the shifting is messed up. Let's fix that.
|
|
var img2 = new Bitmap(Math.Max(NextLargestPow2(bclim.Width), 16), Math.Max(NextLargestPow2(bclim.Height), 16));
|
|
for (int y = 0; y < Math.Max(NextLargestPow2(bclim.Width), 16); y += 8)
|
|
{
|
|
for (int x = 0; x < Math.Max(NextLargestPow2(bclim.Height), 16); x++)
|
|
{
|
|
for (int j = 0; j < 8; j++)
|
|
// Treat every 8 vertical pixels as 1 pixel for purposes of calculation, add to offset later.
|
|
{
|
|
int x1 = (x + (y / 8 * h)) % img2.Width; // Reshift x
|
|
int y1 = (x + (y / 8 * h)) / img2.Width * 8; // Reshift y
|
|
img2.SetPixel(x1, y1 + j, img.GetPixel(x, y + j)); // Reswizzle
|
|
}
|
|
}
|
|
}
|
|
return img2;
|
|
}
|
|
|
|
if (h > w)
|
|
{
|
|
var img2 = new Bitmap(Math.Max(NextLargestPow2(bclim.Width), 16), Math.Max(NextLargestPow2(bclim.Height), 16));
|
|
for (int y = 0; y < Math.Max(NextLargestPow2(bclim.Width), 16); y += 8)
|
|
{
|
|
for (int x = 0; x < Math.Max(NextLargestPow2(bclim.Height), 16); x++)
|
|
{
|
|
for (int j = 0; j < 8; j++)
|
|
// Treat every 8 vertical pixels as 1 pixel for purposes of calculation, add to offset later.
|
|
{
|
|
int x1 = x % img2.Width; // Reshift x
|
|
int y1 = (x + (y / 8 * h)) / img2.Width * 8; // Reshift y
|
|
img2.SetPixel(x1, y1 + j, img.GetPixel(x, y + j)); // Reswizzle
|
|
}
|
|
}
|
|
}
|
|
return img2;
|
|
}
|
|
|
|
return img;
|
|
}
|
|
} |