internal static List <MipMap> LoadImage(byte[] rawDDSData, ImageEngineFormat surfaceFormat, int width, int height, out DDSGeneral.DDS_HEADER header) { header = DDSGeneral.Build_DDS_Header(1, height, width, surfaceFormat); List <MipMap> MipMaps = null; // Create new fully formatted DDS i.e. one with a header. MemoryStream stream = new MemoryStream(); BinaryWriter bw = new BinaryWriter(stream); DDSGeneral.Write_DDS_Header(header, bw); bw.Write(rawDDSData); switch (surfaceFormat) { case ImageEngineFormat.DDS_DXT1: case ImageEngineFormat.DDS_DXT2: case ImageEngineFormat.DDS_DXT3: case ImageEngineFormat.DDS_DXT4: case ImageEngineFormat.DDS_DXT5: if (WindowsWICCodecsAvailable) { MipMaps = WIC_Codecs.LoadWithCodecs(stream, 0, 0, true); } else { MipMaps = DDSGeneral.LoadDDS(stream, header, new Format(surfaceFormat), 0); } break; case ImageEngineFormat.DDS_ARGB: case ImageEngineFormat.DDS_A8L8: case ImageEngineFormat.DDS_RGB: case ImageEngineFormat.DDS_ATI1: case ImageEngineFormat.DDS_ATI2_3Dc: case ImageEngineFormat.DDS_G8_L8: case ImageEngineFormat.DDS_V8U8: MipMaps = DDSGeneral.LoadDDS(stream, header, new Format(surfaceFormat), 0); break; default: throw new InvalidDataException("Image format is unknown."); } bw.Dispose(); // Also disposes MemoryStream return(MipMaps); }
/// <summary> /// Constructor. Checks WIC status before any other operation. /// </summary> static ImageEngine() { var path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); ProfileOptimization.SetProfileRoot(path); ProfileOptimization.StartProfile("Startup.Profile_ImageEngine"); WindowsWICCodecsAvailable = WIC_Codecs.WindowsCodecsPresent(); // Set NumThreads to be more sensible NumThreads = Environment.ProcessorCount - 1; if (NumThreads == 0) // Single core... { NumThreads = 1; } // Enable GPU Acceleration by default /*if (GPU.IsGPUAvailable) * EnableGPUAcceleration = false;*/ }
/// <summary> /// Constructor. Checks WIC status before any other operation. /// </summary> static ImageEngine() { WindowsWICCodecsAvailable = WIC_Codecs.WindowsCodecsPresent(); //WindowsWICCodecsAvailable = false; }
/// <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); }
/// <summary> /// Loads image from stream. /// </summary> /// <param name="stream">Full image stream.</param> /// <param name="Format">Detected Format.</param> /// <param name="extension">File Extension. Used to determine format more easily.</param> /// <param name="maxWidth">Maximum width to allow when loading. Resized if enforceResize = true.</param> /// <param name="maxHeight">Maximum height to allow when loading. Resized if enforceResize = true.</param> /// <param name="enforceResize">True = Resizes image to match either maxWidth or maxHeight.</param> /// <param name="header">DDS header of image.</param> /// <param name="mergeAlpha">ONLY valid when enforceResize is true. True = Flattens alpha down, directly affecting RGB.</param> /// <returns>List of Mipmaps.</returns> internal static List <MipMap> LoadImage(Stream stream, out Format Format, string extension, int maxWidth, int maxHeight, bool enforceResize, out DDSGeneral.DDS_HEADER header, bool mergeAlpha) { // KFreon: See if image is built-in codec agnostic. header = null; Format = ImageFormats.ParseFormat(stream, extension, ref header); List <MipMap> MipMaps = null; switch (Format.SurfaceFormat) { case ImageEngineFormat.BMP: case ImageEngineFormat.JPG: case ImageEngineFormat.PNG: MipMaps = WIC_Codecs.LoadWithCodecs(stream, maxWidth, maxHeight, false); break; case ImageEngineFormat.DDS_DXT1: case ImageEngineFormat.DDS_DXT2: case ImageEngineFormat.DDS_DXT3: case ImageEngineFormat.DDS_DXT4: case ImageEngineFormat.DDS_DXT5: if (WindowsWICCodecsAvailable) { MipMaps = WIC_Codecs.LoadWithCodecs(stream, maxWidth, maxHeight, true); } else { MipMaps = DDSGeneral.LoadDDS(stream, header, Format, maxHeight > maxWidth ? maxHeight : maxWidth); } break; case ImageEngineFormat.DDS_ARGB: case ImageEngineFormat.DDS_A8L8: case ImageEngineFormat.DDS_RGB: case ImageEngineFormat.DDS_ATI1: case ImageEngineFormat.DDS_ATI2_3Dc: case ImageEngineFormat.DDS_G8_L8: case ImageEngineFormat.DDS_V8U8: MipMaps = DDSGeneral.LoadDDS(stream, header, Format, maxHeight > maxWidth ? maxHeight : maxWidth); break; case ImageEngineFormat.TGA: var img = new TargaImage(stream); byte[] pixels = UsefulThings.WinForms.Imaging.GetPixelDataFromBitmap(img.Image); WriteableBitmap wbmp = UsefulThings.WPF.Images.CreateWriteableBitmap(pixels, img.Image.Width, img.Image.Height); var mip1 = new MipMap(wbmp); MipMaps = new List <MipMap>() { mip1 }; img.Dispose(); break; default: throw new InvalidDataException("Image format is unknown."); } if (MipMaps == null || MipMaps.Count == 0) { throw new InvalidDataException("No mipmaps loaded."); } // KFreon: No resizing requested if (maxHeight == 0 && maxWidth == 0) { return(MipMaps); } // KFreon: Test if we need to resize var top = MipMaps.First(); if (top.Width == maxWidth || top.Height == maxHeight) { return(MipMaps); } int max = maxWidth > maxHeight ? maxWidth : maxHeight; // KFreon: Attempt to resize var sizedMips = MipMaps.Where(m => m.Width > m.Height ? m.Width <= max : m.Height <= max); if (sizedMips != null && sizedMips.Any()) // KFreon: If there's already a mip, return that. { MipMaps = sizedMips.ToList(); } else if (enforceResize) { // Get top mip and clear others. var mip = MipMaps[0]; MipMaps.Clear(); MipMap output = null; int divisor = mip.Width > mip.Height ? mip.Width / max : mip.Height / max; output = Resize(mip, 1f / divisor, mergeAlpha); MipMaps.Add(output); } return(MipMaps); }
/// <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); }