summaryrefslogtreecommitdiff
path: root/NW4RTools/Texture.cs
diff options
context:
space:
mode:
Diffstat (limited to 'NW4RTools/Texture.cs')
-rw-r--r--NW4RTools/Texture.cs289
1 files changed, 289 insertions, 0 deletions
diff --git a/NW4RTools/Texture.cs b/NW4RTools/Texture.cs
new file mode 100644
index 0000000..954852d
--- /dev/null
+++ b/NW4RTools/Texture.cs
@@ -0,0 +1,289 @@
+using System;
+using System.Drawing;
+using System.Drawing.Imaging;
+
+namespace NW4RTools {
+ public class Texture {
+ public struct TextureFormatInfo {
+ public int TexelWidth, TexelHeight, NybblesPerPixel;
+
+ public const int TexelByteSize = 32;
+
+ private TextureFormatInfo(int tWidth, int tHeight, int npp) {
+ TexelWidth = tWidth;
+ TexelHeight = tHeight;
+ NybblesPerPixel = npp;
+ }
+
+ static TextureFormatInfo() {
+ I4 = new TextureFormatInfo(8, 8, 1);
+ I8 = new TextureFormatInfo(8, 4, 2);
+ IA4 = new TextureFormatInfo(8, 4, 2);
+ IA8 = new TextureFormatInfo(4, 4, 4);
+ RGB565 = new TextureFormatInfo(4, 4, 4);
+ RGB5A3 = new TextureFormatInfo(4, 4, 4);
+ RGBA8 = new TextureFormatInfo(4, 4, 8);
+ C4 = new TextureFormatInfo(8, 8, 1);
+ C8 = new TextureFormatInfo(8, 4, 2);
+ C14X2 = new TextureFormatInfo(4, 4, 4);
+ CMPR = new TextureFormatInfo(8, 8, 1);
+ }
+
+ public static TextureFormatInfo GetFormat(TextureFormat format) {
+ switch (format) {
+ case TextureFormat.I4:
+ return I4;
+ case TextureFormat.I8:
+ return I8;
+ case TextureFormat.IA4:
+ return IA4;
+ case TextureFormat.IA8:
+ return IA8;
+ case TextureFormat.RGB565:
+ return RGB565;
+ case TextureFormat.RGB5A3:
+ return RGB5A3;
+ case TextureFormat.RGBA8:
+ return RGBA8;
+ case TextureFormat.C4:
+ return C4;
+ case TextureFormat.C8:
+ return C8;
+ case TextureFormat.C14X2:
+ return C14X2;
+ case TextureFormat.CMPR:
+ return CMPR;
+ default:
+ throw new ArgumentOutOfRangeException("unknown texture format");
+ }
+ }
+
+ public static readonly TextureFormatInfo I4, I8, IA4, IA8, RGB565, RGB5A3, RGBA8, C4, C8, C14X2,
+ CMPR;
+ }
+
+
+
+ public Bitmap BaseImage;
+
+ public TextureFormat Format;
+ public UInt32 MipMapCount;
+ public float MinLOD, MaxLOD;
+
+ public Texture() {
+ }
+
+
+
+
+ public static int GetDataSize(int width, int height, TextureFormat format) {
+ var info = TextureFormatInfo.GetFormat(format);
+
+ // align width, height up
+ width = Misc.AlignUp(width, info.TexelWidth);
+ height = Misc.AlignUp(height, info.TexelHeight);
+
+ return width * height * info.NybblesPerPixel / 2;
+ }
+
+ unsafe public void ImportData(byte[] imgdata, int width, int height, TextureFormat format) {
+ var image = new Bitmap(width, height, PixelFormat.Format32bppArgb);
+ var bits = image.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, image.PixelFormat);
+
+ var info = TextureFormatInfo.GetFormat(format);
+ int blkWidth = info.TexelWidth;
+ int blkHeight = info.TexelHeight;
+
+ /*int blkNumY = 0;
+
+ for (int blkY = 0; blkY < height; blkY += blkHeight) {
+ byte* pBlkY = (byte*)bits.Scan0 + (blkY * bits.Stride);
+
+ int blkNumX = 0;
+
+ for (int blkX = 0; blkX < width; blkX += blkWidth) {
+ byte* pBlk = pBlkY + (blkX * sizeof(uint));
+
+ for (int y = 0; y < blkHeight; y++) {
+ if ((blkY + y) >= height)
+ break;
+
+ byte* pRow = pBlk + (y * bits.Stride);
+
+ for (int x = 0; x < blkWidth; x++) {
+ if ((blkX + x) >= width)
+ break;
+
+ uint* pPixel = (uint*)pRow + x;
+
+ }
+ }
+
+ blkNumX++;
+ }
+
+ blkNumY++;
+ }*/
+
+ // What I'm missing overall: IA4, IA8, RGBA8, all the palette ones
+
+ // this thing probably breaks on textures which aren't multiples of the texel width/height, too
+ // oh well
+
+ var data = new InputStream(imgdata);
+
+ switch (format) {
+ case TextureFormat.RGB565:
+ case TextureFormat.RGB5A3:
+ case TextureFormat.I4:
+ case TextureFormat.I8:
+ // I4 is stupid and breaks my parser. I'll keep some state here
+ bool alreadyHaveI4Nybble = false;
+ byte currentI4Byte = 0;
+
+ for (int y = 0; y < height; y += blkHeight) {
+ int yMax = y + blkHeight;
+ if (yMax > height)
+ yMax = height;
+
+ for (int x = 0; x < width; x += blkWidth) {
+ int xMax = x + blkWidth;
+ if (xMax > width)
+ xMax = width;
+
+ for (int y1 = y; y1 < yMax; y1++) {
+ byte *pRow = (byte*)bits.Scan0 + (y1 * bits.Stride);
+
+ for (int x1 = x; x1 < xMax; x1++) {
+ uint *pPixel = (uint*)pRow + x1;
+
+ if (format == TextureFormat.RGB565) {
+ ushort val = data.ReadUInt16();
+ uint r = (uint)(val & 0xF800) >> 8;
+ uint g = (uint)(val & 0x7e0) >> 3;
+ uint b = (uint)(val & 0x1f) << 3;
+ *pPixel = 0xFF000000 | (r << 16) | (g << 8) | b;
+
+ } else if (format == TextureFormat.RGB5A3) {
+ ushort val = data.ReadUInt16();
+ if ((val & 0x8000) == 0) {
+ uint a = (uint)(val & 0x7000) >> 3;
+ uint r = (uint)(val & 0xF00) >> 4;
+ uint g = (uint)(val & 0xF0);
+ uint b = (uint)(val & 0xF) << 4;
+ *pPixel = (a << 24) | (r << 16) | (g << 8) | b;
+ } else {
+ uint r = (uint)(val & 0x7c00) >> 7;
+ uint g = (uint)(val & 0x3e0) >> 2;
+ uint b = (uint)(val & 0x1f) << 3;
+ *pPixel = 0xFF000000 | (r << 16) | (g << 8) | b;
+ }
+
+ } else if (format == TextureFormat.I4) {
+ if (!alreadyHaveI4Nybble) {
+ currentI4Byte = data.ReadByte();
+ uint val = (uint)currentI4Byte & 0xF0;
+ *pPixel = 0xFF000000 | (val << 16) | (val << 8) | val;
+ } else {
+ uint val = (uint)(currentI4Byte & 0xF) << 4;
+ *pPixel = 0xFF000000 | (val << 16) | (val << 8) | val;
+ }
+
+ alreadyHaveI4Nybble = !alreadyHaveI4Nybble;
+
+ } else if (format == TextureFormat.I8) {
+ byte val = data.ReadByte();
+ *pPixel = 0xFF000000 | (uint)(val << 16) | (uint)(val << 8) | val;
+ }
+ }
+ }
+ }
+ }
+
+ break;
+
+ case TextureFormat.CMPR:
+ // this code is messy
+
+ ushort[] rawClrArray = new ushort[4];
+ uint[] clrArray = new uint[4];
+
+ for (int y = 0; y < height; y += 8) {
+ for (int x = 0; x < width; x += 8) {
+ for (int iBlockY = 0; iBlockY < 2; iBlockY++) {
+ for (int iBlockX = 0; iBlockX < 2; iBlockX++) {
+ // decode a block
+ rawClrArray[0] = data.ReadUInt16();
+ rawClrArray[1] = data.ReadUInt16();
+
+ bool hasAlpha = false;
+ if (rawClrArray[0] > rawClrArray[1]) {
+ rawClrArray[2] = CMPRAvgColor(2, 1, rawClrArray[0], rawClrArray[1]);
+ rawClrArray[3] = CMPRAvgColor(1, 2, rawClrArray[0], rawClrArray[1]);
+ } else {
+ rawClrArray[2] = CMPRAvgColor(1, 1, rawClrArray[0], rawClrArray[1]);
+ rawClrArray[3] = rawClrArray[1];
+ hasAlpha = true;
+ }
+
+ for (int i = 0; i < 4; i++) {
+ byte r = (byte)(((rawClrArray[i] >> 11) & 0x1F) << 3);
+ byte g = (byte)(((rawClrArray[i] >> 5) & 0x3F) << 2);
+ byte b = (byte)((rawClrArray[i] & 0x1F) << 3);
+ byte a = (byte)(((i == 3) && hasAlpha) ? 0 : 255);
+
+ clrArray[i] = (uint)(a << 24) | (uint)(r << 16) | (uint)(g << 8) | b;
+ }
+
+ for (int inY = 0; inY < 4; inY++) {
+ byte* pRow = (byte*)bits.Scan0 + ((y + (iBlockY * 4) + inY) * bits.Stride);
+ byte val = data.ReadByte();
+
+ for (int inX = 0; inX < 4; inX++) {
+ uint *pPixel = (uint*)pRow + x + (iBlockX * 4) + inX;
+ *pPixel = clrArray[(val >> 6) & 3];
+ val <<= 2;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ break;
+
+ default:
+ Console.WriteLine("Unsupported texture format: {0}", format);
+ break;
+ }
+
+
+ image.UnlockBits(bits);
+
+ BaseImage = image;
+ Format = format;
+ }
+
+ private static ushort CMPRAvgColor(ushort w0, ushort w1, ushort c0, ushort c1) {
+ uint result;
+
+ ushort a0 = (ushort)(c0 >> 11);
+ ushort a1 = (ushort)(c1 >> 11);
+ uint a = (uint)((w0*a0 + w1*a1) / (w0+w1));
+ result = (a << 11) & 0xffff;
+
+ a0 = (ushort)((c0 >> 5) & 63);
+ a1 = (ushort)((c1 >> 5) & 63);
+ a = (uint)((w0 * a0 + w1 * a1) / (w0 + w1));
+ result |= ((a << 5) & 0xffff);
+
+ a0 = (ushort)(c0 & 31);
+ a1 = (ushort)(c1 & 31);
+ a = (uint)((w0 * a0 + w1 * a1) / (w0 + w1));
+ result |= a;
+
+ return (ushort)result;
+ }
+ }
+}
+