はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】GIFをサポートするようにできる「mgGif」というライブラリの使い方(Imageへの適応例つき)

はじめに

今回はmgGifというライブラリを紹介したいと思います。

A unity library to parse a GIF file and extracts the images, just for fun

// DeepL翻訳
GIF ファイルを解析し、画像を抽出するための Unity ライブラリです。

github.com

また今回実験として利用させていただいたGif画像は以下から借りてきたものです。
http://katus-gifani.sakura.ne.jp/sozai01.html

インストール方法

以下のページのmgGif.csの中身をプロジェクトに導入してください。

github.com

使い方

最初に言っておくと、この使い方がよく分からなくても後述のAnimatedTexture.csもしくはAnimatedImage.csのコードをコピペすれば大丈夫ではあります。

private void Start()
{
    // Assets/StreamingAssets以下の相対パスを指定し、gifのデータを読み込む
    // 以下のコードだと、「Assets/StreamingAssets/sample.gif」
    byte[] data = File.ReadAllBytes(Path.Combine(Application.streamingAssetsPath, "sample.gif"));

    // dataからRGBのデータと間隔を抜き出す
    // NOTE : unsafeを有効にしている場合はDispose必須、有効にしていない場合(デフォルト)ではしなくても大丈夫ではある
    using( var decoder = new MG.GIF.Decoder( data ) )
    {
        // 次の画像の情報を抜き出す
        var img = decoder.NextImage();

        while( img != null )
        {
            // 画像データ
            Texture2D tex = img.CreateTexture();
                
            // 間隔
            int delay = img.Delay;
        
            // 次の画像の情報を抜き出す
            img = decoder.NextImage();
        }
    }
}

コンポーネントとして利用できるようにする

公式のサンプルとしてAnimatedTexture.csがあるので、まずはこれを使ってみます。
github.com

QuadAnimatedTexture.csをアタッチするとgif表示することができます。
またFileNameAssets/StreamingAssets以下からの相対パスを記載してください。

AnimatedTexture.cs
AnimatedTexture.cs

Imageに適応する

ただImageGifを貼り付けたい場面の方が多いかなと個人的には思うので、コードを書いてみました。

using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Image))]
public class AnimatedImage : MonoBehaviour
{
    [SerializeField, Header("Relative path from StreamingAssets folder")] private string filePath;
        
    private Image _image;

    private readonly List<Sprite> _frames     = new List<Sprite>();
    private readonly List<float>  _frameDelay = new List<float>();

    private int _currentFrame = 0;
    private float _time = 0.0f;
        
    private void Start()
    {
        if (string.IsNullOrWhiteSpace(filePath)) return;
        _image = GetComponent<Image>();

        var path = Path.Combine(Application.streamingAssetsPath, filePath);

        using( var decoder = new MG.GIF.Decoder( File.ReadAllBytes( path )))
        {
            var img = decoder.NextImage();

            while( img != null )
            {
                _frames.Add( Texture2DtoSprite(img.CreateTexture()));
                _frameDelay.Add(img.Delay/1000.0f);
                img = decoder.NextImage();
            }
        }

        _image.sprite = _frames[0];
    }

    private void Update()
    {
        if (_frames == null) return;

        _time += Time.deltaTime;

        if( _time >= _frameDelay[ _currentFrame ] )
        {
            _currentFrame = ( _currentFrame + 1 ) % _frames.Count;
            _time = 0.0f;

            _image.sprite = _frames[_currentFrame];
        }
    }

    private static Sprite Texture2DtoSprite(Texture2D tex)
        => Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), Vector2.zero);
}
AnimatedImage.cs

パフォーマンスアップ

mgGifの冒頭にある#define mgGIF_UNSAFEのコメントアウト削除し、プロジェクトでunsafeを利用できるようにすると(ProjectSettings/Player/OtherSettings/ScriptCompilation/Allow 'unsafe' Codeを有効化 or .asmdefAllow unsafe Codeの有効化)、マネージドヒープを利用しないようになりGC.Allocを削減することができるようです。

#define mgGIF_UNSAFE

またパフォーマンス(処理速度?)自体も上がるっぽい?

For an additional performance improvement, uncomment mgGIF_UNSAFE at the top of the file and allow unsafe code compilation in the assembly.

https://github.com/gwaredd/mgGif

ただしDisposeをし忘れるとメモリリークを起こすので要注意です。

var path = Path.Combine( Application.streamingAssetsPath, "sample.gif" );

var decoder = new MG.GIF.Decoder(File.ReadAllBytes(path));
        
// ---色々利用---
        
// 利用し終わったらDisposeを必ず呼ぶ
decoder.Dispose();