void WriteFloat(byte[] source, int sourceStart, DDSFormatDetails sourceFormatDetails, byte[] destination, int destStart) { byte[] bytes = sourceFormatDetails.ReadFloatAsArray(source, sourceStart); destination[destStart] = bytes[0]; destination[destStart + 1] = bytes[1]; destination[destStart + 2] = bytes[2]; destination[destStart + 3] = bytes[3]; }
internal static int GetCompressedSizeUpToIndex(double mipIndex, DDSFormatDetails destFormatDetails, int baseWidth, int baseHeight) { /* * Mipmapping halves both dimensions per mip down. Dimensions are then divided by 4 if block compressed as a texel is 4x4 pixels. * e.g. 4096 x 4096 block compressed texture with 8 byte blocks e.g. DXT1 * Sizes of mipmaps: * 4096 / 4 x 4096 / 4 x 8 * (4096 / 4 / 2) x (4096 / 4 / 2) x 8 * (4096 / 4 / 2 / 2) x (4096 / 4 / 2 / 2) x 8 * * Pattern: Each dimension divided by 2 per mip size decreased. * Thus, total is divided by 4. * Size of any mip = Sum(1/4^n) x divWidth x divHeight x blockSize, * where n is the desired mip (0 based), * divWidth and divHeight are the block compress adjusted dimensions (uncompressed textures lead to just original dimensions, block compressed are divided by 4) * * Turns out the partial sum of the infinite sum: Sum(1/4^n) = 1/3 x (4 - 4^-n). Who knew right? */ // TODO: DDS going down past 4x4 bool requiresTinyAdjustment = false; int selectedMipDimensions = (int)(baseWidth / Math.Pow(2d, mipIndex)); if (selectedMipDimensions < 4) { requiresTinyAdjustment = true; } double divisor = 1; if (destFormatDetails.IsBlockCompressed) { divisor = 4; } double shift = 1d / (4 << (int)(2 * (mipIndex - 1))); if (mipIndex == 0) { shift = 1d; } else if (mipIndex == -1) { shift = 4d; } double sumPart = mipIndex == -1 ? 0 : (1d / 3d) * (4d - shift); // Shifting represents 4^-mipIndex. Math.Pow seems slow. double totalSize = destFormatDetails.HeaderSize + (sumPart * destFormatDetails.BlockSize * (baseWidth / divisor) * (baseHeight / divisor)); if (requiresTinyAdjustment) { totalSize += destFormatDetails.BlockSize * 2; } return((int)totalSize); }
protected override void _Read(BinaryReader reader) { long baseOffset = reader.BaseStream.Position; byte[] buffer = reader.ReadBytes((int)reader.BaseStream.Length); MemoryStream memoryStream = new MemoryStream(buffer, 0, buffer.Length, true, true); DDS_Header header = new DDS_Header(memoryStream); FormatDetails = new DDSFormatDetails(header.Format, header.DX10_DXGI_AdditionalHeader.dxgiFormat); MipMaps = DDSGeneral.LoadDDS(memoryStream, header, 0, FormatDetails); Width = header.Width; Height = header.Height; memoryStream.Close(); }
static int WriteCompressedMipMap(byte[] destination, int mipOffset, MipMap mipmap, int blockSize, Action <byte[], int, int, byte[], int, AlphaSettings, DDSFormatDetails> compressor, AlphaSettings alphaSetting, DDSFormatDetails loadedFormatDetails) { if (mipmap.Width < 4 || mipmap.Height < 4) { return(-1); } int destinationTexelCount = mipmap.Width * mipmap.Height / 16; int sourceLineLength = mipmap.Width * 4 * loadedFormatDetails.ComponentSize; int numTexelsInLine = mipmap.Width / 4; var mipWriter = new Action <int>(texelIndex => { // Since this is the top corner of the first texel in a line, skip 4 pixel rows (texel = 4x4 pixels) and the number of rows down the bitmap we are already. int sourceLineOffset = sourceLineLength * 4 * (texelIndex / numTexelsInLine); // Length in bytes x 3 lines x texel line index (how many texel sized lines down the image are we). Index / width will truncate, so for the first texel line, it'll be < 0. For the second texel line, it'll be < 1 and > 0. int sourceTopLeftCorner = ((texelIndex % numTexelsInLine) * 16) * loadedFormatDetails.ComponentSize + sourceLineOffset; // *16 since its 4 pixels with 4 channels each. Index % numTexels will effectively reset each line. compressor(mipmap.Pixels, sourceTopLeftCorner, sourceLineLength, destination, mipOffset + texelIndex * blockSize, alphaSetting, loadedFormatDetails); }); // Choose an acceleration method. if (EnableThreading) { Parallel.For(0, destinationTexelCount, new ParallelOptions { MaxDegreeOfParallelism = ThreadCount }, (mip, loopState) => { mipWriter(mip); }); } else { for (int i = 0; i < destinationTexelCount; i++) { mipWriter(i); } } return(mipOffset + destinationTexelCount * blockSize); }
static RGBColor ReadColorFromTexel(byte[] texel, int i, bool premultiply, DDSFormatDetails formatDetails) { // Pull out rgb from texel // Create current pixel color RGBColor current = new RGBColor(); // Check that texel is big enough if (i + 3 >= texel.Length) { return(current); // Fully transparent color } int componentSize = formatDetails.ComponentSize; float premultiplyValue = (premultiply ? current.a : 1.0f); current.a = formatDetails.ReadFloat(texel, i + 3 * componentSize); current.r = formatDetails.ReadFloat(texel, i + 2 * componentSize) * premultiplyValue; current.g = formatDetails.ReadFloat(texel, i + componentSize) * premultiplyValue; current.b = formatDetails.ReadFloat(texel, i) * premultiplyValue; return(current); }
internal static void CompressBC2Block(byte[] imgData, int sourcePosition, int sourceLineLength, byte[] destination, int destPosition, AlphaSettings alphaSetting, DDSFormatDetails formatDetails) { // Compress Alpha if (alphaSetting == AlphaSettings.RemoveAlphaChannel) { // No alpha so fill with opaque alpha - has to be an alpha value, so make it so RGB is 100% visible. for (int i = 0; i < 8; i++) { destination[destPosition + i] = 0xFF; } } else { int position = sourcePosition + 3; // Only want to read alphas for (int i = 0; i < 8; i += 2) { destination[destPosition + i] = (byte)((imgData[position + 4] & 0xF0) | (imgData[position] >> 4)); destination[destPosition + i + 1] = (byte)((imgData[position + 12] & 0xF0) | (imgData[position + 8] >> 4)); position += sourceLineLength; } } // Compress Color CompressRGBTexel(imgData, sourcePosition, sourceLineLength, destination, destPosition + 8, false, 0f, alphaSetting, formatDetails); }
static void WriteUncompressedPixel(byte[] source, int sourceStart, int[] sourceInds, DDSFormatDetails sourceFormatDetails, uint[] masks, byte[] destination, int destStart, int[] destInds, DDSFormatDetails destFormatDetails, bool oneChannel, bool twoChannel, bool requiresSignedAdjust) { if (twoChannel) // No large components - silly spec... { byte red = sourceFormatDetails.ReadByte(source, sourceStart); byte alpha = sourceFormatDetails.ReadByte(source, sourceStart + 3 * sourceFormatDetails.ComponentSize); destination[destStart] = masks[3] > masks[2] ? red : alpha; destination[destStart + 1] = masks[3] > masks[2] ? alpha : red; } else if (oneChannel) // No large components - silly spec... { byte blue = sourceFormatDetails.ReadByte(source, sourceStart); byte green = sourceFormatDetails.ReadByte(source, sourceStart + 1 * sourceFormatDetails.ComponentSize); byte red = sourceFormatDetails.ReadByte(source, sourceStart + 2 * sourceFormatDetails.ComponentSize); byte alpha = sourceFormatDetails.ReadByte(source, sourceStart + 3 * sourceFormatDetails.ComponentSize); destination[destStart] = (byte)(blue * 0.082 + green * 0.6094 + blue * 0.3086); // Weightings taken from ATI Compressonator. Dunno if this changes things much. } else { // Handle weird conditions where array isn't long enough... if (sourceInds[3] + sourceStart >= source.Length) { return; } for (int i = 0; i < 4; i++) { uint mask = masks[i]; if (mask != 0) { destFormatDetails.WriteColor(source, sourceStart + sourceInds[i], sourceFormatDetails, destination, destStart + destInds[i]); } } // Signed adjustments - Only happens for bytes for now. V8U8 if (requiresSignedAdjust) { destination[destStart + destInds[2]] += 128; destination[destStart + destInds[1]] += 128; } } }
internal static void CompressBC1Block(byte[] imgData, int sourcePosition, int sourceLineLength, byte[] destination, int destPosition, AlphaSettings alphaSetting, DDSFormatDetails formatDetails) { CompressRGBTexel(imgData, sourcePosition, sourceLineLength, destination, destPosition, true, (alphaSetting == AlphaSettings.RemoveAlphaChannel ? 0 : DXT1AlphaThreshold), alphaSetting, formatDetails); }
internal static void ReadUncompressed(byte[] source, int sourceStart, byte[] destination, int pixelCount, DDS_Header.DDS_PIXELFORMAT ddspf, DDSFormatDetails formatDetails) { bool requiresSignedAdjustment = ((ddspf.dwFlags & DDS_Header.DDS_PFdwFlags.DDPF_SIGNED) == DDS_Header.DDS_PFdwFlags.DDPF_SIGNED); int sourceIncrement = ddspf.dwRGBBitCount / 8; // /8 for bits to bytes conversion bool oneChannel = (ddspf.dwFlags & DDS_Header.DDS_PFdwFlags.DDPF_LUMINANCE) == DDS_Header.DDS_PFdwFlags.DDPF_LUMINANCE; bool twoChannel = (ddspf.dwFlags & DDS_Header.DDS_PFdwFlags.DDPF_ALPHAPIXELS) == DDS_Header.DDS_PFdwFlags.DDPF_ALPHAPIXELS && oneChannel; uint AMask = ddspf.dwABitMask; uint RMask = ddspf.dwRBitMask; uint GMask = ddspf.dwGBitMask; uint BMask = ddspf.dwBBitMask; ///// Figure out channel existance and ordering. // Setup array that indicates channel offset from pixel start. // e.g. Alpha is usually first, and is given offset 0. // NOTE: Ordering array is in ARGB order, and the stored indices change depending on detected channel order. // A negative index indicates channel doesn't exist in data and sets channel to 0xFF. List <uint> maskOrder = new List <uint>(4) { AMask, RMask, GMask, BMask }; maskOrder.Sort(); maskOrder.RemoveAll(t => t == 0); // Required, otherwise indicies get all messed up when there's only two channels, but it's not indicated as such. // TODO: Cubemaps and hardcoded format readers for performance int AIndex = 0; int RIndex = 0; int GIndex = 0; int BIndex = 0; if (twoChannel) // Note: V8U8 does not come under this one. { // Intensity is first byte, then the alpha. Set all RGB to intensity for grayscale. // Second mask is always RMask as determined by the DDS Spec. AIndex = AMask > RMask ? 1 : 0; RIndex = AMask > RMask ? 0 : 1; GIndex = AMask > RMask ? 0 : 1; BIndex = AMask > RMask ? 0 : 1; } else if (oneChannel) { // Decide whether it's alpha or not. AIndex = AMask == 0 ? -1 : 0; RIndex = AMask == 0 ? 0 : -1; GIndex = AMask == 0 ? 0 : -1; BIndex = AMask == 0 ? 0 : -1; } else { // Set default ordering AIndex = AMask == 0 ? -1 : maskOrder.IndexOf(AMask) * formatDetails.ComponentSize; RIndex = RMask == 0 ? -1 : maskOrder.IndexOf(RMask) * formatDetails.ComponentSize; GIndex = GMask == 0 ? -1 : maskOrder.IndexOf(GMask) * formatDetails.ComponentSize; BIndex = BMask == 0 ? -1 : maskOrder.IndexOf(BMask) * formatDetails.ComponentSize; } // Determine order of things int destAInd = 3 * formatDetails.ComponentSize; int destRInd = 2 * formatDetails.ComponentSize; int destGInd = 1 * formatDetails.ComponentSize; int destBInd = 0; switch (formatDetails.ComponentSize) { case 1: // Check masks fit properly if (maskOrder.Count != sourceIncrement) { // Determine mask size var lengths = new int[4]; lengths[0] = CountSetBits(BMask); lengths[1] = CountSetBits(GMask); lengths[2] = CountSetBits(RMask); lengths[3] = CountSetBits(AMask); ReadBytesLegacy(source, sourceStart, sourceIncrement, lengths, destination, new int[] { destBInd, destGInd, destRInd, destAInd }); } else { ReadBytes(source, sourceStart, sourceIncrement, new int[] { BIndex, GIndex, RIndex, AIndex }, destination, new int[] { destBInd, destGInd, destRInd, destAInd }); } break; case 2: ReadUShorts(source, sourceStart, sourceIncrement, new int[] { BIndex, GIndex, RIndex, AIndex }, destination, new int[] { destBInd, destGInd, destRInd, destAInd }); break; case 4: ReadFloats(source, sourceStart, sourceIncrement, new int[] { BIndex, GIndex, RIndex, AIndex }, destination, new int[] { destBInd, destGInd, destRInd, destAInd }); break; } if (requiresSignedAdjustment) { for (int i = 0; i < destination.Length; i += 4) { //destination[i] -= 128; // Don't adjust blue destination[i + 1] -= 128; destination[i + 2] -= 128; // Alpha not adjusted } } }
internal static void CompressRGBTexel(byte[] imgData, int sourcePosition, int sourceLineLength, byte[] destination, int destPosition, bool isDXT1, double alphaRef, AlphaSettings alphaSetting, DDSFormatDetails formatDetails) { int uSteps = 4; bool premultiply = alphaSetting == AlphaSettings.Premultiply; // Read texel RGBColor[] sourceTexel = new RGBColor[16]; int position = sourcePosition; int count = 0; for (int i = 1; i <= 4; i++) { for (int j = 0; j < 4; j++) { sourceTexel[count++] = ReadColorFromTexel(imgData, position, premultiply, formatDetails); position += 4 * formatDetails.ComponentSize; } position = sourcePosition + sourceLineLength * i; } // TODO replace RGBColor with a SIMD vector for speed. Test difference between vector4 and vector<T>, might not be better. // Determine if texel is fully and entirely transparent. If so, can set to white and continue. if (isDXT1) { uSteps = CheckDXT1TexelFullTransparency(sourceTexel, destination, destPosition, alphaRef); if (uSteps == -1) { return; } } RGBColor[] Color = new RGBColor[16]; // Some kind of color adjustment. Not sure what it does, especially if it wasn't dithering... DoColorFixErrorCorrection(Color, sourceTexel); // Palette colors RGBColor ColorA, ColorB, ColorC, ColorD; ColorA = new RGBColor(); ColorB = new RGBColor(); ColorC = new RGBColor(); ColorD = new RGBColor(); // OPTIMISER RGBColor[] minmax = OptimiseRGB(Color, uSteps); ColorA = minmax[0]; ColorB = minmax[1]; // Create interstitial colors? ColorC.r = ColorA.r * LuminanceInv.r; ColorC.g = ColorA.g * LuminanceInv.g; ColorC.b = ColorA.b * LuminanceInv.b; ColorD.r = ColorB.r * LuminanceInv.r; ColorD.g = ColorB.g * LuminanceInv.g; ColorD.b = ColorB.b * LuminanceInv.b; // Yeah...dunno uint wColorA = Encode565(ColorC); uint wColorB = Encode565(ColorD); // Min max are equal - only interpolate 4 interstitial colors if (uSteps == 4 && wColorA == wColorB) { var c2 = BitConverter.GetBytes(wColorA); var c1 = BitConverter.GetBytes(wColorB); ///////////////////// MIN MAX destination[destPosition] = c2[0]; destination[destPosition + 1] = c2[1]; destination[destPosition + 2] = c1[0]; destination[destPosition + 3] = c1[1]; return; } // Interpolate 6 colors or something ColorC = Decode565(wColorA); ColorD = Decode565(wColorB); ColorA.r = ColorC.r * Luminance.r; ColorA.g = ColorC.g * Luminance.g; ColorA.b = ColorC.b * Luminance.b; ColorB.r = ColorD.r * Luminance.r; ColorB.g = ColorD.g * Luminance.g; ColorB.b = ColorD.b * Luminance.b; var step = DoSomethingWithPalette(uSteps, wColorA, wColorB, ColorA, ColorB); // Calculating color direction apparently RGBColor Dir = new RGBColor() { r = step[1].r - step[0].r, g = step[1].g - step[0].g, b = step[1].b - step[0].b }; float fscale = (wColorA != wColorB) ? ((uSteps - 1) / (Dir.r * Dir.r + Dir.g * Dir.g + Dir.b * Dir.b)) : 0.0f; Dir.r *= fscale; Dir.g *= fscale; Dir.b *= fscale; // Encoding colors apparently uint dw = DoOtherColorFixErrorCorrection(sourceTexel, uSteps, alphaRef, step, Dir); uint Min = (uSteps == 3) == (wColorA <= wColorB) ? wColorA : wColorB; uint Max = (uSteps == 3) == (wColorA <= wColorB) ? wColorB : wColorA; var color1 = BitConverter.GetBytes(Min); var color2 = BitConverter.GetBytes(Max); destination[destPosition] = color1[0]; destination[destPosition + 1] = color1[1]; destination[destPosition + 2] = color2[0]; destination[destPosition + 3] = color2[1]; var indicies = BitConverter.GetBytes(dw); destination[destPosition + 4] = indicies[0]; destination[destPosition + 5] = indicies[1]; destination[destPosition + 6] = indicies[2]; destination[destPosition + 7] = indicies[3]; }
internal static void CompressBC7Block(byte[] imgData, int sourcePosition, int sourceLineLength, byte[] destination, int destPosition, AlphaSettings alphaSetting, DDSFormatDetails formatDetails) { BC7.CompressBC7Block(imgData, sourcePosition, sourceLineLength, destination, destPosition); }
internal static int GetMipOffset(double mipIndex, DDSFormatDetails destFormatDetails, int baseWidth, int baseHeight) { // -1 because if we want the offset of the mip, it's the sum of all sizes before it NOT including itself. return(GetCompressedSizeUpToIndex(mipIndex - 1, destFormatDetails, baseWidth, baseHeight)); }
/// <summary> /// Checks image file size to ensure requested mipmap is present in image. /// Header mip count can be incorrect or missing. Use this method to validate the mip you're after. /// </summary> /// <param name="streamLength">Image file stream length.</param> /// <param name="mainWidth">Width of image.</param> /// <param name="mainHeight">Height of image.</param> /// <param name="desiredMipDimension">Max dimension of desired mip.</param> /// <param name="destFormatDetails">Destination format details.</param> /// <param name="mipOffset">Offset of desired mipmap in image.</param> /// <returns>True if mip in image.</returns> public static bool EnsureMipInImage(long streamLength, int mainWidth, int mainHeight, int desiredMipDimension, DDSFormatDetails destFormatDetails, out int mipOffset) { if (mainWidth <= desiredMipDimension && mainHeight <= desiredMipDimension) { mipOffset = destFormatDetails.HeaderSize; return(true); // One mip only } int dependentDimension = mainWidth > mainHeight ? mainWidth : mainHeight; int mipIndex = (int)Math.Log((dependentDimension / desiredMipDimension), 2); if (mipIndex < -1) { throw new InvalidDataException($"Invalid dimensions for mipmapping. Got desired: {desiredMipDimension} and dependent: {dependentDimension}"); } int requiredOffset = GetMipOffset(mipIndex, destFormatDetails, mainHeight, mainWidth); // KFreon: Something wrong with the count here by 1 i.e. the estimate is 1 more than it should be if (destFormatDetails.Format == DDSFormat.DDS_ARGB_8) // TODO: Might not just be 8 bit, still don't know why it's wrong. { requiredOffset -= 2; } mipOffset = requiredOffset; // Should only occur when an image has 0 or 1 mipmap. //if (streamLength <= (requiredOffset - destFormatDetails.HeaderSize)) if (streamLength <= requiredOffset) { return(false); } return(true); }
static void WriteUncompressedMipMap(byte[] destination, int mipOffset, MipMap mipmap, DDSFormatDetails loadedFormatDetails, DDSFormatDetails destFormatDetails, DDS_Header.DDS_PIXELFORMAT ddspf) { DDS_Encoders.WriteUncompressed(mipmap.Pixels, destination, mipOffset, ddspf, loadedFormatDetails, destFormatDetails); }
internal static byte[] Save(List <MipMap> mipMaps, DDSFormatDetails destFormatDetails, AlphaSettings alphaSetting, MipHandling mipChoice) { DDSFormatDetails loadedFormatDetails = new DDSFormatDetails(DDSFormat.DDS_ARGB_8); if ((destFormatDetails.IsMippable && mipChoice == MipHandling.GenerateNew) || (destFormatDetails.IsMippable && mipMaps.Count == 1 && mipChoice == MipHandling.Default)) { BuildMipMaps(mipMaps); } // Set compressor for Block Compressed textures Action <byte[], int, int, byte[], int, AlphaSettings, DDSFormatDetails> compressor = destFormatDetails.BlockEncoder; bool needCheckSize = destFormatDetails.IsBlockCompressed; int height = mipMaps[0].Height; int width = mipMaps[0].Width; if (needCheckSize && !CheckSize_DXT(width, height)) { throw new InvalidOperationException($"DXT compression formats require dimensions to be multiples of 4. Got: {width}x{height}."); } // Create header and write to destination DDS_Header header = new DDS_Header(mipMaps.Count, height, width, destFormatDetails.Format, destFormatDetails.DX10Format); int headerLength = destFormatDetails.HeaderSize; int fullSize = GetCompressedSizeOfImage(mipMaps.Count, destFormatDetails, width, height); /*if (destFormatDetails.ComponentSize != 1) * fullSize += (fullSize - headerLength) * destFormatDetails.ComponentSize;*/// Size adjustment for destination to allow for different component sizes. byte[] destination = new byte[fullSize]; header.WriteToArray(destination, 0); int blockSize = destFormatDetails.BlockSize; if (destFormatDetails.IsBlockCompressed) { int mipOffset = headerLength; foreach (MipMap mipmap in mipMaps) { var temp = WriteCompressedMipMap(destination, mipOffset, mipmap, blockSize, compressor, alphaSetting, loadedFormatDetails); if (temp != -1) // When dimensions too low. { mipOffset = temp; } } } else { // UNCOMPRESSED var action = new Action <int>(mipIndex => { if (alphaSetting == AlphaSettings.RemoveAlphaChannel) { // Remove alpha by setting AMask = 0 var ddspf = header.ddspf; ddspf.dwABitMask = 0; header.ddspf = ddspf; } // Get MipOffset int offset = GetMipOffset(mipIndex, destFormatDetails, width, height); WriteUncompressedMipMap(destination, offset, mipMaps[mipIndex], loadedFormatDetails, destFormatDetails, header.ddspf); }); if (EnableThreading) { Parallel.For(0, mipMaps.Count, new ParallelOptions { MaxDegreeOfParallelism = ThreadCount }, (mip, loopState) => { action(mip); }); } else { for (int i = 0; i < mipMaps.Count; i++) { action(i); } } } return(destination); }
internal static List <MipMap> LoadDDS(MemoryStream compressed, DDS_Header header, int desiredMaxDimension, DDSFormatDetails formatDetails) { MipMap[] MipMaps = null; int mipWidth = header.Width; int mipHeight = header.Height; DDSFormat format = header.Format; int estimatedMips = header.dwMipMapCount; int mipOffset = formatDetails.HeaderSize; int originalOffset = mipOffset; if (!EnsureMipInImage(compressed.Length, mipWidth, mipHeight, 4, formatDetails, out mipOffset)) // Update number of mips too { estimatedMips = 1; } if (estimatedMips == 0) { estimatedMips = EstimateNumMipMaps(mipWidth, mipHeight); } mipOffset = originalOffset; // Needs resetting after checking there's mips in this image. // Ensure there's at least 1 mipmap if (estimatedMips == 0) { estimatedMips = 1; } int orig_estimatedMips = estimatedMips; // Store original count for later (uncompressed only I think) // KFreon: Decide which mip to start loading at - going to just load a few mipmaps if asked instead of loading all, then choosing later. That's slow. if (desiredMaxDimension != 0 && estimatedMips > 1) { if (!EnsureMipInImage(compressed.Length, mipWidth, mipHeight, desiredMaxDimension, formatDetails, out mipOffset)) // Update number of mips too { throw new InvalidDataException($"Requested mipmap does not exist in this image. Top Image Size: {mipWidth}x{mipHeight}, requested mip max dimension: {desiredMaxDimension}."); } // Not the first mipmap. if (mipOffset > formatDetails.HeaderSize) { double divisor = mipHeight > mipWidth ? mipHeight / desiredMaxDimension : mipWidth / desiredMaxDimension; mipHeight = (int)(mipHeight / divisor); mipWidth = (int)(mipWidth / divisor); if (mipWidth == 0 || mipHeight == 0) // Reset as a dimension is too small to resize { mipHeight = header.Height; mipWidth = header.Width; mipOffset = formatDetails.HeaderSize; } else { // Update estimated mips due to changing dimensions. estimatedMips = EstimateNumMipMaps(mipWidth, mipHeight); } } else // The first mipmap { mipOffset = formatDetails.HeaderSize; } } // Move to requested mipmap compressed.Position = mipOffset; // Block Compressed texture chooser. Action <byte[], int, byte[], int, int, bool> DecompressBCBlock = formatDetails.BlockDecoder; MipMaps = new MipMap[estimatedMips]; int blockSize = formatDetails.BlockSize; // KFreon: Read mipmaps if (formatDetails.IsBlockCompressed) // Threading done in the decompression, not here. { for (int m = 0; m < estimatedMips; m++) { // KFreon: If mip is too small, skip out. This happens most often with non-square textures. I think it's because the last mipmap is square instead of the same aspect. // Don't do the mip size check here (<4) since we still need to have a MipMap object for those lower than this for an accurate count. if (mipWidth <= 0 || mipHeight <= 0) // Needed cos it doesn't throw when reading past the end for some reason. { break; } MipMap mipmap = ReadCompressedMipMap(compressed, mipWidth, mipHeight, mipOffset, formatDetails, DecompressBCBlock); MipMaps[m] = mipmap; mipOffset += (int)(mipWidth * mipHeight * (blockSize / 16d)); // Also put the division in brackets cos if the mip dimensions are high enough, the multiplications can exceed int.MaxValue) mipWidth /= 2; mipHeight /= 2; } } else { int startMip = orig_estimatedMips - estimatedMips; // UNCOMPRESSED - Can't really do threading in "decompression" so do it for the mipmaps. var action = new Action <int>(mipIndex => { // Calculate mipOffset and dimensions int offset, width, height; offset = GetMipOffset(mipIndex, formatDetails, header.Width, header.Height); double divisor = mipIndex == 0 ? 1d : 2 << (mipIndex - 1); // Divisor represents 2^mipIndex - Math.Pow seems very slow. width = (int)(header.Width / divisor); height = (int)(header.Height / divisor); MipMap mipmap = null; try { mipmap = ReadUncompressedMipMap(compressed, offset, width, height, header.ddspf, formatDetails); } catch (Exception e) { Debug.WriteLine(e.ToString()); } MipMaps[mipIndex] = mipmap; }); if (EnableThreading) { Parallel.For(startMip, orig_estimatedMips, new ParallelOptions { MaxDegreeOfParallelism = ThreadCount }, (mip, loopState) => { action(mip); }); } else { for (int i = startMip; i < orig_estimatedMips; i++) { action(i); } } } List <MipMap> mips = new List <MipMap>(MipMaps.Where(t => t != null)); if (mips.Count == 0) { throw new InvalidOperationException($"No mipmaps loaded. Estimated mips: {estimatedMips}, mip dimensions: {mipWidth}x{mipHeight}"); } return(mips); }
internal static void CompressBC3Block(byte[] imgData, int sourcePosition, int sourceLineLength, byte[] destination, int destPosition, AlphaSettings alphaSetting, DDSFormatDetails formatDetails) { // Compress Alpha if (alphaSetting == AlphaSettings.RemoveAlphaChannel) { // No alpha so fill with opaque alpha - has to be an alpha value, so make it so RGB is 100% visible. for (int i = 0; i < 8; i++) { destination[destPosition + i] = 0xFF; } } else { Compress8BitBlock(imgData, sourcePosition, sourceLineLength, destination, destPosition, 3, false, formatDetails); } // Compress Color CompressRGBTexel(imgData, sourcePosition, sourceLineLength, destination, destPosition + 8, false, 0f, alphaSetting, formatDetails); }
/// <summary> /// Internal read implementation of the sub classes. /// </summary> /// <param name="reader"></param> /// <exception cref="Exception">Expected DDS RGB24 or RGBA32 color format!</exception> /// <exception cref="NotImplementedException">TODO</exception> protected override void _Read(BinaryReader reader) { long baseOffset = reader.BaseStream.Position; int gbixOffset = 0; int pvrtOffset = 0; uint identifier = reader.ReadUInt32(); if (identifier == m_gbix) //"GBIX" { HasGlobalIndex = true; GlobalIndexSize = reader.ReadUInt32(); GlobalIndex = reader.ReadBytes((int)GlobalIndexSize); reader.BaseStream.Seek(4, SeekOrigin.Current); //Skip "PVRT" gbixOffset = 0x00; pvrtOffset = 0x08 + (int)GlobalIndexSize; } else { identifier = reader.ReadUInt32(); if (identifier == m_gbix) { HasGlobalIndex = true; GlobalIndexSize = reader.ReadUInt32(); GlobalIndex = reader.ReadBytes((int)GlobalIndexSize); gbixOffset = 0x04; pvrtOffset = 0x0C + (int)GlobalIndexSize; } else if (identifier == m_pvrt) { gbixOffset = -1; pvrtOffset = 0x04; } else { gbixOffset = -1; pvrtOffset = 0x00; reader.BaseStream.Seek(-4, SeekOrigin.Current); } } // Read information about the texture ContentSize = reader.ReadUInt32(); PixelFormat = (PvrPixelFormat)reader.ReadByte(); DataFormat = (PvrDataFormat)reader.ReadByte(); reader.BaseStream.Seek(2, SeekOrigin.Current); Width = reader.ReadUInt16(); Height = reader.ReadUInt16(); if (DataFormat == PvrDataFormat.DDS || DataFormat == PvrDataFormat.DDS_2) { if (!(PixelFormat == PvrPixelFormat.DDS_DXT1_RGB24 || PixelFormat == PvrPixelFormat.DDS_DXT3_RGBA32)) { throw new Exception("Expected DDS RGB24 or RGBA32 color format!"); } long ddsOffset = reader.BaseStream.Position; DDS_Header header = new DDS_Header(reader.BaseStream); DDSFormatDetails format = new DDSFormatDetails(header.Format, header.DX10_DXGI_AdditionalHeader.dxgiFormat); reader.BaseStream.Seek(ddsOffset, SeekOrigin.Begin); byte[] ddsBuffer = reader.ReadBytes(header.dwPitchOrLinearSize + header.dwSize + 128); MemoryStream memoryStream = new MemoryStream(ddsBuffer, 0, ddsBuffer.Length, true, true); MipMaps = DDSGeneral.LoadDDS(memoryStream, header, 0, format); memoryStream.Close(); Width = header.Width; Height = header.Height; } else { // Get the codecs and make sure we can decode using them PixelCodec = PvrPixelCodec.GetPixelCodec(PixelFormat); DataCodec = PvrDataCodec.GetDataCodec(DataFormat); if (DataCodec != null && PixelCodec != null) { DataCodec.PixelCodec = PixelCodec; } // Set the number of palette entries int paletteEntries = DataCodec.PaletteEntries; if (DataFormat == PvrDataFormat.VECTOR_QUANTIZATION_SMALL || DataFormat == PvrDataFormat.VECTOR_QUANTIZATION_SMALL_MIPMAP) { if (Width <= 16) { paletteEntries = 64; // Actually 16 } else if (Width <= 32) { paletteEntries = 256; // Actually 64 } else if (Width <= 64) { paletteEntries = 512; // Actually 128 } else { paletteEntries = 1024; // Actually 256 } } // Set the palette and data offsets int paletteOffset = 0; int dataOffset = 0; if (paletteEntries == 0 || DataCodec.NeedsExternalPalette) { paletteOffset = -1; dataOffset = pvrtOffset + 0x10; } else { paletteOffset = pvrtOffset + 0x10; dataOffset = paletteOffset + (paletteEntries * (PixelCodec.Bpp >> 3)); } // Get the compression format and determine if we need to decompress this texture reader.BaseStream.Seek(baseOffset, SeekOrigin.Begin); uint first = reader.ReadUInt32(); reader.BaseStream.Seek(baseOffset + pvrtOffset + 4, SeekOrigin.Begin); uint second = reader.ReadUInt32(); if (first == second - pvrtOffset + dataOffset + 8) { CompressionFormat = PvrCompressionFormat.RLE; } else { CompressionFormat = PvrCompressionFormat.NONE; } CompressionCodec = PvrCompressionCodec.GetCompressionCodec(CompressionFormat); if (CompressionFormat != PvrCompressionFormat.NONE && CompressionCodec != null) { //TODO: Convert to stream compatible code throw new NotImplementedException("TODO"); //m_encodedData = CompressionCodec.Decompress(m_encodedData, dataOffset, PixelCodec, DataCodec); // Now place the offsets in the appropiate area if (CompressionFormat == PvrCompressionFormat.RLE) { if (gbixOffset != -1) { gbixOffset -= 4; } pvrtOffset -= 4; if (paletteOffset != -1) { paletteOffset -= 4; } dataOffset -= 4; } } // If the texture contains mipmaps, gets the offsets of them int[] mipmapOffsets; if (DataCodec.HasMipmaps) { int mipmapOffset = 0; mipmapOffsets = new int[(int)Math.Log(Width, 2) + 1]; // Calculate the padding for the first mipmap offset if (DataFormat == PvrDataFormat.SQUARE_TWIDDLED_MIPMAP) { mipmapOffset = (DataCodec.Bpp) >> 3; // A 1x1 mipmap takes up as much space as a 2x1 mipmap } else if (DataFormat == PvrDataFormat.SQUARE_TWIDDLED_MIPMAP_ALT) { mipmapOffset = (3 * DataCodec.Bpp) >> 3; // A 1x1 mipmap takes up as much space as a 2x2 mipmap } for (int i = mipmapOffsets.Length - 1, size = 1; i >= 0; i--, size <<= 1) { mipmapOffsets[i] = mipmapOffset; mipmapOffset += Math.Max((size * size * DataCodec.Bpp) >> 3, 1); } } else { mipmapOffsets = new int[1] { 0 }; } //DecodeMipmaps() if (paletteOffset != -1) // The texture contains an embedded palette { reader.BaseStream.Seek(baseOffset + paletteOffset, SeekOrigin.Begin); DataCodec.SetPalette(reader, paletteEntries); } MipMaps = new List <MipMap>(); if (DataCodec.HasMipmaps) { for (int i = 0, size = Width; i < mipmapOffsets.Length; i++, size >>= 1) { reader.BaseStream.Seek(baseOffset + dataOffset + mipmapOffsets[i], SeekOrigin.Begin); byte[] pixels = DataCodec.Decode(reader, size, size, PixelCodec); MipMaps.Add(new MipMap(pixels, size, size)); } } else { reader.BaseStream.Seek(baseOffset + dataOffset + mipmapOffsets[0], SeekOrigin.Begin); byte[] pixels = DataCodec.Decode(reader, Width, Height, PixelCodec); MipMaps.Add(new MipMap(pixels, Width, Height)); } } if (HasGlobalIndex) { reader.BaseStream.Seek(baseOffset + ContentSize + 0xC, SeekOrigin.Begin); } else { reader.BaseStream.Seek(baseOffset + ContentSize, SeekOrigin.Begin); } }
// ATI2 3Dc internal static void CompressBC5Block(byte[] imgData, int sourcePosition, int sourceLineLength, byte[] destination, int destPosition, AlphaSettings alphaSetting, DDSFormatDetails formatDetails) { // Green: Channel 1. Compress8BitBlock(imgData, sourcePosition, sourceLineLength, destination, destPosition, 1, false, formatDetails); // Red: Channel 0, 8 destination offset to be after Green. Compress8BitBlock(imgData, sourcePosition, sourceLineLength, destination, destPosition + 8, 2, false, formatDetails); }
void WriteByte(byte[] source, int sourceStart, DDSFormatDetails sourceFormatDetails, byte[] destination, int destStart) { destination[destStart] = sourceFormatDetails.ReadByte(source, sourceStart); }
internal static void WriteUncompressed(byte[] source, byte[] destination, int destStart, DDS_Header.DDS_PIXELFORMAT dest_ddspf, DDSFormatDetails sourceFormatDetails, DDSFormatDetails destFormatDetails) { int byteCount = dest_ddspf.dwRGBBitCount / 8; bool requiresSignedAdjust = (dest_ddspf.dwFlags & DDS_Header.DDS_PFdwFlags.DDPF_SIGNED) == DDS_Header.DDS_PFdwFlags.DDPF_SIGNED; bool oneChannel = (dest_ddspf.dwFlags & DDS_Header.DDS_PFdwFlags.DDPF_LUMINANCE) == DDS_Header.DDS_PFdwFlags.DDPF_LUMINANCE; bool twoChannel = oneChannel && (dest_ddspf.dwFlags & DDS_Header.DDS_PFdwFlags.DDPF_ALPHAPIXELS) == DDS_Header.DDS_PFdwFlags.DDPF_ALPHAPIXELS; uint AMask = dest_ddspf.dwABitMask; uint RMask = dest_ddspf.dwRBitMask; uint GMask = dest_ddspf.dwGBitMask; uint BMask = dest_ddspf.dwBBitMask; ///// Figure out channel existance and ordering. // Setup array that indicates channel offset from pixel start. // e.g. Alpha is usually first, and is given offset 0. // NOTE: Ordering array is in ARGB order, and the stored indices change depending on detected channel order. // A negative index indicates channel doesn't exist in data and sets channel to 0xFF. if (destFormatDetails.Format == DDSFormat.DDS_ARGB_32F) { AMask = 4; BMask = 3; GMask = 2; RMask = 1; } List <uint> maskOrder = new List <uint>(4) { AMask, RMask, GMask, BMask }; maskOrder.Sort(); maskOrder.RemoveAll(t => t == 0); // Required, otherwise indicies get all messed up when there's only two channels, but it's not indicated as such. // Determine channel ordering int destAIndex = AMask == 0 ? -1 : maskOrder.IndexOf(AMask) * destFormatDetails.ComponentSize; int destRIndex = RMask == 0 ? -1 : maskOrder.IndexOf(RMask) * destFormatDetails.ComponentSize; int destGIndex = GMask == 0 ? -1 : maskOrder.IndexOf(GMask) * destFormatDetails.ComponentSize; int destBIndex = BMask == 0 ? -1 : maskOrder.IndexOf(BMask) * destFormatDetails.ComponentSize; int sourceAInd = 3 * sourceFormatDetails.ComponentSize; int sourceRInd = 2 * sourceFormatDetails.ComponentSize; int sourceGInd = 1 * sourceFormatDetails.ComponentSize; int sourceBInd = 0; var sourceInds = new int[] { sourceBInd, sourceGInd, sourceRInd, sourceAInd }; var destInds = new int[] { destBIndex, destGIndex, destRIndex, destAIndex }; var masks = new uint[] { BMask, GMask, RMask, AMask }; int sourceIncrement = 4 * sourceFormatDetails.ComponentSize; if (EnableThreading) { Parallel.For(0, source.Length / sourceIncrement, new ParallelOptions { MaxDegreeOfParallelism = ThreadCount }, (ind, loopState) => { WriteUncompressedPixel(source, ind * sourceIncrement, sourceInds, sourceFormatDetails, masks, destination, destStart + ind * byteCount, destInds, destFormatDetails, oneChannel, twoChannel, requiresSignedAdjust); }); } else { for (int i = 0; i < source.Length; i += 4 * sourceFormatDetails.ComponentSize, destStart += byteCount) { WriteUncompressedPixel(source, i, sourceInds, sourceFormatDetails, masks, destination, destStart, destInds, destFormatDetails, oneChannel, twoChannel, requiresSignedAdjust); } } }
public static void Compress8BitBlock(byte[] source, int sourcePosition, int sourceLineLength, byte[] destination, int destPosition, int channel, bool isSigned, DDSFormatDetails formatDetails) { // KFreon: Get min and max byte min = 255; byte max = 0; int channelBitSize = channel * formatDetails.ComponentSize; int count = sourcePosition + channelBitSize; byte[] sourceTexel = new byte[16]; int sourceTexelInd = 0; for (int i = 1; i <= 4; i++) { for (int j = 0; j < 4; j++) { byte color = formatDetails.ReadByte(source, count); sourceTexel[sourceTexelInd++] = color; // Cache source if (color > max) { max = color; } else if (color < min) { min = color; } count += 4 * formatDetails.ComponentSize; // skip to next entry in channel } count = sourcePosition + channelBitSize + sourceLineLength * i; } // Build Palette byte[] Colors = Build8BitPalette(min, max, isSigned); // Compress Pixels ulong line = 0; sourceTexelInd = 0; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { int ind = (i << 2) + j; byte color = sourceTexel[sourceTexelInd++]; int index = GetClosestValue(Colors, color); line |= (ulong)index << (ind * 3); } } byte[] compressed = BitConverter.GetBytes(line); destination[destPosition] = min; destination[destPosition + 1] = max; for (int i = 2; i < 8; i++) { destination[destPosition + i] = compressed[i - 2]; } }
/// <summary> /// Internal write implementation of the sub classes. /// </summary> /// <param name="writer"></param> /// <exception cref="Exception">Expected DDS RGB24 or RGBA32 color format!</exception> /// <exception cref="InvalidOperationException"> /// </exception> protected override void _Write(BinaryWriter writer) { long baseOffset = writer.BaseStream.Position; Bitmap bmp = CreateBitmap(); //bmp.RotateFlip(RotateFlipType.RotateNoneFlipY); if (DataFormat == PvrDataFormat.DDS || DataFormat == PvrDataFormat.DDS_2) { if (!(PixelFormat == PvrPixelFormat.DDS_DXT1_RGB24 || PixelFormat == PvrPixelFormat.DDS_DXT3_RGBA32)) { throw new Exception("Expected DDS RGB24 or RGBA32 color format!"); } byte[] ddsBuffer = null; if (PixelFormat == PvrPixelFormat.DDS_DXT1_RGB24) { DDSFormatDetails ddsFormatDetails = new DDSFormatDetails(DDSFormat.DDS_DXT1); ddsBuffer = DDSGeneral.Save(MipMaps, ddsFormatDetails, DDSGeneral.AlphaSettings.KeepAlpha, DDSGeneral.MipHandling.Default); } else if (PixelFormat == PvrPixelFormat.DDS_DXT3_RGBA32) { DDSFormatDetails ddsFormatDetails = new DDSFormatDetails(DDSFormat.DDS_DXT3); ddsBuffer = DDSGeneral.Save(MipMaps, ddsFormatDetails, DDSGeneral.AlphaSettings.KeepAlpha, DDSGeneral.MipHandling.Default); } if (HasGlobalIndex) { writer.Write(m_gbix); writer.Write(GlobalIndexSize); writer.Write(GlobalIndex); } writer.Write(m_pvrt); writer.Write(ddsBuffer.Length + 16); writer.Write((byte)PixelFormat); writer.Write((byte)DataFormat); writer.Write((ushort)0); writer.Write((ushort)Width); writer.Write((ushort)Height); writer.Write(ddsBuffer); } else { // Set the data format and pixel format and load the appropiate codecs PixelCodec = PvrPixelCodec.GetPixelCodec(PixelFormat); DataCodec = PvrDataCodec.GetDataCodec(DataFormat); // Make sure the pixel and data codecs exists and we can encode to it if (PixelCodec == null || !PixelCodec.CanEncode) { throw new InvalidOperationException(); } if (DataCodec == null || !DataCodec.CanEncode) { throw new InvalidOperationException(); } DataCodec.PixelCodec = PixelCodec; byte[] decodedData = null; if (DataCodec.PaletteEntries != 0) { if (DataCodec.VQ) { decodedData = BitmapToRawVQ(bmp, DataCodec.PaletteEntries, out m_texturePalette); } else { // Convert the bitmap to an array containing indicies. decodedData = BitmapToRawIndexed(bmp, DataCodec.PaletteEntries, out m_texturePalette); // If this texture has an external palette file, set up the palette encoder if (DataCodec.NeedsExternalPalette) { PaletteEncoder = new PvpPaletteEncoder(m_texturePalette, (ushort)DataCodec.PaletteEntries, PixelFormat, PixelCodec); } } } else { decodedData = BitmapToRaw(bmp); } // Calculate what the length of the texture will be int textureLength = 16 + (int)(Width * Height * (DataCodec.Bpp / 8.0)); if (HasGlobalIndex) { textureLength += 16; } if (DataCodec.PaletteEntries != 0 && !DataCodec.NeedsExternalPalette) { textureLength += (DataCodec.PaletteEntries * PixelCodec.Bpp / 8); } // Calculate the mipmap padding (if the texture contains mipmaps) int mipmapPadding = 0; if (DataCodec.HasMipmaps) { if (DataFormat == PvrDataFormat.SQUARE_TWIDDLED_MIPMAP) { // A 1x1 mipmap takes up as much space as a 2x1 mipmap // There are also 4 extra bytes at the end of the file mipmapPadding = (DataCodec.Bpp) >> 3; textureLength += 4; } else if (DataFormat == PvrDataFormat.SQUARE_TWIDDLED_MIPMAP_ALT) { // A 1x1 mipmap takes up as much space as a 2x2 mipmap mipmapPadding = (3 * DataCodec.Bpp) >> 3; } textureLength += mipmapPadding; for (int size = 1; size < Width; size <<= 1) { textureLength += Math.Max((size * size * DataCodec.Bpp) >> 3, 1); } } MemoryStream output = new MemoryStream(textureLength); BinaryWriter outputWriter = new BinaryWriter(output); // Write out the GBIX header (if we are including one) if (HasGlobalIndex) { outputWriter.Write(m_gbix); outputWriter.Write(GlobalIndexSize); outputWriter.Write(GlobalIndex); } // Write out the PVRT header outputWriter.Write(m_pvrt); if (HasGlobalIndex) { outputWriter.Write(textureLength - 24); } else { outputWriter.Write(textureLength - 8); } outputWriter.Write((byte)PixelFormat); outputWriter.Write((byte)DataFormat); outputWriter.Write((ushort)0); outputWriter.Write((ushort)Width); outputWriter.Write((ushort)Height); // If we have an internal palette, write it if (DataCodec.PaletteEntries != 0 && !DataCodec.NeedsExternalPalette) { byte[] palette = PixelCodec.EncodePalette(m_texturePalette, DataCodec.PaletteEntries); output.Write(palette, 0, palette.Length); } // Write out any mipmaps if (DataCodec.HasMipmaps) { // Write out any padding bytes before the 1x1 mipmap for (int i = 0; i < mipmapPadding; i++) { output.WriteByte(0); } for (int size = 1; size < Width; size <<= 1) { byte[] mipmapDecodedData = null; if (DataCodec.NeedsExternalPalette) { if (DataCodec.VQ) { mipmapDecodedData = BitmapToRawVQResized(bmp, size, 1, m_codeBook); } else { mipmapDecodedData = BitmapToRawIndexedResized(bmp, size, 1, m_palette); } } else { mipmapDecodedData = BitmapToRawResized(bmp, size, 1); } byte[] mipmapTextureData = DataCodec.Encode(mipmapDecodedData, 0, size, size); output.Write(mipmapTextureData, 0, mipmapTextureData.Length); } } // Write the texture data byte[] textureData = DataCodec.Encode(decodedData, Width, Height, null); output.Write(textureData, 0, textureData.Length); // If the data format is square twiddled with mipmaps, write out the extra bytes. if (DataFormat == PvrDataFormat.SQUARE_TWIDDLED_MIPMAP) { output.Write(new byte[] { 0, 0, 0, 0 }, 0, 4); } // Compress the texture if (CompressionFormat != PvrCompressionFormat.NONE) { CompressionCodec = PvrCompressionCodec.GetCompressionCodec(CompressionFormat); if (CompressionCodec != null) { // Ok, we need to convert the current stream to an array, compress it, then write it back to a new stream byte[] buffer = output.ToArray(); buffer = CompressionCodec.Compress(buffer, (HasGlobalIndex ? 0x20 : 0x10), PixelCodec, DataCodec); writer.Write(buffer); } } else { writer.Write(output.GetBuffer()); } } }
internal static int GetCompressedSizeOfImage(int mipCount, DDSFormatDetails destFormatDetails, int baseWidth, int baseHeight) { return(GetCompressedSizeUpToIndex(mipCount, destFormatDetails, baseWidth, baseHeight)); }
void WriteUShort(byte[] source, int sourceStart, DDSFormatDetails sourceFormatDetails, byte[] destination, int destStart) { byte[] bytes = sourceFormatDetails.ReadUShortAsArray(source, sourceStart); destination[destStart] = bytes[0]; destination[destStart + 1] = bytes[1]; }
private static MipMap ReadUncompressedMipMap(MemoryStream stream, int mipOffset, int mipWidth, int mipHeight, DDS_Header.DDS_PIXELFORMAT ddspf, DDSFormatDetails formatDetails) { byte[] data = stream.GetBuffer(); byte[] mipmap = new byte[mipHeight * mipWidth * 4 * formatDetails.ComponentSize]; // Smaller sizes breaks things, so just exclude them if (mipHeight >= 4 && mipWidth >= 4) { DDS_Decoders.ReadUncompressed(data, mipOffset, mipmap, mipWidth * mipHeight, ddspf, formatDetails); } return(new MipMap(mipmap, mipWidth, mipHeight)); }
private static MipMap ReadCompressedMipMap(MemoryStream compressed, int mipWidth, int mipHeight, int mipOffset, DDSFormatDetails formatDetails, Action <byte[], int, byte[], int, int, bool> DecompressBlock) { // Gets stream as data. Note that this array isn't necessarily the correct size. Likely to have garbage at the end. // Don't want to use ToArray as that creates a new array. Don't want that. byte[] CompressedData = compressed.GetBuffer(); byte[] decompressedData = new byte[4 * mipWidth * mipHeight * formatDetails.ComponentSize]; int decompressedRowLength = mipWidth * 4; int texelRowSkip = decompressedRowLength * 4; int texelCount = (mipWidth * mipHeight) / 16; int numTexelsInRow = mipWidth / 4; if (numTexelsInRow < 1) { numTexelsInRow = 1; } if (texelCount != 0) { var action = new Action <int>(texelIndex => { int compressedPosition = mipOffset + texelIndex * formatDetails.BlockSize; int decompressedStart = (int)(texelIndex / numTexelsInRow) * texelRowSkip + (texelIndex % numTexelsInRow) * 16; // Problem with how I handle dimensions (no virtual padding or anything yet) if (!CheckSize_DXT(mipWidth, mipHeight)) { return; } try { DecompressBlock(CompressedData, compressedPosition, decompressedData, decompressedStart, decompressedRowLength, formatDetails.IsPremultipliedFormat); } catch (IndexOutOfRangeException e) { throw; } }); // Actually perform decompression using threading, no threading, or GPU. if (EnableThreading) { Parallel.For(0, texelCount, new ParallelOptions { MaxDegreeOfParallelism = ThreadCount }, (texelIndex, loopstate) => { action(texelIndex); }); } else { for (int texelIndex = 0; texelIndex < texelCount; texelIndex++) { action(texelIndex); } } } return(new MipMap(decompressedData, mipWidth, mipHeight)); }
/// <summary> /// Calculates the compressed size of an image with given parameters. /// </summary> /// <param name="numMipmaps">Number of mipmaps in image. JPG etc only have 1.</param> /// <param name="formatDetails">Detailed information about format.</param> /// <param name="width">Width of image (top mip if mip-able)</param> /// <param name="height">Height of image (top mip if mip-able)</param> /// <returns>Size of compressed image.</returns> public static int GetCompressedSize(int numMipmaps, DDSFormatDetails formatDetails, int width, int height) { return(DDSGeneral.GetCompressedSizeOfImage(numMipmaps, formatDetails, width, height)); }