UniGifDecoder.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. /*
  2. UniGif
  3. Copyright (c) 2015 WestHillApps (Hironari Nishioka)
  4. This software is released under the MIT License.
  5. http://opensource.org/licenses/mit-license.php
  6. */
  7. using System;
  8. using System.Collections;
  9. using System.Collections.Generic;
  10. using System.Text;
  11. using UnityEngine;
  12. public static partial class UniGif
  13. {
  14. /// <summary>
  15. /// Decode to textures from GIF data
  16. /// </summary>
  17. /// <param name="gifData">GIF data</param>
  18. /// <param name="callback">Callback method(param is GIF texture list)</param>
  19. /// <param name="filterMode">Textures filter mode</param>
  20. /// <param name="wrapMode">Textures wrap mode</param>
  21. /// <returns>IEnumerator</returns>
  22. private static IEnumerator DecodeTextureCoroutine(GifData gifData, Action<List<GifTexture>> callback, FilterMode filterMode, TextureWrapMode wrapMode)
  23. {
  24. if (gifData.m_imageBlockList == null || gifData.m_imageBlockList.Count < 1)
  25. {
  26. yield break;
  27. }
  28. List<GifTexture> gifTexList = new List<GifTexture>(gifData.m_imageBlockList.Count);
  29. List<ushort> disposalMethodList = new List<ushort>(gifData.m_imageBlockList.Count);
  30. int imgIndex = 0;
  31. for (int i = 0; i < gifData.m_imageBlockList.Count; i++)
  32. {
  33. byte[] decodedData = GetDecodedData(gifData.m_imageBlockList[i]);
  34. GraphicControlExtension? graphicCtrlEx = GetGraphicCtrlExt(gifData, imgIndex);
  35. int transparentIndex = GetTransparentIndex(graphicCtrlEx);
  36. disposalMethodList.Add(GetDisposalMethod(graphicCtrlEx));
  37. Color32 bgColor;
  38. List<byte[]> colorTable = GetColorTableAndSetBgColor(gifData, gifData.m_imageBlockList[i], transparentIndex, out bgColor);
  39. yield return 0;
  40. bool filledTexture;
  41. Texture2D tex = CreateTexture2D(gifData, gifTexList, imgIndex, disposalMethodList, bgColor, filterMode, wrapMode, out filledTexture);
  42. yield return 0;
  43. // Set pixel data
  44. int dataIndex = 0;
  45. // Reverse set pixels. because GIF data starts from the top left.
  46. for (int y = tex.height - 1; y >= 0; y--)
  47. {
  48. SetTexturePixelRow(tex, y, gifData.m_imageBlockList[i], decodedData, ref dataIndex, colorTable, bgColor, transparentIndex, filledTexture);
  49. }
  50. tex.Apply();
  51. yield return 0;
  52. float delaySec = GetDelaySec(graphicCtrlEx);
  53. // Add to GIF texture list
  54. gifTexList.Add(new GifTexture(tex, delaySec));
  55. imgIndex++;
  56. }
  57. if (callback != null)
  58. {
  59. callback(gifTexList);
  60. }
  61. yield break;
  62. }
  63. #region Call from DecodeTexture methods
  64. /// <summary>
  65. /// Get decoded image data from ImageBlock
  66. /// </summary>
  67. private static byte[] GetDecodedData(ImageBlock imgBlock)
  68. {
  69. // Combine LZW compressed data
  70. List<byte> lzwData = new List<byte>();
  71. for (int i = 0; i < imgBlock.m_imageDataList.Count; i++)
  72. {
  73. for (int k = 0; k < imgBlock.m_imageDataList[i].m_imageData.Length; k++)
  74. {
  75. lzwData.Add(imgBlock.m_imageDataList[i].m_imageData[k]);
  76. }
  77. }
  78. // LZW decode
  79. int needDataSize = imgBlock.m_imageHeight * imgBlock.m_imageWidth;
  80. byte[] decodedData = DecodeGifLZW(lzwData, imgBlock.m_lzwMinimumCodeSize, needDataSize);
  81. // Sort interlace GIF
  82. if (imgBlock.m_interlaceFlag)
  83. {
  84. decodedData = SortInterlaceGifData(decodedData, imgBlock.m_imageWidth);
  85. }
  86. return decodedData;
  87. }
  88. /// <summary>
  89. /// Get color table and set background color (local or global)
  90. /// </summary>
  91. private static List<byte[]> GetColorTableAndSetBgColor(GifData gifData, ImageBlock imgBlock, int transparentIndex, out Color32 bgColor)
  92. {
  93. List<byte[]> colorTable = imgBlock.m_localColorTableFlag ? imgBlock.m_localColorTable : gifData.m_globalColorTableFlag ? gifData.m_globalColorTable : null;
  94. if (colorTable != null)
  95. {
  96. // Set background color from color table
  97. byte[] bgRgb = colorTable[gifData.m_bgColorIndex];
  98. bgColor = new Color32(bgRgb[0], bgRgb[1], bgRgb[2], (byte)(transparentIndex == gifData.m_bgColorIndex ? 0 : 255));
  99. }
  100. else
  101. {
  102. bgColor = Color.black;
  103. }
  104. return colorTable;
  105. }
  106. /// <summary>
  107. /// Get GraphicControlExtension from GifData
  108. /// </summary>
  109. private static GraphicControlExtension? GetGraphicCtrlExt(GifData gifData, int imgBlockIndex)
  110. {
  111. if (gifData.m_graphicCtrlExList != null && gifData.m_graphicCtrlExList.Count > imgBlockIndex)
  112. {
  113. return gifData.m_graphicCtrlExList[imgBlockIndex];
  114. }
  115. return null;
  116. }
  117. /// <summary>
  118. /// Get transparent color index from GraphicControlExtension
  119. /// </summary>
  120. private static int GetTransparentIndex(GraphicControlExtension? graphicCtrlEx)
  121. {
  122. int transparentIndex = -1;
  123. if (graphicCtrlEx != null && graphicCtrlEx.Value.m_transparentColorFlag)
  124. {
  125. transparentIndex = graphicCtrlEx.Value.m_transparentColorIndex;
  126. }
  127. return transparentIndex;
  128. }
  129. /// <summary>
  130. /// Get delay seconds from GraphicControlExtension
  131. /// </summary>
  132. private static float GetDelaySec(GraphicControlExtension? graphicCtrlEx)
  133. {
  134. // Get delay sec from GraphicControlExtension
  135. float delaySec = graphicCtrlEx != null ? graphicCtrlEx.Value.m_delayTime / 100f : (1f / 60f);
  136. if (delaySec <= 0f)
  137. {
  138. delaySec = 0.1f;
  139. }
  140. return delaySec;
  141. }
  142. /// <summary>
  143. /// Get disposal method from GraphicControlExtension
  144. /// </summary>
  145. private static ushort GetDisposalMethod(GraphicControlExtension? graphicCtrlEx)
  146. {
  147. return graphicCtrlEx != null ? graphicCtrlEx.Value.m_disposalMethod : (ushort)2;
  148. }
  149. /// <summary>
  150. /// Create Texture2D object and initial settings
  151. /// </summary>
  152. private static Texture2D CreateTexture2D(GifData gifData, List<GifTexture> gifTexList, int imgIndex, List<ushort> disposalMethodList, Color32 bgColor, FilterMode filterMode, TextureWrapMode wrapMode, out bool filledTexture)
  153. {
  154. filledTexture = false;
  155. // Create texture
  156. Texture2D tex = new Texture2D(gifData.m_logicalScreenWidth, gifData.m_logicalScreenHeight, TextureFormat.ARGB32, false);
  157. tex.filterMode = filterMode;
  158. tex.wrapMode = wrapMode;
  159. // Check dispose
  160. ushort disposalMethod = imgIndex > 0 ? disposalMethodList[imgIndex - 1] : (ushort)2;
  161. int useBeforeIndex = -1;
  162. if (disposalMethod == 0)
  163. {
  164. // 0 (No disposal specified)
  165. }
  166. else if (disposalMethod == 1)
  167. {
  168. // 1 (Do not dispose)
  169. useBeforeIndex = imgIndex - 1;
  170. }
  171. else if (disposalMethod == 2)
  172. {
  173. // 2 (Restore to background color)
  174. filledTexture = true;
  175. Color32[] pix = new Color32[tex.width * tex.height];
  176. for (int i = 0; i < pix.Length; i++)
  177. {
  178. pix[i] = bgColor;
  179. }
  180. tex.SetPixels32(pix);
  181. tex.Apply();
  182. }
  183. else if (disposalMethod == 3)
  184. {
  185. // 3 (Restore to previous)
  186. for (int i = imgIndex - 1; i >= 0; i--)
  187. {
  188. if (disposalMethodList[i] == 0 || disposalMethodList[i] == 1)
  189. {
  190. useBeforeIndex = i;
  191. break;
  192. }
  193. }
  194. }
  195. if (useBeforeIndex >= 0)
  196. {
  197. filledTexture = true;
  198. Color32[] pix = gifTexList[useBeforeIndex].m_texture2d.GetPixels32();
  199. tex.SetPixels32(pix);
  200. tex.Apply();
  201. }
  202. return tex;
  203. }
  204. /// <summary>
  205. /// Set texture pixel row
  206. /// </summary>
  207. private static void SetTexturePixelRow(Texture2D tex, int y, ImageBlock imgBlock, byte[] decodedData, ref int dataIndex, List<byte[]> colorTable, Color32 bgColor, int transparentIndex, bool filledTexture)
  208. {
  209. // Row no (0~)
  210. int row = tex.height - 1 - y;
  211. for (int x = 0; x < tex.width; x++)
  212. {
  213. // Line no (0~)
  214. int line = x;
  215. // Out of image blocks
  216. if (row < imgBlock.m_imageTopPosition ||
  217. row >= imgBlock.m_imageTopPosition + imgBlock.m_imageHeight ||
  218. line < imgBlock.m_imageLeftPosition ||
  219. line >= imgBlock.m_imageLeftPosition + imgBlock.m_imageWidth)
  220. {
  221. // Get pixel color from bg color
  222. if (filledTexture == false)
  223. {
  224. tex.SetPixel(x, y, bgColor);
  225. }
  226. continue;
  227. }
  228. // Out of decoded data
  229. if (dataIndex >= decodedData.Length)
  230. {
  231. if (filledTexture == false)
  232. {
  233. tex.SetPixel(x, y, bgColor);
  234. if (dataIndex == decodedData.Length)
  235. {
  236. Debug.LogError("dataIndex exceeded the size of decodedData. dataIndex:" + dataIndex + " decodedData.Length:" + decodedData.Length + " y:" + y + " x:" + x);
  237. }
  238. }
  239. dataIndex++;
  240. continue;
  241. }
  242. // Get pixel color from color table
  243. {
  244. byte colorIndex = decodedData[dataIndex];
  245. if (colorTable == null || colorTable.Count <= colorIndex)
  246. {
  247. if (filledTexture == false)
  248. {
  249. tex.SetPixel(x, y, bgColor);
  250. if (colorTable == null)
  251. {
  252. Debug.LogError("colorIndex exceeded the size of colorTable. colorTable is null. colorIndex:" + colorIndex);
  253. }
  254. else
  255. {
  256. Debug.LogError("colorIndex exceeded the size of colorTable. colorTable.Count:" + colorTable.Count + " colorIndex:" + colorIndex);
  257. }
  258. }
  259. dataIndex++;
  260. continue;
  261. }
  262. byte[] rgb = colorTable[colorIndex];
  263. // Set alpha
  264. byte alpha = transparentIndex >= 0 && transparentIndex == colorIndex ? (byte)0 : (byte)255;
  265. if (filledTexture == false || alpha != 0)
  266. {
  267. // Set color
  268. Color32 col = new Color32(rgb[0], rgb[1], rgb[2], alpha);
  269. tex.SetPixel(x, y, col);
  270. }
  271. }
  272. dataIndex++;
  273. }
  274. }
  275. #endregion
  276. #region Decode LZW & Sort interrace methods
  277. /// <summary>
  278. /// GIF LZW decode
  279. /// </summary>
  280. /// <param name="compData">LZW compressed data</param>
  281. /// <param name="lzwMinimumCodeSize">LZW minimum code size</param>
  282. /// <param name="needDataSize">Need decoded data size</param>
  283. /// <returns>Decoded data array</returns>
  284. private static byte[] DecodeGifLZW(List<byte> compData, int lzwMinimumCodeSize, int needDataSize)
  285. {
  286. int clearCode = 0;
  287. int finishCode = 0;
  288. // Initialize dictionary
  289. Dictionary<int, string> dic = new Dictionary<int, string>();
  290. int lzwCodeSize = 0;
  291. InitDictionary(dic, lzwMinimumCodeSize, out lzwCodeSize, out clearCode, out finishCode);
  292. // Convert to bit array
  293. byte[] compDataArr = compData.ToArray();
  294. var bitData = new BitArray(compDataArr);
  295. byte[] output = new byte[needDataSize];
  296. int outputAddIndex = 0;
  297. string prevEntry = null;
  298. bool dicInitFlag = false;
  299. int bitDataIndex = 0;
  300. // LZW decode loop
  301. while (bitDataIndex < bitData.Length)
  302. {
  303. if (dicInitFlag)
  304. {
  305. InitDictionary(dic, lzwMinimumCodeSize, out lzwCodeSize, out clearCode, out finishCode);
  306. dicInitFlag = false;
  307. }
  308. int key = bitData.GetNumeral(bitDataIndex, lzwCodeSize);
  309. string entry = null;
  310. if (key == clearCode)
  311. {
  312. // Clear (Initialize dictionary)
  313. dicInitFlag = true;
  314. bitDataIndex += lzwCodeSize;
  315. prevEntry = null;
  316. continue;
  317. }
  318. else if (key == finishCode)
  319. {
  320. // Exit
  321. Debug.LogWarning("early stop code. bitDataIndex:" + bitDataIndex + " lzwCodeSize:" + lzwCodeSize + " key:" + key + " dic.Count:" + dic.Count);
  322. break;
  323. }
  324. else if (dic.ContainsKey(key))
  325. {
  326. // Output from dictionary
  327. entry = dic[key];
  328. }
  329. else if (key >= dic.Count)
  330. {
  331. if (prevEntry != null)
  332. {
  333. // Output from estimation
  334. entry = prevEntry + prevEntry[0];
  335. }
  336. else
  337. {
  338. Debug.LogWarning("It is strange that come here. bitDataIndex:" + bitDataIndex + " lzwCodeSize:" + lzwCodeSize + " key:" + key + " dic.Count:" + dic.Count);
  339. bitDataIndex += lzwCodeSize;
  340. continue;
  341. }
  342. }
  343. else
  344. {
  345. Debug.LogWarning("It is strange that come here. bitDataIndex:" + bitDataIndex + " lzwCodeSize:" + lzwCodeSize + " key:" + key + " dic.Count:" + dic.Count);
  346. bitDataIndex += lzwCodeSize;
  347. continue;
  348. }
  349. // Output
  350. // Take out 8 bits from the string.
  351. byte[] temp = Encoding.Unicode.GetBytes(entry);
  352. for (int i = 0; i < temp.Length; i++)
  353. {
  354. if (i % 2 == 0)
  355. {
  356. output[outputAddIndex] = temp[i];
  357. outputAddIndex++;
  358. }
  359. }
  360. if (outputAddIndex >= needDataSize)
  361. {
  362. // Exit
  363. break;
  364. }
  365. if (prevEntry != null)
  366. {
  367. // Add to dictionary
  368. dic.Add(dic.Count, prevEntry + entry[0]);
  369. }
  370. prevEntry = entry;
  371. bitDataIndex += lzwCodeSize;
  372. if (lzwCodeSize == 3 && dic.Count >= 8)
  373. {
  374. lzwCodeSize = 4;
  375. }
  376. else if (lzwCodeSize == 4 && dic.Count >= 16)
  377. {
  378. lzwCodeSize = 5;
  379. }
  380. else if (lzwCodeSize == 5 && dic.Count >= 32)
  381. {
  382. lzwCodeSize = 6;
  383. }
  384. else if (lzwCodeSize == 6 && dic.Count >= 64)
  385. {
  386. lzwCodeSize = 7;
  387. }
  388. else if (lzwCodeSize == 7 && dic.Count >= 128)
  389. {
  390. lzwCodeSize = 8;
  391. }
  392. else if (lzwCodeSize == 8 && dic.Count >= 256)
  393. {
  394. lzwCodeSize = 9;
  395. }
  396. else if (lzwCodeSize == 9 && dic.Count >= 512)
  397. {
  398. lzwCodeSize = 10;
  399. }
  400. else if (lzwCodeSize == 10 && dic.Count >= 1024)
  401. {
  402. lzwCodeSize = 11;
  403. }
  404. else if (lzwCodeSize == 11 && dic.Count >= 2048)
  405. {
  406. lzwCodeSize = 12;
  407. }
  408. else if (lzwCodeSize == 12 && dic.Count >= 4096)
  409. {
  410. int nextKey = bitData.GetNumeral(bitDataIndex, lzwCodeSize);
  411. if (nextKey != clearCode)
  412. {
  413. dicInitFlag = true;
  414. }
  415. }
  416. }
  417. return output;
  418. }
  419. /// <summary>
  420. /// Initialize dictionary
  421. /// </summary>
  422. /// <param name="dic">Dictionary</param>
  423. /// <param name="lzwMinimumCodeSize">LZW minimum code size</param>
  424. /// <param name="lzwCodeSize">out LZW code size</param>
  425. /// <param name="clearCode">out Clear code</param>
  426. /// <param name="finishCode">out Finish code</param>
  427. private static void InitDictionary(Dictionary<int, string> dic, int lzwMinimumCodeSize, out int lzwCodeSize, out int clearCode, out int finishCode)
  428. {
  429. int dicLength = (int)Math.Pow(2, lzwMinimumCodeSize);
  430. clearCode = dicLength;
  431. finishCode = clearCode + 1;
  432. dic.Clear();
  433. for (int i = 0; i < dicLength + 2; i++)
  434. {
  435. dic.Add(i, ((char)i).ToString());
  436. }
  437. lzwCodeSize = lzwMinimumCodeSize + 1;
  438. }
  439. /// <summary>
  440. /// Sort interlace GIF data
  441. /// </summary>
  442. /// <param name="decodedData">Decoded GIF data</param>
  443. /// <param name="xNum">Pixel number of horizontal row</param>
  444. /// <returns>Sorted data</returns>
  445. private static byte[] SortInterlaceGifData(byte[] decodedData, int xNum)
  446. {
  447. int rowNo = 0;
  448. int dataIndex = 0;
  449. var newArr = new byte[decodedData.Length];
  450. // Every 8th. row, starting with row 0.
  451. for (int i = 0; i < newArr.Length; i++)
  452. {
  453. if (rowNo % 8 == 0)
  454. {
  455. newArr[i] = decodedData[dataIndex];
  456. dataIndex++;
  457. }
  458. if (i != 0 && i % xNum == 0)
  459. {
  460. rowNo++;
  461. }
  462. }
  463. rowNo = 0;
  464. // Every 8th. row, starting with row 4.
  465. for (int i = 0; i < newArr.Length; i++)
  466. {
  467. if (rowNo % 8 == 4)
  468. {
  469. newArr[i] = decodedData[dataIndex];
  470. dataIndex++;
  471. }
  472. if (i != 0 && i % xNum == 0)
  473. {
  474. rowNo++;
  475. }
  476. }
  477. rowNo = 0;
  478. // Every 4th. row, starting with row 2.
  479. for (int i = 0; i < newArr.Length; i++)
  480. {
  481. if (rowNo % 4 == 2)
  482. {
  483. newArr[i] = decodedData[dataIndex];
  484. dataIndex++;
  485. }
  486. if (i != 0 && i % xNum == 0)
  487. {
  488. rowNo++;
  489. }
  490. }
  491. rowNo = 0;
  492. // Every 2nd. row, starting with row 1.
  493. for (int i = 0; i < newArr.Length; i++)
  494. {
  495. if (rowNo % 8 != 0 && rowNo % 8 != 4 && rowNo % 4 != 2)
  496. {
  497. newArr[i] = decodedData[dataIndex];
  498. dataIndex++;
  499. }
  500. if (i != 0 && i % xNum == 0)
  501. {
  502. rowNo++;
  503. }
  504. }
  505. return newArr;
  506. }
  507. #endregion
  508. }