Unity Udexreal开发插件包
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

579 lines
18 KiB

/*
UniGif
Copyright (c) 2015 WestHillApps (Hironari Nishioka)
This software is released under the MIT License.
http://opensource.org/licenses/mit-license.php
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
public static partial class UniGif
{
/// <summary>
/// Decode to textures from GIF data
/// </summary>
/// <param name="gifData">GIF data</param>
/// <param name="callback">Callback method(param is GIF texture list)</param>
/// <param name="filterMode">Textures filter mode</param>
/// <param name="wrapMode">Textures wrap mode</param>
/// <returns>IEnumerator</returns>
private static IEnumerator DecodeTextureCoroutine(GifData gifData, Action<List<GifTexture>> callback, FilterMode filterMode, TextureWrapMode wrapMode)
{
if (gifData.m_imageBlockList == null || gifData.m_imageBlockList.Count < 1)
{
yield break;
}
List<GifTexture> gifTexList = new List<GifTexture>(gifData.m_imageBlockList.Count);
List<ushort> disposalMethodList = new List<ushort>(gifData.m_imageBlockList.Count);
int imgIndex = 0;
for (int i = 0; i < gifData.m_imageBlockList.Count; i++)
{
byte[] decodedData = GetDecodedData(gifData.m_imageBlockList[i]);
GraphicControlExtension? graphicCtrlEx = GetGraphicCtrlExt(gifData, imgIndex);
int transparentIndex = GetTransparentIndex(graphicCtrlEx);
disposalMethodList.Add(GetDisposalMethod(graphicCtrlEx));
Color32 bgColor;
List<byte[]> colorTable = GetColorTableAndSetBgColor(gifData, gifData.m_imageBlockList[i], transparentIndex, out bgColor);
yield return 0;
bool filledTexture;
Texture2D tex = CreateTexture2D(gifData, gifTexList, imgIndex, disposalMethodList, bgColor, filterMode, wrapMode, out filledTexture);
yield return 0;
// Set pixel data
int dataIndex = 0;
// Reverse set pixels. because GIF data starts from the top left.
for (int y = tex.height - 1; y >= 0; y--)
{
SetTexturePixelRow(tex, y, gifData.m_imageBlockList[i], decodedData, ref dataIndex, colorTable, bgColor, transparentIndex, filledTexture);
}
tex.Apply();
yield return 0;
float delaySec = GetDelaySec(graphicCtrlEx);
// Add to GIF texture list
gifTexList.Add(new GifTexture(tex, delaySec));
imgIndex++;
}
if (callback != null)
{
callback(gifTexList);
}
yield break;
}
#region Call from DecodeTexture methods
/// <summary>
/// Get decoded image data from ImageBlock
/// </summary>
private static byte[] GetDecodedData(ImageBlock imgBlock)
{
// Combine LZW compressed data
List<byte> lzwData = new List<byte>();
for (int i = 0; i < imgBlock.m_imageDataList.Count; i++)
{
for (int k = 0; k < imgBlock.m_imageDataList[i].m_imageData.Length; k++)
{
lzwData.Add(imgBlock.m_imageDataList[i].m_imageData[k]);
}
}
// LZW decode
int needDataSize = imgBlock.m_imageHeight * imgBlock.m_imageWidth;
byte[] decodedData = DecodeGifLZW(lzwData, imgBlock.m_lzwMinimumCodeSize, needDataSize);
// Sort interlace GIF
if (imgBlock.m_interlaceFlag)
{
decodedData = SortInterlaceGifData(decodedData, imgBlock.m_imageWidth);
}
return decodedData;
}
/// <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;
}
return colorTable;
}
/// <summary>
/// Get GraphicControlExtension from GifData
/// </summary>
private static GraphicControlExtension? GetGraphicCtrlExt(GifData gifData, int imgBlockIndex)
{
if (gifData.m_graphicCtrlExList != null && gifData.m_graphicCtrlExList.Count > imgBlockIndex)
{
return gifData.m_graphicCtrlExList[imgBlockIndex];
}
return null;
}
/// <summary>
/// Get transparent color index from GraphicControlExtension
/// </summary>
private static int GetTransparentIndex(GraphicControlExtension? graphicCtrlEx)
{
int transparentIndex = -1;
if (graphicCtrlEx != null && graphicCtrlEx.Value.m_transparentColorFlag)
{
transparentIndex = graphicCtrlEx.Value.m_transparentColorIndex;
}
return transparentIndex;
}
/// <summary>
/// Get delay seconds from GraphicControlExtension
/// </summary>
private static float GetDelaySec(GraphicControlExtension? graphicCtrlEx)
{
// Get delay sec from GraphicControlExtension
float delaySec = graphicCtrlEx != null ? graphicCtrlEx.Value.m_delayTime / 100f : (1f / 60f);
if (delaySec <= 0f)
{
delaySec = 0.1f;
}
return delaySec;
}
/// <summary>
/// Get disposal method from GraphicControlExtension
/// </summary>
private static ushort GetDisposalMethod(GraphicControlExtension? graphicCtrlEx)
{
return graphicCtrlEx != null ? graphicCtrlEx.Value.m_disposalMethod : (ushort)2;
}
/// <summary>
/// Create Texture2D object and initial settings
/// </summary>
private static Texture2D CreateTexture2D(GifData gifData, List<GifTexture> gifTexList, int imgIndex, List<ushort> disposalMethodList, Color32 bgColor, FilterMode filterMode, TextureWrapMode wrapMode, out bool filledTexture)
{
filledTexture = false;
// Create texture
Texture2D tex = new Texture2D(gifData.m_logicalScreenWidth, gifData.m_logicalScreenHeight, TextureFormat.ARGB32, false);
tex.filterMode = filterMode;
tex.wrapMode = wrapMode;
// Check dispose
ushort disposalMethod = imgIndex > 0 ? disposalMethodList[imgIndex - 1] : (ushort)2;
int useBeforeIndex = -1;
if (disposalMethod == 0)
{
// 0 (No disposal specified)
}
else if (disposalMethod == 1)
{
// 1 (Do not dispose)
useBeforeIndex = imgIndex - 1;
}
else if (disposalMethod == 2)
{
// 2 (Restore to background color)
filledTexture = true;
Color32[] pix = new Color32[tex.width * tex.height];
for (int i = 0; i < pix.Length; i++)
{
pix[i] = bgColor;
}
tex.SetPixels32(pix);
tex.Apply();
}
else if (disposalMethod == 3)
{
// 3 (Restore to previous)
for (int i = imgIndex - 1; i >= 0; i--)
{
if (disposalMethodList[i] == 0 || disposalMethodList[i] == 1)
{
useBeforeIndex = i;
break;
}
}
}
if (useBeforeIndex >= 0)
{
filledTexture = true;
Color32[] pix = gifTexList[useBeforeIndex].m_texture2d.GetPixels32();
tex.SetPixels32(pix);
tex.Apply();
}
return tex;
}
/// <summary>
/// Set texture pixel row
/// </summary>
private static void SetTexturePixelRow(Texture2D tex, int y, ImageBlock imgBlock, byte[] decodedData, ref int dataIndex, List<byte[]> colorTable, Color32 bgColor, int transparentIndex, bool filledTexture)
{
// Row no (0~)
int row = tex.height - 1 - y;
for (int x = 0; x < tex.width; x++)
{
// Line no (0~)
int line = x;
// Out of image blocks
if (row < imgBlock.m_imageTopPosition ||
row >= imgBlock.m_imageTopPosition + imgBlock.m_imageHeight ||
line < imgBlock.m_imageLeftPosition ||
line >= imgBlock.m_imageLeftPosition + imgBlock.m_imageWidth)
{
// Get pixel color from bg color
if (filledTexture == false)
{
tex.SetPixel(x, y, bgColor);
}
continue;
}
// Out of decoded data
if (dataIndex >= decodedData.Length)
{
if (filledTexture == false)
{
tex.SetPixel(x, y, bgColor);
if (dataIndex == decodedData.Length)
{
Debug.LogError("dataIndex exceeded the size of decodedData. dataIndex:" + dataIndex + " decodedData.Length:" + decodedData.Length + " y:" + y + " x:" + x);
}
}
dataIndex++;
continue;
}
// Get pixel color from color table
{
byte colorIndex = decodedData[dataIndex];
if (colorTable == null || colorTable.Count <= colorIndex)
{
if (filledTexture == false)
{
tex.SetPixel(x, y, bgColor);
if (colorTable == null)
{
Debug.LogError("colorIndex exceeded the size of colorTable. colorTable is null. colorIndex:" + colorIndex);
}
else
{
Debug.LogError("colorIndex exceeded the size of colorTable. colorTable.Count:" + colorTable.Count + " colorIndex:" + colorIndex);
}
}
dataIndex++;
continue;
}
byte[] rgb = colorTable[colorIndex];
// Set alpha
byte alpha = transparentIndex >= 0 && transparentIndex == colorIndex ? (byte)0 : (byte)255;
if (filledTexture == false || alpha != 0)
{
// Set color
Color32 col = new Color32(rgb[0], rgb[1], rgb[2], alpha);
tex.SetPixel(x, y, col);
}
}
dataIndex++;
}
}
#endregion
#region Decode LZW & Sort interrace methods
/// <summary>
/// GIF LZW decode
/// </summary>
/// <param name="compData">LZW compressed data</param>
/// <param name="lzwMinimumCodeSize">LZW minimum code size</param>
/// <param name="needDataSize">Need decoded data size</param>
/// <returns>Decoded data array</returns>
private static byte[] DecodeGifLZW(List<byte> compData, int lzwMinimumCodeSize, int needDataSize)
{
int clearCode = 0;
int finishCode = 0;
// Initialize dictionary
Dictionary<int, string> dic = new Dictionary<int, string>();
int lzwCodeSize = 0;
InitDictionary(dic, lzwMinimumCodeSize, out lzwCodeSize, out clearCode, out finishCode);
// Convert to bit array
byte[] compDataArr = compData.ToArray();
var bitData = new BitArray(compDataArr);
byte[] output = new byte[needDataSize];
int outputAddIndex = 0;
string prevEntry = null;
bool dicInitFlag = false;
int bitDataIndex = 0;
// LZW decode loop
while (bitDataIndex < bitData.Length)
{
if (dicInitFlag)
{
InitDictionary(dic, lzwMinimumCodeSize, out lzwCodeSize, out clearCode, out finishCode);
dicInitFlag = false;
}
int key = bitData.GetNumeral(bitDataIndex, lzwCodeSize);
string entry = null;
if (key == clearCode)
{
// Clear (Initialize dictionary)
dicInitFlag = true;
bitDataIndex += lzwCodeSize;
prevEntry = null;
continue;
}
else if (key == finishCode)
{
// Exit
Debug.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
{
Debug.LogWarning("It is strange that come here. bitDataIndex:" + bitDataIndex + " lzwCodeSize:" + lzwCodeSize + " key:" + key + " dic.Count:" + dic.Count);
bitDataIndex += lzwCodeSize;
continue;
}
}
else
{
Debug.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++)
{
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;
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;
}
else if (lzwCodeSize == 12 && dic.Count >= 4096)
{
int nextKey = bitData.GetNumeral(bitDataIndex, lzwCodeSize);
if (nextKey != clearCode)
{
dicInitFlag = true;
}
}
}
return output;
}
/// <summary>
/// Initialize dictionary
/// </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>
/// 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 static 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;
}
#endregion
}