internal static void SplitChannels(MipMap mip, string savePath) { char[] channels = new char[] { 'B', 'G', 'R', 'A' }; for (int i = 0; i < 4; i++) { // Extract channel into grayscale image var grayChannel = BuildGrayscaleFromChannel(mip.Pixels, i); // Save channel var img = UsefulThings.WPF.Images.CreateWriteableBitmap(grayChannel, mip.Width, mip.Height, PixelFormats.Gray8); PngBitmapEncoder encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(img)); byte[] bytes = null; using (MemoryStream ms = new MemoryStream(grayChannel.Length)) { encoder.Save(ms); bytes = ms.ToArray(); } if (bytes == null) { throw new InvalidDataException("Failed to save channel. Reason unknown."); } string tempPath = Path.GetFileNameWithoutExtension(savePath) + "_" + channels[i] + ".png"; string channelPath = Path.Combine(Path.GetDirectoryName(savePath), UsefulThings.General.FindValidNewFileName(tempPath)); File.WriteAllBytes(channelPath, bytes); } }
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); }
/// <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> /// Builds a DDS image from an existing mipmap. /// </summary> /// <param name="mip">Mip to base image on.</param> /// <param name="DDSFormat">Format of mipmap.</param> public ImageEngineImage(MipMap mip, ImageEngineFormat DDSFormat) { Format = new Format(DDSFormat); MipMaps = new List <MipMap>() { mip }; header = DDSGeneral.Build_DDS_Header(1, mip.Height, mip.Width, DDSFormat); }
/// <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); }
/// <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> /// Gets a preview. /// </summary> /// <param name="ShowAlpha">False = Creates a preview without alpha.</param> /// <param name="index">Index of mipmap to preview.</param> /// <returns>BitmapImage of image.</returns> public BitmapSource GeneratePreview(int index, bool ShowAlpha) { // KFreon: NOTE: Seems to ignore alpha - pretty much ultra useful since premultiplying alpha often removes most of the image MipMap mip = MipMaps[index]; BitmapSource bmp; if (ShowAlpha) { bmp = mip.BaseImage; } else { bmp = new FormatConvertedBitmap(mip.BaseImage, System.Windows.Media.PixelFormats.Bgr32, null, 0); } if (!bmp.IsFrozen) { bmp.Freeze(); } return(bmp); }
/// <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); }
internal static MipMap Resize(MipMap mipMap, double scale, bool mergeAlpha) { WriteableBitmap bmp = mipMap.BaseImage; int origWidth = bmp.PixelWidth; int origHeight = bmp.PixelHeight; int origStride = origWidth * 4; int newWidth = (int)(origWidth * scale); int newHeight = (int)(origHeight * scale); int newStride = newWidth * 4; WriteableBitmap alpha = new WriteableBitmap(origWidth, origHeight, 96, 96, PixelFormats.Bgr32, null); if (!mergeAlpha) { // Pull out alpha since scaling with alpha doesn't work properly for some reason try { unsafe { int index = 3; byte *alphaPtr = (byte *)alpha.BackBuffer.ToPointer(); byte *mainPtr = (byte *)bmp.BackBuffer.ToPointer(); for (int i = 0; i < origWidth * origHeight * 4; i += 4) { // Set all pixels in alpha to value of alpha from original image - otherwise scaling will interpolate colours alphaPtr[i] = mainPtr[index]; alphaPtr[i + 1] = mainPtr[index]; alphaPtr[i + 2] = mainPtr[index]; alphaPtr[i + 3] = mainPtr[index]; index += 4; } } } catch (Exception e) { Debug.WriteLine(e.ToString()); throw; } } FormatConvertedBitmap main = new FormatConvertedBitmap(bmp, PixelFormats.Bgr32, null, 0); // Scale RGB ScaleTransform scaletransform = new ScaleTransform(scale, scale); TransformedBitmap scaledMain = new TransformedBitmap(main, scaletransform); // Put alpha back in FormatConvertedBitmap newConv = new FormatConvertedBitmap(scaledMain, PixelFormats.Bgra32, null, 0); WriteableBitmap resized = new WriteableBitmap(newConv); if (!mergeAlpha) { TransformedBitmap scaledAlpha = new TransformedBitmap(alpha, scaletransform); WriteableBitmap newAlpha = new WriteableBitmap(scaledAlpha); try { unsafe { byte *resizedPtr = (byte *)resized.BackBuffer.ToPointer(); byte *alphaPtr = (byte *)newAlpha.BackBuffer.ToPointer(); for (int i = 3; i < newStride * newHeight; i += 4) { resizedPtr[i] = alphaPtr[i]; } } } catch (Exception e) { Debug.WriteLine(e.ToString()); throw; } } return(new MipMap(resized)); }
/// <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); }
internal static MipMap Resize(MipMap mipMap, double xScale, double yScale) { var baseBMP = UsefulThings.WPF.Images.CreateWriteableBitmap(mipMap.Pixels, mipMap.Width, mipMap.Height); baseBMP.Freeze(); //return Resize(baseBMP, xScale, yScale, mipMap.Width, mipMap.Height, mipMap.LoadedFormatDetails); #region Old code, but want to keep not only for posterity, but I'm not certain the above works in the context below. // KFreon: Only do the alpha bit if there is any alpha. Git #444 (https://github.com/ME3Explorer/ME3Explorer/issues/444) exposed the issue where if there isn't alpha, it overruns the buffer. bool alphaPresent = mipMap.IsAlphaPresent; WriteableBitmap alpha = new WriteableBitmap(mipMap.Width, mipMap.Height, 96, 96, PixelFormats.Bgr32, null); if (alphaPresent)// && !mergeAlpha) { // Pull out alpha since scaling with alpha doesn't work properly for some reason try { unsafe { alpha.Lock(); int index = 3; byte *alphaPtr = (byte *)alpha.BackBuffer.ToPointer(); for (int i = 0; i < mipMap.Width * mipMap.Height * 4; i += 4) { // Set all pixels in alpha to value of alpha from original image - otherwise scaling will interpolate colours alphaPtr[i] = mipMap.Pixels[index]; alphaPtr[i + 1] = mipMap.Pixels[index]; alphaPtr[i + 2] = mipMap.Pixels[index]; alphaPtr[i + 3] = mipMap.Pixels[index]; index += 4; } alpha.Unlock(); } } catch (Exception e) { Debug.WriteLine(e.ToString()); throw; } } var bmp = UsefulThings.WPF.Images.CreateWriteableBitmap(mipMap.Pixels, mipMap.Width, mipMap.Height); FormatConvertedBitmap main = new FormatConvertedBitmap(bmp, PixelFormats.Bgr32, null, 0); // Scale RGB ScaleTransform scaletransform = new ScaleTransform(xScale, yScale); TransformedBitmap scaledMain = new TransformedBitmap(main, scaletransform); int newWidth = (int)(mipMap.Width * xScale); int newHeight = (int)(mipMap.Height * yScale); int newStride = (int)(newWidth * 4); // Put alpha back in FormatConvertedBitmap newConv = new FormatConvertedBitmap(scaledMain, PixelFormats.Bgra32, null, 0); WriteableBitmap resized = new WriteableBitmap(newConv); if (alphaPresent)// && !mergeAlpha) { TransformedBitmap scaledAlpha = new TransformedBitmap(alpha, scaletransform); WriteableBitmap newAlpha = new WriteableBitmap(scaledAlpha); try { unsafe { resized.Lock(); newAlpha.Lock(); byte *resizedPtr = (byte *)resized.BackBuffer.ToPointer(); byte *alphaPtr = (byte *)newAlpha.BackBuffer.ToPointer(); for (int i = 3; i < newStride * newHeight; i += 4) { resizedPtr[i] = alphaPtr[i]; } resized.Unlock(); newAlpha.Unlock(); } } catch (Exception e) { Debug.WriteLine(e.ToString()); throw; } } return(new MipMap(resized.GetPixelsAsBGRA32(), newWidth, newHeight, mipMap.LoadedFormatDetails)); #endregion Old code }
internal static MipMap Resize(MipMap mipMap, double scale) { return(Resize(mipMap, scale, scale)); // Could be either scale dimension, doesn't matter. }
/// <summary> /// Creates a WPF Bitmap from largest mipmap. /// Does NOT require that image remains alive. /// </summary> /// <param name="ShowAlpha">True = flattens alpha, directly affecting RGB.</param> /// <param name="maxDimension">Resizes image or uses a mipmap if available. Overrides mipIndex if specified.</param> /// <param name="mipIndex">Index of mipmap to retrieve. Overridden by maxDimension if it's specified.</param> /// <returns>WPF bitmap of largest mipmap.</returns> public BitmapSource GetWPFBitmap(int maxDimension = 0, bool ShowAlpha = false, int mipIndex = 0) { MipMap mip = MipMaps[mipIndex]; return(GetWPFBitmap(mip, maxDimension, ShowAlpha)); }
/// <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); }