Unity3D中使用SkiaSharp处理Texture2D (上篇)
在 Unity 中,一些时候需要动态修改一些贴图,其中一种方式,就是使用图形处理库,例如 SkiaSharp 或 ImageSharp。这篇文章简单说一下在 Unity 中如何使用 SkiaSharp。
最近一段时间工作比较忙,一直没有更新博客,这一篇文章简单记录一些最近的收获。
这篇文章主要是讲 Texture2D 和 SKBitmap 之间的转换,至于 SkiaSharp 的 API,请自行查阅相关文档。
由于要讲的东西篇幅很长,这里计划分上下两篇,循序渐进,由浅入深讲解,千万不要看到代码就抄,很多是中间的思考过程。
准备工作
需要安装 SkiaSharp ,可以直接使用我的 UnitySkiaSharp,相关代码我以及封装好了,这篇文章只是分析一下转换过程。
借助 PNG 编码转换
这是效率最低的一种方式,除非是特殊情况,请不要使用这种方式。
using SkiaSharp;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(RawImage))]
public class Example0 : MonoBehaviour
{
[SerializeField] private Texture2D tex;
private RawImage _rawImage;
private void Start()
{
_rawImage = GetComponent<RawImage>();
var pngData0 = tex.EncodeToPNG();
var bitmap = SKBitmap.Decode(pngData0);
var pngData1 = bitmap.Encode(SKEncodedImageFormat.Png, 0).ToArray();
var tex0 = new Texture2D(bitmap.Width, bitmap.Height);
tex0.LoadImage(pngData1);
_rawImage.texture = tex0;
_rawImage.GetComponent<RectTransform>().sizeDelta = new Vector2(tex0.width, tex0.height);
}
}
运行效果就不展示了。
使用颜色填充
前面介绍的这种方法,需要编码为 png 格式,然后再解码,中间的编码和解码过程需要浪费大量的性能。
试想一下,能否获取图片中的每一个点的颜色,然后再填充到新的图片中。
在此之前,需要先编写一个转换程序,用于在 Unity 和 SkiaSharp 之间转换颜色
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);
}
}
}
然后使用 GetPixels32
和 SetPixels32
操作 Texture2D,简单写个示例。
using System.Linq;
using SkiaSharp;
using SkiaSharp.Unity;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(RawImage))]
public class Example1 : MonoBehaviour
{
[SerializeField] private string texPath;
private RawImage _rawImage;
private void Start()
{
_rawImage = GetComponent<RawImage>();
var bitmap = SKBitmap.Decode(texPath);
var colors = bitmap.Pixels.AsParallel().AsOrdered().Select(s => s.ToUnityColor32()).ToArray();
var tex0 = new Texture2D(bitmap.Width, bitmap.Height);
tex0.SetPixels32(colors);
tex0.Apply();
_rawImage.texture = tex0;
_rawImage.GetComponent<RectTransform>().sizeDelta = new Vector2(tex0.width, tex0.height);
}
}
运行完成后,会发现图片是反的。

这是 Unity 中 Texture2D 和 SkiaSharp 图片读取位置不一致导致的。简单画了一张示意图,可以看一下结构。

由图可知,需要调整颜色的像素点顺序。知道原理后,修改一下代码。
using System;
using System.Buffers;
using System.Linq;
using SkiaSharp;
using SkiaSharp.Unity;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(RawImage))]
public class Example1 : MonoBehaviour
{
[SerializeField] private string texPath;
private RawImage _rawImage;
private void Start()
{
_rawImage = GetComponent<RawImage>();
var bitmap = SKBitmap.Decode(texPath);
var skcolors = bitmap.Pixels.AsSpan();
var writer = new ArrayBufferWriter<SKColor>(bitmap.Width * bitmap.Height);
for (var i = bitmap.Height - 1; i >= 0; i--) writer.Write(skcolors.Slice(i * bitmap.Width, bitmap.Width));
var colors = writer.WrittenSpan.ToArray().AsParallel().AsOrdered().Select(s => s.ToUnityColor32()).ToArray();
var tex0 = new Texture2D(bitmap.Width, bitmap.Height);
tex0.SetPixels32(colors);
tex0.Apply();
_rawImage.texture = tex0;
_rawImage.GetComponent<RectTransform>().sizeDelta = new Vector2(tex0.width, tex0.height);
}
}
修改代码,再次运行之后就可以正确加载贴图了。

反过来,从 Texture2D 到 SKBitmap 也是一样的,使用 GetPixels32
获取数据,这里就不再赘述。
总结
这一篇简单说了一下 Texture2D 和 SKBitmap 之间的转换方法,下一篇文章,会说明一种更高效的转换方法。