123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579 |
- /*
- 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
- }
|