/// <summary>
        /// Save mipmaps as given format to stream.
        /// </summary>
        /// <param name="MipMaps">List of Mips to save.</param>
        /// <param name="mipChoice">Determines how to handle mipmaps.</param>
        /// <param name="maxDimension">Maximum value for either image dimension.</param>
        /// <param name="alphaSetting">Determines how to handle alpha.</param>
        /// <param name="mipToSave">0 based index on which mipmap to make top of saved image.</param>
        /// <param name="destFormatDetails">Details about the destination format.</param>
        /// <returns>True on success.</returns>
        internal static byte[] Save(List <MipMap> MipMaps, ImageFormats.ImageEngineFormatDetails destFormatDetails, MipHandling mipChoice, AlphaSettings alphaSetting, int maxDimension = 0, int mipToSave = 0)
        {
            List <MipMap> newMips = new List <MipMap>(MipMaps);

            int width  = newMips[0].Width;
            int height = newMips[0].Height;

            if ((destFormatDetails.IsMippable && mipChoice == MipHandling.GenerateNew) || (destFormatDetails.IsMippable && newMips.Count == 1 && mipChoice == MipHandling.Default))
            {
                DDSGeneral.BuildMipMaps(newMips);
            }

            // KFreon: Resize if asked
            if (maxDimension != 0 && maxDimension < width && maxDimension < height)
            {
                if (!UsefulThings.General.IsPowerOfTwo(maxDimension))
                {
                    throw new ArgumentException($"{nameof(maxDimension)} must be a power of 2. Got {nameof(maxDimension)} = {maxDimension}");
                }

                // KFreon: Check if there's a mipmap suitable, removes all larger mipmaps
                var validMipmap = newMips.Where(img => (img.Width == maxDimension && img.Height <= maxDimension) || (img.Height == maxDimension && img.Width <= maxDimension));  // Check if a mip dimension is maxDimension and that the other dimension is equal or smaller
                if (validMipmap?.Count() != 0)
                {
                    int index = newMips.IndexOf(validMipmap.First());
                    newMips.RemoveRange(0, index);
                }
                else
                {
                    // KFreon: Get the amount the image needs to be scaled. Find largest dimension and get it's scale.
                    double scale = maxDimension * 1d / (width > height ? width : height);

                    // KFreon: No mip. Resize.
                    newMips[0] = Resize(newMips[0], scale);
                }
            }

            // KFreon: Ensure we have a power of two for dimensions FOR DDS ONLY
            TestDDSMipSize(newMips, destFormatDetails, width, height, out double fixXScale, out double fixYScale, mipChoice);

            if (fixXScale != 0 || fixYScale != 0 || mipChoice == MipHandling.KeepTopOnly)
            {
                DestroyMipMaps(newMips, mipToSave);
            }

            if ((fixXScale != 0 || fixXScale != 0) && destFormatDetails.IsMippable && mipChoice != MipHandling.KeepTopOnly)
            {
                DDSGeneral.BuildMipMaps(newMips);
            }


            byte[] destination = null;
            if (destFormatDetails.IsDDS)
            {
                destination = DDSGeneral.Save(newMips, destFormatDetails, alphaSetting);
            }
            else
            {
                // KFreon: Try saving with built in codecs
                var mip = newMips[0];


                // Fix formatting
                byte[] newPixels = new byte[mip.Width * mip.Height * 4];
                for (int i = 0, j = 0; i < newPixels.Length; i++, j += mip.LoadedFormatDetails.ComponentSize)
                {
                    newPixels[i] = mip.LoadedFormatDetails.ReadByte(mip.Pixels, j);
                }

                destination = WIC_Codecs.SaveWithCodecs(newPixels, destFormatDetails.Format, mip.Width, mip.Height, alphaSetting);
            }

            return(destination);
        }
Example #2
0
        /// <summary>
        /// Save mipmaps as given format to stream.
        /// </summary>
        /// <param name="MipMaps">List of Mips to save.</param>
        /// <param name="format">Desired format.</param>
        /// <param name="destination">Stream to save to.</param>
        /// <param name="mipChoice">Determines how to handle mipmaps.</param>
        /// <param name="maxDimension">Maximum value for either image dimension.</param>
        /// <param name="mergeAlpha">True = alpha flattened down, directly affecting RGB.</param>
        /// <param name="mipToSave">0 based index on which mipmap to make top of saved image.</param>
        /// <returns>True on success.</returns>
        internal static bool Save(List <MipMap> MipMaps, ImageEngineFormat format, Stream destination, MipHandling mipChoice, bool mergeAlpha, int maxDimension = 0, int mipToSave = 0)
        {
            Format        temp    = new Format(format);
            List <MipMap> newMips = new List <MipMap>(MipMaps);

            if ((temp.IsMippable && mipChoice == MipHandling.GenerateNew) || (temp.IsMippable && newMips.Count == 1 && mipChoice == MipHandling.Default))
            {
                DDSGeneral.BuildMipMaps(newMips, mergeAlpha);
            }

            // KFreon: Resize if asked
            if (maxDimension != 0 && maxDimension < newMips[0].Width && maxDimension < newMips[0].Height)
            {
                if (!UsefulThings.General.IsPowerOfTwo(maxDimension))
                {
                    throw new ArgumentException($"{nameof(maxDimension)} must be a power of 2. Got {nameof(maxDimension)} = {maxDimension}");
                }


                // KFreon: Check if there's a mipmap suitable, removes all larger mipmaps
                var validMipmap = newMips.Where(img => (img.Width == maxDimension && img.Height <= maxDimension) || (img.Height == maxDimension && img.Width <= maxDimension));  // Check if a mip dimension is maxDimension and that the other dimension is equal or smaller
                if (validMipmap?.Count() != 0)
                {
                    int index = newMips.IndexOf(validMipmap.First());
                    newMips.RemoveRange(0, index);
                }
                else
                {
                    // KFreon: Get the amount the image needs to be scaled. Find largest dimension and get it's scale.
                    double scale = maxDimension * 1f / (newMips[0].Width > newMips[0].Height ? newMips[0].Width: newMips[0].Height);

                    // KFreon: No mip. Resize.
                    newMips[0] = Resize(newMips[0], scale, mergeAlpha);
                }
            }

            // KFreon: Ensure we have a power of two for dimensions
            double fixScale = 0;

            if (!UsefulThings.General.IsPowerOfTwo(newMips[0].Width) || !UsefulThings.General.IsPowerOfTwo(newMips[0].Height))
            {
                int newWidth = UsefulThings.General.RoundToNearestPowerOfTwo(newMips[0].Width);
                int newHeigh = UsefulThings.General.RoundToNearestPowerOfTwo(newMips[0].Height);

                // KFreon: Assuming same scale in both dimensions...
                fixScale = 1.0 * newWidth / newMips[0].Width;

                newMips[0] = Resize(newMips[0], fixScale, mergeAlpha);
            }


            if (fixScale != 0 || mipChoice == MipHandling.KeepTopOnly)
            {
                DestroyMipMaps(newMips, mipToSave);
            }

            if (fixScale != 0 && temp.IsMippable && mipChoice != MipHandling.KeepTopOnly)
            {
                DDSGeneral.BuildMipMaps(newMips, mergeAlpha);
            }


            bool result = false;

            if (temp.SurfaceFormat.ToString().Contains("DDS"))
            {
                result = DDSGeneral.Save(newMips, destination, temp);
            }
            else
            {
                // KFreon: Try saving with built in codecs
                var mip = newMips[0];
                if (WindowsWICCodecsAvailable)
                {
                    result = WIC_Codecs.SaveWithCodecs(mip.BaseImage, destination, format);
                }
            }

            if (mipChoice != MipHandling.KeepTopOnly && temp.IsMippable)
            {
                // KFreon: Necessary. Must be how I handle the lowest mip levels. i.e. WRONGLY :(
                // Figure out how big the file should be and make it that size

                int size   = 0;
                int width  = newMips[0].Width;
                int height = newMips[0].Height;

                int divisor = 1;
                if (temp.IsBlockCompressed)
                {
                    divisor = 4;
                }

                while (width >= 1 && height >= 1)
                {
                    int tempWidth  = width;
                    int tempHeight = height;

                    if (temp.IsBlockCompressed)
                    {
                        if (tempWidth < 4)
                        {
                            tempWidth = 4;
                        }
                        if (tempHeight < 4)
                        {
                            tempHeight = 4;
                        }
                    }


                    size   += tempWidth / divisor * tempHeight / divisor * temp.BlockSize;
                    width  /= 2;
                    height /= 2;
                }

                if (size > destination.Length - 128)
                {
                    byte[] blanks = new byte[size - (destination.Length - 128)];
                    destination.Write(blanks, 0, blanks.Length);
                }
            }

            return(result);
        }
        byte[] AttemptSaveUsingOriginalData(ImageFormats.ImageEngineFormatDetails destFormatDetails, MipHandling GenerateMips, int desiredMaxDimension, int mipToSave, AlphaSettings alphaSetting)
        {
            int        start      = 0;
            int        destStart  = 0;
            int        length     = OriginalData.Length;
            int        newWidth   = Width;
            int        newHeight  = Height;
            DDS_Header tempHeader = null;

            byte[] data             = null;
            byte[] tempOriginalData = OriginalData;

            if (destFormatDetails.IsDDS)
            {
                destStart = destFormatDetails.HeaderSize;
                start     = destStart;

                int mipCount = 0;

                if (mipToSave != 0)
                {
                    mipCount  = 1;
                    newWidth  = MipMaps[mipToSave].Width;
                    newHeight = MipMaps[mipToSave].Height;

                    start  = ImageFormats.GetCompressedSize(mipToSave, destFormatDetails, Width, Height);
                    length = ImageFormats.GetCompressedSize(1, destFormatDetails, newWidth, newHeight);
                }
                else if (desiredMaxDimension != 0 && desiredMaxDimension < Width && desiredMaxDimension < Height)
                {
                    int index = MipMaps.FindIndex(t => t.Width < desiredMaxDimension && t.Height < desiredMaxDimension);

                    // If none found, do a proper save and see what happens.
                    if (index == -1)
                    {
                        return(ImageEngine.Save(MipMaps, destFormatDetails, GenerateMips, alphaSetting, desiredMaxDimension, mipToSave));
                    }

                    mipCount -= index;
                    newWidth  = MipMaps[index].Width;
                    newHeight = MipMaps[index].Height;

                    start  = ImageFormats.GetCompressedSize(index, destFormatDetails, Width, Height);
                    length = ImageFormats.GetCompressedSize(mipCount, destFormatDetails, newWidth, newHeight);
                }
                else
                {
                    if (alphaSetting == AlphaSettings.RemoveAlphaChannel)
                    {
                        // Can't edit alpha directly in premultiplied formats. Not easily anyway.
                        if (destFormatDetails.IsPremultipliedFormat)
                        {
                            return(ImageEngine.Save(MipMaps, destFormatDetails, GenerateMips, alphaSetting, desiredMaxDimension, mipToSave));
                        }


                        // DDS Formats only
                        switch (destFormatDetails.Format)
                        {
                        // Excluded cos they have no true alpha
                        case ImageEngineFormat.DDS_A8:
                        case ImageEngineFormat.DDS_A8L8:
                        case ImageEngineFormat.DDS_ATI1:
                        case ImageEngineFormat.DDS_ATI2_3Dc:
                        case ImageEngineFormat.DDS_V8U8:
                        case ImageEngineFormat.DDS_G16_R16:
                        case ImageEngineFormat.DDS_G8_L8:
                        case ImageEngineFormat.DDS_R5G6B5:
                        case ImageEngineFormat.DDS_RGB_8:
                        case ImageEngineFormat.DDS_DXT1:
                            break;

                        // Exluded cos they're alpha isn't easily edited
                        case ImageEngineFormat.DDS_DXT2:
                        case ImageEngineFormat.DDS_DXT4:
                            break;

                        // Excluded cos they're currently unsupported
                        case ImageEngineFormat.DDS_CUSTOM:
                        case ImageEngineFormat.DDS_DX10:
                        case ImageEngineFormat.DDS_ARGB_4:
                            break;

                        case ImageEngineFormat.DDS_ABGR_8:
                        case ImageEngineFormat.DDS_ARGB_32F:
                        case ImageEngineFormat.DDS_ARGB_8:
                        case ImageEngineFormat.DDS_DXT3:
                        case ImageEngineFormat.DDS_DXT5:
                            tempOriginalData = new byte[OriginalData.Length];
                            Array.Copy(OriginalData, tempOriginalData, OriginalData.Length);

                            // Edit alpha values
                            int    alphaStart = 128;
                            int    alphaJump  = 0;
                            byte[] alphaBlock = null;
                            if (destFormatDetails.IsBlockCompressed)
                            {
                                alphaJump  = 16;
                                alphaBlock = new byte[8];
                                for (int i = 0; i < 8; i++)
                                {
                                    alphaBlock[i] = 255;
                                }
                            }
                            else
                            {
                                alphaJump  = destFormatDetails.ComponentSize * 4;
                                alphaBlock = new byte[destFormatDetails.ComponentSize];

                                switch (destFormatDetails.ComponentSize)
                                {
                                case 1:
                                    alphaBlock[0] = 255;
                                    break;

                                case 2:
                                    alphaBlock = BitConverter.GetBytes(ushort.MaxValue);
                                    break;

                                case 4:
                                    alphaBlock = BitConverter.GetBytes(1f);
                                    break;
                                }
                            }

                            for (int i = alphaStart; i < OriginalData.Length; i += alphaJump)
                            {
                                Array.Copy(alphaBlock, 0, tempOriginalData, i, alphaBlock.Length);
                            }

                            break;
                        }
                    }


                    switch (GenerateMips)
                    {
                    case MipHandling.KeepExisting:
                        mipCount = NumMipMaps;
                        break;

                    case MipHandling.Default:
                        if (NumMipMaps > 1)
                        {
                            mipCount = NumMipMaps;
                        }
                        else
                        {
                            goto case MipHandling.GenerateNew;      // Eww goto...
                        }
                        break;

                    case MipHandling.GenerateNew:
                        ImageEngine.DestroyMipMaps(MipMaps);
                        ImageEngine.TestDDSMipSize(MipMaps, destFormatDetails, Width, Height, out double fixXScale, out double fixYScale, GenerateMips);

                        // Wrong sizing, so can't use original data anyway.
                        if (fixXScale != 0 || fixYScale != 0)
                        {
                            return(ImageEngine.Save(MipMaps, destFormatDetails, GenerateMips, alphaSetting, desiredMaxDimension, mipToSave));
                        }


                        mipCount = DDSGeneral.BuildMipMaps(MipMaps);

                        // Compress mipmaps excl top
                        byte[] formattedMips = DDSGeneral.Save(MipMaps.GetRange(1, MipMaps.Count - 1), destFormatDetails, alphaSetting);
                        if (formattedMips == null)
                        {
                            return(null);
                        }

                        // Get top mip size and create destination array
                        length = ImageFormats.GetCompressedSize(0, destFormatDetails, newWidth, newHeight);     // Should be the length of the top mipmap.
                        data   = new byte[formattedMips.Length + length];

                        // Copy smaller mips to destination
                        Array.Copy(formattedMips, destFormatDetails.HeaderSize, data, length, formattedMips.Length - destFormatDetails.HeaderSize);
                        break;

                    case MipHandling.KeepTopOnly:
                        mipCount = 1;
                        length   = ImageFormats.GetCompressedSize(1, destFormatDetails, newWidth, newHeight);
                        break;
                    }
                }

                // Header
                tempHeader = new DDS_Header(mipCount, newHeight, newWidth, destFormatDetails.Format, destFormatDetails.DX10Format);
            }

            // Use existing array, otherwise create one.
            data = data ?? new byte[length];
            Array.Copy(tempOriginalData, start, data, destStart, length - destStart);

            // Write header if existing (DDS Only)
            if (tempHeader != null)
            {
                tempHeader.WriteToArray(data, 0);
            }

            return(data);
        }