Exemplo n.º 1
0
		private static ImageFrame ReadImageFrame(Stream stream, byte[] globalColorTable, GraphicControlExtension graphicControlExtension)
		{
			var imageDescriptor = ImageDescriptor.Read(stream);

			var imageFrame = new ImageFrame
				{
					ImageDescriptor = imageDescriptor,
					LocalColorTable = globalColorTable,
					GraphicControlExtension = graphicControlExtension
				};

			if (imageDescriptor.LocalColorTableFlag)
			{
				imageFrame.LocalColorTable = stream.ReadBytes(imageDescriptor.LocalColorTableSize * 3);
			}

			imageFrame.ColorDepth = stream.ReadByte();

			var lzwDecoder = new LzwDecoder(stream);
			var imageData = lzwDecoder.DecodeImageData(imageDescriptor.ImageWidth, imageDescriptor.ImageHeight, imageFrame.ColorDepth);

			ApplicationData.Read(stream);

			imageFrame.Bitmap = CreateBitmap(
				imageData,
				imageFrame.GetPalette(),
				imageDescriptor.InterlaceFlag,
				imageDescriptor.ImageWidth,
				imageDescriptor.ImageHeight);

			return imageFrame;
		}
Exemplo n.º 2
0
 public GifFrame(Stream inputStream,
                 LogicalScreenDescriptor lsd,
                 ColourTable gct,
                 GraphicControlExtension gce,
                 GifFrame previousFrame,
                 GifFrame previousFrameBut1)
     : base(inputStream, lsd, gct, gce, previousFrame, previousFrameBut1)
 {
 }
Exemplo n.º 3
0
        public void ConvertFrame()
        {
            for (var i = 0; i < Frames.Count; i++)
            {
                var  index = i;//(int)context;
                var  colorTable = colorTables[index];
                var  localColorTableFlag = (byte)(colorTable == globalColorTable ? 0 : 1);
                var  localColorTableSize = GetColorTableSize(colorTable);
                byte transparentColorFlag = 0, transparentColorIndex = 0;
                byte max;
                var  colorIndexes            = GetColorIndexes(Frames[index].Texture, scale, colorTable, localColorTableFlag, ref transparentColorFlag, ref transparentColorIndex, out max);
                var  graphicControlExtension = new GraphicControlExtension(4, 0, (byte)Frames[index].DisposalMethod, 0, transparentColorFlag, (ushort)(100 * Frames[index].Delay), transparentColorIndex);
                var  imageDescriptor         = new ImageDescriptor(0, 0, Width, Height, localColorTableFlag, 0, 0, 0, localColorTableSize);
                var  minCodeSize             = LzwEncoder.GetMinCodeSize(max);
                var  lzw = LzwEncoder.Encode(colorIndexes, minCodeSize);
                var  tableBasedImageData = new TableBasedImageData(minCodeSize, lzw);
                var  tmpBytes            = new List <byte>();

                tmpBytes.AddRange(graphicControlExtension.GetBytes());
                tmpBytes.AddRange(imageDescriptor.GetBytes());

                if (localColorTableFlag == 1)
                {
                    tmpBytes.AddRange(ColorTableToBytes(colorTable, localColorTableSize));
                }

                tmpBytes.AddRange(tableBasedImageData.GetBytes());


                encoded.Add(index, tmpBytes);
                // encodeProgress.Progress++;

                if (encoded.Count == Frames.Count)
                {
                    var globalColorTableSize    = GetColorTableSize(globalColorTable);
                    var logicalScreenDescriptor = new LogicalScreenDescriptor(Width, Height, 1, 7, 0, globalColorTableSize, 0, 0);
                    var binary = new List <byte>();

                    binary.AddRange(Encoding.UTF8.GetBytes(header));
                    binary.AddRange(logicalScreenDescriptor.GetBytes());
                    binary.AddRange(ColorTableToBytes(globalColorTable, globalColorTableSize));
                    binary.AddRange(applicationExtension.GetBytes());
                    binary.AddRange(encoded.OrderBy(j => j.Key).SelectMany(j => j.Value));
                    binary.Add(0x3B); // GIF Trailer.

                    bytes = binary.ToArray();
                    // encodeProgress.Completed = true;
                }

                // onProgress(encodeProgress);
            }

            currentStep++;
        }
Exemplo n.º 4
0
 internal static void CheckGraphicControlExtension(GraphicControlExtension ext)
 {
     Assert.AreEqual(GraphicControlExtension.ExpectedBlockSize,
                     ext.BlockSize,
                     "ExpectedBlockSize");
     Assert.AreEqual(DelayTime, ext.DelayTime, "DelayTime");
     Assert.AreEqual(DisposalMethod, ext.DisposalMethod, "DisposalMethod");
     Assert.AreEqual(TransparentColourIndex,
                     ext.TransparentColourIndex,
                     "TransparentColourIndex");
     Assert.AreEqual(HasTransparentColour, ext.HasTransparentColour, "HasTransparentColour");
     Assert.AreEqual(ExpectsUserInput, ext.ExpectsUserInput, "ExpectsUserInput");
 }
Exemplo n.º 5
0
        private static GifFrame DecodeFrame(GraphicControlExtension extension, ImageDescriptor descriptor, byte[] colorIndexes, bool filled, int Width, int Height, Color[] state)
        {
            var frame            = new GifFrame();
            var pixels           = state;
            var transparentIndex = -1;

            if (extension != null)
            {
                frame.Delay          = extension.DelayTime / 100f;
                frame.DisposalMethod = (DisposalMethod)extension.DisposalMethod;

                if (frame.DisposalMethod == DisposalMethod.RestoreToPrevious)
                {
                    pixels = state.ToArray();
                }

                if (extension.TransparentColorFlag == 1)
                {
                    transparentIndex = extension.TransparentColorIndex;
                }
            }

            for (var y = 0; y < descriptor.ImageHeight; y++)
            {
                for (var x = 0; x < descriptor.ImageWidth; x++)
                {
                    var colorIndex  = colorIndexes[x + y * descriptor.ImageWidth];
                    var transparent = colorIndex == transparentIndex;

                    if (transparent && !filled)
                    {
                        continue;
                    }

                    var color = EmptyColor;                                   //transparent ? EmptyColor : colorTable[colorIndex];
                    var fx    = x + descriptor.ImageLeftPosition;
                    var fy    = Height - y - 1 - descriptor.ImageTopPosition; // Y-flip

                    pixels[fx + fy * Width] = pixels[fx + fy * Width] = color;
                }
            }

            frame.Texture = new Texture2D(Width, Height);
            frame.Texture.SetPixels32(pixels);
            // frame.Texture.Apply();

            return(frame);
        }
Exemplo n.º 6
0
    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 disposal method from GraphicControlExtension
 /// </summary>
 static ushort GetDisposalMethod (GraphicControlExtension? graphicCtrlEx)
 {
     return graphicCtrlEx != null ? graphicCtrlEx.Value.disposalMethod : (ushort) 2;
 }
 /// <summary>
 /// Get delay seconds from GraphicControlExtension
 /// </summary>
 static float GetDelaySec (GraphicControlExtension? graphicCtrlEx)
 {
     // Get delay sec from GraphicControlExtension
     float delaySec = graphicCtrlEx != null ? (float) graphicCtrlEx.Value.delayTime / 100f : 0.1f;
     // Minimum 0.1 seconds delay (because major browsers have become so...)
     if (delaySec < 0.1f) {
         delaySec = 0.1f;
     }
     return delaySec;
 }
 /// <summary>
 /// Get transparent color index from GraphicControlExtension
 /// </summary>
 static int GetTransparentIndex (GraphicControlExtension? graphicCtrlEx)
 {
     int transparentIndex = -1;
     if (graphicCtrlEx != null && graphicCtrlEx.Value.transparentColorFlag) {
         transparentIndex = graphicCtrlEx.Value.transparentColorIndex;
     }
     return transparentIndex;
 }
Exemplo n.º 10
0
        private void FinishDecoding()
        {
            // var globalColorTable = parser.LogicalScreenDescriptor.GlobalColorTableFlag == 1 ? GetUnityColors(parser.GlobalColorTable) : null;
            //var backgroundColor = globalColorTable?[parser.LogicalScreenDescriptor.BackgroundColorIndex] ?? EmptyColor;
            GraphicControlExtension gcExtension = null;

            Width  = parser.LogicalScreenDescriptor.LogicalScreenWidth;
            Height = parser.LogicalScreenDescriptor.LogicalScreenHeight;
            state  = new Color[Width * Height];
            filled = false;
            frames = new List <GifFrame>();

            for (var j = 0; j < parser.Blocks.Count; j++)
            {
                if (parser.Blocks[j] is GraphicControlExtension)
                {
                    gcExtension = (GraphicControlExtension)parser.Blocks[j];
                }
                else if (parser.Blocks[j] is ImageDescriptor)
                {
                    var imageDescriptor = (ImageDescriptor)parser.Blocks[j];

                    if (imageDescriptor.InterlaceFlag == 1)
                    {
                        throw new NotSupportedException("Interlacing is not supported!");
                    }

                    // var colorTable = imageDescriptor.LocalColorTableFlag == 1 ? GetUnityColors((ColorTable) parser.Blocks[j + 1]) : globalColorTable;
                    var colorIndexes = decoded[imageDescriptor];
                    var frame        = DecodeFrame(gcExtension, imageDescriptor, colorIndexes, filled, Width, Height, state);

                    frames.Add(frame);

                    //if (frames.Count == 1 && globalColorTable != null)
                    //{
                    //	if (gcExtension == null || gcExtension.TransparentColorFlag == 0 || gcExtension.TransparentColorIndex != parser.LogicalScreenDescriptor.BackgroundColorIndex)
                    //	{
                    //		backgroundColor = globalColorTable[parser.LogicalScreenDescriptor.BackgroundColorIndex];
                    //	}
                    //}

                    switch (frame.DisposalMethod)
                    {
                    case DisposalMethod.NoDisposalSpecified:
                    case DisposalMethod.DoNotDispose:
                        break;

                    case DisposalMethod.RestoreToBackgroundColor:
                        for (var i = 0; i < state.Length; i++)
                        {
                            state[i] = EmptyColor;
                        }
                        filled = true;
                        break;

                    case DisposalMethod.RestoreToPrevious:     // 'state' was already copied before decoding current frame
                        filled = false;
                        break;

                    default:
                        throw new NotSupportedException($"Unknown disposal method: {frame.DisposalMethod}!");
                    }
                }
            }

            // return new Gif(frames);
        }
Exemplo n.º 11
0
        private static GifFrame DecodeFrame(GraphicControlExtension extension, ImageDescriptor descriptor, TableBasedImageData data, bool filled, int width, int height, Color32[] state, Color32[] colorTable)
        {
            var colorIndexes = LzwDecoder.Decode(data.ImageData, data.LzwMinimumCodeSize);

            return(DecodeFrame(extension, descriptor, colorIndexes, filled, width, height, state, colorTable));
        }
Exemplo n.º 12
0
        /// <summary>
        /// Iterator can be used for large GIF-files in order to display progress bar.
        /// </summary>
        public IEnumerable <List <byte> > EncodeIterator(int scale = 1)
        {
            if (_free)
            {
                if (Frames[0].Texture.width > 256 || Frames[0].Texture.height > 256)
                {
                    throw new Exception("The free version has maximum supported size 256x256 px. Please consider buying the Full version of Power GIF.");
                }
                if (Frames.Count > 20)
                {
                    throw new Exception("The Free version is limited by 20 frames. Please consider buying the Full version of Power GIF.");
                }
            }

            const string header               = "GIF89a";
            var          width                = (ushort)(Frames[0].Texture.width * scale);
            var          height               = (ushort)(Frames[0].Texture.height * scale);
            var          globalColorTable     = new List <Color32>();
            var          applicationExtension = new ApplicationExtension();
            var          bytes                = new List <byte>();
            var          colorTables          = new List <Color32> [Frames.Count];
            var          distinctColors       = new Dictionary <int, List <Color32> >();
            var          manualResetEvent     = new ManualResetEvent(false);

            for (var i = 0; i < Frames.Count; i++)
            {
                var frame = Frames[i];

                ThreadPool.QueueUserWorkItem(context =>
                {
                    var distinct = frame.Texture.GetPixels32().Distinct().ToList();

                    lock (distinctColors)
                    {
                        distinctColors.Add((int)context, distinct);

                        if (distinctColors.Count == Frames.Count)
                        {
                            manualResetEvent.Set();
                        }
                    }
                }, i);
            }

            manualResetEvent.WaitOne();

            for (var i = 0; i < Frames.Count; i++)
            {
                var colors = distinctColors[i];
                var add    = colors.Where(j => !globalColorTable.Contains(j)).ToList();

                if (globalColorTable.Count + add.Count <= 256)
                {
                    globalColorTable.AddRange(add);
                    colorTables[i] = globalColorTable;
                }
                else if (add.Count <= 256)                 // Introducing local color table
                {
                    colorTables[i] = colors;
                }
                else
                {
                    throw new Exception($"Frame #{i} contains more than 256 colors!");
                }
            }

            ReplaceTransparentColor(ref globalColorTable);

            for (var i = 0; i < Frames.Count; i++)
            {
                var  frame = Frames[i];
                var  colorTable = colorTables[i];
                var  localColorTableFlag = (byte)(colorTable == globalColorTable ? 0 : 1);
                var  localColorTableSize = GetColorTableSize(colorTable);
                byte transparentColorFlag = 0, transparentColorIndex = 0;
                byte max;
                var  colorIndexes            = GetColorIndexes(frame.Texture, scale, colorTable, localColorTableFlag, ref transparentColorFlag, ref transparentColorIndex, out max);
                var  graphicControlExtension = new GraphicControlExtension(4, 0, (byte)frame.DisposalMethod, 0, transparentColorFlag, (ushort)(100 * frame.Delay), transparentColorIndex);
                var  imageDescriptor         = new ImageDescriptor(0, 0, width, height, localColorTableFlag, 0, 0, 0, localColorTableSize);
                var  minCodeSize             = LzwEncoder.GetMinCodeSize(max);
                var  lzw = LzwEncoder.Encode(colorIndexes, minCodeSize);
                var  tableBasedImageData = new TableBasedImageData(minCodeSize, lzw);

                bytes.Clear();
                bytes.AddRange(graphicControlExtension.GetBytes());
                bytes.AddRange(imageDescriptor.GetBytes());

                if (localColorTableFlag == 1)
                {
                    bytes.AddRange(ColorTableToBytes(colorTable, localColorTableSize));
                }

                bytes.AddRange(tableBasedImageData.GetBytes());

                yield return(bytes);
            }

            yield return(new List <byte> {
                0x3B
            });                                               // GIF Trailer.

            // Then output GIF header as last iterator element! This way we can build global color table "on fly" instead of expensive building operation.

            var globalColorTableSize    = GetColorTableSize(globalColorTable);
            var logicalScreenDescriptor = new LogicalScreenDescriptor(width, height, 1, 7, 0, globalColorTableSize, 0, 0);

            bytes.Clear();
            bytes.AddRange(Encoding.UTF8.GetBytes(header));
            bytes.AddRange(logicalScreenDescriptor.GetBytes());
            bytes.AddRange(ColorTableToBytes(globalColorTable, globalColorTableSize));
            bytes.AddRange(applicationExtension.GetBytes());

            yield return(bytes);
        }
Exemplo n.º 13
0
        /// <summary>
        /// Encode GIF in multiple threads.
        /// </summary>
        public void EncodeParallel(Action <EncodeProgress> onProgress, int scale = 1)        // TODO: Refact.
        {
            if (_free)
            {
                throw new Exception("The Free version doesn't support this feature. Please consider buying the Full version of Power GIF.");
            }

            const string header               = "GIF89a";
            var          width                = (ushort)(Frames[0].Texture.width * scale);
            var          height               = (ushort)(Frames[0].Texture.height * scale);
            var          globalColorTable     = new List <Color32>();
            var          applicationExtension = new ApplicationExtension();
            var          encoded              = new Dictionary <int, List <byte> >();
            var          encodeProgress       = new EncodeProgress {
                FrameCount = Frames.Count
            };
            var colorTables      = new List <Color32> [Frames.Count];
            var distinctColors   = new Dictionary <int, List <Color32> >();
            var manualResetEvent = new ManualResetEvent(false);

            for (var i = 0; i < Frames.Count; i++)
            {
                var frame = Frames[i];

                ThreadPool.QueueUserWorkItem(context =>
                {
                    var distinct = frame.Texture.GetPixels32().Distinct().ToList();

                    lock (distinctColors)
                    {
                        distinctColors.Add((int)context, distinct);

                        if (distinctColors.Count == Frames.Count)
                        {
                            manualResetEvent.Set();
                        }
                    }
                }, i);
            }

            manualResetEvent.WaitOne();

            for (var i = 0; i < Frames.Count; i++)
            {
                var colors = distinctColors[i];
                var add    = colors.Where(j => !globalColorTable.Contains(j)).ToList();

                if (globalColorTable.Count + add.Count <= 256)
                {
                    globalColorTable.AddRange(add);
                    colorTables[i] = globalColorTable;
                }
                else if (colors.Count <= 256)                 // Introduce local color table.
                {
                    colorTables[i] = colors;
                }
                else
                {
                    onProgress(new EncodeProgress {
                        Completed = true, Exception = new Exception($"Frame #{i} contains more than 256 colors!")
                    });
                    return;
                }
            }

            ReplaceTransparentColor(ref globalColorTable);

            for (var i = 0; i < Frames.Count; i++)             // Don't use Parallel.For to leave .NET compatibility.
            {
                ThreadPool.QueueUserWorkItem(context =>
                {
                    var index                 = (int)context;
                    var colorTable            = colorTables[index];
                    var localColorTableFlag   = (byte)(colorTable == globalColorTable ? 0 : 1);
                    var localColorTableSize   = GetColorTableSize(colorTable);
                    byte transparentColorFlag = 0, transparentColorIndex = 0;
                    byte max;
                    var colorIndexes            = GetColorIndexes(Frames[index].Texture, scale, colorTable, localColorTableFlag, ref transparentColorFlag, ref transparentColorIndex, out max);
                    var graphicControlExtension = new GraphicControlExtension(4, 0, (byte)Frames[index].DisposalMethod, 0, transparentColorFlag, (ushort)(100 * Frames[index].Delay), transparentColorIndex);
                    var imageDescriptor         = new ImageDescriptor(0, 0, width, height, localColorTableFlag, 0, 0, 0, localColorTableSize);
                    var minCodeSize             = LzwEncoder.GetMinCodeSize(max);
                    var lzw = LzwEncoder.Encode(colorIndexes, minCodeSize);
                    var tableBasedImageData = new TableBasedImageData(minCodeSize, lzw);
                    var bytes = new List <byte>();

                    bytes.AddRange(graphicControlExtension.GetBytes());
                    bytes.AddRange(imageDescriptor.GetBytes());

                    if (localColorTableFlag == 1)
                    {
                        bytes.AddRange(ColorTableToBytes(colorTable, localColorTableSize));
                    }

                    bytes.AddRange(tableBasedImageData.GetBytes());

                    lock (encoded)
                    {
                        encoded.Add(index, bytes);
                        encodeProgress.Progress++;

                        if (encoded.Count == Frames.Count)
                        {
                            var globalColorTableSize    = GetColorTableSize(globalColorTable);
                            var logicalScreenDescriptor = new LogicalScreenDescriptor(width, height, 1, 7, 0, globalColorTableSize, 0, 0);
                            var binary = new List <byte>();

                            binary.AddRange(Encoding.UTF8.GetBytes(header));
                            binary.AddRange(logicalScreenDescriptor.GetBytes());
                            binary.AddRange(ColorTableToBytes(globalColorTable, globalColorTableSize));
                            binary.AddRange(applicationExtension.GetBytes());
                            binary.AddRange(encoded.OrderBy(j => j.Key).SelectMany(j => j.Value));
                            binary.Add(0x3B);                         // GIF Trailer.

                            encodeProgress.Bytes     = binary.ToArray();
                            encodeProgress.Completed = true;
                        }

                        onProgress(encodeProgress);
                    }
                }, i);
            }
        }
Exemplo n.º 14
0
        /// <summary>
        /// Iterator can be used for large GIF-files in order to display progress bar.
        /// </summary>
        public static IEnumerable <GifFrame> DecodeIterator(byte[] bytes)
        {
            var parser           = new GifParser(bytes);
            var blocks           = parser.Blocks;
            var width            = parser.LogicalScreenDescriptor.LogicalScreenWidth;
            var height           = parser.LogicalScreenDescriptor.LogicalScreenHeight;
            var globalColorTable = parser.LogicalScreenDescriptor.GlobalColorTableFlag == 1 ? GetUnityColors(parser.GlobalColorTable) : null;
            //var backgroundColor = globalColorTable?[parser.LogicalScreenDescriptor.BackgroundColorIndex] ?? EmptyColor;
            GraphicControlExtension graphicControlExtension = null;
            var state  = new Color32[width * height];
            var filled = false;

            for (var j = 0; j < parser.Blocks.Count; j++)
            {
                if (blocks[j] is GraphicControlExtension)
                {
                    graphicControlExtension = (GraphicControlExtension)blocks[j];
                }
                else if (blocks[j] is ImageDescriptor)
                {
                    var imageDescriptor = (ImageDescriptor)blocks[j];

                    if (imageDescriptor.InterlaceFlag == 1)
                    {
                        throw new NotSupportedException("Interlacing is not supported!");
                    }

                    var colorTable = imageDescriptor.LocalColorTableFlag == 1 ? GetUnityColors((ColorTable)blocks[j + 1]) : globalColorTable;
                    var data       = (TableBasedImageData)blocks[j + 1 + imageDescriptor.LocalColorTableFlag];
                    var frame      = DecodeFrame(graphicControlExtension, imageDescriptor, data, filled, width, height, state, colorTable);

                    if (_free)
                    {
                        if (frame.Texture.width > 256 || frame.Texture.height > 256)
                        {
                            throw new Exception("The Free version has maximum supported size 256x256 px. Please consider buying the Full version of Power GIF.");
                        }
                        //if (++frames > 20) throw new Exception("The Free version is limited by 20 frames. Please consider buying the Full version of Power GIF.");
                    }

                    yield return(frame);

                    switch (frame.DisposalMethod)
                    {
                    case DisposalMethod.NoDisposalSpecified:
                    case DisposalMethod.DoNotDispose:
                        break;

                    case DisposalMethod.RestoreToBackgroundColor:
                        for (var i = 0; i < state.Length; i++)
                        {
                            state[i] = EmptyColor;
                        }
                        filled = true;
                        break;

                    case DisposalMethod.RestoreToPrevious:                             // 'state' was already copied before decoding current frame
                        filled = false;
                        break;

                    default:
                        throw new NotSupportedException($"Unknown disposal method: {frame.DisposalMethod}!");
                    }
                }
            }
        }