/// <summary>
        /// Saves fully formatted image in specified format to byte array.
        /// </summary>
        /// <param name="destFormatDetails">Details about destination format.</param>
        /// <param name="GenerateMips">Determines how mipmaps are handled during saving.</param>
        /// <param name="desiredMaxDimension">Maximum size for saved image. Resizes if required, but uses mipmaps if available.</param>
        /// <param name="mipToSave">Index of mipmap to save directly.</param>
        /// <param name="removeAlpha">True = Alpha removed. False = Uses threshold value and alpha values to mask RGB FOR DXT1, otherwise completely removed.</param>
        /// <returns></returns>
        public byte[] Save(ImageFormats.ImageEngineFormatDetails destFormatDetails, MipHandling GenerateMips, int desiredMaxDimension = 0, int mipToSave = 0, bool removeAlpha = true)
        {
            if (destFormatDetails.Format == ImageEngineFormat.Unknown)
            {
                throw new InvalidOperationException("Save format cannot be 'Unknown'");
            }

            AlphaSettings alphaSetting = AlphaSettings.KeepAlpha;

            if (removeAlpha)
            {
                alphaSetting = AlphaSettings.RemoveAlphaChannel;
            }
            else if (destFormatDetails.Format == ImageEngineFormat.DDS_DXT2 || destFormatDetails.Format == ImageEngineFormat.DDS_DXT4)
            {
                alphaSetting = AlphaSettings.Premultiply;
            }

            // If same format and stuff, can just return original data, or chunks of it.
            if (destFormatDetails.Format == Format)
            {
                return(AttemptSaveUsingOriginalData(destFormatDetails, GenerateMips, desiredMaxDimension, mipToSave, alphaSetting));
            }
            else
            {
                return(ImageEngine.Save(MipMaps, destFormatDetails, GenerateMips, alphaSetting, desiredMaxDimension, mipToSave));
            }
        }
Exemple #2
0
 /// <summary>
 /// Saves image to byte[].
 /// </summary>
 /// <param name="MipMaps">Mipmaps to save.</param>
 /// <param name="format">Format to save image as.</param>
 /// <param name="generateMips">Determines how to handle mipmaps.</param>
 /// <param name="desiredMaxDimension">Maximum dimension to allow. Resizes if required.</param>
 /// <param name="mipToSave">Mipmap to save. If > 0, all other mipmaps removed, and this mipmap saved.</param>
 /// <param name="mergeAlpha">True = Flattens alpha into RGB.</param>
 /// <returns>Byte[] containing fully formatted image.</returns>
 internal static byte[] Save(List <MipMap> MipMaps, ImageEngineFormat format, MipHandling generateMips, int desiredMaxDimension, int mipToSave, bool mergeAlpha)
 {
     using (MemoryStream ms = new MemoryStream())
     {
         Save(MipMaps, format, ms, generateMips, mergeAlpha, desiredMaxDimension, mipToSave);
         return(ms.ToArray());
     }
 }
        /// <summary>
        /// Performs a bulk conversion of a bunch of images given conversion parameters.
        /// </summary>
        /// <param name="files">List of supported files to be converted.</param>
        /// <param name="saveFolder">Destination folder of all textures. Can be null if <paramref name="useSourceAsDestination"/> is set.</param>
        /// <param name="saveMipType">Determines how to handle mipmaps for converted images.</param>
        /// <param name="useSourceAsDestination">True = Converted images are saved next to the originals.</param>
        /// <param name="removeAlpha">True = Alpha is removed from converted images.</param>
        /// <param name="destFormatDetails">Details about destination format.</param>
        /// <param name="progressReporter">Progress reporting callback.</param>
        /// <param name="useSourceFormat">No format conversion is performed if possible.</param>
        /// <returns>Errors</returns>
        public static async Task <ConcurrentBag <string> > BulkConvert(IEnumerable <string> files, ImageFormats.ImageEngineFormatDetails destFormatDetails, bool useSourceFormat, string saveFolder,
                                                                       MipHandling saveMipType = MipHandling.Default, bool useSourceAsDestination = false, bool removeAlpha = false, IProgress <int> progressReporter = null)
        {
            ConcurrentBag <string> Failures = new ConcurrentBag <string>();


            // Test if can parallelise uncompressed saving
            // Below says: Only formats that don't support mips or do but aren't block compressed - can be parallised. Also don't parallelise if using source formats.
            bool supportsParallel = false;

            if (!useSourceFormat)
            {
                supportsParallel  = !useSourceFormat && !destFormatDetails.IsMippable;
                supportsParallel |= !supportsParallel && !destFormatDetails.IsBlockCompressed;
            }


            if (EnableThreading && supportsParallel)
            {
                Failures = await DoBulkParallel(files, destFormatDetails, saveFolder, saveMipType, useSourceAsDestination, removeAlpha, progressReporter);
            }
            else
            {
                foreach (var file in files)
                {
                    using (ImageEngineImage img = new ImageEngineImage(file))
                    {
                        // Using source format can only come into this leg of the operation.
                        var saveFormatDetails = useSourceFormat ? img.FormatDetails : destFormatDetails;

                        string filename = useSourceFormat ? Path.GetFileName(file) : Path.GetFileNameWithoutExtension(file) + "." + destFormatDetails.Extension;  // This can stay destFormatDetails instead of saveFormatDetails as it shouldn't be able to get here if destFormatDetails not set.
                        string path     = Path.Combine(useSourceAsDestination ? Path.GetDirectoryName(file) : saveFolder, filename);

                        path = UsefulThings.General.FindValidNewFileName(path);

                        try
                        {
                            await img.Save(path, saveFormatDetails, saveMipType, removeAlpha : removeAlpha);
                        }
                        catch (Exception e)
                        {
                            Failures.Add(path + "  Reason: " + e.ToString());
                        }
                    }

                    progressReporter?.Report(1);   // Value not relevent.
                }
            }


            return(Failures);
        }
Exemple #4
0
 /// <summary>
 /// Saves image in specified format to file. If file exists, it will be overwritten.
 /// </summary>
 /// <param name="destination">File to save to.</param>
 /// <param name="format">Desired image format.</param>
 /// <param name="GenerateMips">Determines how mipmaps are handled during saving.</param>
 /// <param name="desiredMaxDimension">Maximum size for saved image. Resizes if required, but uses mipmaps if available.</param>
 /// <param name="mergeAlpha">DXT1 only. True = Uses threshold value and alpha values to mask RGB.</param>
 /// <param name="mipToSave">Index of mipmap to save as single image.</param>
 /// <returns>True if success.</returns>
 public bool Save(string destination, ImageEngineFormat format, MipHandling GenerateMips, int desiredMaxDimension = 0, int mipToSave = 0, bool mergeAlpha = false)
 {
     using (FileStream fs = new FileStream(destination, FileMode.Create))
         return(Save(fs, format, GenerateMips, desiredMaxDimension, mipToSave, mergeAlpha));
 }
Exemple #5
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);
        }
        static async Task <ConcurrentBag <string> > DoBulkParallel(IEnumerable <string> files, ImageFormats.ImageEngineFormatDetails destFormatDetails, string saveFolder,
                                                                   MipHandling saveMipType = MipHandling.Default, bool useSourceAsDestination = false, bool removeAlpha = false, IProgress <int> progressReporter = null)
        {
            ConcurrentBag <string> failures = new ConcurrentBag <string>();

            BufferBlock <string> fileNameStore = new BufferBlock <string>();
            int maxParallelism = ImageEngine.NumThreads == 1 ? 1 :
                                 (ImageEngine.NumThreads == -1 ? Environment.ProcessorCount : ImageEngine.NumThreads);


            // Define block to perform each conversion
            var encoder = new TransformBlock <string, Tuple <byte[], string> >(file =>
            {
                byte[] data = null;

                string filename = Path.GetFileNameWithoutExtension(file) + "." + destFormatDetails.Extension;
                string path     = Path.Combine(useSourceAsDestination ? Path.GetDirectoryName(file) : saveFolder, filename);
                path            = UsefulThings.General.FindValidNewFileName(path);


                using (ImageEngineImage img = new ImageEngineImage(file))
                {
                    try
                    {
                        data = img.Save(destFormatDetails, saveMipType, removeAlpha: removeAlpha);
                    }
                    catch (Exception e)
                    {
                        failures.Add(path + "  Reason: " + e.ToString());
                    }
                }

                progressReporter.Report(1);  // Value not relevent.
                return(new Tuple <byte[], string>(data, path));
            }, new ExecutionDataflowBlockOptions {
                MaxDegreeOfParallelism = maxParallelism, BoundedCapacity = maxParallelism
            });

            // Define block to write converted data to disk
            var diskWriter = new ActionBlock <Tuple <byte[], string> >(tuple =>
            {
                string path = UsefulThings.General.FindValidNewFileName(tuple.Item2);

                try
                {
                    File.WriteAllBytes(path, tuple.Item1);
                }
                catch (Exception e)
                {
                    failures.Add(path + "  Reason: " + e.ToString());
                }
            }, new ExecutionDataflowBlockOptions {
                MaxDegreeOfParallelism = 2, BoundedCapacity = maxParallelism
            });                                                                                                      // Limit to 2 disk write operations at a time, but allow many to be stored in it's buffer.


            // Link blocks together
            fileNameStore.LinkTo(encoder, new DataflowLinkOptions {
                PropagateCompletion = true
            });
            encoder.LinkTo(diskWriter, new DataflowLinkOptions {
                PropagateCompletion = true
            });

            // Begin production
            new Action(async() =>
            {
                foreach (var file in files)
                {
                    await fileNameStore.SendAsync(file);
                }

                fileNameStore.Complete();
            }).Invoke();

            await diskWriter.Completion;

            return(failures);
        }
        internal static void TestDDSMipSize(List <MipMap> newMips, ImageFormats.ImageEngineFormatDetails destFormatDetails, int width, int height, out double fixXScale, out double fixYScale, MipHandling mipChoice)
        {
            fixXScale = 0;
            fixYScale = 0;
            if (destFormatDetails.IsBlockCompressed && (!UsefulThings.General.IsPowerOfTwo(width) || !UsefulThings.General.IsPowerOfTwo(height)))
            {
                // If only keeping top mip, and that mip is divisible by 4, it's ok.
                if ((mipChoice == MipHandling.KeepTopOnly || mipChoice == MipHandling.KeepExisting) &&
                    DDSGeneral.CheckSize_DXT(width, height))
                {
                    return;
                }


                double newWidth  = 0;
                double newHeight = 0;

                // Takes into account aspect ratio (a little bit)
                double aspect = width / height;
                if (aspect > 1)
                {
                    newWidth = UsefulThings.General.RoundToNearestPowerOfTwo(width);
                    var tempScale = newWidth / width;
                    newHeight = UsefulThings.General.RoundToNearestPowerOfTwo((int)(height * tempScale));
                }
                else
                {
                    newHeight = UsefulThings.General.RoundToNearestPowerOfTwo(height);
                    var tempScale = newHeight / height;
                    newWidth = UsefulThings.General.RoundToNearestPowerOfTwo((int)(width * tempScale));
                }


                // Little extra bit to allow integer cast from Double with the correct answer. Occasionally dimensions * scale would be 511.99999999998 instead of 512, so adding a little allows the integer cast to return correct value.
                fixXScale  = 1d * newWidth / width + 0.001;
                fixYScale  = 1d * newHeight / height + 0.001;
                newMips[0] = Resize(newMips[0], fixXScale, fixYScale);
            }
        }
 /// <summary>
 /// Saves image in specified format to stream.
 /// Stream position not reset before or after.
 /// </summary>
 /// <param name="destination">Stream to write to at current position.</param>
 /// <param name="format">Format to save to.</param>
 /// <param name="GenerateMips">Determines how mipmaps are handled during saving.</param>
 /// <param name="desiredMaxDimension">Maximum dimension of saved image. Keeps aspect.</param>
 /// <param name="mipToSave">Specifies a mipmap to save within the whole.</param>
 /// <param name="removeAlpha">True = removes alpha. False = Uses threshold value and alpha values to mask RGB FOR DXT1 ONLY, otherwise removes completely.</param>
 /// <param name="customMasks">Custom user defined masks for DDS colours.</param>
 public void Save(Stream destination, ImageEngineFormat format, MipHandling GenerateMips, int desiredMaxDimension = 0, int mipToSave = 0, bool removeAlpha = true, List<uint> customMasks = null)
 {
     var data = Save(format, GenerateMips, desiredMaxDimension, mipToSave, removeAlpha, customMasks);
     destination.Write(data, 0, data.Length);
 }
        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);
        }
        /// <summary>
        /// Saves image in specified format to stream.
        /// Stream position not reset before or after.
        /// </summary>
        /// <param name="destination">Stream to write to at current position.</param>
        /// <param name="destFormatDetails">Details of destination format</param>
        /// <param name="GenerateMips">Determines how mipmaps are handled during saving.</param>
        /// <param name="desiredMaxDimension">Maximum dimension of saved image. Keeps aspect.</param>
        /// <param name="mipToSave">Specifies a mipmap to save within the whole.</param>
        /// <param name="removeAlpha">True = removes alpha. False = Uses threshold value and alpha values to mask RGB FOR DXT1 ONLY, otherwise removes completely.</param>
        public void Save(Stream destination, ImageFormats.ImageEngineFormatDetails destFormatDetails, MipHandling GenerateMips, int desiredMaxDimension = 0, int mipToSave = 0, bool removeAlpha = true)
        {
            var data = Save(destFormatDetails, GenerateMips, desiredMaxDimension, mipToSave, removeAlpha);

            destination.Write(data, 0, data.Length);
        }
        /// <summary>
        /// Saves image in specified format to file. If file exists, it will be overwritten.
        /// </summary>
        /// <param name="destination">File to save to.</param>
        /// <param name="destFormatDetails">Details of destination format.</param>
        /// <param name="GenerateMips">Determines how mipmaps are handled during saving.</param>
        /// <param name="desiredMaxDimension">Maximum size for saved image. Resizes if required, but uses mipmaps if available.</param>
        /// <param name="removeAlpha">True = Alpha removed. False = Uses threshold value and alpha values to mask RGB FOR DXT1 ONLY, otherwise removes completely.</param>
        /// <param name="mipToSave">Index of mipmap to save as single image.</param>
        public async Task Save(string destination, ImageFormats.ImageEngineFormatDetails destFormatDetails, MipHandling GenerateMips, int desiredMaxDimension = 0, int mipToSave = 0, bool removeAlpha = true)
        {
            var data = Save(destFormatDetails, GenerateMips, desiredMaxDimension, mipToSave, removeAlpha);

            using (FileStream fs = new FileStream(destination, FileMode.Create))
                await fs.WriteAsync(data, 0, data.Length);
        }
        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);
        }
        /// <summary>
        /// Saves fully formatted image in specified format to byte array.
        /// </summary>
        /// <param name="format">Format to save as.</param>
        /// <param name="GenerateMips">Determines how mipmaps are handled during saving.</param>
        /// <param name="desiredMaxDimension">Maximum size for saved image. Resizes if required, but uses mipmaps if available.</param>
        /// <param name="mipToSave">Index of mipmap to save directly.</param>
        /// <param name="removeAlpha">True = Alpha removed. False = Uses threshold value and alpha values to mask RGB FOR DXT1, otherwise completely removed.</param>
        /// <param name="customMasks">Custom user defined masks for colours.</param>
        /// <returns></returns>
        public byte[] Save(ImageEngineFormat format, MipHandling GenerateMips, int desiredMaxDimension = 0, int mipToSave = 0, bool removeAlpha = true, List<uint> customMasks = null)
        {
            if (format == ImageEngineFormat.Unknown)
                throw new InvalidOperationException("Save format cannot be 'Unknown'");

            AlphaSettings alphaSetting = AlphaSettings.KeepAlpha;
            if (removeAlpha)
                alphaSetting = AlphaSettings.RemoveAlphaChannel;
            else if (format == ImageEngineFormat.DDS_DXT2 || format == ImageEngineFormat.DDS_DXT4)
                alphaSetting = AlphaSettings.Premultiply;

            return ImageEngine.Save(MipMaps, format, GenerateMips, alphaSetting, desiredMaxDimension, mipToSave, customMasks);
        }
Exemple #14
0
 /// <summary>
 /// Saves fully formatted image in specified format to stream.
 /// </summary>
 /// <param name="destination">Stream to save to.</param>
 /// <param name="format">Format to save as.</param>
 /// <param name="GenerateMips">Determines how mipmaps are handled during saving.</param>
 /// <param name="desiredMaxDimension">Maximum size for saved image. Resizes if required, but uses mipmaps if available.</param>
 /// <param name="mergeAlpha">ONLY valid when desiredMaxDimension != 0. True = alpha flattened, directly affecting RGB.</param>
 /// <param name="mipToSave">Selects a certain mip to save. 0 based.</param>
 /// <returns>True if success</returns>
 public bool Save(Stream destination, ImageEngineFormat format, MipHandling GenerateMips, int desiredMaxDimension = 0, int mipToSave = 0, bool mergeAlpha = false)
 {
     return(ImageEngine.Save(MipMaps, format, destination, GenerateMips, mergeAlpha, desiredMaxDimension, mipToSave));
 }
        /// <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);
        }
Exemple #16
0
 /// <summary>
 /// Saves fully formatted image in specified format to byte array.
 /// </summary>
 /// <param name="format">Format to save as.</param>
 /// <param name="GenerateMips">Determines how mipmaps are handled during saving.</param>
 /// <param name="desiredMaxDimension">Maximum size for saved image. Resizes if required, but uses mipmaps if available.</param>
 /// <param name="mipToSave">Index of mipmap to save directly.</param>
 /// <param name="mergeAlpha">ONLY valid when desiredMaxDimension != 0. True = alpha flattened, directly affecting RGB.</param>
 /// <returns></returns>
 public byte[] Save(ImageEngineFormat format, MipHandling GenerateMips, int desiredMaxDimension = 0, int mipToSave = 0, bool mergeAlpha = false)
 {
     return(ImageEngine.Save(MipMaps, format, GenerateMips, desiredMaxDimension, mipToSave, mergeAlpha));
 }
        /// <summary>
        /// Saves image in specified format to file. If file exists, it will be overwritten.
        /// </summary>
        /// <param name="destination">File to save to.</param>
        /// <param name="format">Desired image format.</param>
        /// <param name="GenerateMips">Determines how mipmaps are handled during saving.</param>
        /// <param name="desiredMaxDimension">Maximum size for saved image. Resizes if required, but uses mipmaps if available.</param>
        /// <param name="removeAlpha">True = Alpha removed. False = Uses threshold value and alpha values to mask RGB FOR DXT1 ONLY, otherwise removes completely.</param>
        /// <param name="mipToSave">Index of mipmap to save as single image.</param>
        /// <param name="customMasks">Custom user defined masks for colours.</param>
        public async Task Save(string destination, ImageEngineFormat format, MipHandling GenerateMips, int desiredMaxDimension = 0, int mipToSave = 0, bool removeAlpha = true, List<uint> customMasks = null)
        {
            var data = Save(format, GenerateMips, desiredMaxDimension, mipToSave, removeAlpha, customMasks);

            using (FileStream fs = new FileStream(destination, FileMode.Create))
                await fs.WriteAsync(data, 0, data.Length);
        }