Jump to content
  • Advertisement
Sign in to follow this  
MajinCry

C# - Detect If Pre-Existing .DDS Contains Alpha Channel?

This topic is 689 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

For the past wee while, I've been scouring the net to find a way to tell if a .dds file contains an alpha channel. And I've come up completely dry.

 

I've been working on a tool that sorts through all of the .dds file in a given directory, including it's sub-directories, and resizing all .dds images of a specified size to another. Pretty much just takes text from the gui, reads a couple bytes from the header of the .dds image, and then chucks it all to texconv.exe.

 

Thing is, I need to detect whether or not a .dds image uses transparency. If it does, include the -pmalpha argument when launching texconv.exe

 

How would I go about doing this? I've not much experience with C# (this program is my first foray into the language), and my sum total of programming experience is this and some Pascal for Tes5Edit, so I'll probably need to be explained to as if I'm 5 years old or somethin'.

 

I'm thinking that maybe SharpDX has some use for this? The documentation isn't, err, beginner friendly to say the least, sadly.

 

 

Anywho, if it helps at all, 'eres a couple links:

 

Current release of the program: http://www.nexusmods.com/fallout4/mods/17624/?

Current source code: https://github.com/MajinCry/Fallout-4-Texture-Resizer

Share this post


Link to post
Share on other sites
Advertisement
You could write a pretty simple check by reading the DDS file's headers:

https://msdn.microsoft.com/en-us/library/bb943991.aspx

Note: These documents outline the structures in C style, but you can easily convert that to C#.

DWORD = uint
WORD = ushort
etc.

It looks like the information you want can be found in the DDS_HEADER -> DDS_PIXELFORMAT -> DDPF_ALPHAPIXELS bit in the flags field.

Personally I would just read the file using a BinaryReader and seek to the correct offset in the file that corresponds to that field. Just count the # of DWORDs you need to skip in order to get to the dwFlags field, multiply that by 4, say filestream.Offset = whatever_that_is, then dwFlags = binaryReader.ReadInt32() and then bool containsAlpha = (dwFlags & 1) == 1; Edited by Nypyren

Share this post


Link to post
Share on other sites

The header contains alpha data? I swear I tried comparing the headers between various formats (with and without alpha), and couldn't find any consistency between them. Finding the x & y resolution, as well as the mipmap levels, was easy on the other hand.

 

Me findings can be found here: http://pastebin.com/isBKwaas

 

I think the format is stored in 4 bytes, starting @ offset +54, but that's about it. Comparing the >100 different .dds formats, with the various alpha combinations...Err, hmm.

 

 

The microsoft documentation seems to be really awkward. It was rather good for the simpler functions, but it seems to rather terse about this directx stuff.

DWORD               dwMagic;
DDS_HEADER          header;
DDS_HEADER_DXT10    header10;

dwMagic starts at the beginning of the file, and looks like this in a hex viewer: 44 44 53 20

 

Maybe header starts @ offset +04?

 

And header10 is probably the four bytes @ offset +54.

 

No clue where to find the PIXELFORMAT bytes, though. https://msdn.microsoft.com/en-us/library/bb943984.aspx

Share this post


Link to post
Share on other sites
The way the C++ struct layouts are defined here, each field comes in the order listed, and nested structs exist at that position in the file as well (they aren't pointers to arbitrary locations).

You are correct that DWORDs are 4 bytes (or, 32-bits since 1 byte = 8 bits). The corresponding type in C# to a DWORD is called a uint, or sometimes seen as UInt32. For example the BinaryReader function to read a uint from a file is called ReadUInt32.

so here's how you would find the offset you want:
 
DWORD               dwMagic; = offset 0
DDS_HEADER          header; = offset 0 + sizeof(DWORD) = offset 4
{
  DWORD           dwSize; = offset 4
  DWORD           dwFlags; = offset 8
  DWORD           dwHeight; = offset 12
  DWORD           dwWidth; = offset 16
  DWORD           dwPitchOrLinearSize; = offset 20
  DWORD           dwDepth; = offset 24
  DWORD           dwMipMapCount; = offset 28
  DWORD           dwReserved1[11]; == offset 32, notice this is an array of 11 DWORDs.
  DDS_PIXELFORMAT ddspf; = offset 32 + sizeof(DWORD) * 11 = offset 32 + 44 = offset 76
  {
    DWORD dwSize; = offset 76
    DWORD dwFlags; = offset 80   <============================== This is what you're interested in
    DWORD dwFourCC;
    DWORD dwRGBBitCount;
    DWORD dwRBitMask;
    DWORD dwGBitMask;
    DWORD dwBBitMask;
    DWORD dwABitMask;
  }
  DWORD           dwCaps;
  DWORD           dwCaps2;
  DWORD           dwCaps3;
  DWORD           dwCaps4;
  DWORD           dwReserved2;  
}
dwFlags is a bitfield (i.e. it is a number composed of several other numbers ORed together). the value you care about is:
 
DDPF_ALPHAPIXELS       Texture contains alpha data; dwRGBAlphaBitMask contains valid data.     Value = 0x1 
What this means is, if the texture contains alpha data, the dwFlags should have bits corresponding to the value 0x1 set.

To check whether that value is set, you say:
 
bool isValueSetInBitfield = (bitfield & mask) == value;
where 'bitfield' is 'dwFlags' and both 'mask' and 'value' are 0x1, since it's a one-bit field.


If you discover that you need to also examine the DXT10 header, figuring out which offset it's at works the same; it just comes immediately after the first header. Edited by Nypyren

Share this post


Link to post
Share on other sites
In your existing code, the function GetDDSDimensions already looks like it's using this technique to get the width and height (the offsets are written in hexadecimal in your code, and my previous post is using decimal instead). You could easily add a read of the dwFlags field and check for alpha in there as well, if you want. Edited by Nypyren

Share this post


Link to post
Share on other sites

Sorry for the late reply, been trying to wrap my head around this, trying to get the concept down. Honestly, it's making my brain hurt.

 

Been converting the code you posted to a C# equivalent, and here's what I've got so far:

        public bool IsAlphaPresent(string strDDSFileName)

        {

            using (FileStream fsSourceDDS = new FileStream(strDDSFileName, FileMode.Open, FileAccess.Read))

            using (BinaryReader binaryreaderDDS = new BinaryReader(fsSourceDDS))

            {

                fsSourceDDS.Seek(0x04, SeekOrigin.Begin);

                uint uintSizeOfDDS = binaryreaderDDS.ReadUInt32();

                fsSourceDDS.Seek(0x08, SeekOrigin.Begin);

                uint uintDDSFlags = binaryreaderDDS.ReadUInt32();



                fsSourceDDS.Seek(0x0c, SeekOrigin.Begin);

                ushort ushortDDSHeight = binaryreaderDDS.ReadUInt16();

                fsSourceDDS.Seek(0x10, SeekOrigin.Begin);

                ushort ushortDDSWidth = binaryreaderDDS.ReadUInt16();



                fsSourceDDS.Seek(0x14, SeekOrigin.Begin);

                uint uintDDSPitchOrLinearSize = binaryreaderDDS.ReadUInt32();

                fsSourceDDS.Seek(0x18, SeekOrigin.Begin);

                uint uintDDSDepth = binaryreaderDDS.ReadUInt32();



                fsSourceDDS.Seek(0x1c, SeekOrigin.Begin);

                byte byteNumberOfMipmaps = binaryreaderDDS.ReadByte();

                fsSourceDDS.Seek(0x20, SeekOrigin.Begin);

                byte[] bytearrayDDSReserved = binaryreaderDDS.ReadBytes(44); // Collection of 11 dwords





            }

        }

I've been using hexadecimal values because I've a teensy bit of experience with CheatEngine, in that I've hooked a couple instructions and documented the npc data structure for Dark Souls. Feels more sane to me to use hex, especially since I'm having to reference the header in the .dds file via a hex editor.

 

Is the dwFlags always at a specific offset (i.e, always at the same offset in every BCn format .dds texture), or does it change depending on the format?

Share this post


Link to post
Share on other sites

Is the dwFlags always at a specific offset (i.e, always at the same offset in every BCn format .dds texture), or does it change depending on the format?


As far as I can tell from MSDN, yes, it's always at a specific offset.

Also, you don't need to seek and read all of the intermediate fields if you don't care about them. you can seek directly to the dwFlags field, skipping everything inbetween.
 
fsSourceDDS.Position = 0x50;  // 0x50 == 80; using .Position does the same thing as Seek does.  I pretty much never use Seek anymore.
uint dwFlags = binaryreaderDDS.ReadUint32();
bool containsAlpha = (dwFlags & 1) == 1;
FileStream.Position: https://msdn.microsoft.com/en-us/library/system.io.filestream.position(v=vs.110).aspx Edited by Nypyren

Share this post


Link to post
Share on other sites

Sweet, I'll give it a shot.

 

What I've done, is added the following code:

 public bool IsAlphaPresent(string strDDSFileName)
        {
            using (FileStream fsSourceDDS = new FileStream(strDDSFileName, FileMode.Open, FileAccess.Read))
            using (BinaryReader binaryreaderDDS = new BinaryReader(fsSourceDDS))
            {
                fsSourceDDS.Position = 0x50;
                uint uintDWFlags = binaryreaderDDS.ReadUInt32();
                bool boolIsValueSetInBitfield = (uintDWFlags & 1) == 1;

                if (boolIsValueSetInBitfield)
                {
                    return true;
                }
                else
                {
                    return false;
                }

            }
        }

At the beginning of RunTexConv():

string strAlphaArgument;
            if (IsAlphaPresent(strFinalDDS))
            {
                strAlphaArgument = "-pmalpha ";
                MessageBox.Show(strFinalDDS + " Has an alpha channel!");
            }
            else
            {
                strAlphaArgument = "";
            }

If everything goes as planned, it should just throw up a message for the .dds files that have an alpha channel. Time to see if it works.

 

 

Edit: The code above didn't work, had to do a wee bit of editing to a couple functions, to include the source .dds filepath (due to the destination .dds not existing yet). Alas, no alpha was found in any of the files, despite there being an alpha channel in several of them.

 

Made a new branch with the edited code: https://github.com/MajinCry/Fallout-4-Texture-Resizer/tree/Test---Check-For-Alpha

 

Couple of .dds images I'm using to test: http://www66.zippyshare.com/v/cEJnMsOj/file.html

Edited by MajinCry

Share this post


Link to post
Share on other sites

I think testing DDPF_ALPHAPIXELS is only going to work reliably for uncompressed textures. If it's say a DXT5 texture then it could leave that flag clear and still work correctly. It would also be possible to generate a file where that flag is set, but the alpha channel is actually set to opaque for every pixel.

 

If you want to be certain, the easiest option is probably to convert to an uncompressed format, and read back the alpha channel. Of course for DXT5 you can be fairly certain there is an alpha channel - for opaque textures DXT1 is equivalent quality, and half the file size as it doesn't store full alpha data. However, a DXT1 image may or may not use the single bit alpha channel feature.

 

Of course most of the time game toolchains work the other way round - they start with uncompressed RGBA images and convert to .DDS to reduce the memory cost. When you're doing that you can make sure your converter sets a flag somewhere.

 

Also note that DXT is lossy compression. You will get better quality results if your tools work with uncompressed (or losslessly compressed) textures, and convert to .DDS as the final step.

Edited by Adam_42

Share this post


Link to post
Share on other sites

I think testing DDPF_ALPHAPIXELS is only going to work reliably for uncompressed textures. If it's say a DXT5 texture then it could leave that flag clear and still work correctly. It would also be possible to generate a file where that flag is set, but the alpha channel is actually set to opaque for every pixel.

 

If you want to be certain, the easiest option is probably to convert to an uncompressed format, and read back the alpha channel. Of course for DXT5 you can be fairly certain there is an alpha channel - for opaque textures DXT1 is equivalent quality, and half the file size as it doesn't store full alpha data. However, a DXT1 image may or may not use the single bit alpha channel feature.

 

Of course most of the time game toolchains work the other way round - they start with uncompressed RGBA images and convert to .DDS to reduce the memory cost. When you're doing that you can make sure your converter sets a flag somewhere.

 

Also note that DXT is lossy compression. You will get better quality results if your tools work with uncompressed (or losslessly compressed) textures, and convert to .DDS as the final step.

 

Y'know, I think you might be on to something. I checked a couple of the transparent textures, and they had DXT5 in the header. Checked a few that weren't transparent, and they had DXT1. Hmm.

 

If the image has an alpha channel but doesn't use it for anything, that's of no consequence; I don't plan on "optimizing" textures, as that would be more work and an absolute pain seeing how I can't even check if an alpha is present or not, for now at least. Would be good to have, but I'm aiming to get the base functionality down first.

 

Aye, it'd be great if Bethesda supplied the source textures (and the PSDs while they're at it), but, alas, they've only given us peasants the final .dds textures. 'Tis all I've got to work with, and it's also what the modders supply everyone with. Not ideal for this project, but, well, there's no alternative.

 

 

I wonder if SharpDX would be any use. The documentation is pretty curt, and there aren't any tutorials on how to use it for something like this (identifying pre-existing .dds image properties), though. Are there any users kicking around here who are fairly pro with that? I could try firin' them a wee message.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!