Unity3D中使用SkiaSharp处理Texture2D (下篇)
大约 5 分钟
这一篇主要勘正上一篇文章的错误以及使用 Job 提高转换效率。
看本片文章之前,请先阅读我的上一篇文章。
上一篇的错误
上一篇文章中提到的从 GPU 读取数据的方法,没有考虑到本机不支持的格式。所有代码以本文为准。
NativeArray
要想使用 Job,就一定会需要 NativeArray<T>
,但是阅读上一篇的代码可知,使用更多的是 Span<T>
和 ReadOnlySpan<T>
,所以需要转换。
using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace SkiaSharp.Unity
{
public static class NativeArrayExt
{
/// <summary>
/// 转换到 NativeArray
/// </summary>
/// <param name="span"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static unsafe NativeArray<T> AsNativeArray<T>(this ReadOnlySpan<T> span) where T : unmanaged
{
fixed (void* source = span)
{
var data = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(source, span.Length, Allocator.None);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref data, AtomicSafetyHandle.Create());
#endif
return data;
}
}
/// <summary>
/// 转换到 NativeArray
/// </summary>
/// <param name="span"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static unsafe NativeArray<T> AsNativeArray<T>(this Span<T> span) where T : unmanaged
{
fixed (void* source = span)
{
var data = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(source, span.Length, Allocator.None);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref data, AtomicSafetyHandle.Create());
#endif
return data;
}
}
}
}
使用 Job 并行计算
使用 Job 并行计算,在一定程度上可以加快颜色格式转换效率,而且这里使用了位运算转换颜色格式。
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
namespace SkiaSharp.Unity
{
/// <summary>
/// 颜色转换
/// </summary>
public static class ColorConverter
{
/// <summary>
/// 转换到 Unity 颜色
/// </summary>
/// <param name="skColorF"></param>
/// <returns></returns>
public static Color ToUnityColor(this SKColorF skColorF)
{
return new Color(skColorF.Red, skColorF.Green, skColorF.Blue, skColorF.Alpha);
}
/// <summary>
/// 转换到 Unity 颜色
/// </summary>
/// <param name="skColor"></param>
/// <returns></returns>
public static Color32 ToUnityColor32(this SKColor skColor)
{
return new Color32(skColor.Red, skColor.Green, skColor.Blue, skColor.Alpha);
}
/// <summary>
/// 转换到 SKColorF
/// </summary>
/// <param name="color"></param>
/// <returns></returns>
public static SKColorF ToSkColorF(this Color color)
{
return new SKColorF(color.r, color.g, color.b, color.a);
}
/// <summary>
/// 转换到 SKColor
/// </summary>
/// <param name="color32"></param>
/// <returns></returns>
public static SKColor ToSkColor(this Color32 color32)
{
return new SKColor(color32.r, color32.g, color32.b, color32.a);
}
/// <summary>
/// 批量转换到 Color32
/// </summary>
/// <param name="colors"></param>
/// <param name="batchCount"></param>
/// <returns></returns>
public static NativeArray<Color32> ConvertToColor32(NativeArray<SKColor> colors, int batchCount = 512)
{
var handle = FastColorConverter(colors.Reinterpret<uint>(), out var data, batchCount);
handle.Complete();
return data.Reinterpret<Color32>();
}
/// <summary>
/// 批量转换到 SKColor
/// </summary>
/// <param name="colors"></param>
/// <param name="batchCount"></param>
/// <returns></returns>
public static NativeArray<SKColor> ConvertToSkColor(NativeArray<Color32> colors, int batchCount = 512)
{
var handle = FastColorConverter(colors.Reinterpret<uint>(), out var data, batchCount);
handle.Complete();
return data.Reinterpret<SKColor>();
}
/// <summary>
/// 快速转换颜色
/// </summary>
/// <param name="dataIn"></param>
/// <param name="dataOut"></param>
/// <param name="batchCount"></param>
public static JobHandle FastColorConverter(NativeArray<uint> dataIn, out NativeArray<uint> dataOut, int batchCount = 512)
{
dataOut = new NativeArray<uint>(dataIn.Length, Allocator.TempJob);
var job = new ColorConverterJob
{
DataIn = dataIn,
DataOut = dataOut
};
return job.Schedule(dataIn.Length, batchCount);
}
[BurstCompile]
private struct ColorConverterJob : IJobParallelFor
{
[ReadOnly] public NativeArray<uint> DataIn;
public NativeArray<uint> DataOut;
private const uint Mask0 = 0x00FF0000;
private const uint Mask1 = 0x000000FF;
public void Execute(int index)
{
var color = DataIn[index];
DataOut[index] = ((color & Mask0) >> 16) | ((color & Mask1) << 16) | (color & ~(Mask0 | Mask1));
}
}
}
}
贴图转换
using System;
using System.Buffers;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
namespace SkiaSharp.Unity
{
public static class Texture2DConverter
{
public static Texture2D ToTexture2D(this SKBitmap bitmap, int width = 0, int height = 0,
SKSamplingOptions? options = null)
{
var resize = width != 0 || height != 0;
width = width == 0 ? bitmap.Width : width;
height = height == 0 ? bitmap.Height : height;
if (resize) bitmap = bitmap.Resize(new SKSizeI(width, height), options ?? SKSamplingOptions.Default);
Texture2D texture2D;
var l = bitmap.ColorType.TryConvertToTextureFormat(out var textureFormat);
if (l > 0)
{
var data = bitmap.GetPixelSpan();
var writer = new ArrayBufferWriter<byte>(width * height * l);
for (var i = height - 1; i >= 0; i--) writer.Write(data.Slice(i * width * l, width * l));
texture2D = new Texture2D(width, height, textureFormat, false);
texture2D.SetPixelData(writer.WrittenSpan.ToArray(), 0);
}
else
{
var data0 = bitmap.Pixels.AsSpan();
var writer = new ArrayBufferWriter<SKColor>();
for (var i = height - 1; i >= 0; i--) writer.Write(data0.Slice(i * width, width));
var data1 = writer.WrittenSpan.AsNativeArray();
var colors = ColorConverter.ConvertToColor32(data1, width * 64);
texture2D = new Texture2D(width, height, textureFormat, false);
texture2D.SetPixels32(colors.ToArray());
data1.Dispose();
colors.Dispose();
}
texture2D.Apply();
return texture2D;
}
public static SKBitmap ToSkBitmap(this Texture2D texture2D, int width = 0, int height = 0,
SKSamplingOptions? options = null)
{
var resize = width != 0 || height != 0;
width = width == 0 ? texture2D.width : width;
height = height == 0 ? texture2D.height : height;
SKBitmap bitmap;
var l = texture2D.format.TryConvertSkColorTypes(out var skColorType);
if (l > 0 && texture2D.isReadable)
{
ReadOnlySpan<byte> data = texture2D.GetPixelData<byte>(0);
var writer = new ArrayBufferWriter<byte>(width * height * l);
for (var i = height - 1; i >= 0; i--) writer.Write(data.Slice(i * width * l, width * l));
var span = writer.WrittenSpan;
unsafe
{
fixed (byte* ptr = span)
{
bitmap = new SKBitmap(width, height, skColorType, SKAlphaType.Premul);
bitmap.SetPixels((IntPtr)ptr);
}
}
}
else if (l > 0 && texture2D.GetTextureDataFromGpu(out var textureData))
{
ReadOnlySpan<byte> data = textureData;
var writer = new ArrayBufferWriter<byte>(width * height * l);
for (var i = height - 1; i >= 0; i--) writer.Write(data.Slice(i * width * l, width * l));
var span = writer.WrittenSpan;
unsafe
{
fixed (byte* ptr = span)
{
bitmap = new SKBitmap(width, height, skColorType, SKAlphaType.Premul);
bitmap.SetPixels((IntPtr)ptr);
}
}
}
else
{
var data0 = (texture2D.isReadable ? texture2D : texture2D.GetTextureFromGpu()).GetPixels32();
var data1 = new NativeArray<Color32>(data0, Allocator.TempJob);
var data2 = ColorConverter.ConvertToSkColor(data1, width * 64);
var skColors = data2.AsSpan();
var writer = new ArrayBufferWriter<SKColor>();
for (var i = height - 1; i >= 0; i--) writer.Write(skColors.Slice(i * width, width));
bitmap = new SKBitmap(texture2D.width, texture2D.height, skColorType, SKAlphaType.Premul);
bitmap.Pixels = writer.WrittenSpan.ToArray();
data1.Dispose();
data2.Dispose();
}
if (resize) bitmap = bitmap.Resize(new SKSizeI(width, height), options ?? SKSamplingOptions.Default);
return bitmap;
}
/// <summary>
/// 从GUP读取贴图
/// </summary>
/// <param name="texture"></param>
/// <returns></returns>
public static Texture2D GetTextureFromGpu(this Texture texture)
{
var width = texture.width;
var height = texture.height;
Texture2D tex2;
if (texture.GetTextureDataFromGpu(out var data))
{
tex2 = new Texture2D(width, height, texture.graphicsFormat, TextureCreationFlags.None);
tex2.SetPixelData(data, 0);
}
else
{
var renderTexture = new RenderTexture(width, height, 32);
Graphics.Blit(texture, renderTexture);
var tmpTexture = RenderTexture.active;
RenderTexture.active = renderTexture;
tex2 = new Texture2D(width, height);
tex2.ReadPixels(new Rect(0, 0, width, height), 0, 0);
RenderTexture.active = tmpTexture;
}
return tex2;
}
/// <summary>
/// 从GPU读取数据
/// </summary>
/// <param name="texture"></param>
/// <param name="data"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static bool GetTextureDataFromGpu(this Texture texture, out NativeArray<byte> data)
{
#if UNITY_2023_2_OR_NEWER
if (SystemInfo.IsFormatSupported(texture.graphicsFormat, GraphicsFormatUsage.ReadPixels))
#else
if (SystemInfo.IsFormatSupported(texture.graphicsFormat, FormatUsage.ReadPixels))
#endif
{
var request = AsyncGPUReadback.Request(texture, 0, texture.graphicsFormat);
request.WaitForCompletion();
if (request.hasError) throw new Exception("");
data = request.GetData<byte>();
return true;
}
data = new NativeArray<byte>();
return false;
}
}
}
using System;
using UnityEngine;
namespace SkiaSharp.Unity
{
internal static class ColorTypeConverter
{
private static readonly SKColorType[] SkColorTypes =
{
SKColorType.Alpha8,
SKColorType.Rgb565,
SKColorType.Rgba8888,
SKColorType.Rgb888x,
SKColorType.Bgra8888,
SKColorType.RgbaF16,
SKColorType.RgbaF16Clamped,
SKColorType.RgbaF32,
SKColorType.Rg88,
SKColorType.RgF16,
SKColorType.Rg1616,
SKColorType.Rgba16161616,
SKColorType.Rgba1010102,
SKColorType.Rgb101010x,
SKColorType.Gray8,
SKColorType.AlphaF16,
SKColorType.Alpha16,
SKColorType.Bgra1010102,
SKColorType.Bgr101010x
};
private static readonly TextureFormat[] TextureFormats =
{
TextureFormat.Alpha8,
TextureFormat.RGB565,
TextureFormat.RGBA32,
TextureFormat.RGBA32,
TextureFormat.BGRA32,
TextureFormat.RGBAHalf,
TextureFormat.RGBAHalf,
TextureFormat.RGBAFloat,
TextureFormat.RG16,
TextureFormat.RGHalf,
TextureFormat.RG32,
TextureFormat.RGBA64,
TextureFormat.RGBA64,
TextureFormat.RGBA64,
TextureFormat.RGBA32,
TextureFormat.RGBAHalf,
TextureFormat.RGBA64,
TextureFormat.RGBA64,
TextureFormat.RGBA64
};
public static readonly int[] LInts = { 1, 2, 4, 4, 4, 8, 8, 16, 2, 4, 4, 8, 0, 0, 0, 0, 0, 0, 0 };
public static int TryConvertToTextureFormat(this SKColorType skColorType, out TextureFormat textureFormat)
{
var index = Array.IndexOf(SkColorTypes, skColorType);
if (index >= 0)
{
textureFormat = TextureFormats[index];
return LInts[index];
}
textureFormat = TextureFormat.RGBA32;
return 0;
}
public static int TryConvertSkColorTypes(this TextureFormat textureFormat, out SKColorType skColorType)
{
var index = Array.IndexOf(TextureFormats, textureFormat);
if (index >= 0)
{
skColorType = SkColorTypes[index];
return LInts[index];
}
skColorType = SKColorType.Rgba8888;
return 0;
}
}
}