/// <summary> /// Determines image type via headers. /// Keeps stream position. /// </summary> /// <param name="imgData">Image data, incl header.</param> /// <returns>Type of image.</returns> public static SupportedExtensions DetermineImageType(Stream imgData) { SupportedExtensions ext = SupportedExtensions.UNKNOWN; // KFreon: Save position and go back to start long originalPos = imgData.Position; imgData.Seek(0, SeekOrigin.Begin); var bits = new byte[8]; imgData.Read(bits, 0, 8); // DDS if (DDS_Header.CheckIdentifier(bits)) { ext = SupportedExtensions.DDS; } // KFreon: Reset stream position imgData.Seek(originalPos, SeekOrigin.Begin); return(ext); }
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); }