public override Codec.DecodeResult Decode(Stream input)
{
using (var br = new BinaryReader(input))
{
// Read 4 character code
var fileType = br.ReadInt32();
using (var wrap = BufferBase.Wrap(fileType, 2))
{
_flipEndian(wrap, sizeof(uint), 1);
}
if (FOURCC('D', 'D', 'S', ' ') != fileType)
{
throw new AxiomException("This is not a DDS file!");
}
// Read header in full
var header = DDSHeader.Read(br);
// Endian flip if required, all 32-bit values
using (var wrap = BufferBase.Wrap(header, Memory.SizeOf(typeof(DDSHeader))))
{
_flipEndian(wrap, 4, Memory.SizeOf(typeof(DDSHeader)) / 4);
}
// Check some sizes
if (header.size != DDS_HEADER_SIZE)
{
throw new AxiomException("DDS header size mismatch!");
}
if (header.pixelFormat.size != DDS_PIXELFORMAT_SIZE)
{
throw new AxiomException("DDS header size mismatch!");
}
var imgData = new ImageData();
imgData.depth = 1; // (deal with volume later)
imgData.width = header.width;
imgData.height = header.height;
var numFaces = 1; // assume one face until we know otherwise
if ((header.caps.caps1 & DDSCAPS_MIPMAP) != 0)
{
imgData.numMipMaps = header.mipMapCount - 1;
}
else
{
imgData.numMipMaps = 0;
}
imgData.flags = 0;
var decompressDXT = false;
// Figure out basic image type
if ((header.caps.caps2 & DDSCAPS2_CUBEMAP) != 0)
{
imgData.flags |= ImageFlags.CubeMap;
numFaces = 6;
}
else if ((header.caps.caps2 & DDSCAPS2_VOLUME) != 0)
{
imgData.flags |= ImageFlags.Volume;
imgData.depth = header.depth;
}
// Pixel format
var sourceFormat = PixelFormat.Unknown;
if ((header.pixelFormat.flags & DDPF_FOURCC) != 0)
{
sourceFormat = _convertFourCCFormat(header.pixelFormat.fourCC);
}
else
{
sourceFormat = _convertPixelFormat(header.pixelFormat.rgbBits, header.pixelFormat.redMask,
header.pixelFormat.greenMask, header.pixelFormat.blueMask,
(header.pixelFormat.flags & DDPF_ALPHAPIXELS) != 0
? header.pixelFormat.alphaMask
: 0);
}
if (PixelUtil.IsCompressed(sourceFormat))
{
if (!Root.Instance.RenderSystem.Capabilities.HasCapability(Capabilities.TextureCompressionDXT))
{
// We'll need to decompress
decompressDXT = true;
// Convert format
switch (sourceFormat)
{
case PixelFormat.DXT1:
// source can be either 565 or 5551 depending on whether alpha present
// unfortunately you have to read a block to figure out which
// Note that we upgrade to 32-bit pixel formats here, even
// though the source is 16-bit; this is because the interpolated
// values will benefit from the 32-bit results, and the source
// from which the 16-bit samples are calculated may have been
// 32-bit so can benefit from this.
var block = DXTColorBlock.Read(br);
using (var wrap = BufferBase.Wrap(block.colour_0, sizeof(ushort)))
{
_flipEndian(wrap, sizeof(ushort), 1);
}
using (var wrap = BufferBase.Wrap(block.colour_1, sizeof(ushort)))
{
_flipEndian(wrap, sizeof(ushort), 1);
}
// skip back since we'll need to read this again
br.BaseStream.Seek(0 - (long)Memory.SizeOf(typeof(DXTColorBlock)), SeekOrigin.Current);
// colour_0 <= colour_1 means transparency in DXT1
if (block.colour_0 <= block.colour_1)
{
imgData.format = PixelFormat.BYTE_RGBA;
}
else
{
imgData.format = PixelFormat.BYTE_RGB;
}
break;
case PixelFormat.DXT2:
case PixelFormat.DXT3:
case PixelFormat.DXT4:
case PixelFormat.DXT5:
// full alpha present, formats vary only in encoding
imgData.format = PixelFormat.BYTE_RGBA;
break;
default:
// all other cases need no special format handling
break;
}
}
else
{
// Use original format
imgData.format = sourceFormat;
// Keep DXT data compressed
imgData.flags |= ImageFlags.Compressed;
}
}
else // not compressed
{
// Don't test against DDPF_RGB since greyscale DDS doesn't set this
// just derive any other kind of format
imgData.format = sourceFormat;
}
// Calculate total size from number of mipmaps, faces and size
imgData.size = Image.CalculateSize(imgData.numMipMaps, numFaces, imgData.width, imgData.height, imgData.depth, imgData.format);
// Now deal with the data
var dest = new byte[imgData.size];
var destBuffer = BufferBase.Wrap(dest);
// all mips for a face, then each face
for (var i = 0; i < numFaces; ++i)
{
var width = imgData.width;
var height = imgData.height;
var depth = imgData.depth;
for (var mip = 0; mip <= imgData.numMipMaps; ++mip)
{
var dstPitch = width * PixelUtil.GetNumElemBytes(imgData.format);
if (PixelUtil.IsCompressed(sourceFormat))
{
// Compressed data
if (decompressDXT)
{
DXTColorBlock col;
DXTInterpolatedAlphaBlock iAlpha;
DXTExplicitAlphaBlock eAlpha;
// 4x4 block of decompressed colour
var tempColours = new ColorEx[16];
var destBpp = PixelUtil.GetNumElemBytes(imgData.format);
var sx = Utility.Min(width, 4);
var sy = Utility.Min(height, 4);
var destPitchMinus4 = dstPitch - destBpp * sx;
// slices are done individually
for (var z = 0; z < depth; ++z)
{
// 4x4 blocks in x/y
for (var y = 0; y < height; y += 4)
{
for (var x = 0; x < width; x += 4)
{
if (sourceFormat == PixelFormat.DXT2 || sourceFormat == PixelFormat.DXT3)
{
// explicit alpha
eAlpha = DXTExplicitAlphaBlock.Read(br);
using (var wrap = BufferBase.Wrap(eAlpha.alphaRow, eAlpha.alphaRow.Length * sizeof(ushort)))
{
_flipEndian(wrap, sizeof(ushort), 4);
}
_unpackDXTAlpha(eAlpha, tempColours);
}
else if (sourceFormat == PixelFormat.DXT4 || sourceFormat == PixelFormat.DXT5)
{
// interpolated alpha
iAlpha = DXTInterpolatedAlphaBlock.Read(br);
using (var wrap = BufferBase.Wrap(iAlpha.alpha_0, 1))
{
_flipEndian(wrap, sizeof(ushort), 1);
}
using (var wrap = BufferBase.Wrap(iAlpha.alpha_1, 1))
{
_flipEndian(wrap, sizeof(ushort), 1);
}
_unpackDXTAlpha(iAlpha, tempColours);
}
// always read colour
col = DXTColorBlock.Read(br);
using (var wrap = BufferBase.Wrap(col.colour_0, sizeof(ushort)))
{
_flipEndian(wrap, sizeof(ushort), 1);
}
using (var wrap = BufferBase.Wrap(col.colour_1, sizeof(ushort)))
{
_flipEndian(wrap, sizeof(ushort), 1);
}
_unpackDXTColor(sourceFormat, col, tempColours);
// write 4x4 block to uncompressed version
for (var by = 0; by < sy; ++by)
{
for (var bx = 0; bx < sx; ++bx)
{
PixelConverter.PackColor(tempColours[by * 4 + bx], imgData.format, destBuffer);
destBuffer += destBpp;
}
// advance to next row
destBuffer += destPitchMinus4;
}
// next block. Our dest pointer is 4 lines down
// from where it started
if (x + 4 >= width)
{
// Jump back to the start of the line
destBuffer += -destPitchMinus4;
}
else
{
// Jump back up 4 rows and 4 pixels to the
// right to be at the next block to the right
destBuffer += -(dstPitch * sy + destBpp * sx);
}
}
}
}
}
else
{
// load directly
// DDS format lies! sizeOrPitch is not always set for DXT!!
var dxtSize = PixelUtil.GetMemorySize(width, height, depth, imgData.format);
using (var src = BufferBase.Wrap(br.ReadBytes(dxtSize)))
{
Memory.Copy(src, destBuffer, dxtSize);
}
destBuffer += dxtSize;
}
}
else
{
// Final data - trim incoming pitch
int srcPitch;
if ((header.flags & DDSD_PITCH) != 0)
{
srcPitch = header.sizeOrPitch / Utility.Max(1, mip * 2);
}
else
{
// assume same as final pitch
srcPitch = dstPitch;
}
Contract.Requires(dstPitch <= srcPitch);
var srcAdvance = (long)(srcPitch - dstPitch);
for (var z = 0; z < imgData.depth; ++z)
{
for (var y = 0; y < imgData.height; ++y)
{
using (var src = BufferBase.Wrap(br.ReadBytes(dstPitch)))
{
Memory.Copy(src, destBuffer, dstPitch);
}
if (srcAdvance > 0)
{
br.BaseStream.Seek(srcAdvance, SeekOrigin.Current);
}
destBuffer += dstPitch;
}
}
}
// Next mip
if (width != 1)
{
width /= 2;
}
if (height != 1)
{
height /= 2;
}
if (depth != 1)
{
depth /= 2;
}
}
}
destBuffer.Dispose();
return(new DecodeResult(new MemoryStream(dest), imgData));
}
}