Skip to content

Instantly share code, notes, and snippets.

@soeminnminn
Last active November 18, 2023 16:39
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save soeminnminn/e9c4c99867743a717f5b to your computer and use it in GitHub Desktop.
Save soeminnminn/e9c4c99867743a717f5b to your computer and use it in GitHub Desktop.
Read DDS image in C# way. Converted from "[il_dds.c] https://github.com/DentonW/DevIL/blob/master/DevIL/src-IL/src/il_dds.c" (DevIL).
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.IO;
namespace S16.Drawing
{
#region DDSImage Class
public class DDSImage : IDisposable
{
#region Variables
private bool m_isValid = false;
private System.Drawing.Bitmap m_bitmap = null;
#endregion
#region Constructor/Destructor
public DDSImage(byte[] ddsImage)
{
if (ddsImage == null) return;
if (ddsImage.Length == 0) return;
using (MemoryStream stream = new MemoryStream(ddsImage.Length))
{
stream.Write(ddsImage, 0, ddsImage.Length);
stream.Seek(0, SeekOrigin.Begin);
using (BinaryReader reader = new BinaryReader(stream))
{
this.Parse(reader);
}
}
}
public DDSImage(Stream ddsImage)
{
if (ddsImage == null) return;
if (!ddsImage.CanRead) return;
using (BinaryReader reader = new BinaryReader(ddsImage))
{
this.Parse(reader);
}
}
private DDSImage(System.Drawing.Bitmap bitmap)
{
this.m_bitmap = bitmap;
}
#endregion
#region Override Methods
#endregion
#region Private Methods
private void Parse(BinaryReader reader)
{
DDSStruct header = new DDSStruct();
PixelFormat pixelFormat = PixelFormat.UNKNOWN;
byte[] data = null;
if (this.ReadHeader(reader, ref header))
{
this.m_isValid = true;
// patches for stuff
if (header.depth == 0) header.depth = 1;
uint blocksize = 0;
pixelFormat = this.GetFormat(header, ref blocksize);
if (pixelFormat == PixelFormat.UNKNOWN)
{
throw new InvalidFileHeaderException();
}
data = this.ReadData(reader, header);
if (data != null)
{
byte[] rawData = this.DecompressData(header, data, pixelFormat);
this.m_bitmap = this.CreateBitmap((int)header.width, (int)header.height, rawData);
}
}
}
private byte[] ReadData(BinaryReader reader, DDSStruct header)
{
byte[] compdata = null;
uint compsize = 0;
if ((header.flags & DDSD_LINEARSIZE) > 1)
{
compdata = reader.ReadBytes((int)header.sizeorpitch);
compsize = (uint)compdata.Length;
}
else
{
uint bps = header.width * header.pixelformat.rgbbitcount / 8;
compsize = bps * header.height * header.depth;
compdata = new byte[compsize];
MemoryStream mem = new MemoryStream((int)compsize);
byte[] temp;
for (int z = 0; z < header.depth; z++)
{
for (int y = 0; y < header.height; y++)
{
temp = reader.ReadBytes((int)bps);
mem.Write(temp, 0, temp.Length);
}
}
mem.Seek(0, SeekOrigin.Begin);
mem.Read(compdata, 0, compdata.Length);
mem.Close();
}
return compdata;
}
private System.Drawing.Bitmap CreateBitmap(int width, int height, byte[] rawData)
{
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height)
, ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
IntPtr scan = data.Scan0;
int size = bitmap.Width * bitmap.Height * 4;
unsafe
{
byte* p = (byte*)scan;
for (int i = 0; i < size; i += 4)
{
// iterate through bytes.
// Bitmap stores it's data in RGBA order.
// DDS stores it's data in BGRA order.
p[i] = rawData[i + 2]; // blue
p[i + 1] = rawData[i + 1]; // green
p[i + 2] = rawData[i]; // red
p[i + 3] = rawData[i + 3]; // alpha
}
}
bitmap.UnlockBits(data);
return bitmap;
}
private bool ReadHeader(BinaryReader reader, ref DDSStruct header)
{
byte[] signature = reader.ReadBytes(4);
if (!(signature[0] == 'D' && signature[1] == 'D' && signature[2] == 'S' && signature[3] == ' '))
return false;
header.size = reader.ReadUInt32();
if (header.size != 124)
return false;
//convert the data
header.flags = reader.ReadUInt32();
header.height = reader.ReadUInt32();
header.width = reader.ReadUInt32();
header.sizeorpitch = reader.ReadUInt32();
header.depth = reader.ReadUInt32();
header.mipmapcount = reader.ReadUInt32();
header.alphabitdepth = reader.ReadUInt32();
header.reserved = new uint[10];
for (int i = 0; i < 10; i++)
{
header.reserved[i] = reader.ReadUInt32();
}
//pixelfromat
header.pixelformat.size = reader.ReadUInt32();
header.pixelformat.flags = reader.ReadUInt32();
header.pixelformat.fourcc = reader.ReadUInt32();
header.pixelformat.rgbbitcount = reader.ReadUInt32();
header.pixelformat.rbitmask = reader.ReadUInt32();
header.pixelformat.gbitmask = reader.ReadUInt32();
header.pixelformat.bbitmask = reader.ReadUInt32();
header.pixelformat.alphabitmask = reader.ReadUInt32();
//caps
header.ddscaps.caps1 = reader.ReadUInt32();
header.ddscaps.caps2 = reader.ReadUInt32();
header.ddscaps.caps3 = reader.ReadUInt32();
header.ddscaps.caps4 = reader.ReadUInt32();
header.texturestage = reader.ReadUInt32();
return true;
}
private PixelFormat GetFormat(DDSStruct header, ref uint blocksize)
{
PixelFormat format = PixelFormat.UNKNOWN;
if ((header.pixelformat.flags & DDPF_FOURCC) == DDPF_FOURCC)
{
blocksize = ((header.width + 3) / 4) * ((header.height + 3) / 4) * header.depth;
switch (header.pixelformat.fourcc)
{
case FOURCC_DXT1:
format = PixelFormat.DXT1;
blocksize *= 8;
break;
case FOURCC_DXT2:
format = PixelFormat.DXT2;
blocksize *= 16;
break;
case FOURCC_DXT3:
format = PixelFormat.DXT3;
blocksize *= 16;
break;
case FOURCC_DXT4:
format = PixelFormat.DXT4;
blocksize *= 16;
break;
case FOURCC_DXT5:
format = PixelFormat.DXT5;
blocksize *= 16;
break;
case FOURCC_ATI1:
format = PixelFormat.ATI1N;
blocksize *= 8;
break;
case FOURCC_ATI2:
format = PixelFormat.THREEDC;
blocksize *= 16;
break;
case FOURCC_RXGB:
format = PixelFormat.RXGB;
blocksize *= 16;
break;
case FOURCC_DOLLARNULL:
format = PixelFormat.A16B16G16R16;
blocksize = header.width * header.height * header.depth * 8;
break;
case FOURCC_oNULL:
format = PixelFormat.R16F;
blocksize = header.width * header.height * header.depth * 2;
break;
case FOURCC_pNULL:
format = PixelFormat.G16R16F;
blocksize = header.width * header.height * header.depth * 4;
break;
case FOURCC_qNULL:
format = PixelFormat.A16B16G16R16F;
blocksize = header.width * header.height * header.depth * 8;
break;
case FOURCC_rNULL:
format = PixelFormat.R32F;
blocksize = header.width * header.height * header.depth * 4;
break;
case FOURCC_sNULL:
format = PixelFormat.G32R32F;
blocksize = header.width * header.height * header.depth * 8;
break;
case FOURCC_tNULL:
format = PixelFormat.A32B32G32R32F;
blocksize = header.width * header.height * header.depth * 16;
break;
default:
format = PixelFormat.UNKNOWN;
blocksize *= 16;
break;
} // switch
}
else
{
// uncompressed image
if ((header.pixelformat.flags & DDPF_LUMINANCE) == DDPF_LUMINANCE)
{
if ((header.pixelformat.flags & DDPF_ALPHAPIXELS) == DDPF_ALPHAPIXELS)
{
format = PixelFormat.LUMINANCE_ALPHA;
}
else
{
format = PixelFormat.LUMINANCE;
}
}
else
{
if ((header.pixelformat.flags & DDPF_ALPHAPIXELS) == DDPF_ALPHAPIXELS)
{
format = PixelFormat.RGBA;
}
else
{
format = PixelFormat.RGB;
}
}
blocksize = (header.width * header.height * header.depth * (header.pixelformat.rgbbitcount >> 3));
}
return format;
}
#region Helper Methods
// iCompFormatToBpp
private uint PixelFormatToBpp(PixelFormat pf, uint rgbbitcount)
{
switch (pf)
{
case PixelFormat.LUMINANCE:
case PixelFormat.LUMINANCE_ALPHA:
case PixelFormat.RGBA:
case PixelFormat.RGB:
return rgbbitcount / 8;
case PixelFormat.THREEDC:
case PixelFormat.RXGB:
return 3;
case PixelFormat.ATI1N:
return 1;
case PixelFormat.R16F:
return 2;
case PixelFormat.A16B16G16R16:
case PixelFormat.A16B16G16R16F:
case PixelFormat.G32R32F:
return 8;
case PixelFormat.A32B32G32R32F:
return 16;
default:
return 4;
}
}
// iCompFormatToBpc
private uint PixelFormatToBpc(PixelFormat pf)
{
switch (pf)
{
case PixelFormat.R16F:
case PixelFormat.G16R16F:
case PixelFormat.A16B16G16R16F:
return 4;
case PixelFormat.R32F:
case PixelFormat.G32R32F:
case PixelFormat.A32B32G32R32F:
return 4;
case PixelFormat.A16B16G16R16:
return 2;
default:
return 1;
}
}
private bool Check16BitComponents(DDSStruct header)
{
if (header.pixelformat.rgbbitcount != 32)
return false;
// a2b10g10r10 format
if (header.pixelformat.rbitmask == 0x3FF00000 && header.pixelformat.gbitmask == 0x000FFC00 && header.pixelformat.bbitmask == 0x000003FF
&& header.pixelformat.alphabitmask == 0xC0000000)
return true;
// a2r10g10b10 format
else if (header.pixelformat.rbitmask == 0x000003FF && header.pixelformat.gbitmask == 0x000FFC00 && header.pixelformat.bbitmask == 0x3FF00000
&& header.pixelformat.alphabitmask == 0xC0000000)
return true;
return false;
}
private void CorrectPremult(uint pixnum, ref byte[] buffer)
{
for (uint i = 0; i < pixnum; i++)
{
byte alpha = buffer[i + 3];
if (alpha == 0) continue;
int red = (buffer[i] << 8) / alpha;
int green = (buffer[i + 1] << 8) / alpha;
int blue = (buffer[i + 2] << 8) / alpha;
buffer[i] = (byte)red;
buffer[i + 1] = (byte)green;
buffer[i + 2] = (byte)blue;
}
}
private void ComputeMaskParams(uint mask, ref int shift1, ref int mul, ref int shift2)
{
shift1 = 0; mul = 1; shift2 = 0;
while ((mask & 1) == 0)
{
mask >>= 1;
shift1++;
}
uint bc = 0;
while ((mask & (1 << (int)bc)) != 0) bc++;
while ((mask * mul) < 255)
mul = (mul << (int)bc) + 1;
mask *= (uint)mul;
while ((mask & ~0xff) != 0)
{
mask >>= 1;
shift2++;
}
}
private unsafe void DxtcReadColors(byte* data, ref Colour8888[] op)
{
byte r0, g0, b0, r1, g1, b1;
b0 = (byte)(data[0] & 0x1F);
g0 = (byte)(((data[0] & 0xE0) >> 5) | ((data[1] & 0x7) << 3));
r0 = (byte)((data[1] & 0xF8) >> 3);
b1 = (byte)(data[2] & 0x1F);
g1 = (byte)(((data[2] & 0xE0) >> 5) | ((data[3] & 0x7) << 3));
r1 = (byte)((data[3] & 0xF8) >> 3);
op[0].red = (byte)(r0 << 3 | r0 >> 2);
op[0].green = (byte)(g0 << 2 | g0 >> 3);
op[0].blue = (byte)(b0 << 3 | b0 >> 2);
op[1].red = (byte)(r1 << 3 | r1 >> 2);
op[1].green = (byte)(g1 << 2 | g1 >> 3);
op[1].blue = (byte)(b1 << 3 | b1 >> 2);
}
private void DxtcReadColor(ushort data, ref Colour8888 op)
{
byte r, g, b;
b = (byte)(data & 0x1f);
g = (byte)((data & 0x7E0) >> 5);
r = (byte)((data & 0xF800) >> 11);
op.red = (byte)(r << 3 | r >> 2);
op.green = (byte)(g << 2 | g >> 3);
op.blue = (byte)(b << 3 | r >> 2);
}
private unsafe void DxtcReadColors(byte* data, ref Colour565 color_0, ref Colour565 color_1)
{
color_0.blue = (byte)(data[0] & 0x1F);
color_0.green = (byte)(((data[0] & 0xE0) >> 5) | ((data[1] & 0x7) << 3));
color_0.red = (byte)((data[1] & 0xF8) >> 3);
color_0.blue = (byte)(data[2] & 0x1F);
color_0.green = (byte)(((data[2] & 0xE0) >> 5) | ((data[3] & 0x7) << 3));
color_0.red = (byte)((data[3] & 0xF8) >> 3);
}
private void GetBitsFromMask(uint mask, ref uint shiftLeft, ref uint shiftRight)
{
uint temp, i;
if (mask == 0)
{
shiftLeft = shiftRight = 0;
return;
}
temp = mask;
for (i = 0; i < 32; i++, temp >>= 1)
{
if ((temp & 1) != 0)
break;
}
shiftRight = i;
// Temp is preserved, so use it again:
for (i = 0; i < 8; i++, temp >>= 1)
{
if ((temp & 1) == 0)
break;
}
shiftLeft = 8 - i;
}
// This function simply counts how many contiguous bits are in the mask.
private uint CountBitsFromMask(uint mask)
{
uint i, testBit = 0x01, count = 0;
bool foundBit = false;
for (i = 0; i < 32; i++, testBit <<= 1)
{
if ((mask & testBit) != 0)
{
if (!foundBit)
foundBit = true;
count++;
}
else if (foundBit)
return count;
}
return count;
}
private uint HalfToFloat(ushort y)
{
int s = (y >> 15) & 0x00000001;
int e = (y >> 10) & 0x0000001f;
int m = y & 0x000003ff;
if (e == 0)
{
if (m == 0)
{
//
// Plus or minus zero
//
return (uint)(s << 31);
}
else
{
//
// Denormalized number -- renormalize it
//
while ((m & 0x00000400) == 0)
{
m <<= 1;
e -= 1;
}
e += 1;
m &= ~0x00000400;
}
}
else if (e == 31)
{
if (m == 0)
{
//
// Positive or negative infinity
//
return (uint)((s << 31) | 0x7f800000);
}
else
{
//
// Nan -- preserve sign and significand bits
//
return (uint)((s << 31) | 0x7f800000 | (m << 13));
}
}
//
// Normalized number
//
e = e + (127 - 15);
m = m << 13;
//
// Assemble s, e and m.
//
return (uint)((s << 31) | (e << 23) | m);
}
private unsafe void ConvFloat16ToFloat32(uint* dest, ushort* src, uint size)
{
uint i;
for (i = 0; i < size; ++i, ++dest, ++src)
{
//float: 1 sign bit, 8 exponent bits, 23 mantissa bits
//half: 1 sign bit, 5 exponent bits, 10 mantissa bits
*dest = HalfToFloat(*src);
}
}
private unsafe void ConvG16R16ToFloat32(uint* dest, ushort* src, uint size)
{
uint i;
for (i = 0; i < size; i += 3)
{
//float: 1 sign bit, 8 exponent bits, 23 mantissa bits
//half: 1 sign bit, 5 exponent bits, 10 mantissa bits
*dest++ = HalfToFloat(*src++);
*dest++ = HalfToFloat(*src++);
*((float*)dest++) = 1.0f;
}
}
private unsafe void ConvR16ToFloat32(uint* dest, ushort* src, uint size)
{
uint i;
for (i = 0; i < size; i += 3)
{
//float: 1 sign bit, 8 exponent bits, 23 mantissa bits
//half: 1 sign bit, 5 exponent bits, 10 mantissa bits
*dest++ = HalfToFloat(*src++);
*((float*)dest++) = 1.0f;
*((float*)dest++) = 1.0f;
}
}
#endregion
#region Decompress Methods
private byte[] DecompressData(DDSStruct header, byte[] data, PixelFormat pixelFormat)
{
System.Diagnostics.Debug.WriteLine(pixelFormat);
// allocate bitmap
byte[] rawData = null;
switch (pixelFormat)
{
case PixelFormat.RGBA:
rawData = this.DecompressRGBA(header, data, pixelFormat);
break;
case PixelFormat.RGB:
rawData = this.DecompressRGB(header, data, pixelFormat);
break;
case PixelFormat.LUMINANCE:
case PixelFormat.LUMINANCE_ALPHA:
rawData = this.DecompressLum(header, data, pixelFormat);
break;
case PixelFormat.DXT1:
rawData = this.DecompressDXT1(header, data, pixelFormat);
break;
case PixelFormat.DXT2:
rawData = this.DecompressDXT2(header, data, pixelFormat);
break;
case PixelFormat.DXT3:
rawData = this.DecompressDXT3(header, data, pixelFormat);
break;
case PixelFormat.DXT4:
rawData = this.DecompressDXT4(header, data, pixelFormat);
break;
case PixelFormat.DXT5:
rawData = this.DecompressDXT5(header, data, pixelFormat);
break;
case PixelFormat.THREEDC:
rawData = this.Decompress3Dc(header, data, pixelFormat);
break;
case PixelFormat.ATI1N:
rawData = this.DecompressAti1n(header, data, pixelFormat);
break;
case PixelFormat.RXGB:
rawData = this.DecompressRXGB(header, data, pixelFormat);
break;
case PixelFormat.R16F:
case PixelFormat.G16R16F:
case PixelFormat.A16B16G16R16F:
case PixelFormat.R32F:
case PixelFormat.G32R32F:
case PixelFormat.A32B32G32R32F:
rawData = this.DecompressFloat(header, data, pixelFormat);
break;
default:
throw new UnknownFileFormatException();
}
return rawData;
}
private unsafe byte[] DecompressDXT1(DDSStruct header, byte[] data, PixelFormat pixelFormat)
{
// allocate bitmap
int bpp = (int)(PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount));
int bps = (int)(header.width * bpp * PixelFormatToBpc(pixelFormat));
int sizeofplane = (int)(bps * header.height);
int width = (int)header.width;
int height = (int)header.height;
int depth = (int)header.depth;
// DXT1 decompressor
byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp];
Colour8888[] colours = new Colour8888[4];
colours[0].alpha = 0xFF;
colours[1].alpha = 0xFF;
colours[2].alpha = 0xFF;
fixed (byte* bytePtr = data)
{
byte* temp = bytePtr;
for (int z = 0; z < depth; z++)
{
for (int y = 0; y < height; y += 4)
{
for (int x = 0; x < width; x += 4)
{
ushort colour0 = *((ushort*)temp);
ushort colour1 = *((ushort*)(temp + 2));
DxtcReadColor(colour0, ref colours[0]);
DxtcReadColor(colour1, ref colours[1]);
uint bitmask = ((uint*)temp)[1];
temp += 8;
if (colour0 > colour1)
{
// Four-color block: derive the other two colors.
// 00 = color_0, 01 = color_1, 10 = color_2, 11 = color_3
// These 2-bit codes correspond to the 2-bit fields
// stored in the 64-bit block.
colours[2].blue = (byte)((2 * colours[0].blue + colours[1].blue + 1) / 3);
colours[2].green = (byte)((2 * colours[0].green + colours[1].green + 1) / 3);
colours[2].red = (byte)((2 * colours[0].red + colours[1].red + 1) / 3);
//colours[2].alpha = 0xFF;
colours[3].blue = (byte)((colours[0].blue + 2 * colours[1].blue + 1) / 3);
colours[3].green = (byte)((colours[0].green + 2 * colours[1].green + 1) / 3);
colours[3].red = (byte)((colours[0].red + 2 * colours[1].red + 1) / 3);
colours[3].alpha = 0xFF;
}
else
{
// Three-color block: derive the other color.
// 00 = color_0, 01 = color_1, 10 = color_2,
// 11 = transparent.
// These 2-bit codes correspond to the 2-bit fields
// stored in the 64-bit block.
colours[2].blue = (byte)((colours[0].blue + colours[1].blue) / 2);
colours[2].green = (byte)((colours[0].green + colours[1].green) / 2);
colours[2].red = (byte)((colours[0].red + colours[1].red) / 2);
//colours[2].alpha = 0xFF;
colours[3].blue = (byte)((colours[0].blue + 2 * colours[1].blue + 1) / 3);
colours[3].green = (byte)((colours[0].green + 2 * colours[1].green + 1) / 3);
colours[3].red = (byte)((colours[0].red + 2 * colours[1].red + 1) / 3);
colours[3].alpha = 0x00;
}
for (int j = 0, k = 0; j < 4; j++)
{
for (int i = 0; i < 4; i++, k++)
{
int select = (int)((bitmask & (0x03 << k * 2)) >> k * 2);
Colour8888 col = colours[select];
if (((x + i) < width) && ((y + j) < height))
{
uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp);
rawData[offset + 0] = (byte)col.red;
rawData[offset + 1] = (byte)col.green;
rawData[offset + 2] = (byte)col.blue;
rawData[offset + 3] = (byte)col.alpha;
}
}
}
}
}
}
}
return rawData;
}
private byte[] DecompressDXT2(DDSStruct header, byte[] data, PixelFormat pixelFormat)
{
// allocate bitmap
int width = (int)header.width;
int height = (int)header.height;
int depth = (int)header.depth;
// Can do color & alpha same as dxt3, but color is pre-multiplied
// so the result will be wrong unless corrected.
byte[] rawData = DecompressDXT3(header, data, pixelFormat);
CorrectPremult((uint)(width * height * depth), ref rawData);
return rawData;
}
private unsafe byte[] DecompressDXT3(DDSStruct header, byte[] data, PixelFormat pixelFormat)
{
// allocate bitmap
int bpp = (int)(PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount));
int bps = (int)(header.width * bpp * PixelFormatToBpc(pixelFormat));
int sizeofplane = (int)(bps * header.height);
int width = (int)header.width;
int height = (int)header.height;
int depth = (int)header.depth;
// DXT3 decompressor
byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp];
Colour8888[] colours = new Colour8888[4];
fixed (byte* bytePtr = data)
{
byte* temp = bytePtr;
for (int z = 0; z < depth; z++)
{
for (int y = 0; y < height; y += 4)
{
for (int x = 0; x < width; x += 4)
{
byte* alpha = temp;
temp += 8;
DxtcReadColors(temp, ref colours);
temp += 4;
uint bitmask = ((uint*)temp)[1];
temp += 4;
// Four-color block: derive the other two colors.
// 00 = color_0, 01 = color_1, 10 = color_2, 11 = color_3
// These 2-bit codes correspond to the 2-bit fields
// stored in the 64-bit block.
colours[2].blue = (byte)((2 * colours[0].blue + colours[1].blue + 1) / 3);
colours[2].green = (byte)((2 * colours[0].green + colours[1].green + 1) / 3);
colours[2].red = (byte)((2 * colours[0].red + colours[1].red + 1) / 3);
//colours[2].alpha = 0xFF;
colours[3].blue = (byte)((colours[0].blue + 2 * colours[1].blue + 1) / 3);
colours[3].green = (byte)((colours[0].green + 2 * colours[1].green + 1) / 3);
colours[3].red = (byte)((colours[0].red + 2 * colours[1].red + 1) / 3);
//colours[3].alpha = 0xFF;
for (int j = 0, k = 0; j < 4; j++)
{
for (int i = 0; i < 4; k++, i++)
{
int select = (int)((bitmask & (0x03 << k * 2)) >> k * 2);
if (((x + i) < width) && ((y + j) < height))
{
uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp);
rawData[offset + 0] = (byte)colours[select].red;
rawData[offset + 1] = (byte)colours[select].green;
rawData[offset + 2] = (byte)colours[select].blue;
}
}
}
for (int j = 0; j < 4; j++)
{
//ushort word = (ushort)(alpha[2 * j] + 256 * alpha[2 * j + 1]);
ushort word = (ushort)(alpha[2 * j] | (alpha[2 * j + 1] << 8));
for (int i = 0; i < 4; i++)
{
if (((x + i) < width) && ((y + j) < height))
{
uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp + 3);
rawData[offset] = (byte)(word & 0x0F);
rawData[offset] = (byte)(rawData[offset] | (rawData[offset] << 4));
}
word >>= 4;
}
}
}
}
}
}
return rawData;
}
private byte[] DecompressDXT4(DDSStruct header, byte[] data, PixelFormat pixelFormat)
{
// allocate bitmap
int width = (int)header.width;
int height = (int)header.height;
int depth = (int)header.depth;
// Can do color & alpha same as dxt5, but color is pre-multiplied
// so the result will be wrong unless corrected.
byte[] rawData = DecompressDXT5(header, data, pixelFormat);
CorrectPremult((uint)(width * height * depth), ref rawData);
return rawData;
}
private unsafe byte[] DecompressDXT5(DDSStruct header, byte[] data, PixelFormat pixelFormat)
{
// allocate bitmap
int bpp = (int)(PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount));
int bps = (int)(header.width * bpp * PixelFormatToBpc(pixelFormat));
int sizeofplane = (int)(bps * header.height);
int width = (int)header.width;
int height = (int)header.height;
int depth = (int)header.depth;
byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp];
Colour8888[] colours = new Colour8888[4];
ushort[] alphas = new ushort[8];
fixed (byte* bytePtr = data)
{
byte* temp = bytePtr;
for (int z = 0; z < depth; z++)
{
for (int y = 0; y < height; y += 4)
{
for (int x = 0; x < width; x += 4)
{
if (y >= height || x >= width)
break;
alphas[0] = temp[0];
alphas[1] = temp[1];
byte* alphamask = (temp + 2);
temp += 8;
DxtcReadColors(temp, ref colours);
uint bitmask = ((uint*)temp)[1];
temp += 8;
// Four-color block: derive the other two colors.
// 00 = color_0, 01 = color_1, 10 = color_2, 11 = color_3
// These 2-bit codes correspond to the 2-bit fields
// stored in the 64-bit block.
colours[2].blue = (byte)((2 * colours[0].blue + colours[1].blue + 1) / 3);
colours[2].green = (byte)((2 * colours[0].green + colours[1].green + 1) / 3);
colours[2].red = (byte)((2 * colours[0].red + colours[1].red + 1) / 3);
//colours[2].alpha = 0xFF;
colours[3].blue = (byte)((colours[0].blue + 2 * colours[1].blue + 1) / 3);
colours[3].green = (byte)((colours[0].green + 2 * colours[1].green + 1) / 3);
colours[3].red = (byte)((colours[0].red + 2 * colours[1].red + 1) / 3);
//colours[3].alpha = 0xFF;
int k = 0;
for (int j = 0; j < 4; j++)
{
for (int i = 0; i < 4; k++, i++)
{
int select = (int)((bitmask & (0x03 << k * 2)) >> k * 2);
Colour8888 col = colours[select];
// only put pixels out < width or height
if (((x + i) < width) && ((y + j) < height))
{
uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp);
rawData[offset] = (byte)col.red;
rawData[offset + 1] = (byte)col.green;
rawData[offset + 2] = (byte)col.blue;
}
}
}
// 8-alpha or 6-alpha block?
if (alphas[0] > alphas[1])
{
// 8-alpha block: derive the other six alphas.
// Bit code 000 = alpha_0, 001 = alpha_1, others are interpolated.
alphas[2] = (ushort)((6 * alphas[0] + 1 * alphas[1] + 3) / 7); // bit code 010
alphas[3] = (ushort)((5 * alphas[0] + 2 * alphas[1] + 3) / 7); // bit code 011
alphas[4] = (ushort)((4 * alphas[0] + 3 * alphas[1] + 3) / 7); // bit code 100
alphas[5] = (ushort)((3 * alphas[0] + 4 * alphas[1] + 3) / 7); // bit code 101
alphas[6] = (ushort)((2 * alphas[0] + 5 * alphas[1] + 3) / 7); // bit code 110
alphas[7] = (ushort)((1 * alphas[0] + 6 * alphas[1] + 3) / 7); // bit code 111
}
else
{
// 6-alpha block.
// Bit code 000 = alpha_0, 001 = alpha_1, others are interpolated.
alphas[2] = (ushort)((4 * alphas[0] + 1 * alphas[1] + 2) / 5); // Bit code 010
alphas[3] = (ushort)((3 * alphas[0] + 2 * alphas[1] + 2) / 5); // Bit code 011
alphas[4] = (ushort)((2 * alphas[0] + 3 * alphas[1] + 2) / 5); // Bit code 100
alphas[5] = (ushort)((1 * alphas[0] + 4 * alphas[1] + 2) / 5); // Bit code 101
alphas[6] = 0x00; // Bit code 110
alphas[7] = 0xFF; // Bit code 111
}
// Note: Have to separate the next two loops,
// it operates on a 6-byte system.
// First three bytes
//uint bits = (uint)(alphamask[0]);
uint bits = (uint)((alphamask[0]) | (alphamask[1] << 8) | (alphamask[2] << 16));
for (int j = 0; j < 2; j++)
{
for (int i = 0; i < 4; i++)
{
// only put pixels out < width or height
if (((x + i) < width) && ((y + j) < height))
{
uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp + 3);
rawData[offset] = (byte)alphas[bits & 0x07];
}
bits >>= 3;
}
}
// Last three bytes
//bits = (uint)(alphamask[3]);
bits = (uint)((alphamask[3]) | (alphamask[4] << 8) | (alphamask[5] << 16));
for (int j = 2; j < 4; j++)
{
for (int i = 0; i < 4; i++)
{
// only put pixels out < width or height
if (((x + i) < width) && ((y + j) < height))
{
uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp + 3);
rawData[offset] = (byte)alphas[bits & 0x07];
}
bits >>= 3;
}
}
}
}
}
}
return rawData;
}
private unsafe byte[] DecompressRGB(DDSStruct header, byte[] data, PixelFormat pixelFormat)
{
// allocate bitmap
int bpp = (int)(this.PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount));
int bps = (int)(header.width * bpp * this.PixelFormatToBpc(pixelFormat));
int sizeofplane = (int)(bps * header.height);
int width = (int)header.width;
int height = (int)header.height;
int depth = (int)header.depth;
byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp];
uint valMask = (uint)((header.pixelformat.rgbbitcount == 32) ? ~0 : (1 << (int)header.pixelformat.rgbbitcount) - 1);
uint pixSize = (uint)(((int)header.pixelformat.rgbbitcount + 7) / 8);
int rShift1 = 0; int rMul = 0; int rShift2 = 0;
ComputeMaskParams(header.pixelformat.rbitmask, ref rShift1, ref rMul, ref rShift2);
int gShift1 = 0; int gMul = 0; int gShift2 = 0;
ComputeMaskParams(header.pixelformat.gbitmask, ref gShift1, ref gMul, ref gShift2);
int bShift1 = 0; int bMul = 0; int bShift2= 0;
ComputeMaskParams(header.pixelformat.bbitmask, ref bShift1, ref bMul, ref bShift2);
int offset = 0;
int pixnum = width * height * depth;
fixed (byte* bytePtr = data)
{
byte* temp = bytePtr;
while (pixnum-- > 0)
{
uint px = *((uint*)temp) & valMask;
temp += pixSize;
uint pxc = px & header.pixelformat.rbitmask;
rawData[offset + 0] = (byte)(((pxc >> rShift1) * rMul) >> rShift2);
pxc = px & header.pixelformat.gbitmask;
rawData[offset + 1] = (byte)(((pxc >> gShift1) * gMul) >> gShift2);
pxc = px & header.pixelformat.bbitmask;
rawData[offset + 2] = (byte)(((pxc >> bShift1) * bMul) >> bShift2);
rawData[offset + 3] = 0xff;
offset += 4;
}
}
return rawData;
}
private unsafe byte[] DecompressRGBA(DDSStruct header, byte[] data, PixelFormat pixelFormat)
{
// allocate bitmap
int bpp = (int)(this.PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount));
int bps = (int)(header.width * bpp * this.PixelFormatToBpc(pixelFormat));
int sizeofplane = (int)(bps * header.height);
int width = (int)header.width;
int height = (int)header.height;
int depth = (int)header.depth;
byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp];
uint valMask = (uint)((header.pixelformat.rgbbitcount == 32) ? ~0 : (1 << (int)header.pixelformat.rgbbitcount) - 1);
// Funny x86s, make 1 << 32 == 1
uint pixSize = (header.pixelformat.rgbbitcount + 7) / 8;
int rShift1 = 0; int rMul = 0; int rShift2 = 0;
ComputeMaskParams(header.pixelformat.rbitmask, ref rShift1, ref rMul, ref rShift2);
int gShift1 = 0; int gMul = 0; int gShift2 = 0;
ComputeMaskParams(header.pixelformat.gbitmask, ref gShift1, ref gMul, ref gShift2);
int bShift1 = 0; int bMul = 0; int bShift2 = 0;
ComputeMaskParams(header.pixelformat.bbitmask, ref bShift1, ref bMul, ref bShift2);
int aShift1 = 0; int aMul = 0; int aShift2 = 0;
ComputeMaskParams(header.pixelformat.alphabitmask, ref aShift1, ref aMul, ref aShift2);
int offset = 0;
int pixnum = width * height * depth;
fixed (byte* bytePtr = data)
{
byte* temp = bytePtr;
while (pixnum-- > 0)
{
uint px = *((uint*)temp) & valMask;
temp += pixSize;
uint pxc = px & header.pixelformat.rbitmask;
rawData[offset + 0] = (byte)(((pxc >> rShift1) * rMul) >> rShift2);
pxc = px & header.pixelformat.gbitmask;
rawData[offset + 1] = (byte)(((pxc >> gShift1) * gMul) >> gShift2);
pxc = px & header.pixelformat.bbitmask;
rawData[offset + 2] = (byte)(((pxc >> bShift1) * bMul) >> bShift2);
pxc = px & header.pixelformat.alphabitmask;
rawData[offset + 3] = (byte)(((pxc >> aShift1) * aMul) >> aShift2);
offset += 4;
}
}
return rawData;
}
private unsafe byte[] Decompress3Dc(DDSStruct header, byte[] data, PixelFormat pixelFormat)
{
// allocate bitmap
int bpp = (int)(this.PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount));
int bps = (int)(header.width * bpp * this.PixelFormatToBpc(pixelFormat));
int sizeofplane = (int)(bps * header.height);
int width = (int)header.width;
int height = (int)header.height;
int depth = (int)header.depth;
byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp];
byte[] yColours = new byte[8];
byte[] xColours = new byte[8];
int offset = 0;
fixed (byte* bytePtr = data)
{
byte* temp = bytePtr;
for (int z = 0; z < depth; z++)
{
for (int y = 0; y < height; y += 4)
{
for (int x = 0; x < width; x += 4)
{
byte* temp2 = temp + 8;
//Read Y palette
int t1 = yColours[0] = temp[0];
int t2 = yColours[1] = temp[1];
temp += 2;
if (t1 > t2)
for (int i = 2; i < 8; ++i)
yColours[i] = (byte)(t1 + ((t2 - t1) * (i - 1)) / 7);
else
{
for (int i = 2; i < 6; ++i)
yColours[i] = (byte)(t1 + ((t2 - t1) * (i - 1)) / 5);
yColours[6] = 0;
yColours[7] = 255;
}
// Read X palette
t1 = xColours[0] = temp2[0];
t2 = xColours[1] = temp2[1];
temp2 += 2;
if (t1 > t2)
for (int i = 2; i < 8; ++i)
xColours[i] = (byte)(t1 + ((t2 - t1) * (i - 1)) / 7);
else
{
for (int i = 2; i < 6; ++i)
xColours[i] = (byte)(t1 + ((t2 - t1) * (i - 1)) / 5);
xColours[6] = 0;
xColours[7] = 255;
}
//decompress pixel data
int currentOffset = offset;
for (int k = 0; k < 4; k += 2)
{
// First three bytes
uint bitmask = ((uint)(temp[0]) << 0) | ((uint)(temp[1]) << 8) | ((uint)(temp[2]) << 16);
uint bitmask2 = ((uint)(temp2[0]) << 0) | ((uint)(temp2[1]) << 8) | ((uint)(temp2[2]) << 16);
for (int j = 0; j < 2; j++)
{
// only put pixels out < height
if ((y + k + j) < height)
{
for (int i = 0; i < 4; i++)
{
// only put pixels out < width
if (((x + i) < width))
{
int t;
byte tx, ty;
t1 = currentOffset + (x + i) * 3;
rawData[t1 + 1] = ty = yColours[bitmask & 0x07];
rawData[t1 + 0] = tx = xColours[bitmask2 & 0x07];
//calculate b (z) component ((r/255)^2 + (g/255)^2 + (b/255)^2 = 1
t = 127 * 128 - (tx - 127) * (tx - 128) - (ty - 127) * (ty - 128);
if (t > 0)
rawData[t1 + 2] = (byte)(Math.Sqrt(t) + 128);
else
rawData[t1 + 2] = 0x7F;
}
bitmask >>= 3;
bitmask2 >>= 3;
}
currentOffset += bps;
}
}
temp += 3;
temp2 += 3;
}
//skip bytes that were read via Temp2
temp += 8;
}
offset += bps * 4;
}
}
}
return rawData;
}
private unsafe byte[] DecompressAti1n(DDSStruct header, byte[] data, PixelFormat pixelFormat)
{
// allocate bitmap
int bpp = (int)(this.PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount));
int bps = (int)(header.width * bpp * this.PixelFormatToBpc(pixelFormat));
int sizeofplane = (int)(bps * header.height);
int width = (int)header.width;
int height = (int)header.height;
int depth = (int)header.depth;
byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp];
byte[] colours = new byte[8];
uint offset = 0;
fixed (byte* bytePtr = data)
{
byte* temp = bytePtr;
for (int z = 0; z < depth; z++)
{
for (int y = 0; y < height; y += 4)
{
for (int x = 0; x < width; x += 4)
{
//Read palette
int t1 = colours[0] = temp[0];
int t2 = colours[1] = temp[1];
temp += 2;
if (t1 > t2)
for (int i = 2; i < 8; ++i)
colours[i] = (byte)(t1 + ((t2 - t1) * (i - 1)) / 7);
else
{
for (int i = 2; i < 6; ++i)
colours[i] = (byte)(t1 + ((t2 - t1) * (i - 1)) / 5);
colours[6] = 0;
colours[7] = 255;
}
//decompress pixel data
uint currOffset = offset;
for (int k = 0; k < 4; k += 2)
{
// First three bytes
uint bitmask = ((uint)(temp[0]) << 0) | ((uint)(temp[1]) << 8) | ((uint)(temp[2]) << 16);
for (int j = 0; j < 2; j++)
{
// only put pixels out < height
if ((y + k + j) < height)
{
for (int i = 0; i < 4; i++)
{
// only put pixels out < width
if (((x + i) < width))
{
t1 = (int)(currOffset + (x + i));
rawData[t1] = colours[bitmask & 0x07];
}
bitmask >>= 3;
}
currOffset += (uint)bps;
}
}
temp += 3;
}
}
offset += (uint)(bps * 4);
}
}
}
return rawData;
}
private unsafe byte[] DecompressLum(DDSStruct header, byte[] data, PixelFormat pixelFormat)
{
// allocate bitmap
int bpp = (int)(this.PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount));
int bps = (int)(header.width * bpp * this.PixelFormatToBpc(pixelFormat));
int sizeofplane = (int)(bps * header.height);
int width = (int)header.width;
int height = (int)header.height;
int depth = (int)header.depth;
byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp];
int lShift1 = 0; int lMul = 0; int lShift2 = 0;
ComputeMaskParams(header.pixelformat.rbitmask, ref lShift1, ref lMul, ref lShift2);
int offset = 0;
int pixnum = width * height * depth;
fixed (byte* bytePtr = data)
{
byte* temp = bytePtr;
while (pixnum-- > 0)
{
byte px = *(temp++);
rawData[offset + 0] = (byte)(((px >> lShift1) * lMul) >> lShift2);
rawData[offset + 1] = (byte)(((px >> lShift1) * lMul) >> lShift2);
rawData[offset + 2] = (byte)(((px >> lShift1) * lMul) >> lShift2);
rawData[offset + 3] = (byte)(((px >> lShift1) * lMul) >> lShift2);
offset += 4;
}
}
return rawData;
}
private unsafe byte[] DecompressRXGB(DDSStruct header, byte[] data, PixelFormat pixelFormat)
{
// allocate bitmap
int bpp = (int)(this.PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount));
int bps = (int)(header.width * bpp * this.PixelFormatToBpc(pixelFormat));
int sizeofplane = (int)(bps * header.height);
int width = (int)header.width;
int height = (int)header.height;
int depth = (int)header.depth;
byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp];
Colour565 color_0 = new Colour565();
Colour565 color_1 = new Colour565();
Colour8888[] colours = new Colour8888[4];
byte[] alphas = new byte[8];
fixed (byte* bytePtr = data)
{
byte* temp = bytePtr;
for (int z = 0; z < depth; z++)
{
for (int y = 0; y < height; y += 4)
{
for (int x = 0; x < width; x += 4)
{
if (y >= height || x >= width)
break;
alphas[0] = temp[0];
alphas[1] = temp[1];
byte* alphamask = temp + 2;
temp += 8;
DxtcReadColors(temp, ref color_0, ref color_1);
temp += 4;
uint bitmask = ((uint*)temp)[1];
temp += 4;
colours[0].red = (byte)(color_0.red << 3);
colours[0].green = (byte)(color_0.green << 2);
colours[0].blue = (byte)(color_0.blue << 3);
colours[0].alpha = 0xFF;
colours[1].red = (byte)(color_1.red << 3);
colours[1].green = (byte)(color_1.green << 2);
colours[1].blue = (byte)(color_1.blue << 3);
colours[1].alpha = 0xFF;
// Four-color block: derive the other two colors.
// 00 = color_0, 01 = color_1, 10 = color_2, 11 = color_3
// These 2-bit codes correspond to the 2-bit fields
// stored in the 64-bit block.
colours[2].blue = (byte)((2 * colours[0].blue + colours[1].blue + 1) / 3);
colours[2].green = (byte)((2 * colours[0].green + colours[1].green + 1) / 3);
colours[2].red = (byte)((2 * colours[0].red + colours[1].red + 1) / 3);
colours[2].alpha = 0xFF;
colours[3].blue = (byte)((colours[0].blue + 2 * colours[1].blue + 1) / 3);
colours[3].green = (byte)((colours[0].green + 2 * colours[1].green + 1) / 3);
colours[3].red = (byte)((colours[0].red + 2 * colours[1].red + 1) / 3);
colours[3].alpha = 0xFF;
int k = 0;
for (int j = 0; j < 4; j++)
{
for (int i = 0; i < 4; i++, k++)
{
int select = (int)((bitmask & (0x03 << k * 2)) >> k * 2);
Colour8888 col = colours[select];
// only put pixels out < width or height
if (((x + i) < width) && ((y + j) < height))
{
uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp);
rawData[offset + 0] = col.red;
rawData[offset + 1] = col.green;
rawData[offset + 2] = col.blue;
}
}
}
// 8-alpha or 6-alpha block?
if (alphas[0] > alphas[1])
{
// 8-alpha block: derive the other six alphas.
// Bit code 000 = alpha_0, 001 = alpha_1, others are interpolated.
alphas[2] = (byte)((6 * alphas[0] + 1 * alphas[1] + 3) / 7); // bit code 010
alphas[3] = (byte)((5 * alphas[0] + 2 * alphas[1] + 3) / 7); // bit code 011
alphas[4] = (byte)((4 * alphas[0] + 3 * alphas[1] + 3) / 7); // bit code 100
alphas[5] = (byte)((3 * alphas[0] + 4 * alphas[1] + 3) / 7); // bit code 101
alphas[6] = (byte)((2 * alphas[0] + 5 * alphas[1] + 3) / 7); // bit code 110
alphas[7] = (byte)((1 * alphas[0] + 6 * alphas[1] + 3) / 7); // bit code 111
}
else
{
// 6-alpha block.
// Bit code 000 = alpha_0, 001 = alpha_1, others are interpolated.
alphas[2] = (byte)((4 * alphas[0] + 1 * alphas[1] + 2) / 5); // Bit code 010
alphas[3] = (byte)((3 * alphas[0] + 2 * alphas[1] + 2) / 5); // Bit code 011
alphas[4] = (byte)((2 * alphas[0] + 3 * alphas[1] + 2) / 5); // Bit code 100
alphas[5] = (byte)((1 * alphas[0] + 4 * alphas[1] + 2) / 5); // Bit code 101
alphas[6] = 0x00; // Bit code 110
alphas[7] = 0xFF; // Bit code 111
}
// Note: Have to separate the next two loops,
// it operates on a 6-byte system.
// First three bytes
uint bits = *((uint*)alphamask);
for (int j = 0; j < 2; j++)
{
for (int i = 0; i < 4; i++)
{
// only put pixels out < width or height
if (((x + i) < width) && ((y + j) < height))
{
uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp + 3);
rawData[offset] = alphas[bits & 0x07];
}
bits >>= 3;
}
}
// Last three bytes
bits = *((uint*)&alphamask[3]);
for (int j = 2; j < 4; j++)
{
for (int i = 0; i < 4; i++)
{
// only put pixels out < width or height
if (((x + i) < width) && ((y + j) < height))
{
uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp + 3);
rawData[offset] = alphas[bits & 0x07];
}
bits >>= 3;
}
}
}
}
}
}
return rawData;
}
private unsafe byte[] DecompressFloat(DDSStruct header, byte[] data, PixelFormat pixelFormat)
{
// allocate bitmap
int bpp = (int)(this.PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount));
int bps = (int)(header.width * bpp * this.PixelFormatToBpc(pixelFormat));
int sizeofplane = (int)(bps * header.height);
int width = (int)header.width;
int height = (int)header.height;
int depth = (int)header.depth;
byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp];
int size = 0;
fixed (byte* bytePtr = data)
{
byte* temp = bytePtr;
fixed (byte* destPtr = rawData)
{
byte* destData = destPtr;
switch (pixelFormat)
{
case PixelFormat.R32F: // Red float, green = blue = max
size = width * height * depth * 3;
for (int i = 0, j = 0; i < size; i += 3, j++)
{
((float*)destData)[i] = ((float*)temp)[j];
((float*)destData)[i + 1] = 1.0f;
((float*)destData)[i + 2] = 1.0f;
}
break;
case PixelFormat.A32B32G32R32F: // Direct copy of float RGBA data
Array.Copy(data, rawData, data.Length);
break;
case PixelFormat.G32R32F: // Red float, green float, blue = max
size = width * height * depth * 3;
for (int i = 0, j = 0; i < size; i += 3, j += 2)
{
((float*)destData)[i] = ((float*)temp)[j];
((float*)destData)[i + 1] = ((float*)temp)[j + 1];
((float*)destData)[i + 2] = 1.0f;
}
break;
case PixelFormat.R16F: // Red float, green = blue = max
size = width * height * depth * bpp;
ConvR16ToFloat32((uint*)destData, (ushort*)temp, (uint)size);
break;
case PixelFormat.A16B16G16R16F: // Just convert from half to float.
size = width * height * depth * bpp;
ConvFloat16ToFloat32((uint*)destData, (ushort*)temp, (uint)size);
break;
case PixelFormat.G16R16F: // Convert from half to float, set blue = max.
size = width * height * depth * bpp;
ConvG16R16ToFloat32((uint*)destData, (ushort*)temp, (uint)size);
break;
default:
break;
}
}
}
return rawData;
}
#region UNUSED
private unsafe byte[] DecompressARGB(DDSStruct header, byte[] data, PixelFormat pixelFormat)
{
// allocate bitmap
int bpp = (int)(PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount));
int bps = (int)(header.width * bpp * PixelFormatToBpc(pixelFormat));
int sizeofplane = (int)(bps * header.height);
int width = (int)header.width;
int height = (int)header.height;
int depth = (int)header.depth;
if (Check16BitComponents(header))
return DecompressARGB16(header, data, pixelFormat);
int sizeOfData = (int)((header.width * header.pixelformat.rgbbitcount / 8) * header.height * header.depth);
byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp];
if ((pixelFormat == PixelFormat.LUMINANCE) && (header.pixelformat.rgbbitcount == 16) && (header.pixelformat.rbitmask == 0xFFFF))
{
Array.Copy(data, rawData, data.Length);
return rawData;
}
uint readI = 0, tempBpp;
uint redL = 0, redR = 0;
uint greenL = 0, greenR = 0;
uint blueL = 0, blueR = 0;
uint alphaL = 0, alphaR = 0;
GetBitsFromMask(header.pixelformat.rbitmask, ref redL, ref redR);
GetBitsFromMask(header.pixelformat.gbitmask, ref greenL, ref greenR);
GetBitsFromMask(header.pixelformat.bbitmask, ref blueL, ref blueR);
GetBitsFromMask(header.pixelformat.alphabitmask, ref alphaL, ref alphaR);
tempBpp = header.pixelformat.rgbbitcount / 8;
fixed (byte* bytePtr = data)
{
byte* temp = bytePtr;
for (int i = 0; i < sizeOfData; i += bpp)
{
//@TODO: This is SLOOOW...
//but the old version crashed in release build under
//winxp (and xp is right to stop this code - I always
//wondered that it worked the old way at all)
if (sizeOfData - i < 4)
{
//less than 4 byte to write?
if (tempBpp == 3)
{
//this branch is extra-SLOOOW
readI = (uint)(*temp | ((*(temp + 1)) << 8) | ((*(temp + 2)) << 16));
}
else if (tempBpp == 1)
readI = *((byte*)temp);
else if (tempBpp == 2)
readI = (uint)(temp[0] | (temp[1] << 8));
}
else
readI = (uint)(temp[0] | (temp[1] << 8) | (temp[2] << 16) | (temp[3] << 24));
temp += tempBpp;
rawData[i] = (byte)((((int)readI & (int)header.pixelformat.rbitmask) >> (int)redR) << (int)redL);
if (bpp >= 3)
{
rawData[i + 1] = (byte)((((int)readI & (int)header.pixelformat.gbitmask) >> (int)greenR) << (int)greenL);
rawData[i + 2] = (byte)((((int)readI & header.pixelformat.bbitmask) >> (int)blueR) << (int)blueL);
if (bpp == 4)
{
rawData[i + 3] = (byte)((((int)readI & (int)header.pixelformat.alphabitmask) >> (int)alphaR) << (int)alphaL);
if (alphaL >= 7)
{
rawData[i + 3] = (byte)(rawData[i + 3] != 0 ? 0xFF : 0x00);
}
else if (alphaL >= 4)
{
rawData[i + 3] = (byte)(rawData[i + 3] | (rawData[i + 3] >> 4));
}
}
}
else if (bpp == 2)
{
rawData[i + 1] = (byte)((((int)readI & (int)header.pixelformat.alphabitmask) >> (int)alphaR) << (int)alphaL);
if (alphaL >= 7)
{
rawData[i + 1] = (byte)(rawData[i + 1] != 0 ? 0xFF : 0x00);
}
else if (alphaL >= 4)
{
rawData[i + 1] = (byte)(rawData[i + 1] | (rawData[i + 3] >> 4));
}
}
}
}
return rawData;
}
private unsafe byte[] DecompressARGB16(DDSStruct header, byte[] data, PixelFormat pixelFormat)
{
// allocate bitmap
int bpp = (int)(PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount));
int bps = (int)(header.width * bpp * PixelFormatToBpc(pixelFormat));
int sizeofplane = (int)(bps * header.height);
int width = (int)header.width;
int height = (int)header.height;
int depth = (int)header.depth;
int sizeOfData = (int)((header.width * header.pixelformat.rgbbitcount / 8) * header.height * header.depth);
byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp];
uint readI = 0, tempBpp = 0;
uint redL = 0, redR = 0;
uint greenL = 0, greenR = 0;
uint blueL = 0, blueR = 0;
uint alphaL = 0, alphaR = 0;
uint redPad = 0, greenPad = 0, bluePad = 0, alphaPad = 0;
GetBitsFromMask(header.pixelformat.rbitmask, ref redL, ref redR);
GetBitsFromMask(header.pixelformat.gbitmask, ref greenL, ref greenR);
GetBitsFromMask(header.pixelformat.bbitmask, ref blueL, ref blueR);
GetBitsFromMask(header.pixelformat.alphabitmask, ref alphaL, ref alphaR);
redPad = 16 - CountBitsFromMask(header.pixelformat.rbitmask);
greenPad = 16 - CountBitsFromMask(header.pixelformat.gbitmask);
bluePad = 16 - CountBitsFromMask(header.pixelformat.bbitmask);
alphaPad = 16 - CountBitsFromMask(header.pixelformat.alphabitmask);
redL = redL + redPad;
greenL = greenL + greenPad;
blueL = blueL + bluePad;
alphaL = alphaL + alphaPad;
tempBpp = header.pixelformat.rgbbitcount / 8;
fixed (byte* bytePtr = data)
{
byte* temp = bytePtr;
fixed (byte* destPtr = rawData)
{
byte* destData = destPtr;
for (int i = 0; i < sizeOfData / 2; i += bpp)
{
//@TODO: This is SLOOOW...
//but the old version crashed in release build under
//winxp (and xp is right to stop this code - I always
//wondered that it worked the old way at all)
if (sizeOfData - i < 4)
{
//less than 4 byte to write?
if (tempBpp == 3)
{
//this branch is extra-SLOOOW
readI = (uint)(*temp | ((*(temp + 1)) << 8) | ((*(temp + 2)) << 16));
}
else if (tempBpp == 1)
readI = *((byte*)temp);
else if (tempBpp == 2)
readI = (uint)(temp[0] | (temp[1] << 8));
}
else
readI = (uint)(temp[0] | (temp[1] << 8) | (temp[2] << 16) | (temp[3] << 24));
temp += tempBpp;
((ushort*)destData)[i + 2] = (ushort)((((int)readI & (int)header.pixelformat.rbitmask) >> (int)redR) << (int)redL);
if (bpp >= 3)
{
((ushort*)destData)[i + 1] = (ushort)((((int)readI & (int)header.pixelformat.gbitmask) >> (int)greenR) << (int)greenL);
((ushort*)destData)[i] = (ushort)((((int)readI & (int)header.pixelformat.bbitmask) >> (int)blueR) << (int)blueL);
if (bpp == 4)
{
((ushort*)destData)[i + 3] = (ushort)((((int)readI & (int)header.pixelformat.alphabitmask) >> (int)alphaR) << (int)alphaL);
if (alphaL >= 7)
{
((ushort*)destData)[i + 3] = (ushort)(((ushort*)destData)[i + 3] != 0 ? 0xFF : 0x00);
}
else if (alphaL >= 4)
{
((ushort*)destData)[i + 3] = (ushort)(((ushort*)destData)[i + 3] | (((ushort*)destData)[i + 3] >> 4));
}
}
}
else if (bpp == 2)
{
((ushort*)destData)[i + 1] = (ushort)((((int)readI & (int)header.pixelformat.alphabitmask) >> (int)alphaR) << (int)alphaL);
if (alphaL >= 7)
{
((ushort*)destData)[i + 1] = (ushort)(((ushort*)destData)[i + 1] != 0 ? 0xFF : 0x00);
}
else if (alphaL >= 4)
{
((ushort*)destData)[i + 1] = (ushort)(((ushort*)destData)[i + 1] | (rawData[i + 3] >> 4));
}
}
}
}
}
return rawData;
}
#endregion
#endregion
#endregion
#region Public Methods
public void Dispose()
{
if (this.m_bitmap != null)
{
this.m_bitmap.Dispose();
this.m_bitmap = null;
}
}
#endregion
#region Properties
/// <summary>
/// Returns a System.Imaging.Bitmap containing the DDS image.
/// </summary>
public System.Drawing.Bitmap BitmapImage
{
get { return this.m_bitmap; }
}
/// <summary>
/// Returns the DDS image is valid format.
/// </summary>
public bool IsValid
{
get { return this.m_isValid; }
}
#endregion
#region Operators
public static implicit operator DDSImage(System.Drawing.Bitmap value)
{
return new DDSImage(value);
}
public static explicit operator System.Drawing.Bitmap(DDSImage value)
{
return value.BitmapImage;
}
#endregion
#region Nested Types
#region Colour8888
[StructLayout(LayoutKind.Sequential)]
private struct Colour8888
{
public byte red;
public byte green;
public byte blue;
public byte alpha;
}
#endregion
#region Colour565
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct Colour565
{
public ushort blue; //: 5;
public ushort green; //: 6;
public ushort red; //: 5;
}
#endregion
#region DDSStruct
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct DDSStruct
{
public uint size; // equals size of struct (which is part of the data file!)
public uint flags;
public uint height;
public uint width;
public uint sizeorpitch;
public uint depth;
public uint mipmapcount;
public uint alphabitdepth;
//[MarshalAs(UnmanagedType.U4, SizeConst = 11)]
public uint[] reserved;//[11];
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct pixelformatstruct
{
public uint size; // equals size of struct (which is part of the data file!)
public uint flags;
public uint fourcc;
public uint rgbbitcount;
public uint rbitmask;
public uint gbitmask;
public uint bbitmask;
public uint alphabitmask;
}
public pixelformatstruct pixelformat;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct ddscapsstruct
{
public uint caps1;
public uint caps2;
public uint caps3;
public uint caps4;
}
public ddscapsstruct ddscaps;
public uint texturestage;
//#ifndef __i386__
//void to_little_endian()
//{
// size_t size = sizeof(DDSStruct);
// assert(size % 4 == 0);
// size /= 4;
// for (size_t i=0; i<size; i++)
// {
// ((int32_t*) this)[i] = little_endian(((int32_t*) this)[i]);
// }
//}
//#endif
}
#endregion
#region DDSStruct Flags
private const int DDSD_CAPS = 0x00000001;
private const int DDSD_HEIGHT = 0x00000002;
private const int DDSD_WIDTH = 0x00000004;
private const int DDSD_PITCH = 0x00000008;
private const int DDSD_PIXELFORMAT = 0x00001000;
private const int DDSD_MIPMAPCOUNT = 0x00020000;
private const int DDSD_LINEARSIZE = 0x00080000;
private const int DDSD_DEPTH = 0x00800000;
#endregion
#region pixelformat values
private const int DDPF_ALPHAPIXELS = 0x00000001;
private const int DDPF_FOURCC = 0x00000004;
private const int DDPF_RGB = 0x00000040;
private const int DDPF_LUMINANCE = 0x00020000;
#endregion
#region ddscaps
// caps1
private const int DDSCAPS_COMPLEX = 0x00000008;
private const int DDSCAPS_TEXTURE = 0x00001000;
private const int DDSCAPS_MIPMAP = 0x00400000;
// caps2
private const int DDSCAPS2_CUBEMAP = 0x00000200;
private const int DDSCAPS2_CUBEMAP_POSITIVEX = 0x00000400;
private const int DDSCAPS2_CUBEMAP_NEGATIVEX = 0x00000800;
private const int DDSCAPS2_CUBEMAP_POSITIVEY = 0x00001000;
private const int DDSCAPS2_CUBEMAP_NEGATIVEY = 0x00002000;
private const int DDSCAPS2_CUBEMAP_POSITIVEZ = 0x00004000;
private const int DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x00008000;
private const int DDSCAPS2_VOLUME = 0x00200000;
#endregion
#region fourccs
private const uint FOURCC_DXT1 = 0x31545844;
private const uint FOURCC_DXT2 = 0x32545844;
private const uint FOURCC_DXT3 = 0x33545844;
private const uint FOURCC_DXT4 = 0x34545844;
private const uint FOURCC_DXT5 = 0x35545844;
private const uint FOURCC_ATI1 = 0x31495441;
private const uint FOURCC_ATI2 = 0x32495441;
private const uint FOURCC_RXGB = 0x42475852;
private const uint FOURCC_DOLLARNULL = 0x24;
private const uint FOURCC_oNULL = 0x6f;
private const uint FOURCC_pNULL = 0x70;
private const uint FOURCC_qNULL = 0x71;
private const uint FOURCC_rNULL = 0x72;
private const uint FOURCC_sNULL = 0x73;
private const uint FOURCC_tNULL = 0x74;
#endregion
#region PixelFormat
/// <summary>
/// Various pixel formats/compressors used by the DDS image.
/// </summary>
private enum PixelFormat
{
/// <summary>
/// 32-bit image, with 8-bit red, green, blue and alpha.
/// </summary>
RGBA,
/// <summary>
/// 24-bit image with 8-bit red, green, blue.
/// </summary>
RGB,
/// <summary>
/// 16-bit DXT-1 compression, 1-bit alpha.
/// </summary>
DXT1,
/// <summary>
/// DXT-2 Compression
/// </summary>
DXT2,
/// <summary>
/// DXT-3 Compression
/// </summary>
DXT3,
/// <summary>
/// DXT-4 Compression
/// </summary>
DXT4,
/// <summary>
/// DXT-5 Compression
/// </summary>
DXT5,
/// <summary>
/// 3DC Compression
/// </summary>
THREEDC,
/// <summary>
/// ATI1n Compression
/// </summary>
ATI1N,
LUMINANCE,
LUMINANCE_ALPHA,
RXGB,
A16B16G16R16,
R16F,
G16R16F,
A16B16G16R16F,
R32F,
G32R32F,
A32B32G32R32F,
/// <summary>
/// Unknown pixel format.
/// </summary>
UNKNOWN
}
#endregion
#endregion
}
#endregion
#region Exceptions Class
/// <summary>
/// Thrown when an invalid file header has been encountered.
/// </summary>
public class InvalidFileHeaderException : Exception
{
}
/// <summary>
/// Thrown when the data does not contain a DDS image.
/// </summary>
public class NotADDSImageException : Exception
{
}
/// <summary>
/// Thrown when there is an unknown compressor used in the DDS file.
/// </summary>
public class UnknownFileFormatException : Exception
{
}
#endregion
}
@DCxDemo
Copy link

DCxDemo commented Jul 20, 2015

Alpha channel in DXT5 textures is decoded incorrectly.
Example: http://levelmod.webatu.com/test.png

also this line seems to be mistyped:
case PixelFormat.DXT4: rawData = this.DecompressDXT3(header, data, pixelFormat);

@micenote
Copy link

DecompressDXT5
alphas[0] = temp[0];
can not load alpha channel.

@Paliha
Copy link

Paliha commented Mar 24, 2016

Грамотный нейм_спейс, и стрим есть, и из файла парсит, выручил настолько что из проекта удалил три сторонних dll. ОГРОМНОЕ СПАСИБО.
MemoryStream PCDStream = new MemoryStream(Data, 0, Data.Length);
var ResourceStream = ConvertPCD9ToDDSv22 (PCDStream,VersionDRM);
ResourceStream.Position = 0;
var DDSImage = new DDSImage (ResourceStream);
var Bitmap = DDSImage.BitmapImage;
previewPictureBox.Image = Bitmap;

И всё...)))

пыс.пыс
@Альфа-канал в DXT5 текстур декодируется неправильно.
Всё правильно декодируется, разбирайтесь с тем что на входе.

@tarek1500
Copy link

"uint bps = header.width * header.pixelformat.rgbbitcount / 8;"
"bps" will equal to 0 if the DDS file doesn't contain RGBbitcount.

@DCxDemo
Copy link

DCxDemo commented May 8, 2016

Всё правильно декодируется, разбирайтесь с тем что на входе.

там скрин прикреплён с paint.net с того же файла. в паинте ок, в либе не ок. ну да, писать научили, а смотреть, видимо, не научили.

@Gerysis
Copy link

Gerysis commented Aug 13, 2016

Нужно заменить в декомпрессоре DXT3 следующую строку:

    private unsafe byte[] DecompressDXT3(DDSStruct header, byte[] data, PixelFormat pixelFormat)
    {
        // allocate bitmap
        int bpp = (int)(PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount));
        int bps = (int)(header.width * bpp * PixelFormatToBpc(pixelFormat));
        int sizeofplane = (int)(bps * header.height);
        int width = (int)header.width;
        int height = (int)header.height;
        int depth = (int)header.depth;

        // DXT3 decompressor
        byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp];
        Colour8888[] colours = new Colour8888[4];

        fixed (byte* bytePtr = data)
        {
            byte* temp = bytePtr;
            for (int z = 0; z < depth; z++)
            {
                for (int y = 0; y < height; y += 4)
                {
                    for (int x = 0; x < width; x += 4)
                    {
                        byte* alpha = temp;
                        temp += 8;

                        DxtcReadColors(temp, ref colours);
                        temp += 4;

                        **uint bitmask = ((uint*)temp)[1];// заменить 1  на 0**
                        temp += 4;

здесь неправильно бралась битовая маска. Проверил DXT1 и DXT5, они конвертятся нормально.

@LeeC2202
Copy link

As other people have said (over TWO years ago "micenote commented on 15 Mar 2016") DXT5 decompression is broken, it throws an exception on this line

alphas[0] = temp[0];

I don't understand what needs changing to fix it, if I knew that, I probably wouldn't have needed to use this library, as I would have known how to write my own.

@Bxaa
Copy link

Bxaa commented May 28, 2022

BC_7 support?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment