/// <summary> /// Set GIF data /// </summary> /// <param name="gifBytes">GIF byte data</param> /// <param name="gifData">ref GIF data</param> /// <param name="debugLog">Debug log flag</param> /// <returns>Result</returns> private static bool SetGifData(byte[] gifBytes, ref GifData gifData, bool debugLog) { if (debugLog) { Debug.Log("SetGifData Start."); } if (gifBytes == null || gifBytes.Length <= 0) { Debug.LogError("bytes is nothing."); return(false); } int byteIndex = 0; if (SetGifHeader(gifBytes, ref byteIndex, ref gifData) == false) { Debug.LogError("GIF header set error."); return(false); } if (SetGifBlock(gifBytes, ref byteIndex, ref gifData) == false) { Debug.LogError("GIF block set error."); return(false); } if (debugLog) { gifData.Dump(); Debug.Log("SetGifData Finish."); } return(true); }
internal GifData GetGifData(byte[] data) { if ((data[0] == 71) || (data[1] == 73) || (data[2] == 70)) { GifData g = new GifData(); g.BackgroundColor = data[11]; int ii = 10; g.HasGCTF = ((data[ii] & 0x80) > 0); if (g.HasGCTF == true) { // if we have a GCTF g.GCTFColors = (int)Math.Pow(2, (data[ii] & 0x07) + 1); g.GCTFSize = 3 * g.GCTFColors; g.Palette = new Color[g.GCTFColors]; ii += 3; for (int c = 0; c < g.GCTFColors; c++) { g.Palette[c] = new Color(data[ii], data[ii + 1], data[ii + 2]); ii += 3; } } // should have application block here now if (data[ii] == 0x21 && data[ii + 1] == 0xff) { ii += 19; if (data[ii] == 0x21 && data[ii + 1] == 0xf9) { g.TransparencyIndex = data[ii + 6]; } } return(g); } return(null); }
/// <summary> /// Get GIF texture list (This is a possibility of lock up) /// </summary> /// <param name="bytes">GIF file byte data</param> /// <param name="loopCount">out Animation loop count</param> /// <param name="width">out GIF image width (px)</param> /// <param name="height">out GIF image height (px)</param> /// <param name="filterMode">Textures filter mode</param> /// <param name="wrapMode">Textures wrap mode</param> /// <param name="debugLog">Debug Log Flag</param> /// <returns>GIF texture list</returns> public static List<GifTexture> GetTextureList (byte[] bytes, out int loopCount, out int width, out int height, FilterMode filterMode = FilterMode.Bilinear, TextureWrapMode wrapMode = TextureWrapMode.Clamp, bool debugLog = false) { loopCount = -1; width = 0; height = 0; // Set GIF data var gifData = new GifData (); if (SetGifData (bytes, ref gifData, debugLog) == false) { Debug.LogError ("GIF file data set error."); return null; } // Decode to textures from GIF data var gifTexList = new List<GifTexture> (); if (DecodeTexture (gifData, gifTexList, filterMode, wrapMode) == false) { Debug.LogError ("GIF texture decode error."); return null; } loopCount = gifData.appEx.loopCount; width = gifData.logicalScreenWidth; height = gifData.logicalScreenHeight; return gifTexList; }
/// <summary> /// Create Texture2D object and initial settings /// </summary> private static Texture2D CreateTexture2D(GifData gifData, List <GifTexture> gifTexList, int imgBlockIndex, ushort disposalMethod, FilterMode filterMode, TextureWrapMode wrapMode, ref bool useBeforeTex) { // Create texture Texture2D tex = new Texture2D(gifData.logicalScreenWidth, gifData.logicalScreenHeight, TextureFormat.ARGB32, false); tex.filterMode = filterMode; tex.wrapMode = wrapMode; // Check dispose useBeforeTex = false; int beforeIndex = -1; if (imgBlockIndex > 0 && disposalMethod == 0 || disposalMethod == 1) { // before 1 beforeIndex = imgBlockIndex - 1; } else if (imgBlockIndex > 1 && disposalMethod == 3) { // before 2 beforeIndex = imgBlockIndex - 2; } if (beforeIndex >= 0) { // Do not dispose useBeforeTex = true; Color32[] pix = gifTexList[beforeIndex].texture2d.GetPixels32(); tex.SetPixels32(pix); tex.Apply(); } return(tex); }
public async static Task <Texture2D> GetTextureAsync ( byte[] bytes, FilterMode filterMode = FilterMode.Bilinear, TextureWrapMode wrapMode = TextureWrapMode.Clamp, bool debugLog = false ) { // Set GIF data var gifData = new GifData(); if (!SetGifData(bytes, ref gifData, debugLog)) { Debug.LogError("GIF file data set error."); await Task.Delay(1); } // Decode to textures from GIF data Texture2D texture = await DecodeTextureAsync(gifData, filterMode, wrapMode); if (texture == null) { Debug.LogError("GIF texture decode error."); await Task.Delay(1); } int loopCount = gifData.m_appEx.loopCount; int width = gifData.m_logicalScreenWidth; int height = gifData.m_logicalScreenHeight; return(texture); }
static void CalculColors(GifData gifData, int index) { Color[] previousFrame = gifData.previousFrame; if (previousFrame == null) { previousFrame = new Color[gifData.canvasWidth * gifData.canvasHeight]; } Color[] currentFrame = gifData.currentFrame; if (currentFrame == null) { currentFrame = new Color[gifData.canvasWidth * gifData.canvasHeight]; } Color[] transparentFrame = gifData.transparentFrame; if (transparentFrame == null) { transparentFrame = new Color[gifData.canvasWidth * gifData.canvasHeight]; } GifGraphicsControlExtension graphicsControlExt = gifData.graphicsControlExtensions[index]; GifImageDescriptor imageDescriptor = graphicsControlExt.imageDescriptor; GifImageData imageData = imageDescriptor.imageData; int top = imageDescriptor.imageTop; int left = imageDescriptor.imageLeft; int disposalMethod = graphicsControlExt.disposalMethod; int transparencyIndex = graphicsControlExt.transparentColorFlag ? graphicsControlExt.transparentColorIndex : -1; Color[] colorTabel = imageData.imageDescriptor.localColorTableFlag ? imageData.imageDescriptor.localColorTable : gifData.globalColorTable; for (int j = 0; j < imageDescriptor.imageWidth; j++) { for (int k = 0; k < imageDescriptor.imageHeight; k++) { int x = left + j; int y = (gifData.canvasHeight - 1) - (top + k); int colorIndex = imageData.colorIndices[j + k * imageDescriptor.imageWidth]; int pixelOffset = x + y * gifData.canvasWidth; if (colorIndex != transparencyIndex) { currentFrame[pixelOffset] = colorTabel[colorIndex]; } } } currentFrame.CopyTo(previousFrame, 0); if (disposalMethod == 0 || disposalMethod == 2) { currentFrame = new Color[currentFrame.Length]; imageData.colors = currentFrame; } else { imageData.colors = new Color[currentFrame.Length]; currentFrame.CopyTo(imageData.colors, 0); } gifData.previousFrame = previousFrame; gifData.currentFrame = currentFrame; gifData.transparentFrame = transparentFrame; }
/// <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) { var colorTable = imgBlock.m_localColorTableFlag ? imgBlock.m_localColorTable : gifData.m_globalColorTableFlag ? gifData.m_globalColorTable : null; if (colorTable != null) { // Set background color from color table var bgRgb = colorTable[gifData.m_bgColorIndex]; bgColor = new Color32(bgRgb[0], bgRgb[1], bgRgb[2], 1); // After inspecting over hours this implementation I have determined that this part is incorrect. // Because this is comparting transparentIndex that comes from the local color table, while the background color index comes from the upper scope (the global one). // (byte)(transparentIndex == gifData.m_bgColorIndex ? 0 : 255)); // El bgColor puede que sea opaco porque se está comparando el transparentIndex (que viene dado por el GraphicControlExtension) // cuando el bgColorIndex que se está comparando viene del "Background Color Index" que es independiente a cada GraphicControlExtension (es global y anexo al GifData) } else { bgColor = Color.black; } return(colorTable); }
/// <summary> /// Set GIF data /// </summary> /// <param name="gifBytes">GIF byte data</param> /// <param name="gifData">ref GIF data</param> /// <param name="debugLog">Debug log flag</param> /// <returns>Result</returns> static bool SetGifData (byte[] gifBytes, ref GifData gifData, bool debugLog) { if (debugLog) { Debug.Log ("SetGifData Start."); } if (gifBytes == null || gifBytes.Length <= 0) { Debug.LogError ("bytes is nothing."); return false; } int byteIndex = 0; if (SetGifHeader (gifBytes, ref byteIndex, ref gifData) == false) { Debug.LogError ("GIF header set error."); return false; } if (SetGifBlock (gifBytes, ref byteIndex, ref gifData) == false) { Debug.LogError ("GIF block set error."); return false; } if (debugLog) { gifData.Dump (); Debug.Log ("SetGifData Finish."); } return true; }
/// <summary> /// Get GIF texture list (This is a possibility of lock up) /// </summary> /// <param name="bytes">GIF file byte data</param> /// <param name="loopCount">out Animation loop count</param> /// <param name="width">out GIF image width (px)</param> /// <param name="height">out GIF image height (px)</param> /// <param name="filterMode">Textures filter mode</param> /// <param name="wrapMode">Textures wrap mode</param> /// <param name="debugLog">Debug Log Flag</param> /// <returns>GIF texture list</returns> public static List <GifTexture> GetTextureList(byte[] bytes, out int loopCount, out int width, out int height, FilterMode filterMode = FilterMode.Bilinear, TextureWrapMode wrapMode = TextureWrapMode.Clamp, bool debugLog = false) { loopCount = -1; width = 0; height = 0; // Set GIF data var gifData = new GifData(); if (SetGifData(bytes, ref gifData, debugLog) == false) { Debug.LogError("GIF file data set error."); return(null); } // Decode to textures from GIF data var gifTexList = new List <GifTexture>(); if (DecodeTexture(gifData, gifTexList, filterMode, wrapMode) == false) { Debug.LogError("GIF texture decode error."); return(null); } loopCount = gifData.appEx.loopCount; width = gifData.logicalScreenWidth; height = gifData.logicalScreenHeight; return(gifTexList); }
private GifImageData readImageData(GifData gifData, byte[] bytes, int offset) { GifImageData imgData = new GifImageData(gifData); int subblockOffset = offset + 1; int subblockCount = 0; imgData.lzwMinimumCodeSize = bytes[offset]; // Read subblock data while (true) { int subblockSize = bytes[subblockOffset]; if (subblockSize == 0) { break; } else { for (int i = 0; i < subblockSize; i++) { imgData.blockBytes.Add(bytes[subblockOffset + 1 + i]); } subblockOffset += subblockSize + 1; subblockCount++; } } imgData.endingOffset = subblockOffset; //Debug.Log("Number of subblocks read: " + subblockCount); return(imgData); }
/* * public Coroutine Init(GifData gif) * { * Coroutine lc; * playing = true; * lc= StartCoroutine(play(gif)); * return lc; * } * * * * private void Start() * { * Init(_g); * } * * public void ChangeGif(GifData gd) { * playing = false; * StopCoroutine(lastCoroutine); * lastCoroutine = Init(gd); * } */ public override void Initialize(GifData gif) { playing = true; StopAllCoroutines(); StartCoroutine(Play(gif)); }
/// <summary> /// 加载gif /// </summary> public void LoadGif() { string fullName = FilePath.picPath + "暗中观察.gif"; GifData data = LoadLocalImage.LoadImage(fullName); _imagePlayer.SetImage(data); }
/// <summary> /// 加载png /// </summary> public void LoadPng() { string fullName = FilePath.picPath + "magic.png"; GifData data = LoadLocalImage.LoadImage(fullName); _imagePlayer.SetImage(data); }
/// <summary> /// 路径错误的情况 /// </summary> public void LoadFalse() { string fullName = FilePath.picPath + "这张图不存在的"; GifData data = LoadLocalImage.LoadImage(fullName); _imagePlayer.SetImage(data); }
void OneFrame(GifData gif) { _imageComponent.sprite = gif.frames[0].sprite; AdjustmentHeightAndWidth(gif); Stop(); }
public GifImageData(GifData gifData) { _gifData = gifData; codeTable = new List <int[]>(4096); colorIndices = new List <int>(256); blockBytes = new List <byte>(255); }
/// <summary> /// Get GraphicControlExtension from GifData /// </summary> private static GraphicControlExtension?GetGraphicCtrlExt(GifData gifData, int imgBlockIndex) { if (gifData.graphicCtrlExList != null && gifData.graphicCtrlExList.Count > imgBlockIndex) { return(gifData.graphicCtrlExList[imgBlockIndex]); } return(null); }
/// <summary> /// Get GIF texture list Coroutine /// </summary> /// <param name="bytes">GIF file byte data</param> /// <param name="callback"> /// Callback method(param is GIF texture list, Animation loop count, GIF image width (px), GIF image /// height (px)) /// </param> /// <param name="thisReference">The this reference.</param> /// <param name="mono">The mono.</param> /// <param name="filterMode">Textures filter mode</param> /// <param name="wrapMode">Textures wrap mode</param> /// <param name="debugLog">Debug Log Flag</param> /// <returns> /// IEnumerator /// </returns> /// <exception cref="ArgumentNullException">mono</exception> public static IEnumerator GetTextureListCoroutine( byte[] bytes, Action <List <GifTexture>, int, int, int> callback, object thisReference, MonoBehaviour mono, FilterMode filterMode = FilterMode.Bilinear, TextureWrapMode wrapMode = TextureWrapMode.Clamp, bool debugLog = false) { var isEditor = thisReference != null; if (mono == null) { throw new ArgumentNullException(nameof(mono)); } var loopCount = -1; var width = 0; var height = 0; // Set GIF data var gifData = new GifData(); if (!SetGifData(bytes, ref gifData, debugLog)) { Debug.LogError("GIF file data set error."); callback?.Invoke(null, loopCount, width, height); yield break; } // Decode to textures from GIF data List <GifTexture> gifTexList = null; yield return(gifData.DecodeTextureCoroutine(result => gifTexList = result, filterMode, wrapMode, isEditor) .CreateSmartCorotine(thisReference, mono)); if (gifTexList == null || gifTexList.Count <= 0) { Debug.LogError( $"GIF texture decode error. Count: {gifTexList.PrintListLength()} || Byte Count: {bytes?.Length}"); callback?.Invoke(null, loopCount, width, height); yield break; } if (gifData.IsNull()) { throw new NullReferenceException(); } loopCount = gifData.m_appEx.loopCount; width = gifData.m_logicalScreenWidth; height = gifData.m_logicalScreenHeight; callback?.Invoke(gifTexList, loopCount, width, height); }
/// <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> /// 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; }
/// <summary> /// Decode to textures from GIF data /// </summary> /// <param name="gifData">GIF data</param> /// <param name="gifTexList">GIF texture list</param> /// <param name="filterMode">Textures filter mode</param> /// <param name="wrapMode">Textures wrap mode</param> /// <returns>IEnumerator</returns> static IEnumerator DecodeTextureCoroutine(GifData gifData, List<GifTexture> gifTexList, FilterMode filterMode, TextureWrapMode wrapMode) { if (gifData.imageBlockList == null || gifData.imageBlockList.Count < 1) { yield break; } Color32? bgColor = GetGlobalBgColor (gifData); // Disposal Method // 0 (No disposal specified) // 1 (Do not dispose) // 2 (Restore to background color) // 3 (Restore to previous) ushort disposalMethod = 0; int imgBlockIndex = 0; foreach (var imgBlock in gifData.imageBlockList) { var decodedData = GetDecodedData (imgBlock); var colorTable = GetColorTable (gifData, imgBlock, ref bgColor); var graphicCtrlEx = GetGraphicCtrlExt (gifData, imgBlockIndex); int transparentIndex = GetTransparentIndex (graphicCtrlEx); // avoid lock up yield return 0; bool useBeforeTex = false; var tex = CreateTexture2D (gifData, gifTexList, imgBlockIndex, disposalMethod, filterMode, wrapMode, ref useBeforeTex); // 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, imgBlock, decodedData, ref dataIndex, colorTable, bgColor, transparentIndex, useBeforeTex); // avoid lock up //if (y % 10 == 0) { // yield return 0; //} } tex.Apply (); float delaySec = GetDelaySec (graphicCtrlEx); // Add to GIF texture list gifTexList.Add (new GifTexture (tex, delaySec)); disposalMethod = GetDisposalMethod (graphicCtrlEx); imgBlockIndex++; // avoid lock up yield return 0; } }
public GifImageData(GifData gifData) { _gifData = gifData; _colors = new Dictionary <int, GifColor>(256); codeTable = new Dictionary <int, int[]>(4096); colorIndices = new List <int>(256); blockBytes = new List <byte>(255); }
public GifImageData(GifData gifData) { _gifData = gifData; _colors = new Dictionary<int, GifColor>(256); codeTable = new Dictionary<int, int[]>(4096); colorIndices = new List<int>(256); blockBytes = new List<byte>(255); }
/// <summary> /// Decode to textures from GIF data /// </summary> /// <param name="gifData">GIF data</param> /// <param name="gifTexList">GIF texture list</param> /// <param name="filterMode">Textures filter mode</param> /// <param name="wrapMode">Textures wrap mode</param> /// <returns>Result</returns> private static bool DecodeTexture(GifData gifData, List <GifTexture> gifTexList, FilterMode filterMode, TextureWrapMode wrapMode) { if (gifData.imageBlockList == null || gifData.imageBlockList.Count < 1) { return(false); } Color32?bgColor = GetGlobalBgColor(gifData); // Disposal Method // 0 (No disposal specified) // 1 (Do not dispose) // 2 (Restore to background color) // 3 (Restore to previous) ushort disposalMethod = 0; int imgBlockIndex = 0; foreach (var imgBlock in gifData.imageBlockList) { var decodedData = GetDecodedData(imgBlock); var colorTable = GetColorTable(gifData, imgBlock, ref bgColor); var graphicCtrlEx = GetGraphicCtrlExt(gifData, imgBlockIndex); int transparentIndex = GetTransparentIndex(graphicCtrlEx); bool useBeforeTex = false; var tex = CreateTexture2D(gifData, gifTexList, imgBlockIndex, disposalMethod, filterMode, wrapMode, ref useBeforeTex); // 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, imgBlock, decodedData, ref dataIndex, colorTable, bgColor, transparentIndex, useBeforeTex); } tex.Apply(); float delaySec = GetDelaySec(graphicCtrlEx); // Add to GIF texture list if (gifTexList == null) { gifTexList = new List <GifTexture>(); } gifTexList.Add(new GifTexture(tex, delaySec)); disposalMethod = GetDisposalMethod(graphicCtrlEx); imgBlockIndex++; } return(true); }
void MultipleFrames(GifData gif) { _gifData = gif; AdjustmentHeightAndWidth(gif); ComputeNextFrameTimeArray(gif); PlayFromBeginning(); }
/* * 假设Image是正方形,有四种情况 * 1.图片是扁的、适应大小 -> 保持宽度 * 2.图片是扁的、填充 -> 保持高度 * 3.图片是高的、适应大小 -> 保持高度 * 4.图片是高的、填充 -> 保持宽度 * * 假设Image不是正方形,宽度 / 高度 = WH * 1.图片WH > 显示WH、适应 -> 保持宽度 * 2.图片WH > 显示WH、填充 -> 保持高度 * 3.图片WH < 显示WH、适应 -> 保持高度 * 4.图片WH < 显示WH、填充 -> 保持宽度 * * 需要先求出适应和填充,之后插值,则 * 1.图片WH > 显示WH、适应 -> 保持宽度 * 2.图片WH < 显示WH、适应 -> 保持高度 * 3.图片WH > 显示WH、填充 -> 保持高度 * 4.图片WH < 显示WH、填充 -> 保持宽度 */ void AdjustmentHeightAndWidth(GifData imageData) { Vector2 imageSize = new Vector2(imageData.frames[0].sprite.texture.width, imageData.frames[0].sprite.texture.height); //如果用 imageData.frames[0].sprite.textureRect.size 就能直接获取到宽高,但不知道为什么有时候宽高会获取错误,只能这样分两次获取了 Vector2 adaptableSize = GetAdaptableSizeDelta(imageSize); Vector2 fillSize = GetFillSizeDelta(imageSize); _rectTransform.sizeDelta = Vector2.Lerp(adaptableSize, fillSize, _AdaptableToFill); }
private GifGraphicsControlExtension readGraphicsControlExtension(GifData gifData, byte[] bytes, int offset) { GifGraphicsControlExtension gce = new GifGraphicsControlExtension(gifData); gce.disposalMethod = BitHelper.getIntFromPackedByte(bytes[offset + 3], 3, 6); gce.transparentColorFlag = BitHelper.getIntFromPackedByte(bytes[offset + 3], 7, 8) == 1; gce.delayTime = BitHelper.getInt16FromBytes(bytes, offset + 4); gce.transparentColorIndex = bytes[offset + 6]; return(gce); }
public void ChangeGif(GifData gd) { lastPlayed = _g; playing = false; //StopCoroutine(play(_g)); if (lastPlayed.name != gd.name) { Initialize(gd); } }
static async Task <Texture2D> DecodeTextureAsync ( GifData gifData, FilterMode filterMode, TextureWrapMode wrapMode ) { if (gifData.m_imageBlockList == null || gifData.m_imageBlockList.Count < 1) { await Task.Delay(1); } List <ushort> disposalMethodList = new List <ushort>(gifData.m_imageBlockList.Count); int imgIndex = 0; int i = 0; 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); await Task.Delay(1); bool filledTexture; Texture2D texture = CreateTexture2D(gifData, imgIndex, disposalMethodList, bgColor, filterMode, wrapMode, out filledTexture); await Task.Delay(1); // Set pixel data int dataIndex = 0; // Reverse set pixels. because GIF data starts from the top left. for (int y = texture.height - 1; y >= 0; y--) { SetTexturePixelRow(texture, y, gifData.m_imageBlockList[i], decodedData, ref dataIndex, colorTable, bgColor, transparentIndex, filledTexture); } texture.Apply(); await Task.Delay(1); float delaySec = GetDelaySec(graphicCtrlEx); imgIndex++; return(texture); }
public GifImageData(GifData gifData, int curOffset) { _curOffset = curOffset; _gifData = gifData; _colors = new Dictionary <int, GifColor>(256); _indices = new HashSet <int>(); codeTable = new Dictionary <int, int[]>(4096); colorIndices = new List <int>(256); blockBytes = new List <byte>(255); }
private void Start() { var sw = Stopwatch.StartNew(); _gifData = parseGifData(gif.bytes); createAnimator(_gifData); sw.Stop(); Debug.Log($"Gif readed in {sw.ElapsedMilliseconds} ms!"); Debug.Log(_gifData.ToString()); }
IEnumerator play(GifData gif) { while (playing) { for (int j = 0; j < gif.frames.Length; j++) { spritey.sprite = Sprite.Create(gif.frames[j], new Rect(0, 0, gif.frames[j].width, gif.frames[j].height), new Vector2(0.5f, 0.5f)); yield return(new WaitForSeconds(gif.delay)); } } playing = false; }
/// <summary> /// Get background color from global color table /// </summary> private static Color32?GetGlobalBgColor(GifData gifData) { Color32?bgColor = null; if (gifData.globalColorTableFlag) { // Set background color from global color table byte[] bgRgb = gifData.globalColorTable[gifData.bgColorIndex]; bgColor = new Color32(bgRgb[0], bgRgb[1], bgRgb[2], 255); } return(bgColor); }
IEnumerator play(GifData gif) { while (playing) { for (int j = 0; j < gif.frames.Length; j++) { img.texture = gif.frames[j]; yield return(new WaitForSeconds(gif.delay)); } } playing = false; }
/// <summary> /// Get GIF texture list Coroutine (Avoid lock up but more slow) /// </summary> /// <param name="mb">MonoBehaviour to start the coroutine</param> /// <param name="bytes">GIF file byte data</param> /// <param name="cb">Callback method(param is GIF texture list, Animation loop count, GIF image width (px), GIF image height (px))</param> /// <param name="filterMode">Textures filter mode</param> /// <param name="wrapMode">Textures wrap mode</param> /// <param name="debugLog">Debug Log Flag</param> /// <returns>IEnumerator</returns> public static IEnumerator GetTextureListCoroutine (MonoBehaviour mb, byte[] bytes, Action<List<GifTexture>, int, int, int> cb, FilterMode filterMode = FilterMode.Bilinear, TextureWrapMode wrapMode = TextureWrapMode.Clamp, bool debugLog = false) { int loopCount = -1; int width = 0; int height = 0; // Set GIF data var gifData = new GifData (); if (SetGifData (bytes, ref gifData, debugLog) == false) { Debug.LogError ("GIF file data set error."); if (cb != null) { cb (null, loopCount, width, height); } yield break; } // avoid lock up yield return 0; // Decode to textures from GIF data List<GifTexture> gifTexList = null; yield return mb.StartCoroutine (UniGif.DecodeTextureCoroutine (gifData, gtList => { gifTexList = gtList; }, filterMode, wrapMode)); if (gifTexList == null) { Debug.LogError ("GIF texture decode error."); if (cb != null) { cb (null, loopCount, width, height); } yield break; } loopCount = gifData.appEx.loopCount; width = gifData.logicalScreenWidth; height = gifData.logicalScreenHeight; if (cb != null) { cb (gifTexList, loopCount, width, height); } }
static void SetGraphicControlExtension (byte[] gifBytes, ref int byteIndex, ref GifData gifData) { GraphicControlExtension gcEx = new GraphicControlExtension (); // Extension Introducer(1 Byte) // 0x21 gcEx.extensionIntroducer = gifBytes[byteIndex]; byteIndex++; // Graphic Control Label(1 Byte) // 0xf9 gcEx.graphicControlLabel = gifBytes[byteIndex]; byteIndex++; // Block Size(1 Byte) // 0x04 gcEx.blockSize = gifBytes[byteIndex]; byteIndex++; // 1 Byte { // Reserved(3 Bits) // Unused // Disposal Mothod(3 Bits) // 0 (No disposal specified) // 1 (Do not dispose) // 2 (Restore to background color) // 3 (Restore to previous) switch (gifBytes[byteIndex] & 28) { // 0b00011100 case 4: // 0b00000100 gcEx.disposalMethod = 1; break; case 8: // 0b00001000 gcEx.disposalMethod = 2; break; case 12: // 0b00001100 gcEx.disposalMethod = 3; break; default: gcEx.disposalMethod = 0; break; } // User Input Flag(1 Bit) // Unknown // Transparent Color Flag(1 Bit) gcEx.transparentColorFlag = (gifBytes[byteIndex] & 1) == 1; // 0b00000001 byteIndex++; } // Delay Time(2 Bytes) gcEx.delayTime = BitConverter.ToUInt16 (gifBytes, byteIndex); byteIndex += 2; // Transparent Color Index(1 Byte) gcEx.transparentColorIndex = gifBytes[byteIndex]; byteIndex++; // Block Terminator(1 Byte) gcEx.blockTerminator = gifBytes[byteIndex]; byteIndex++; if (gifData.graphicCtrlExList == null) { gifData.graphicCtrlExList = new List<GraphicControlExtension> (); } gifData.graphicCtrlExList.Add (gcEx); }
/// <summary> /// Get GIF texture list Coroutine (Avoid lock up but more slow) /// </summary> /// <param name="mb">MonoBehaviour to start the coroutine</param> /// <param name="bytes">GIF file byte data</param> /// <param name="cb">Callback method(param is GIF texture list, Animation loop count, GIF image width (px), GIF image height (px))</param> /// <param name="filterMode">Textures filter mode</param> /// <param name="wrapMode">Textures wrap mode</param> /// <param name="debugLog">Debug Log Flag</param> /// <returns>IEnumerator</returns> public static IEnumerator GetTextureListCoroutine (MonoBehaviour mb, byte[] bytes, int instanceId, List<GifTexture> gifTexList, Action<List<GifTexture>, int, int, int> cb, FilterMode filterMode = FilterMode.Bilinear, TextureWrapMode wrapMode = TextureWrapMode.Clamp, bool debugLog = false) { int loopCount = -1; int width = 0; int height = 0; if (gifTexList == null) { gifTexList = new List<GifTexture>(); } else { gifTexList = gifDict[instanceId].textureList; } if (!gifDict.ContainsKey(instanceId)) { // Set GIF data var gifData = new GifData(); if (SetGifData(bytes, ref gifData, debugLog) == false) { Debug.LogError("GIF file data set error."); if (cb != null) { cb(null, loopCount, width, height); } yield break; } loopCount = gifData.appEx.loopCount; width = gifData.logicalScreenWidth; height = gifData.logicalScreenHeight; Debug.Log("loopCount " + loopCount); gifDict.Add(instanceId, new GifAnimation { loopCount = loopCount, width = width, height = height, textureList = gifTexList, }); // avoid lock up yield return 0; // Decode to textures from GIF data yield return mb.StartCoroutine(UniGif.DecodeTextureCoroutine(gifData, gifTexList, filterMode, wrapMode)); } if (cb != null) { //Debug.Log(gifDict[instanceId].textureList.Count + " " + gifDict[instanceId].loopCount); //if (gifDict[instanceId].textureList.Count == gifDict[instanceId].loopCount) //{ loopCount = gifDict[instanceId].loopCount; width = gifDict[instanceId].width; height = gifDict[instanceId].height; gifTexList = gifDict[instanceId].textureList; cb(gifTexList, loopCount, width, height); // yield break; //} //yield return new WaitForSeconds(0.1f); } }
public GifGraphicsControlExtension(GifData gifData) { _gifData = gifData; }
/// <summary> /// Get GraphicControlExtension from GifData /// </summary> static GraphicControlExtension? GetGraphicCtrlExt (GifData gifData, int imgBlockIndex) { if (gifData.graphicCtrlExList != null && gifData.graphicCtrlExList.Count > imgBlockIndex) { return gifData.graphicCtrlExList[imgBlockIndex]; } return null; }
/// <summary> /// Create Texture2D object and initial settings /// </summary> static Texture2D CreateTexture2D (GifData gifData, List<GifTexture> gifTexList, int imgBlockIndex, ushort disposalMethod, FilterMode filterMode, TextureWrapMode wrapMode, ref bool useBeforeTex) { // Create texture Texture2D tex = new Texture2D (gifData.logicalScreenWidth, gifData.logicalScreenHeight, TextureFormat.ARGB32, false); tex.filterMode = filterMode; tex.wrapMode = wrapMode; // Check dispose useBeforeTex = false; int beforeIndex = -1; if (imgBlockIndex > 0 && disposalMethod == 0 || disposalMethod == 1) { // before 1 beforeIndex = imgBlockIndex - 1; } else if (imgBlockIndex > 1 && disposalMethod == 3) { // before 2 beforeIndex = imgBlockIndex - 2; } if (beforeIndex >= 0) { // Do not dispose useBeforeTex = true; Color32[] pix = gifTexList[beforeIndex].texture2d.GetPixels32 (); tex.SetPixels32 (pix); tex.Apply (); } return tex; }
/// <summary> /// Get background color from global color table /// </summary> static Color32? GetGlobalBgColor (GifData gifData) { Color32? bgColor = null; if (gifData.globalColorTableFlag) { // Set background color from global color table byte[] bgRgb = gifData.globalColorTable[gifData.bgColorIndex]; bgColor = new Color32 (bgRgb[0], bgRgb[1], bgRgb[2], 255); } return bgColor; }
/// <summary> /// Get color table (local or global) /// </summary> static List<byte[]> GetColorTable (GifData gifData, ImageBlock imgBlock, ref Color32? bgColor) { var colorTable = imgBlock.localColorTableFlag ? imgBlock.localColorTable : gifData.globalColorTable; if (imgBlock.localColorTableFlag) { // Set background color from local color table byte[] bgRgb = imgBlock.localColorTable[gifData.bgColorIndex]; bgColor = new Color32 (bgRgb[0], bgRgb[1], bgRgb[2], 255); } return colorTable; }
static void SetImageBlock (byte[] gifBytes, ref int byteIndex, ref GifData gifData) { ImageBlock ib = new ImageBlock (); // Image Separator(1 Byte) // 0x2c ib.imageSeparator = gifBytes[byteIndex]; byteIndex++; // Image Left Position(2 Bytes) ib.imageLeftPosition = BitConverter.ToUInt16 (gifBytes, byteIndex); byteIndex += 2; // Image Top Position(2 Bytes) ib.imageTopPosition = BitConverter.ToUInt16 (gifBytes, byteIndex); byteIndex += 2; // Image Width(2 Bytes) ib.imageWidth = BitConverter.ToUInt16 (gifBytes, byteIndex); byteIndex += 2; // Image Height(2 Bytes) ib.imageHeight = BitConverter.ToUInt16 (gifBytes, byteIndex); byteIndex += 2; // 1 Byte { // Local Color Table Flag(1 Bit) ib.localColorTableFlag = (gifBytes[byteIndex] & 128) == 128; // 0b10000000 // Interlace Flag(1 Bit) ib.interlaceFlag = (gifBytes[byteIndex] & 64) == 64; // 0b01000000 // Sort Flag(1 Bit) ib.sortFlag = (gifBytes[byteIndex] & 32) == 32; // 0b00100000 // Reserved(2 Bits) // Unused // Size of Local Color Table(3 Bits) int val = (gifBytes[byteIndex] & 7) + 1; ib.sizeOfLocalColorTable = (int) Math.Pow (2, val); byteIndex++; } if (ib.localColorTableFlag) { // Local Color Table(0~255×3 Bytes) ib.localColorTable = new List<byte[]> (); for (int i = byteIndex; i < byteIndex + (ib.sizeOfLocalColorTable * 3); i += 3) { ib.localColorTable.Add (new byte[] { gifBytes[i], gifBytes[i + 1], gifBytes[i + 2] }); } byteIndex = byteIndex + (ib.sizeOfLocalColorTable * 3); } // LZW Minimum Code Size(1 Byte) ib.LzwMinimumCodeSize = gifBytes[byteIndex]; byteIndex++; // Block Size & Image Data List while (true) { // Block Size(1 Byte) byte blockSize = gifBytes[byteIndex]; byteIndex++; if (blockSize == 0x00) { // Block Terminator(1 Byte) break; } var imageDataBlock = new ImageBlock.ImageDataBlock (); imageDataBlock.blockSize = blockSize; // Image Data(? Bytes) imageDataBlock.imageData = new byte[imageDataBlock.blockSize]; for (int i = 0; i < imageDataBlock.imageData.Length; i++) { imageDataBlock.imageData[i] = gifBytes[byteIndex]; byteIndex++; } if (ib.imageDataList == null) { ib.imageDataList = new List<ImageBlock.ImageDataBlock> (); } ib.imageDataList.Add (imageDataBlock); } if (gifData.imageBlockList == null) { gifData.imageBlockList = new List<ImageBlock> (); } gifData.imageBlockList.Add (ib); }
static bool SetGifBlock (byte[] gifBytes, ref int byteIndex, ref GifData gifData) { try { int lastIndex = 0; while (true) { int nowIndex = byteIndex; if (gifBytes[nowIndex] == 0x2c) { // Image Block(0x2c) SetImageBlock (gifBytes, ref byteIndex, ref gifData); } else if (gifBytes[nowIndex] == 0x21) { // Extension switch (gifBytes[nowIndex + 1]) { case 0xf9: // Graphic Control Extension(0x21 0xf9) SetGraphicControlExtension (gifBytes, ref byteIndex, ref gifData); break; case 0xfe: // Comment Extension(0x21 0xfe) SetCommentExtension (gifBytes, ref byteIndex, ref gifData); break; case 0x01: // Plain Text Extension(0x21 0x01) SetPlainTextExtension (gifBytes, ref byteIndex, ref gifData); break; case 0xff: // Application Extension(0x21 0xff) SetApplicationExtension (gifBytes, ref byteIndex, ref gifData); break; default: break; } } else if (gifBytes[nowIndex] == 0x3b) { // Trailer(1 Byte) gifData.trailer = gifBytes[byteIndex]; byteIndex++; break; } if (lastIndex == nowIndex) { Debug.LogError ("Infinite loop error."); return false; } lastIndex = nowIndex; } } catch (Exception ex) { Debug.LogError (ex.Message); return false; } return true; }
static bool SetGifHeader (byte[] gifBytes, ref int byteIndex, ref GifData gifData) { // Signature(3 Bytes) // 0x47 0x49 0x46 (GIF) if (gifBytes[0] != 'G' || gifBytes[1] != 'I' || gifBytes[2] != 'F') { Debug.LogError ("This is not GIF image."); return false; } gifData.sig0 = gifBytes[0]; gifData.sig1 = gifBytes[1]; gifData.sig2 = gifBytes[2]; // Version(3 Bytes) // 0x38 0x37 0x61 (87a) or 0x38 0x39 0x61 (89a) if ((gifBytes[3] != '8' || gifBytes[4] != '7' || gifBytes[5] != 'a') && (gifBytes[3] != '8' || gifBytes[4] != '9' || gifBytes[5] != 'a')) { Debug.LogError ("GIF version error.\nSupported only GIF87a or GIF89a."); return false; } gifData.ver0 = gifBytes[3]; gifData.ver1 = gifBytes[4]; gifData.ver2 = gifBytes[5]; // Logical Screen Width(2 Bytes) gifData.logicalScreenWidth = BitConverter.ToUInt16 (gifBytes, 6); // Logical Screen Height(2 Bytes) gifData.logicalScreenHeight = BitConverter.ToUInt16 (gifBytes, 8); // 1 Byte { // Global Color Table Flag(1 Bit) gifData.globalColorTableFlag = (gifBytes[10] & 128) == 128; // 0b10000000 // Color Resolution(3 Bits) switch (gifBytes[10] & 112) { case 112: // 0b01110000 gifData.colorResolution = 8; break; case 96: // 0b01100000 gifData.colorResolution = 7; break; case 80: // 0b01010000 gifData.colorResolution = 6; break; case 64: // 0b01000000 gifData.colorResolution = 5; break; case 48: // 0b00110000 gifData.colorResolution = 4; break; case 32: // 0b00100000 gifData.colorResolution = 3; break; case 16: // 0b00010000 gifData.colorResolution = 2; break; default: gifData.colorResolution = 1; break; } // Sort Flag(1 Bit) gifData.sortFlag = (gifBytes[10] & 8) == 8; // 0b00001000 // Size of Global Color Table(3 Bits) int val = (gifBytes[10] & 7) + 1; gifData.sizeOfGlobalColorTable = (int) Math.Pow (2, val); } // Background Color Index(1 Byte) gifData.bgColorIndex = gifBytes[11]; // Pixel Aspect Ratio(1 Byte) gifData.pixelAspectRatio = gifBytes[12]; byteIndex = 13; if (gifData.globalColorTableFlag) { // Global Color Table(0~255×3 Bytes) gifData.globalColorTable = new List<byte[]> (); for (int i = byteIndex; i < byteIndex + (gifData.sizeOfGlobalColorTable * 3); i += 3) { gifData.globalColorTable.Add (new byte[] { gifBytes[i], gifBytes[i + 1], gifBytes[i + 2] }); } byteIndex = byteIndex + (gifData.sizeOfGlobalColorTable * 3); } return true; }
static void SetApplicationExtension (byte[] gifBytes, ref int byteIndex, ref GifData gifData) { // Extension Introducer(1 Byte) // 0x21 gifData.appEx.extensionIntroducer = gifBytes[byteIndex]; byteIndex++; // Extension Label(1 Byte) // 0xff gifData.appEx.extensionLabel = gifBytes[byteIndex]; byteIndex++; // Block Size(1 Byte) // 0x0b gifData.appEx.blockSize = gifBytes[byteIndex]; byteIndex++; // Application Identifier(8 Bytes) gifData.appEx.appId1 = gifBytes[byteIndex]; byteIndex++; gifData.appEx.appId2 = gifBytes[byteIndex]; byteIndex++; gifData.appEx.appId3 = gifBytes[byteIndex]; byteIndex++; gifData.appEx.appId4 = gifBytes[byteIndex]; byteIndex++; gifData.appEx.appId5 = gifBytes[byteIndex]; byteIndex++; gifData.appEx.appId6 = gifBytes[byteIndex]; byteIndex++; gifData.appEx.appId7 = gifBytes[byteIndex]; byteIndex++; gifData.appEx.appId8 = gifBytes[byteIndex]; byteIndex++; // Application Authentication Code(3 Bytes) gifData.appEx.appAuthCode1 = gifBytes[byteIndex]; byteIndex++; gifData.appEx.appAuthCode2 = gifBytes[byteIndex]; byteIndex++; gifData.appEx.appAuthCode3 = gifBytes[byteIndex]; byteIndex++; // Block Size & Application Data List while (true) { // Block Size (1 Byte) byte blockSize = gifBytes[byteIndex]; byteIndex++; if (blockSize == 0x00) { // Block Terminator(1 Byte) break; } var appDataBlock = new ApplicationExtension.ApplicationDataBlock (); appDataBlock.blockSize = blockSize; // Application Data(n Byte) appDataBlock.applicationData = new byte[appDataBlock.blockSize]; for (int i = 0; i < appDataBlock.applicationData.Length; i++) { appDataBlock.applicationData[i] = gifBytes[byteIndex]; byteIndex++; } if (gifData.appEx.appDataList == null) { gifData.appEx.appDataList = new List<ApplicationExtension.ApplicationDataBlock> (); } gifData.appEx.appDataList.Add (appDataBlock); } }
static void SetPlainTextExtension (byte[] gifBytes, ref int byteIndex, ref GifData gifData) { PlainTextExtension plainTxtEx = new PlainTextExtension (); // Extension Introducer(1 Byte) // 0x21 plainTxtEx.extensionIntroducer = gifBytes[byteIndex]; byteIndex++; // Plain Text Label(1 Byte) // 0x01 plainTxtEx.plainTextLabel = gifBytes[byteIndex]; byteIndex++; // Block Size(1 Byte) // 0x0c plainTxtEx.blockSize = gifBytes[byteIndex]; byteIndex++; // Text Grid Left Position(2 Bytes) // Not supported byteIndex += 2; // Text Grid Top Position(2 Bytes) // Not supported byteIndex += 2; // Text Grid Width(2 Bytes) // Not supported byteIndex += 2; // Text Grid Height(2 Bytes) // Not supported byteIndex += 2; // Character Cell Width(1 Bytes) // Not supported byteIndex++; // Character Cell Height(1 Bytes) // Not supported byteIndex++; // Text Foreground Color Index(1 Bytes) // Not supported byteIndex++; // Text Background Color Index(1 Bytes) // Not supported byteIndex++; // Block Size & Plain Text Data List while (true) { // Block Size(1 Byte) byte blockSize = gifBytes[byteIndex]; byteIndex++; if (blockSize == 0x00) { // Block Terminator(1 Byte) break; } var plainTextDataBlock = new PlainTextExtension.PlainTextDataBlock (); plainTextDataBlock.blockSize = blockSize; // Plain Text Data(n Byte) plainTextDataBlock.plainTextData = new byte[plainTextDataBlock.blockSize]; for (int i = 0; i < plainTextDataBlock.plainTextData.Length; i++) { plainTextDataBlock.plainTextData[i] = gifBytes[byteIndex]; byteIndex++; } if (plainTxtEx.plainTextDataList == null) { plainTxtEx.plainTextDataList = new List<PlainTextExtension.PlainTextDataBlock> (); } plainTxtEx.plainTextDataList.Add (plainTextDataBlock); } if (gifData.plainTextExList == null) { gifData.plainTextExList = new List<PlainTextExtension> (); } gifData.plainTextExList.Add (plainTxtEx); }
static void SetCommentExtension (byte[] gifBytes, ref int byteIndex, ref GifData gifData) { CommentExtension commentEx = new CommentExtension (); // Extension Introducer(1 Byte) // 0x21 commentEx.extensionIntroducer = gifBytes[byteIndex]; byteIndex++; // Comment Label(1 Byte) // 0xfe commentEx.commentLabel = gifBytes[byteIndex]; byteIndex++; // Block Size & Comment Data List while (true) { // Block Size(1 Byte) byte blockSize = gifBytes[byteIndex]; byteIndex++; if (blockSize == 0x00) { // Block Terminator(1 Byte) break; } var commentDataBlock = new CommentExtension.CommentDataBlock (); commentDataBlock.blockSize = blockSize; // Comment Data(n Byte) commentDataBlock.commentData = new byte[commentDataBlock.blockSize]; for (int i = 0; i < commentDataBlock.commentData.Length; i++) { commentDataBlock.commentData[i] = gifBytes[byteIndex]; byteIndex++; } if (commentEx.commentDataList == null) { commentEx.commentDataList = new List<CommentExtension.CommentDataBlock> (); } commentEx.commentDataList.Add (commentDataBlock); } if (gifData.commentExList == null) { gifData.commentExList = new List<CommentExtension> (); } gifData.commentExList.Add (commentEx); }
public GifImageDescriptor(GifData gifData) { _gifData = gifData; }