/// <summary> /// Function to resize the image to a new width, height and/or depth. /// </summary> /// <param name="baseImage">The image to resize.</param> /// <param name="newWidth">The new width for the image.</param> /// <param name="newHeight">The new height for the image (for <see cref="ImageType.Image2D"/> and <see cref="ImageType.ImageCube"/> images).</param> /// <param name="newDepth">The new depth for the image (for <see cref="ImageType.Image3D"/> images).</param> /// <param name="filter">[Optional] The type of filtering to apply to the scaled image to help smooth larger and smaller images.</param> /// <returns>A <see cref="IGorgonImage"/> containing the resized image.</returns> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="baseImage"/> parameter is <b>null</b>.</exception> /// <exception cref="ArgumentOutOfRangeException">Thrown if the <paramref name="newWidth"/>, <paramref name="newHeight"/>, or <paramref name="newDepth"/> parameters are less than 1.</exception> /// <exception cref="GorgonException">Thrown if there was an error during resizing regarding image pixel format conversion due to the type of <paramref name="filter"/> applied.</exception> /// <remarks> /// <para> /// This method will change the size of an existing image and return a larger or smaller version of itself as a new <see cref="IGorgonImage"/>. /// </para> /// </remarks> public static IGorgonImage Resize(this IGorgonImage baseImage, int newWidth, int newHeight, int newDepth, ImageFilter filter = ImageFilter.Point) { if (baseImage == null) { throw new ArgumentNullException(nameof(baseImage)); } if (newWidth < 1) { throw new ArgumentOutOfRangeException(Resources.GORIMG_ERR_IMAGE_WIDTH_TOO_SMALL, nameof(newWidth)); } if ((newHeight < 1) && ((baseImage.ImageType == ImageType.Image2D) || (baseImage.ImageType == ImageType.ImageCube))) { throw new ArgumentOutOfRangeException(Resources.GORIMG_ERR_IMAGE_HEIGHT_TOO_SMALL, nameof(newHeight)); } if ((newDepth < 1) && (baseImage.ImageType == ImageType.Image3D)) { throw new ArgumentOutOfRangeException(Resources.GORIMG_ERR_IMAGE_DEPTH_TOO_SMALL, nameof(newDepth)); } // Only use the appropriate dimensions. switch (baseImage.ImageType) { case ImageType.Image1D: newHeight = baseImage.Height; break; case ImageType.Image2D: case ImageType.ImageCube: newDepth = baseImage.Depth; break; } // If we haven't actually changed the size, then skip out. if ((newWidth == baseImage.Width) && (newHeight == baseImage.Height) && (newDepth == baseImage.Depth)) { return(baseImage); } var wic = new WicUtilities(); IGorgonImage newImage = null; try { int calcMipLevels = GorgonImage.CalculateMaxMipCount(newWidth, newHeight, newDepth).Min(baseImage.MipCount); newImage = wic.Resize(baseImage, 0, 0, newWidth, newHeight, newDepth, calcMipLevels, filter, ResizeMode.Scale); newImage.CopyTo(baseImage); return(baseImage); } finally { newImage?.Dispose(); wic.Dispose(); } }
/// <summary> /// Function to convert the image data into a premultiplied format. /// </summary> /// <param name="baseImage">The image to convert.</param> /// <returns>A <see cref="IGorgonImage"/> containing the image data with the premultiplied alpha pixel data.</returns> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="baseImage"/> is <b>null</b>.</exception> /// <exception cref="ArgumentException">Thrown when the original format could not be converted to <see cref="BufferFormat.R8G8B8A8_UNorm"/>.</exception> /// <remarks> /// <para> /// Use this to convert an image to a premultiplied format. This takes each Red, Green and Blue element and multiplies them by the Alpha element. /// </para> /// <para> /// Because this method will only operate on <see cref="BufferFormat.R8G8B8A8_UNorm"/> formattted image data, the image will be converted to that format and converted back to its original format /// after the alpha is premultiplied. This may cause color fidelity issues. If the image cannot be converted, then an exception will be thrown. /// </para> /// </remarks> public static IGorgonImage ConvertToPremultipliedAlpha(this IGorgonImage baseImage) { IGorgonImage newImage = null; if (baseImage == null) { throw new ArgumentNullException(nameof(baseImage)); } try { // Worker image. var cloneImageInfo = new GorgonImageInfo(baseImage) { HasPreMultipliedAlpha = true }; newImage = new GorgonImage(cloneImageInfo); baseImage.CopyTo(newImage); if (newImage.Format != BufferFormat.R8G8B8A8_UNorm) { if (!newImage.CanConvertToFormat(BufferFormat.R8G8B8A8_UNorm)) { throw new ArgumentException(string.Format(Resources.GORIMG_ERR_FORMAT_NOT_SUPPORTED, BufferFormat.R8G8B8A8_UNorm), nameof(baseImage)); } // Clone the image so we can convert it to the correct format. newImage.ConvertToFormat(BufferFormat.R8G8B8A8_UNorm); } unsafe { int *imagePtr = (int *)(newImage.ImageData); for (int i = 0; i < newImage.SizeInBytes; i += newImage.FormatInfo.SizeInBytes) { var color = GorgonColor.FromABGR(*imagePtr); color = new GorgonColor(color.Red * color.Alpha, color.Green * color.Alpha, color.Blue * color.Alpha, color.Alpha); *(imagePtr++) = color.ToABGR(); } } if (newImage.Format != baseImage.Format) { newImage.ConvertToFormat(baseImage.Format); } newImage.CopyTo(baseImage); return(baseImage); } finally { newImage?.Dispose(); } }
/// <summary> /// Function to crop the image to the rectangle passed to the parameters. /// </summary> /// <param name="baseImage">The image to resize.</param> /// <param name="cropRect">The rectangle that will be used to crop the image.</param> /// <param name="newDepth">The new depth for the image (for <see cref="ImageType.Image3D"/> images).</param> /// <returns>A <see cref="IGorgonImage"/> containing the resized image.</returns> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="baseImage"/> parameter is <b>null</b>.</exception> /// <exception cref="ArgumentOutOfRangeException">Thrown if the <paramref name="newDepth"/> parameter is less than 1.</exception> /// <remarks> /// <para> /// This method will crop the existing image a smaller version of itself as a new <see cref="IGorgonImage"/>. If the sizes are the same, or the <paramref name="cropRect"/> is larger than the size /// of the <paramref name="baseImage"/>, then no changes will be made. /// </para> /// </remarks> public static IGorgonImage Crop(this IGorgonImage baseImage, DX.Rectangle cropRect, int newDepth) { if (baseImage == null) { throw new ArgumentNullException(nameof(baseImage)); } if ((newDepth < 1) && (baseImage.ImageType == ImageType.Image3D)) { throw new ArgumentOutOfRangeException(Resources.GORIMG_ERR_IMAGE_DEPTH_TOO_SMALL, nameof(newDepth)); } // Only use the appropriate dimensions. switch (baseImage.ImageType) { case ImageType.Image1D: cropRect.Height = baseImage.Height; break; case ImageType.Image2D: case ImageType.ImageCube: newDepth = baseImage.Depth; break; } // If the intersection of the crop rectangle and the source buffer are the same (and the depth is the same), then we don't need to crop. var bufferRect = new DX.Rectangle(0, 0, baseImage.Width, baseImage.Height); var clipRect = DX.Rectangle.Intersect(cropRect, bufferRect); if ((bufferRect.Equals(ref clipRect)) && (newDepth == baseImage.Depth)) { return(baseImage); } var wic = new WicUtilities(); IGorgonImage newImage = null; try { int calcMipLevels = GorgonImage.CalculateMaxMipCount(cropRect.Width, cropRect.Height, newDepth).Min(baseImage.MipCount); newImage = wic.Resize(baseImage, cropRect.X, cropRect.Y, cropRect.Width, cropRect.Height, newDepth, calcMipLevels, ImageFilter.Point, ResizeMode.Crop); // Send the data over to the new image. newImage.CopyTo(baseImage); return(baseImage); } finally { newImage?.Dispose(); wic.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(); } }