using System; using System.Collections.Generic; using System.Runtime.InteropServices; using unity.libwebp; using unity.libwebp.Interop; using UnityEngine; using UnityEngine.Assertions; namespace WebP { /// /// /// public static class Texture2DExt { public delegate void ScalingFunction(ref int width, ref int height); /// /// Scaling funtion to scale image to specific width and height. /// /// The to size. /// Width in pixels. /// Height in pixels. public static ScalingFunction ScaleToSize(int widthInPixels, int heightInPixels) { return delegate (ref int width, ref int height) { width = widthInPixels; height = heightInPixels; }; } /// /// Scaling function scale both height and width by a specific amount. /// /// The by. /// Scale. public static ScalingFunction ScaleBy(float scale) { return delegate (ref int width, ref int height) { width = (int)(width * scale); height = (int)(height * scale); }; } /// /// Scaling function to scale to a specifc width while retaining aspect. /// /// The to width. /// Width in pixels. public static ScalingFunction ScaleToWidth(int widthInPixels) { return delegate (ref int width, ref int height) { height = (int)((height / (float)width) * widthInPixels); width = widthInPixels; }; } /// /// Scaling function to scale to a specific height while retaining aspect. /// /// The to height. /// Height in pixels. public static ScalingFunction ScaleToHeight(int heightInPixels) { return delegate (ref int width, ref int height) { width = (int)((width / (float)height) * heightInPixels); height = heightInPixels; }; } /// /// Gets dimensions from a webp format block of data. /// /// L data. /// L width. /// L height. public static unsafe void GetWebPDimensions(byte[] lData, out int lWidth, out int lHeight) { fixed (byte* lDataPtr = lData) { int w; int h; lWidth = 0; lHeight = 0; if (NativeLibwebp.WebPGetInfo(lDataPtr, (UIntPtr)lData.Length, &w, &h) == 0) { throw new Exception("Invalid WebP header detected"); } lWidth = w; lHeight = h; } } /// /// Loads an image from webp into a byte array in RGBA format. /// /// The RGBA from web p. /// L data. /// L width. /// L height. /// If set to true l mipmaps. /// L error. /// Scaling function. public static unsafe byte[] LoadRGBAFromWebP(byte[] lData, ref int lWidth, ref int lHeight, bool lMipmaps, out Error lError, ScalingFunction scalingFunction = null) { lError = 0; byte[] lRawData = null; int lLength = lData.Length; fixed (byte* lDataPtr = lData) { // If we've been supplied a function to alter the width and height, use that now. scalingFunction?.Invoke(ref lWidth, ref lHeight); // If mipmaps are requested we need to create 1/3 more memory for the mipmaps to be generated in. int numBytesRequired = lWidth * lHeight * 4; if (lMipmaps) { numBytesRequired = Mathf.CeilToInt((numBytesRequired * 4.0f) / 3.0f); } lRawData = new byte[numBytesRequired]; fixed (byte* lRawDataPtr = lRawData) { int lStride = 4 * lWidth; // As we have to reverse the y order of the data, we pass through a negative stride and // pass through a pointer to the last line of the data. byte* lTmpDataPtr = lRawDataPtr + (lHeight - 1) * lStride; WebPDecoderConfig config = new WebPDecoderConfig(); if (NativeLibwebp.WebPInitDecoderConfig(&config) == 0) { throw new Exception("WebPInitDecoderConfig failed. Wrong version?"); } // Set up decode options config.options.use_threads = 1; if (scalingFunction != null) { config.options.use_scaling = 1; } config.options.scaled_width = lWidth; config.options.scaled_height = lHeight; // read the .webp input file information VP8StatusCode result = NativeLibwebp.WebPGetFeatures(lDataPtr, (UIntPtr)lLength, &config.input); if (result != VP8StatusCode.VP8_STATUS_OK) { throw new Exception(string.Format("Failed WebPGetFeatures with error {0}.", result.ToString())); } // specify the output format config.output.colorspace = WEBP_CSP_MODE.MODE_RGBA; config.output.u.RGBA.rgba = lTmpDataPtr; config.output.u.RGBA.stride = -lStride; config.output.u.RGBA.size = (UIntPtr)(lHeight * lStride); config.output.height = lHeight; config.output.width = lWidth; config.output.is_external_memory = 1; // Decode result = NativeLibwebp.WebPDecode(lDataPtr, (UIntPtr)lLength, &config); if (result != VP8StatusCode.VP8_STATUS_OK) { throw new Exception(string.Format("Failed WebPDecode with error {0}.", result.ToString())); } } lError = Error.Success; } return lRawData; } public static Texture2D CreateWebpTexture2D(int width, int height, bool isUseMipmap, bool isLinear) { if (isUseMipmap) { UnityEngine.Experimental.Rendering.GraphicsFormat graphicsFormat = UnityEngine.Experimental.Rendering.GraphicsFormatUtility.GetGraphicsFormat(TextureFormat.RGBA32, false); uint mipmapSize = UnityEngine.Experimental.Rendering.GraphicsFormatUtility.ComputeMipmapSize(width, height, graphicsFormat); int mipmapCount = (int)mipmapSize / (width * height); return new Texture2D(width, height, TextureFormat.RGBA32, mipCount: mipmapCount, isLinear); } else { return new Texture2D(width, height, TextureFormat.RGBA32, mipChain: false, isLinear); } } /// /// /// /// /// /// public static unsafe Texture2D CreateTexture2DFromWebP(byte[] lData, bool lMipmaps, bool lLinear, out Error lError, ScalingFunction scalingFunction = null, bool makeNoLongerReadable = true) { Texture2D lTexture2D = null; int lWidth; int lHeight; GetWebPDimensions(lData, out lWidth, out lHeight); byte[] lRawData = LoadRGBAFromWebP(lData, ref lWidth, ref lHeight, lMipmaps, out lError, scalingFunction); if (lError == Error.Success) { lTexture2D = CreateWebpTexture2D(lWidth, lHeight, lMipmaps, lLinear); lTexture2D.LoadRawTextureData(lRawData); lTexture2D.Apply(lMipmaps, makeNoLongerReadable); } return lTexture2D; } /// /// /// /// /// /// public static unsafe void LoadWebP(this Texture2D lTexture2D, byte[] lData, out Error lError, ScalingFunction scalingFunction = null, bool makeNoLongerReadable = true) { lError = 0; bool lMipmaps = lTexture2D.mipmapCount != 1; int lWidth = 0, lHeight = 0; GetWebPDimensions(lData, out lWidth, out lHeight); byte[] lRawData = LoadRGBAFromWebP(lData, ref lWidth, ref lHeight, lMipmaps, out lError, scalingFunction); if (lError == Error.Success) { lTexture2D.LoadRawTextureData(lRawData); lTexture2D.Apply(lMipmaps, makeNoLongerReadable); } } /// /// /// /// /// /// public static unsafe byte[] EncodeToWebP(this Texture2D lTexture2D, float lQuality, out Error lError) { lError = 0; if (lQuality < -1) { lQuality = -1; } if (lQuality > 100) { lQuality = 100; } Color32[] lRawColorData = lTexture2D.GetPixels32(); int lWidth = lTexture2D.width; int lHeight = lTexture2D.height; IntPtr lResult = new IntPtr(); byte** pResult = (byte**)&lResult; GCHandle lPinnedArray = GCHandle.Alloc(lRawColorData, GCHandleType.Pinned); IntPtr lRawDataPtr = lPinnedArray.AddrOfPinnedObject(); byte[] lOutputBuffer = null; try { int lLength; if (lQuality == -1) { lLength = (int)NativeLibwebp.WebPEncodeLosslessRGBA((byte*)lRawDataPtr, lWidth, lHeight, 4 * lWidth, pResult); } else { lLength = (int)NativeLibwebp.WebPEncodeRGBA((byte*)lRawDataPtr, lWidth, lHeight, 4 * lWidth, lQuality, pResult); } if (lLength == 0) { throw new Exception("WebP encode failed!"); } lOutputBuffer = new byte[lLength]; Marshal.Copy(lResult, lOutputBuffer, 0, lLength); } finally { NativeLibwebp.WebPSafeFree(*pResult); } lPinnedArray.Free(); return lOutputBuffer; } public static unsafe void LoadTexture2DFromWebP(byte[] webpBytes, Texture2D texture, bool lMipmaps, bool lLinear, byte[] bytePool, int numBytesRequired, ScalingFunction scalingFunction = null) { Debug.Assert(bytePool.Length >= numBytesRequired); Array.Clear(bytePool, 0, numBytesRequired); int width = texture.width; int height = texture.height; fixed (byte* lDataPtr = webpBytes) { fixed (byte* lRawDataPtr = bytePool) { int lStride = 4 * width; // As we have to reverse the y order of the data, we pass through a negative stride and // pass through a pointer to the last line of the data. byte* lTmpDataPtr = lRawDataPtr + (height - 1) * lStride; WebPDecoderConfig config = new WebPDecoderConfig(); if (NativeLibwebp.WebPInitDecoderConfig(&config) == 0) { throw new Exception("WebPInitDecoderConfig failed. Wrong version?"); } // Set up decode options config.options.use_threads = 1; if (scalingFunction != null) { config.options.use_scaling = 1; } config.options.scaled_width = width; config.options.scaled_height = height; // read the .webp input file information VP8StatusCode result = NativeLibwebp.WebPGetFeatures(lDataPtr, (UIntPtr)webpBytes.Length, &config.input); if (result != VP8StatusCode.VP8_STATUS_OK) { throw new Exception(string.Format("Failed WebPGetFeatures with error {0}.", result.ToString())); } // specify the output format config.output.colorspace = WEBP_CSP_MODE.MODE_RGBA; config.output.u.RGBA.rgba = lTmpDataPtr; config.output.u.RGBA.stride = -lStride; config.output.u.RGBA.size = (UIntPtr)(height * lStride); config.output.height = height; config.output.width = width; config.output.is_external_memory = 1; // Decode result = NativeLibwebp.WebPDecode(lDataPtr, (UIntPtr)webpBytes.Length, &config); if (result != VP8StatusCode.VP8_STATUS_OK) { throw new Exception(string.Format("Failed WebPDecode with error {0}.", result.ToString())); } texture.LoadRawTextureData((IntPtr)lRawDataPtr, numBytesRequired); texture.Apply(lMipmaps, true); } } } public static int GetRequireByteSize(byte[] webpBytes, bool isUseMipmap = false) { GetWebPDimensions(webpBytes, out int width, out int height); return GetRequireByteSize(width, height, isUseMipmap); } public static int GetRequireByteSize(int width, int height, bool isUseMipmap = false) { int numBytesRequired = width * height * 4; if (isUseMipmap) { numBytesRequired = Mathf.CeilToInt((numBytesRequired * 4.0f) / 3.0f); } return numBytesRequired; } #region Animation public static unsafe List<(Texture2D, int)> LoadTexture2DFromWebP(byte[] bytes) { List<(Texture2D, int)> ret = new List<(Texture2D, int)>(); WebPAnimDecoderOptions option = new WebPAnimDecoderOptions { use_threads = 1, color_mode = WEBP_CSP_MODE.MODE_RGBA, }; option.padding[5] = 1; NativeLibwebpdemux.WebPAnimDecoderOptionsInit(&option); fixed (byte* p = bytes) { WebPData webpdata = new WebPData { bytes = p, size = new UIntPtr((uint)bytes.Length) }; WebPAnimDecoder* dec = NativeLibwebpdemux.WebPAnimDecoderNew(&webpdata, &option); WebPAnimInfo anim_info = new WebPAnimInfo(); NativeLibwebpdemux.WebPAnimDecoderGetInfo(dec, &anim_info); //Debug.LogWarning($"{anim_info.frame_count} {anim_info.canvas_width}/{anim_info.canvas_height}"); uint size = anim_info.canvas_width * 4 * anim_info.canvas_height; int timestamp = 0; IntPtr pp = new IntPtr(); byte** unmanagedPointer = (byte**)&pp; for (int i = 0; i < anim_info.frame_count; ++i) { int result = NativeLibwebpdemux.WebPAnimDecoderGetNext(dec, unmanagedPointer, ×tamp); Assert.AreEqual(1, result); int lWidth = (int)anim_info.canvas_width; int lHeight = (int)anim_info.canvas_height; bool lMipmaps = false; bool lLinear = false; Texture2D texture = new Texture2D(lWidth, lHeight, TextureFormat.RGBA32, lMipmaps, lLinear); texture.LoadRawTextureData(pp, (int)size); texture.Compress(true); texture.Apply(); ret.Add((texture, timestamp)); } NativeLibwebpdemux.WebPAnimDecoderReset(dec); NativeLibwebpdemux.WebPAnimDecoderDelete(dec); } return ret; } #endregion } }