diff options
Diffstat (limited to 'NW4RTools/Texture.cs')
-rw-r--r-- | NW4RTools/Texture.cs | 289 |
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; + } + } +} + |