// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) James South. // Licensed under the Apache License, Version 2.0. // // // Provides the necessary information to support webp images. // Adapted from // by Jose M. Piñeiro // // -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor.Imaging.Formats { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; using System.Text; using ImageProcessor.Common.Exceptions; /// /// Provides the necessary information to support webp images. /// Adapted from /// by Jose M. Piñeiro /// [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")] public class WebPFormat : FormatBase { /// /// Whether the process is running in 64bit mode. Used for calling the correct dllimport method. /// Clunky I know but I couldn't get dynamic methods to work. /// private static readonly bool Is64Bit = Environment.Is64BitProcess; /// /// Gets the file headers. /// public override byte[][] FileHeaders { get { return new[] { Encoding.ASCII.GetBytes("RIFF") }; } } /// /// Gets the list of file extensions. /// public override string[] FileExtensions { get { return new[] { "webp" }; } } /// /// Gets the standard identifier used on the Internet to indicate the type of data that a file contains. /// public override string MimeType { get { return "image/webp"; } } /// /// Gets the file format of the image. /// public override ImageFormat ImageFormat { get { return null; } } /// /// Applies the given processor the current image. /// /// The processor delegate. /// The . public override void ApplyProcessor(Func processor, ImageFactory factory) { base.ApplyProcessor(processor, factory); // Set the property item information from any Exif metadata. // We do this here so that they can be changed between processor methods. if (factory.PreserveExifData) { foreach (KeyValuePair propertItem in factory.ExifPropertyItems) { factory.Image.SetPropertyItem(propertItem.Value); } } } /// /// Decodes the image to process. /// /// /// The containing the image information. /// /// /// The . /// public override Image Load(Stream stream) { byte[] bytes = new byte[stream.Length]; stream.Read(bytes, 0, bytes.Length); return Decode(bytes); } /// /// Saves the current image to the specified file path. /// /// The path to save the image to. /// The /// to save. /// /// The . /// public override Image Save(string path, Image image) { byte[] bytes; // Encode in webP format. if (EncodeLossly((Bitmap)image, this.Quality, out bytes)) { File.WriteAllBytes(path, bytes); } return image; } /// /// Saves the current image to the specified output stream. /// /// The to save the image information to. /// The to save. /// /// The . /// public override Image Save(Stream stream, Image image) { byte[] bytes; // Encode in webP format. if (EncodeLossly((Bitmap)image, this.Quality, out bytes)) { using (MemoryStream memoryStream = new MemoryStream(bytes)) { memoryStream.CopyTo(stream); memoryStream.Position = stream.Position = 0; } } else { throw new ImageFormatException("Unable to encode WebP image."); } return image; } /// /// Decodes a WebP image /// /// /// The data to uncompress /// /// /// The . /// private static Bitmap Decode(byte[] webpData) { // Get the image width and height GCHandle pinnedWebP = GCHandle.Alloc(webpData, GCHandleType.Pinned); IntPtr ptrData = pinnedWebP.AddrOfPinnedObject(); uint dataSize = (uint)webpData.Length; Bitmap bitmap = null; BitmapData bitmapData = null; IntPtr outputBuffer = IntPtr.Zero; int width; int height; if (Is64Bit) { if (NativeMethods.WebPGetInfo64(ptrData, dataSize, out width, out height) != 1) { throw new ImageFormatException("WebP image header is corrupted."); } } else { if (NativeMethods.WebPGetInfo86(ptrData, dataSize, out width, out height) != 1) { throw new ImageFormatException("WebP image header is corrupted."); } } try { // Create a BitmapData and Lock all pixels to be written bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb); bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat); // Allocate memory for uncompress image int outputBufferSize = bitmapData.Stride * height; outputBuffer = Marshal.AllocHGlobal(outputBufferSize); // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression if (Is64Bit) { // Uncompress the image outputBuffer = NativeMethods.WebPDecodeBGRAInto64(ptrData, dataSize, outputBuffer, outputBufferSize, bitmapData.Stride); } else { // Uncompress the image outputBuffer = NativeMethods.WebPDecodeBGRAInto86(ptrData, dataSize, outputBuffer, outputBufferSize, bitmapData.Stride); } // Write image to bitmap using Marshal byte[] buffer = new byte[outputBufferSize]; Marshal.Copy(outputBuffer, buffer, 0, outputBufferSize); Marshal.Copy(buffer, 0, bitmapData.Scan0, outputBufferSize); } finally { // Unlock the pixels if (bitmap != null) { bitmap.UnlockBits(bitmapData); } // Free memory pinnedWebP.Free(); Marshal.FreeHGlobal(outputBuffer); } return bitmap; } /// /// Lossy encodes the image in bitmap. /// /// /// Bitmap with the image /// /// /// Quality. 0 = minimum ... 100 = maximum quality /// /// /// The byte array containing the encoded image data. /// /// /// True if success; False otherwise /// private static bool EncodeLossly(Bitmap bitmap, int quality, out byte[] webpData) { webpData = null; BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); IntPtr unmanagedData = IntPtr.Zero; bool encoded; try { int size; // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression if (Is64Bit) { // Attempt to lossy encode the image. size = NativeMethods.WebPEncodeBGRA64(bmpData.Scan0, bitmap.Width, bitmap.Height, bmpData.Stride, quality, out unmanagedData); } else { // Attempt to lossy encode the image. size = NativeMethods.WebPEncodeBGRA86(bmpData.Scan0, bitmap.Width, bitmap.Height, bmpData.Stride, quality, out unmanagedData); } // Copy image compress data to output array webpData = new byte[size]; Marshal.Copy(unmanagedData, webpData, 0, size); encoded = true; } catch { encoded = false; } finally { // Unlock the pixels bitmap.UnlockBits(bmpData); if (Is64Bit) { // Free memory NativeMethods.WebPFree64(unmanagedData); } else { // Free memory NativeMethods.WebPFree86(unmanagedData); } } return encoded; } } }