internal static int GetCompressedSizeUpToIndex(double mipIndex, ImageFormats.ImageEngineFormatDetails 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); }
/// <summary> /// Converts a single .png file to .dds format. /// </summary> /// <param name="file">Full path of the file.</param> /// <param name="inPath">Full input directory path.</param> /// <param name="outPath">Full output directory path.</param> /// <param name="index">Integer label. Start at i = 1.</param> /// <param name="total">Total number of files to be converted.</param> public void ConvertPngToDds(string file, string inPath, string outPath, ref int index, int total) { //get data about file..."D:\\msys2\\home\\Nathan\\dbg\\00004d824c7204b6f7-DDS_ARGB_8.png" string name = Path.GetFileName(file); string[] details = name.Split('-'); if (details.Length > 2) { throw new Exception("Additional details detected."); } if (details.Length == 1) //no details found { details = new string[] { name, "" } } ; ImageFormats.ImageEngineFormatDetails imageDetails; switch (details[1].Split('.')[0]) //A bit of a complicated way to only look at the part without file extension... { case "DDS_ARGB_8": imageDetails = new ImageFormats.ImageEngineFormatDetails(ImageEngineFormat.DDS_ARGB_8); break; case "DDS_G16_R16": imageDetails = new ImageFormats.ImageEngineFormatDetails(ImageEngineFormat.DDS_G16_R16); break; case "DDS_DXT1": imageDetails = new ImageFormats.ImageEngineFormatDetails(ImageEngineFormat.DDS_DXT1); break; case "DDS_DXT5": default: //we don't know what to do here...so we just assume DXT5. imageDetails = new ImageFormats.ImageEngineFormatDetails(ImageEngineFormat.DDS_DXT5); break; } //we have already stripped metadata in split. Begin to return it to dds with the stripped metadata. FileStream fs = new FileStream(Path.Combine(outPath, Path.ChangeExtension(details[0], ".dds")), FileMode.Create); ImageEngineImage imi = new ImageEngineImage(Path.Combine(inPath, name)); Console.Out.WriteLine("Converting: " + Path.GetFileName(file) + " " + index + "\\" + total); imi.Save(fs, imageDetails, MipHandling.Default, 0, 0, false); fs.Close(); } }
private void SaveDDS(List <string> paths, string outputPath) { ImageEngineImage outputImage = new ImageEngineImage(paths[0]); for (int i = 1; i < paths.Count; i++) { ImageEngineImage mipImage = new ImageEngineImage(paths[i]); MipMap mip = new MipMap(mipImage.MipMaps[0].Pixels, mipImage.Width, mipImage.Height, mipImage.FormatDetails); outputImage.MipMaps.Add(mip); } ImageFormats.ImageEngineFormatDetails outputFormat = new ImageFormats.ImageEngineFormatDetails(ImageEngineFormat.DDS_DXT1); byte[] data = outputImage.Save(outputFormat, MipHandling.KeepExisting); using (var file = File.Create(outputPath)) { file.Write(data, 0, data.Length); } }
// ATI2 3Dc internal static void CompressBC5Block(byte[] imgData, int sourcePosition, int sourceLineLength, byte[] destination, int destPosition, AlphaSettings alphaSetting, ImageFormats.ImageEngineFormatDetails 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); }
internal static void CompressBC3Block(byte[] imgData, int sourcePosition, int sourceLineLength, byte[] destination, int destPosition, AlphaSettings alphaSetting, ImageFormats.ImageEngineFormatDetails 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 Colour CompressRGBTexel(imgData, sourcePosition, sourceLineLength, destination, destPosition + 8, false, 0f, alphaSetting, formatDetails); }
internal static void CompressBC2Block(byte[] imgData, int sourcePosition, int sourceLineLength, byte[] destination, int destPosition, AlphaSettings alphaSetting, ImageFormats.ImageEngineFormatDetails 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] & 0xF0) | (imgData[position + 4] >> 4)); destination[destPosition + i + 1] = (byte)((imgData[position + 8] & 0xF0) | (imgData[position + 12] >> 4)); position += sourceLineLength; } } // Compress Colour CompressRGBTexel(imgData, sourcePosition, sourceLineLength, destination, destPosition + 8, false, 0f, alphaSetting, formatDetails); }
static void WriteUncompressedPixel(byte[] source, int sourceStart, int[] sourceInds, ImageFormats.ImageEngineFormatDetails sourceFormatDetails, uint[] masks, byte[] destination, int destStart, int[] destInds, ImageFormats.ImageEngineFormatDetails 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.WriteColour(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, ImageFormats.ImageEngineFormatDetails 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, ImageFormats.ImageEngineFormatDetails 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 } } }
private static MipMap ReadUncompressedMipMap(MemoryStream stream, int mipOffset, int mipWidth, int mipHeight, DDS_Header.DDS_PIXELFORMAT ddspf, ImageFormats.ImageEngineFormatDetails 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, formatDetails)); }
/// <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, ImageFormats.ImageEngineFormatDetails 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 == ImageEngineFormat.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); }
internal static int GetMipOffset(double mipIndex, ImageFormats.ImageEngineFormatDetails 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)); }
static void WriteUncompressedMipMap(byte[] destination, int mipOffset, MipMap mipmap, ImageFormats.ImageEngineFormatDetails destFormatDetails, DDS_Header.DDS_PIXELFORMAT ddspf) { DDS_Encoders.WriteUncompressed(mipmap.Pixels, destination, mipOffset, ddspf, mipmap.LoadedFormatDetails, destFormatDetails); }
private static MipMap ReadCompressedMipMap(MemoryStream compressed, int mipWidth, int mipHeight, int mipOffset, ImageFormats.ImageEngineFormatDetails 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 (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 (ImageEngine.EnableThreading) { Parallel.For(0, texelCount, new ParallelOptions { MaxDegreeOfParallelism = ImageEngine.NumThreads }, (texelIndex, loopstate) => { if (ImageEngine.IsCancellationRequested) { loopstate.Stop(); } action(texelIndex); }); } else { for (int texelIndex = 0; texelIndex < texelCount; texelIndex++) { if (ImageEngine.IsCancellationRequested) { break; } action(texelIndex); } } } // No else here cos the lack of texels means it's below texel dimensions (4x4). So the resulting block is set to 0. Not ideal, but who sees 2x2 mipmaps? if (ImageEngine.IsCancellationRequested) { return(null); } return(new MipMap(decompressedData, mipWidth, mipHeight, formatDetails)); }
internal static byte[] Save(List <MipMap> mipMaps, ImageFormats.ImageEngineFormatDetails destFormatDetails, AlphaSettings alphaSetting) { // Set compressor for Block Compressed textures Action <byte[], int, int, byte[], int, AlphaSettings, ImageFormats.ImageEngineFormatDetails> 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) { if (ImageEngine.IsCancellationRequested) { break; } var temp = WriteCompressedMipMap(destination, mipOffset, mipmap, blockSize, compressor, alphaSetting); 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], destFormatDetails, header.ddspf); }); if (ImageEngine.EnableThreading) { Parallel.For(0, mipMaps.Count, new ParallelOptions { MaxDegreeOfParallelism = ImageEngine.NumThreads }, (mip, loopState) => { if (ImageEngine.IsCancellationRequested) { loopState.Stop(); } action(mip); }); } else { for (int i = 0; i < mipMaps.Count; i++) { if (ImageEngine.IsCancellationRequested) { break; } action(i); } } } return(ImageEngine.IsCancellationRequested ? null : destination); }
internal static void CompressBC7Block(byte[] imgData, int sourcePosition, int sourceLineLength, byte[] destination, int destPosition, AlphaSettings alphaSetting, ImageFormats.ImageEngineFormatDetails formatDetails) { BC7.CompressBC7Block(imgData, sourcePosition, sourceLineLength, destination, destPosition); }
internal static int GetCompressedSizeOfImage(int mipCount, ImageFormats.ImageEngineFormatDetails destFormatDetails, int baseWidth, int baseHeight) { return(GetCompressedSizeUpToIndex(mipCount, destFormatDetails, baseWidth, baseHeight)); }
internal static void WriteUncompressed(byte[] source, byte[] destination, int destStart, DDS_Header.DDS_PIXELFORMAT dest_ddspf, ImageFormats.ImageEngineFormatDetails sourceFormatDetails, ImageFormats.ImageEngineFormatDetails 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 == ImageEngineFormat.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 (ImageEngine.EnableThreading) { Parallel.For(0, source.Length / sourceIncrement, new ParallelOptions { MaxDegreeOfParallelism = ImageEngine.NumThreads }, (ind, loopState) => { if (ImageEngine.IsCancellationRequested) { loopState.Stop(); } 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) { if (ImageEngine.IsCancellationRequested) { break; } WriteUncompressedPixel(source, i, sourceInds, sourceFormatDetails, masks, destination, destStart, destInds, destFormatDetails, oneChannel, twoChannel, requiresSignedAdjust); } } }
internal static List <MipMap> LoadDDS(MemoryStream compressed, DDS_Header header, int desiredMaxDimension, ImageFormats.ImageEngineFormatDetails formatDetails) { MipMap[] MipMaps = null; int mipWidth = header.Width; int mipHeight = header.Height; ImageEngineFormat 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++) { if (ImageEngine.IsCancellationRequested) { break; } // 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 (ImageEngine.EnableThreading) { Parallel.For(startMip, orig_estimatedMips, new ParallelOptions { MaxDegreeOfParallelism = ImageEngine.NumThreads }, (mip, loopState) => { if (ImageEngine.IsCancellationRequested) { loopState.Stop(); } action(mip); }); } else { for (int i = startMip; i < orig_estimatedMips; i++) { if (ImageEngine.IsCancellationRequested) { break; } 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); }