DirectX Image (.dds) File Format Explained
by bkenwright@xbdev.net
It seems to be popping up all the times these days....I mean you see tga, bmp
and jpg a lot, but if your doing 3d programming or touching directx in any way,
you'll know what the dds format is. Basically m$ decided to do there own
file format for images, which took into acount how the graphics card works with
textures and lossless compression techniques.
As the format is supported with photoshop and allows you to export it.
What we'll be doing here, is seeing what exactly is in there. Be good to
poke around, read in a few bytes and analyse how it works and how we could use
it in custom demos that don't actually have directx in them.
For my test dds file, I've started with a simple example that I created
myself in photoshop - its a small cross 8x8 pixels in size and was exported in
ARGB(8:8:8:8). Don't want to complicate things to start with. Of
course theres dozens of different compression methods possible with this dds
format, and hopefully we'll try and cover some of the simple ones as we go
along.
|
code010 - Download Source
Code |
////////////////////////////////////////////////////////////////////////////////////////
// //
// File: main.cpp //
// Author: bkenwright@xbdev.net //
// //
////////////////////////////////////////////////////////////////////////////////////////
#include <stdio.h> // So we can use fopen and fread
// Simple Debug Feedback - writes to a simple text file located where the exe is.
void debug(char* str)
{
FILE* fp = fopen("dbg.txt", "a+");
fprintf(fp, "%s\n", str);
fclose(fp);
}// End of debug(..)
char buf[500]; // Temp buffer for text output
// Program Entry Point - Where we always start!
void main()
{
FILE* fp = fopen("cross.dds", "rb");
char readbuf[3]; // Temp Buffer
// +- Buffer to put the data in
// | +- Size of each value read in bytes (1 byte each)
// | | +- How many times to read in the value (e.g. twice read in 1 byte)
// | | | +- Stream souce
// | | | |
fread(readbuf, 1, 3, fp);
debug("First Three Bytes:");
// Write the 3 bytes as characters to our debug file
sprintf(buf, "%c %c %c", readbuf[0], readbuf[1], readbuf[2]);
debug(buf);
fclose(fp);
}// End of main()
|
output.txt |
First Three Bytes:
D D S |
Of course most formats start with some magic numbers so that we can identify
what file we are dealing with. The next part is to actually get
information about the image file we have, such as image width, compression etc.
I don't want to include any external files, so I've copied the important
parts from ddraw.h header file which define the, DDSURFACE2 structure,
which is how the header file is defined. Its probably a good idea to put
these structure defines in there own .h file, something called ddstypes.h, or
something like that....but for now I've just put them in our file.
NOTE! I've only read in 3 bytes for the ID above, but in fact we should read
in 4 bytes, as the DDSURFACEDESC2 header data is offset from the start of the
file by 4 bytes. The 4th byte is just empty.
code020 : Download Source Code |
////////////////////////////////////////////////////////////////////////////////////////
// //
// File: main.cpp //
// Author: bkenwright@xbdev.net //
// //
////////////////////////////////////////////////////////////////////////////////////////
#include <stdio.h> // So we can use fopen and fread
// Simple Debug Feedback - writes to a simple text file located where the exe is.
void debug(char* str)
{
FILE* fp = fopen("output.txt", "a+");
fprintf(fp, "%s\n", str);
fclose(fp);
}// End of debug(..)
char buf[500]; // Temp buffer for text output
// ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW *
// ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW *
#define DWORD unsigned int
#define WORD unsigned short
#define LONG unsigned int
#define LPVOID void*
struct DDCOLORKEY
{
DWORD dwColorSpaceLowValue;
DWORD dwColorSpaceHighValue;
};
struct DDSCAPS2
{
DWORD dwCaps;
DWORD dwCaps2;
DWORD dwCaps3;
union
{
DWORD dwCaps4;
DWORD dwVolumeDepth;
};
};
struct DDPIXELFORMAT
{
DWORD dwSize;
DWORD dwFlags;
DWORD dwFourCC;
union
{
DWORD dwRGBBitCount;
DWORD dwYUVBitCount;
DWORD dwZBufferBitDepth;
DWORD dwAlphaBitDepth;
DWORD dwLuminanceBitCount;
DWORD dwBumpBitCount;
DWORD dwPrivateFormatBitCount;
};
union
{
DWORD dwRBitMask;
DWORD dwYBitMask;
DWORD dwStencilBitDepth;
DWORD dwLuminanceBitMask;
DWORD dwBumpDuBitMask;
DWORD dwOperations;
};
union
{
DWORD dwGBitMask;
DWORD dwUBitMask;
DWORD dwZBitMask;
DWORD dwBumpDvBitMask;
struct
{
WORD wFlipMSTypes;
WORD wBltMSTypes;
}MultiSampleCaps;
};
union
{
DWORD dwBBitMask;
DWORD dwVBitMask;
DWORD dwStencilBitMask;
DWORD dwBumpLuminanceBitMask;
};
union
{
DWORD dwRGBAlphaBitMask;
DWORD dwYUVAlphaBitMask;
DWORD dwLuminanceAlphaBitMask;
DWORD dwRGBZBitMask;
DWORD dwYUVZBitMask;
};
};
// Basically a copy from the ddraw.h directx header file which defines
// the DDSURFACEDESC2 structure, which we need for our file, as the file
// head is basically a copy of this.
struct DDSURFACEDESC2
{
DWORD dwSize;
DWORD dwFlags;
DWORD dwHeight;
DWORD dwWidth;
union
{
LONG lPitch;
DWORD dwLinearSize;
};
DWORD dwBackBufferCount;
union
{
DWORD dwMipMapCount;
DWORD dwRefreshRate;
DWORD dwSrcVBHandle;
};
DWORD dwAlphaBitDepth;
DWORD dwReserved;
LPVOID lpSurface;
union
{
DDCOLORKEY ddckCKDestOverlay;
DWORD dwEmptyFaceColor;
};
DDCOLORKEY ddckCKDestBlt;
DDCOLORKEY ddckCKSrcOverlay;
DDCOLORKEY ddckCKSrcBlt;
union
{
DDPIXELFORMAT ddpfPixelFormat;
DWORD dwFVF;
};
DDSCAPS2 ddsCaps;
DWORD dwTextureStage;
};
// Program Entry Point - Where we always start!
void main()
{
FILE* fp = fopen("cross.dds", "rb");
char readbuf[4]; // Temp Buffer
// +- Buffer to put the data in
// | +- Size of each value read in bytes (1 byte each)
// | | +- How many times to read in the value
// | | | +- Stream souce
// | | | |
fread(readbuf, 1, 4, fp);
debug("First Four Bytes:");
// Write the 4 bytes as characters to our debug file
sprintf(buf, "'%c' '%c' '%c' '%c'", readbuf[0],
readbuf[1],
readbuf[2],
readbuf[3]);
debug(buf);
// ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW **
// ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW **
// Create an instance of our giant structure which we just defined above.
DDSURFACEDESC2 ddsd;
// Lets print out out big it is, just for curiosity
sprintf(buf, "sizeof(DDSURFACEDESC2): %d (bytes)", sizeof(DDSURFACEDESC2));
debug(buf);
// Read in the header data
fread(&ddsd, sizeof(ddsd), 1, fp);
// Lets write out what we've actually read in!
sprintf(buf, "dwSize: %d", ddsd.dwSize);
debug(buf);
sprintf(buf, "dwFlags: 0x%x", ddsd.dwFlags);
debug(buf);
sprintf(buf, "dwHeight: %d", ddsd.dwHeight);
debug(buf);
sprintf(buf, "dwWidth: %d", ddsd.dwWidth);
debug(buf);
sprintf(buf, "dwLinearSize: %d", ddsd.dwLinearSize);
debug(buf);
sprintf(buf, "dwBackBufferCount: %d", ddsd.dwBackBufferCount);
debug(buf);
sprintf(buf, "dwMipMapCount: %d", ddsd.dwMipMapCount);
debug(buf);
sprintf(buf, "dwAlphaBitDepth: %d", ddsd.dwAlphaBitDepth);
debug(buf);
sprintf(buf, "dwReserved: %d", ddsd.dwReserved);
debug(buf);
sprintf(buf, "lpSurface: %d", ddsd.lpSurface);
debug(buf);
debug("DDCOLORKEY");
debug("{");
sprintf(buf, "\tddckCKDestOverlay(low): %d", ddsd.ddckCKDestOverlay.dwColorSpaceLowValue);
debug(buf);
sprintf(buf, "\tddckCKDestOverlay(high): %d", ddsd.ddckCKDestOverlay.dwColorSpaceHighValue);
debug(buf);
debug("}");
debug("..."); // To lazy to do these, don't think there important
sprintf(buf, "ddpfPixelFormat: %d", ddsd.ddpfPixelFormat);
debug(buf);
sprintf(buf, "ddsCaps: %d", ddsd.ddsCaps);
debug(buf);
sprintf(buf, "dwTextureStage: %d", ddsd.dwTextureStage);
debug(buf);
fclose(fp);
}// End of main()
|
output.txt |
First Four Bytes:
'D' 'D' 'S' ' '
sizeof(DDSURFACEDESC2): 124 (bytes)
dwSize: 124
dwFlags: 0x81007
dwHeight: 8
dwWidth: 8
dwLinearSize: 256
dwBackBufferCount: 0
dwMipMapCount: 0
dwAlphaBitDepth: 0
dwReserved: 0
lpSurface: 0
DDCOLORKEY
{
ddckCKDestOverlay(low): 0
ddckCKDestOverlay(high): 0
}
...
ddpfPixelFormat: 32
ddsCaps: 4096
dwTextureStage: 0 |
So what we have now found, is essentially our basic image information.
We know our image width and height, and we know what type of pixel format its
in, which is 32 bit, ARGB, which is what I exported it as.
I've modified the code a bit so that I don't automatically write a new line
'\n' in my debug text out function, so I can write out the pixel information in
a nice tidy square....but of course this means I've had to add the newline
comment to all my other lines that write info to the file instead.
Also as I said I was going to do above, I just put all those struct files in
an include file, 'ddstypes.h'...its tidier I think.
We now go about getting the pixel information. Because where only
working with a 8x8 image which I've created, and of course I know what the file
format looks like, well then its not so hard to dump the data.
code030 : Download Source Code |
////////////////////////////////////////////////////////////////////////////////////////
// //
// File: main.cpp //
// Author: bkenwright@xbdev.net //
// //
////////////////////////////////////////////////////////////////////////////////////////
#include <stdio.h> // So we can use fopen and fread
// ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW *
// ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW *
#include "ddstypes.h" // dds structures / defines
// Simple Debug Feedback - writes to a simple text file located where the exe is.
void debug(char* str)
{
FILE* fp = fopen("output.txt", "a+");
fprintf(fp, "%s", str);
fclose(fp);
}// End of debug(..)
char buf[500]; // Temp buffer for text output
// Program Entry Point - Where we always start!
void main()
{
FILE* fp = fopen("cross.dds", "rb");
char readbuf[4]; // Temp Buffer
// +- Buffer to put the data in
// | +- Size of each value read in bytes (1 byte each)
// | | +- How many times to read in the value
// | | | +- Stream souce
// | | | |
fread(readbuf, 1, 4, fp);
debug("First Four Bytes:\n");
// Write the 4 bytes as characters to our debug file
sprintf(buf, "'%c' '%c' '%c' '%c'\n", readbuf[0],
readbuf[1],
readbuf[2],
readbuf[3]);
debug(buf);
// Create an instance of our giant structure which we just defined above.
DDSURFACEDESC2 ddsd;
// Lets print out out big it is, just for curiosity
sprintf(buf, "sizeof(DDSURFACEDESC2): %d (bytes)", sizeof(DDSURFACEDESC2));
debug(buf);
// Read in the header data
fread(&ddsd, sizeof(ddsd), 1, fp);
// Lets write out what we've actually read in!
sprintf(buf, "dwSize: %d\n", ddsd.dwSize);
debug(buf);
sprintf(buf, "dwFlags: 0x%x\n", ddsd.dwFlags);
debug(buf);
sprintf(buf, "dwHeight: %d\n", ddsd.dwHeight);
debug(buf);
sprintf(buf, "dwWidth: %d\n", ddsd.dwWidth);
debug(buf);
sprintf(buf, "dwLinearSize: %d\n", ddsd.dwLinearSize);
debug(buf);
sprintf(buf, "dwBackBufferCount: %d\n", ddsd.dwBackBufferCount);
debug(buf);
sprintf(buf, "dwMipMapCount: %d\n", ddsd.dwMipMapCount);
debug(buf);
sprintf(buf, "dwAlphaBitDepth: %d\n", ddsd.dwAlphaBitDepth);
debug(buf);
sprintf(buf, "dwReserved: %d\n", ddsd.dwReserved);
debug(buf);
sprintf(buf, "lpSurface: %d\n", ddsd.lpSurface);
debug(buf);
debug("...\n"); // This information really isn't needed
sprintf(buf, "ddpfPixelFormat: %d\n", ddsd.ddpfPixelFormat);
debug(buf);
sprintf(buf, "ddsCaps: %d\n", ddsd.ddsCaps);
debug(buf);
sprintf(buf, "dwTextureStage: %d\n", ddsd.dwTextureStage);
debug(buf);
// ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW **
// ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW **
// Lets get the image pixel information
// If we have mipmaps - i.e. multiple resolution images saved, then we'll have
// additional data to get. Not as complicated as it sounds.
int bufSize = ddsd.dwMipMapCount > 1 ? ddsd.dwLinearSize * 2 : ddsd.dwLinearSize;
char* buffer = new char[bufSize];
fread(buffer, 1, bufSize, fp);
// Since we have ARGB pixels, its easier to just put it to a dword
DWORD* pixels = (DWORD*)buffer;
// Well just dump it as hex for now
debug("Pixel Data:\n");
for (int y=0; y<ddsd.dwHeight; y++)
{
debug("\t");
for (int x=0; x<ddsd.dwWidth; x++)
{
sprintf(buf, "0x%.8x ", pixels[0]);
debug(buf);
pixels++;
}//End for x
debug("\n");
}//End for y
delete[] buffer; // Always remember to tidy up
fclose(fp);
}// End of main()
|
output.txt |
First Four Bytes:
'D' 'D' 'S' ' '
sizeof(DDSURFACEDESC2): 124 (bytes)dwSize: 124
dwFlags: 0x81007
dwHeight: 8
dwWidth: 8
dwLinearSize: 256
dwBackBufferCount: 0
dwMipMapCount: 0
dwAlphaBitDepth: 0
dwReserved: 0
lpSurface: 0
...
ddpfPixelFormat: 32
ddsCaps: 4096
dwTextureStage: 0
Pixel Data:
0xffff0000 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffff0000
0xffffffff 0xffff0000 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffff0000 0xffffffff
0xffffffff 0xffffffff 0xffff0000 0xffffffff 0xffffffff 0xffff0000 0xffffffff 0xffffffff
0xffffffff 0xffffffff 0xffffffff 0xffff0000 0xffff0000 0xffffffff 0xffffffff 0xffffffff
0xffffffff 0xffffffff 0xffffffff 0xffff0000 0xffff0000 0xffffffff 0xffffffff 0xffffffff
0xffffffff 0xffffffff 0xffff0000 0xffffffff 0xffffffff 0xffff0000 0xffffffff 0xffffffff
0xffffffff 0xffff0000 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffff0000 0xffffffff
0xffff0000 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffff0000
|
Well as we can see, we have got our pixel info! Its not so bad now,
basically all we need to do now, is do some checks for compression and various
file formats and mipmaps.
TODO :
- Uncompression Functions
- Various Pixel Formats
- MipMaps
|