441 lines
17 KiB
C#
441 lines
17 KiB
C#
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Runtime.InteropServices;
|
|||
|
using unity.libwebp;
|
|||
|
using unity.libwebp.Interop;
|
|||
|
using UnityEngine;
|
|||
|
using UnityEngine.Assertions;
|
|||
|
|
|||
|
namespace WebP
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
///
|
|||
|
/// </summary>
|
|||
|
public static class Texture2DExt
|
|||
|
{
|
|||
|
public delegate void ScalingFunction(ref int width, ref int height);
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Scaling funtion to scale image to specific width and height.
|
|||
|
/// </summary>
|
|||
|
/// <returns>The to size.</returns>
|
|||
|
/// <param name="widthInPixels">Width in pixels.</param>
|
|||
|
/// <param name="heightInPixels">Height in pixels.</param>
|
|||
|
public static ScalingFunction ScaleToSize(int widthInPixels, int heightInPixels)
|
|||
|
{
|
|||
|
return delegate (ref int width, ref int height)
|
|||
|
{
|
|||
|
width = widthInPixels;
|
|||
|
height = heightInPixels;
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Scaling function scale both height and width by a specific amount.
|
|||
|
/// </summary>
|
|||
|
/// <returns>The by.</returns>
|
|||
|
/// <param name="scale">Scale.</param>
|
|||
|
public static ScalingFunction ScaleBy(float scale)
|
|||
|
{
|
|||
|
return delegate (ref int width, ref int height)
|
|||
|
{
|
|||
|
width = (int)(width * scale);
|
|||
|
height = (int)(height * scale);
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Scaling function to scale to a specifc width while retaining aspect.
|
|||
|
/// </summary>
|
|||
|
/// <returns>The to width.</returns>
|
|||
|
/// <param name="widthInPixels">Width in pixels.</param>
|
|||
|
public static ScalingFunction ScaleToWidth(int widthInPixels)
|
|||
|
{
|
|||
|
return delegate (ref int width, ref int height)
|
|||
|
{
|
|||
|
height = (int)((height / (float)width) * widthInPixels);
|
|||
|
width = widthInPixels;
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Scaling function to scale to a specific height while retaining aspect.
|
|||
|
/// </summary>
|
|||
|
/// <returns>The to height.</returns>
|
|||
|
/// <param name="heightInPixels">Height in pixels.</param>
|
|||
|
public static ScalingFunction ScaleToHeight(int heightInPixels)
|
|||
|
{
|
|||
|
return delegate (ref int width, ref int height)
|
|||
|
{
|
|||
|
width = (int)((width / (float)height) * heightInPixels);
|
|||
|
height = heightInPixels;
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets dimensions from a webp format block of data.
|
|||
|
/// </summary>
|
|||
|
/// <param name="lData">L data.</param>
|
|||
|
/// <param name="lWidth">L width.</param>
|
|||
|
/// <param name="lHeight">L height.</param>
|
|||
|
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;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Loads an image from webp into a byte array in RGBA format.
|
|||
|
/// </summary>
|
|||
|
/// <returns>The RGBA from web p.</returns>
|
|||
|
/// <param name="lData">L data.</param>
|
|||
|
/// <param name="lWidth">L width.</param>
|
|||
|
/// <param name="lHeight">L height.</param>
|
|||
|
/// <param name="lMipmaps">If set to <c>true</c> l mipmaps.</param>
|
|||
|
/// <param name="lError">L error.</param>
|
|||
|
/// <param name="scalingFunction">Scaling function.</param>
|
|||
|
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);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
///
|
|||
|
/// </summary>
|
|||
|
/// <param name="lData"></param>
|
|||
|
/// <param name="lError"></param>
|
|||
|
/// <returns></returns>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
///
|
|||
|
/// </summary>
|
|||
|
/// <param name="lTexture2D"></param>
|
|||
|
/// <param name="lData"></param>
|
|||
|
/// <param name="lError"></param>
|
|||
|
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);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
///
|
|||
|
/// </summary>
|
|||
|
/// <param name="lTexture2D"></param>
|
|||
|
/// <param name="lError"></param>
|
|||
|
/// <returns></returns>
|
|||
|
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
|
|||
|
}
|
|||
|
}
|