/// <summary> /// Function to create a WIC bitmap from a System.Drawing.Image object. /// </summary> /// <param name="bitmap">WIC bitmap to copy to our image data.</param> /// <param name="filter">Filter used to scale the image.</param> /// <param name="ditherFlags">Flags used to dither the image.</param> /// <param name="buffer">Buffer for holding the image data.</param> /// <param name="clip">TRUE to clip the data, FALSE to scale it.</param> public void AddWICBitmapToImageData(WIC.Bitmap bitmap, ImageFilter filter, ImageDithering ditherFlags, GorgonImageBuffer buffer, bool clip) { Guid conversionFormat = GetGUID(buffer.Format); bool needsResize = (buffer.Width != bitmap.Size.Width) || (buffer.Height != bitmap.Size.Height); if (conversionFormat == Guid.Empty) { throw new GorgonException(GorgonResult.FormatNotSupported, string.Format(Resources.GORGFX_FORMAT_NOT_SUPPORTED, buffer.Format)); } // Turn off filtering if we're not resizing. if (!needsResize) { filter = ImageFilter.Point; } // If the pixel format of the bitmap is not the same as our // conversion format, then we need to convert the image. if (bitmap.PixelFormat != conversionFormat) { ConvertFormat(bitmap.PixelFormat, conversionFormat, (WIC.BitmapDitherType)ditherFlags, (WIC.BitmapInterpolationMode)filter, bitmap, null, 0, buffer, needsResize, clip); } else { // Just dump without converting because our formats are equal. if ((!needsResize) || (!ResizeBitmap(bitmap, (WIC.BitmapInterpolationMode)filter, buffer, clip))) { bitmap.CopyPixels(buffer.PitchInformation.RowPitch, buffer.Data.BasePointer, buffer.PitchInformation.SlicePitch); } } }
/// <summary> /// Function to convert the image to B4G4R4A4. /// </summary> /// <param name="baseImage">The base image to convert.</param> /// <param name="dithering">Dithering to apply to the converstion to B8G8R8A8.</param> /// <returns>The updated image.</returns> private static IGorgonImage ConvertToB4G4R4A4(IGorgonImage baseImage, ImageDithering dithering) { // This temporary image will be used to convert to B8G8R8A8. IGorgonImage newImage = baseImage; IGorgonImageInfo destInfo = new GorgonImageInfo(baseImage.ImageType, BufferFormat.B4G4R4A4_UNorm) { Depth = baseImage.Depth, Height = baseImage.Height, Width = baseImage.Width, ArrayCount = baseImage.ArrayCount, MipCount = baseImage.MipCount }; // This is our working buffer for B4G4R4A4. IGorgonImage destImage = new GorgonImage(destInfo); try { // If necessary, convert to B8G8R8A8. Otherwise, we'll just downsample directly. if ((newImage.Format != BufferFormat.B8G8R8A8_UNorm) && (newImage.Format != BufferFormat.R8G8B8A8_UNorm)) { newImage = baseImage.Clone(); ConvertToFormat(newImage, BufferFormat.B8G8R8A8_UNorm, dithering); } // The next step is to manually downsample to R4G4B4A4. // Because we're doing this manually, dithering won't be an option unless unless we've downsampled from a much higher bit format when converting to B8G8R8A8. for (int array = 0; array < newImage.ArrayCount; ++array) { for (int mip = 0; mip < newImage.MipCount; ++mip) { int depthCount = newImage.GetDepthCount(mip); for (int depth = 0; depth < depthCount; depth++) { IGorgonImageBuffer destBuffer = destImage.Buffers[mip, destInfo.ImageType == ImageType.Image3D ? depth : array]; IGorgonImageBuffer srcBuffer = newImage.Buffers[mip, newImage.ImageType == ImageType.Image3D ? depth : array]; ConvertPixelsToB4G4R4A4(destBuffer, srcBuffer); } } } destImage.CopyTo(baseImage); return(baseImage); } finally { destImage.Dispose(); if (newImage != baseImage) { newImage.Dispose(); } } }
/// <summary> /// Function to convert the pixel format of an image into another pixel format. /// </summary> /// <param name="baseImage">The image to convert.</param> /// <param name="format">The new pixel format for the image.</param> /// <param name="dithering">[Optional] Flag to indicate the type of dithering to perform when the bit depth for the <paramref name="format"/> is lower than the original bit depth.</param> /// <returns>A <see cref="IGorgonImage"/> containing the image data with the converted pixel format.</returns> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="baseImage"/> is <b>null</b>.</exception> /// <exception cref="ArgumentEmptyException">Thrown when the <paramref name="format"/> is set to <see cref="BufferFormat.Unknown"/>. /// <para>-or-</para> /// <para>Thrown when the original format could not be converted into the desired <paramref name="format"/>.</para> /// </exception> /// <remarks> /// <para> /// Use this to convert an image format from one to another. The conversion functionality uses Windows Imaging Components (WIC) to perform the conversion. /// </para> /// <para> /// Because this method uses WIC, not all formats will be convertible. To determine if a format can be converted, use the <see cref="GorgonImage.CanConvertToFormat"/> method. /// </para> /// <para> /// For the <see cref="BufferFormat.B4G4R4A4_UNorm"/> format, Gorgon has to perform a manual conversion since that format is not supported by WIC. Because of this, the /// <paramref name="dithering"/> flag will be ignored when downsampling to that format. /// </para> /// </remarks> public static IGorgonImage ConvertToFormat(this IGorgonImage baseImage, BufferFormat format, ImageDithering dithering = ImageDithering.None) { if (baseImage == null) { throw new ArgumentNullException(nameof(baseImage)); } if ((format == BufferFormat.Unknown) || (!baseImage.CanConvertToFormat(format))) { throw new ArgumentException(string.Format(Resources.GORIMG_ERR_FORMAT_NOT_SUPPORTED, format), nameof(format)); } if (format == baseImage.Format) { return(baseImage); } // If we've asked for 4 bit per channel BGRA, then we have to convert the base image to B8R8G8A8,and then convert manually (no support in WIC). if (format == BufferFormat.B4G4R4A4_UNorm) { return(ConvertToB4G4R4A4(baseImage, dithering)); } // If we're currently using B4G4R4A4, then manually convert (no support in WIC). if (baseImage.Format == BufferFormat.B4G4R4A4_UNorm) { return(ConvertFromB4G4R4A4(baseImage, format)); } var destInfo = new GorgonFormatInfo(format); WicUtilities wic = null; IGorgonImage newImage = null; try { wic = new WicUtilities(); newImage = wic.ConvertToFormat(baseImage, format, dithering, baseImage.FormatInfo.IsSRgb, destInfo.IsSRgb); newImage.CopyTo(baseImage); return(baseImage); } finally { newImage?.Dispose(); wic?.Dispose(); } }
/// <summary> /// Function to perform conversion/transformation of a bitmap. /// </summary> /// <param name="sourceData">WIC bitmap to transform.</param> /// <param name="destData">Destination data buffer.</param> /// <param name="rowPitch">Number of bytes per row in the image.</param> /// <param name="slicePitch">Number of bytes in total for the image.</param> /// <param name="destFormat">Destination format for transformation.</param> /// <param name="isSourcesRGB">TRUE if the source format is sRGB.</param> /// <param name="isDestsRGB">TRUE if the destination format is sRGB.</param> /// <param name="dither">Dithering to apply to images that get converted to a lower bit depth.</param> /// <param name="destRect">Rectangle containing the area to scale or clip</param> /// <param name="clip">TRUE to perform clipping instead of scaling.</param> /// <param name="scaleFilter">Filter to apply to scaled data.</param> public void TransformImageData(WIC.BitmapSource sourceData, IntPtr destData, int rowPitch, int slicePitch, Guid destFormat, bool isSourcesRGB, bool isDestsRGB, ImageDithering dither, Rectangle destRect, bool clip, ImageFilter scaleFilter) { WIC.BitmapSource source = sourceData; WIC.FormatConverter converter = null; WIC.BitmapClipper clipper = null; WIC.BitmapScaler scaler = null; WIC.ColorContext sourceContext = null; WIC.ColorContext destContext = null; WIC.ColorTransform sRGBTransform = null; try { if (destFormat != Guid.Empty) { if (sourceData.PixelFormat != destFormat) { converter = new WIC.FormatConverter(Factory); if (!converter.CanConvert(sourceData.PixelFormat, destFormat)) { throw new GorgonException(GorgonResult.FormatNotSupported, string.Format(Resources.GORGFX_FORMAT_NOT_SUPPORTED, destFormat)); } converter.Initialize(source, destFormat, (WIC.BitmapDitherType)dither, null, 0, WIC.BitmapPaletteType.Custom); source = converter; } if ((isDestsRGB) || (isSourcesRGB)) { sRGBTransform = new WIC.ColorTransform(Factory); sourceContext = new WIC.ColorContext(Factory); destContext = new WIC.ColorContext(Factory); sourceContext.InitializeFromExifColorSpace(isSourcesRGB ? 1 : 2); destContext.InitializeFromExifColorSpace(isDestsRGB ? 1 : 2); sRGBTransform.Initialize(source, sourceContext, destContext, destFormat); source = sRGBTransform; } } if (destRect.IsEmpty) { source.CopyPixels(rowPitch, destData, slicePitch); return; } Guid pixelFormat = source.PixelFormat; if (!clip) { scaler = new WIC.BitmapScaler(Factory); scaler.Initialize(source, destRect.Width, destRect.Height, (WIC.BitmapInterpolationMode)scaleFilter); source = scaler; } else { destRect.Width = destRect.Width.Min(source.Size.Width); destRect.Height = destRect.Height.Min(source.Size.Height); if ((destRect.Width < source.Size.Width) || (destRect.Height < source.Size.Height)) { clipper = new WIC.BitmapClipper(Factory); clipper.Initialize(source, new DX.Rectangle(destRect.X, destRect.Y, destRect.Width, destRect.Height)); source = clipper; destRect.X = 0; destRect.Y = 0; } } // We have a change of format (probably due to the filter when scaling)... so we need to convert. if (source.PixelFormat != pixelFormat) { converter = new WIC.FormatConverter(Factory); if (!converter.CanConvert(source.PixelFormat, pixelFormat)) { throw new GorgonException(GorgonResult.FormatNotSupported, string.Format(Resources.GORGFX_FORMAT_NOT_SUPPORTED, pixelFormat)); } converter.Initialize(source, pixelFormat, WIC.BitmapDitherType.None, null, 0, WIC.BitmapPaletteType.Custom); source = converter; } source.CopyPixels(rowPitch, destData, slicePitch); } finally { if (converter != null) { converter.Dispose(); } if (sourceContext != null) { sourceContext.Dispose(); } if (destContext != null) { destContext.Dispose(); } if (sRGBTransform != null) { sRGBTransform.Dispose(); } if (scaler != null) { scaler.Dispose(); } if (clipper != null) { clipper.Dispose(); } } }