using System; using System.Collections.Generic; using System.IO; namespace Oni.Imaging { internal class DdsHeader { #region Private data private enum FOURCC { FOURCC_NONE = 0, FOURCC_DXT1 = 0x31545844 } [Flags] private enum DDS_FLAGS { DDSD_CAPS = 0x00000001, DDSD_HEIGHT = 0x00000002, DDSD_WIDTH = 0x00000004, DDSD_PITCH = 0x00000008, DDSD_PIXELFORMAT = 0x00001000, DDSD_MIPMAPCOUNT = 0x00020000, DDSD_LINEARSIZE = 0x00080000, DDSD_DEPTH = 0x00800000 } [Flags] private enum DDP_FLAGS { DDPF_RGB = 0x00000040, DDPF_FOURCC = 0x00000004, DDPF_ALPHAPIXELS = 0x00000001 } [Flags] private enum DDS_CAPS { DDSCAPS_TEXTURE = 0x00001000, DDSCAPS_MIPMAP = 0x00400000, DDSCAPS_COMPLEX = 0x00000008 } [Flags] private enum DDS_CAPS2 { DDSCAPS2_CUBEMAP = 0x00000200, DDSCAPS2_VOLUME = 0x00200000 } private const int DDS_MAGIC = 0x20534444; private DDS_FLAGS flags; private int height; private int width; private int linearSize; private int depth; private int mipmapCount; private DDP_FLAGS formatFlags; private FOURCC fourCC; private int rgbBitCount; private uint rBitMask; private uint gBitMask; private uint bBitMask; private uint aBitMask; private DDS_CAPS caps; private DDS_CAPS2 caps2; #endregion public int Width => width; public int Height => height; public int MipmapCount => mipmapCount; public SurfaceFormat GetSurfaceFormat() { if (fourCC == FOURCC.FOURCC_DXT1) return SurfaceFormat.DXT1; if (rgbBitCount == 32) { if (rBitMask == 0x00ff0000 && gBitMask == 0x0000ff00 && bBitMask == 0x000000ff) { if ((formatFlags & DDP_FLAGS.DDPF_ALPHAPIXELS) == 0) return SurfaceFormat.BGRX; if (aBitMask == 0xff000000) return SurfaceFormat.BGRA; } } else if (rgbBitCount == 16) { if (rBitMask == 0x7c00 && gBitMask == 0x03e0 && bBitMask == 0x001f) { if ((formatFlags & DDP_FLAGS.DDPF_ALPHAPIXELS) == 0) return SurfaceFormat.BGRX5551; if (aBitMask == 0x8000) return SurfaceFormat.BGRA5551; } else if (rBitMask == 0x0f00 && gBitMask == 0x00f0 && bBitMask == 0x000f) { if ((formatFlags & DDP_FLAGS.DDPF_ALPHAPIXELS) != 0) return SurfaceFormat.BGRA4444; } } throw new NotSupportedException(string.Format("Unsupported pixel format {0} {1} {2} {3} {4} {5} {6}", formatFlags, fourCC, rgbBitCount, rBitMask, gBitMask, bBitMask, aBitMask)); } public static DdsHeader Read(BinaryReader reader) { if (reader.ReadInt32() != DDS_MAGIC) throw new InvalidDataException("Not a DDS file"); var header = new DdsHeader(); if (reader.ReadInt32() != 124) throw new InvalidDataException("Invalid DDS header size"); header.flags = (DDS_FLAGS)reader.ReadInt32(); var requiredFlags = DDS_FLAGS.DDSD_CAPS | DDS_FLAGS.DDSD_HEIGHT | DDS_FLAGS.DDSD_WIDTH | DDS_FLAGS.DDSD_PIXELFORMAT; if ((header.flags & requiredFlags) != requiredFlags) throw new InvalidDataException(string.Format("Invalid DDS header flags ({0})", header.flags)); header.height = reader.ReadInt32(); header.width = reader.ReadInt32(); if (header.width == 0 || header.height == 0) throw new InvalidDataException("DDS file has 0 width or height"); header.linearSize = reader.ReadInt32(); header.depth = reader.ReadInt32(); if ((header.flags & DDS_FLAGS.DDSD_MIPMAPCOUNT) != 0) { header.mipmapCount = reader.ReadInt32(); } else { reader.ReadInt32(); header.mipmapCount = 1; } reader.Position += 44; if (reader.ReadInt32() != 32) throw new InvalidDataException("Invalid DDS pixel format size"); header.formatFlags = (DDP_FLAGS)reader.ReadInt32(); if ((header.formatFlags & DDP_FLAGS.DDPF_FOURCC) != 0) { header.fourCC = (FOURCC)reader.ReadInt32(); } else { reader.ReadInt32(); header.fourCC = FOURCC.FOURCC_NONE; } header.rgbBitCount = reader.ReadInt32(); header.rBitMask = reader.ReadUInt32(); header.gBitMask = reader.ReadUInt32(); header.bBitMask = reader.ReadUInt32(); header.aBitMask = reader.ReadUInt32(); header.caps = (DDS_CAPS)reader.ReadInt32(); header.caps2 = (DDS_CAPS2)reader.ReadInt32(); reader.Position += 12; if (header.fourCC == FOURCC.FOURCC_NONE) { if (header.rgbBitCount != 16 && header.rgbBitCount != 32) throw new NotSupportedException(string.Format("Unsupported RGB bit count {0}", header.rgbBitCount)); } else if (header.fourCC != FOURCC.FOURCC_DXT1) { throw new NotSupportedException(string.Format("Unsupported FOURCC {0}", header.fourCC)); } return header; } public static DdsHeader Create(IList surfaces) { var header = new DdsHeader(); int width = surfaces[0].Width; int height = surfaces[0].Height; var format = surfaces[0].Format; header.flags = DDS_FLAGS.DDSD_CAPS | DDS_FLAGS.DDSD_HEIGHT | DDS_FLAGS.DDSD_WIDTH | DDS_FLAGS.DDSD_PIXELFORMAT; header.width = width; header.height = height; header.caps = DDS_CAPS.DDSCAPS_TEXTURE; switch (format) { case SurfaceFormat.BGRA4444: header.formatFlags = DDP_FLAGS.DDPF_RGB | DDP_FLAGS.DDPF_ALPHAPIXELS; header.rgbBitCount = 16; header.aBitMask = 0xf000; header.rBitMask = 0x0f00; header.gBitMask = 0x00f0; header.bBitMask = 0x000f; break; case SurfaceFormat.BGRX5551: case SurfaceFormat.BGRA5551: header.formatFlags = DDP_FLAGS.DDPF_RGB | DDP_FLAGS.DDPF_ALPHAPIXELS; header.rgbBitCount = 16; header.aBitMask = 0x8000; header.rBitMask = 0x7c00; header.gBitMask = 0x03e0; header.bBitMask = 0x001f; break; case SurfaceFormat.BGRA: header.formatFlags = DDP_FLAGS.DDPF_RGB | DDP_FLAGS.DDPF_ALPHAPIXELS; header.rgbBitCount = 32; header.aBitMask = 0xff000000; header.rBitMask = 0x00ff0000; header.gBitMask = 0x0000ff00; header.bBitMask = 0x000000ff; break; case SurfaceFormat.BGRX: header.formatFlags = DDP_FLAGS.DDPF_RGB; header.rgbBitCount = 32; header.rBitMask = 0x00ff0000; header.gBitMask = 0x0000ff00; header.bBitMask = 0x000000ff; break; case SurfaceFormat.RGBA: header.formatFlags = DDP_FLAGS.DDPF_RGB | DDP_FLAGS.DDPF_ALPHAPIXELS; header.rgbBitCount = 32; header.aBitMask = 0x000000ff; header.rBitMask = 0x0000ff00; header.gBitMask = 0x00ff0000; header.bBitMask = 0xff000000; break; case SurfaceFormat.RGBX: header.formatFlags = DDP_FLAGS.DDPF_RGB; header.rgbBitCount = 32; header.rBitMask = 0x0000ff00; header.gBitMask = 0x00ff0000; header.bBitMask = 0xff000000; break; case SurfaceFormat.DXT1: header.formatFlags = DDP_FLAGS.DDPF_FOURCC; header.fourCC = FOURCC.FOURCC_DXT1; break; } switch (format) { case SurfaceFormat.BGRA4444: case SurfaceFormat.BGRX5551: case SurfaceFormat.BGRA5551: header.flags |= DDS_FLAGS.DDSD_PITCH; header.linearSize = width * 2; break; case SurfaceFormat.BGRX: case SurfaceFormat.BGRA: case SurfaceFormat.RGBA: case SurfaceFormat.RGBX: header.flags |= DDS_FLAGS.DDSD_PITCH; header.linearSize = width * 4; break; case SurfaceFormat.DXT1: header.flags |= DDS_FLAGS.DDSD_LINEARSIZE; header.linearSize = Math.Max(1, width / 4) * Math.Max(1, height / 4) * 8; break; } if (surfaces.Count > 1) { header.flags |= DDS_FLAGS.DDSD_MIPMAPCOUNT; header.mipmapCount = surfaces.Count; header.caps |= DDS_CAPS.DDSCAPS_COMPLEX | DDS_CAPS.DDSCAPS_MIPMAP; } return header; } public void Write(BinaryWriter writer) { writer.Write(DDS_MAGIC); writer.Write(124); writer.Write((int)flags); writer.Write(height); writer.Write(width); writer.Write(linearSize); writer.Write(depth); writer.Write(mipmapCount); writer.BaseStream.Seek(44, SeekOrigin.Current); writer.Write(32); writer.Write((int)formatFlags); writer.Write((int)fourCC); writer.Write(rgbBitCount); writer.Write(rBitMask); writer.Write(gBitMask); writer.Write(bBitMask); writer.Write(aBitMask); writer.Write((int)caps); writer.Write((int)caps2); writer.BaseStream.Seek(12, SeekOrigin.Current); } } }