/// <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)); }