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; } }
internal static extern unsafe void SquishDecompressImage(byte *rgba, int width, int height, byte *blocks, int flags, [MarshalAs(UnmanagedType.FunctionPtr)] DdsSquish.ProgressFn progressFn);
public void Save(Stream output, Surface surface, DdsFileFormat fileFormat, DdsCompressorType compressorType, DdsErrorMetric errorMetric, bool generateMipMaps, ResamplingAlgorithm mipMapResamplingAlgorithm, bool weightColorByAlpha, ProgressEventHandler progressCallback) { int num21; int num = 0; bool flag = ((fileFormat == DdsFileFormat.DDS_FORMAT_DXT1) || (fileFormat == DdsFileFormat.DDS_FORMAT_DXT3)) || (fileFormat == DdsFileFormat.DDS_FORMAT_DXT5); int num2 = 1; int mipWidth = surface.Width; int height = surface.Height; if (generateMipMaps) { while ((mipWidth > 1) || (height > 1)) { num2++; mipWidth /= 2; height /= 2; } } this.m_header.m_size = this.m_header.Size(); this.m_header.m_headerFlags = 0x1007; if (flag) { this.m_header.m_headerFlags |= 0x80000; } else { this.m_header.m_headerFlags |= 8; } if (num2 > 1) { this.m_header.m_headerFlags |= 0x20000; } this.m_header.m_height = (uint)surface.Height; this.m_header.m_width = (uint)surface.Width; if (flag) { int num5 = ((surface.Width + 3) / 4) * ((surface.Height + 3) / 4); int num6 = (fileFormat == DdsFileFormat.DDS_FORMAT_DXT1) ? 8 : 0x10; this.m_header.m_pitchOrLinearSize = (uint)(num5 * num6); } else { switch (fileFormat) { case DdsFileFormat.DDS_FORMAT_A8R8G8B8: case DdsFileFormat.DDS_FORMAT_X8R8G8B8: case DdsFileFormat.DDS_FORMAT_A8B8G8R8: case DdsFileFormat.DDS_FORMAT_X8B8G8R8: num = 4; break; case DdsFileFormat.DDS_FORMAT_A1R5G5B5: case DdsFileFormat.DDS_FORMAT_A4R4G4B4: case DdsFileFormat.DDS_FORMAT_R5G6B5: num = 2; break; case DdsFileFormat.DDS_FORMAT_R8G8B8: num = 3; break; } this.m_header.m_pitchOrLinearSize = (uint)(this.m_header.m_width * num); } this.m_header.m_depth = 0; this.m_header.m_mipMapCount = (num2 == 1) ? 0 : ((uint)num2); this.m_header.m_reserved1_0 = 0; this.m_header.m_reserved1_1 = 0; this.m_header.m_reserved1_2 = 0; this.m_header.m_reserved1_3 = 0; this.m_header.m_reserved1_4 = 0; this.m_header.m_reserved1_5 = 0; this.m_header.m_reserved1_6 = 0; this.m_header.m_reserved1_7 = 0; this.m_header.m_reserved1_8 = 0; this.m_header.m_reserved1_9 = 0; this.m_header.m_reserved1_10 = 0; this.m_header.m_pixelFormat.Initialise(fileFormat); this.m_header.m_surfaceFlags = 0x1000; if (num2 > 1) { this.m_header.m_surfaceFlags |= 0x400008; } this.m_header.m_cubemapFlags = 0; this.m_header.m_reserved2_0 = 0; this.m_header.m_reserved2_1 = 0; this.m_header.m_reserved2_2 = 0; output.WriteUInt32(0x20534444); this.m_header.Write(output); int squishFlags = this.GetSquishFlags(fileFormat, compressorType, errorMetric, weightColorByAlpha); mipWidth = surface.Width; height = surface.Height; SizeInt32[] numArray = new SizeInt32[num2]; int[] numArray2 = new int[num2]; int[] pixelsCompleted = new int[num2]; long totalPixels = 0L; for (int i = 0; i < num2; i++) { SizeInt32 num8 = new SizeInt32((mipWidth > 0) ? mipWidth : 1, (height > 0) ? height : 1); numArray[i] = num8; int num9 = num8.Width * num8.Height; numArray2[i] = num9; if (i == 0) { pixelsCompleted[i] = 0; } else { pixelsCompleted[i] = pixelsCompleted[i - 1] + numArray2[i - 1]; } totalPixels += num9; mipWidth /= 2; height /= 2; } mipWidth = surface.Width; height = surface.Height; for (int mipLoop = 0; mipLoop < num2; mipLoop = num21 + 1) { byte[] buffer; SizeInt32 size = numArray[mipLoop]; Surface surface2 = new Surface(size); if (mipLoop == 0) { surface2 = surface; } else { IRenderer <ColorBgra> renderer; SizeInt32 newSize = surface2.Size <ColorBgra>(); switch (mipMapResamplingAlgorithm) { case ResamplingAlgorithm.NearestNeighbor: renderer = surface.ResizeNearestNeighbor(newSize); break; case ResamplingAlgorithm.Bilinear: renderer = surface.ResizeBilinear(newSize); break; case ResamplingAlgorithm.Bicubic: renderer = surface.ResizeBicubic(newSize); break; case ResamplingAlgorithm.SuperSampling: renderer = surface.ResizeSuperSampling(newSize); break; case ResamplingAlgorithm.Fant: renderer = surface.ResizeFant(newSize); break; default: throw ExceptionUtil.InvalidEnumArgumentException <ResamplingAlgorithm>(mipMapResamplingAlgorithm, "mipMapResamplingAlgorithm"); } renderer.Render <ColorBgra>(surface2); } DdsSquish.ProgressFn fn = delegate(int workDone, int workTotal) { long num = workDone * mipWidth; long num2 = pixelsCompleted[mipLoop]; double num3 = (num + num2) / ((double)totalPixels); progressCallback(this, new ProgressEventArgs(DoubleUtil.Clamp(100.0 * num3, 0.0, 100.0))); }; if ((fileFormat >= DdsFileFormat.DDS_FORMAT_DXT1) && (fileFormat <= DdsFileFormat.DDS_FORMAT_DXT5)) { buffer = DdsSquish.CompressImage(surface2, squishFlags, (progressCallback == null) ? null : fn); } else { int num12 = num * surface2.Width; buffer = new byte[num12 * surface2.Height]; buffer.Initialize(); for (int j = 0; j < surface2.Height; j++) { for (int k = 0; k < surface2.Width; k++) { ColorBgra point = surface2.GetPoint(k, j); uint num15 = 0; switch (fileFormat) { case DdsFileFormat.DDS_FORMAT_A8R8G8B8: num15 = (uint)((((point.A << 0x18) | (point.R << 0x10)) | (point.G << 8)) | point.B); break; case DdsFileFormat.DDS_FORMAT_X8R8G8B8: num15 = (uint)(((point.R << 0x10) | (point.G << 8)) | point.B); break; case DdsFileFormat.DDS_FORMAT_A8B8G8R8: num15 = (uint)((((point.A << 0x18) | (point.B << 0x10)) | (point.G << 8)) | point.R); break; case DdsFileFormat.DDS_FORMAT_X8B8G8R8: num15 = (uint)(((point.B << 0x10) | (point.G << 8)) | point.R); break; case DdsFileFormat.DDS_FORMAT_A1R5G5B5: num15 = (uint)((((((point.A != null) ? 1 : 0) << 15) | ((point.R >> 3) << 10)) | ((point.G >> 3) << 5)) | (point.B >> 3)); break; case DdsFileFormat.DDS_FORMAT_A4R4G4B4: num15 = (uint)(((((point.A >> 4) << 12) | ((point.R >> 4) << 8)) | ((point.G >> 4) << 4)) | (point.B >> 4)); break; case DdsFileFormat.DDS_FORMAT_R8G8B8: num15 = (uint)(((point.R << 0x10) | (point.G << 8)) | point.B); break; case DdsFileFormat.DDS_FORMAT_R5G6B5: num15 = (uint)((((point.R >> 3) << 11) | ((point.G >> 2) << 5)) | (point.B >> 3)); break; } int num16 = (j * num12) + (k * num); for (int m = 0; m < num; m++) { buffer[num16 + m] = (byte)((num15 >> (8 * m)) & 0xff); } } if (progressCallback != null) { long num18 = (j + 1) * mipWidth; long num19 = pixelsCompleted[mipLoop]; double num20 = (num18 + num19) / ((double)totalPixels); progressCallback(this, new ProgressEventArgs(100.0 * num20)); } } } output.Write(buffer, 0, buffer.GetLength(0)); mipWidth /= 2; height /= 2; num21 = mipLoop; } }