【Unity(C#)】テクスチャを画像データに変換して端末に保存、読み込み

##はじめに
画像の保存処理で手間取ったのでメモを残します。

今回は下記の処理を行います。
**・ImageコンポーネントにアタッチされたSprite(Texture)をPngに変換して保存
・Pngを読み込んでImageコンポーネントにSprite(Texture)としてアタッチ**

##デモ

実際にAndroid端末にて検証したデモがこちらになります。
![Qiita画像保存.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/2bcca132-5954-7d34-c50a-a9b7e541f5f1.gif)

書き込んだ画像を消去したのち復元に成功しました。
端末内に画像データは保存されているのでアプリを終了しても復元が可能です。

---

下記画像はPC内部の保存先ディレクトリを参照した際のスクショです。
ちゃんとPng形式で保存されていました。
![PaintQiitaSaveSS.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/8ce396aa-d44e-97c2-88f3-ad2471cf85bf.png)


##お絵描きの部分のコード
お絵描きの部分の実装は完全にそのまんま使わせていただいてます。

【参考リンク】：[UI.Image にペイントする。線を手書きする。](https://qiita.com/chocho/items/d5125ed051b22e95709c)

スマホ対応部分とテクスチャのリセットだけ下記のように追記してます。

```C#:Painterクラスに追記

    /// <summary>
    /// テクスチャーをリセット
    /// </summary>
    public void ResetTexture()
    {
        var img = GetComponent<Image>();
        var rt = GetComponent<RectTransform>();
        var width = (int)rt.rect.width;
        var height = (int)rt.rect.height;
        texture = new Texture2D(width, height, TextureFormat.ARGB32, false);
        img.sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
        
        Color32[] texColors = Enumerable.Repeat<Color32>(bgColor, width * height).ToArray();
        texture.SetPixels32(texColors);
        texture.Apply();
    }

    void Update()
    {
#if UNITY_EDITOR
        if (Input.GetMouseButtonDown(0))
        {
            beforeMousePos = GetPosition();
        }
        else if (Input.GetMouseButton(0))
        {
            Vector3 v = GetPosition();
            LineTo(beforeMousePos, v, lineColor);
            beforeMousePos = v;
            texture.Apply();
        }
#elif UNITY_ANDROID && !UNITY_EDITOR
        if (0 < Input.touchCount)
        {
            Touch touch = Input.GetTouch(0);

            if (touch.phase == TouchPhase.Began)
            {
                beforeMousePos = GetPosition();
            }
            else if (touch.phase == TouchPhase.Moved || touch.phase == TouchPhase.Stationary)
            {
                Vector3 v = GetPosition();
                LineTo(beforeMousePos, v, lineColor);
                beforeMousePos = v;
                texture.Apply();
            }
        }
#endif
    }
```

##画像の保存と読み込み

ボタンの押下処理はUniRxで実装してます。特に意味はないです。

```C#
using System.IO;
using UniRx;
using UniRx.Triggers;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// テクスチャー ⇔ Png画像 の変換と保存と読み込み
/// </summary>
public class TexturePngConverter : MonoBehaviour
{
    [SerializeField] private Button _saveButton;
    [SerializeField] private Button _loadButton;
    [SerializeField] private Button _resetButton;
    [SerializeField] private Image _paintImage;
    [SerializeField] private Painter _painter;

    private const string IMAGE_SAVE_FOLDER = "Image";
    
    private void Start()
    {
        //セーブボタン
        _saveButton.OnPointerClickAsObservable().Subscribe(_ => ConvertToPngAndSave(GetSavePath(IMAGE_SAVE_FOLDER))).AddTo(this);
        //ロードボタン
        _loadButton.OnPointerClickAsObservable().Subscribe(_ => ConvertToTextureAndLoad(GetSavePath(IMAGE_SAVE_FOLDER))).AddTo(this);
        //リセットボタン
        _resetButton.OnPointerClickAsObservable().Subscribe(_ => _painter.ResetTexture());
    }
    
    ///  /// <summary>
    /// 保存先のパス取得
    /// </summary>
    /// <param name="folderName">区切りのフォルダ名</param>
    /// <returns>保存先のパス</returns>
    private string GetSavePath(string folderName)
    {
        string directoryPath = Application.persistentDataPath + "/" + folderName + "/";

        if (!Directory.Exists(directoryPath))
        {
            //まだ存在してなかったら作成
            Directory.CreateDirectory(directoryPath);
            return directoryPath + "paint.png";
        }

        return directoryPath + "paint.png";
    }

    /// <summary>
    /// 画像に変換＆保存
    /// </summary>
    private void ConvertToPngAndSave(string path)
    {
        //Pngに変換
        byte[] bytes = _paintImage.sprite.texture.EncodeToPNG();
        //保存
        File.WriteAllBytes(path, bytes);
    }

    /// <summary>
    /// テクスチャに変換＆読み込み
    /// </summary>
    private void ConvertToTextureAndLoad(string path)
    {
        //読み込み
        byte[] bytes = File.ReadAllBytes(path);
        //画像をテクスチャに変換
        Texture2D loadTexture = new Texture2D(2, 2); 
        loadTexture.LoadImage(bytes);
        //テクスチャをスプライトに変換
        _paintImage.sprite = Sprite.Create(loadTexture, new Rect(0, 0, loadTexture.width, loadTexture.height), Vector2.zero);
    }
}
```

##Application.persistentDataPath
`Application.persistentDataPath`を呼ぶと
文字通りアプリ内に存在する永続的なパスへアクセスできます。
ここにフォルダを作って画像を保存しています。

##EncodeToPNG
`ImageConversion`クラスから`EncodeToPNG`を呼び出すとbyte配列としてpngデータに変換してくれます。

【参考リンク】：[ImageConversion.EncodeToPNG
](https://docs.unity3d.com/ScriptReference/ImageConversion.EncodeToPNG.html)

##WriteAllBytes,ReadAllBytes
`WriteAllBytes`,`ReadAllBytes`それぞれデータの書き込みと読み込みを担います。
これ書くだけでやってくれるのでありがとうございますって感じです。

##おわりに
書き込み、読み込み時の**リソースの開放**とかがよくわかりませんでした。
書かなくても動いたので"まあいっか"ってなってます。
素直で良い子なので危険なコード書いてたら教えて下さい。

##参考リンク

【参考リンク】：[EncodeToPNG hangs the script
](https://stackoverflow.com/questions/60484595/encodetopng-hangs-the-script)【参考リンク】：[はなちるのマイノート](https://www.hanachiru-blog.com/entry/2019/09/13/214940)