鸟语天空
Unity中播放GIF
post by:追风剑情 2021-5-10 9:54

GIF文件格式介绍 https://www.jianshu.com/p/4fabac6b15b3
GIF文件格式介绍 https://blog.csdn.net/wzy198852/article/details/17266507
GIFLIB 项目

下载GIF播放插件 https://github.com/WestHillApps/UniGif

如果播放时GIF背景显示纯黑色,可以修改下源码让GIF的背景显示为透明色:

修改 UniGifDecoder.cs 中的 GetColorTableAndSetBgColor() 方法:


/// <summary>
/// Get color table and set background color (local or global)
/// </summary>
private static List<byte[]> GetColorTableAndSetBgColor(GifData gifData, ImageBlock imgBlock, int transparentIndex, out Color32 bgColor)
{
	List<byte[]> colorTable = imgBlock.m_localColorTableFlag ? imgBlock.m_localColorTable : gifData.m_globalColorTableFlag ? gifData.m_globalColorTable : null;

	// 注释掉原来的代码
	//if (colorTable != null)
	//{
	//    // Set background color from color table
	//    byte[] bgRgb = colorTable[gifData.m_bgColorIndex];
	//    bgColor = new Color32(bgRgb[0], bgRgb[1], bgRgb[2], (byte)(transparentIndex == gifData.m_bgColorIndex ? 0 : 255));
	//}
	//else
	//{
	//    bgColor = Color.black;
	//}

	// 直接修改成透明背景
	bgColor = Color.black;
	bgColor.a = 0;

	return colorTable;
}


GIF文件结构

11111.png

1111.png

图形控制器扩展(graphic control extension)中的透明颜色索引(transparent color index)对应全局颜色表(global color table)中的索引。

GIF辅助类


#define DEBUG_GIF_DECODE
using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
/// <summary>
/// GIF 辅助类
/// GIF用的LZW压缩算法能将图像尺寸压缩20%~25%
/// 字符=ASCII: 
///     'G'=71,'I'=73,'F'=70,'7'=55,'8'=56,'9'=57,'a'=97
/// 参考 https://blog.csdn.net/Swallow_he/article/details/76165202
/// </summary>
public sealed class GIFHelper
{
    #region 文件头
    //判断是否为GIF文件
    public static bool IsGIF(byte[] bytes)
    {
        byte b0 = bytes[0];
        byte b1 = bytes[1];
        byte b2 = bytes[2];
        return 71 == b0 && 73 == b1 && 70 == b2;
    }

    //判断GIF版本是否为 GIF87a
    public static bool IsGIF87a(byte[] bytes)
    {
        if (!IsGIF(bytes))
            return false;

        byte b3 = bytes[3];
        byte b4 = bytes[4];
        byte b5 = bytes[5];
        return 56 == b3 && 55 == b4 && 97 == b5;
    }

    //判断GIF版本是否为 GIF89a
    //这个版本才支持动画和透明背景
    public static bool IsGIF89a(byte[] bytes)
    {
        if (!IsGIF(bytes))
            return false;

        byte b3 = bytes[3];
        byte b4 = bytes[4];
        byte b5 = bytes[5];
        return 56 == b3 && 57 == b4 && 97 == b5;
    }

    //获取标记域和版本号
    public static string SignatureVersion(byte[] bytes)
    {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 6; i++)
            sb.Append((char)bytes[i]);
        return sb.ToString();
    }
    #endregion

    #region 逻辑描述块(LogicalScreen Descriptor), 包含7个字节

    #region 图片宽高
    //获取逻辑显示屏宽度 (图片宽度), 2字节
    public static ushort LogicalScreenWidth(byte[] bytes)
    {
        //byte b6 = bytes[6];
        //byte b7 = bytes[7];
        ////小端
        //short width = (short)((b7 << 8) | (b6 & 0xff));
        ushort width = BitConverter.ToUInt16(bytes, 6);
        return width;
    }

    //获取逻辑显示屏高度 (图片高度), 2字节
    public static ushort LogicalScreenHeight(byte[] bytes)
    {
        //byte b8 = bytes[8];
        //byte b9 = bytes[9];
        ////小端
        //short height = (short)((b9 << 8) | (b8 & 0xff));
        ushort height = BitConverter.ToUInt16(bytes, 8);
        return height;
    }
    #endregion

    #region 包装域(Packed Fields) (G CR S Size), 1字节
    //Size of Global Color Table 占 3bit
    //Sort Flag 占 1bit
    //Color Resolution 占 3bit
    //Global Color Table Flag 占 1bit

    //全局彩色表大小,代表每个像素的位数, 7bit可表示0~255
    public static int SizeOfGlobalColorTable(byte[] bytes)
    {
        byte b10 = bytes[10];
        byte size = (byte)(b10 & 7); //7: 00000111
        //表项数目
        int table_size = (int)Math.Pow(2, size + 1);
        //每个表项由3个字节组成(R、G、B)
        //彩色表的字节数等于 3 * table_size
        return table_size;
    }

    //彩色表排序标志
    //Flag=1: 表示全局彩色表中的颜色存在重要性排序,重要的颜色被排在前面
    public static byte SortFlag(byte[] bytes)
    {
        byte b10 = bytes[10];
        byte flag = (byte)((b10 >> 3) & 1);//00001000
        return flag;
    }

    //调色板中的基色占几bit
    //例如 CR=3, 表示占4bit
    public static byte ColorResolution(byte[] bytes)
    {
        byte b10 = bytes[10];
        byte cr = (byte)((b10 >> 4) & 7);//01110000
        return cr;
    }

    //全局彩色表标志
    //G=1: 表示存在全局彩色表
    public static byte GlobalColorTableFlag(byte[] bytes)
    {
        byte b10 = bytes[10];
        byte G = (byte)(b10 >> 7);//?0000000
        return G;
    }
    #endregion

    #region 背景颜色索引
    //获取背景颜色索引(彩色表位置)
    //如果Global Color Table Flag为0,则该值也设为0
    public static byte BackgroundColorIndex(byte[] bytes)
    {
        byte b11 = bytes[11];
        return b11;
    }
    #endregion

    #region 像素宽高比
    //像素宽高比,是原始图像像素的宽高比的一个近似值
    //宽高比计算公式: Aspect Ratio = (Pixel AspectRatio + 15) / 64
    public static byte PixelAspectRatio(byte[] bytes)
    {
        byte b12 = bytes[12];
        return b12;
    }
    #endregion

    #region 全局彩色表

    //获取全局彩色表
    public static List<byte[]> GlobalColorTable(byte[] bytes)
    {
        var globalColorTable = new List<byte[]>();
        if (GlobalColorTableFlag(bytes) == 0)
            return globalColorTable;
        
        int byteIndex = 13;
        int sizeOfGlobalColorTable = SizeOfGlobalColorTable(bytes);
        // Global Color Table(0~255×3 Bytes)
        for (int i = byteIndex; i < byteIndex + (sizeOfGlobalColorTable * 3); i += 3)
        {
            globalColorTable.Add(new byte[] { bytes[i], bytes[i + 1], bytes[i + 2] });
        }
        return globalColorTable;
    }
    #endregion

    #endregion
}

/// <summary>
/// GIF图像
/// </summary>
public class GIFImage
{
    //头信息
    public GIFHeader Header { get; private set; }
    //屏幕逻辑描述符
    public GIFLogicalScreenDescriptor LogicalScreenDescriptor { get; private set; }
    //全局颜色表
    public GIFGlobalColorTable GlobalColorTable { get; private set; }
    //图形控制扩展
    public List<GIFGraphicControlExtension> graphicControlExtList { get; private set; }
    //文本扩展
    public List<GIFPlainTextExtension> plainTextExtList { get; private set; }
    //应用程序扩展
    public List<GIFApplicationExtension> applicationExtList { get; private set; }
    //注释扩展
    public List<GIFCommentExtension> commentExtList { get; private set; }
    //图像描述符
    public List<GIFImageDescriptor> imageDescriptorList { get; private set; }
    //Texture2D 列表
    public List<Texture2D> textures { 
        get {
            if (gifData == null)
                return new List<Texture2D>();
            return gifData.textures; 
        }
    }

    private GIFData gifData;

    /// <summary>
    /// 构造方法
    /// </summary>
    /// <param name="bytes">gif文件二进制数据</param>
    public GIFImage(byte[] bytes)
    {
        LoadFileData(bytes);
    }

    //载入GIF文件数据
    public void LoadFileData(byte[] bytes)
    {
        GIFDebug.Log("<i><b>*** gif decode start... ***</b></i>");

        gifData = new GIFData();
        int byteIndex = 0;
        Header = new GIFHeader(bytes, ref byteIndex);
        LogicalScreenDescriptor = new GIFLogicalScreenDescriptor(bytes, ref byteIndex);
        if (LogicalScreenDescriptor.packedField.globalColorTableFlag == 1)
        {
            GlobalColorTable = new GIFGlobalColorTable(bytes, ref byteIndex,
                LogicalScreenDescriptor.packedField.sizeOfGlobalColorTable);
            gifData.m_globalColorTable = GlobalColorTable;
        }
        gifData.m_logicalScreenDescriptor = LogicalScreenDescriptor;

        graphicControlExtList = new List<GIFGraphicControlExtension>();
        plainTextExtList = new List<GIFPlainTextExtension>();
        applicationExtList = new List<GIFApplicationExtension>();
        commentExtList = new List<GIFCommentExtension>();
        imageDescriptorList = new List<GIFImageDescriptor>();

        while (true)
        {
            //特殊标志
            //0x21: 扩展入口(Extension Introducer)
            //0x3b: GIF文件结束标志
            byte specialFlag = bytes[byteIndex];
            byteIndex++;

            //扩展入口
            if (0x21 == specialFlag)
            {
                //扩展类型
                byte extensionFlag = bytes[byteIndex];
                byteIndex++;
                switch (extensionFlag)
                {
                    case 0xf9:// Graphic Control Extension(0x21 0xf9)
                        var gExt = new GIFGraphicControlExtension(bytes, ref byteIndex);
                        graphicControlExtList.Add(gExt);
                        gifData.m_graphicControlExtension = gExt;
                        break;
                    case 0xfe:// Comment Extension(0x21 0xfe)
                        var cExt = new GIFCommentExtension(bytes, ref byteIndex);
                        commentExtList.Add(cExt);
                        break;
                    case 0x01:// Plain Text Extension(0x21 0x01)
                        var pExt = new GIFPlainTextExtension(bytes, ref byteIndex);
                        plainTextExtList.Add(pExt);
                        break;
                    case 0xff:// Application Extension(0x21 0xff)
                        var aExt = new GIFApplicationExtension(bytes, ref byteIndex);
                        applicationExtList.Add(aExt);
                        break;
                    default:
                        break;
                }
            }
            //图像描述符(Image Descriptor)
            else if (0x2c == specialFlag)
            {
                var imgDes = new GIFImageDescriptor(bytes, ref byteIndex, gifData);
                imageDescriptorList.Add(imgDes);
            }
            //GIF结束
            else if (0x3b == specialFlag)
            {
                GIFDebug.Log("<i><b>*** gif decode completed (0x3b) ***</b></i>");
                break;
            }
        }
    }
}

/// <summary>
/// GIF文件头
/// </summary>
public sealed class GIFHeader
{
    //"GIF"
    public string signature;
    //"87a" 或 "89a"
    public string version;

    public GIFHeader(byte[] bytes, ref int byteIndex)
    {
        signature = BitConverter.ToString(bytes, byteIndex, 3);
        byteIndex += 3;
        version = BitConverter.ToString(bytes, byteIndex, 3);
        byteIndex += 3;
    }
}

/// <summary>
/// 逻辑屏幕描述符
/// </summary>
public sealed class GIFLogicalScreenDescriptor
{
    //画布宽度
    public int canvasWidth;
    //画布高度
    public int canvasHeight;
    public GIFPackedField packedField;
    //表示 GIF 的背景色在 Global Color Table 中的索引
    public byte backgroundColorIndex;
    //表示用于计算原始图像中像素宽高比的近似因子,一般情况为 0
    public byte pixelAspectRatio;

    public GIFLogicalScreenDescriptor(byte[] bytes, ref int byteIndex)
    {
        canvasWidth = BitConverter.ToInt16(bytes, byteIndex);
        byteIndex += 2;
        canvasHeight = BitConverter.ToInt16(bytes, byteIndex);
        byteIndex += 2;
        packedField = new GIFPackedField(bytes[byteIndex]);
        byteIndex++;
        backgroundColorIndex = bytes[byteIndex];
        byteIndex++;
        pixelAspectRatio = bytes[byteIndex];
        byteIndex++;

        Print();
        packedField.Print();
    }

    public void Print()
    {
        GIFDebug.Log("<color=green>[GIFLogicalScreenDescriptor]</color>");
        GIFDebug.LogFormat("->width={0}", canvasWidth);
        GIFDebug.LogFormat("->height={0}", canvasHeight);
        GIFDebug.LogFormat("->backgroundColorIndex={0}", backgroundColorIndex);
        GIFDebug.LogFormat("->pixelAspectRatio={0}", pixelAspectRatio);
    }

    /// <summary>
    /// GIF包装域
    /// </summary>
    public sealed class GIFPackedField
    {
        //全局彩色表大小
        public int sizeOfGlobalColorTable;
        //彩色表排序标志
        public byte sortFlag;
        //彩色分辨率
        public byte colorResolution;
        //全局彩色表标志, 1:表示存在全局颜色表
        public byte globalColorTableFlag;

        public GIFPackedField(byte b)
        {
            byte size = (byte)(b & 7); //7: 00000111
                                       //表项数目
            sizeOfGlobalColorTable = (int)Math.Pow(2, size + 1);
            //每个表项由3个字节组成(R、G、B)
            //彩色表的字节数等于 3 * table_size

            sortFlag = (byte)((b >> 3) & 1);//00001000
            colorResolution = (byte)((b >> 4) & 7);//01110000
            globalColorTableFlag = (byte)(b >> 7);//10000000
        }

        public void Print()
        {
            GIFDebug.LogFormat("->sizeOfGlobalColorTable={0}", sizeOfGlobalColorTable);
            GIFDebug.LogFormat("->sortFlag={0}", sortFlag);
            GIFDebug.LogFormat("->colorResolution={0}", colorResolution);
            GIFDebug.LogFormat("->globalColorTableFlag={0}", globalColorTableFlag);
        }
    }
}

/// <summary>
/// GIF全局颜色表
/// </summary>
public sealed class GIFGlobalColorTable
{
    public List<byte[]> Table { get; private set; }

    public GIFGlobalColorTable(byte[] bytes, ref int byteIndex, int sizeOfGlobalColorTable)
    {
        Table = new List<byte[]>();
        // Global Color Table(0~255×3 Bytes)
        for (int i = byteIndex; i < byteIndex + (sizeOfGlobalColorTable * 3); i += 3)
        {
            Table.Add(new byte[] { bytes[i], bytes[i + 1], bytes[i + 2] });
        }
        byteIndex += sizeOfGlobalColorTable * 3;
    }
}

/// <summary>
/// GIF图形控制扩展
/// </summary>
public sealed class GIFGraphicControlExtension
{
    //表示接下来的有效数据字节数
    public byte byteSize;
    //是一个包装字段,内部不同位的意义也不同
    public GIFPackedField packedField;
    //表示 GIF 动图每一帧之间的间隔,单位为百分之一秒。当为 0 时间隔由解码器管理
    public short delayTime;
    //当 Transparent Flag 为 1 时,此字节有效,表示此索引在 Global Color Table 中对应的颜色将被当做透明色做处理。
    public byte transparentColorIndex;
    //表示 Extension 到此结束
    public byte blockTerminator;//总是0

    public GIFGraphicControlExtension(byte[] bytes, ref int byteIndex)
    {
        GIFDebug.LogFormat("<color=green>GIFGraphicControlExtension</color> byteIndex={0}", byteIndex);
        byteSize = bytes[byteIndex];
        byteIndex++;
        packedField = new GIFPackedField(bytes[byteIndex]);
        byteIndex++;
        delayTime = BitConverter.ToInt16(bytes, byteIndex);
        byteIndex += 2;
        transparentColorIndex = bytes[byteIndex];
        byteIndex++;
        blockTerminator = bytes[byteIndex];
        byteIndex++;
        //Debug.LogFormat("-- end byteIndex={0}", byteIndex);
    }

    //包装字段
    public sealed class GIFPackedField
    {
        //当该值为 1 时,后面的 Transparent Color Index 指定的颜色将被当做透明色处理。为 0 则不做处理。
        public byte transparentColorFlag; //1bit
        //表示是否需要在得到用户的输入时才进行下一帧的输入(具体用户输入指什么视应用而定)。
        //0: 表示无需用户输入
        //1: 表示需要用户输入
        public byte userInputFlag; //1bit
        //表示在进行逐帧渲染时,前一帧留下的图像作何处理:
        //0: 不做任何处理
        //1: 保留前一帧图像,在此基础上进行渲染
        //2: 渲染前将图像置为背景色
        //3: 将被下一帧覆盖的图像重置
        public byte disposalMethod;//3bit
        //保留位,暂无用处
        public byte reservedForFutureUse;//3bit

        public GIFPackedField(byte b)
        {
            transparentColorFlag = (byte)(b & 1);//00000001
            userInputFlag = (byte)((b >> 1) & 1);//00000010
            disposalMethod = (byte)((b >> 2) & 7);//00011100
            reservedForFutureUse = (byte)((b >> 5) & 7);//11100000
        }
    }
}

/// <summary>
/// GIF文本扩展 (存储额外信息)
/// </summary>
public sealed class GIFPlainTextExtension
{
    public GIFPlainTextExtension(byte[] bytes, ref int byteIndex)
    {
        GIFDebug.LogFormat("<color=green>GIFPlainTextExtension</color> byteIndex={0}", byteIndex);
        //暂时不关心这部分数据,直接找结束标记0
        while (bytes[byteIndex++] != 0) ;
    }
}

/// <summary>
/// GIF应用扩展 (存储额外信息)
/// </summary>
public sealed class GIFApplicationExtension
{
    public GIFApplicationExtension(byte[] bytes, ref int byteIndex)
    {
        GIFDebug.LogFormat("<color=green>GIFApplicationExtension</color> byteIndex={0}", byteIndex);
        //暂时不关心这部分数据,直接找结束标记0
        while (bytes[byteIndex++] != 0) ;
    }
}

/// <summary>
/// GIF注释扩展 (存储额外信息)
/// </summary>
public sealed class GIFCommentExtension
{
    public GIFCommentExtension(byte[] bytes, ref int byteIndex)
    {
        GIFDebug.LogFormat("<color=green>GIFCommentExtension</color> byteIndex={0}", byteIndex);
        //暂时不关心这部分数据,直接找结束标记0
        while (bytes[byteIndex++] != 0) ;
    }
}

/// <summary>
/// GIF图像描述符
/// </summary>
public sealed class GIFImageDescriptor
{
    //该值表示当前帧图像渲染位置离画布左边的距离
    public short left;
    //该值表示当前帧图像渲染位置离画布上边的距离
    public short top;
    //该值表示当前帧图像的宽度
    public short width;
    //该值表示当前帧图像的高度
    public short height;
    //这是一个包装字段,内部不同位的意义也不同
    public GIFPackedField packedField;
    public GIFLocalColorTable localColorTable;
    public GIFImageData imageData;
    private GIFData gifData;

    public GIFImageDescriptor(byte[] bytes, ref int byteIndex, GIFData gifData)
    {
        GIFDebug.LogFormat("<color=green>GIFImageDescriptor</color> byteIndex={0}", byteIndex);
        this.gifData = gifData;
        left = BitConverter.ToInt16(bytes, byteIndex);
        byteIndex += 2;
        top = BitConverter.ToInt16(bytes, byteIndex);
        byteIndex += 2;
        width = BitConverter.ToInt16(bytes, byteIndex);
        byteIndex += 2;
        height = BitConverter.ToInt16(bytes, byteIndex);
        byteIndex += 2;
        packedField = new GIFPackedField(bytes[byteIndex]);
        byteIndex++;

        if (packedField.localColorTableFlag == 1)
        {
            localColorTable = new GIFLocalColorTable(bytes, ref byteIndex, packedField.sizeOfLocalColorTable);
        }

        bool interlaceFlag = (packedField.interlaceFlag == 1);
        imageData = new GIFImageData(bytes, ref byteIndex, width, height, interlaceFlag);

        //利用解压出来的图像数据生成Texture2D
        Texture2D tex = CreateTexture2D();
        gifData.textures.Add(tex);
    }

    private List<byte[]> GetColorTable()
    {
        //存在局部颜色表,则使用局部颜色表,否则使用全局颜色表
        List<byte[]> colorTable = (packedField.localColorTableFlag == 1) ? localColorTable.Table : gifData.m_globalColorTable.Table;
        return colorTable;
    }

    public Texture2D CreateTexture2D()
    {
        List<byte[]> colorTable = GetColorTable();

        int canvasWidth = gifData.m_logicalScreenDescriptor.canvasWidth;
        int canvasHeight = gifData.m_logicalScreenDescriptor.canvasHeight;
        Texture2D tex = new Texture2D(canvasWidth, canvasHeight, TextureFormat.ARGB32, false);
        tex.filterMode = FilterMode.Bilinear;
        tex.wrapMode = TextureWrapMode.Clamp;

        //判断背景色
        byte transparentIndex = gifData.m_graphicControlExtension.transparentColorIndex;
        int bgColorIndex = gifData.m_logicalScreenDescriptor.backgroundColorIndex;
        byte[] bgRgb = colorTable[bgColorIndex];
        Color32 bgColor = new Color32(bgRgb[0], bgRgb[1], bgRgb[2], (byte)(transparentIndex == bgColorIndex ? 0 : 255));
        Color32[] pix;
        bool filledTexture = false;

        //初始化图像像素
        byte disposalMethod = gifData.m_graphicControlExtension.packedField.disposalMethod;
        GIFDebug.LogFormat("disposalMethod={0}", disposalMethod);
        switch (disposalMethod)
        {
            case 0://不做任何处理

                break;
            case 1://保留前一帧图像,在此基础上进行渲染
                if (gifData.textures.Count > 0)
                {
                    Texture2D preTex = gifData.textures[gifData.textures.Count - 1];
                    pix = preTex.GetPixels32();
                    tex.SetPixels32(pix);
                    tex.Apply();
                    filledTexture = true;
                }
                break;
            case 2://渲染前将图像置为背景色
                pix = new Color32[tex.width * tex.height];
                for (int i = 0; i < pix.Length; i++)
                {
                    pix[i] = bgColor;
                }
                tex.SetPixels32(pix);
                tex.Apply();
                filledTexture = true;
                break;
            case 3://将被下一帧覆盖的图像重置

                break;
        }

        int dataIndex = 0;
        //dataIndex是从图像的左上角开始存储的,图像的原点在左下角
        for (int y = canvasHeight - 1; y >= 0; y--)
        {
            int row = canvasHeight - y - 1;

            for (int x = 0; x < canvasWidth; x++)
            {
                //(left,top)左上角坐标
                if (x < left || x >= left + width ||
                    row < top || row >= top + height)
                {
                    if (!filledTexture)
                        tex.SetPixel(x, y, bgColor);
                    continue;
                }

                //越界检测
                if (dataIndex >= imageData.decodedData.Count)
                {
                    if (filledTexture == false)
                    {
                        tex.SetPixel(x, y, bgColor);
                        if (dataIndex == imageData.decodedData.Count)
                        {
                            GIFDebug.LogError("dataIndex exceeded the size of decodedData. dataIndex:" + dataIndex + " decodedData.Length:" + imageData.decodedData.Count + " y:" + y + " x:" + x);
                        }
                    }
                    dataIndex++;
                    continue;
                }

                //像素索引对应颜色表中的索引
                int colorIndex = imageData.decodedData[dataIndex];
                //越界检测
                if (colorTable == null || colorTable.Count <= colorIndex)
                {
                    if (filledTexture == false)
                    {
                        tex.SetPixel(x, y, bgColor);
                        if (colorTable == null)
                        {
                            GIFDebug.LogError("colorIndex exceeded the size of colorTable. colorTable is null. colorIndex:" + colorIndex);
                        }
                        else
                        {
                            GIFDebug.LogError("colorIndex exceeded the size of colorTable. colorTable.Count:" + colorTable.Count + " colorIndex:" + colorIndex);
                        }
                    }
                    dataIndex++;
                    continue;
                }

                byte[] rgb = colorTable[colorIndex];
                //Alpha的判断规则
                byte alpha = transparentIndex >= 0 && transparentIndex == colorIndex ? (byte)0 : (byte)255;
                if (filledTexture == false || alpha != 0)
                {
                    Color32 color = new Color32(rgb[0], rgb[1], rgb[2], alpha);
                    tex.SetPixel(x, y, color);
                }

                dataIndex++;
            }
        }
        tex.Apply();
        return tex;
    }

    public sealed class GIFPackedField
    {
        //本地颜色表大小
        public int sizeOfLocalColorTable;
        //保留位
        public byte reservedForFutureUse;
        //如果需要 Local Color Table 的话,这个字段表示其排列顺序,同 Global Color Table
        public byte sortFlag;
        //表示是否需要隔行扫描。1 为需要,0 为不需要
        public byte interlaceFlag;
        //表示下一帧图像是否需要一个独立的颜色表。1 为需要,0 为不需要
        public byte localColorTableFlag;

        public GIFPackedField(byte b)
        {
            byte size = (byte)(b & 7); //7: 00000111
            //表项数目
            sizeOfLocalColorTable = (int)Math.Pow(2, size + 1);
            //每个表项由3个字节组成(R、G、B)
            //彩色表的字节数等于 3 * table_size
            reservedForFutureUse = (byte)((b >> 3) & 3);
            sortFlag = (byte)((b >> 5) & 1);
            interlaceFlag = (byte)((b >> 6) & 1);
            localColorTableFlag = (byte)((b >> 7) & 1);
        }
    }
}

/// <summary>
/// GIF本地颜色表
/// </summary>
public sealed class GIFLocalColorTable
{
    public List<byte[]> Table { get; private set; }

    public GIFLocalColorTable(byte[] bytes, ref int byteIndex, int sizeOfLocalColorTable)
    {
        Table = new List<byte[]>();
        // Global Color Table(0~255×3 Bytes)
        for (int i = byteIndex; i < byteIndex + (sizeOfLocalColorTable * 3); i += 3)
        {
            Table.Add(new byte[] { bytes[i], bytes[i + 1], bytes[i + 2] });
        }
        byteIndex += (sizeOfLocalColorTable * 3);
    }
}

/// <summary>
/// GIF图像数据
/// </summary>
public sealed class GIFImageData
{
    //LZW最小编码表大小
    public byte lzwMinimumCodeSize;
    //压缩数据(LZW Data)
    public List<byte> lzwData;
    //解压数据
    public List<byte> decodedData;
    //0x00 表示Image Data结束
    public byte blockTerminator = 0;
    private int width;
    private int height;
    private int needDataSize;//该帧有多少个像素
    private bool interlaceFlag;

    public GIFImageData(byte[] bytes, ref int byteIndex, int width, int height, bool interlaceFlag)
    {
        this.width = width;
        this.height = height;
        this.needDataSize = width * height;
        this.interlaceFlag = interlaceFlag;
        lzwMinimumCodeSize = bytes[byteIndex];
        GIFDebug.LogFormat("->>[GIFImageData] lzwMinimumCodeSize={0}", lzwMinimumCodeSize);
        byteIndex++;
        lzwData = new List<byte>();
        //因为图像数据长度是用byte表示,所以可能有多个子块数据
        while (ReadDataSubBlock(bytes, ref byteIndex) > 0) ;
        //对图像数据解码
        DecodeLZWData();
    }

    private int ReadDataSubBlock(byte[] bytes, ref int byteIndex)
    {
        //size取值范围[0x1~0xFF],0表示整个Image Data结束, 
        byte size = bytes[byteIndex];
        byteIndex++;
        try
        {
            int length = byteIndex + size;
            while (byteIndex < length)
                lzwData.Add(bytes[byteIndex++]);
        }
        catch (IndexOutOfRangeException e)
        {
            UnityEngine.Debug.LogErrorFormat("{0}\nbyteIndex={1}", e.Message, byteIndex);
        }
        return size;
    }

    /// <summary>
    /// 参考 https://www.pianshen.com/article/3347116142/
    /// 对LZW数据解码 (参考 UniGif)
    /// 1、gif中最大编码长度为12位,能表示的最大数字为4096,因此,编译表中序号不能超过4096
    /// 2、gif最大支持256色
    /// 3、gif定义了clear code,清除标记为原始数据长度+1
    /// 4、gif定义了end code,结束标记的值为清除标记+1
    /// </summary>
    private void DecodeLZWData()
    {
        decodedData = new List<byte>();

        int lzwCodeSize = 0;//数据块大小,这是个变长值
        int clearCode = 0;
        int finishCode = 0;

        //初始化编码表
        var dic = new Dictionary<int, string>();
        InitDictionary(dic, lzwMinimumCodeSize, out lzwCodeSize, out clearCode, out finishCode);
        
        //转成bit数组
        var bitData = new BitArray(lzwData.ToArray());
        //用来保存解码后的字节数组
        byte[] output = new byte[needDataSize];
        int outputAddIndex = 0;

        bool dicInitFlag = false;
        string prevEntry = null;
        int bitDataIndex = 0;

        while (bitDataIndex < bitData.Length)
        {
            if (dicInitFlag)
            {
                InitDictionary(dic, lzwMinimumCodeSize, out lzwCodeSize, out clearCode, out finishCode);
                dicInitFlag = false;
            }

            string entry = null;//LZW算法中的后缀

            int key = bitData.GetInt(bitDataIndex, lzwCodeSize);

            //发现清除标记,说明当前数据块读取完毕
            if (key == clearCode)
            {
                // Clear (Initialize dictionary)
                dicInitFlag = true;
                bitDataIndex += lzwCodeSize;
                prevEntry = null;
                continue;
            }
            //发现结束标记,说明所有数据块已读取完毕
            else if (key == finishCode)
            {
                // Exit
                GIFDebug.LogWarning("early stop code. bitDataIndex:" + bitDataIndex + " lzwCodeSize:" + lzwCodeSize + " key:" + key + " dic.Count:" + dic.Count);
                break;
            }
            else if (dic.ContainsKey(key))
            {
                // Output from dictionary
                entry = dic[key];
            }
            else if (key >= dic.Count)
            {
                if (prevEntry != null)
                {
                    // Output from estimation
                    entry = prevEntry + prevEntry[0];
                }
                else
                {
                    //读到一个不合理的数据,这里直接跳过此数据
                    GIFDebug.LogWarning("It is strange that come here. bitDataIndex:" + bitDataIndex + " lzwCodeSize:" + lzwCodeSize + " key:" + key + " dic.Count:" + dic.Count);
                    bitDataIndex += lzwCodeSize;
                    continue;
                }
            }
            else
            {
                //读到一个不合理的数据,这里直接跳过此数据
                GIFDebug.LogWarning("It is strange that come here. bitDataIndex:" + bitDataIndex + " lzwCodeSize:" + lzwCodeSize + " key:" + key + " dic.Count:" + dic.Count);
                bitDataIndex += lzwCodeSize;
                continue;
            }

            // Output
            // Take out 8 bits from the string.
            // 将后缀字符(或字符串)转成字节数组并输出
            byte[] temp = Encoding.Unicode.GetBytes(entry);
            for (int i = 0; i < temp.Length; i++)
            {
                //Unicode.GetBytes() API会将每个字符用两个字节表示,
                //跳过没用的高位字节
                if (i % 2 == 0)
                {
                    output[outputAddIndex] = temp[i];
                    outputAddIndex++;
                }
            }

            if (outputAddIndex >= needDataSize)
            {
                // Exit
                break;
            }

            if (prevEntry != null)
            {
                // Add to dictionary
                dic.Add(dic.Count, prevEntry + entry[0]);
            }

            prevEntry = entry;
            bitDataIndex += lzwCodeSize;

            //GIF中LZW算法采用变长参数记录后面数据块中每个数字(字符)用几个bit表示
            //第1个数据块中的不同字符(或字符串)用3bit表示,总共能表示8种,当字典存满8项后,清空字典,后面的数据存入第2个数据块。
            //第2个数据块中的不同字符(或字符串)用4bit表示,总共能表示16种,当字典存满16项后,清空字典,后面的数据存入第3个数据块。
            //依此递推......

            if (lzwCodeSize == 3 && dic.Count >= 8)
            {
                lzwCodeSize = 4;
            }
            else if (lzwCodeSize == 4 && dic.Count >= 16)
            {
                lzwCodeSize = 5;
            }
            else if (lzwCodeSize == 5 && dic.Count >= 32)
            {
                lzwCodeSize = 6;
            }
            else if (lzwCodeSize == 6 && dic.Count >= 64)
            {
                lzwCodeSize = 7;
            }
            else if (lzwCodeSize == 7 && dic.Count >= 128)
            {
                lzwCodeSize = 8;
            }
            else if (lzwCodeSize == 8 && dic.Count >= 256)
            {
                lzwCodeSize = 9;
            }
            else if (lzwCodeSize == 9 && dic.Count >= 512)
            {
                lzwCodeSize = 10;
            }
            else if (lzwCodeSize == 10 && dic.Count >= 1024)
            {
                lzwCodeSize = 11;
            }
            else if (lzwCodeSize == 11 && dic.Count >= 2048)
            {
                lzwCodeSize = 12;
            }
            //GIF的LZW规定最大使用12bit来表示1个字符(或字符串)
            //字典存满后,则继续读下一个数据块, lzwCodeSize的值不再变化
            else if (lzwCodeSize == 12 && dic.Count >= 4096)
            {
                int nextKey = bitData.GetNumeral(bitDataIndex, lzwCodeSize);
                //字典已满,即便下一个字符不是清除标记,也要重置字典
                if (nextKey != clearCode)
                {
                    dicInitFlag = true;
                }
            }
        }

        //是否需要交错排序
        if (interlaceFlag)
            output = SortInterlaceGifData(output, width);

        decodedData.AddRange(output);
    }

    /// <summary>
    /// 初始化字典 (代码引用 UniGif)
    /// </summary>
    /// <param name="dic">Dictionary</param>
    /// <param name="lzwMinimumCodeSize">LZW minimum code size</param>
    /// <param name="lzwCodeSize">out LZW code size</param>
    /// <param name="clearCode">out Clear code</param>
    /// <param name="finishCode">out Finish code</param>
    private static void InitDictionary(Dictionary<int, string> dic, int lzwMinimumCodeSize, out int lzwCodeSize, out int clearCode, out int finishCode)
    {
        int dicLength = (int)Math.Pow(2, lzwMinimumCodeSize);

        clearCode = dicLength;
        finishCode = clearCode + 1;

        dic.Clear();

        for (int i = 0; i < dicLength + 2; i++)
        {
            dic.Add(i, ((char)i).ToString());
        }

        lzwCodeSize = lzwMinimumCodeSize + 1;
    }

    /// <summary>
    /// 代码引用 UniGif
    /// 交错排序(Sort interlace GIF data)
    /// </summary>
    /// <param name="decodedData">Decoded GIF data</param>
    /// <param name="xNum">Pixel number of horizontal row</param>
    /// <returns>Sorted data</returns>
    private byte[] SortInterlaceGifData(byte[] decodedData, int xNum)
    {
        int rowNo = 0;
        int dataIndex = 0;
        var newArr = new byte[decodedData.Length];
        // Every 8th. row, starting with row 0.
        for (int i = 0; i < newArr.Length; i++)
        {
            if (rowNo % 8 == 0)
            {
                newArr[i] = decodedData[dataIndex];
                dataIndex++;
            }
            if (i != 0 && i % xNum == 0)
            {
                rowNo++;
            }
        }
        rowNo = 0;
        // Every 8th. row, starting with row 4.
        for (int i = 0; i < newArr.Length; i++)
        {
            if (rowNo % 8 == 4)
            {
                newArr[i] = decodedData[dataIndex];
                dataIndex++;
            }
            if (i != 0 && i % xNum == 0)
            {
                rowNo++;
            }
        }
        rowNo = 0;
        // Every 4th. row, starting with row 2.
        for (int i = 0; i < newArr.Length; i++)
        {
            if (rowNo % 4 == 2)
            {
                newArr[i] = decodedData[dataIndex];
                dataIndex++;
            }
            if (i != 0 && i % xNum == 0)
            {
                rowNo++;
            }
        }
        rowNo = 0;
        // Every 2nd. row, starting with row 1.
        for (int i = 0; i < newArr.Length; i++)
        {
            if (rowNo % 8 != 0 && rowNo % 8 != 4 && rowNo % 4 != 2)
            {
                newArr[i] = decodedData[dataIndex];
                dataIndex++;
            }
            if (i != 0 && i % xNum == 0)
            {
                rowNo++;
            }
        }

        return newArr;
    }
}

//代码引用 UniGif
public static class BitArrayExtension
{
    public static int GetInt(this BitArray array, int startIndex, int bitLength)
    {
        var newArray = new BitArray(bitLength);

        for (int i = 0; i < bitLength; i++)
        {
            if (array.Length <= startIndex + i)
            {
                newArray[i] = false;
            }
            else
            {
                bool bit = array.Get(startIndex + i);
                newArray[i] = bit;
            }
        }

        return newArray.ToInt();
    }

    public static int ToInt(this BitArray array)
    {
        if (array == null)
        {
            GIFDebug.LogError("array is nothing.");
            return 0;
        }
        if (array.Length > 32)
        {
            GIFDebug.LogError("must be at most 32 bits long.");
            return 0;
        }

        var result = new int[1];
        array.CopyTo(result, 0);
        return result[0];
    }
}

//保存解析完成后的GIF数据
public sealed class GIFData
{
    public GIFLogicalScreenDescriptor m_logicalScreenDescriptor;
    public GIFGraphicControlExtension m_graphicControlExtension;
    public GIFGlobalColorTable m_globalColorTable;
    public List<Texture2D> textures = new List<Texture2D>();
}

public sealed class GIFDebug
{
    [Conditional("UNITY_EDITOR")]
    public static void Log(string msg)
    {
#if DEBUG_GIF_DECODE
        UnityEngine.Debug.Log(msg);
#endif
    }

    [Conditional("UNITY_EDITOR")]
    public static void LogFormat(string format, params object[] args)
    {
#if DEBUG_GIF_DECODE
        UnityEngine.Debug.LogFormat(format, args);
#endif
    }

    public static void LogError(string msg)
    {
        UnityEngine.Debug.LogError(msg);
    }

    public static void LogErrorFormat(string format, params object[] args)
    {
        UnityEngine.Debug.LogErrorFormat(format, args);
    }

    public static void LogWarning(string msg)
    {
        UnityEngine.Debug.LogWarning(msg);
    }
}


使用


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour
{
    public List<Texture2D> textures;

    void Start()
    {
        string path = string.Format("file:///{0}/test.gif", Application.streamingAssetsPath);
        Debug.Log(path);
        StartCoroutine(LoadGif(path));
    }

    IEnumerator LoadGif(string url)
    {
        using (WWW www = new WWW(url))
        {
            yield return www;

            if (string.IsNullOrEmpty(www.error) == false)
            {
                Debug.LogError("File load error.\n" + www.error);
                yield break;
            }

            byte[] bytes = www.bytes;
            GIFImage gif = new GIFImage(bytes);
            textures = gif.textures;
        }
    }
}


运行测试

111111.png

评论:
发表评论:
昵称

邮件地址 (选填)

个人主页 (选填)

内容