// Note that many common DDS reader/writers (including D3DX) swap the // the RED/BLUE masks for 10:10:10:2 formats. We assumme // below that the 'backwards' header mask is being used since it is most // likely written by D3DX. The more robust solution is to use the 'DX10' // header extension and specify the Format.R10G10B10A2_UNorm format directly // We do not support the following legacy Direct3D 9 formats: // BumpDuDv D3DFMT_V8U8, D3DFMT_Q8W8V8U8, D3DFMT_V16U16, D3DFMT_A2W10V10U10 // BumpLuminance D3DFMT_L6V5U5, D3DFMT_X8L8V8U8 // FourCC "UYVY" D3DFMT_UYVY // FourCC "YUY2" D3DFMT_YUY2 // FourCC 117 D3DFMT_CxV8U8 // ZBuffer D3DFMT_D16_LOCKABLE // FourCC 82 D3DFMT_D32F_LOCKABLE private static Format GetDXGIFormat(ref DDS.PixelFormat pixelFormat, DDSFlags flags, out ConversionFlags conversionFlags) { conversionFlags = ConversionFlags.None; int index = 0; for (index = 0; index < LegacyMaps.Length; ++index) { var entry = LegacyMaps[index]; if ((pixelFormat.Flags & entry.PixelFormat.Flags) != 0) { if ((entry.PixelFormat.Flags & DDS.PixelFormatFlags.FourCC) != 0) { if (pixelFormat.FourCC == entry.PixelFormat.FourCC) { break; } } else if ((entry.PixelFormat.Flags & DDS.PixelFormatFlags.Pal8) != 0) { if (pixelFormat.RGBBitCount == entry.PixelFormat.RGBBitCount) { break; } } else if (pixelFormat.RGBBitCount == entry.PixelFormat.RGBBitCount) { // RGB, RGBA, ALPHA, LUMINANCE if (pixelFormat.RBitMask == entry.PixelFormat.RBitMask && pixelFormat.GBitMask == entry.PixelFormat.GBitMask && pixelFormat.BBitMask == entry.PixelFormat.BBitMask && pixelFormat.ABitMask == entry.PixelFormat.ABitMask) { break; } } } } if (index >= LegacyMaps.Length) { return(Format.Unknown); } conversionFlags = LegacyMaps[index].ConversionFlags; var format = LegacyMaps[index].Format; if ((conversionFlags & ConversionFlags.Expand) != 0 && (flags & DDSFlags.NoLegacyExpansion) != 0) { return(Format.Unknown); } if ((format == Format.R10G10B10A2_UNorm) && (flags & DDSFlags.NoR10B10G10A2Fixup) != 0) { conversionFlags ^= ConversionFlags.Swizzle; } return(format); }
public void Read(GenericReader r) { var size = r.ReadUInt32(); if (size != 124) { throw new FileFormatException($"Invalid DDS file header size: {size}."); } dwFlags = (DDSFlags)r.ReadUInt32(); if (!Utils.ContainsBitFlags((int)dwFlags, (int)DDSFlags.Height, (int)DDSFlags.Width)) { throw new FileFormatException($"Invalid DDS file flags: {dwFlags}."); } dwHeight = r.ReadUInt32(); dwWidth = r.ReadUInt32(); dwPitchOrLinearSize = r.ReadUInt32(); dwDepth = r.ReadUInt32(); dwMipMapCount = r.ReadUInt32(); dwReserved1 = new uint[11]; for (var i = 0; i < 11; i++) { dwReserved1[i] = r.ReadUInt32(); } ddspf = new DDSPixelFormat(r); dwCaps = (DDSCaps)r.ReadUInt32(); if (!Utils.ContainsBitFlags((int)dwCaps, (int)DDSCaps.Texture)) { throw new FileFormatException($"Invalid DDS file caps: {dwCaps}."); } dwCaps2 = (DDSCaps2)r.ReadUInt32(); dwCaps3 = r.ReadUInt32(); dwCaps4 = r.ReadUInt32(); dwReserved2 = r.ReadUInt32(); }
private void SetTextureFormat(int mipMapCount, BitmapFormat format, BitmapFlags flags) { if (mipMapCount > 0) { MipMapCount = mipMapCount; Flags |= DDSFlags.MipMapCount; } else { MipMapCount = 0; } if (flags.HasFlag(BitmapFlags.Compressed)) { Flags |= DDSFlags.LinearSize; int blockSize = BitmapFormatUtils.GetBlockSize(format); int blockDimension = BitmapFormatUtils.GetBlockDimension(format); var nearestWidth = blockDimension * ((Height + (blockDimension - 1)) / blockDimension); var nearestHeight = blockDimension * ((Width + (blockDimension - 1)) / blockDimension);; PitchOrLinearSize = (nearestWidth * nearestHeight / 16) * blockSize; } else { Flags |= DDSFlags.Pitch; int bitsPerPixel = BitmapFormatUtils.GetBitsPerPixel(format); PitchOrLinearSize = (Width * bitsPerPixel + 7) / 8; } PixelFormat = new PixelFormat(format, flags); }
private void CreateHeaderFromType(int height, int width, int depth, int mipMapCount, BitmapFormat format, BitmapType type, BitmapFlags flags) { Height = height; Width = width; Flags |= DDSFlags.Height | DDSFlags.Width; switch (type) { case BitmapType.Texture2D: CreateHeaderTexture2D(mipMapCount, format, flags); break; case BitmapType.Texture3D: case BitmapType.Array: CreateHeaderVolume(mipMapCount, depth, format, flags); break; case BitmapType.CubeMap: CreateHeaderCubemap(mipMapCount, format, flags); break; } return; }
/// <summary> /// Read the content of the DDS header. /// </summary> /// <param name="reader"></param> /// <returns>File is valid</returns> public override bool Read(EndianReader reader) { reader.Format = EndianFormat.LittleEndian; var tag = reader.ReadInt32(); var size = reader.ReadInt32(); if (tag != 0x20534444 && size != Size) { return(false); } else { Flags = (DDSFlags)reader.ReadInt32(); Height = reader.ReadInt32(); Width = reader.ReadInt32(); PitchOrLinearSize = reader.ReadInt32(); Depth = reader.ReadInt32(); MipMapCount = reader.ReadInt32(); reader.ReadBlock(Reserved1, 0, 0x2C); if (!PixelFormat.Read(reader)) { return(false); } Caps = (DDSComplexityFlags)reader.ReadInt32(); Caps2 = (DDSSurfaceInfoFlags)reader.ReadInt32(); Caps3 = reader.ReadInt32(); Caps4 = reader.ReadInt32(); Reserved2 = reader.ReadInt32(); // Add more verifications here return(true); } }
private void CreateHeaderVolume(int mipMapCount, int depth, BitmapFormat format, BitmapFlags flags) { Flags |= DDSFlags.Depth; Caps |= DDSComplexityFlags.Complex; Caps2 |= DDSSurfaceInfoFlags.Volume; Depth = depth; SetTextureFormat(mipMapCount, format, flags); }
/// <summary> /// Reads a DDS file from disk. Image data is always returned as DXGI-Compliant /// format, therefore some old legacy formats will automatically be converted. /// </summary> /// <param name="fileName">File to load.</param> /// <param name="flags">Flags to control how the DDS data is loaded.</param> /// <returns>Loaded image data, or null if the data failed to load.</returns> public static DDSContainer Read(String fileName, DDSFlags flags = DDSFlags.None) { if (!File.Exists(fileName)) { return(null); } using (FileStream fs = File.OpenRead(fileName)) return(Read(fs, flags)); }
private static DDSHeader ReadHeader(BinaryReader input) { DDSHeader header = new DDSHeader(); header.DwSize = input.ReadInt32(); if (header.DwSize != 124) { throw new InvalidOperationException("Invalid dds header size."); } header.DwFlags = input.ReadInt32(); header.DwHeight = input.ReadInt32(); header.DwWidth = input.ReadInt32(); header.DwLinearSize = input.ReadInt32(); header.DwDepth = input.ReadInt32(); header.DwMipMapCount = input.ReadInt32(); header.DwAlphaBitDepth = input.ReadInt32(); header.DwReserved1 = new int[10]; for (int i = 0; i < 10; i++) { header.DwReserved1[i] = input.ReadInt32(); } header.ImageFormat = ReadImageFormat(input); header.DwCaps = input.ReadInt32(); header.DwCaps2 = input.ReadInt32(); header.DwCaps3 = input.ReadInt32(); header.DwCaps4 = input.ReadInt32(); header.DwTextureStage = input.ReadInt32(); int mipMaps = 1 + (int)System.Math.Ceiling(System.Math.Log(System.Math.Max(header.DwHeight, header.DwWidth)) / System.Math.Log(2)); DDSCaps cap = (DDSCaps)header.DwCaps; if ((cap & DDSCaps.MipMap) == DDSCaps.MipMap) { DDSFlags flag = (DDSFlags)header.DwFlags; if ((flag & DDSFlags.MipCount) != DDSFlags.MipCount) { header.DwMipMapCount = mipMaps; } } else { header.DwMipMapCount = 1; } return(header); }
private void CreateHeaderTexture2D(int mipMapCount, BitmapFormat format, BitmapFlags flags) { Depth = 0; if (mipMapCount > 0) { MipMapCount = 1 + mipMapCount; Flags |= DDSFlags.MipMapCount; Caps |= DDSComplexityFlags.MipMap | DDSComplexityFlags.Complex; } else { MipMapCount = 0; } SetTextureFormat(mipMapCount, format, flags); }
/// <summary> /// Generates a DDS Header, and if requires, a DX10 Header /// </summary> /// <param name="metaData">Meta Data</param> /// <param name="flags">Flags</param> /// <param name="header">DDS Header Output</param> /// <param name="dx10Header">DX10 Header Output</param> public static void GenerateDDSHeader(TexMetadata metaData, DDSFlags flags, out DDSHeader header, out DX10Header dx10Header) { // Check array size if (metaData.ArraySize > 1) { // Check if we have an array and whether we're cube maps/non-2D if (metaData.ArraySize != 6 || metaData.Dimension != TexDimension.TEXTURE2D || !metaData.IsCubeMap()) { // Texture1D arrays, Texture2D arrays, and Cubemap arrays must be stored using 'DX10' extended header flags |= DDSFlags.FORCEDX10EXT; } } // Check for DX10 Ext if (flags.HasFlag(DDSFlags.FORCEDX10EXTMISC2)) { flags |= DDSFlags.FORCEDX10EXT; } // Create DDS Header header = new DDSHeader { // Set Data Size = (uint)Marshal.SizeOf <DDSHeader>(), Flags = DDSHeader.HeaderFlags.TEXTURE, Caps = (uint)DDSHeader.SurfaceFlags.TEXTURE, PixelFormat = new DDSHeader.DDSPixelFormat(0, 0, 0, 0, 0, 0, 0, 0) }; // Create DX10 Header dx10Header = new DX10Header(); // Switch format switch (metaData.Format) { case DXGIFormat.R8G8B8A8UNORM: header.PixelFormat = PixelFormats.A8B8G8R8; break; case DXGIFormat.R16G16UNORM: header.PixelFormat = PixelFormats.G16R16; break; case DXGIFormat.R8G8UNORM: header.PixelFormat = PixelFormats.A8L8; break; case DXGIFormat.R16UNORM: header.PixelFormat = PixelFormats.L16; break; case DXGIFormat.R8UNORM: header.PixelFormat = PixelFormats.L8; break; case DXGIFormat.A8UNORM: header.PixelFormat = PixelFormats.A8; break; case DXGIFormat.R8G8B8G8UNORM: header.PixelFormat = PixelFormats.R8G8B8G8; break; case DXGIFormat.G8R8G8B8UNORM: header.PixelFormat = PixelFormats.G8R8G8B8; break; case DXGIFormat.BC1UNORM: header.PixelFormat = PixelFormats.DXT1; break; case DXGIFormat.BC2UNORM: header.PixelFormat = metaData.IsPMAlpha() ? (PixelFormats.DXT2) : (PixelFormats.DXT3); break; case DXGIFormat.BC3UNORM: header.PixelFormat = metaData.IsPMAlpha() ? (PixelFormats.DXT4) : (PixelFormats.DXT5); break; case DXGIFormat.BC4UNORM: header.PixelFormat = PixelFormats.BC4UNORM; break; case DXGIFormat.BC4SNORM: header.PixelFormat = PixelFormats.BC4SNORM; break; case DXGIFormat.BC5UNORM: header.PixelFormat = PixelFormats.BC5UNORM; break; case DXGIFormat.BC5SNORM: header.PixelFormat = PixelFormats.BC5SNORM; break; case DXGIFormat.B5G6R5UNORM: header.PixelFormat = PixelFormats.R5G6B5; break; case DXGIFormat.B5G5R5A1UNORM: header.PixelFormat = PixelFormats.A1R5G5B5; break; case DXGIFormat.R8G8SNORM: header.PixelFormat = PixelFormats.V8U8; break; case DXGIFormat.R8G8B8A8SNORM: header.PixelFormat = PixelFormats.Q8W8V8U8; break; case DXGIFormat.R16G16SNORM: header.PixelFormat = PixelFormats.V16U16; break; case DXGIFormat.B8G8R8A8UNORM: header.PixelFormat = PixelFormats.A8R8G8B8; break; case DXGIFormat.B8G8R8X8UNORM: header.PixelFormat = PixelFormats.X8R8G8B8; break; case DXGIFormat.B4G4R4A4UNORM: header.PixelFormat = PixelFormats.A4R4G4B4; break; case DXGIFormat.YUY2: header.PixelFormat = PixelFormats.YUY2; break; // Legacy D3DX formats using D3DFMT enum value as FourCC case DXGIFormat.R32G32B32A32FLOAT: header.PixelFormat.Flags = PixelFormats.DDSFOURCC; header.PixelFormat.FourCC = 116; // D3DFMTA32B32G32R32F break; case DXGIFormat.R16G16B16A16FLOAT: header.PixelFormat.Flags = PixelFormats.DDSFOURCC; header.PixelFormat.FourCC = 113; // D3DFMTA16B16G16R16F break; case DXGIFormat.R16G16B16A16UNORM: header.PixelFormat.Flags = PixelFormats.DDSFOURCC; header.PixelFormat.FourCC = 36; // D3DFMTA16B16G16R16 break; case DXGIFormat.R16G16B16A16SNORM: header.PixelFormat.Flags = PixelFormats.DDSFOURCC; header.PixelFormat.FourCC = 110; // D3DFMTQ16W16V16U16 break; case DXGIFormat.R32G32FLOAT: header.PixelFormat.Flags = PixelFormats.DDSFOURCC; header.PixelFormat.FourCC = 115; // D3DFMTG32R32F break; case DXGIFormat.R16G16FLOAT: header.PixelFormat.Flags = PixelFormats.DDSFOURCC; header.PixelFormat.FourCC = 112; // D3DFMTG16R16F break; case DXGIFormat.R32FLOAT: header.PixelFormat.Flags = PixelFormats.DDSFOURCC; header.PixelFormat.FourCC = 114; // D3DFMTR32F break; case DXGIFormat.R16FLOAT: header.PixelFormat.Flags = PixelFormats.DDSFOURCC; header.PixelFormat.FourCC = 111; // D3DFMTR16F break; default: break; } // Check for mips if (metaData.MipLevels > 0) { // Set flag header.Flags |= DDSHeader.HeaderFlags.MIPMAP; // Check size if (metaData.MipLevels > UInt16.MaxValue) { throw new ArgumentException(String.Format("Too many mipmaps: {0}. Max: {1}", metaData.MipLevels, UInt16.MaxValue)); } // Set header.MipMapCount = (uint)metaData.MipLevels; // Check count if (header.MipMapCount > 1) { header.Caps |= (uint)DDSHeader.SurfaceFlags.MIPMAP; } } // Switch Dimension switch (metaData.Dimension) { case TexDimension.TEXTURE1D: { // Check size if (metaData.Width > Int32.MaxValue) { throw new ArgumentException(String.Format("Image Width too large: {0}. Max: {1}", metaData.Width, Int32.MaxValue)); } // Set header.Width = (uint)metaData.Width; header.Height = header.Depth = 1; // Check size break; } case TexDimension.TEXTURE2D: { // Check size if (metaData.Width > Int32.MaxValue || metaData.Height > Int32.MaxValue) { throw new ArgumentException(String.Format("Image Width and/or Height too large: {0}x{1}. Max: {2}", metaData.Width, metaData.Height, Int32.MaxValue)); } // Set header.Width = (uint)metaData.Width; header.Height = (uint)metaData.Height; header.Depth = 1; // Check size break; } case TexDimension.TEXTURE3D: { // Check size if (metaData.Width > Int32.MaxValue || metaData.Height > Int32.MaxValue) { throw new ArgumentException(String.Format("Image Width and/or Height too large: {0}x{1}. Max: {2}", metaData.Width, metaData.Height, Int32.MaxValue)); } // Check size if (metaData.Depth > UInt16.MaxValue) { throw new ArgumentException(String.Format("Image Depth too large: {0}. Max: {1}", metaData.Depth, UInt16.MaxValue)); } // Set header.Flags |= DDSHeader.HeaderFlags.VOLUME; header.Caps2 |= 0x00200000; header.Width = (uint)metaData.Width; header.Height = (uint)metaData.Height; header.Depth = (uint)metaData.Depth; // Check size break; } default: throw new ArgumentException("Invalid Texture Dimension."); } // Calculate the Pitch ComputePitch(metaData.Format, metaData.Width, metaData.Height, out long rowPitch, out long slicePitch, CPFLAGS.NONE); // Validate results if (slicePitch > UInt32.MaxValue || rowPitch > UInt32.MaxValue) { throw new ArgumentException("Failed to calculate row and/or slice pitch, values returned were too large"); } // Check is it compressed if (IsCompressed(metaData.Format)) { header.Flags |= DDSHeader.HeaderFlags.LINEARSIZE; header.PitchOrLinearSize = (uint)slicePitch; } else { header.Flags |= DDSHeader.HeaderFlags.PITCH; header.PitchOrLinearSize = (uint)rowPitch; } // Check for do we need to create the DX10 Header if (header.PixelFormat.Size == 0) { // Check size if (metaData.ArraySize > UInt16.MaxValue) { throw new ArgumentException(String.Format("Array Size too large: {0}. Max: {1}", metaData.ArraySize, UInt16.MaxValue)); } // Set Pixel format header.PixelFormat = PixelFormats.DX10; // Set Data dx10Header.Format = metaData.Format; dx10Header.ResourceDimension = metaData.Dimension; dx10Header.MiscFlag = metaData.MiscFlags & ~TexMiscFlags.TEXTURECUBE; dx10Header.ArraySize = (uint)metaData.ArraySize; // Check for Cube Maps if (metaData.MiscFlags.HasFlag(TexMiscFlags.TEXTURECUBE)) { // Check array size, must be a multiple of 6 for cube maps if ((metaData.ArraySize % 6) != 0) { throw new ArgumentException("Array size must be a multiple of 6"); } // Set Flag dx10Header.MiscFlag |= TexMiscFlags.TEXTURECUBE; dx10Header.ArraySize /= 6; } // Check for mist flags if (flags.HasFlag(DDSFlags.FORCEDX10EXTMISC2)) { // This was formerly 'reserved'. D3DX10 and D3DX11 will fail if this value is anything other than 0 dx10Header.MiscFlags2 = (uint)metaData.MiscFlags2; } } }
/// <summary> /// Writes a DDS file to disk. Image data is expected to be 32-bit color data, if not then mipmaps are converted as necessary automatically without modifying input data. /// </summary> /// <param name="fileName">File to write to. If it doesn't exist, it will be created.</param> /// <param name="image">Single image to write.</param> /// <param name="texDim">Dimension of the texture to write.</param> /// <param name="flags">Flags to control how the DDS data is saved.</param> /// <returns>True if writing the data was successful, false if otherwise.</returns> public static bool Write(String fileName, Surface image, TextureDimension texDim, DDSFlags flags = DDSFlags.None) { if (!Directory.Exists(Path.GetDirectoryName(fileName))) { Directory.CreateDirectory(Path.GetDirectoryName(fileName)); } using (FileStream fs = File.Create(fileName)) return(Write(fs, image, texDim, flags)); }
/// <summary> /// Writes a DDS file to a stream. Image data is expected to be 32-bit color data, if not then mipmaps are converted as necessary automatically without modifying input data. /// </summary> /// <param name="output">Output stream.</param> /// <param name="mipChain">Single mipchain of images to write.</param> /// <param name="texDim">Dimension of the texture to write.</param> /// <param name="flags">Flags to control how the DDS data is saved.</param> /// <returns>True if writing the data was successful, false if otherwise.</returns> public static bool Write(Stream output, List <Surface> mipChain, TextureDimension texDim, DDSFlags flags = DDSFlags.None) { List <List <Surface> > mipChains = new List <List <Surface> >(1); mipChains.Add(mipChain); return(Write(output, mipChains, texDim, flags)); }
/// <summary> /// Writes a DDS file to a stream. Image data is expected to be 32-bit color data, if not then mipmaps are converted as necessary automatically without modifying input data. /// </summary> /// <param name="output">Output stream.</param> /// <param name="mipChains">Mipmap chains to write. Each mipmap chain represents a single face (so > 1 represents an array texture or a Cubemap). All faces must have /// equivalent dimensions and each chain must have the same number of mipmaps.</param> /// <param name="texDim">Dimension of the texture to write.</param> /// <param name="flags">Flags to control how the DDS data is saved.</param> /// <returns>True if writing the data was successful, false if otherwise.</returns> public static bool Write(Stream output, List <List <Surface> > mipChains, TextureDimension texDim, DDSFlags flags = DDSFlags.None) { if (mipChains == null || mipChains.Count == 0 || mipChains[0] == null || mipChains[0].Count == 0) { return(false); } //FreeImage doesn't support volume textures. if (texDim == TextureDimension.Three) { return(false); } //If texcube, must have multiples of 6, every 6 mipchains are a complete cubemap if (texDim == TextureDimension.Cube && (mipChains.Count % 6 != 0)) { return(false); } //FreeImage surfaces are always uncompressed and we expect 32-bit color, if not we'll convert. We'll export in whatever color order freeimage is in, //but we can force RGBA based on the flags List <MipChain> ddsMipChains = new List <MipChain>(mipChains.Count); bool forceRGBA = (flags & DDSFlags.ForceRgb) == DDSFlags.ForceRgb; bool isBGRAOrder = Surface.IsBGRAOrder; bool needToSwizzle = isBGRAOrder && forceRGBA; DXGIFormat format = (isBGRAOrder) ? DXGIFormat.B8G8R8A8_UNorm : DXGIFormat.R8G8B8A8_UNorm; if (forceRGBA) { format = DXGIFormat.R8G8B8A8_UNorm; } try { int mipCount = -1; foreach (List <Surface> fiMipChain in mipChains) { MipChain ddsMipChain = new MipChain(fiMipChain.Count); ddsMipChains.Add(ddsMipChain); if (mipCount == -1) { mipCount = fiMipChain.Count; } //All chains must have same # of mips if (mipCount != fiMipChain.Count) { return(false); } foreach (Surface fiMip in fiMipChain) { if (fiMip == null) { return(false); } //Validate dimension switch (texDim) { case TextureDimension.One: if (fiMip.Height > 1) { return(false); } break; case TextureDimension.Cube: if (fiMip.Width != fiMip.Height) { return(false); } break; } bool is32BitBitmap = fiMip.ImageType == ImageType.Bitmap && fiMip.ColorType == ImageColorType.RGBA && fiMip.BitsPerPixel == 32; if (is32BitBitmap) { //If no swizzling...just use the data directly if (!needToSwizzle) { ddsMipChain.Add(new MipData(fiMip)); } else { MipData newMip = new MipData(fiMip.Width, fiMip.Height, fiMip.Pitch); ImageHelper.CopyColorImageData(newMip.Data, newMip.RowPitch, 0, fiMip.DataPtr, fiMip.Pitch, 0, newMip.Width, newMip.Height, 1, true); ddsMipChain.Add(newMip); } } else { //Need to convert. Possible to map other DXGI formats to free image bitmaps (most likely RGBA floats), but we're keeping it simple. User can wrap surfaces //and use the general write method using (Surface converted = fiMip.Clone()) { if (!converted.ConvertTo(ImageConversion.To32Bits)) { return(false); } MipData newMip = new MipData(converted.Width, converted.Height, converted.Pitch); ImageHelper.CopyColorImageData(newMip.Data, newMip.RowPitch, 0, converted.DataPtr, converted.Pitch, 0, newMip.Width, newMip.Height, 1, needToSwizzle); ddsMipChain.Add(newMip); } } } } //Write out DDS return(Write(output, ddsMipChains, format, texDim, flags)); } finally { //Dispose of mip surfaces. If they own any data, it'll be cleaned up DisposeMipChains(ddsMipChains); } }
/// <summary> /// Writes a DDS file to disk. Image data is expected to be DXGI-compliant data, but an effort is made to write out D3D9-compatible headers when possible. /// </summary> /// <param name="fileName">File to write to. If it doesn't exist, it will be created.</param> /// <param name="mipChains">Mipmap chains to write. Each mipmap chain represents a single face (so > 1 represents an array texture or a Cubemap). All faces must have /// equivalent dimensions and each chain must have the same number of mipmaps.</param> /// <param name="format">DXGI format the image data is stored as.</param> /// <param name="texDim">Dimension of the texture to write.</param> /// <param name="flags">Flags to control how the DDS data is saved.</param> /// <returns>True if writing the data was successful, false if otherwise.</returns> public static bool Write(String fileName, List <MipChain> mipChains, DXGIFormat format, TextureDimension texDim, DDSFlags flags = DDSFlags.None) { if (!Directory.Exists(Path.GetDirectoryName(fileName))) { Directory.CreateDirectory(Path.GetDirectoryName(fileName)); } using (FileStream fs = File.Create(fileName)) return(Write(fs, mipChains, format, texDim, flags)); }
/// <summary> /// Reads DDS formatted data from a stream. Image data is always returned as DXGI-Compliant /// format, therefore some old legacy formats will automatically be converted. /// </summary> /// <param name="input">Input stream.</param> /// <param name="flags">Flags to control how the DDS data is loaded.</param> /// <returns>Loaded image data, or null if the data failed to load.</returns> public static DDSContainer Read(Stream input, DDSFlags flags = DDSFlags.None) { StreamTransferBuffer buffer = new StreamTransferBuffer(); Header header; Header10?headerExt; //Reads + validates header(s) if (!ReadHeader(input, buffer, out header, out headerExt)) { return(null); } //Gather up metadata List <MipChain> mipChains = null; DXGIFormat format = DXGIFormat.Unknown; TextureDimension texDim = TextureDimension.Two; ConversionFlags convFlags = ConversionFlags.None; bool legacyDword = (flags & DDSFlags.LegacyDword) == DDSFlags.LegacyDword ? true : false; int width = Math.Max((int)header.Width, 1); int height = Math.Max((int)header.Height, 1); int depth = Math.Max((int)header.Depth, 1); int mipCount = (int)header.MipMapCount; int arrayCount = 1; //Has extended header, a modern DDS if (headerExt.HasValue) { Header10 extendedHeader = headerExt.Value; arrayCount = (int)extendedHeader.ArraySize; format = extendedHeader.Format; switch (extendedHeader.ResourceDimension) { case D3D10ResourceDimension.Texture1D: { texDim = TextureDimension.One; if (height > 1 || depth > 1) { return(null); } } break; case D3D10ResourceDimension.Texture2D: { if ((extendedHeader.MiscFlags & Header10Flags.TextureCube) == Header10Flags.TextureCube) { //Specifies # of cubemaps, so to get total # of faces must multiple by 6 arrayCount *= 6; texDim = TextureDimension.Cube; } else { texDim = TextureDimension.Two; } if (depth > 1) { return(null); } } break; case D3D10ResourceDimension.Texture3D: { texDim = TextureDimension.Three; if (arrayCount > 1 || (header.Caps2 & HeaderCaps2.Volume) != HeaderCaps2.Volume) { return(null); } } break; } } else { //Otherwise, read legacy DDS and possibly convert data //Check volume flag if ((header.Caps2 & HeaderCaps2.Volume) == HeaderCaps2.Volume) { texDim = TextureDimension.Three; } else { //legacy DDS could not express 1D textures, so either a cubemap or a 2D non-array texture if ((header.Caps2 & HeaderCaps2.Cubemap) == HeaderCaps2.Cubemap) { //Must have all six faces. DirectX 8 and above always would write out all 6 faces if ((header.Caps2 & HeaderCaps2.Cubemap_AllFaces) != HeaderCaps2.Cubemap_AllFaces) { return(null); } arrayCount = 6; texDim = TextureDimension.Cube; } else { texDim = TextureDimension.Two; } } format = FormatConverter.DetermineDXGIFormat(header.PixelFormat, flags, out convFlags); } //Modify conversion flags, if necessary FormatConverter.ModifyConversionFormat(ref format, ref convFlags, flags); //If palette image, the palette will be the first thing int[] palette = null; if (FormatConverter.HasConversionFlag(convFlags, ConversionFlags.Pal8)) { palette = new int[256]; int palSize = palette.Length * sizeof(int); buffer.ReadBytes(input, palSize); if (buffer.LastReadByteCount != palSize) { return(null); } MemoryHelper.CopyBytes <int>(buffer.ByteArray, 0, palette, 0, palette.Length); } //Now read data based on available mip/arrays mipChains = new List <MipChain>(arrayCount); byte[] scanline = buffer.ByteArray; IntPtr scanlinePtr = buffer.Pointer; bool noPadding = (flags & DDSFlags.NoPadding) == DDSFlags.NoPadding ? true : false; bool isCompressed = FormatConverter.IsCompressed(format); bool errored = false; try { //Iterate over each array face... for (int i = 0; i < arrayCount; i++) { MipChain mipChain = new MipChain(mipCount); mipChains.Add(mipChain); //Iterate over each mip face... for (int mipLevel = 0; mipLevel < mipCount; mipLevel++) { //Calculate mip dimensions int mipWidth = width; int mipHeight = height; int mipDepth = depth; ImageHelper.CalculateMipmapLevelDimensions(mipLevel, ref mipWidth, ref mipHeight, ref mipDepth); //Compute pitch, based on MSDN programming guide which says PitchOrLinearSize is unreliable and to calculate based on format. //"real" mip width/height is the given mip width/height for all non-compressed, compressed images it will be smaller since each block //is a 4x4 region of pixels. int realMipWidth, realMipHeight, dstRowPitch, dstSlicePitch, bytesPerPixel; ImageHelper.ComputePitch(format, mipWidth, mipHeight, out dstRowPitch, out dstSlicePitch, out realMipWidth, out realMipHeight, out bytesPerPixel, legacyDword); int srcRowPitch = dstRowPitch; int srcSlicePitch = dstSlicePitch; //Are we converting from a legacy format, possibly? if (!headerExt.HasValue) { int legacySize = FormatConverter.LegacyFormatBitsPerPixelFromConversionFlag(convFlags); if (legacySize != 0) { srcRowPitch = (realMipWidth * legacySize + 7) / 8; srcSlicePitch = srcRowPitch * realMipHeight; } } //If output data is requested not to have padding, recompute destination pitches if (noPadding) { dstRowPitch = bytesPerPixel * realMipWidth; dstSlicePitch = dstRowPitch * realMipHeight; } //Setup memory to hold the loaded image MipData mipSurface = new MipData(mipWidth, mipHeight, mipDepth, dstRowPitch, dstSlicePitch); mipChain.Add(mipSurface); //Ensure read buffer is sufficiently sized for a single scanline if (buffer.Length < srcRowPitch) { buffer.Resize(srcRowPitch, false); } IntPtr dstPtr = mipSurface.Data; //Advance stream one slice at a time... for (int slice = 0; slice < mipDepth; slice++) { long slicePos = input.Position; IntPtr dPtr = dstPtr; //Copy scanline into temp buffer, do any conversions, copy to output for (int row = 0; row < realMipHeight; row++) { int numBytesRead = input.Read(scanline, 0, srcRowPitch); if (numBytesRead != srcRowPitch) { errored = true; System.Diagnostics.Debug.Assert(false); return(null); } //Copy scanline, optionally convert data FormatConverter.CopyScanline(dPtr, dstRowPitch, scanlinePtr, srcRowPitch, format, convFlags, palette); //Increment dest pointer to next row dPtr = MemoryHelper.AddIntPtr(dPtr, dstRowPitch); } //Advance stream and destination pointer to the next slice input.Position = slicePos + srcSlicePitch; dstPtr = MemoryHelper.AddIntPtr(dstPtr, dstSlicePitch); } } } } finally { //If errored, clean up any mip surfaces we allocated...no null entries should have been made either if (errored) { DisposeMipChains(mipChains); } } if (!ValidateInternal(mipChains, format, texDim)) { System.Diagnostics.Debug.Assert(false); return(null); } return(new DDSContainer(mipChains, format, texDim)); }
public void Invoke() { string outPath = string.IsNullOrEmpty(OutputPath) ? $"{Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), Path.GetFileNameWithoutExtension(FilePath))}.dds" : $"{Path.Combine(OutputPath, Path.GetFileNameWithoutExtension(FilePath))}.dds"; byte[] fileData = File.ReadAllBytes(FilePath); // I am not going to write an entire class that verifies that this is an actual uasset file so just get the magic number instead. if (BitConverter.ToUInt32(fileData.GetBytes(0x0, 0x4), 0x0) != 0x9E2A83C1) { Console.WriteLine("ERROR: Not a valid uasset file."); return; } // Check if the licensee version is FFFFFFFD (-3) if (BitConverter.ToUInt32(fileData.GetBytes(0x4, 0x4), 0x0) != 0xFFFFFFFD) { Console.WriteLine("ERROR: The file provided is not a Tekken 7.0 (2015) uasset file."); return; } Console.WriteLine("INFO: Finding PF offset..."); int[] offsets = ArrayExtensions.Locate(fileData, Encoding.ASCII.GetBytes("PF_")); if (offsets.Length < 1 || offsets.Length == 0) { Console.WriteLine("ERROR: No texture was found."); return; } int dataStartOffset = BitConverter.ToInt32(fileData.GetBytes(0x93, 0x4), 0x0); int pfOffset = offsets[1]; Console.WriteLine("INFO: PF offset was found, getting texture info..."); EPixelFormat pixelFormat = EPixelFormat.PF_Unknown; if (!Enum.TryParse(fileData.GetNullTerminatedString(pfOffset), out pixelFormat)) { Console.WriteLine("ERROR: Could not retrieve texture's pixel format."); return; } int width = BitConverter.ToInt32(fileData.GetBytes(pfOffset - 0x10, 0x4), 0x0); int height = BitConverter.ToInt32(fileData.GetBytes(pfOffset - 0xC, 0x4), 0x0); // NOTE TO SELF: Get rid of this awful pixel format offset calculation mess that is happening here and write something proper FTexture2DMipMap mipMapData = new FTexture2DMipMap(fileData.GetBytes(pfOffset + 0x10 + (pixelFormat == EPixelFormat.PF_B8G8R8A8 ? 0x4 : 0x0) + // pixel format can't be BC5 and BGRA8 at the same time so it's fine (pixelFormat == EPixelFormat.PF_BC5 ? -0x1 : 0x0) // it's fine, i promise! , 20)); Console.WriteLine($"INFO: Texture resolution is {width}x{height} with the size of 0x{mipMapData.Size:X2} - Pixel Format: {pixelFormat.ToString()}"); byte[] textureData = fileData.GetBytes(dataStartOffset, mipMapData.Size); Console.WriteLine("INFO: Generating a DDS file..."); DXGIFormat dxgiFormat = FormatMapList.Find(map => map.x == pixelFormat).y; DDSFlags flags = DDSFlags.NONE; TexMetadata x = GenerateMataData(width, height, 0, dxgiFormat, false); DDSHeader ddsHeader; DX10Header dx10Header; dx10Header.Format = dxgiFormat; GenerateDDSHeader(x, flags, out ddsHeader, out dx10Header); List <byte> ddsData = new List <byte>(); ddsData.AddRange(EncodeDDSHeader(ddsHeader, dx10Header)); ddsData.AddRange(textureData); try { File.WriteAllBytes(outPath, ddsData.ToArray()); } catch (DirectoryNotFoundException) { Directory.CreateDirectory(Path.GetDirectoryName(outPath)); File.WriteAllBytes(outPath, ddsData.ToArray()); } catch (Exception ex) { Console.WriteLine($"ERROR: {ex.Message}"); } Console.WriteLine($"Extracted {Path.GetFileName(FilePath)} ({pixelFormat.ToString()}) -> {Path.GetFileName(outPath)}"); }
//------------------------------------------------------------------------------------- // Save a DDS to a stream //------------------------------------------------------------------------------------- public unsafe static void SaveToDDSStream(PixelBuffer[] pixelBuffers, int count, ImageDescription metadata, DDSFlags flags, System.IO.Stream stream) { // Determine memory required int totalSize = 0; int headerSize = 0; EncodeDDSHeader(metadata, flags, IntPtr.Zero, 0, out totalSize); headerSize = totalSize; int maxSlice = 0; for (int i = 0; i < pixelBuffers.Length; ++i) { int slice = pixelBuffers[i].BufferStride; totalSize += slice; if (slice > maxSlice) maxSlice = slice; } Debug.Assert(totalSize > 0); // Allocate a single temporary buffer to save the headers and each slice. var buffer = new byte[Math.Max(maxSlice, headerSize)]; fixed (void* pbuffer = buffer) { int required; EncodeDDSHeader(metadata, flags, (IntPtr) pbuffer, headerSize, out required); stream.Write(buffer, 0, headerSize); } int remaining = totalSize - headerSize; Debug.Assert(remaining > 0); int index = 0; for (int item = 0; item < metadata.ArraySize; ++item) { int d = metadata.Depth; for (int level = 0; level < metadata.MipLevels; ++level) { for (int slice = 0; slice < d; ++slice) { int pixsize = pixelBuffers[index].BufferStride; Utilities.Read(pixelBuffers[index].DataPointer, buffer, 0, pixsize); stream.Write(buffer, 0, pixsize); ++index; } if (d > 1) d >>= 1; } } }
private static bool WriteHeader(Stream output, StreamTransferBuffer buffer, TextureDimension texDim, DXGIFormat format, int width, int height, int depth, int arrayCount, int mipCount, DDSFlags flags) { //Force the DX10 header... bool writeDX10Header = (flags & DDSFlags.ForceExtendedHeader) == DDSFlags.ForceExtendedHeader; //Or do DX10 if the following is true...1D textures or 2D texture arrays that aren't cubemaps... if (!writeDX10Header) { switch (texDim) { case TextureDimension.One: writeDX10Header = true; break; case TextureDimension.Two: writeDX10Header = arrayCount > 1; break; } } //Figure out pixel format, if not writing DX10 header... PixelFormat pixelFormat; if (!writeDX10Header) { switch (format) { case DXGIFormat.R8G8B8A8_UNorm: pixelFormat = PixelFormat.A8B8G8R8; break; case DXGIFormat.R16G16_UNorm: pixelFormat = PixelFormat.G16R16; break; case DXGIFormat.R8G8_UNorm: pixelFormat = PixelFormat.A8L8; break; case DXGIFormat.R16_UNorm: pixelFormat = PixelFormat.L16; break; case DXGIFormat.R8_UNorm: pixelFormat = PixelFormat.L8; break; case DXGIFormat.A8_UNorm: pixelFormat = PixelFormat.A8; break; case DXGIFormat.R8G8_B8G8_UNorm: pixelFormat = PixelFormat.R8G8_B8G8; break; case DXGIFormat.G8R8_G8B8_UNorm: pixelFormat = PixelFormat.G8R8_G8B8; break; case DXGIFormat.BC1_UNorm: pixelFormat = PixelFormat.DXT1; break; case DXGIFormat.BC2_UNorm: pixelFormat = PixelFormat.DXT3; break; case DXGIFormat.BC3_UNorm: pixelFormat = PixelFormat.DXT5; break; case DXGIFormat.BC4_UNorm: pixelFormat = PixelFormat.BC4_UNorm; break; case DXGIFormat.BC4_SNorm: pixelFormat = PixelFormat.BC4_SNorm; break; case DXGIFormat.BC5_UNorm: pixelFormat = PixelFormat.BC5_UNorm; break; case DXGIFormat.BC5_SNorm: pixelFormat = PixelFormat.BC5_SNorm; break; case DXGIFormat.B5G6R5_UNorm: pixelFormat = PixelFormat.R5G6B5; break; case DXGIFormat.B5G5R5A1_UNorm: pixelFormat = PixelFormat.A1R5G5B5; break; case DXGIFormat.B8G8R8A8_UNorm: pixelFormat = PixelFormat.A8R8G8B8; break; case DXGIFormat.B8G8R8X8_UNorm: pixelFormat = PixelFormat.X8R8G8B8; break; case DXGIFormat.B4G4R4A4_UNorm: pixelFormat = PixelFormat.A4R4G4B4; break; case DXGIFormat.R32G32B32A32_Float: pixelFormat = PixelFormat.R32G32B32A32_Float; break; case DXGIFormat.R16G16B16A16_Float: pixelFormat = PixelFormat.R16G16B16A16_Float; break; case DXGIFormat.R16G16B16A16_UNorm: pixelFormat = PixelFormat.R16G16B16A16_UNorm; break; case DXGIFormat.R16G16B16A16_SNorm: pixelFormat = PixelFormat.R16G16B16A16_SNorm; break; case DXGIFormat.R32G32_Float: pixelFormat = PixelFormat.R32G32_Float; break; case DXGIFormat.R16G16_Float: pixelFormat = PixelFormat.R16G16_Float; break; case DXGIFormat.R32_Float: pixelFormat = PixelFormat.R32_Float; break; case DXGIFormat.R16_Float: pixelFormat = PixelFormat.R16_Float; break; default: pixelFormat = PixelFormat.DX10Extended; writeDX10Header = true; break; } } else { pixelFormat = PixelFormat.DX10Extended; } Header header = new Header(); header.Size = (uint)MemoryHelper.SizeOf <Header>(); header.PixelFormat = pixelFormat; header.Flags = HeaderFlags.Caps | HeaderFlags.Width | HeaderFlags.Height | HeaderFlags.PixelFormat; header.Caps = HeaderCaps.Texture; Header10?header10 = null; if (mipCount > 0) { header.Flags |= HeaderFlags.MipMapCount; header.MipMapCount = (uint)mipCount; header.Caps |= HeaderCaps.MipMap; } switch (texDim) { case TextureDimension.One: header.Width = (uint)width; header.Height = 1; header.Depth = 1; //Should always be writing out extended header for 1D textures System.Diagnostics.Debug.Assert(writeDX10Header); header10 = new Header10(format, D3D10ResourceDimension.Texture1D, Header10Flags.None, (uint)arrayCount, Header10Flags2.None); break; case TextureDimension.Two: header.Width = (uint)width; header.Height = (uint)height; header.Depth = 1; if (writeDX10Header) { header10 = new Header10(format, D3D10ResourceDimension.Texture2D, Header10Flags.None, (uint)arrayCount, Header10Flags2.None); } break; case TextureDimension.Cube: header.Width = (uint)width; header.Height = (uint)height; header.Depth = 1; header.Caps |= HeaderCaps.Complex; header.Caps2 |= HeaderCaps2.Cubemap_AllFaces; //can support array tex cubes, so must be multiples of 6 if (arrayCount % 6 != 0) { return(false); } if (writeDX10Header) { header10 = new Header10(format, D3D10ResourceDimension.Texture2D, Header10Flags.TextureCube, (uint)arrayCount / 6, Header10Flags2.None); } break; case TextureDimension.Three: header.Width = (uint)width; header.Height = (uint)height; header.Depth = (uint)depth; header.Flags |= HeaderFlags.Depth; header.Caps2 |= HeaderCaps2.Volume; if (arrayCount != 1) { return(false); } if (writeDX10Header) { header10 = new Header10(format, D3D10ResourceDimension.Texture3D, Header10Flags.None, 1, Header10Flags2.None); } break; } int realWidth, realHeight, rowPitch, slicePitch; ImageHelper.ComputePitch(format, width, height, out rowPitch, out slicePitch, out realWidth, out realHeight); if (FormatConverter.IsCompressed(format)) { header.Flags |= HeaderFlags.LinearSize; header.PitchOrLinearSize = (uint)slicePitch; } else { header.Flags |= HeaderFlags.Pitch; header.PitchOrLinearSize = (uint)rowPitch; } //Write out magic word, DDS header, and optionally extended header buffer.Write <FourCC>(output, DDS_MAGIC); buffer.Write <Header>(output, header); if (header10.HasValue) { System.Diagnostics.Debug.Assert(header.PixelFormat.IsDX10Extended); buffer.Write <Header10>(output, header10.Value); } return(true); }
/// <summary> /// Writes DDS formatted data to a stream. Image data is expected to be DXGI-compliant data, but an effort is made to write out D3D9-compatible headers when possible. /// </summary> /// <param name="output">Output stream.</param> /// <param name="mipChains">Mipmap chains to write. Each mipmap chain represents a single face (so > 1 represents an array texture or a Cubemap). All faces must have /// equivalent dimensions and each chain must have the same number of mipmaps.</param> /// <param name="format">DXGI format the image data is stored as.</param> /// <param name="texDim">Dimension of the texture to write.</param> /// <param name="flags">Flags to control how the DDS data is saved.</param> /// <returns>True if writing the data was successful, false if otherwise.</returns> public static bool Write(Stream output, List <MipChain> mipChains, DXGIFormat format, TextureDimension texDim, DDSFlags flags = DDSFlags.None) { if (output == null || !output.CanWrite || mipChains == null || mipChains.Count == 0 || mipChains[0].Count == 0 || format == DXGIFormat.Unknown) { return(false); } //Extract details int width, height, depth, arrayCount, mipCount; MipData firstMip = mipChains[0][0]; width = firstMip.Width; height = firstMip.Height; depth = firstMip.Depth; arrayCount = mipChains.Count; mipCount = mipChains[0].Count; if (!ValidateInternal(mipChains, format, texDim)) { return(false); } //Setup a transfer buffer StreamTransferBuffer buffer = new StreamTransferBuffer(firstMip.RowPitch, false); //Write out header if (!WriteHeader(output, buffer, texDim, format, width, height, depth, arrayCount, mipCount, flags)) { return(false); } //Iterate over each array face... for (int i = 0; i < arrayCount; i++) { MipChain mipChain = mipChains[i]; //Iterate over each mip face... for (int mipLevel = 0; mipLevel < mipCount; mipLevel++) { MipData mip = mipChain[mipLevel]; //Compute pitch, based on MSDN programming guide. We will write out these pitches rather than the supplied in order to conform to the recomendation //that we compute pitch based on format int realMipWidth, realMipHeight, dstRowPitch, dstSlicePitch, bytesPerPixel; ImageHelper.ComputePitch(format, mip.Width, mip.Height, out dstRowPitch, out dstSlicePitch, out realMipWidth, out realMipHeight, out bytesPerPixel); //Ensure write buffer is sufficiently sized for a single scanline if (buffer.Length < dstRowPitch) { buffer.Resize(dstRowPitch, false); } //Sanity check if (dstRowPitch < mip.RowPitch) { return(false); } IntPtr srcPtr = mip.Data; //Advance stream one slice at a time... for (int slice = 0; slice < mip.Depth; slice++) { int bytesToWrite = dstSlicePitch; IntPtr sPtr = srcPtr; //Copy scanline into temp buffer, write to output for (int row = 0; row < realMipHeight; row++) { MemoryHelper.CopyMemory(buffer.Pointer, sPtr, dstRowPitch); buffer.WriteBytes(output, dstRowPitch); bytesToWrite -= dstRowPitch; //Advance to next scanline in source data sPtr = MemoryHelper.AddIntPtr(sPtr, mip.RowPitch); } //Pad slice if necessary if (bytesToWrite > 0) { MemoryHelper.ClearMemory(buffer.Pointer, 0, bytesToWrite); buffer.WriteBytes(output, bytesToWrite); } //Advance source pointer to next slice srcPtr = MemoryHelper.AddIntPtr(srcPtr, mip.SlicePitch); } } } return(true); }
/// <summary> /// Encodes DDS file header (magic value, header, optional DX10 extended header) /// </summary> /// <param name="flags">Flags used for decoding the DDS header.</param> /// <param name="description">Output texture description.</param> /// <param name="pDestination">Pointer to the DDS output header. Can be set to IntPtr.Zero to calculated the required bytes.</param> /// <param name="maxsize">The maximum size of the destination buffer.</param> /// <param name="required">Output the number of bytes required to write the DDS header.</param> /// <exception cref="ArgumentException">If the argument headerPtr is null</exception> /// <exception cref="InvalidOperationException">If the DDS header contains invalid datas.</exception> /// <returns>True if the decoding is successfull, false if this is not a DDS header.</returns> private unsafe static void EncodeDDSHeader( ImageDescription description, DDSFlags flags, IntPtr pDestination, int maxsize, out int required ) { if (description.ArraySize > 1) { if ((description.ArraySize != 6) || (description.Dimension != TextureDimension.Texture2D) || (description.Dimension != TextureDimension.TextureCube)) { flags |= DDSFlags.ForceDX10Ext; } } var ddpf = default(DDS.DDSPixelFormat); if ((flags & DDSFlags.ForceDX10Ext) == 0) { switch (description.Format) { case PixelFormat.R8G8B8A8_UNorm: ddpf = DDS.DDSPixelFormat.A8B8G8R8; break; case PixelFormat.R16G16_UNorm: ddpf = DDS.DDSPixelFormat.G16R16; break; case PixelFormat.R8G8_UNorm: ddpf = DDS.DDSPixelFormat.A8L8; break; case PixelFormat.R16_UNorm: ddpf = DDS.DDSPixelFormat.L16; break; case PixelFormat.R8_UNorm: ddpf = DDS.DDSPixelFormat.L8; break; case PixelFormat.A8_UNorm: ddpf = DDS.DDSPixelFormat.A8; break; case PixelFormat.R8G8_B8G8_UNorm: ddpf = DDS.DDSPixelFormat.R8G8_B8G8; break; case PixelFormat.G8R8_G8B8_UNorm: ddpf = DDS.DDSPixelFormat.G8R8_G8B8; break; case PixelFormat.BC1_UNorm: ddpf = DDS.DDSPixelFormat.DXT1; break; case PixelFormat.BC2_UNorm: ddpf = DDS.DDSPixelFormat.DXT3; break; case PixelFormat.BC3_UNorm: ddpf = DDS.DDSPixelFormat.DXT5; break; case PixelFormat.BC4_UNorm: ddpf = DDS.DDSPixelFormat.BC4_UNorm; break; case PixelFormat.BC4_SNorm: ddpf = DDS.DDSPixelFormat.BC4_SNorm; break; case PixelFormat.BC5_UNorm: ddpf = DDS.DDSPixelFormat.BC5_UNorm; break; case PixelFormat.BC5_SNorm: ddpf = DDS.DDSPixelFormat.BC5_SNorm; break; case PixelFormat.B5G6R5_UNorm: ddpf = DDS.DDSPixelFormat.R5G6B5; break; case PixelFormat.B5G5R5A1_UNorm: ddpf = DDS.DDSPixelFormat.A1R5G5B5; break; case PixelFormat.B8G8R8A8_UNorm: ddpf = DDS.DDSPixelFormat.A8R8G8B8; break; // DXGI 1.1 case PixelFormat.B8G8R8X8_UNorm: ddpf = DDS.DDSPixelFormat.X8R8G8B8; break; // DXGI 1.1 #if DIRECTX11_1 case PixelFormat.B4G4R4A4_UNorm: ddpf = DDS.PixelFormat.A4R4G4B4; break; #endif // Legacy D3DX formats using D3DFMT enum value as FourCC case PixelFormat.R32G32B32A32_Float: ddpf.Size = Utilities.SizeOf<DDS.DDSPixelFormat>(); ddpf.Flags = DDS.PixelFormatFlags.FourCC; ddpf.FourCC = 116; // D3DFMT_A32B32G32R32F break; case PixelFormat.R16G16B16A16_Float: ddpf.Size = Utilities.SizeOf<DDS.DDSPixelFormat>(); ddpf.Flags = DDS.PixelFormatFlags.FourCC; ddpf.FourCC = 113; // D3DFMT_A16B16G16R16F break; case PixelFormat.R16G16B16A16_UNorm: ddpf.Size = Utilities.SizeOf<DDS.DDSPixelFormat>(); ddpf.Flags = DDS.PixelFormatFlags.FourCC; ddpf.FourCC = 36; // D3DFMT_A16B16G16R16 break; case PixelFormat.R16G16B16A16_SNorm: ddpf.Size = Utilities.SizeOf<DDS.DDSPixelFormat>(); ddpf.Flags = DDS.PixelFormatFlags.FourCC; ddpf.FourCC = 110; // D3DFMT_Q16W16V16U16 break; case PixelFormat.R32G32_Float: ddpf.Size = Utilities.SizeOf<DDS.DDSPixelFormat>(); ddpf.Flags = DDS.PixelFormatFlags.FourCC; ddpf.FourCC = 115; // D3DFMT_G32R32F break; case PixelFormat.R16G16_Float: ddpf.Size = Utilities.SizeOf<DDS.DDSPixelFormat>(); ddpf.Flags = DDS.PixelFormatFlags.FourCC; ddpf.FourCC = 112; // D3DFMT_G16R16F break; case PixelFormat.R32_Float: ddpf.Size = Utilities.SizeOf<DDS.DDSPixelFormat>(); ddpf.Flags = DDS.PixelFormatFlags.FourCC; ddpf.FourCC = 114; // D3DFMT_R32F break; case PixelFormat.R16_Float: ddpf.Size = Utilities.SizeOf<DDS.DDSPixelFormat>(); ddpf.Flags = DDS.PixelFormatFlags.FourCC; ddpf.FourCC = 111; // D3DFMT_R16F break; } } required = sizeof (int) + Utilities.SizeOf<DDS.Header>(); if (ddpf.Size == 0) required += Utilities.SizeOf<DDS.HeaderDXT10>(); if (pDestination == IntPtr.Zero) return; if (maxsize < required) throw new ArgumentException("Not enough size for destination buffer", "maxsize"); *(uint*)(pDestination) = DDS.MagicHeader; var header = (DDS.Header*)((byte*)(pDestination) + sizeof (int)); Utilities.ClearMemory((IntPtr)header, 0, Utilities.SizeOf<DDS.Header>()); header->Size = Utilities.SizeOf<DDS.Header>(); header->Flags = DDS.HeaderFlags.Texture; header->SurfaceFlags = DDS.SurfaceFlags.Texture; if (description.MipLevels > 0) { header->Flags |= DDS.HeaderFlags.Mipmap; header->MipMapCount = description.MipLevels; if (header->MipMapCount > 1) header->SurfaceFlags |= DDS.SurfaceFlags.Mipmap; } switch (description.Dimension) { case TextureDimension.Texture1D: header->Height = description.Height; header->Width = header->Depth = 1; break; case TextureDimension.Texture2D: case TextureDimension.TextureCube: header->Height = description.Height; header->Width = description.Width; header->Depth = 1; if (description.Dimension == TextureDimension.TextureCube) { header->SurfaceFlags |= DDS.SurfaceFlags.Cubemap; header->CubemapFlags |= DDS.CubemapFlags.CubeMap | DDS.CubemapFlags.AllFaces; } break; case TextureDimension.Texture3D: header->Flags |= DDS.HeaderFlags.Volume; header->CubemapFlags |= DDS.CubemapFlags.Volume; header->Height = description.Height; header->Width = description.Width; header->Depth = description.Depth; break; } int rowPitch, slicePitch; int newWidth; int newHeight; Image.ComputePitch(description.Format, description.Width, description.Height, out rowPitch, out slicePitch, out newWidth, out newHeight); if (description.Format.IsCompressed()) { header->Flags |= DDS.HeaderFlags.LinearSize; header->PitchOrLinearSize = slicePitch; } else { header->Flags |= DDS.HeaderFlags.Pitch; header->PitchOrLinearSize = rowPitch; } if (ddpf.Size == 0) { header->PixelFormat = DDS.DDSPixelFormat.DX10; var ext = (DDS.HeaderDXT10*)((byte*)(header) + Utilities.SizeOf<DDS.Header>()); Utilities.ClearMemory((IntPtr) ext, 0, Utilities.SizeOf<DDS.HeaderDXT10>()); ext->DXGIFormat = description.Format; switch (description.Dimension) { case TextureDimension.Texture1D: ext->ResourceDimension = DDS.ResourceDimension.Texture1D; break; case TextureDimension.Texture2D: case TextureDimension.TextureCube: ext->ResourceDimension = DDS.ResourceDimension.Texture2D; break; case TextureDimension.Texture3D: ext->ResourceDimension = DDS.ResourceDimension.Texture3D; break; } if (description.Dimension == TextureDimension.TextureCube) { ext->MiscFlags |= DDS.ResourceOptionFlags.TextureCube; ext->ArraySize = description.ArraySize / 6; } else { ext->ArraySize = description.ArraySize; } } else { header->PixelFormat = ddpf; } }
/// <summary> /// Decodes DDS header including optional DX10 extended header /// </summary> /// <param name="headerPtr">Pointer to the DDS header.</param> /// <param name="size">Size of the DDS content.</param> /// <param name="flags">Flags used for decoding the DDS header.</param> /// <param name="description">Output texture description.</param> /// <param name="convFlags">Output conversion flags.</param> /// <exception cref="ArgumentException">If the argument headerPtr is null</exception> /// <exception cref="InvalidOperationException">If the DDS header contains invalid datas.</exception> /// <returns>True if the decoding is successfull, false if this is not a DDS header.</returns> private static unsafe bool DecodeDDSHeader(IntPtr headerPtr, int size, DDSFlags flags, out ImageDescription description, out ConversionFlags convFlags) { description = new ImageDescription(); convFlags = ConversionFlags.None; if (headerPtr == IntPtr.Zero) throw new ArgumentException("Pointer to DDS header cannot be null", "headerPtr"); if (size < (Utilities.SizeOf<DDS.Header>() + sizeof(uint))) return false; // DDS files always start with the same magic number ("DDS ") if (*(uint*)(headerPtr) != DDS.MagicHeader) return false; var header = *(DDS.Header*)((byte*)headerPtr + sizeof(int)); // Verify header to validate DDS file if (header.Size != Utilities.SizeOf<DDS.Header>() || header.PixelFormat.Size != Utilities.SizeOf<DDS.PixelFormat>()) return false; // Setup MipLevels description.MipLevels = header.MipMapCount; if (description.MipLevels == 0) description.MipLevels = 1; // Check for DX10 extension if ((header.PixelFormat.Flags & DDS.PixelFormatFlags.FourCC) != 0 && (new FourCC('D', 'X', '1', '0') == header.PixelFormat.FourCC)) { // Buffer must be big enough for both headers and magic value if (size < (Utilities.SizeOf<DDS.Header>() + sizeof(uint) + Utilities.SizeOf<DDS.HeaderDXT10>())) return false; var headerDX10 = *(DDS.HeaderDXT10*)((byte*)headerPtr + sizeof(int) + Utilities.SizeOf<DDS.Header>()); convFlags |= ConversionFlags.DX10; description.ArraySize = headerDX10.ArraySize; if (description.ArraySize == 0) throw new InvalidOperationException("Unexpected ArraySize == 0 from DDS HeaderDX10 "); description.Format = headerDX10.DXGIFormat; if (!FormatHelper.IsValid(description.Format)) throw new InvalidOperationException("Invalid Format from DDS HeaderDX10 "); switch (headerDX10.ResourceDimension) { case ResourceDimension.Texture1D: // D3DX writes 1D textures with a fixed Height of 1 if ((header.Flags & DDS.HeaderFlags.Height) != 0 && header.Height != 1) throw new InvalidOperationException("Unexpected Height != 1 from DDS HeaderDX10 "); description.Width = header.Width; description.Height = 1; description.Depth = 1; description.Dimension = TextureDimension.Texture1D; break; case ResourceDimension.Texture2D: if ((headerDX10.MiscFlags & ResourceOptionFlags.TextureCube) != 0) { description.ArraySize *= 6; description.Dimension = TextureDimension.TextureCube; } else { description.Dimension = TextureDimension.Texture2D; } description.Width = header.Width; description.Height = header.Height; description.Depth = 1; break; case ResourceDimension.Texture3D: if ((header.Flags & DDS.HeaderFlags.Volume) == 0) throw new InvalidOperationException("Texture3D missing HeaderFlags.Volume from DDS HeaderDX10"); if (description.ArraySize > 1) throw new InvalidOperationException("Unexpected ArraySize > 1 for Texture3D from DDS HeaderDX10"); description.Width = header.Width; description.Height = header.Height; description.Depth = header.Depth; description.Dimension = TextureDimension.Texture3D; break; default: throw new InvalidOperationException(string.Format("Unexpected dimension [{0}] from DDS HeaderDX10", headerDX10.ResourceDimension)); } } else { description.ArraySize = 1; if ((header.Flags & DDS.HeaderFlags.Volume) != 0) { description.Width = header.Width; description.Height = header.Height; description.Depth = header.Depth; description.Dimension = TextureDimension.Texture3D; } else { if ((header.CubemapFlags & DDS.CubemapFlags.CubeMap) != 0) { // We require all six faces to be defined if ((header.CubemapFlags & DDS.CubemapFlags.AllFaces) != DDS.CubemapFlags.AllFaces) throw new InvalidOperationException("Unexpected CubeMap, expecting all faces from DDS Header"); description.ArraySize = 6; description.Dimension = TextureDimension.TextureCube; } else { description.Dimension = TextureDimension.Texture2D; } description.Width = header.Width; description.Height = header.Height; description.Depth = 1; // Note there's no way for a legacy Direct3D 9 DDS to express a '1D' texture } description.Format = GetDXGIFormat(ref header.PixelFormat, flags, out convFlags); if (description.Format == Format.Unknown) throw new InvalidOperationException("Unsupported PixelFormat from DDS Header"); } // Special flag for handling BGR DXGI 1.1 formats if ((flags & DDSFlags.ForceRgb) != 0) { switch ((Format)description.Format) { case Format.B8G8R8A8_UNorm: description.Format = Format.R8G8B8A8_UNorm; convFlags |= ConversionFlags.Swizzle; break; case Format.B8G8R8X8_UNorm: description.Format = Format.R8G8B8A8_UNorm; convFlags |= ConversionFlags.Swizzle | ConversionFlags.NoAlpha; break; case Format.B8G8R8A8_Typeless: description.Format = Format.R8G8B8A8_Typeless; convFlags |= ConversionFlags.Swizzle; break; case Format.B8G8R8A8_UNorm_SRgb: description.Format = Format.R8G8B8A8_UNorm_SRgb; convFlags |= ConversionFlags.Swizzle; break; case Format.B8G8R8X8_Typeless: description.Format = Format.R8G8B8A8_Typeless; convFlags |= ConversionFlags.Swizzle | ConversionFlags.NoAlpha; break; case Format.B8G8R8X8_UNorm_SRgb: description.Format = Format.R8G8B8A8_UNorm_SRgb; convFlags |= ConversionFlags.Swizzle | ConversionFlags.NoAlpha; break; } } // Pass DDSFlags copy memory to the conversion flags if ((flags & DDSFlags.CopyMemory) != 0) convFlags |= ConversionFlags.CopyMemory; // Special flag for handling 16bpp formats if ((flags & DDSFlags.No16Bpp) != 0) { switch ((Format)description.Format) { case Format.B5G6R5_UNorm: case Format.B5G5R5A1_UNorm: case Format.B4G4R4A4_UNorm: description.Format = Format.R8G8B8A8_UNorm; convFlags |= ConversionFlags.Expand; if (description.Format == Format.B5G6R5_UNorm) convFlags |= ConversionFlags.NoAlpha; break; } } return true; }
/// <summary> /// Writes images to a DDS file to disk. /// </summary> /// <param name="fileName">File to write to. If it doesn't exist, it will be created.</param> /// <param name="flags">Flags to control how the DDS data is saved.</param> /// <returns>True if writing the data was successful, false if otherwise.</returns> public bool Write(String fileName, DDSFlags flags = DDSFlags.None) { return(DDSTypes.Write(fileName, m_mipChains, m_format, m_dimension, flags)); }
/// <summary> /// Writes images contained as DDS formatted data to a stream. /// </summary> /// <param name="output">Output stream.</param> /// <param name="flags">Flags to control how the DDS data is saved.</param> /// <returns>True if writing the data was successful, false if otherwise.</returns> public bool Write(Stream output, DDSFlags flags = DDSFlags.None) { return(DDSTypes.Write(output, m_mipChains, m_format, m_dimension, flags)); }
// Note that many common DDS reader/writers (including D3DX) swap the // the RED/BLUE masks for 10:10:10:2 formats. We assumme // below that the 'backwards' header mask is being used since it is most // likely written by D3DX. The more robust solution is to use the 'DX10' // header extension and specify the Format.R10G10B10A2_UNorm format directly // We do not support the following legacy Direct3D 9 formats: // BumpDuDv D3DFMT_V8U8, D3DFMT_Q8W8V8U8, D3DFMT_V16U16, D3DFMT_A2W10V10U10 // BumpLuminance D3DFMT_L6V5U5, D3DFMT_X8L8V8U8 // FourCC "UYVY" D3DFMT_UYVY // FourCC "YUY2" D3DFMT_YUY2 // FourCC 117 D3DFMT_CxV8U8 // ZBuffer D3DFMT_D16_LOCKABLE // FourCC 82 D3DFMT_D32F_LOCKABLE private static Format GetDXGIFormat(ref DDS.PixelFormat pixelFormat, DDSFlags flags, out ConversionFlags conversionFlags) { conversionFlags = ConversionFlags.None; int index = 0; for (index = 0; index < LegacyMaps.Length; ++index) { var entry = LegacyMaps[index]; if ((pixelFormat.Flags & entry.PixelFormat.Flags) != 0) { if ((entry.PixelFormat.Flags & DDS.PixelFormatFlags.FourCC) != 0) { if (pixelFormat.FourCC == entry.PixelFormat.FourCC) break; } else if ((entry.PixelFormat.Flags & DDS.PixelFormatFlags.Pal8) != 0) { if (pixelFormat.RGBBitCount == entry.PixelFormat.RGBBitCount) break; } else if (pixelFormat.RGBBitCount == entry.PixelFormat.RGBBitCount) { // RGB, RGBA, ALPHA, LUMINANCE if (pixelFormat.RBitMask == entry.PixelFormat.RBitMask && pixelFormat.GBitMask == entry.PixelFormat.GBitMask && pixelFormat.BBitMask == entry.PixelFormat.BBitMask && pixelFormat.ABitMask == entry.PixelFormat.ABitMask) break; } } } if (index >= LegacyMaps.Length) return Format.Unknown; conversionFlags = LegacyMaps[index].ConversionFlags; var format = LegacyMaps[index].Format; if ((conversionFlags & ConversionFlags.Expand) != 0 && (flags & DDSFlags.NoLegacyExpansion) != 0) return Format.Unknown; if ((format == Format.R10G10B10A2_UNorm) && (flags & DDSFlags.NoR10B10G10A2Fixup) != 0) { conversionFlags ^= ConversionFlags.Swizzle; } return format; }
/// <summary> /// Decodes DDS header including optional DX10 extended header /// </summary> /// <param name="headerPtr">Pointer to the DDS header.</param> /// <param name="size">Size of the DDS content.</param> /// <param name="flags">Flags used for decoding the DDS header.</param> /// <param name="description">Output texture description.</param> /// <param name="convFlags">Output conversion flags.</param> /// <exception cref="ArgumentException">If the argument headerPtr is null</exception> /// <exception cref="InvalidOperationException">If the DDS header contains invalid datas.</exception> /// <returns>True if the decoding is successfull, false if this is not a DDS header.</returns> private static unsafe bool DecodeDDSHeader(IntPtr headerPtr, int size, DDSFlags flags, out ImageDescription description, out ConversionFlags convFlags) { description = new ImageDescription(); convFlags = ConversionFlags.None; if (headerPtr == IntPtr.Zero) { throw new ArgumentException("Pointer to DDS header cannot be null", "headerPtr"); } if (size < (Utilities.SizeOf <DDS.Header>() + sizeof(uint))) { return(false); } // DDS files always start with the same magic number ("DDS ") if (*(uint *)(headerPtr) != DDS.MagicHeader) { return(false); } var header = *(DDS.Header *)((byte *)headerPtr + sizeof(int)); // Verify header to validate DDS file if (header.Size != Utilities.SizeOf <DDS.Header>() || header.PixelFormat.Size != Utilities.SizeOf <DDS.PixelFormat>()) { return(false); } // Setup MipLevels description.MipLevels = header.MipMapCount; if (description.MipLevels == 0) { description.MipLevels = 1; } // Check for DX10 extension if ((header.PixelFormat.Flags & DDS.PixelFormatFlags.FourCC) != 0 && (new FourCC('D', 'X', '1', '0') == header.PixelFormat.FourCC)) { // Buffer must be big enough for both headers and magic value if (size < (Utilities.SizeOf <DDS.Header>() + sizeof(uint) + Utilities.SizeOf <DDS.HeaderDXT10>())) { return(false); } var headerDX10 = *(DDS.HeaderDXT10 *)((byte *)headerPtr + sizeof(int) + Utilities.SizeOf <DDS.Header>()); convFlags |= ConversionFlags.DX10; description.ArraySize = headerDX10.ArraySize; if (description.ArraySize == 0) { throw new InvalidOperationException("Unexpected ArraySize == 0 from DDS HeaderDX10 "); } description.Format = headerDX10.DXGIFormat; if (!FormatHelper.IsValid(description.Format)) { throw new InvalidOperationException("Invalid Format from DDS HeaderDX10 "); } switch (headerDX10.ResourceDimension) { case ResourceDimension.Texture1D: // D3DX writes 1D textures with a fixed Height of 1 if ((header.Flags & DDS.HeaderFlags.Height) != 0 && header.Height != 1) { throw new InvalidOperationException("Unexpected Height != 1 from DDS HeaderDX10 "); } description.Width = header.Width; description.Height = 1; description.Depth = 1; description.Dimension = TextureDimension.Texture1D; break; case ResourceDimension.Texture2D: if ((headerDX10.MiscFlags & ResourceOptionFlags.TextureCube) != 0) { description.ArraySize *= 6; description.Dimension = TextureDimension.TextureCube; } else { description.Dimension = TextureDimension.Texture2D; } description.Width = header.Width; description.Height = header.Height; description.Depth = 1; break; case ResourceDimension.Texture3D: if ((header.Flags & DDS.HeaderFlags.Volume) == 0) { throw new InvalidOperationException("Texture3D missing HeaderFlags.Volume from DDS HeaderDX10"); } if (description.ArraySize > 1) { throw new InvalidOperationException("Unexpected ArraySize > 1 for Texture3D from DDS HeaderDX10"); } description.Width = header.Width; description.Height = header.Height; description.Depth = header.Depth; description.Dimension = TextureDimension.Texture3D; break; default: throw new InvalidOperationException(string.Format("Unexpected dimension [{0}] from DDS HeaderDX10", headerDX10.ResourceDimension)); } } else { description.ArraySize = 1; if ((header.Flags & DDS.HeaderFlags.Volume) != 0) { description.Width = header.Width; description.Height = header.Height; description.Depth = header.Depth; description.Dimension = TextureDimension.Texture3D; } else { if ((header.CubemapFlags & DDS.CubemapFlags.CubeMap) != 0) { // We require all six faces to be defined if ((header.CubemapFlags & DDS.CubemapFlags.AllFaces) != DDS.CubemapFlags.AllFaces) { throw new InvalidOperationException("Unexpected CubeMap, expecting all faces from DDS Header"); } description.ArraySize = 6; description.Dimension = TextureDimension.TextureCube; } else { description.Dimension = TextureDimension.Texture2D; } description.Width = header.Width; description.Height = header.Height; description.Depth = 1; // Note there's no way for a legacy Direct3D 9 DDS to express a '1D' texture } description.Format = GetDXGIFormat(ref header.PixelFormat, flags, out convFlags); if (description.Format == Format.Unknown) { throw new InvalidOperationException("Unsupported PixelFormat from DDS Header"); } } // Special flag for handling BGR DXGI 1.1 formats if ((flags & DDSFlags.ForceRgb) != 0) { switch ((Format)description.Format) { case Format.B8G8R8A8_UNorm: description.Format = Format.R8G8B8A8_UNorm; convFlags |= ConversionFlags.Swizzle; break; case Format.B8G8R8X8_UNorm: description.Format = Format.R8G8B8A8_UNorm; convFlags |= ConversionFlags.Swizzle | ConversionFlags.NoAlpha; break; case Format.B8G8R8A8_Typeless: description.Format = Format.R8G8B8A8_Typeless; convFlags |= ConversionFlags.Swizzle; break; case Format.B8G8R8A8_UNorm_SRgb: description.Format = Format.R8G8B8A8_UNorm_SRgb; convFlags |= ConversionFlags.Swizzle; break; case Format.B8G8R8X8_Typeless: description.Format = Format.R8G8B8A8_Typeless; convFlags |= ConversionFlags.Swizzle | ConversionFlags.NoAlpha; break; case Format.B8G8R8X8_UNorm_SRgb: description.Format = Format.R8G8B8A8_UNorm_SRgb; convFlags |= ConversionFlags.Swizzle | ConversionFlags.NoAlpha; break; } } // Pass DDSFlags copy memory to the conversion flags if ((flags & DDSFlags.CopyMemory) != 0) { convFlags |= ConversionFlags.CopyMemory; } // Special flag for handling 16bpp formats if ((flags & DDSFlags.No16Bpp) != 0) { switch ((Format)description.Format) { case Format.B5G6R5_UNorm: case Format.B5G5R5A1_UNorm: case Format.B4G4R4A4_UNorm: description.Format = Format.R8G8B8A8_UNorm; convFlags |= ConversionFlags.Expand; if (description.Format == Format.B5G6R5_UNorm) { convFlags |= ConversionFlags.NoAlpha; } break; } } return(true); }