Пример #1
0
        /// <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);
        }
Пример #2
0
        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);
        }
Пример #3
0
        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);
        }