跳至主要內容

Unity3D中使用SkiaSharp处理Texture2D (上篇)

MonoLogueChiUnity大约 3 分钟

在 Unity 中,一些时候需要动态修改一些贴图,其中一种方式,就是使用图形处理库,例如 SkiaSharp 或 ImageSharp。这篇文章简单说一下在 Unity 中如何使用 SkiaSharp。

最近一段时间工作比较忙,一直没有更新博客,这一篇文章简单记录一些最近的收获。

这篇文章主要是讲 Texture2D 和 SKBitmap 之间的转换,至于 SkiaSharp 的 API,请自行查阅相关文档。

由于要讲的东西篇幅很长,这里计划分上下两篇,循序渐进,由浅入深讲解,千万不要看到代码就抄,很多是中间的思考过程。

准备工作

需要安装 SkiaSharpopen in new window ,可以直接使用我的 UnitySkiaSharpopen in new window,相关代码我以及封装好了,这篇文章只是分析一下转换过程。

借助 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);
    }
  }
}

然后使用 GetPixels32SetPixels32 操作 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 之间的转换方法,下一篇文章,会说明一种更高效的转换方法。