public void Save(System.IO.Stream output, Surface surface, DdsSaveConfigToken ddsToken, ProgressEventHandler progressCallback) { // For non-compressed textures, we need pixel width. int pixelWidth = 0; // Identify if we're a compressed image bool isCompressed = ((ddsToken.m_fileFormat == DdsFileFormat.DDS_FORMAT_DXT1) || (ddsToken.m_fileFormat == DdsFileFormat.DDS_FORMAT_DXT3) || (ddsToken.m_fileFormat == DdsFileFormat.DDS_FORMAT_DXT5)); // Compute mip map count.. int mipCount = 1; int mipWidth = surface.Width; int mipHeight = surface.Height; if (ddsToken.m_generateMipMaps) { // This breaks! while ((mipWidth > 1) || (mipHeight > 1)) { mipCount++; mipWidth /= 2; mipHeight /= 2; } } // Populate bulk of our DdsHeader m_header.m_size = m_header.Size(); m_header.m_headerFlags = ( uint )(DdsHeader.HeaderFlags.DDS_HEADER_FLAGS_TEXTURE); if (isCompressed) { m_header.m_headerFlags |= ( uint )(DdsHeader.HeaderFlags.DDS_HEADER_FLAGS_LINEARSIZE); } else { m_header.m_headerFlags |= ( uint )(DdsHeader.HeaderFlags.DDS_HEADER_FLAGS_PITCH); } if (mipCount > 1) { m_header.m_headerFlags |= ( uint )(DdsHeader.HeaderFlags.DDS_HEADER_FLAGS_MIPMAP); } m_header.m_height = ( uint )surface.Height; m_header.m_width = ( uint )surface.Width; if (isCompressed) { // Compresssed textures have the linear flag set.So pitchOrLinearSize // needs to contain the entire size of the DXT block. int blockCount = ((surface.Width + 3) / 4) * ((surface.Height + 3) / 4); int blockSize = (ddsToken.m_fileFormat == 0) ? 8 : 16; m_header.m_pitchOrLinearSize = ( uint )(blockCount * blockSize); } else { // Non-compressed textures have the pitch flag set. So pitchOrLinearSize // needs to contain the row pitch of the main image. DWORD aligned too. switch (ddsToken.m_fileFormat) { case DdsFileFormat.DDS_FORMAT_A8R8G8B8: case DdsFileFormat.DDS_FORMAT_X8R8G8B8: case DdsFileFormat.DDS_FORMAT_A8B8G8R8: case DdsFileFormat.DDS_FORMAT_X8B8G8R8: pixelWidth = 4; // 32bpp break; case DdsFileFormat.DDS_FORMAT_A1R5G5B5: case DdsFileFormat.DDS_FORMAT_A4R4G4B4: case DdsFileFormat.DDS_FORMAT_R5G6B5: pixelWidth = 2; // 16bpp break; case DdsFileFormat.DDS_FORMAT_R8G8B8: pixelWidth = 3; // 24bpp break; } // Compute row pitch m_header.m_pitchOrLinearSize = ( uint )(( int )m_header.m_width * pixelWidth); #if APPLY_PITCH_ALIGNMENT // Align to DWORD, if we need to.. (see notes about pitch alignment all over this code) m_header.m_pitchOrLinearSize = ( uint )((( int )m_header.m_pitchOrLinearSize + 3) & (~3)); #endif //APPLY_PITCH_ALIGNMENT } m_header.m_depth = 0; m_header.m_mipMapCount = (mipCount == 1) ? 0 : ( uint )mipCount; m_header.m_reserved1_0 = 0; m_header.m_reserved1_1 = 0; m_header.m_reserved1_2 = 0; m_header.m_reserved1_3 = 0; m_header.m_reserved1_4 = 0; m_header.m_reserved1_5 = 0; m_header.m_reserved1_6 = 0; m_header.m_reserved1_7 = 0; m_header.m_reserved1_8 = 0; m_header.m_reserved1_9 = 0; m_header.m_reserved1_10 = 0; // Populate our DdsPixelFormat object m_header.m_pixelFormat.Initialise(ddsToken.m_fileFormat); // Populate miscellanous header flags m_header.m_surfaceFlags = ( uint )DdsHeader.SurfaceFlags.DDS_SURFACE_FLAGS_TEXTURE; if (mipCount > 1) { m_header.m_surfaceFlags |= ( uint )DdsHeader.SurfaceFlags.DDS_SURFACE_FLAGS_MIPMAP; } m_header.m_cubemapFlags = 0; m_header.m_reserved2_0 = 0; m_header.m_reserved2_1 = 0; m_header.m_reserved2_2 = 0; // Write out our DDS tag Utility.WriteUInt32(output, 0x20534444); // 'DDS ' // Write out the header m_header.Write(output); int squishFlags = ddsToken.GetSquishFlags(); // Our output data array will be sized as necessary byte[] outputData; // Reset our mip width & height variables... mipWidth = surface.Width; mipHeight = surface.Height; // Figure out how much total work each mip map is Size[] writeSizes = new Size[mipCount]; int[] mipPixels = new int[mipCount]; int[] pixelsCompleted = new int[mipCount]; // # pixels completed once we have reached this mip long totalPixels = 0; for (int mipLoop = 0; mipLoop < mipCount; mipLoop++) { Size writeSize = new Size((mipWidth > 0) ? mipWidth : 1, (mipHeight > 0) ? mipHeight : 1); writeSizes[mipLoop] = writeSize; int thisMipPixels = writeSize.Width * writeSize.Height; mipPixels[mipLoop] = thisMipPixels; if (mipLoop == 0) { pixelsCompleted[mipLoop] = 0; } else { pixelsCompleted[mipLoop] = pixelsCompleted[mipLoop - 1] + mipPixels[mipLoop - 1]; } totalPixels += thisMipPixels; mipWidth /= 2; mipHeight /= 2; } mipWidth = surface.Width; mipHeight = surface.Height; for (int mipLoop = 0; mipLoop < mipCount; mipLoop++) { Size writeSize = writeSizes[mipLoop]; Surface writeSurface = new Surface(writeSize); if (mipLoop == 0) { // No point resampling the first level.. it's got exactly what we want. writeSurface = surface; } else { // I'd love to have a UI component to select what kind of resampling, but // there's hardly any space for custom UI stuff in the Save Dialog. And I'm // not having any scrollbars in there..! // Also, note that each mip level is formed from the main level, to reduce // compounded errors when generating mips. writeSurface.SuperSamplingFitSurface(surface); } DdsSquish.ProgressFn progressFn = delegate(int workDone, int workTotal) { long thisMipPixelsDone = workDone * (long)mipWidth; long previousMipsPixelsDone = pixelsCompleted[mipLoop]; double progress = (double)((double)thisMipPixelsDone + (double)previousMipsPixelsDone) / (double)totalPixels; progressCallback(this, new ProgressEventArgs(100.0 * progress)); }; if ((ddsToken.m_fileFormat >= DdsFileFormat.DDS_FORMAT_DXT1) && (ddsToken.m_fileFormat <= DdsFileFormat.DDS_FORMAT_DXT5)) { outputData = DdsSquish.CompressImage(writeSurface, squishFlags, (progressCallback == null) ? null : progressFn); } else { int mipPitch = pixelWidth * writeSurface.Width; // From the DDS documents I read, I'd expected the pitch of each mip level to be // DWORD aligned. As it happens, that's not the case. Re-aligning the pitch of // each level results in later mips getting sheared as the pitch is incorrect. // So, the following line is intentionally optional. Maybe the documentation // is referring to the pitch when accessing the mip directly.. who knows. // // Infact, all the talk of non-compressed textures having DWORD alignment of pitch // seems to be bollocks.. If I apply alignment, then they fail to load in 3rd Party // or Microsoft DDS viewing applications. // #if APPLY_PITCH_ALIGNMENT mipPitch = (mipPitch + 3) & (~3); #endif // APPLY_PITCH_ALIGNMENT outputData = new byte[mipPitch * writeSurface.Height]; outputData.Initialize(); for (int y = 0; y < writeSurface.Height; y++) { for (int x = 0; x < writeSurface.Width; x++) { // Get colour from surface ColorBgra pixelColour = writeSurface.GetPoint(x, y); uint pixelData = 0; switch (ddsToken.m_fileFormat) { case DdsFileFormat.DDS_FORMAT_A8R8G8B8: { pixelData = (( uint )pixelColour.A << 24) | (( uint )pixelColour.R << 16) | (( uint )pixelColour.G << 8) | (( uint )pixelColour.B << 0); break; } case DdsFileFormat.DDS_FORMAT_X8R8G8B8: { pixelData = (( uint )pixelColour.R << 16) | (( uint )pixelColour.G << 8) | (( uint )pixelColour.B << 0); break; } case DdsFileFormat.DDS_FORMAT_A8B8G8R8: { pixelData = (( uint )pixelColour.A << 24) | (( uint )pixelColour.B << 16) | (( uint )pixelColour.G << 8) | (( uint )pixelColour.R << 0); break; } case DdsFileFormat.DDS_FORMAT_X8B8G8R8: { pixelData = (( uint )pixelColour.B << 16) | (( uint )pixelColour.G << 8) | (( uint )pixelColour.R << 0); break; } case DdsFileFormat.DDS_FORMAT_A1R5G5B5: { pixelData = (( uint )((pixelColour.A != 0) ? 1 : 0) << 15) | (( uint )(pixelColour.R >> 3) << 10) | (( uint )(pixelColour.G >> 3) << 5) | (( uint )(pixelColour.B >> 3) << 0); break; } case DdsFileFormat.DDS_FORMAT_A4R4G4B4: { pixelData = (( uint )(pixelColour.A >> 4) << 12) | (( uint )(pixelColour.R >> 4) << 8) | (( uint )(pixelColour.G >> 4) << 4) | (( uint )(pixelColour.B >> 4) << 0); break; } case DdsFileFormat.DDS_FORMAT_R8G8B8: { pixelData = (( uint )pixelColour.R << 16) | (( uint )pixelColour.G << 8) | (( uint )pixelColour.B << 0); break; } case DdsFileFormat.DDS_FORMAT_R5G6B5: { pixelData = (( uint )(pixelColour.R >> 3) << 11) | (( uint )(pixelColour.G >> 2) << 5) | (( uint )(pixelColour.B >> 3) << 0); break; } } // pixelData contains our target data.. so now set the pixel bytes int pixelOffset = (y * mipPitch) + (x * pixelWidth); for (int loop = 0; loop < pixelWidth; loop++) { outputData[pixelOffset + loop] = ( byte )((pixelData >> (8 * loop)) & 0xff); } } if (progressCallback != null) { long thisMipPixelsDone = (y + 1) * (long)mipWidth; long previousMipsPixelsDone = pixelsCompleted[mipLoop]; double progress = (double)((double)thisMipPixelsDone + (double)previousMipsPixelsDone) / (double)totalPixels; progressCallback(this, new ProgressEventArgs(100.0 * progress)); } } } // Write the data for this mip level out.. output.Write(outputData, 0, outputData.GetLength(0)); mipWidth = mipWidth / 2; mipHeight = mipHeight / 2; } }
public void Load(System.IO.Stream input) { // Read the DDS tag. If it's not right, then bail.. uint ddsTag = ( uint )Utility.ReadUInt32(input); if (ddsTag != 0x20534444) { throw new FormatException("File does not appear to be a DDS image"); } // Read everything in.. for now assume it worked like a charm.. m_header.Read(input); if ((m_header.m_pixelFormat.m_flags & ( int )DdsPixelFormat.PixelFormatFlags.DDS_FOURCC) != 0) { int squishFlags = 0; switch (m_header.m_pixelFormat.m_fourCC) { case 0x31545844: squishFlags = ( int )DdsSquish.SquishFlags.kDxt1; break; case 0x33545844: squishFlags = ( int )DdsSquish.SquishFlags.kDxt3; break; case 0x35545844: squishFlags = ( int )DdsSquish.SquishFlags.kDxt5; break; default: throw new FormatException("File is not a supported DDS format"); } // Compute size of compressed block area int blockCount = ((GetWidth() + 3) / 4) * ((GetHeight() + 3) / 4); int blockSize = ((squishFlags & ( int )DdsSquish.SquishFlags.kDxt1) != 0) ? 8 : 16; // Allocate room for compressed blocks, and read data into it. byte[] compressedBlocks = new byte[blockCount * blockSize]; input.Read(compressedBlocks, 0, compressedBlocks.GetLength(0)); // Now decompress.. m_pixelData = DdsSquish.DecompressImage(compressedBlocks, GetWidth(), GetHeight(), squishFlags); } else { // We can only deal with the non-DXT formats we know about.. this is a bit of a mess.. // Sorry.. DdsFileFormat fileFormat = DdsFileFormat.DDS_FORMAT_INVALID; if ((m_header.m_pixelFormat.m_flags == ( int )DdsPixelFormat.PixelFormatFlags.DDS_RGBA) && (m_header.m_pixelFormat.m_rgbBitCount == 32) && (m_header.m_pixelFormat.m_rBitMask == 0x00ff0000) && (m_header.m_pixelFormat.m_gBitMask == 0x0000ff00) && (m_header.m_pixelFormat.m_bBitMask == 0x000000ff) && (m_header.m_pixelFormat.m_aBitMask == 0xff000000)) { fileFormat = DdsFileFormat.DDS_FORMAT_A8R8G8B8; } else if ((m_header.m_pixelFormat.m_flags == ( int )DdsPixelFormat.PixelFormatFlags.DDS_RGB) && (m_header.m_pixelFormat.m_rgbBitCount == 32) && (m_header.m_pixelFormat.m_rBitMask == 0x00ff0000) && (m_header.m_pixelFormat.m_gBitMask == 0x0000ff00) && (m_header.m_pixelFormat.m_bBitMask == 0x000000ff) && (m_header.m_pixelFormat.m_aBitMask == 0x00000000)) { fileFormat = DdsFileFormat.DDS_FORMAT_X8R8G8B8; } else if ((m_header.m_pixelFormat.m_flags == ( int )DdsPixelFormat.PixelFormatFlags.DDS_RGBA) && (m_header.m_pixelFormat.m_rgbBitCount == 32) && (m_header.m_pixelFormat.m_rBitMask == 0x000000ff) && (m_header.m_pixelFormat.m_gBitMask == 0x0000ff00) && (m_header.m_pixelFormat.m_bBitMask == 0x00ff0000) && (m_header.m_pixelFormat.m_aBitMask == 0xff000000)) { fileFormat = DdsFileFormat.DDS_FORMAT_A8B8G8R8; } else if ((m_header.m_pixelFormat.m_flags == ( int )DdsPixelFormat.PixelFormatFlags.DDS_RGB) && (m_header.m_pixelFormat.m_rgbBitCount == 32) && (m_header.m_pixelFormat.m_rBitMask == 0x000000ff) && (m_header.m_pixelFormat.m_gBitMask == 0x0000ff00) && (m_header.m_pixelFormat.m_bBitMask == 0x00ff0000) && (m_header.m_pixelFormat.m_aBitMask == 0x00000000)) { fileFormat = DdsFileFormat.DDS_FORMAT_X8B8G8R8; } else if ((m_header.m_pixelFormat.m_flags == ( int )DdsPixelFormat.PixelFormatFlags.DDS_RGBA) && (m_header.m_pixelFormat.m_rgbBitCount == 16) && (m_header.m_pixelFormat.m_rBitMask == 0x00007c00) && (m_header.m_pixelFormat.m_gBitMask == 0x000003e0) && (m_header.m_pixelFormat.m_bBitMask == 0x0000001f) && (m_header.m_pixelFormat.m_aBitMask == 0x00008000)) { fileFormat = DdsFileFormat.DDS_FORMAT_A1R5G5B5; } else if ((m_header.m_pixelFormat.m_flags == ( int )DdsPixelFormat.PixelFormatFlags.DDS_RGBA) && (m_header.m_pixelFormat.m_rgbBitCount == 16) && (m_header.m_pixelFormat.m_rBitMask == 0x00000f00) && (m_header.m_pixelFormat.m_gBitMask == 0x000000f0) && (m_header.m_pixelFormat.m_bBitMask == 0x0000000f) && (m_header.m_pixelFormat.m_aBitMask == 0x0000f000)) { fileFormat = DdsFileFormat.DDS_FORMAT_A4R4G4B4; } else if ((m_header.m_pixelFormat.m_flags == ( int )DdsPixelFormat.PixelFormatFlags.DDS_RGB) && (m_header.m_pixelFormat.m_rgbBitCount == 24) && (m_header.m_pixelFormat.m_rBitMask == 0x00ff0000) && (m_header.m_pixelFormat.m_gBitMask == 0x0000ff00) && (m_header.m_pixelFormat.m_bBitMask == 0x000000ff) && (m_header.m_pixelFormat.m_aBitMask == 0x00000000)) { fileFormat = DdsFileFormat.DDS_FORMAT_R8G8B8; } else if ((m_header.m_pixelFormat.m_flags == ( int )DdsPixelFormat.PixelFormatFlags.DDS_RGB) && (m_header.m_pixelFormat.m_rgbBitCount == 16) && (m_header.m_pixelFormat.m_rBitMask == 0x0000f800) && (m_header.m_pixelFormat.m_gBitMask == 0x000007e0) && (m_header.m_pixelFormat.m_bBitMask == 0x0000001f) && (m_header.m_pixelFormat.m_aBitMask == 0x00000000)) { fileFormat = DdsFileFormat.DDS_FORMAT_R5G6B5; } // If fileFormat is still invalid, then it's an unsupported format. if (fileFormat == DdsFileFormat.DDS_FORMAT_INVALID) { throw new FormatException("File is not a supported DDS format"); } // Size of a source pixel, in bytes int srcPixelSize = (( int )m_header.m_pixelFormat.m_rgbBitCount / 8); // We need the pitch for a row, so we can allocate enough memory for the load. int rowPitch = 0; if ((m_header.m_headerFlags & ( int )DdsHeader.HeaderFlags.DDS_HEADER_FLAGS_PITCH) != 0) { // Pitch specified.. so we can use directly rowPitch = ( int )m_header.m_pitchOrLinearSize; } else if ((m_header.m_headerFlags & ( int )DdsHeader.HeaderFlags.DDS_HEADER_FLAGS_LINEARSIZE) != 0) { // Linear size specified.. compute row pitch. Of course, this should never happen // as linear size is *supposed* to be for compressed textures. But Microsoft don't // always play by the rules when it comes to DDS output. rowPitch = ( int )m_header.m_pitchOrLinearSize / ( int )m_header.m_height; } else { // Another case of Microsoft not obeying their standard is the 'Convert to..' shell extension // that ships in the DirectX SDK. Seems to always leave flags empty..so no indication of pitch // or linear size. And - to cap it all off - they leave pitchOrLinearSize as *zero*. Zero??? If // we get this bizarre set of inputs, we just go 'screw it' and compute row pitch ourselves, // making sure we DWORD align it (if that code path is enabled). rowPitch = (( int )m_header.m_width * srcPixelSize); #if APPLY_PITCH_ALIGNMENT rowPitch = ((( int )rowPitch + 3) & (~3)); #endif // APPLY_PITCH_ALIGNMENT } // System.Diagnostics.Debug.WriteLine( "Image width : " + m_header.m_width + ", rowPitch = " + rowPitch ); // Ok.. now, we need to allocate room for the bytes to read in from.. it's rowPitch bytes * height byte[] readPixelData = new byte[rowPitch * m_header.m_height]; input.Read(readPixelData, 0, readPixelData.GetLength(0)); // We now need space for the real pixel data.. that's width * height * 4.. m_pixelData = new byte[m_header.m_width * m_header.m_height * 4]; // And now we have the arduous task of filling that up with stuff.. for (int destY = 0; destY < ( int )m_header.m_height; destY++) { for (int destX = 0; destX < ( int )m_header.m_width; destX++) { // Compute source pixel offset int srcPixelOffset = (destY * rowPitch) + (destX * srcPixelSize); // Read our pixel uint pixelColour = 0; uint pixelRed = 0; uint pixelGreen = 0; uint pixelBlue = 0; uint pixelAlpha = 0; // Build our pixel colour as a DWORD for (int loop = 0; loop < srcPixelSize; loop++) { pixelColour |= ( uint )(readPixelData[srcPixelOffset + loop] << (8 * loop)); } if (fileFormat == DdsFileFormat.DDS_FORMAT_A8R8G8B8) { pixelAlpha = (pixelColour >> 24) & 0xff; pixelRed = (pixelColour >> 16) & 0xff; pixelGreen = (pixelColour >> 8) & 0xff; pixelBlue = (pixelColour >> 0) & 0xff; } else if (fileFormat == DdsFileFormat.DDS_FORMAT_X8R8G8B8) { pixelAlpha = 0xff; pixelRed = (pixelColour >> 16) & 0xff; pixelGreen = (pixelColour >> 8) & 0xff; pixelBlue = (pixelColour >> 0) & 0xff; } else if (fileFormat == DdsFileFormat.DDS_FORMAT_A8B8G8R8) { pixelAlpha = (pixelColour >> 24) & 0xff; pixelRed = (pixelColour >> 0) & 0xff; pixelGreen = (pixelColour >> 8) & 0xff; pixelBlue = (pixelColour >> 16) & 0xff; } else if (fileFormat == DdsFileFormat.DDS_FORMAT_X8B8G8R8) { pixelAlpha = 0xff; pixelRed = (pixelColour >> 0) & 0xff; pixelGreen = (pixelColour >> 8) & 0xff; pixelBlue = (pixelColour >> 16) & 0xff; } else if (fileFormat == DdsFileFormat.DDS_FORMAT_A1R5G5B5) { pixelAlpha = (pixelColour >> 15) * 0xff; pixelRed = (pixelColour >> 10) & 0x1f; pixelGreen = (pixelColour >> 5) & 0x1f; pixelBlue = (pixelColour >> 0) & 0x1f; pixelRed = (pixelRed << 3) | (pixelRed >> 2); pixelGreen = (pixelGreen << 3) | (pixelGreen >> 2); pixelBlue = (pixelBlue << 3) | (pixelBlue >> 2); } else if (fileFormat == DdsFileFormat.DDS_FORMAT_A4R4G4B4) { pixelAlpha = (pixelColour >> 12) & 0xff; pixelRed = (pixelColour >> 8) & 0x0f; pixelGreen = (pixelColour >> 4) & 0x0f; pixelBlue = (pixelColour >> 0) & 0x0f; pixelAlpha = (pixelAlpha << 4) | (pixelAlpha >> 0); pixelRed = (pixelRed << 4) | (pixelRed >> 0); pixelGreen = (pixelGreen << 4) | (pixelGreen >> 0); pixelBlue = (pixelBlue << 4) | (pixelBlue >> 0); } else if (fileFormat == DdsFileFormat.DDS_FORMAT_R8G8B8) { pixelAlpha = 0xff; pixelRed = (pixelColour >> 16) & 0xff; pixelGreen = (pixelColour >> 8) & 0xff; pixelBlue = (pixelColour >> 0) & 0xff; } else if (fileFormat == DdsFileFormat.DDS_FORMAT_R5G6B5) { pixelAlpha = 0xff; pixelRed = (pixelColour >> 11) & 0x1f; pixelGreen = (pixelColour >> 5) & 0x3f; pixelBlue = (pixelColour >> 0) & 0x1f; pixelRed = (pixelRed << 3) | (pixelRed >> 2); pixelGreen = (pixelGreen << 2) | (pixelGreen >> 4); pixelBlue = (pixelBlue << 3) | (pixelBlue >> 2); } // Write the colours away.. int destPixelOffset = (destY * ( int )m_header.m_width * 4) + (destX * 4); m_pixelData[destPixelOffset + 0] = ( byte )pixelRed; m_pixelData[destPixelOffset + 1] = ( byte )pixelGreen; m_pixelData[destPixelOffset + 2] = ( byte )pixelBlue; m_pixelData[destPixelOffset + 3] = ( byte )pixelAlpha; } } } }
public FileType[] GetFileTypeInstances() { DdsSquish.Initialize(); return((FileType[])fileTypes.Clone()); }