private void ConstructorStreamTest(bool xmlDebugging) { MemoryStream s = CreateStream(); // Extra stuff not included in the frame stream, to pass to the // constructor. ColourTable colourTable = WikipediaExample.GlobalColourTable; GraphicControlExtension ext = WikipediaExample.GraphicControlExtension; LogicalScreenDescriptor lsd = WikipediaExample.LogicalScreenDescriptor; _frame = new GifFrame(s, lsd, colourTable, ext, null, null, xmlDebugging); Assert.AreEqual(ErrorState.Ok, _frame.ConsolidatedState); WikipediaExample.CheckImage(_frame.TheImage); WikipediaExample.CheckImageDescriptor(_frame.ImageDescriptor); WikipediaExample.CheckGraphicControlExtension(_frame.GraphicControlExtension); WikipediaExample.CheckImageData(_frame.IndexedPixels); Assert.AreEqual(0, _frame.BackgroundColour.R); Assert.AreEqual(0, _frame.BackgroundColour.G); Assert.AreEqual(0, _frame.BackgroundColour.B); if (xmlDebugging) { Assert.AreEqual(ExpectedDebugXml, _frame.DebugXml); } }
public void ConstructorTestBackgroundColourIndexTooLarge() { ReportStart(); int width = 15; int height = 20; Size screenSize = new Size(width, height); bool hasGlobalColourTable = true; int colourResolution = 6; bool colourTableIsSorted = false; int globalColourTableSizeBits = 3; int backgroundColourIndex = byte.MaxValue + 1; int pixelAspectRatio = 2; try { _lsd = new LogicalScreenDescriptor(screenSize, hasGlobalColourTable, colourResolution, colourTableIsSorted, globalColourTableSizeBits, backgroundColourIndex, pixelAspectRatio); } catch (ArgumentException ex) { string message = "Background colour index cannot be more than " + byte.MaxValue + ". " + "Supplied value: " + backgroundColourIndex; StringAssert.Contains(message, ex.Message); Assert.AreEqual("backgroundColourIndex", ex.ParamName); ReportEnd(); throw; } }
/// <summary> /// Decodes the supplied GIF stream. /// </summary> public void Decode() { _frameDelays = new List <int>(); _loadedFrames = new Queue <GifFrame>(); _frames = new Collection <GifFrame>(); _applicationExtensions = new Collection <ApplicationExtension>(); _gct = null; _header = new GifHeader(_stream); if (_header.ErrorState != ErrorState.Ok) { return; } _lsd = new LogicalScreenDescriptor(_stream); if (TestState(ErrorState.EndOfInputStream)) { return; } if (_lsd.HasGlobalColourTable) { _gct = new ColourTable(_stream, _lsd.GlobalColourTableSize); } if (ConsolidatedState == ErrorState.Ok) { ReadContents(_stream); } ApplyMemoryFields(); }
/// <summary> /// Checks whether the supplied logical screen descriptor matches that /// returned by this class. /// </summary> /// <param name="lsd"></param> internal static void CheckLogicalScreenDescriptor(LogicalScreenDescriptor lsd) { Assert.AreEqual(BackgroundColourIndex, lsd.BackgroundColourIndex, "BackgroundColourIndex"); Assert.AreEqual(ColourResolution, lsd.ColourResolution, "ColourResolution"); Assert.AreEqual(GlobalColourTableSize, lsd.GlobalColourTableSize, "GlobalColourTableSize"); Assert.AreEqual(GlobalColourTableSizeBits, lsd.GlobalColourTableSizeBits, "GlobalColourTableSizeBits"); Assert.AreEqual(HasGlobalColourTable, lsd.HasGlobalColourTable, "HasGlobalColourTable"); Assert.AreEqual(LogicalScreenSize, lsd.LogicalScreenSize, "LogicalScreenSize"); Assert.AreEqual(PixelAspectRatio, lsd.PixelAspectRatio, "PixelAspectRatio"); Assert.AreEqual(GlobalColourTableIsSorted, lsd.GlobalColourTableIsSorted, "GlobalColourTableIsSorted"); }
private void ConstructorStreamTest(bool xmlDebugging) { int width = 15; int height = 20; Size screenSize = new Size(width, height); bool hasGlobalColourTable = true; int colourResolution = 6; bool colourTableIsSorted = false; int globalColourTableSizeBits = 3; int backgroundColourIndex = 12; int pixelAspectRatio = 2; MemoryStream s = new MemoryStream(); // Write screen width and height, least significant bit first s.WriteByte((byte)(width & 0xff)); s.WriteByte((byte)((width & 0xff00) >> 8)); s.WriteByte((byte)(height & 0xff)); s.WriteByte((byte)((height & 0xff00) >> 8)); // Packed fields: // bit 1 = global colour table flag // bits 2-4 = colour resolution // bit 5 = sort flag // bits 6-8 = global colour table size byte packed = (byte) ( (hasGlobalColourTable ? 1 : 0) << 7 | (colourResolution & 7) << 4 | (colourTableIsSorted ? 1 : 0) << 3 | (globalColourTableSizeBits & 7) ); s.WriteByte(packed); s.WriteByte((byte)backgroundColourIndex); s.WriteByte((byte)pixelAspectRatio); s.Seek(0, SeekOrigin.Begin); _lsd = new LogicalScreenDescriptor(s, xmlDebugging); Assert.AreEqual(ErrorState.Ok, _lsd.ConsolidatedState); Assert.AreEqual(screenSize, _lsd.LogicalScreenSize); Assert.AreEqual(hasGlobalColourTable, _lsd.HasGlobalColourTable); Assert.AreEqual(colourResolution, _lsd.ColourResolution); Assert.AreEqual(colourTableIsSorted, _lsd.GlobalColourTableIsSorted); Assert.AreEqual(globalColourTableSizeBits, _lsd.GlobalColourTableSizeBits); Assert.AreEqual(Math.Pow(2, globalColourTableSizeBits + 1), _lsd.GlobalColourTableSize); Assert.AreEqual(backgroundColourIndex, _lsd.BackgroundColourIndex); Assert.AreEqual(pixelAspectRatio, _lsd.PixelAspectRatio); Assert.AreEqual(ErrorState.Ok, _lsd.ConsolidatedState); if (xmlDebugging) { Assert.AreEqual(ExpectedDebugXml, _lsd.DebugXml); } }
public GifFrame(Stream inputStream, LogicalScreenDescriptor lsd, ColourTable gct, GraphicControlExtension gce, GifFrame previousFrame, GifFrame previousFrameBut1) : base(inputStream, lsd, gct, gce, previousFrame, previousFrameBut1) { }
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++; }
public GifImageSource(Stream stream) { try { _gifStream = new MemoryStream(); stream.CopyTo(_gifStream); _gifStream.Seek(0, SeekOrigin.Begin); using (var reader = new BinaryReader(_gifStream)) { var gifSign = new string(reader.ReadChars(6)); var gif89a = string.Equals(gifSign, "gif89a", StringComparison.OrdinalIgnoreCase); var gif87a = string.Equals(gifSign, "gif87a", StringComparison.OrdinalIgnoreCase); if ((gif87a || gif89a) == false) { return; } _sreenDescriptor = LogicalScreenDescriptor.Read(reader); _globalPalette = _sreenDescriptor.HasGlobalPalette ? GifPalette.Read(reader, _sreenDescriptor.GlobalPaletteColorCount) : null; while (true) { var ch = reader.ReadChar(); if (ch == ';' && reader.BaseStream.Position == reader.BaseStream.Length) { break; } switch (ch) { case '!': SkipExtension(reader); break; case ',': _frames.Add(ReadImage(reader)); break; } } } _isValid = true; } catch (Exception) { _isValid = false; } }
/// <summary> /// Reads GIF image from stream /// </summary> /// <param name="inputStream"> /// Stream containing GIF file. /// </param> /// <exception cref="ArgumentNullException"> /// The supplied stream is null. /// </exception> private void ReadStream(Stream inputStream) { _frames = new Collection <GifFrame>(); _applicationExtensions = new Collection <ApplicationExtension>(); _gct = null; _readStreamCounterText = "Reading stream byte"; AddCounter(_readStreamCounterText, (int)inputStream.Length); _header = new GifHeader(inputStream, XmlDebugging); MyProgressCounters[_readStreamCounterText].Value = (int)inputStream.Position; WriteDebugXmlNode(_header.DebugXmlReader); if (_header.ErrorState != ErrorState.Ok) { WriteDebugXmlFinish(); return; } _lsd = new LogicalScreenDescriptor(inputStream, XmlDebugging); MyProgressCounters[_readStreamCounterText].Value = (int)inputStream.Position; WriteDebugXmlNode(_lsd.DebugXmlReader); if (TestState(ErrorState.EndOfInputStream)) { WriteDebugXmlFinish(); return; } if (_lsd.HasGlobalColourTable) { _gct = new ColourTable(inputStream, _lsd.GlobalColourTableSize, XmlDebugging); MyProgressCounters[_readStreamCounterText].Value = (int)inputStream.Position; WriteDebugXmlNode(_gct.DebugXmlReader); } if (ConsolidatedState == ErrorState.Ok) { ReadContents(inputStream); MyProgressCounters[_readStreamCounterText].Value = (int)inputStream.Position; } inputStream.Close(); WriteDebugXmlFinish(); RemoveCounter(_readStreamCounterText); }
CheckLogicalScreenDescriptor(Stream s, bool shouldHaveGlobalColourTable) { // check logical screen descriptor LogicalScreenDescriptor lsd = new LogicalScreenDescriptor(s); Assert.AreEqual(ErrorState.Ok, lsd.ConsolidatedState, "Logical screen descriptor consolidated state"); Assert.AreEqual(new Size(2, 2), lsd.LogicalScreenSize, "Logical screen size"); Assert.AreEqual(shouldHaveGlobalColourTable, lsd.HasGlobalColourTable, "Should have global colour table"); Assert.AreEqual(false, lsd.GlobalColourTableIsSorted, "Global colour table is sorted"); return(lsd); }
public void ConstructorStreamNullLogicalScreenDescriptorTest() { MemoryStream s = CreateStream(); // Extra stuff not included in the frame stream, to pass to the // constructor ColourTable colourTable = WikipediaExample.GlobalColourTable; GraphicControlExtension ext = WikipediaExample.GraphicControlExtension; LogicalScreenDescriptor lsd = null; try { _frame = new GifFrame(s, lsd, colourTable, ext, null, null); } catch (ArgumentNullException ex) { Assert.AreEqual("lsd", ex.ParamName); throw; } }
private void WriteLogicalScreenDescriptor(LogicalScreenDescriptor screen) { var colorTableSizeIndex = ColorTable.TableSizes.IndexOf(screen.SizeOfGlobalColorTable); var data = new byte[7]; data[0] = (byte)(screen.Width & 0xFF); data[1] = (byte)((screen.Width & 0xFF00) >> 8); data[2] = (byte)(screen.Height & 0xFF); data[3] = (byte)((screen.Height & 0xFF00) >> 8); data[4] = (byte)((screen.GlobalColorTableFlag ? 1 : 0) << 7 | (screen.ColorResolution - 1) << 4 | (screen.SortFlag ? 1 : 0) << 3 | colorTableSizeIndex); data[5] = (byte)screen.BackgroundColorIndex; data[6] = screen.PixelAspectRatio == null ? byte.MinValue : (byte)Math.Min(Math.Max((int)Math.Round(screen.PixelAspectRatio.Value * 64 - 15), byte.MinValue + 1), byte.MaxValue); this.output.Write(data, 0, data.Length); }
private void ConstructorStreamNoImageDataTest(bool xmlDebugging) { MemoryStream s = new MemoryStream(); // Image descriptor byte[] idBytes = WikipediaExample.ImageDescriptorBytes; s.Write(idBytes, 0, idBytes.Length); // miss out the image data // // Table-based image data // byte[] imageData = WikipediaExample.ImageDataBytes; // s.Write( imageData, 0, imageData.Length ); byte[] imageData = new byte[] { 0x08, // LZW minimum code size // 0x00, // block size = 11 // // 11 bytes of LZW encoded data follows // 0x00, 0x01, 0x04, 0x18, 0x28, 0x70, 0xA0, 0xC1, 0x83, 0x01, 0x01, 0x00 // block terminator }; s.Write(imageData, 0, imageData.Length); s.Seek(0, SeekOrigin.Begin); // Extra stuff not included in the frame stream, to pass to the // constructor ColourTable colourTable = WikipediaExample.GlobalColourTable; GraphicControlExtension ext = WikipediaExample.GraphicControlExtension; LogicalScreenDescriptor lsd = WikipediaExample.LogicalScreenDescriptor; _frame = new GifFrame(s, lsd, colourTable, ext, null, null, xmlDebugging); Assert.AreEqual(ErrorState.TooFewPixelsInImageData, _frame.ConsolidatedState); if (xmlDebugging) { Assert.AreEqual(ExpectedDebugXml, _frame.DebugXml); } }
/// <summary> /// Populates the Logical Screen Descriptor byte[] from the source image, and performs lexical parsing on the array. /// </summary> /// <param name="bytes"></param> public static LogicalScreenDescriptor ParseLogicalScreenDescriptor(byte[] bytes) { string packedField = Convert.ToString(bytes[4], 2).PadLeft(8, '0'); int globalColorTableSize = Convert.ToInt32(packedField.Substring(5, 3), 2); LogicalScreenDescriptor lsd = new LogicalScreenDescriptor() { Bytes = (byte[])bytes.Clone(), CanvasWidth = BitConverter.ToInt32(new byte[] { bytes[0], bytes[1], 0, 0 }, 0), CanvasHeight = BitConverter.ToInt32(new byte[] { bytes[2], bytes[3], 0, 0 }, 0), PackedField = packedField, HasGlobalColorTable = packedField.Substring(0, 1) == "1", ColorResolution = Convert.ToInt32(packedField.Substring(1, 3), 2), SortFlag = packedField.Substring(4, 1) == "1", GlobalColorTableSize = globalColorTableSize, GlobalColorTableLength = 3 * (int)(Math.Pow(2, globalColorTableSize + 1)), // 3 * (2 ^ (n + 1)), where n equals the GlobalColorTableSize BackgroundColorIndex = bytes[5], PixelAspectRatio = bytes[6] }; return(lsd); }
private void ConstructorStreamEndOfStreamTest(bool xmlDebugging) { Size screenSize = new Size(12, 4); bool hasGlobalColourTable = false; int colourResolution = 3; bool globalColourTableIsSorted = true; int globalColourTableSizeBits = 4; int backgroundColourIndex = 2; int pixelAspectRatio = 1; _lsd = new LogicalScreenDescriptor(screenSize, hasGlobalColourTable, colourResolution, globalColourTableIsSorted, globalColourTableSizeBits, backgroundColourIndex, pixelAspectRatio); MemoryStream s = new MemoryStream(); _lsd.WriteToStream(s); s.SetLength(s.Length - 1); // remove final byte from stream s.Seek(0, SeekOrigin.Begin); _lsd = new LogicalScreenDescriptor(s, xmlDebugging); Assert.AreEqual(ErrorState.EndOfInputStream, _lsd.ConsolidatedState); Assert.AreEqual(screenSize, _lsd.LogicalScreenSize); Assert.AreEqual(hasGlobalColourTable, _lsd.HasGlobalColourTable); Assert.AreEqual(colourResolution, _lsd.ColourResolution); Assert.AreEqual(globalColourTableIsSorted, _lsd.GlobalColourTableIsSorted); Assert.AreEqual(globalColourTableSizeBits, _lsd.GlobalColourTableSizeBits); Assert.AreEqual(backgroundColourIndex, _lsd.BackgroundColourIndex); Assert.AreEqual(-1, _lsd.PixelAspectRatio); if (xmlDebugging) { Assert.AreEqual(ExpectedDebugXml, _lsd.DebugXml); } }
public void WriteToStreamTest() { ReportStart(); Size screenSize = new Size(12, 4); bool hasGlobalColourTable = false; int colourResolution = 3; bool globalColourTableIsSorted = true; int globalColourTableSizeBits = 4; int backgroundColourIndex = 2; int pixelAspectRatio = 1; _lsd = new LogicalScreenDescriptor(screenSize, hasGlobalColourTable, colourResolution, globalColourTableIsSorted, globalColourTableSizeBits, backgroundColourIndex, pixelAspectRatio); MemoryStream s = new MemoryStream(); _lsd.WriteToStream(s); s.Seek(0, SeekOrigin.Begin); _lsd = new LogicalScreenDescriptor(s); Assert.AreEqual(ErrorState.Ok, _lsd.ConsolidatedState); Assert.AreEqual(screenSize, _lsd.LogicalScreenSize); Assert.AreEqual(hasGlobalColourTable, _lsd.HasGlobalColourTable); Assert.AreEqual(colourResolution, _lsd.ColourResolution); Assert.AreEqual(globalColourTableIsSorted, _lsd.GlobalColourTableIsSorted); Assert.AreEqual(globalColourTableSizeBits, _lsd.GlobalColourTableSizeBits); Assert.AreEqual(backgroundColourIndex, _lsd.BackgroundColourIndex); Assert.AreEqual(pixelAspectRatio, _lsd.PixelAspectRatio); ReportEnd(); }
/// <summary> /// Reads GIF image from stream /// </summary> /// <param name="inputStream"> /// Stream containing GIF file. /// </param> /// <exception cref="ArgumentNullException"> /// The supplied stream is null. /// </exception> private void ReadStream(Stream inputStream) { _frames = new Collection <GifFrame>(); _applicationExtensions = new Collection <ApplicationExtension>(); _gct = null; _header = new GifHeader(inputStream, XmlDebugging); WriteDebugXmlNode(_header.DebugXmlReader); if (_header.ErrorState != ErrorState.Ok) { WriteDebugXmlFinish(); return; } _lsd = new LogicalScreenDescriptor(inputStream, XmlDebugging); WriteDebugXmlNode(_lsd.DebugXmlReader); if (TestState(ErrorState.EndOfInputStream)) { WriteDebugXmlFinish(); return; } if (_lsd.HasGlobalColourTable) { _gct = new ColourTable(inputStream, _lsd.GlobalColourTableSize, XmlDebugging); WriteDebugXmlNode(_gct.DebugXmlReader); } if (ConsolidatedState == ErrorState.Ok) { ReadContents(inputStream); } inputStream.Close(); WriteDebugXmlFinish(); }
public void ConstructorTest() { ReportStart(); int width = 15; int height = 20; Size screenSize = new Size(width, height); bool hasGlobalColourTable = true; int colourResolution = 6; bool colourTableIsSorted = false; int globalColourTableSizeBits = 3; int backgroundColourIndex = 12; int pixelAspectRatio = 2; _lsd = new LogicalScreenDescriptor(screenSize, hasGlobalColourTable, colourResolution, colourTableIsSorted, globalColourTableSizeBits, backgroundColourIndex, pixelAspectRatio); Assert.AreEqual(screenSize, _lsd.LogicalScreenSize); Assert.AreEqual(hasGlobalColourTable, _lsd.HasGlobalColourTable); Assert.AreEqual(colourResolution, _lsd.ColourResolution); Assert.AreEqual(colourTableIsSorted, _lsd.GlobalColourTableIsSorted); Assert.AreEqual(globalColourTableSizeBits, _lsd.GlobalColourTableSizeBits); Assert.AreEqual(Math.Pow(2, globalColourTableSizeBits + 1), _lsd.GlobalColourTableSize); Assert.AreEqual(backgroundColourIndex, _lsd.BackgroundColourIndex); Assert.AreEqual(pixelAspectRatio, _lsd.PixelAspectRatio); Assert.AreEqual(ErrorState.Ok, _lsd.ConsolidatedState); ReportEnd(); }
/// <summary> /// 写逻辑屏幕标识符 /// </summary> /// <param name="lsd"></param> internal void WriteLSD(LogicalScreenDescriptor lsd) { WriteBytes(lsd.GetBuffer()); }
/// <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); } }
public void WikipediaExampleTest() { ReportStart(); _e = new AnimatedGifEncoder(); GifFrame frame = new GifFrame(WikipediaExample.ExpectedBitmap); frame.Delay = WikipediaExample.DelayTime; _e.AddFrame(frame); // TODO: some way of creating/testing a UseLocal version of WikipediaExample string fileName = "WikipediaExampleUseGlobal.gif"; _e.WriteToFile(fileName); Stream s = File.OpenRead(fileName); int code; // check GIF header GifHeader gh = new GifHeader(s); Assert.AreEqual(ErrorState.Ok, gh.ConsolidatedState); // check logical screen descriptor LogicalScreenDescriptor lsd = new LogicalScreenDescriptor(s); Assert.AreEqual(ErrorState.Ok, lsd.ConsolidatedState); WikipediaExample.CheckLogicalScreenDescriptor(lsd); // read global colour table ColourTable gct = new ColourTable(s, WikipediaExample.GlobalColourTableSize); Assert.AreEqual(ErrorState.Ok, gct.ConsolidatedState); // cannot compare global colour table as different encoders will // produce difference colour tables. // WikipediaExample.CheckGlobalColourTable( gct ); // check for extension introducer code = ExampleComponent.CallRead(s); Assert.AreEqual(GifComponent.CodeExtensionIntroducer, code); // check for app extension label code = ExampleComponent.CallRead(s); Assert.AreEqual(GifComponent.CodeApplicationExtensionLabel, code); // check netscape extension ApplicationExtension ae = new ApplicationExtension(s); Assert.AreEqual(ErrorState.Ok, ae.ConsolidatedState); NetscapeExtension ne = new NetscapeExtension(ae); Assert.AreEqual(ErrorState.Ok, ne.ConsolidatedState); Assert.AreEqual(0, ne.LoopCount); // check for extension introducer code = ExampleComponent.CallRead(s); Assert.AreEqual(GifComponent.CodeExtensionIntroducer, code); // check for gce label code = ExampleComponent.CallRead(s); Assert.AreEqual(GifComponent.CodeGraphicControlLabel, code); // check graphic control extension GraphicControlExtension gce = new GraphicControlExtension(s); Assert.AreEqual(ErrorState.Ok, gce.ConsolidatedState); WikipediaExample.CheckGraphicControlExtension(gce); // check for image separator code = ExampleComponent.CallRead(s); Assert.AreEqual(GifComponent.CodeImageSeparator, code); // check for image descriptor ImageDescriptor id = new ImageDescriptor(s); Assert.AreEqual(ErrorState.Ok, id.ConsolidatedState); WikipediaExample.CheckImageDescriptor(id); // read, decode and check image data // Cannot compare encoded LZW data directly as different encoders // will create different colour tables, so even if the bitmaps are // identical, the colour indices will be different int pixelCount = WikipediaExample.FrameSize.Width * WikipediaExample.FrameSize.Height; TableBasedImageData tbid = new TableBasedImageData(s, pixelCount); for (int y = 0; y < WikipediaExample.LogicalScreenSize.Height; y++) { for (int x = 0; x < WikipediaExample.LogicalScreenSize.Width; x++) { int i = (y * WikipediaExample.LogicalScreenSize.Width) + x; Assert.AreEqual(WikipediaExample.ExpectedBitmap.GetPixel(x, y), gct[tbid.Pixels[i]], "X: " + x + ", Y: " + y); } } // Check for block terminator after image data code = ExampleComponent.CallRead(s); Assert.AreEqual(0x00, code); // check for GIF trailer code = ExampleComponent.CallRead(s); Assert.AreEqual(GifComponent.CodeTrailer, code); // check we're at the end of the stream code = ExampleComponent.CallRead(s); Assert.AreEqual(-1, code); s.Close(); _d = new GifDecoder(fileName); _d.Decode(); Assert.AreEqual(ErrorState.Ok, _d.ConsolidatedState); BitmapAssert.AreEqual(WikipediaExample.ExpectedBitmap, (Bitmap)_d.Frames[0].TheImage, ""); ReportEnd(); }
/// <summary> /// 从文件数据流中读取图形控制扩展(Graphic Control Extension) /// </summary> /// <param name="stream"></param> /// <returns></returns> internal LogicalScreenDescriptor GetLCD(Stream stream) { LogicalScreenDescriptor lcd = new LogicalScreenDescriptor(); lcd.Width = ReadShort(); lcd.Height = ReadShort(); lcd.Packed = (byte)Read(); lcd.GlobalColorTableFlag = ((lcd.Packed & 0x80) >> 7) == 1; lcd.ColorResoluTion = (byte)((lcd.Packed & 0x60) >> 5); lcd.SortFlag = (byte)(lcd.Packed & 0x10) >> 4; lcd.GlobalColorTableSize = 2 << (lcd.Packed & 0x07); lcd.BgColorIndex = (byte)Read(); lcd.PixcelAspect = (byte)Read(); return lcd; }
/// <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); }
/// <summary> /// Tests the AnimatedGifEncoder and the encoded GIF file it produces /// using the supplied parameters as property values. /// </summary> private void TestAnimatedGifEncoder(ColourTableStrategy strategy, int colourQuality, Size logicalScreenSize) { _e = new AnimatedGifEncoder(); // Check default properties set by constructor. Assert.AreEqual(ColourTableStrategy.UseGlobal, _e.ColourTableStrategy, "Colour table strategy set by constructor"); Assert.AreEqual(10, _e.SamplingFactor, "Colour quantization quality set by constructor"); Assert.AreEqual(Size.Empty, _e.LogicalScreenSize, "Logical screen size set by constructor"); _e.ColourTableStrategy = strategy; _e.SamplingFactor = colourQuality; _e.LogicalScreenSize = logicalScreenSize; // Check property set/gets Assert.AreEqual(strategy, _e.ColourTableStrategy, "Colour table strategy property set/get"); Assert.AreEqual(colourQuality, _e.SamplingFactor, "Colour quantization quality property set/get"); Assert.AreEqual(logicalScreenSize, _e.LogicalScreenSize, "Logical screen size property get/set"); foreach (GifFrame thisFrame in _frames) { _e.AddFrame(thisFrame); } StackTrace t = new StackTrace(); StackFrame f = t.GetFrame(1); string fileName = "Checks." + this.GetType().Name + "." + f.GetMethod().Name + ".gif"; _e.WriteToFile(fileName); Stream s = File.OpenRead(fileName); // global info CheckGifHeader(s); bool shouldHaveGlobalColourTable = (strategy == ColourTableStrategy.UseGlobal); LogicalScreenDescriptor lsd = CheckLogicalScreenDescriptor(s, shouldHaveGlobalColourTable); // Only check the global colour table if there should be one ColourTable gct = null; if (shouldHaveGlobalColourTable) { gct = CheckColourTable(s, lsd.GlobalColourTableSize); } CheckExtensionIntroducer(s); CheckAppExtensionLabel(s); CheckNetscapeExtension(s, 0); CheckFrame(s, gct, Bitmap1()); CheckFrame(s, gct, Bitmap2()); // end of image data CheckGifTrailer(s); CheckEndOfStream(s); s.Close(); // Check the file using the decoder _d = new GifDecoder(fileName); _d.Decode(); Assert.AreEqual(ErrorState.Ok, _d.ConsolidatedState, "Decoder consolidated state"); Assert.AreEqual(2, _d.Frames.Count, "Decoder frame count"); Assert.AreEqual(shouldHaveGlobalColourTable, _d.LogicalScreenDescriptor.HasGlobalColourTable, "Should have global colour table"); Assert.AreEqual(logicalScreenSize, _d.LogicalScreenDescriptor.LogicalScreenSize, "Decoder logical screen size"); BitmapAssert.AreEqual(Bitmap1(), (Bitmap)_d.Frames[0].TheImage, "frame 0"); BitmapAssert.AreEqual(Bitmap2(), (Bitmap)_d.Frames[1].TheImage, "frame 1"); bool shouldHaveLocalColourTable = !shouldHaveGlobalColourTable; Assert.AreEqual(shouldHaveLocalColourTable, _d.Frames[0].ImageDescriptor.HasLocalColourTable, "Frame 0 has local colour table"); Assert.AreEqual(shouldHaveLocalColourTable, _d.Frames[1].ImageDescriptor.HasLocalColourTable, "Frame 0 has local colour table"); }
/// <summary> /// 合并多个gif动画,在空间坐标上 /// </summary> /// <param name="sourceGifs">原图像</param> /// <param name="outPath">合并后图像</param> public static void Merge(List <string> sourceGifs, string outPath) { List <List <GifFrame> > frames = new List <List <GifFrame> >(); foreach (string source in sourceGifs) { if (!File.Exists(source)) { throw new IOException(string.Format("文件{0}不存在!", source)); } using (Bitmap ora_Img = new Bitmap(source)) { if (ora_Img.RawFormat.Guid != ImageFormat.Gif.Guid) { throw new IOException(string.Format("文件{0}!", source)); } } GifImage gifImage = GifDecoder.Decode(source); ThinkDisposalMethod(gifImage); int index = 0; foreach (GifFrame f in gifImage.Frames) { if (frames.Count <= index) { List <GifFrame> list = new List <GifFrame>(); frames.Add(list); } List <GifFrame> frameList = frames[index]; frameList.Add(f); index++; } } List <GifFrame> frameCol = new List <GifFrame>(); int frameIndex = 0; foreach (List <GifFrame> fs in frames) { GifFrame frame = Merge(fs); frameCol.Add(frame); if (frame.Image.Width != frameCol[0].Image.Width || frame.Image.Height != frameCol[0].Image.Height) { frame.ImageDescriptor.XOffSet = frames[frameIndex][0].ImageDescriptor.XOffSet; frame.ImageDescriptor.YOffSet = frames[frameIndex][0].ImageDescriptor.YOffSet; frame.GraphicExtension.DisposalMethod = frames[frameIndex][0].GraphicExtension.DisposalMethod; } frame.GraphicExtension.Delay = frame.Delay = frames[frameIndex][0].Delay; frameIndex++; } GifImage gifImg = new GifImage(); gifImg.Header = "GIF89a"; LogicalScreenDescriptor lcd = new LogicalScreenDescriptor(); lcd.Width = (short)frameCol[0].Image.Width; lcd.Height = (short)frameCol[0].Image.Height; gifImg.LogicalScreenDescriptor = lcd; ApplicationEx ape = new ApplicationEx(); List <ApplicationEx> apps = new List <ApplicationEx>(); apps.Add(ape); gifImg.ApplictionExtensions = apps; gifImg.Frames = frameCol; GifEncoder.Encode(gifImg, outPath); }
private void DecodeRotatingGlobe(bool xmlDebugging) { string fileName = @"images\globe\spinning globe better 200px transparent background.gif"; _decoder = new GifDecoder(fileName, xmlDebugging); WriteMessage("Started decoding. XmlDebugging=" + xmlDebugging); _decoder.Decode(); WriteMessage("Finished decoding. XmlDebugging=" + xmlDebugging); if (xmlDebugging) { Assert.AreEqual(ExpectedDebugXml, _decoder.DebugXml); } _lsd = _decoder.LogicalScreenDescriptor; int expectedColourTableSize = 64; Assert.AreEqual(ErrorState.Ok, _decoder.ConsolidatedState); Assert.AreEqual("GIF", _decoder.Header.Signature); Assert.AreEqual("89a", _decoder.Header.Version); Assert.IsNotNull(_decoder.GlobalColourTable); Assert.AreEqual(true, _lsd.HasGlobalColourTable); Assert.AreEqual(expectedColourTableSize, _lsd.GlobalColourTableSize); Assert.AreEqual(expectedColourTableSize, _decoder.GlobalColourTable.Length); Assert.AreEqual(2, _lsd.ColourResolution); Assert.AreEqual(Color.FromArgb(255, 255, 255, 255), _decoder.BackgroundColour); Assert.AreEqual(63, _lsd.BackgroundColourIndex); Assert.AreEqual(false, _lsd.GlobalColourTableIsSorted); Assert.AreEqual(200, _lsd.LogicalScreenSize.Width); Assert.AreEqual(191, _lsd.LogicalScreenSize.Height); Assert.AreEqual(0, _lsd.PixelAspectRatio); Assert.AreEqual(0, _decoder.NetscapeExtension.LoopCount); Assert.AreEqual(ErrorState.Ok, _decoder.ErrorState); Assert.AreEqual(20, _decoder.Frames.Count); int frameNumber = 0; string frameId; foreach (GifFrame thisFrame in _decoder.Frames) { frameId = "Frame " + frameNumber; Assert.IsNull(thisFrame.LocalColourTable, frameId); Assert.AreEqual(10, thisFrame.Delay, frameId); #region image descriptor tests ImageDescriptor descriptor = thisFrame.ImageDescriptor; Assert.AreEqual(false, descriptor.HasLocalColourTable, frameId); Assert.AreEqual(2, descriptor.LocalColourTableSize, frameId); Assert.AreEqual(200, descriptor.Size.Width, frameId); Assert.AreEqual(191, descriptor.Size.Height, frameId); Assert.AreEqual(0, descriptor.Position.X, frameId); Assert.AreEqual(0, descriptor.Position.Y, frameId); Assert.AreEqual(false, descriptor.IsInterlaced, frameId); Assert.AreEqual(false, descriptor.IsSorted, frameId); #endregion #region graphic control extension tests GraphicControlExtension gce = thisFrame.GraphicControlExtension; Assert.AreEqual(4, gce.BlockSize, frameId); Assert.AreEqual(10, gce.DelayTime, frameId); if (frameNumber == 19) { Assert.AreEqual(DisposalMethod.DoNotDispose, gce.DisposalMethod, frameId); } else { Assert.AreEqual(DisposalMethod.RestoreToBackgroundColour, gce.DisposalMethod, frameId); } Assert.AreEqual(63, gce.TransparentColourIndex, frameId); Assert.IsTrue(gce.HasTransparentColour, frameId); Assert.IsFalse(gce.ExpectsUserInput, frameId); #endregion frameNumber++; } CompareFrames(fileName); }
/// <summary> /// Writes a Logical Screen Descriptor to the supplied stream. /// Also writes a global colour table if required. /// </summary> /// <param name="outputStream"> /// The stream to write to. /// </param> private void WriteLogicalScreenDescriptor(Stream outputStream) { bool hasGlobalColourTable = _strategy == ColourTableStrategy.UseGlobal; int colourResolution = 7; // TODO: parameterise colourResolution? bool globalColourTableIsSorted = false; // Sorting of colour tables is not currently supported int backgroundColorIndex = 0; // TODO: parameterise backgroundColourIndex? int pixelAspectRatio = 0; // TODO: parameterise pixelAspectRatio? if (_strategy == ColourTableStrategy.UseGlobal) { if (_quantizerType == QuantizerType.UseSuppliedPalette) { // use supplied palette _globalColourTable = new ColourTable(); //AddCounter( buildColourTableCounterText, // _palette.Count ); int paletteIndex = 0; foreach (Color c in _palette) { _globalColourTable.Add(c); //MyProgressCounters[buildColourTableCounterText].Value //= paletteIndex; paletteIndex++; } _globalColourTable.Pad(); //RemoveCounter( buildColourTableCounterText ); } else { // Analyse the pixels in all the images to build the // global colour table. Collection <Image> images = new Collection <Image>(); foreach (GifFrame thisFrame in _frames) { Image thisImage = thisFrame.TheImage; images.Add(thisImage); } _pixelAnalysis = new PixelAnalysis(images); _pixelAnalysis.ColourQuality = _quality; _pixelAnalysis.Analyse(); _globalColourTable = _pixelAnalysis.ColourTable; } LogicalScreenDescriptor lsd = new LogicalScreenDescriptor(_logicalScreenSize, hasGlobalColourTable, colourResolution, globalColourTableIsSorted, _globalColourTable.SizeBits, backgroundColorIndex, pixelAspectRatio); lsd.WriteToStream(outputStream); _globalColourTable.WriteToStream(outputStream); } else { LogicalScreenDescriptor lsd = new LogicalScreenDescriptor(_logicalScreenSize, hasGlobalColourTable, colourResolution, globalColourTableIsSorted, 7, backgroundColorIndex, pixelAspectRatio); lsd.WriteToStream(outputStream); } }
private void DecodeSmiley(bool xmlDebugging) { string fileName = @"images\smiley\smiley.gif"; _decoder = new GifDecoder(fileName, xmlDebugging); _decoder.Decode(); _lsd = _decoder.LogicalScreenDescriptor; int expectedColourTableSize = 128; Assert.AreEqual("GIF", _decoder.Header.Signature); Assert.AreEqual("89a", _decoder.Header.Version); Assert.IsNotNull(_decoder.GlobalColourTable); Assert.AreEqual(true, _lsd.HasGlobalColourTable); Assert.AreEqual(expectedColourTableSize, _lsd.GlobalColourTableSize); Assert.AreEqual(expectedColourTableSize, _decoder.GlobalColourTable.Length); Assert.AreEqual(7, _lsd.ColourResolution); Assert.AreEqual(Color.FromArgb(255, 0, 0, 255), _decoder.BackgroundColour); Assert.AreEqual(0, _lsd.BackgroundColourIndex); Assert.AreEqual(false, _lsd.GlobalColourTableIsSorted); Assert.AreEqual(18, _lsd.LogicalScreenSize.Width); Assert.AreEqual(18, _lsd.LogicalScreenSize.Height); Assert.AreEqual(0, _lsd.PixelAspectRatio); Assert.AreEqual(0, _decoder.NetscapeExtension.LoopCount); Assert.AreEqual(ErrorState.Ok, _decoder.ConsolidatedState); Assert.AreEqual(4, _decoder.Frames.Count); int frameNumber = 0; string frameId; foreach (GifFrame thisFrame in _decoder.Frames) { frameId = "Frame " + frameNumber; Assert.IsNull(thisFrame.LocalColourTable, frameId); #region image descriptor properties ImageDescriptor descriptor = thisFrame.ImageDescriptor; Assert.AreEqual(false, descriptor.HasLocalColourTable, frameId); Assert.AreEqual(expectedColourTableSize, descriptor.LocalColourTableSize, frameId); Assert.AreEqual(false, descriptor.IsInterlaced, frameId); Assert.AreEqual(false, descriptor.IsSorted, frameId); #endregion #region graphic control extension properties GraphicControlExtension gce = thisFrame.GraphicControlExtension; Assert.AreEqual(4, gce.BlockSize, frameId); Assert.AreEqual(0, gce.TransparentColourIndex, frameId); Assert.IsTrue(gce.HasTransparentColour, frameId); Assert.IsFalse(gce.ExpectsUserInput, frameId); #endregion switch (frameNumber) { case 0: Assert.AreEqual(250, thisFrame.Delay, frameId); Assert.AreEqual(250, gce.DelayTime, frameId); Assert.AreEqual(DisposalMethod.DoNotDispose, gce.DisposalMethod, frameId); break; case 1: Assert.AreEqual(5, thisFrame.Delay, frameId); Assert.AreEqual(5, gce.DelayTime, frameId); Assert.AreEqual(DisposalMethod.RestoreToBackgroundColour, gce.DisposalMethod, frameId); break; case 2: Assert.AreEqual(10, thisFrame.Delay, frameId); Assert.AreEqual(10, gce.DelayTime, frameId); Assert.AreEqual(DisposalMethod.RestoreToBackgroundColour, gce.DisposalMethod, frameId); break; case 3: Assert.AreEqual(5, thisFrame.Delay, frameId); Assert.AreEqual(5, gce.DelayTime, frameId); Assert.AreEqual(DisposalMethod.DoNotDispose, gce.DisposalMethod, frameId); break; } frameNumber++; } CompareFrames(fileName); if (xmlDebugging) { Assert.AreEqual(ExpectedDebugXml, _decoder.DebugXml); } }