/// <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 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 convert the image from B4G4R4A4. /// </summary> /// <param name="baseImage">The base image to convert.</param> /// <param name="destFormat">The destination format.</param> /// <returns>The updated image.</returns> private static IGorgonImage ConvertFromB4G4R4A4(IGorgonImage baseImage, BufferFormat destFormat) { // If we're converting to R8G8B8A8 or B8G8R8A8, then use those formats, otherwise, default to B8G8R8A8 as an intermediate buffer. BufferFormat tempFormat = ((destFormat != BufferFormat.B8G8R8A8_UNorm) && (destFormat != BufferFormat.R8G8B8A8_UNorm)) ? BufferFormat.B8G8R8A8_UNorm : destFormat; // Create an worker image in B8G8R8A8 format. IGorgonImageInfo destInfo = new GorgonImageInfo(baseImage.ImageType, tempFormat) { Depth = baseImage.Depth, Height = baseImage.Height, Width = baseImage.Width, ArrayCount = baseImage.ArrayCount, MipCount = baseImage.MipCount }; // Our destination image for B8G8R8A8 or R8G8B8A8. var destImage = new GorgonImage(destInfo); try { // We have to manually upsample from R4G4B4A4 to B8R8G8A8. // Because we're doing this manually, dithering won't be an option unless for (int array = 0; array < baseImage.ArrayCount; ++array) { for (int mip = 0; mip < baseImage.MipCount; ++mip) { int depthCount = baseImage.GetDepthCount(mip); for (int depth = 0; depth < depthCount; depth++) { IGorgonImageBuffer destBuffer = destImage.Buffers[mip, baseImage.ImageType == ImageType.Image3D ? depth : array]; IGorgonImageBuffer srcBuffer = baseImage.Buffers[mip, baseImage.ImageType == ImageType.Image3D ? depth : array]; ConvertPixelsFromB4G4R4A4(destBuffer, srcBuffer); } } } // If the destination format is not R8G8B8A8 or B8G8R8A8, then we need to do more conversion. if (destFormat != destImage.Format) { ConvertToFormat(destImage, destFormat); } // Update the base image with our worker image. destImage.CopyTo(baseImage); return(baseImage); } finally { destImage.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 image format is compressed.</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> /// If the image does not contain alpha then the method will return right away and no alterations to the image will be performed. /// </para> /// </remarks> public static IGorgonImage ConvertToPremultipliedAlpha(this IGorgonImage baseImage) { IGorgonImage newImage = null; GorgonNativeBuffer <byte> imageData = null; if (baseImage == null) { throw new ArgumentNullException(nameof(baseImage)); } if (!baseImage.FormatInfo.HasAlpha) { return(baseImage); } if (baseImage.FormatInfo.IsCompressed) { throw new ArgumentException(string.Format(Resources.GORIMG_ERR_FORMAT_NOT_SUPPORTED, baseImage.Format), nameof(baseImage)); } try { var cloneImageInfo = new GorgonImageInfo(baseImage) { HasPreMultipliedAlpha = true }; imageData = new GorgonNativeBuffer <byte>(baseImage.ImageData.Length); baseImage.ImageData.CopyTo(imageData); unsafe { newImage = new GorgonImage(cloneImageInfo, new GorgonReadOnlyPointer((void *)imageData, imageData.SizeInBytes)); int arrayOrDepth = newImage.ImageType == ImageType.Image3D ? newImage.Depth : newImage.ArrayCount; for (int mip = 0; mip < newImage.MipCount; ++mip) { for (int i = 0; i < arrayOrDepth; ++i) { IGorgonImageBuffer buffer = newImage.Buffers[mip, i]; byte *ptr = (byte *)buffer.Data; int rowPitch = buffer.PitchInformation.RowPitch; for (int y = 0; y < buffer.Height; ++y) { ImageUtilities.SetPremultipliedScanline(ptr, rowPitch, ptr, rowPitch, buffer.Format); ptr += rowPitch; } } } } newImage.CopyTo(baseImage); return(baseImage); } finally { imageData?.Dispose(); newImage?.Dispose(); } }
/// <summary> /// Function to generate a new mip map chain. /// </summary> /// <param name="baseImage">The image which will have its mip map chain updated.</param> /// <param name="mipCount">The number of mip map levels.</param> /// <param name="filter">[Optional] The filter to apply when copying the data from one mip level to another.</param> /// <returns>A <see cref="IGorgonImage"/> containing the updated mip map data.</returns> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="baseImage"/> parameter is <b>null</b>.</exception> /// <remarks> /// <para> /// This method will generate a new mip map chain for the <paramref name="mipCount"/>. If the current number of mip maps is not the same as the requested number, then the image buffer will be /// adjusted to use the requested number of mip maps. If 0 is passed to <paramref name="mipCount"/>, then a full mip map chain is generated. /// </para> /// <para> /// Note that the <paramref name="mipCount"/> may not be honored depending on the current width, height, and depth of the image. Check the width, height and/or depth property on the returned /// <see cref="IGorgonImage"/> to determine how many mip levels were actually generated. /// </para> /// </remarks> public static IGorgonImage GenerateMipMaps(this IGorgonImage baseImage, int mipCount, ImageFilter filter = ImageFilter.Point) { if (baseImage == null) { throw new ArgumentNullException(nameof(baseImage)); } int maxMips = GorgonImage.CalculateMaxMipCount(baseImage); // If we specify 0, then generate a full chain. if ((mipCount <= 0) || (mipCount > maxMips)) { mipCount = maxMips; } // If we don't have any mip levels, then return the image as-is. if (mipCount < 2) { return(baseImage); } var destSettings = new GorgonImageInfo(baseImage) { MipCount = mipCount }; var newImage = new GorgonImage(destSettings); var wic = new WicUtilities(); try { // Copy the top mip level from the source image to the dest image. for (int array = 0; array < baseImage.ArrayCount; ++array) { GorgonNativeBuffer <byte> buffer = newImage.Buffers[0, array].Data; int size = buffer.SizeInBytes; baseImage.Buffers[0, array].Data.CopyTo(buffer, count: size); } // If we have 4 bits per channel, then we need to convert to 8 bit per channel to make WIC happy. if (baseImage.Format == BufferFormat.B4G4R4A4_UNorm) { newImage.ConvertToFormat(BufferFormat.R8G8B8A8_UNorm); } wic.GenerateMipImages(newImage, filter); // Convert back if we asked for 4 bit per channel. if (baseImage.Format == BufferFormat.B4G4R4A4_UNorm) { newImage.ConvertToFormat(BufferFormat.B4G4R4A4_UNorm); } newImage.CopyTo(baseImage); return(baseImage); } finally { newImage.Dispose(); wic.Dispose(); } }