/// <summary> /// Loads DDS that has no header - primarily for ME3Explorer. DDS data is standard, just without a header. /// ASSUMES VALID DDS DATA. Also, single mipmap only. /// </summary> /// <param name="rawDDSData">Standard DDS data but lacking header.</param> /// <param name="surfaceFormat">Surface format of DDS.</param> /// <param name="width">Width of image.</param> /// <param name="height">Height of image.</param> public ImageEngineImage(byte[] rawDDSData, ImageEngineFormat surfaceFormat, int width, int height) { Format = new Format(surfaceFormat); DDSGeneral.DDS_HEADER tempHeader = null; MipMaps = ImageEngine.LoadImage(rawDDSData, surfaceFormat, width, height, out tempHeader); header = tempHeader; }
/// <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)); } }
BitmapSource GetWPFBitmap(MipMap mip, int maxDimension, bool ShowAlpha) { BitmapSource bmp = null; if (maxDimension != 0) { // Choose a mip of the correct size, if available. var sizedMip = MipMaps.Where(m => (m.Height <= maxDimension && m.Width <= maxDimension) || (m.Width <= maxDimension && m.Height <= maxDimension)); if (sizedMip.Any()) { var mip1 = sizedMip.First(); bmp = mip1.ToImage(); } else { double scale = (double)maxDimension / (Height > Width ? Height : Width); mip = ImageEngine.Resize(mip, scale); bmp = mip.ToImage(); } } else { bmp = mip.ToImage(); } if (!ShowAlpha) { bmp = new FormatConvertedBitmap(bmp, System.Windows.Media.PixelFormats.Bgr32, null, 0); } bmp.Freeze(); return(bmp); }
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> /// 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> /// <returns>BGRA Pixel Data as stream.</returns> internal static List <MipMap> LoadWithCodecs(Stream stream, int decodeWidth, int decodeHeight, bool isDDS) { if (!WindowsCodecsAvailable) { return(null); } List <MipMap> mipmaps = new List <MipMap>(); bool alternateDecodeDimensions = decodeWidth != 0 || decodeHeight != 0; if (isDDS) { // KFreon: Attempt to load any mipmaps stream.Seek(0, SeekOrigin.Begin); var decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.OnDemand); foreach (var mipmap in decoder.Frames) { // KFreon: Skip mipmaps that are too big if asked to load a smaller image if (alternateDecodeDimensions) { if (mipmap.Width > decodeWidth || mipmap.Height > decodeHeight) { continue; } } mipmaps.Add(new MipMap(mipmap)); } if (mipmaps.Count == 0) { // KFreon: No mips, so resize largest var mip = new MipMap(decoder.Frames[0]); // KFreon: Keep aspect ratio. Take smallest scaling value. double hScale = decodeHeight != 0 ? decodeHeight * 1f / mip.Height : 1; double wScale = decodeWidth != 0 ? decodeWidth * 1f / mip.Width : 1; double scale = hScale < wScale ? hScale : wScale; mip = ImageEngine.Resize(mip, scale, false); mipmaps.Add(mip); } } else { // KFreon: No Mipmaps BitmapImage bmp = AttemptUsingWindowsCodecs(stream, decodeWidth, decodeHeight); if (bmp == null) { return(null); } mipmaps.Add(new MipMap(bmp)); } return(mipmaps); }
/// <summary> /// Creates a WPF image from this mipmap. /// </summary> /// <returns>WriteableBitmap of image.</returns> public BitmapSource ToImage() { var tempPixels = ImageEngine.GetPixelsAsBGRA32(Width, Height, Pixels, LoadedFormatDetails); var bmp = UsefulThings.WPF.Images.CreateWriteableBitmap(tempPixels, Width, Height); bmp.Freeze(); return(bmp); }
private void LoadFromStream(Stream stream, string extension = null, int desiredMaxDimension = 0, bool enforceResize = true) { Format format = new Format(); // KFreon: Load image and save useful information including BGRA pixel data - may be processed from original into this form. DDSGeneral.DDS_HEADER tempheader = null; MipMaps = ImageEngine.LoadImage(stream, out format, extension, desiredMaxDimension, enforceResize, out tempheader, false); header = tempheader; Format = format; }
private void LoadFromFile(string imagePath, int desiredMaxDimension = 0, bool enforceResize = true) { Format format = new Format(); FilePath = imagePath; // KFreon: Load image and save useful information including BGRA pixel data - may be processed from original into this form. DDSGeneral.DDS_HEADER tempheader = null; MipMaps = ImageEngine.LoadImage(imagePath, out format, desiredMaxDimension, enforceResize, out tempheader, false); // KFreon: Can't pass properties as out :( header = tempheader; Format = format; }
/// <summary> /// Creates a WPF Bitmap from largest mipmap. /// Does NOT require that image remains alive. /// </summary> /// <param name="mergeAlpha">Only valid if maxDimension set. True = flattens alpha, directly affecting RGB.</param> /// <param name="maxDimension">Resizes image or uses a mipmap if available.</param> /// <returns>WPF bitmap of largest mipmap.</returns> public BitmapSource GetWPFBitmap(int maxDimension = 0, bool mergeAlpha = false) { MipMap mip = MipMaps[0]; if (maxDimension != 0) { // Choose a mip of the correct size, if available. var sizedMip = MipMaps.Where(m => (m.Height == maxDimension && m.Width <= maxDimension) || (m.Width == maxDimension && m.Height <= maxDimension)); if (sizedMip.Any()) { mip = sizedMip.First(); } else { double scale = maxDimension * 1f / (Height > Width ? Height : Width); mip = ImageEngine.Resize(mip, scale, mergeAlpha); } } mip.BaseImage.Freeze(); return(mip.BaseImage); }
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); }
/// <summary> /// Creates a GDI+ bitmap from largest mipmap. /// Does NOT require that image remains alive. /// </summary> /// <param name="ignoreAlpha">True = Previews image without alpha channel.</param> /// <param name="maxDimension">Largest size to display.</param> /// <param name="mergeAlpha">ONLY valid when maxDimension is set. True = flattens alpha, directly affecting RGB.</param> /// <returns>GDI+ bitmap of largest mipmap.</returns> public System.Drawing.Bitmap GetGDIBitmap(bool ignoreAlpha, bool mergeAlpha, int maxDimension = 0) { MipMap mip = MipMaps[0]; if (maxDimension != 0) { // Choose a mip of the correct size, if available. var sizedMip = MipMaps.Where(m => (m.Height == maxDimension && m.Width <= maxDimension) || (m.Width == maxDimension && m.Height <= maxDimension)); if (sizedMip.Any()) { mip = sizedMip.First(); } else { double scale = maxDimension * 1f / (Height > Width ? Height : Width); mip = ImageEngine.Resize(mip, scale, mergeAlpha); } } mip.BaseImage.Freeze(); return(UsefulThings.WinForms.Imaging.CreateBitmap(mip.BaseImage, ignoreAlpha)); }
/// <summary> /// Scales top mipmap and DESTROYS ALL OTHERS. /// </summary> /// <param name="scale">Scaling factor. </param> public void Resize(double scale) { MipMap closestMip = null; double newScale = 0; double desiredSize = MipMaps[0].Width * scale; double min = double.MaxValue; foreach (var mip in MipMaps) { double temp = Math.Abs(mip.Width - desiredSize); if (temp < min) { closestMip = mip; min = temp; } } newScale = desiredSize / closestMip.Width; MipMaps[0] = ImageEngine.Resize(closestMip, newScale); MipMaps.RemoveRange(1, NumMipMaps - 1); }
/// <summary> /// Scales top mipmap and DESTROYS ALL OTHERS. /// </summary> /// <param name="scale">Scaling factor. </param> /// <param name="mergeAlpha">True = flattens alpha, directly affecting RGB.</param> public void Resize(double scale, bool mergeAlpha) { MipMaps[0] = ImageEngine.Resize(MipMaps[0], scale, mergeAlpha); MipMaps.RemoveRange(1, NumMipMaps - 1); }
/// <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 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> /// Saves each channel separately incl Alpha. /// </summary> /// <param name="savePath">General save path. Appends channel name too.</param> public void SplitChannels(string savePath) { ImageEngine.SplitChannels(MipMaps[0], savePath); }
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> /// 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); }