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[] Images; public TextureFormat Format; 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); // SPECIAL CASE: return width * height * info.NybblesPerPixel / 2; } public int GetDataSize() { int size = 0; for (int i = 0; i < Images.Length; i++) { size += GetDataSize(i); } return size; } public int GetDataSize(int imageID) { return GetDataSize(Images[imageID].Width, Images[imageID].Height, Format); } unsafe public void ImportData(int imageID, 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: case TextureFormat.IA4: case TextureFormat.IA8: // 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; } else if (format == TextureFormat.IA4) { byte val = data.ReadByte(); uint i = (uint)(val >> 4); uint a = (uint)((val & 0xF0) << 4); *pPixel = (a << 24) | (i << 16) | (i << 8) | i; } else if (format == TextureFormat.IA8) { byte i = data.ReadByte(); byte a = data.ReadByte(); *pPixel = (uint)(a << 24) | (uint)(i << 16) | (uint)(i << 8) | i; } } } } } 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); Images[imageID] = 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; } } }