/// <summary> /// Gets pixels as a BGRA32 array regardless of their original format (float, short) /// </summary> /// <param name="width">Width of image.</param> /// <param name="height">Height of image.</param> /// <param name="pixels">Original pixels.</param> /// <param name="formatDetails">Details about format pixels array is currently in.</param> /// <returns>BGRA32 pixel array.</returns> public static byte[] GetPixelsAsBGRA32(int width, int height, byte[] pixels, ImageFormats.ImageEngineFormatDetails formatDetails) { if (formatDetails.ComponentSize == 1) { return(pixels); } byte[] tempPixels = new byte[width * height * 4]; Action <int> action = new Action <int>(ind => tempPixels[ind] = formatDetails.ReadByte(pixels, ind * formatDetails.ComponentSize)); if (EnableThreading) { Parallel.For(0, tempPixels.Length, new ParallelOptions { MaxDegreeOfParallelism = NumThreads }, ind => action(ind)); } else { for (int i = 0; i < tempPixels.Length; i++) { action(i); } } return(tempPixels); }
/// <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)); } }
/// <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); }
void Load(Stream stream, int maxDimension) { CompressedSize = (int)stream.Length; Header = ImageEngine.LoadHeader(stream); // DX10 var DX10Format = DDS_Header.DXGI_FORMAT.DXGI_FORMAT_UNKNOWN; if (Header is Headers.DDS_Header) { DX10Format = ((Headers.DDS_Header)Header).DX10_DXGI_AdditionalHeader.dxgiFormat; } ImageEngineFormat tempFormat = Header.Format; if (DX10Format == DDS_Header.DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT) // Trickses to get around the DX10 float header deal - Apparently float formats should be specified with the DX10 header... { tempFormat = ImageEngineFormat.DDS_ARGB_32F; var tempPF = ((DDS_Header)Header).ddspf; tempPF.dwRBitMask = 1; tempPF.dwGBitMask = 2; tempPF.dwBBitMask = 3; tempPF.dwABitMask = 4; ((DDS_Header)Header).ddspf = tempPF; } FormatDetails = new ImageFormats.ImageEngineFormatDetails(tempFormat, DX10Format); MipMaps = ImageEngine.LoadImage(stream, Header, maxDimension, 0, FormatDetails); // Read original data OriginalData = new byte[CompressedSize]; stream.Position = 0; stream.Read(OriginalData, 0, CompressedSize); }
/// <summary> /// Creates a Mipmap object from a WPF image. /// </summary> public MipMap(byte[] pixels, int width, int height, ImageFormats.ImageEngineFormatDetails details) { Pixels = pixels; Width = width; Height = height; LoadedFormatDetails = details; UncompressedSize = ImageFormats.GetUncompressedSize(width, height, details.MaxNumberOfChannels, false); }
void Load(Stream stream, int maxDimension) { CompressedSize = (int)stream.Length; Header = ImageEngine.LoadHeader(stream); // DX10 var DX10Format = DDS_Header.DXGI_FORMAT.DXGI_FORMAT_UNKNOWN; if (Header is Headers.DDS_Header) { DX10Format = ((Headers.DDS_Header)Header).DX10_DXGI_AdditionalHeader.dxgiFormat; } FormatDetails = new ImageFormats.ImageEngineFormatDetails(Header.Format, DX10Format); MipMaps = ImageEngine.LoadImage(stream, Header, maxDimension, 0, FormatDetails); // Read original data OriginalData = new byte[CompressedSize]; stream.Position = 0; stream.Read(OriginalData, 0, CompressedSize); }
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); } }
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); }
/// <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); }
/// <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); }
internal static List <MipMap> LoadImage(Stream imageStream, AbstractHeader header, int maxDimension, double scale, ImageFormats.ImageEngineFormatDetails formatDetails) { imageStream.Seek(0, SeekOrigin.Begin); List <MipMap> MipMaps = null; int decodeWidth = header.Width > header.Height ? maxDimension : 0; int decodeHeight = header.Width < header.Height ? maxDimension : 0; switch (header.Format) { case ImageEngineFormat.DDS_DXT1: case ImageEngineFormat.DDS_DXT2: case ImageEngineFormat.DDS_DXT3: case ImageEngineFormat.DDS_DXT4: case ImageEngineFormat.DDS_DXT5: MipMaps = WIC_Codecs.LoadWithCodecs(imageStream, decodeWidth, decodeHeight, scale, true, formatDetails); if (MipMaps == null) { // Windows codecs unavailable/failed. Load with mine. MipMaps = DDSGeneral.LoadDDS((MemoryStream)imageStream, (DDS_Header)header, maxDimension, formatDetails); } break; case ImageEngineFormat.DDS_G8_L8: case ImageEngineFormat.DDS_ARGB_4: case ImageEngineFormat.DDS_RGB_8: case ImageEngineFormat.DDS_V8U8: case ImageEngineFormat.DDS_A8L8: case ImageEngineFormat.DDS_ARGB_8: case ImageEngineFormat.DDS_ARGB_32F: case ImageEngineFormat.DDS_ABGR_8: case ImageEngineFormat.DDS_G16_R16: case ImageEngineFormat.DDS_R5G6B5: case ImageEngineFormat.DDS_ATI1: case ImageEngineFormat.DDS_ATI2_3Dc: case ImageEngineFormat.DDS_CUSTOM: case ImageEngineFormat.DDS_DX10: MipMaps = DDSGeneral.LoadDDS((MemoryStream)imageStream, (DDS_Header)header, maxDimension, formatDetails); break; case ImageEngineFormat.GIF: case ImageEngineFormat.JPG: case ImageEngineFormat.PNG: case ImageEngineFormat.BMP: case ImageEngineFormat.TIF: MipMaps = WIC_Codecs.LoadWithCodecs(imageStream, decodeWidth, decodeHeight, scale, false, formatDetails); break; case ImageEngineFormat.TGA: using (var tga = new TargaImage(imageStream, ((TGA_Header)header).header)) MipMaps = new List <MipMap>() { new MipMap(tga.ImageData, tga.Header.Width, tga.Header.Height, formatDetails) }; break; default: throw new FormatException($"Format unknown: {header.Format}."); } return(MipMaps); }
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); }
internal static MipMap Resize(BitmapSource baseBMP, double xScale, double yScale, int width, int height, ImageFormats.ImageEngineFormatDetails formatDetails) { int origWidth = width; int origHeight = height; int origStride = origWidth * 4; int newWidth = (int)(origWidth * xScale); int newHeight = (int)(origHeight * yScale); int newStride = newWidth * 4; var bmp = UsefulThings.WPF.Images.CreateWPFBitmap(baseBMP, newWidth, newHeight); bmp.Freeze(); return(new MipMap(bmp.GetPixelsAsBGRA32(), newWidth, newHeight, formatDetails)); }
/// <summary> /// Loads useful information from image stream using Windows 8.1+ codecs. /// </summary> /// <param name="stream">Stream containing entire file. NOT just pixels.</param> /// <param name="decodeWidth">Width to decode as. Aspect ratio unchanged if decodeHeight = 0.</param> /// <param name="decodeHeight">Height to decode as. Aspect ratio unchanged if decodeWidth = 0.</param> /// <param name="isDDS">True = image is a DDS.</param> /// <param name="scale">DOMINANT. DecodeWidth and DecodeHeight ignored if this is > 0. Amount to scale by. Range 0-1.</param> /// <param name="formatDetails">Details about the format being loaded.</param> /// <returns>BGRA Pixel Data as stream.</returns> internal static List <MipMap> LoadWithCodecs(Stream stream, int decodeWidth, int decodeHeight, double scale, bool isDDS, ImageFormats.ImageEngineFormatDetails formatDetails) { if (isDDS && !ImageEngine.WindowsWICCodecsAvailable) { return(null); } bool alternateDecodeDimensions = decodeHeight != 0 || decodeWidth != 0 || scale != 0; int alternateWidth = decodeWidth; int alternateHeight = decodeHeight; List <MipMap> mipmaps = new List <MipMap>(); if (isDDS) { // KFreon: Attempt to load any mipmaps stream.Seek(0, SeekOrigin.Begin); var decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.OnDemand); // Setup alternateDimensions if required if (scale != 0) { alternateHeight = (int)(decoder.Frames[0].Height * scale); alternateWidth = (int)(decoder.Frames[0].Width * scale); } foreach (var mipmap in decoder.Frames) { // KFreon: Skip mipmaps that are too big if asked to load a smaller image if (alternateDecodeDimensions) { if ((alternateWidth != 0 && mipmap.Width > alternateWidth) || (alternateHeight != 0 && mipmap.Height > alternateHeight)) { continue; } } mipmaps.Add(new MipMap(mipmap.GetPixelsAsBGRA32(), mipmap.PixelWidth, mipmap.PixelHeight, formatDetails)); } if (mipmaps.Count == 0) { // KFreon: Image has no mips, so resize largest var frame = decoder.Frames[0]; var mip = new MipMap(frame.GetPixelsAsBGRA32(), frame.PixelWidth, frame.PixelHeight, formatDetails); // Calculate scale if required if (scale == 0) { double xScale = alternateWidth * 1.0 / frame.PixelWidth; double yScale = alternateHeight * 1.0 / frame.PixelHeight; scale = xScale == 0 ? yScale : xScale; } mip = ImageEngine.Resize(mip, scale); mipmaps.Add(mip); } } else { // KFreon: No Mipmaps BitmapImage bmp = AttemptUsingWindowsCodecs(stream, alternateWidth, alternateHeight); if (bmp == null) { return(null); } bmp.Freeze(); mipmaps.Add(new MipMap(bmp.GetPixelsAsBGRA32(), bmp.PixelWidth, bmp.PixelHeight, formatDetails)); } return(mipmaps); }
/// <summary> /// Loads useful information from an image file. /// </summary> /// <param name="imageFile">Path to image file.</param> /// <param name="decodeWidth">Width to decode to. Aspect unchanged if decodeHeight = 0.</param> /// <param name="decodeHeight">Height to decode to. Aspect unchanged if decodeWidth = 0.</param> /// <param name="scale">DOMINANT. decodeWidth and decodeHeight ignored if this is > 0. Amount to scale by. Range 0-1.</param> /// <param name="formatDetails">Details about the format being loaded.</param> /// <param name="isDDS">True = Image is a DDS.</param> /// <returns>BGRA Pixel Data as stream.</returns> internal static List <MipMap> LoadWithCodecs(string imageFile, int decodeWidth, int decodeHeight, double scale, bool isDDS, ImageFormats.ImageEngineFormatDetails formatDetails) { if (isDDS && !ImageEngine.WindowsWICCodecsAvailable) { return(null); } using (FileStream fs = new FileStream(imageFile, FileMode.Open, FileAccess.Read, FileShare.Read)) return(LoadWithCodecs(fs, decodeWidth, decodeHeight, scale, isDDS, formatDetails)); }