From 1c609a3d6a04de46490a486cb7a307f167b56ee3 Mon Sep 17 00:00:00 2001 From: trawzified <55751269+tr4wzified@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:15:19 +0100 Subject: [PATCH] Fix WebP images not loading on Windows installations without WebP extensions installed --- Wabbajack.App.Wpf/Util/UIUtils.cs | 39 +- Wabbajack.App.Wpf/Util/WebPWrapper.cs | 1976 +++++++++++++++++ .../ViewModels/Gallery/ModListGalleryVM.cs | 12 +- .../ViewModels/Gallery/ModListMetadataVM.cs | 2 +- .../Views/ModListGalleryView.xaml | 39 +- Wabbajack.App.Wpf/Views/ModListTileView.xaml | 338 ++- .../Views/ModListTileView.xaml.cs | 38 +- Wabbajack.App.Wpf/Wabbajack.App.Wpf.csproj | 1 + 8 files changed, 2239 insertions(+), 206 deletions(-) create mode 100644 Wabbajack.App.Wpf/Util/WebPWrapper.cs diff --git a/Wabbajack.App.Wpf/Util/UIUtils.cs b/Wabbajack.App.Wpf/Util/UIUtils.cs index 152de857..df8aba19 100644 --- a/Wabbajack.App.Wpf/Util/UIUtils.cs +++ b/Wabbajack.App.Wpf/Util/UIUtils.cs @@ -19,6 +19,9 @@ using Wabbajack.Extensions; using Wabbajack.Models; using Wabbajack.Paths; using Wabbajack.Paths.IO; +using System.Drawing; +using Catel.IO; +using System.Drawing.Imaging; namespace Wabbajack { @@ -37,6 +40,32 @@ namespace Wabbajack return img; } + public static BitmapImage BitmapImageFromWebp(byte[] bytes, bool getThumbnail = false) + { + using(WebP webp = new()) + { + Bitmap bitmap; + if (getThumbnail) + bitmap = webp.GetThumbnailFast(bytes, 640, 360); + else + bitmap = webp.Decode(bytes); + + using(var ms = new MemoryStream()) + { + bitmap.Save(ms, ImageFormat.Png); + ms.Position = 0; + + var img = new BitmapImage(); + img.BeginInit(); + img.CacheOption = BitmapCacheOption.OnLoad; + img.StreamSource = ms; + img.EndInit(); + img.Freeze(); + return img; + } + } + } + public static bool TryGetBitmapImageFromFile(AbsolutePath path, out BitmapImage bitmapImage) { try @@ -95,7 +124,7 @@ namespace Wabbajack try { var (found, mstream) = await FindCachedImage(url); - if (found) return (ll, mstream); + if (found) return (ll, mstream, url); var ret = new MemoryStream(); using (var client = new HttpClient()) @@ -107,21 +136,21 @@ namespace Wabbajack ret.Seek(0, SeekOrigin.Begin); await WriteCachedImage(url, ret.ToArray()); - return (ll, ret); + return (ll, ret, url); } catch (Exception ex) { exceptionHandler(ex); - return (ll, default); + return (ll, default, url); } }) .Select(x => { - var (ll, memStream) = x; + var (ll, memStream, url) = x; if (memStream == null) return default; try { - return BitmapImageFromStream(memStream); + return url.EndsWith("webp", StringComparison.InvariantCultureIgnoreCase) ? BitmapImageFromWebp(memStream.ToArray(), true) : BitmapImageFromStream(memStream); } catch (Exception ex) { diff --git a/Wabbajack.App.Wpf/Util/WebPWrapper.cs b/Wabbajack.App.Wpf/Util/WebPWrapper.cs new file mode 100644 index 00000000..e0acad2b --- /dev/null +++ b/Wabbajack.App.Wpf/Util/WebPWrapper.cs @@ -0,0 +1,1976 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// Wrapper for WebP format in C#. (MIT) Jose M. Piñeiro +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// Decode Functions: +/// Bitmap Load(string pathFileName) - Load a WebP file in bitmap. +/// Bitmap Decode(byte[] rawWebP) - Decode WebP data (rawWebP) to bitmap. +/// Bitmap Decode(byte[] rawWebP, WebPDecoderOptions options) - Decode WebP data (rawWebP) to bitmap using 'options'. +/// Bitmap GetThumbnailFast(byte[] rawWebP, int width, int height) - Get a thumbnail from WebP data (rawWebP) with dimensions 'width x height'. Fast mode. +/// Bitmap GetThumbnailQuality(byte[] rawWebP, int width, int height) - Fast get a thumbnail from WebP data (rawWebP) with dimensions 'width x height'. Quality mode. +/// +/// Encode Functions: +/// Save(Bitmap bmp, string pathFileName, int quality) - Save bitmap with quality lost to WebP file. Opcionally select 'quality'. +/// byte[] EncodeLossy(Bitmap bmp, int quality) - Encode bitmap with quality lost to WebP byte array. Opcionally select 'quality'. +/// byte[] EncodeLossy(Bitmap bmp, int quality, int speed, bool info) - Encode bitmap with quality lost to WebP byte array. Select 'quality', 'speed' and optionally select 'info'. +/// byte[] EncodeLossless(Bitmap bmp) - Encode bitmap without quality lost to WebP byte array. +/// byte[] EncodeLossless(Bitmap bmp, int speed, bool info = false) - Encode bitmap without quality lost to WebP byte array. Select 'speed'. +/// byte[] EncodeNearLossless(Bitmap bmp, int quality, int speed = 9, bool info = false) - Encode bitmap with a near lossless method to WebP byte array. Select 'quality', 'speed' and optionally select 'info'. +/// +/// Another functions: +/// string GetVersion() - Get the library version +/// GetInfo(byte[] rawWebP, out int width, out int height, out bool has_alpha, out bool has_animation, out string format) - Get information of WEBP data +/// float[] PictureDistortion(Bitmap source, Bitmap reference, int metric_type) - Get PSNR, SSIM or LSIM distortion metric between two pictures +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Runtime.InteropServices; +using System.Security; +using System.Windows.Forms; + +namespace Wabbajack +{ + public sealed class WebP : IDisposable + { + private const int WEBP_MAX_DIMENSION = 16383; + #region | Public Decode Functions | + /// Read a WebP file + /// WebP file to load + /// Bitmap with the WebP image + public Bitmap Load(string pathFileName) + { + try + { + byte[] rawWebP = File.ReadAllBytes(pathFileName); + + return Decode(rawWebP); + } + catch (Exception ex) { throw new Exception(ex.Message + "\r\nIn WebP.Load"); } + } + + /// Decode a WebP image + /// The data to uncompress + /// Bitmap with the WebP image + public Bitmap Decode(byte[] rawWebP) + { + Bitmap bmp = null; + BitmapData bmpData = null; + GCHandle pinnedWebP = GCHandle.Alloc(rawWebP, GCHandleType.Pinned); + + try + { + //Get image width and height + GetInfo(rawWebP, out int imgWidth, out int imgHeight, out bool hasAlpha, out bool hasAnimation, out string format); + + //Create a BitmapData and Lock all pixels to be written + if (hasAlpha) + bmp = new Bitmap(imgWidth, imgHeight, PixelFormat.Format32bppArgb); + else + bmp = new Bitmap(imgWidth, imgHeight, PixelFormat.Format24bppRgb); + bmpData = bmp.LockBits(new Rectangle(0, 0, imgWidth, imgHeight), ImageLockMode.WriteOnly, bmp.PixelFormat); + + //Uncompress the image + int outputSize = bmpData.Stride * imgHeight; + IntPtr ptrData = pinnedWebP.AddrOfPinnedObject(); + if (bmp.PixelFormat == PixelFormat.Format24bppRgb) + UnsafeNativeMethods.WebPDecodeBGRInto(ptrData, rawWebP.Length, bmpData.Scan0, outputSize, bmpData.Stride); + else + UnsafeNativeMethods.WebPDecodeBGRAInto(ptrData, rawWebP.Length, bmpData.Scan0, outputSize, bmpData.Stride); + + return bmp; + } + catch (Exception) { throw; } + finally + { + //Unlock the pixels + if (bmpData != null) + bmp.UnlockBits(bmpData); + + //Free memory + if (pinnedWebP.IsAllocated) + pinnedWebP.Free(); + } + } + + /// Decode a WebP image + /// the data to uncompress + /// Options for advanced decode + /// Bitmap with the WebP image + public Bitmap Decode(byte[] rawWebP, WebPDecoderOptions options, PixelFormat pixelFormat = PixelFormat.DontCare) + { + GCHandle pinnedWebP = GCHandle.Alloc(rawWebP, GCHandleType.Pinned); + Bitmap bmp = null; + BitmapData bmpData = null; + VP8StatusCode result; + try + { + WebPDecoderConfig config = new WebPDecoderConfig(); + if (UnsafeNativeMethods.WebPInitDecoderConfig(ref config) == 0) + { + throw new Exception("WebPInitDecoderConfig failed. Wrong version?"); + } + // Read the .webp input file information + IntPtr ptrRawWebP = pinnedWebP.AddrOfPinnedObject(); + int height; + int width; + if (options.use_scaling == 0) + { + result = UnsafeNativeMethods.WebPGetFeatures(ptrRawWebP, rawWebP.Length, ref config.input); + if (result != VP8StatusCode.VP8_STATUS_OK) + throw new Exception("Failed WebPGetFeatures with error " + result); + + //Test cropping values + if (options.use_cropping == 1) + { + if (options.crop_left + options.crop_width > config.input.Width || options.crop_top + options.crop_height > config.input.Height) + throw new Exception("Crop options exceeded WebP image dimensions"); + width = options.crop_width; + height = options.crop_height; + } + } + else + { + width = options.scaled_width; + height = options.scaled_height; + } + + config.options.bypass_filtering = options.bypass_filtering; + config.options.no_fancy_upsampling = options.no_fancy_upsampling; + config.options.use_cropping = options.use_cropping; + config.options.crop_left = options.crop_left; + config.options.crop_top = options.crop_top; + config.options.crop_width = options.crop_width; + config.options.crop_height = options.crop_height; + config.options.use_scaling = options.use_scaling; + config.options.scaled_width = options.scaled_width; + config.options.scaled_height = options.scaled_height; + config.options.use_threads = options.use_threads; + config.options.dithering_strength = options.dithering_strength; + config.options.flip = options.flip; + config.options.alpha_dithering_strength = options.alpha_dithering_strength; + + //Create a BitmapData and Lock all pixels to be written + if (config.input.Has_alpha == 1) + { + config.output.colorspace = WEBP_CSP_MODE.MODE_bgrA; + bmp = new Bitmap(config.input.Width, config.input.Height, PixelFormat.Format32bppArgb); + } + else + { + config.output.colorspace = WEBP_CSP_MODE.MODE_BGR; + bmp = new Bitmap(config.input.Width, config.input.Height, PixelFormat.Format24bppRgb); + } + bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat); + + // Specify the output format + config.output.u.RGBA.rgba = bmpData.Scan0; + config.output.u.RGBA.stride = bmpData.Stride; + config.output.u.RGBA.size = (UIntPtr)(bmp.Height * bmpData.Stride); + config.output.height = bmp.Height; + config.output.width = bmp.Width; + config.output.is_external_memory = 1; + + // Decode + result = UnsafeNativeMethods.WebPDecode(ptrRawWebP, rawWebP.Length, ref config); + if (result != VP8StatusCode.VP8_STATUS_OK) + { + throw new Exception("Failed WebPDecode with error " + result); + } + UnsafeNativeMethods.WebPFreeDecBuffer(ref config.output); + + return bmp; + } + catch (Exception ex) { throw new Exception(ex.Message + "\r\nIn WebP.Decode"); } + finally + { + //Unlock the pixels + if (bmpData != null) + bmp.UnlockBits(bmpData); + + //Free memory + if (pinnedWebP.IsAllocated) + pinnedWebP.Free(); + } + } + + /// Get Thumbnail from webP in mode faster/low quality + /// The data to uncompress + /// Wanted width of thumbnail + /// Wanted height of thumbnail + /// Bitmap with the WebP thumbnail in 24bpp + public Bitmap GetThumbnailFast(byte[] rawWebP, int width, int height) + { + GCHandle pinnedWebP = GCHandle.Alloc(rawWebP, GCHandleType.Pinned); + Bitmap bmp = null; + BitmapData bmpData = null; + + try + { + WebPDecoderConfig config = new WebPDecoderConfig(); + if (UnsafeNativeMethods.WebPInitDecoderConfig(ref config) == 0) + throw new Exception("WebPInitDecoderConfig failed. Wrong version?"); + + // Set up decode options + config.options.bypass_filtering = 1; + config.options.no_fancy_upsampling = 1; + config.options.use_threads = 1; + config.options.use_scaling = 1; + config.options.scaled_width = width; + config.options.scaled_height = height; + + // Create a BitmapData and Lock all pixels to be written + bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb); + bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, bmp.PixelFormat); + + // Specify the output format + config.output.colorspace = WEBP_CSP_MODE.MODE_BGR; + config.output.u.RGBA.rgba = bmpData.Scan0; + config.output.u.RGBA.stride = bmpData.Stride; + config.output.u.RGBA.size = (UIntPtr)(height * bmpData.Stride); + config.output.height = height; + config.output.width = width; + config.output.is_external_memory = 1; + + // Decode + IntPtr ptrRawWebP = pinnedWebP.AddrOfPinnedObject(); + VP8StatusCode result = UnsafeNativeMethods.WebPDecode(ptrRawWebP, rawWebP.Length, ref config); + if (result != VP8StatusCode.VP8_STATUS_OK) + throw new Exception("Failed WebPDecode with error " + result); + + UnsafeNativeMethods.WebPFreeDecBuffer(ref config.output); + + return bmp; + } + catch (Exception ex) { throw new Exception(ex.Message + "\r\nIn WebP.Thumbnail"); } + finally + { + //Unlock the pixels + if (bmpData != null) + bmp.UnlockBits(bmpData); + + //Free memory + if (pinnedWebP.IsAllocated) + pinnedWebP.Free(); + } + } + + /// Thumbnail from webP in mode slow/high quality + /// The data to uncompress + /// Wanted width of thumbnail + /// Wanted height of thumbnail + /// Bitmap with the WebP thumbnail + public Bitmap GetThumbnailQuality(byte[] rawWebP, int width, int height) + { + GCHandle pinnedWebP = GCHandle.Alloc(rawWebP, GCHandleType.Pinned); + Bitmap bmp = null; + BitmapData bmpData = null; + + try + { + WebPDecoderConfig config = new WebPDecoderConfig(); + if (UnsafeNativeMethods.WebPInitDecoderConfig(ref config) == 0) + throw new Exception("WebPInitDecoderConfig failed. Wrong version?"); + + IntPtr ptrRawWebP = pinnedWebP.AddrOfPinnedObject(); + VP8StatusCode result = UnsafeNativeMethods.WebPGetFeatures(ptrRawWebP, rawWebP.Length, ref config.input); + if (result != VP8StatusCode.VP8_STATUS_OK) + throw new Exception("Failed WebPGetFeatures with error " + result); + + // Set up decode options + config.options.bypass_filtering = 0; + config.options.no_fancy_upsampling = 0; + config.options.use_threads = 1; + config.options.use_scaling = 1; + config.options.scaled_width = width; + config.options.scaled_height = height; + + //Create a BitmapData and Lock all pixels to be written + if (config.input.Has_alpha == 1) + { + config.output.colorspace = WEBP_CSP_MODE.MODE_bgrA; + bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb); + } + else + { + config.output.colorspace = WEBP_CSP_MODE.MODE_BGR; + bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb); + } + bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, bmp.PixelFormat); + + // Specify the output format + config.output.u.RGBA.rgba = bmpData.Scan0; + config.output.u.RGBA.stride = bmpData.Stride; + config.output.u.RGBA.size = (UIntPtr)(height * bmpData.Stride); + config.output.height = height; + config.output.width = width; + config.output.is_external_memory = 1; + + // Decode + result = UnsafeNativeMethods.WebPDecode(ptrRawWebP, rawWebP.Length, ref config); + if (result != VP8StatusCode.VP8_STATUS_OK) + throw new Exception("Failed WebPDecode with error " + result); + + UnsafeNativeMethods.WebPFreeDecBuffer(ref config.output); + + return bmp; + } + catch (Exception ex) { throw new Exception(ex.Message + "\r\nIn WebP.Thumbnail"); } + finally + { + //Unlock the pixels + if (bmpData != null) + bmp.UnlockBits(bmpData); + + //Free memory + if (pinnedWebP.IsAllocated) + pinnedWebP.Free(); + } + } + #endregion + + #region | Public Encode Functions | + /// Save bitmap to file in WebP format + /// Bitmap with the WebP image + /// The file to write + /// Between 0 (lower quality, lowest file size) and 100 (highest quality, higher file size) + public void Save(Bitmap bmp, string pathFileName, int quality = 75) + { + byte[] rawWebP; + + try + { + //Encode in webP format + rawWebP = EncodeLossy(bmp, quality); + + //Write webP file + File.WriteAllBytes(pathFileName, rawWebP); + } + catch (Exception ex) { throw new Exception(ex.Message + "\r\nIn WebP.Save"); } + } + + /// Lossy encoding bitmap to WebP (Simple encoding API) + /// Bitmap with the image + /// Between 0 (lower quality, lowest file size) and 100 (highest quality, higher file size) + /// Compressed data + public byte[] EncodeLossy(Bitmap bmp, int quality = 75) + { + //test bmp + if (bmp.Width == 0 || bmp.Height == 0) + throw new ArgumentException("Bitmap contains no data.", "bmp"); + if (bmp.Width > WEBP_MAX_DIMENSION || bmp.Height > WEBP_MAX_DIMENSION) + throw new NotSupportedException("Bitmap's dimension is too large. Max is " + WEBP_MAX_DIMENSION + "x" + WEBP_MAX_DIMENSION + " pixels."); + if (bmp.PixelFormat != PixelFormat.Format24bppRgb && bmp.PixelFormat != PixelFormat.Format32bppArgb) + throw new NotSupportedException("Only support Format24bppRgb and Format32bppArgb pixelFormat."); + + BitmapData bmpData = null; + IntPtr unmanagedData = IntPtr.Zero; + + try + { + int size; + + //Get bmp data + bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat); + + //Compress the bmp data + if (bmp.PixelFormat == PixelFormat.Format24bppRgb) + size = UnsafeNativeMethods.WebPEncodeBGR(bmpData.Scan0, bmp.Width, bmp.Height, bmpData.Stride, quality, out unmanagedData); + else + size = UnsafeNativeMethods.WebPEncodeBGRA(bmpData.Scan0, bmp.Width, bmp.Height, bmpData.Stride, quality, out unmanagedData); + if (size == 0) + throw new Exception("Can´t encode WebP"); + + //Copy image compress data to output array + byte[] rawWebP = new byte[size]; + Marshal.Copy(unmanagedData, rawWebP, 0, size); + + return rawWebP; + } + catch (Exception ex) { throw new Exception(ex.Message + "\r\nIn WebP.EncodeLossly"); } + finally + { + //Unlock the pixels + if (bmpData != null) + bmp.UnlockBits(bmpData); + + //Free memory + if (unmanagedData != IntPtr.Zero) + UnsafeNativeMethods.WebPFree(unmanagedData); + } + } + + /// Lossy encoding bitmap to WebP (Advanced encoding API) + /// Bitmap with the image + /// Between 0 (lower quality, lowest file size) and 100 (highest quality, higher file size) + /// Between 0 (fastest, lowest compression) and 9 (slower, best compression) + /// Compressed data + public byte[] EncodeLossy(Bitmap bmp, int quality, int speed, bool info = false) + { + //Initialize configuration structure + WebPConfig config = new WebPConfig(); + + //Set compression parameters + if (UnsafeNativeMethods.WebPConfigInit(ref config, WebPPreset.WEBP_PRESET_DEFAULT, 75) == 0) + throw new Exception("Can´t configure preset"); + + // Add additional tuning: + config.method = speed; + if (config.method > 6) + config.method = 6; + config.quality = quality; + config.autofilter = 1; + config.pass = speed + 1; + config.segments = 4; + config.partitions = 3; + config.thread_level = 1; + config.alpha_quality = quality; + config.alpha_filtering = 2; + config.use_sharp_yuv = 1; + + if (UnsafeNativeMethods.WebPGetDecoderVersion() > 1082) //Old version does not support preprocessing 4 + { + config.preprocessing = 4; + config.use_sharp_yuv = 1; + } + else + config.preprocessing = 3; + + return AdvancedEncode(bmp, config, info); + } + + /// Lossless encoding bitmap to WebP (Simple encoding API) + /// Bitmap with the image + /// Compressed data + public byte[] EncodeLossless(Bitmap bmp) + { + //test bmp + if (bmp.Width == 0 || bmp.Height == 0) + throw new ArgumentException("Bitmap contains no data.", "bmp"); + if (bmp.Width > WEBP_MAX_DIMENSION || bmp.Height > WEBP_MAX_DIMENSION) + throw new NotSupportedException("Bitmap's dimension is too large. Max is " + WEBP_MAX_DIMENSION + "x" + WEBP_MAX_DIMENSION + " pixels."); + if (bmp.PixelFormat != PixelFormat.Format24bppRgb && bmp.PixelFormat != PixelFormat.Format32bppArgb) + throw new NotSupportedException("Only support Format24bppRgb and Format32bppArgb pixelFormat."); + + BitmapData bmpData = null; + IntPtr unmanagedData = IntPtr.Zero; + try + { + //Get bmp data + bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat); + + //Compress the bmp data + int size; + if (bmp.PixelFormat == PixelFormat.Format24bppRgb) + size = UnsafeNativeMethods.WebPEncodeLosslessBGR(bmpData.Scan0, bmp.Width, bmp.Height, bmpData.Stride, out unmanagedData); + else + size = UnsafeNativeMethods.WebPEncodeLosslessBGRA(bmpData.Scan0, bmp.Width, bmp.Height, bmpData.Stride, out unmanagedData); + + //Copy image compress data to output array + byte[] rawWebP = new byte[size]; + Marshal.Copy(unmanagedData, rawWebP, 0, size); + + return rawWebP; + } + catch (Exception ex) { throw new Exception(ex.Message + "\r\nIn WebP.EncodeLossless (Simple)"); } + finally + { + //Unlock the pixels + if (bmpData != null) + bmp.UnlockBits(bmpData); + + //Free memory + if (unmanagedData != IntPtr.Zero) + UnsafeNativeMethods.WebPFree(unmanagedData); + } + } + + /// Lossless encoding image in bitmap (Advanced encoding API) + /// Bitmap with the image + /// Between 0 (fastest, lowest compression) and 9 (slower, best compression) + /// Compressed data + public byte[] EncodeLossless(Bitmap bmp, int speed) + { + //Initialize configuration structure + WebPConfig config = new WebPConfig(); + + //Set compression parameters + if (UnsafeNativeMethods.WebPConfigInit(ref config, WebPPreset.WEBP_PRESET_DEFAULT, (speed + 1) * 10) == 0) + throw new Exception("Can´t config preset"); + + //Old version of DLL does not support info and WebPConfigLosslessPreset + if (UnsafeNativeMethods.WebPGetDecoderVersion() > 1082) + { + if (UnsafeNativeMethods.WebPConfigLosslessPreset(ref config, speed) == 0) + throw new Exception("Can´t configure lossless preset"); + } + else + { + config.lossless = 1; + config.method = speed; + if (config.method > 6) + config.method = 6; + config.quality = (speed + 1) * 10; + } + config.pass = speed + 1; + config.thread_level = 1; + config.alpha_filtering = 2; + config.use_sharp_yuv = 1; + config.exact = 0; + + return AdvancedEncode(bmp, config, false); + } + + /// Near lossless encoding image in bitmap + /// Bitmap with the image + /// Between 0 (lower quality, lowest file size) and 100 (highest quality, higher file size) + /// Between 0 (fastest, lowest compression) and 9 (slower, best compression) + /// Compress data + public byte[] EncodeNearLossless(Bitmap bmp, int quality, int speed = 9) + { + //test DLL version + if (UnsafeNativeMethods.WebPGetDecoderVersion() <= 1082) + throw new Exception("This DLL version not support EncodeNearLossless"); + + //Inicialize config struct + WebPConfig config = new WebPConfig(); + + //Set compression parameters + if (UnsafeNativeMethods.WebPConfigInit(ref config, WebPPreset.WEBP_PRESET_DEFAULT, (speed + 1) * 10) == 0) + throw new Exception("Can´t configure preset"); + if (UnsafeNativeMethods.WebPConfigLosslessPreset(ref config, speed) == 0) + throw new Exception("Can´t configure lossless preset"); + config.pass = speed + 1; + config.near_lossless = quality; + config.thread_level = 1; + config.alpha_filtering = 2; + config.use_sharp_yuv = 1; + config.exact = 0; + + return AdvancedEncode(bmp, config, false); + } + #endregion + + #region | Another Public Functions | + /// Get the libwebp version + /// Version of library + public string GetVersion() + { + try + { + uint v = (uint)UnsafeNativeMethods.WebPGetDecoderVersion(); + var revision = v % 256; + var minor = (v >> 8) % 256; + var major = (v >> 16) % 256; + return major + "." + minor + "." + revision; + } + catch (Exception ex) { throw new Exception(ex.Message + "\r\nIn WebP.GetVersion"); } + } + + /// Get info of WEBP data + /// The data of WebP + /// width of image + /// height of image + /// Image has alpha channel + /// Image is a animation + /// Format of image: 0 = undefined (/mixed), 1 = lossy, 2 = lossless + public void GetInfo(byte[] rawWebP, out int width, out int height, out bool has_alpha, out bool has_animation, out string format) + { + VP8StatusCode result; + GCHandle pinnedWebP = GCHandle.Alloc(rawWebP, GCHandleType.Pinned); + + try + { + IntPtr ptrRawWebP = pinnedWebP.AddrOfPinnedObject(); + + WebPBitstreamFeatures features = new WebPBitstreamFeatures(); + result = UnsafeNativeMethods.WebPGetFeatures(ptrRawWebP, rawWebP.Length, ref features); + + if (result != 0) + throw new Exception(result.ToString()); + + width = features.Width; + height = features.Height; + if (features.Has_alpha == 1) has_alpha = true; else has_alpha = false; + if (features.Has_animation == 1) has_animation = true; else has_animation = false; + switch (features.Format) + { + case 1: + format = "lossy"; + break; + case 2: + format = "lossless"; + break; + default: + format = "undefined"; + break; + } + } + catch (Exception ex) { throw new Exception(ex.Message + "\r\nIn WebP.GetInfo"); } + finally + { + //Free memory + if (pinnedWebP.IsAllocated) + pinnedWebP.Free(); + } + } + + /// Compute PSNR, SSIM or LSIM distortion metric between two pictures. Warning: this function is rather CPU-intensive + /// Picture to measure + /// Reference picture + /// 0 = PSNR, 1 = SSIM, 2 = LSIM + /// dB in the Y/U/V/Alpha/All order + public float[] GetPictureDistortion(Bitmap source, Bitmap reference, int metric_type) + { + WebPPicture wpicSource = new WebPPicture(); + WebPPicture wpicReference = new WebPPicture(); + BitmapData sourceBmpData = null; + BitmapData referenceBmpData = null; + float[] result = new float[5]; + GCHandle pinnedResult = GCHandle.Alloc(result, GCHandleType.Pinned); + + try + { + if (source == null) + throw new Exception("Source picture is void"); + if (reference == null) + throw new Exception("Reference picture is void"); + if (metric_type > 2) + throw new Exception("Bad metric_type. Use 0 = PSNR, 1 = SSIM, 2 = LSIM"); + if (source.Width != reference.Width || source.Height != reference.Height) + throw new Exception("Source and Reference pictures have different dimensions"); + + // Setup the source picture data, allocating the bitmap, width and height + sourceBmpData = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, source.PixelFormat); + wpicSource = new WebPPicture(); + if (UnsafeNativeMethods.WebPPictureInitInternal(ref wpicSource) != 1) + throw new Exception("Can´t initialize WebPPictureInit"); + wpicSource.width = (int)source.Width; + wpicSource.height = (int)source.Height; + + //Put the source bitmap componets in wpic + if (sourceBmpData.PixelFormat == PixelFormat.Format32bppArgb) + { + wpicSource.use_argb = 1; + if (UnsafeNativeMethods.WebPPictureImportBGRA(ref wpicSource, sourceBmpData.Scan0, sourceBmpData.Stride) != 1) + throw new Exception("Can´t allocate memory in WebPPictureImportBGR"); + } + else + { + wpicSource.use_argb = 0; + if (UnsafeNativeMethods.WebPPictureImportBGR(ref wpicSource, sourceBmpData.Scan0, sourceBmpData.Stride) != 1) + throw new Exception("Can´t allocate memory in WebPPictureImportBGR"); + } + + // Setup the reference picture data, allocating the bitmap, width and height + referenceBmpData = reference.LockBits(new Rectangle(0, 0, reference.Width, reference.Height), ImageLockMode.ReadOnly, reference.PixelFormat); + wpicReference = new WebPPicture(); + if (UnsafeNativeMethods.WebPPictureInitInternal(ref wpicReference) != 1) + throw new Exception("Can´t initialize WebPPictureInit"); + wpicReference.width = (int)reference.Width; + wpicReference.height = (int)reference.Height; + wpicReference.use_argb = 1; + + //Put the source bitmap contents in WebPPicture instance + if (sourceBmpData.PixelFormat == PixelFormat.Format32bppArgb) + { + wpicSource.use_argb = 1; + if (UnsafeNativeMethods.WebPPictureImportBGRA(ref wpicReference, referenceBmpData.Scan0, referenceBmpData.Stride) != 1) + throw new Exception("Can´t allocate memory in WebPPictureImportBGR"); + } + else + { + wpicSource.use_argb = 0; + if (UnsafeNativeMethods.WebPPictureImportBGR(ref wpicReference, referenceBmpData.Scan0, referenceBmpData.Stride) != 1) + throw new Exception("Can´t allocate memory in WebPPictureImportBGR"); + } + + //Measure + IntPtr ptrResult = pinnedResult.AddrOfPinnedObject(); + if (UnsafeNativeMethods.WebPPictureDistortion(ref wpicSource, ref wpicReference, metric_type, ptrResult) != 1) + throw new Exception("Can´t measure."); + return result; + } + catch (Exception ex) { throw new Exception(ex.Message + "\r\nIn WebP.GetPictureDistortion"); } + finally + { + //Unlock the pixels + if (sourceBmpData != null) + source.UnlockBits(sourceBmpData); + if (referenceBmpData != null) + reference.UnlockBits(referenceBmpData); + + //Free memory + if (wpicSource.argb != IntPtr.Zero) + UnsafeNativeMethods.WebPPictureFree(ref wpicSource); + if (wpicReference.argb != IntPtr.Zero) + UnsafeNativeMethods.WebPPictureFree(ref wpicReference); + //Free memory + if (pinnedResult.IsAllocated) + pinnedResult.Free(); + } + } + #endregion + + #region | Private Methods | + /// Encoding image using Advanced encoding API + /// Bitmap with the image + /// Configuration for encode + /// True if need encode info. + /// Compressed data + private byte[] AdvancedEncode(Bitmap bmp, WebPConfig config, bool info) + { + byte[] rawWebP = null; + byte[] dataWebp = null; + WebPPicture wpic = new WebPPicture(); + BitmapData bmpData = null; + WebPAuxStats stats = new WebPAuxStats(); + IntPtr ptrStats = IntPtr.Zero; + GCHandle pinnedArrayHandle = new GCHandle(); + int dataWebpSize; + try + { + //Validate the configuration + if (UnsafeNativeMethods.WebPValidateConfig(ref config) != 1) + throw new Exception("Bad configuration parameters"); + + //test bmp + if (bmp.Width == 0 || bmp.Height == 0) + throw new ArgumentException("Bitmap contains no data.", "bmp"); + if (bmp.Width > WEBP_MAX_DIMENSION || bmp.Height > WEBP_MAX_DIMENSION) + throw new NotSupportedException("Bitmap's dimension is too large. Max is " + WEBP_MAX_DIMENSION + "x" + WEBP_MAX_DIMENSION + " pixels."); + if (bmp.PixelFormat != PixelFormat.Format24bppRgb && bmp.PixelFormat != PixelFormat.Format32bppArgb) + throw new NotSupportedException("Only support Format24bppRgb and Format32bppArgb pixelFormat."); + + // Setup the input data, allocating a the bitmap, width and height + bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat); + if (UnsafeNativeMethods.WebPPictureInitInternal(ref wpic) != 1) + throw new Exception("Can´t initialize WebPPictureInit"); + wpic.width = (int)bmp.Width; + wpic.height = (int)bmp.Height; + wpic.use_argb = 1; + + if (bmp.PixelFormat == PixelFormat.Format32bppArgb) + { + //Put the bitmap componets in wpic + int result = UnsafeNativeMethods.WebPPictureImportBGRA(ref wpic, bmpData.Scan0, bmpData.Stride); + if (result != 1) + throw new Exception("Can´t allocate memory in WebPPictureImportBGRA"); + wpic.colorspace = (uint)WEBP_CSP_MODE.MODE_bgrA; + dataWebpSize = bmp.Width * bmp.Height * 32; + dataWebp = new byte[bmp.Width * bmp.Height * 32]; //Memory for WebP output + } + else + { + //Put the bitmap contents in WebPPicture instance + int result = UnsafeNativeMethods.WebPPictureImportBGR(ref wpic, bmpData.Scan0, bmpData.Stride); + if (result != 1) + throw new Exception("Can´t allocate memory in WebPPictureImportBGR"); + dataWebpSize = bmp.Width * bmp.Height * 24; + + } + + //Set up statistics of compression + if (info) + { + stats = new WebPAuxStats(); + ptrStats = Marshal.AllocHGlobal(Marshal.SizeOf(stats)); + Marshal.StructureToPtr(stats, ptrStats, false); + wpic.stats = ptrStats; + } + + //Memory for WebP output + if (dataWebpSize > 2147483591) + dataWebpSize = 2147483591; + dataWebp = new byte[bmp.Width * bmp.Height * 32]; + pinnedArrayHandle = GCHandle.Alloc(dataWebp, GCHandleType.Pinned); + IntPtr initPtr = pinnedArrayHandle.AddrOfPinnedObject(); + wpic.custom_ptr = initPtr; + + //Set up a byte-writing method (write-to-memory, in this case) + UnsafeNativeMethods.OnCallback = new UnsafeNativeMethods.WebPMemoryWrite(MyWriter); + wpic.writer = Marshal.GetFunctionPointerForDelegate(UnsafeNativeMethods.OnCallback); + + //compress the input samples + if (UnsafeNativeMethods.WebPEncode(ref config, ref wpic) != 1) + throw new Exception("Encoding error: " + ((WebPEncodingError)wpic.error_code).ToString()); + + //Remove OnCallback + UnsafeNativeMethods.OnCallback = null; + + //Unlock the pixels + bmp.UnlockBits(bmpData); + bmpData = null; + + //Copy webpData to rawWebP + int size = (int)((long)wpic.custom_ptr - (long)initPtr); + rawWebP = new byte[size]; + Array.Copy(dataWebp, rawWebP, size); + + //Remove compression data + pinnedArrayHandle.Free(); + dataWebp = null; + + //Show statistics + if (info) + { + stats = (WebPAuxStats)Marshal.PtrToStructure(ptrStats, typeof(WebPAuxStats)); + MessageBox.Show("Dimension: " + wpic.width + " x " + wpic.height + " pixels\n" + + "Output: " + stats.coded_size + " bytes\n" + + "PSNR Y: " + stats.PSNRY + " db\n" + + "PSNR u: " + stats.PSNRU + " db\n" + + "PSNR v: " + stats.PSNRV + " db\n" + + "PSNR ALL: " + stats.PSNRALL + " db\n" + + "Block intra4: " + stats.block_count_intra4 + "\n" + + "Block intra16: " + stats.block_count_intra16 + "\n" + + "Block skipped: " + stats.block_count_skipped + "\n" + + "Header size: " + stats.header_bytes + " bytes\n" + + "Mode-partition: " + stats.mode_partition_0 + " bytes\n" + + "Macro-blocks 0: " + stats.segment_size_segments0 + " residuals bytes\n" + + "Macro-blocks 1: " + stats.segment_size_segments1 + " residuals bytes\n" + + "Macro-blocks 2: " + stats.segment_size_segments2 + " residuals bytes\n" + + "Macro-blocks 3: " + stats.segment_size_segments3 + " residuals bytes\n" + + "Quantizer 0: " + stats.segment_quant_segments0 + " residuals bytes\n" + + "Quantizer 1: " + stats.segment_quant_segments1 + " residuals bytes\n" + + "Quantizer 2: " + stats.segment_quant_segments2 + " residuals bytes\n" + + "Quantizer 3: " + stats.segment_quant_segments3 + " residuals bytes\n" + + "Filter level 0: " + stats.segment_level_segments0 + " residuals bytes\n" + + "Filter level 1: " + stats.segment_level_segments1 + " residuals bytes\n" + + "Filter level 2: " + stats.segment_level_segments2 + " residuals bytes\n" + + "Filter level 3: " + stats.segment_level_segments3 + " residuals bytes\n", "Compression statistics"); + } + + return rawWebP; + } + catch (Exception ex) { throw new Exception(ex.Message + "\r\nIn WebP.AdvancedEncode"); } + finally + { + //Free temporal compress memory + if (pinnedArrayHandle.IsAllocated) + { + pinnedArrayHandle.Free(); + } + + //Free statistics memory + if (ptrStats != IntPtr.Zero) + { + Marshal.FreeHGlobal(ptrStats); + } + + //Unlock the pixels + if (bmpData != null) + { + bmp.UnlockBits(bmpData); + } + + //Free memory + if (wpic.argb != IntPtr.Zero) + { + UnsafeNativeMethods.WebPPictureFree(ref wpic); + } + } + } + + private int MyWriter([InAttribute()] IntPtr data, UIntPtr data_size, ref WebPPicture picture) + { + UnsafeNativeMethods.CopyMemory(picture.custom_ptr, data, (uint)data_size); + //picture.custom_ptr = IntPtr.Add(picture.custom_ptr, (int)data_size); //Only in .NET > 4.0 + picture.custom_ptr = new IntPtr(picture.custom_ptr.ToInt64() + (int)data_size); + return 1; + } + + private delegate int MyWriterDelegate([InAttribute()] IntPtr data, UIntPtr data_size, ref WebPPicture picture); + #endregion + + #region | Destruction | + /// Free memory + public void Dispose() + { + GC.SuppressFinalize(this); + } + #endregion + } + + #region | Import libwebp functions | + [SuppressUnmanagedCodeSecurityAttribute] + internal sealed partial class UnsafeNativeMethods + { + + [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)] + internal static extern void CopyMemory(IntPtr dest, IntPtr src, uint count); + + private static readonly int WEBP_DECODER_ABI_VERSION = 0x0208; + + /// This function will initialize the configuration according to a predefined set of parameters (referred to by 'preset') and a given quality factor + /// The WebPConfig structure + /// Type of image + /// Quality of compression + /// 0 if error + internal static int WebPConfigInit(ref WebPConfig config, WebPPreset preset, float quality) + { + switch (IntPtr.Size) + { + case 4: + return WebPConfigInitInternal_x86(ref config, preset, quality, WEBP_DECODER_ABI_VERSION); + case 8: + return WebPConfigInitInternal_x64(ref config, preset, quality, WEBP_DECODER_ABI_VERSION); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPConfigInitInternal")] + private static extern int WebPConfigInitInternal_x86(ref WebPConfig config, WebPPreset preset, float quality, int WEBP_DECODER_ABI_VERSION); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPConfigInitInternal")] + private static extern int WebPConfigInitInternal_x64(ref WebPConfig config, WebPPreset preset, float quality, int WEBP_DECODER_ABI_VERSION); + + /// Get info of WepP image + /// Bytes[] of WebP image + /// Size of rawWebP + /// Features of WebP image + /// VP8StatusCode + internal static VP8StatusCode WebPGetFeatures(IntPtr rawWebP, int data_size, ref WebPBitstreamFeatures features) + { + switch (IntPtr.Size) + { + case 4: + return WebPGetFeaturesInternal_x86(rawWebP, (UIntPtr)data_size, ref features, WEBP_DECODER_ABI_VERSION); + case 8: + return WebPGetFeaturesInternal_x64(rawWebP, (UIntPtr)data_size, ref features, WEBP_DECODER_ABI_VERSION); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPGetFeaturesInternal")] + private static extern VP8StatusCode WebPGetFeaturesInternal_x86([InAttribute()] IntPtr rawWebP, UIntPtr data_size, ref WebPBitstreamFeatures features, int WEBP_DECODER_ABI_VERSION); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPGetFeaturesInternal")] + private static extern VP8StatusCode WebPGetFeaturesInternal_x64([InAttribute()] IntPtr rawWebP, UIntPtr data_size, ref WebPBitstreamFeatures features, int WEBP_DECODER_ABI_VERSION); + + /// Activate the lossless compression mode with the desired efficiency + /// The WebPConfig struct + /// between 0 (fastest, lowest compression) and 9 (slower, best compression) + /// 0 in case of parameter error + internal static int WebPConfigLosslessPreset(ref WebPConfig config, int level) + { + switch (IntPtr.Size) + { + case 4: + return WebPConfigLosslessPreset_x86(ref config, level); + case 8: + return WebPConfigLosslessPreset_x64(ref config, level); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPConfigLosslessPreset")] + private static extern int WebPConfigLosslessPreset_x86(ref WebPConfig config, int level); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPConfigLosslessPreset")] + private static extern int WebPConfigLosslessPreset_x64(ref WebPConfig config, int level); + + /// Check that configuration is non-NULL and all configuration parameters are within their valid ranges + /// The WebPConfig structure + /// 1 if configuration is OK + internal static int WebPValidateConfig(ref WebPConfig config) + { + switch (IntPtr.Size) + { + case 4: + return WebPValidateConfig_x86(ref config); + case 8: + return WebPValidateConfig_x64(ref config); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPValidateConfig")] + private static extern int WebPValidateConfig_x86(ref WebPConfig config); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPValidateConfig")] + private static extern int WebPValidateConfig_x64(ref WebPConfig config); + + /// Initialize the WebPPicture structure checking the DLL version + /// The WebPPicture structure + /// 1 if not error + internal static int WebPPictureInitInternal(ref WebPPicture wpic) + { + switch (IntPtr.Size) + { + case 4: + return WebPPictureInitInternal_x86(ref wpic, WEBP_DECODER_ABI_VERSION); + case 8: + return WebPPictureInitInternal_x64(ref wpic, WEBP_DECODER_ABI_VERSION); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureInitInternal")] + private static extern int WebPPictureInitInternal_x86(ref WebPPicture wpic, int WEBP_DECODER_ABI_VERSION); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureInitInternal")] + private static extern int WebPPictureInitInternal_x64(ref WebPPicture wpic, int WEBP_DECODER_ABI_VERSION); + + /// Colorspace conversion function to import RGB samples + /// The WebPPicture structure + /// Point to BGR data + /// stride of BGR data + /// Returns 0 in case of memory error. + internal static int WebPPictureImportBGR(ref WebPPicture wpic, IntPtr bgr, int stride) + { + switch (IntPtr.Size) + { + case 4: + return WebPPictureImportBGR_x86(ref wpic, bgr, stride); + case 8: + return WebPPictureImportBGR_x64(ref wpic, bgr, stride); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureImportBGR")] + private static extern int WebPPictureImportBGR_x86(ref WebPPicture wpic, IntPtr bgr, int stride); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureImportBGR")] + private static extern int WebPPictureImportBGR_x64(ref WebPPicture wpic, IntPtr bgr, int stride); + + /// Color-space conversion function to import RGB samples + /// The WebPPicture structure + /// Point to BGRA data + /// stride of BGRA data + /// Returns 0 in case of memory error. + internal static int WebPPictureImportBGRA(ref WebPPicture wpic, IntPtr bgra, int stride) + { + switch (IntPtr.Size) + { + case 4: + return WebPPictureImportBGRA_x86(ref wpic, bgra, stride); + case 8: + return WebPPictureImportBGRA_x64(ref wpic, bgra, stride); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureImportBGRA")] + private static extern int WebPPictureImportBGRA_x86(ref WebPPicture wpic, IntPtr bgra, int stride); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureImportBGRA")] + private static extern int WebPPictureImportBGRA_x64(ref WebPPicture wpic, IntPtr bgra, int stride); + + /// Color-space conversion function to import RGB samples + /// The WebPPicture structure + /// Point to BGR data + /// stride of BGR data + /// Returns 0 in case of memory error. + internal static int WebPPictureImportBGRX(ref WebPPicture wpic, IntPtr bgr, int stride) + { + switch (IntPtr.Size) + { + case 4: + return WebPPictureImportBGRX_x86(ref wpic, bgr, stride); + case 8: + return WebPPictureImportBGRX_x64(ref wpic, bgr, stride); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureImportBGRX")] + private static extern int WebPPictureImportBGRX_x86(ref WebPPicture wpic, IntPtr bgr, int stride); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureImportBGRX")] + private static extern int WebPPictureImportBGRX_x64(ref WebPPicture wpic, IntPtr bgr, int stride); + + /// The writer type for output compress data + /// Data returned + /// Size of data returned + /// Picture structure + /// + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int WebPMemoryWrite([In()] IntPtr data, UIntPtr data_size, ref WebPPicture wpic); + internal static WebPMemoryWrite OnCallback; + + /// Compress to WebP format + /// The configuration structure for compression parameters + /// 'picture' hold the source samples in both YUV(A) or ARGB input + /// Returns 0 in case of error, 1 otherwise. In case of error, picture->error_code is updated accordingly. + internal static int WebPEncode(ref WebPConfig config, ref WebPPicture picture) + { + switch (IntPtr.Size) + { + case 4: + return WebPEncode_x86(ref config, ref picture); + case 8: + return WebPEncode_x64(ref config, ref picture); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncode")] + private static extern int WebPEncode_x86(ref WebPConfig config, ref WebPPicture picture); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncode")] + private static extern int WebPEncode_x64(ref WebPConfig config, ref WebPPicture picture); + + /// Release the memory allocated by WebPPictureAlloc() or WebPPictureImport*() + /// Note that this function does _not_ free the memory used by the 'picture' object itself. + /// Besides memory (which is reclaimed) all other fields of 'picture' are preserved + /// Picture structure + internal static void WebPPictureFree(ref WebPPicture picture) + { + switch (IntPtr.Size) + { + case 4: + WebPPictureFree_x86(ref picture); + break; + case 8: + WebPPictureFree_x64(ref picture); + break; + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureFree")] + private static extern void WebPPictureFree_x86(ref WebPPicture wpic); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureFree")] + private static extern void WebPPictureFree_x64(ref WebPPicture wpic); + + /// Validate the WebP image header and retrieve the image height and width. Pointers *width and *height can be passed NULL if deemed irrelevant + /// Pointer to WebP image data + /// This is the size of the memory block pointed to by data containing the image data + /// The range is limited currently from 1 to 16383 + /// The range is limited currently from 1 to 16383 + /// 1 if success, otherwise error code returned in the case of (a) formatting error(s). + internal static int WebPGetInfo(IntPtr data, int data_size, out int width, out int height) + { + switch (IntPtr.Size) + { + case 4: + return WebPGetInfo_x86(data, (UIntPtr)data_size, out width, out height); + case 8: + return WebPGetInfo_x64(data, (UIntPtr)data_size, out width, out height); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPGetInfo")] + private static extern int WebPGetInfo_x86([InAttribute()] IntPtr data, UIntPtr data_size, out int width, out int height); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPGetInfo")] + private static extern int WebPGetInfo_x64([InAttribute()] IntPtr data, UIntPtr data_size, out int width, out int height); + + /// Decode WEBP image pointed to by *data and returns BGR samples into a preallocated buffer + /// Pointer to WebP image data + /// This is the size of the memory block pointed to by data containing the image data + /// Pointer to decoded WebP image + /// Size of allocated buffer + /// Specifies the distance between scan lines + internal static void WebPDecodeBGRInto(IntPtr data, int data_size, IntPtr output_buffer, int output_buffer_size, int output_stride) + { + switch (IntPtr.Size) + { + case 4: + if (WebPDecodeBGRInto_x86(data, (UIntPtr)data_size, output_buffer, output_buffer_size, output_stride) == null) + throw new InvalidOperationException("Can not decode WebP"); + break; + case 8: + if (WebPDecodeBGRInto_x64(data, (UIntPtr)data_size, output_buffer, output_buffer_size, output_stride) == null) + throw new InvalidOperationException("Can not decode WebP"); + break; + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPDecodeBGRInto")] + private static extern IntPtr WebPDecodeBGRInto_x86([InAttribute()] IntPtr data, UIntPtr data_size, IntPtr output_buffer, int output_buffer_size, int output_stride); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPDecodeBGRInto")] + private static extern IntPtr WebPDecodeBGRInto_x64([InAttribute()] IntPtr data, UIntPtr data_size, IntPtr output_buffer, int output_buffer_size, int output_stride); + + /// Decode WEBP image pointed to by *data and returns BGRA samples into a preallocated buffer + /// Pointer to WebP image data + /// This is the size of the memory block pointed to by data containing the image data + /// Pointer to decoded WebP image + /// Size of allocated buffer + /// Specifies the distance between scan lines + internal static void WebPDecodeBGRAInto(IntPtr data, int data_size, IntPtr output_buffer, int output_buffer_size, int output_stride) + { + switch (IntPtr.Size) + { + case 4: + if (WebPDecodeBGRAInto_x86(data, (UIntPtr)data_size, output_buffer, output_buffer_size, output_stride) == null) + throw new InvalidOperationException("Can not decode WebP"); + break; + case 8: + if (WebPDecodeBGRAInto_x64(data, (UIntPtr)data_size, output_buffer, output_buffer_size, output_stride) == null) + throw new InvalidOperationException("Can not decode WebP"); + break; + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPDecodeBGRAInto")] + private static extern IntPtr WebPDecodeBGRAInto_x86([InAttribute()] IntPtr data, UIntPtr data_size, IntPtr output_buffer, int output_buffer_size, int output_stride); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPDecodeBGRAInto")] + private static extern IntPtr WebPDecodeBGRAInto_x64([InAttribute()] IntPtr data, UIntPtr data_size, IntPtr output_buffer, int output_buffer_size, int output_stride); + + /// Decode WEBP image pointed to by *data and returns ARGB samples into a preallocated buffer + /// Pointer to WebP image data + /// This is the size of the memory block pointed to by data containing the image data + /// Pointer to decoded WebP image + /// Size of allocated buffer + /// Specifies the distance between scan lines + internal static void WebPDecodeARGBInto(IntPtr data, int data_size, IntPtr output_buffer, int output_buffer_size, int output_stride) + { + switch (IntPtr.Size) + { + case 4: + if (WebPDecodeARGBInto_x86(data, (UIntPtr)data_size, output_buffer, output_buffer_size, output_stride) == null) + throw new InvalidOperationException("Can not decode WebP"); + break; + case 8: + if (WebPDecodeARGBInto_x64(data, (UIntPtr)data_size, output_buffer, output_buffer_size, output_stride) == null) + throw new InvalidOperationException("Can not decode WebP"); + break; + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPDecodeARGBInto")] + private static extern IntPtr WebPDecodeARGBInto_x86([InAttribute()] IntPtr data, UIntPtr data_size, IntPtr output_buffer, int output_buffer_size, int output_stride); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPDecodeARGBInto")] + private static extern IntPtr WebPDecodeARGBInto_x64([InAttribute()] IntPtr data, UIntPtr data_size, IntPtr output_buffer, int output_buffer_size, int output_stride); + + /// Initialize the configuration as empty. This function must always be called first, unless WebPGetFeatures() is to be called + /// Configuration structure + /// False in case of mismatched version. + internal static int WebPInitDecoderConfig(ref WebPDecoderConfig webPDecoderConfig) + { + switch (IntPtr.Size) + { + case 4: + return WebPInitDecoderConfigInternal_x86(ref webPDecoderConfig, WEBP_DECODER_ABI_VERSION); + case 8: + return WebPInitDecoderConfigInternal_x64(ref webPDecoderConfig, WEBP_DECODER_ABI_VERSION); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPInitDecoderConfigInternal")] + private static extern int WebPInitDecoderConfigInternal_x86(ref WebPDecoderConfig webPDecoderConfig, int WEBP_DECODER_ABI_VERSION); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPInitDecoderConfigInternal")] + private static extern int WebPInitDecoderConfigInternal_x64(ref WebPDecoderConfig webPDecoderConfig, int WEBP_DECODER_ABI_VERSION); + + /// Decodes the full data at once, taking configuration into account + /// WebP raw data to decode + /// Size of WebP data + /// Configuration structure + /// VP8_STATUS_OK if the decoding was successful + internal static VP8StatusCode WebPDecode(IntPtr data, int data_size, ref WebPDecoderConfig webPDecoderConfig) + { + switch (IntPtr.Size) + { + case 4: + return WebPDecode_x86(data, (UIntPtr)data_size, ref webPDecoderConfig); + case 8: + return WebPDecode_x64(data, (UIntPtr)data_size, ref webPDecoderConfig); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPDecode")] + private static extern VP8StatusCode WebPDecode_x86(IntPtr data, UIntPtr data_size, ref WebPDecoderConfig config); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPDecode")] + private static extern VP8StatusCode WebPDecode_x64(IntPtr data, UIntPtr data_size, ref WebPDecoderConfig config); + + /// Free any memory associated with the buffer. Must always be called last. Doesn't free the 'buffer' structure itself + /// WebPDecBuffer + internal static void WebPFreeDecBuffer(ref WebPDecBuffer buffer) + { + switch (IntPtr.Size) + { + case 4: + WebPFreeDecBuffer_x86(ref buffer); + break; + case 8: + WebPFreeDecBuffer_x64(ref buffer); + break; + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPFreeDecBuffer")] + private static extern void WebPFreeDecBuffer_x86(ref WebPDecBuffer buffer); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPFreeDecBuffer")] + private static extern void WebPFreeDecBuffer_x64(ref WebPDecBuffer buffer); + + /// Lossy encoding images + /// Pointer to BGR image data + /// The range is limited currently from 1 to 16383 + /// The range is limited currently from 1 to 16383 + /// Specifies the distance between scanlines + /// Ranges from 0 (lower quality) to 100 (highest quality). Controls the loss and quality during compression + /// output_buffer with WebP image + /// Size of WebP Image or 0 if an error occurred + internal static int WebPEncodeBGR(IntPtr bgr, int width, int height, int stride, float quality_factor, out IntPtr output) + { + switch (IntPtr.Size) + { + case 4: + return WebPEncodeBGR_x86(bgr, width, height, stride, quality_factor, out output); + case 8: + return WebPEncodeBGR_x64(bgr, width, height, stride, quality_factor, out output); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncodeBGR")] + private static extern int WebPEncodeBGR_x86([InAttribute()] IntPtr bgr, int width, int height, int stride, float quality_factor, out IntPtr output); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncodeBGR")] + private static extern int WebPEncodeBGR_x64([InAttribute()] IntPtr bgr, int width, int height, int stride, float quality_factor, out IntPtr output); + + /// Lossy encoding images + /// Pointer to BGRA image data + /// The range is limited currently from 1 to 16383 + /// The range is limited currently from 1 to 16383 + /// Specifies the distance between scan lines + /// Ranges from 0 (lower quality) to 100 (highest quality). Controls the loss and quality during compression + /// output_buffer with WebP image + /// Size of WebP Image or 0 if an error occurred + internal static int WebPEncodeBGRA(IntPtr bgra, int width, int height, int stride, float quality_factor, out IntPtr output) + { + switch (IntPtr.Size) + { + case 4: + return WebPEncodeBGRA_x86(bgra, width, height, stride, quality_factor, out output); + case 8: + return WebPEncodeBGRA_x64(bgra, width, height, stride, quality_factor, out output); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncodeBGRA")] + private static extern int WebPEncodeBGRA_x86([InAttribute()] IntPtr bgra, int width, int height, int stride, float quality_factor, out IntPtr output); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncodeBGRA")] + private static extern int WebPEncodeBGRA_x64([InAttribute()] IntPtr bgra, int width, int height, int stride, float quality_factor, out IntPtr output); + + /// Lossless encoding images pointed to by *data in WebP format + /// Pointer to BGR image data + /// The range is limited currently from 1 to 16383 + /// The range is limited currently from 1 to 16383 + /// Specifies the distance between scan lines + /// output_buffer with WebP image + /// Size of WebP Image or 0 if an error occurred + internal static int WebPEncodeLosslessBGR(IntPtr bgr, int width, int height, int stride, out IntPtr output) + { + switch (IntPtr.Size) + { + case 4: + return WebPEncodeLosslessBGR_x86(bgr, width, height, stride, out output); + case 8: + return WebPEncodeLosslessBGR_x64(bgr, width, height, stride, out output); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncodeLosslessBGR")] + private static extern int WebPEncodeLosslessBGR_x86([InAttribute()] IntPtr bgr, int width, int height, int stride, out IntPtr output); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncodeLosslessBGR")] + private static extern int WebPEncodeLosslessBGR_x64([InAttribute()] IntPtr bgr, int width, int height, int stride, out IntPtr output); + + /// Lossless encoding images pointed to by *data in WebP format + /// Pointer to BGRA image data + /// The range is limited currently from 1 to 16383 + /// The range is limited currently from 1 to 16383 + /// Specifies the distance between scan lines + /// output_buffer with WebP image + /// Size of WebP Image or 0 if an error occurred + internal static int WebPEncodeLosslessBGRA(IntPtr bgra, int width, int height, int stride, out IntPtr output) + { + switch (IntPtr.Size) + { + case 4: + return WebPEncodeLosslessBGRA_x86(bgra, width, height, stride, out output); + case 8: + return WebPEncodeLosslessBGRA_x64(bgra, width, height, stride, out output); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncodeLosslessBGRA")] + private static extern int WebPEncodeLosslessBGRA_x86([InAttribute()] IntPtr bgra, int width, int height, int stride, out IntPtr output); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncodeLosslessBGRA")] + private static extern int WebPEncodeLosslessBGRA_x64([InAttribute()] IntPtr bgra, int width, int height, int stride, out IntPtr output); + + /// Releases memory returned by the WebPEncode + /// Pointer to memory + internal static void WebPFree(IntPtr p) + { + switch (IntPtr.Size) + { + case 4: + WebPFree_x86(p); + break; + case 8: + WebPFree_x64(p); + break; + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPFree")] + private static extern void WebPFree_x86(IntPtr p); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPFree")] + private static extern void WebPFree_x64(IntPtr p); + + /// Get the WebP version library + /// 8bits for each of major/minor/revision packet in integer. E.g: v2.5.7 is 0x020507 + internal static int WebPGetDecoderVersion() + { + switch (IntPtr.Size) + { + case 4: + return WebPGetDecoderVersion_x86(); + case 8: + return WebPGetDecoderVersion_x64(); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPGetDecoderVersion")] + private static extern int WebPGetDecoderVersion_x86(); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPGetDecoderVersion")] + private static extern int WebPGetDecoderVersion_x64(); + + /// Compute PSNR, SSIM or LSIM distortion metric between two pictures + /// Picture to measure + /// Reference picture + /// 0 = PSNR, 1 = SSIM, 2 = LSIM + /// dB in the Y/U/V/Alpha/All order + /// False in case of error (the two pictures don't have same dimension, ...) + internal static int WebPPictureDistortion(ref WebPPicture srcPicture, ref WebPPicture refPicture, int metric_type, IntPtr pResult) + { + switch (IntPtr.Size) + { + case 4: + return WebPPictureDistortion_x86(ref srcPicture, ref refPicture, metric_type, pResult); + case 8: + return WebPPictureDistortion_x64(ref srcPicture, ref refPicture, metric_type, pResult); + default: + throw new InvalidOperationException("Invalid platform. Can not find proper function"); + } + } + [DllImport("libwebp_x86.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureDistortion")] + private static extern int WebPPictureDistortion_x86(ref WebPPicture srcPicture, ref WebPPicture refPicture, int metric_type, IntPtr pResult); + [DllImport("libwebp_x64.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureDistortion")] + private static extern int WebPPictureDistortion_x64(ref WebPPicture srcPicture, ref WebPPicture refPicture, int metric_type, IntPtr pResult); + } + #endregion + + #region | Predefined | + /// Enumerate some predefined settings for WebPConfig, depending on the type of source picture. These presets are used when calling WebPConfigPreset() + internal enum WebPPreset + { + /// Default preset + WEBP_PRESET_DEFAULT = 0, + /// Digital picture, like portrait, inner shot + WEBP_PRESET_PICTURE, + /// Outdoor photograph, with natural lighting + WEBP_PRESET_PHOTO, + /// Hand or line drawing, with high-contrast details + WEBP_PRESET_DRAWING, + /// Small-sized colorful images + WEBP_PRESET_ICON, + /// Text-like + WEBP_PRESET_TEXT + }; + + /// Encoding error conditions + internal enum WebPEncodingError + { + /// No error + VP8_ENC_OK = 0, + /// Memory error allocating objects + VP8_ENC_ERROR_OUT_OF_MEMORY, + /// Memory error while flushing bits + VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY, + /// A pointer parameter is NULL + VP8_ENC_ERROR_NULL_PARAMETER, + /// Configuration is invalid + VP8_ENC_ERROR_INVALID_CONFIGURATION, + /// Picture has invalid width/height + VP8_ENC_ERROR_BAD_DIMENSION, + /// Partition is bigger than 512k + VP8_ENC_ERROR_PARTITION0_OVERFLOW, + /// Partition is bigger than 16M + VP8_ENC_ERROR_PARTITION_OVERFLOW, + /// Error while flushing bytes + VP8_ENC_ERROR_BAD_WRITE, + /// File is bigger than 4G + VP8_ENC_ERROR_FILE_TOO_BIG, + /// Abort request by user + VP8_ENC_ERROR_USER_ABORT, + /// List terminator. Always last + VP8_ENC_ERROR_LAST, + } + + /// Enumeration of the status codes + internal enum VP8StatusCode + { + /// No error + VP8_STATUS_OK = 0, + /// Memory error allocating objects + VP8_STATUS_OUT_OF_MEMORY, + /// Configuration is invalid + VP8_STATUS_INVALID_PARAM, + VP8_STATUS_BITSTREAM_ERROR, + /// Configuration is invalid + VP8_STATUS_UNSUPPORTED_FEATURE, + VP8_STATUS_SUSPENDED, + /// Abort request by user + VP8_STATUS_USER_ABORT, + VP8_STATUS_NOT_ENOUGH_DATA, + } + + /// Image characteristics hint for the underlying encoder + internal enum WebPImageHint + { + /// Default preset + WEBP_HINT_DEFAULT = 0, + /// Digital picture, like portrait, inner shot + WEBP_HINT_PICTURE, + /// Outdoor photograph, with natural lighting + WEBP_HINT_PHOTO, + /// Discrete tone image (graph, map-tile etc) + WEBP_HINT_GRAPH, + /// List terminator. Always last + WEBP_HINT_LAST + }; + + /// Describes the byte-ordering of packed samples in memory + internal enum WEBP_CSP_MODE + { + /// Byte-order: R,G,B,R,G,B,.. + MODE_RGB = 0, + /// Byte-order: R,G,B,A,R,G,B,A,.. + MODE_RGBA = 1, + /// Byte-order: B,G,R,B,G,R,.. + MODE_BGR = 2, + /// Byte-order: B,G,R,A,B,G,R,A,.. + MODE_BGRA = 3, + /// Byte-order: A,R,G,B,A,R,G,B,.. + MODE_ARGB = 4, + /// Byte-order: RGB-565: [a4 a3 a2 a1 a0 r5 r4 r3], [r2 r1 r0 g4 g3 g2 g1 g0], ... + /// WEBP_SWAP_16BITS_CSP is defined, + /// Byte-order: RGB-565: [a4 a3 a2 a1 a0 b5 b4 b3], [b2 b1 b0 g4 g3 g2 g1 g0], .. + MODE_RGBA_4444 = 5, + /// Byte-order: RGB-565: [r4 r3 r2 r1 r0 g5 g4 g3], [g2 g1 g0 b4 b3 b2 b1 b0], ... + /// WEBP_SWAP_16BITS_CSP is defined, + /// Byte-order: [b3 b2 b1 b0 a3 a2 a1 a0], [r3 r2 r1 r0 g3 g2 g1 g0], .. + MODE_RGB_565 = 6, + /// RGB-premultiplied transparent modes (alpha value is preserved) + MODE_rgbA = 7, + /// RGB-premultiplied transparent modes (alpha value is preserved) + MODE_bgrA = 8, + /// RGB-premultiplied transparent modes (alpha value is preserved) + MODE_Argb = 9, + /// RGB-premultiplied transparent modes (alpha value is preserved) + MODE_rgbA_4444 = 10, + /// YUV 4:2:0 + MODE_YUV = 11, + /// YUV 4:2:0 + MODE_YUVA = 12, + /// MODE_LAST -> 13 + MODE_LAST = 13, + } + + /// + /// Decoding states. State normally flows as: + /// WEBP_HEADER->VP8_HEADER->VP8_PARTS0->VP8_DATA->DONE for a lossy image, and + /// WEBP_HEADER->VP8L_HEADER->VP8L_DATA->DONE for a lossless image. + /// If there is any error the decoder goes into state ERROR. + /// + internal enum DecState + { + STATE_WEBP_HEADER, // All the data before that of the VP8/VP8L chunk. + STATE_VP8_HEADER, // The VP8 Frame header (within the VP8 chunk). + STATE_VP8_PARTS0, + STATE_VP8_DATA, + STATE_VP8L_HEADER, + STATE_VP8L_DATA, + STATE_DONE, + STATE_ERROR + }; + #endregion + + #region | libwebp structs | + /// Features gathered from the bit stream + [StructLayoutAttribute(LayoutKind.Sequential)] + internal struct WebPBitstreamFeatures + { + /// Width in pixels, as read from the bit stream + public int Width; + /// Height in pixels, as read from the bit stream + public int Height; + /// True if the bit stream contains an alpha channel + public int Has_alpha; + /// True if the bit stream is an animation + public int Has_animation; + /// 0 = undefined (/mixed), 1 = lossy, 2 = lossless + public int Format; + /// Padding for later use + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 5, ArraySubType = UnmanagedType.U4)] + private readonly uint[] pad; + }; + + /// Compression parameters + [StructLayoutAttribute(LayoutKind.Sequential)] + internal struct WebPConfig + { + /// Lossless encoding (0=lossy(default), 1=lossless) + public int lossless; + /// Between 0 (smallest file) and 100 (biggest) + public float quality; + /// Quality/speed trade-off (0=fast, 6=slower-better) + public int method; + /// Hint for image type (lossless only for now) + public WebPImageHint image_hint; + /// If non-zero, set the desired target size in bytes. Takes precedence over the 'compression' parameter + public int target_size; + /// If non-zero, specifies the minimal distortion to try to achieve. Takes precedence over target_size + public float target_PSNR; + /// Maximum number of segments to use, in [1..4] + public int segments; + /// Spatial Noise Shaping. 0=off, 100=maximum + public int sns_strength; + /// Range: [0 = off .. 100 = strongest] + public int filter_strength; + /// Range: [0 = off .. 7 = least sharp] + public int filter_sharpness; + /// Filtering type: 0 = simple, 1 = strong (only used if filter_strength > 0 or auto-filter > 0) + public int filter_type; + /// Auto adjust filter's strength [0 = off, 1 = on] + public int autofilter; + /// Algorithm for encoding the alpha plane (0 = none, 1 = compressed with WebP lossless). Default is 1 + public int alpha_compression; + /// Predictive filtering method for alpha plane. 0: none, 1: fast, 2: best. Default if 1 + public int alpha_filtering; + /// Between 0 (smallest size) and 100 (lossless). Default is 100 + public int alpha_quality; + /// Number of entropy-analysis passes (in [1..10]) + public int pass; + /// If true, export the compressed picture back. In-loop filtering is not applied + public int show_compressed; + /// Preprocessing filter (0=none, 1=segment-smooth, 2=pseudo-random dithering) + public int preprocessing; + /// Log2(number of token partitions) in [0..3] Default is set to 0 for easier progressive decoding + public int partitions; + /// Quality degradation allowed to fit the 512k limit on prediction modes coding (0: no degradation, 100: maximum possible degradation) + public int partition_limit; + /// If true, compression parameters will be remapped to better match the expected output size from JPEG compression. Generally, the output size will be similar but the degradation will be lower + public int emulate_jpeg_size; + /// If non-zero, try and use multi-threaded encoding + public int thread_level; + /// If set, reduce memory usage (but increase CPU use) + public int low_memory; + /// Near lossless encoding [0 = max loss .. 100 = off (default)] + public int near_lossless; + /// If non-zero, preserve the exact RGB values under transparent area. Otherwise, discard this invisible RGB information for better compression. The default value is 0 + public int exact; + /// Reserved for future lossless feature + public int delta_palettization; + /// If needed, use sharp (and slow) RGB->YUV conversion + public int use_sharp_yuv; + /// Padding for later use + private readonly int pad1; + private readonly int pad2; + }; + + /// Main exchange structure (input samples, output bytes, statistics) + [StructLayoutAttribute(LayoutKind.Sequential)] + internal struct WebPPicture + { + /// Main flag for encoder selecting between ARGB or YUV input. Recommended to use ARGB input (*argb, argb_stride) for lossless, and YUV input (*y, *u, *v, etc.) for lossy + public int use_argb; + /// Color-space: should be YUV420 for now (=Y'CbCr). Value = 0 + public UInt32 colorspace; + /// Width of picture (less or equal to WEBP_MAX_DIMENSION) + public int width; + /// Height of picture (less or equal to WEBP_MAX_DIMENSION) + public int height; + /// Pointer to luma plane + public IntPtr y; + /// Pointer to chroma U plane + public IntPtr u; + /// Pointer to chroma V plane + public IntPtr v; + /// Luma stride + public int y_stride; + /// Chroma stride + public int uv_stride; + /// Pointer to the alpha plane + public IntPtr a; + /// stride of the alpha plane + public int a_stride; + /// Padding for later use + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.U4)] + private readonly uint[] pad1; + /// Pointer to ARGB (32 bit) plane + public IntPtr argb; + /// This is stride in pixels units, not bytes + public int argb_stride; + /// Padding for later use + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.U4)] + private readonly uint[] pad2; + /// Byte-emission hook, to store compressed bytes as they are ready + public IntPtr writer; + /// Can be used by the writer + public IntPtr custom_ptr; + // map for extra information (only for lossy compression mode) + /// 1: intra type, 2: segment, 3: quant, 4: intra-16 prediction mode, 5: chroma prediction mode, 6: bit cost, 7: distortion + public int extra_info_type; + /// If not NULL, points to an array of size ((width + 15) / 16) * ((height + 15) / 16) that will be filled with a macroblock map, depending on extra_info_type + public IntPtr extra_info; + /// Pointer to side statistics (updated only if not NULL) + public IntPtr stats; + /// Error code for the latest error encountered during encoding + public UInt32 error_code; + /// If not NULL, report progress during encoding + public IntPtr progress_hook; + /// This field is free to be set to any value and used during callbacks (like progress-report e.g.) + public IntPtr user_data; + /// Padding for later use + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 13, ArraySubType = UnmanagedType.U4)] + private readonly uint[] pad3; + /// Row chunk of memory for YUVA planes + private readonly IntPtr memory_; + /// Row chunk of memory for ARGB planes + private readonly IntPtr memory_argb_; + /// Padding for later use + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.U4)] + private readonly uint[] pad4; + }; + + /// Structure for storing auxiliary statistics (mostly for lossy encoding) + [StructLayoutAttribute(LayoutKind.Sequential)] + internal struct WebPAuxStats + { + /// Final size + public int coded_size; + /// Peak-signal-to-noise ratio for Y + public float PSNRY; + /// Peak-signal-to-noise ratio for U + public float PSNRU; + /// Peak-signal-to-noise ratio for V + public float PSNRV; + /// Peak-signal-to-noise ratio for All + public float PSNRALL; + /// Peak-signal-to-noise ratio for Alpha + public float PSNRAlpha; + /// Number of intra4 + public int block_count_intra4; + /// Number of intra16 + public int block_count_intra16; + /// Number of skipped macro-blocks + public int block_count_skipped; + /// Approximate number of bytes spent for header + public int header_bytes; + /// Approximate number of bytes spent for mode-partition #0 + public int mode_partition_0; + /// Approximate number of bytes spent for DC coefficients for segment 0 + public int residual_bytes_DC_segments0; + /// Approximate number of bytes spent for AC coefficients for segment 0 + public int residual_bytes_AC_segments0; + /// Approximate number of bytes spent for UV coefficients for segment 0 + public int residual_bytes_uv_segments0; + /// Approximate number of bytes spent for DC coefficients for segment 1 + public int residual_bytes_DC_segments1; + /// Approximate number of bytes spent for AC coefficients for segment 1 + public int residual_bytes_AC_segments1; + /// Approximate number of bytes spent for UV coefficients for segment 1 + public int residual_bytes_uv_segments1; + /// Approximate number of bytes spent for DC coefficients for segment 2 + public int residual_bytes_DC_segments2; + /// Approximate number of bytes spent for AC coefficients for segment 2 + public int residual_bytes_AC_segments2; + /// Approximate number of bytes spent for UV coefficients for segment 2 + public int residual_bytes_uv_segments2; + /// Approximate number of bytes spent for DC coefficients for segment 3 + public int residual_bytes_DC_segments3; + /// Approximate number of bytes spent for AC coefficients for segment 3 + public int residual_bytes_AC_segments3; + /// Approximate number of bytes spent for UV coefficients for segment 3 + public int residual_bytes_uv_segments3; + /// Number of macro-blocks in segments 0 + public int segment_size_segments0; + /// Number of macro-blocks in segments 1 + public int segment_size_segments1; + /// Number of macro-blocks in segments 2 + public int segment_size_segments2; + /// Number of macro-blocks in segments 3 + public int segment_size_segments3; + /// Quantizer values for segment 0 + public int segment_quant_segments0; + /// Quantizer values for segment 1 + public int segment_quant_segments1; + /// Quantizer values for segment 2 + public int segment_quant_segments2; + /// Quantizer values for segment 3 + public int segment_quant_segments3; + /// Filtering strength for segment 0 [0..63] + public int segment_level_segments0; + /// Filtering strength for segment 1 [0..63] + public int segment_level_segments1; + /// Filtering strength for segment 2 [0..63] + public int segment_level_segments2; + /// Filtering strength for segment 3 [0..63] + public int segment_level_segments3; + /// Size of the transparency data + public int alpha_data_size; + /// Size of the enhancement layer data + public int layer_data_size; + + // lossless encoder statistics + /// bit0:predictor bit1:cross-color transform bit2:subtract-green bit3:color indexing + public Int32 lossless_features; + /// Number of precision bits of histogram + public int histogram_bits; + /// Precision bits for transform + public int transform_bits; + /// Number of bits for color cache lookup + public int cache_bits; + /// Number of color in palette, if used + public int palette_size; + /// Final lossless size + public int lossless_size; + /// Lossless header (transform, Huffman, etc) size + public int lossless_hdr_size; + /// Lossless image data size + public int lossless_data_size; + /// Padding for later use + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.U4)] + private readonly uint[] pad; + }; + + [StructLayoutAttribute(LayoutKind.Sequential)] + internal struct WebPDecoderConfig + { + /// Immutable bit stream features (optional) + public WebPBitstreamFeatures input; + /// Output buffer (can point to external memory) + public WebPDecBuffer output; + /// Decoding options + public WebPDecoderOptions options; + } + + /// Output buffer + [StructLayoutAttribute(LayoutKind.Sequential)] + internal struct WebPDecBuffer + { + /// Color space + public WEBP_CSP_MODE colorspace; + /// Width of image + public int width; + /// Height of image + public int height; + /// If non-zero, 'internal_memory' pointer is not used. If value is '2' or more, the external memory is considered 'slow' and multiple read/write will be avoided + public int is_external_memory; + /// Output buffer parameters + public RGBA_YUVA_Buffer u; + /// Padding for later use + private readonly UInt32 pad1; + /// Padding for later use + private readonly UInt32 pad2; + /// Padding for later use + private readonly UInt32 pad3; + /// Padding for later use + private readonly UInt32 pad4; + /// Internally allocated memory (only when is_external_memory is 0). Should not be used externally, but accessed via WebPRGBABuffer + public IntPtr private_memory; + } + + /// Union of buffer parameters + [StructLayoutAttribute(LayoutKind.Explicit)] + internal struct RGBA_YUVA_Buffer + { + [FieldOffsetAttribute(0)] + public WebPRGBABuffer RGBA; + + [FieldOffsetAttribute(0)] + public WebPYUVABuffer YUVA; + } + + [StructLayoutAttribute(LayoutKind.Sequential)] + internal struct WebPYUVABuffer + { + /// Pointer to luma samples + public IntPtr y; + /// Pointer to chroma U samples + public IntPtr u; + /// Pointer to chroma V samples + public IntPtr v; + /// Pointer to alpha samples + public IntPtr a; + /// Luma stride + public int y_stride; + /// Chroma U stride + public int u_stride; + /// Chroma V stride + public int v_stride; + /// Alpha stride + public int a_stride; + /// Luma plane size + public UIntPtr y_size; + /// Chroma plane U size + public UIntPtr u_size; + /// Chroma plane V size + public UIntPtr v_size; + /// Alpha plane size + public UIntPtr a_size; + } + + /// Generic structure for describing the output sample buffer + [StructLayoutAttribute(LayoutKind.Sequential)] + internal struct WebPRGBABuffer + { + /// Pointer to RGBA samples + public IntPtr rgba; + /// Stride in bytes from one scanline to the next + public int stride; + /// Total size of the RGBA buffer + public UIntPtr size; + } + + /// Decoding options + [StructLayout(LayoutKind.Sequential)] + public struct WebPDecoderOptions + { + /// If true, skip the in-loop filtering + public int bypass_filtering; + /// If true, use faster point-wise up-sampler + public int no_fancy_upsampling; + /// If true, cropping is applied _first_ + public int use_cropping; + /// Left position for cropping. Will be snapped to even values + public int crop_left; + /// Top position for cropping. Will be snapped to even values + public int crop_top; + /// Width of the cropping area + public int crop_width; + /// Height of the cropping area + public int crop_height; + /// If true, scaling is applied _afterward_ + public int use_scaling; + /// Final width + public int scaled_width; + /// Final height + public int scaled_height; + /// If true, use multi-threaded decoding + public int use_threads; + /// Dithering strength (0=Off, 100=full) + public int dithering_strength; + /// Flip output vertically + public int flip; + /// Alpha dithering strength in [0..100] + public int alpha_dithering_strength; + /// Padding for later use + private readonly UInt32 pad1; + /// Padding for later use + private readonly UInt32 pad2; + /// Padding for later use + private readonly UInt32 pad3; + /// Padding for later use + private readonly UInt32 pad4; + /// Padding for later use + private readonly UInt32 pad5; + }; + #endregion +} \ No newline at end of file diff --git a/Wabbajack.App.Wpf/ViewModels/Gallery/ModListGalleryVM.cs b/Wabbajack.App.Wpf/ViewModels/Gallery/ModListGalleryVM.cs index bdba2dff..a9336974 100644 --- a/Wabbajack.App.Wpf/ViewModels/Gallery/ModListGalleryVM.cs +++ b/Wabbajack.App.Wpf/ViewModels/Gallery/ModListGalleryVM.cs @@ -133,7 +133,8 @@ namespace Wabbajack { if (string.IsNullOrWhiteSpace(txt)) return _ => true; return item => item.Metadata.Title.ContainsCaseInsensitive(txt) || - item.Metadata.Description.ContainsCaseInsensitive(txt); + item.Metadata.Description.ContainsCaseInsensitive(txt) || + item.Metadata.Tags.Contains(txt); }); var onlyInstalledGamesFilter = this.ObservableForProperty(vm => vm.OnlyInstalled) @@ -169,12 +170,12 @@ namespace Wabbajack }) .StartWith(_ => true); - var searchSorter = this.WhenValueChanged(x => x.Search) - .Where(x => !string.IsNullOrWhiteSpace(x)) + var searchSorter = this.WhenValueChanged(vm => vm.Search) + .Where(s => !string.IsNullOrWhiteSpace(s)) .Throttle(searchThrottle, RxApp.MainThreadScheduler) .Select(s => SortExpressionComparer - .Descending(modlist => modlist.Metadata.Title.StartsWith(s, StringComparison.InvariantCultureIgnoreCase)) - .ThenByDescending(modlist => modlist.Metadata.Title.Contains(s, StringComparison.InvariantCultureIgnoreCase))); + .Descending(m => m.Metadata.Title.StartsWith(s, StringComparison.InvariantCultureIgnoreCase)) + .ThenByDescending(m => m.Metadata.Title.Contains(s, StringComparison.InvariantCultureIgnoreCase))); _modLists.Connect() .ObserveOn(RxApp.MainThreadScheduler) .Filter(searchTextPredicates) @@ -183,6 +184,7 @@ namespace Wabbajack .Filter(showNSFWFilter) .Filter(gameFilter) .Sort(searchSorter) + .Sort(SortExpressionComparer.Descending(modlist => !modlist.IsBroken)) .TreatMovesAsRemoveAdd() .Bind(out _filteredModLists) .Subscribe((_) => diff --git a/Wabbajack.App.Wpf/ViewModels/Gallery/ModListMetadataVM.cs b/Wabbajack.App.Wpf/ViewModels/Gallery/ModListMetadataVM.cs index 01e96839..de0b91fe 100644 --- a/Wabbajack.App.Wpf/ViewModels/Gallery/ModListMetadataVM.cs +++ b/Wabbajack.App.Wpf/ViewModels/Gallery/ModListMetadataVM.cs @@ -176,7 +176,7 @@ namespace Wabbajack .ToGuiProperty(this, nameof(Exists)); var imageObs = Observable.Return(Metadata.Links.ImageUri) - .DownloadBitmapImage((ex) => _logger.LogError("Error downloading modlist image {Title}", Metadata.Title), LoadingImageLock); + .DownloadBitmapImage((ex) => _logger.LogError("Error downloading modlist image {Title} from {ImageUri}: {Exception}", Metadata.Title, Metadata.Links.ImageUri, ex.Message), LoadingImageLock); _Image = imageObs .ToGuiProperty(this, nameof(Image)); diff --git a/Wabbajack.App.Wpf/Views/ModListGalleryView.xaml b/Wabbajack.App.Wpf/Views/ModListGalleryView.xaml index 9bc699f7..8a90c7a6 100644 --- a/Wabbajack.App.Wpf/Views/ModListGalleryView.xaml +++ b/Wabbajack.App.Wpf/Views/ModListGalleryView.xaml @@ -17,7 +17,7 @@ - + - - - - - - - - - - - - - + @@ -172,6 +155,24 @@ + + + + + + + + + + + + + diff --git a/Wabbajack.App.Wpf/Views/ModListTileView.xaml b/Wabbajack.App.Wpf/Views/ModListTileView.xaml index 7d9dc59a..292c1458 100644 --- a/Wabbajack.App.Wpf/Views/ModListTileView.xaml +++ b/Wabbajack.App.Wpf/Views/ModListTileView.xaml @@ -48,14 +48,15 @@ + CornerRadius="8" + BorderThickness="0"> + Opacity="0.25" + ShadowDepth="3" /> - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - + - + - + - + - - + + + + + + + + + + diff --git a/Wabbajack.App.Wpf/Views/ModListTileView.xaml.cs b/Wabbajack.App.Wpf/Views/ModListTileView.xaml.cs index 1b7adb87..215bcd1b 100644 --- a/Wabbajack.App.Wpf/Views/ModListTileView.xaml.cs +++ b/Wabbajack.App.Wpf/Views/ModListTileView.xaml.cs @@ -2,6 +2,8 @@ using System.Reactive.Disposables; using System.Reactive.Linq; using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; using System.Windows.Media.Media3D; using MahApps.Metro.IconPacks; using ReactiveUI; @@ -19,14 +21,39 @@ namespace Wabbajack this.WhenActivated(disposables => { ViewModel.WhenAnyValue(vm => vm.Image) - .BindToStrict(this, view => view.ModListImage.Source) + .BindToStrict(this, v => v.ModlistImage.ImageSource) + .DisposeWith(disposables); + /* + this.WhenAny(x => x.ViewModel.Metadata.Links.ImageUri) + .Select(x => new BitmapImage() { UriSource = new Uri(x) }) + .BindToStrict(this, v => v.ModlistImage.ImageSource) .DisposeWith(disposables); + */ + /* + ViewModel.WhenAnyValue(x => x.Metadata.Links.ImageUri) + .Select(x => { + var img = new BitmapImage(); + img.BeginInit(); + img.CacheOption = BitmapCacheOption.OnDemand; + img.DecodePixelWidth = 327; + var uri = new Uri(x, UriKind.Absolute); + img.UriSource = uri; + img.EndInit(); + + return img; + }) + .BindToStrict(this, v => v.ModlistImage.ImageSource) + .DisposeWith(disposables); + */ + var textXformed = ViewModel.WhenAnyValue(vm => vm.Metadata.Title) .CombineLatest(ViewModel.WhenAnyValue(vm => vm.Metadata.ImageContainsTitle), ViewModel.WhenAnyValue(vm => vm.IsBroken)) .Select(x => x.Second && !x.Third ? "" : x.First); + + /* textXformed .BindToStrict(this, view => view.ModListTitle.Text) .DisposeWith(disposables); @@ -42,6 +69,7 @@ namespace Wabbajack ViewModel.WhenAnyValue(x => x.ModListTagList) .BindToStrict(this, x => x.TagsList.ItemsSource) .DisposeWith(disposables); + */ ViewModel.WhenAnyValue(x => x.LoadingImageLock.IsLoading) .Select(x => x ? Visibility.Visible : Visibility.Collapsed) @@ -53,6 +81,7 @@ namespace Wabbajack .BindToStrict(this, view => view.Overlay.Visibility) .DisposeWith(disposables); + /* ViewModel.WhenAnyValue(x => x.OpenWebsiteCommand) .BindToStrict(this, x => x.OpenWebsiteButton.Command) .DisposeWith(disposables); @@ -64,14 +93,18 @@ namespace Wabbajack ViewModel.WhenAnyValue(x => x.ExecuteCommand) .BindToStrict(this, x => x.ExecuteButton.Command) .DisposeWith(disposables); + */ + /* ViewModel.WhenAnyValue(x => x.ProgressPercent) - .ObserveOnDispatcher() + .ObserveOn(RxApp.MainThreadScheduler) .Select(p => p.Value) .BindTo(this, x => x.DownloadProgressBar.Value) .DisposeWith(disposables); + */ + /* ViewModel.WhenAnyValue(x => x.Status) .ObserveOnGuiThread() .Subscribe(x => @@ -91,6 +124,7 @@ namespace Wabbajack }); }) .DisposeWith(disposables); + */ /* this.MarkAsNeeded(this.ViewModel, x => x.IsBroken); diff --git a/Wabbajack.App.Wpf/Wabbajack.App.Wpf.csproj b/Wabbajack.App.Wpf/Wabbajack.App.Wpf.csproj index cbac7eda..49ea479e 100644 --- a/Wabbajack.App.Wpf/Wabbajack.App.Wpf.csproj +++ b/Wabbajack.App.Wpf/Wabbajack.App.Wpf.csproj @@ -96,6 +96,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive +