diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index 1fd5d3c2f2..b21696c64c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -8,38 +8,36 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal class Av1Decoder : IAv1TileDecoder { - private readonly Av1TileDecoder tileDecoder; + private readonly ObuReader obuReader; + private Av1TileDecoder? tileDecoder; + private Av1FrameBuffer? frameBuffer; - public Av1Decoder() - { - this.FrameInfo = new ObuFrameHeader(); - this.SequenceHeader = new ObuSequenceHeader(); - this.TileInfo = new ObuTileInfo(); - this.SeenFrameHeader = false; - this.tileDecoder = new Av1TileDecoder(this.SequenceHeader, this.FrameInfo, this.TileInfo); - } - - public bool SequenceHeaderDone { get; set; } - - public bool ShowExistingFrame { get; set; } + public Av1Decoder() => this.obuReader = new(); - public bool SeenFrameHeader { get; set; } + public ObuFrameHeader? FrameHeader { get; private set; } - public ObuFrameHeader FrameInfo { get; } + public ObuSequenceHeader? SequenceHeader { get; private set; } - public ObuSequenceHeader SequenceHeader { get; } - - public ObuTileInfo TileInfo { get; } + public ObuTileGroupHeader? TilesHeader { get; private set; } public void Decode(Span buffer) { Av1BitStreamReader reader = new(buffer); - ObuReader.Read(ref reader, buffer.Length, this, false); + this.obuReader.Read(ref reader, buffer.Length, this, false); + this.frameBuffer = this.tileDecoder?.FrameBuffer; + } + + public void StartDecodeTiles() + { + this.SequenceHeader = this.obuReader.SequenceHeader; + this.FrameHeader = this.obuReader.FrameHeader; + this.TilesHeader = this.obuReader.TileGroupHeader; + this.tileDecoder = new Av1TileDecoder(this.SequenceHeader!, this.FrameHeader!, this.TilesHeader!); } public void DecodeTile(Span tileData, int tileNum) - => this.tileDecoder.DecodeTile(tileData, tileNum); + => this.tileDecoder!.DecodeTile(tileData, tileNum); public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) - => this.tileDecoder.FinishDecodeTiles(doCdef, doLoopRestoration); + => this.tileDecoder!.FinishDecodeTiles(doCdef, doLoopRestoration); } diff --git a/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs index c7aa943f8d..daac2ef1d9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; - namespace SixLabors.ImageSharp.Formats.Heif.Av1; /// @@ -11,34 +9,9 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal interface IAv1TileDecoder { /// - /// Gets or sets a value indicating whether a sequence header has been read. - /// - bool SequenceHeaderDone { get; set; } - - /// - /// Gets or sets a value indicating whether to show the existing frame. - /// - bool ShowExistingFrame { get; set; } - - /// - /// Gets or sets a value indicating whether a FrameHeader has just been read. - /// - bool SeenFrameHeader { get; set; } - - /// - /// Gets Information about the frame. - /// - ObuFrameHeader FrameInfo { get; } - - /// - /// Gets Information about the sequence of frames. - /// - ObuSequenceHeader SequenceHeader { get; } - - /// - /// Gets information required to decode the tiles of a frame. + /// Start decoding all tiles of a frame. /// - ObuTileInfo TileInfo { get; } + void StartDecodeTiles(); /// /// Decode a single tile. diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs index f3f7e957dd..ffe384e688 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs @@ -15,7 +15,7 @@ internal class ObuFrameHeader public bool AllowHighPrecisionMotionVector { get; set; } - public ObuTileInfo TilesInfo { get; set; } = new ObuTileInfo(); + public ObuTileGroupHeader TilesInfo { get; set; } = new ObuTileGroupHeader(); public bool CodedLossless { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 1a90eaebfc..889fe51f0d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -7,11 +7,18 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuReader { + public ObuSequenceHeader? SequenceHeader { get; set; } + + public ObuFrameHeader? FrameHeader { get; set; } + + public ObuTileGroupHeader? TileGroupHeader { get; set; } + /// /// Decode all OBU's in a frame. /// - public static void Read(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder decoder, bool isAnnexB = false) + public void Read(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder decoder, bool isAnnexB = false) { + bool seenFrameHeader = false; bool frameDecodingFinished = false; while (!frameDecodingFinished) { @@ -40,34 +47,35 @@ internal class ObuReader switch (header.Type) { case ObuType.SequenceHeader: - ReadSequenceHeader(ref reader, decoder.SequenceHeader); - if (decoder.SequenceHeader.ColorConfig.BitDepth == 12) + this.SequenceHeader = new(); + ReadSequenceHeader(ref reader, this.SequenceHeader); + if (this.SequenceHeader.ColorConfig.BitDepth == 12) { // TODO: Initialize 12 bit predictors } - decoder.SequenceHeaderDone = true; break; case ObuType.FrameHeader: case ObuType.RedundantFrameHeader: case ObuType.Frame: if (header.Type != ObuType.Frame) { - decoder.ShowExistingFrame = false; + // Nothing to do here. } else if (header.Type != ObuType.FrameHeader) { - Guard.IsFalse(decoder.SeenFrameHeader, nameof(Av1Decoder.SeenFrameHeader), "Frame header expected"); + Guard.IsFalse(seenFrameHeader, nameof(seenFrameHeader), "Frame header expected"); } else { - Guard.IsTrue(decoder.SeenFrameHeader, nameof(Av1Decoder.SeenFrameHeader), "Already decoded a frame header"); + Guard.IsTrue(seenFrameHeader, nameof(seenFrameHeader), "Already decoded a frame header"); } - if (!decoder.SeenFrameHeader) + if (!seenFrameHeader) { - decoder.SeenFrameHeader = true; - ReadFrameHeader(ref reader, decoder, header, header.Type != ObuType.Frame); + seenFrameHeader = true; + this.FrameHeader = new(); + this.ReadFrameHeader(ref reader, header, header.Type != ObuType.Frame); } if (header.Type != ObuType.Frame) @@ -78,15 +86,16 @@ internal class ObuReader goto TILE_GROUP; case ObuType.TileGroup: TILE_GROUP: - if (!decoder.SeenFrameHeader) + if (!seenFrameHeader) { throw new InvalidImageContentException("Corrupt frame"); } - ReadTileGroup(ref reader, decoder, header, out frameDecodingFinished); + this.TileGroupHeader = new(); + this.ReadTileGroup(ref reader, decoder, header, out frameDecodingFinished); if (frameDecodingFinished) { - decoder.SeenFrameHeader = false; + seenFrameHeader = false; } break; @@ -465,9 +474,9 @@ internal class ObuReader ComputeImageSize(sequenceHeader, frameInfo); } - private static ObuTileInfo ReadTileInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { - ObuTileInfo tileInfo = new(); + ObuTileGroupHeader tileInfo = new(); int superBlockColumnCount; int superBlockRowCount; int superBlockShift; @@ -633,10 +642,10 @@ internal class ObuReader return tileInfo; } - private static void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, IAv1TileDecoder decoder, ObuHeader header, int planesCount) + private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, int planesCount) { - ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; - ObuFrameHeader frameInfo = decoder.FrameInfo; + ObuSequenceHeader sequenceHeader = this.SequenceHeader!; + ObuFrameHeader frameInfo = this.FrameHeader!; int idLength = 0; uint previousFrameId = 0; bool isIntraFrame = false; @@ -915,13 +924,11 @@ internal class ObuReader } } - private static void ReadFrameHeader(ref Av1BitStreamReader reader, IAv1TileDecoder decoder, ObuHeader header, bool trailingBit) + private void ReadFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, bool trailingBit) { - ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; - ObuFrameHeader frameInfo = decoder.FrameInfo; - int planeCount = sequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; + int planeCount = this.SequenceHeader!.ColorConfig.IsMonochrome ? 1 : 3; int startBitPosition = reader.BitPosition; - ReadUncompressedFrameHeader(ref reader, decoder, header, planeCount); + this.ReadUncompressedFrameHeader(ref reader, header, planeCount); if (trailingBit) { ReadTrailingBits(ref reader); @@ -934,11 +941,12 @@ internal class ObuReader header.PayloadSize -= headerBytes; } - private static void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileDecoder decoder, ObuHeader header, out bool isLastTileGroup) + private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileDecoder decoder, ObuHeader header, out bool isLastTileGroup) { - ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; - ObuFrameHeader frameInfo = decoder.FrameInfo; - ObuTileInfo tileInfo = decoder.TileInfo; + ObuSequenceHeader sequenceHeader = this.SequenceHeader!; + ObuFrameHeader frameInfo = this.FrameHeader!; + ObuTileGroupHeader tileInfo = this.TileGroupHeader!; + this.TileGroupHeader = tileInfo; int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; int startBitPosition = reader.BitPosition; bool tileStartAndEndPresentFlag = false; diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileGroupHeader.cs similarity index 96% rename from src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs rename to src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileGroupHeader.cs index b995713d8a..848fef55b6 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileGroupHeader.cs @@ -3,7 +3,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -internal class ObuTileInfo +internal class ObuTileGroupHeader { internal int MaxTileWidthSuperBlock { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index 00346743f8..e9847ffea0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -10,23 +10,23 @@ internal class ObuWriter /// /// Encode a single frame into OBU's. /// - public static void Write(Stream stream, IAv1TileDecoder decoder) + public static void Write(Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileGroupHeader tileInfo) { MemoryStream bufferStream = new(100); Av1BitStreamWriter writer = new(bufferStream); WriteObuHeaderAndSize(stream, ObuType.TemporalDelimiter, [], 0); - WriteSequenceHeader(ref writer, decoder.SequenceHeader); + WriteSequenceHeader(ref writer, sequenceHeader); writer.Flush(); WriteObuHeaderAndSize(stream, ObuType.SequenceHeader, bufferStream.GetBuffer(), (int)bufferStream.Position); bufferStream.Position = 0; - WriteFrameHeader(ref writer, decoder, true); + WriteFrameHeader(ref writer, sequenceHeader, frameInfo, true); writer.Flush(); WriteObuHeaderAndSize(stream, ObuType.FrameHeader, bufferStream.GetBuffer(), (int)bufferStream.Position); bufferStream.Position = 0; - WriteTileGroup(ref writer, decoder.TileInfo); + WriteTileGroup(ref writer, tileInfo); writer.Flush(); WriteObuHeaderAndSize(stream, ObuType.TileGroup, bufferStream.GetBuffer(), (int)bufferStream.Position); } @@ -218,7 +218,7 @@ internal class ObuWriter WriteSuperResolutionParameters(ref writer, sequenceHeader, frameInfo); } - private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileInfo tileInfo) + private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileGroupHeader tileInfo) { int superBlockColumnCount; int superBlockRowCount; @@ -444,10 +444,8 @@ internal class ObuWriter } } - private static int WriteFrameHeader(ref Av1BitStreamWriter writer, IAv1TileDecoder decoder, bool writeTrailingBits) + private static int WriteFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool writeTrailingBits) { - ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; - ObuFrameHeader frameInfo = decoder.FrameInfo; int planeCount = sequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; int startBitPosition = writer.BitPosition; WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameInfo, planeCount); @@ -463,7 +461,7 @@ internal class ObuWriter return headerBytes; } - private static int WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileInfo tileInfo) + private static int WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo) { int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; int startBitPosition = writer.BitPosition; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index da734fb133..d283906a0f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -29,16 +29,15 @@ internal class Av1TileDecoder : IAv1TileDecoder private int maxLumaHeight; private int deltaLoopFilterResolution = -1; private int deltaQuantizerResolution = -1; - private readonly Av1FrameBuffer frameBuffer; - public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileInfo tileInfo) + public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileGroupHeader tileInfo) { this.FrameInfo = frameInfo; this.SequenceHeader = sequenceHeader; this.TileInfo = tileInfo; // init_main_frame_ctxt - this.frameBuffer = new(this.SequenceHeader); + this.FrameBuffer = new(this.SequenceHeader); } public bool SequenceHeaderDone { get; set; } @@ -51,7 +50,9 @@ internal class Av1TileDecoder : IAv1TileDecoder public ObuSequenceHeader SequenceHeader { get; } - public ObuTileInfo TileInfo { get; } + public ObuTileGroupHeader TileInfo { get; } + + public Av1FrameBuffer FrameBuffer { get; } public void DecodeTile(Span tileData, int tileNum) { @@ -93,7 +94,7 @@ internal class Av1TileDecoder : IAv1TileDecoder this.ClearBlockDecodedFlags(row, column, superBlock4x4Size); Point superblockPosition = new Point(superBlockColumn, superBlockRow); - Av1SuperblockInfo superblockInfo = new(this.frameBuffer, superblockPosition); + Av1SuperblockInfo superblockInfo = new(this.FrameBuffer, superblockPosition); // Nothing to do for CDEF // this.ClearCdef(row, column); @@ -104,7 +105,7 @@ internal class Av1TileDecoder : IAv1TileDecoder } private void ClearLoopFilterDelta() - => this.frameBuffer.ClearDeltaLoopFilter(); + => this.FrameBuffer.ClearDeltaLoopFilter(); private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size) { @@ -152,6 +153,11 @@ internal class Av1TileDecoder : IAv1TileDecoder } } + public void StartDecodeTiles() + { + // TODO: Implement + } + public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) { // TODO: Implement @@ -402,7 +408,7 @@ internal class Av1TileDecoder : IAv1TileDecoder private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize transformSize, int x, int y) { - Av1PartitionInfo partitionInfo = new(new(1, Av1BlockSize.Invalid), new(this.frameBuffer, default), false, Av1PartitionType.None); + Av1PartitionInfo partitionInfo = new(new(1, Av1BlockSize.Invalid), new(this.FrameBuffer, default), false, Av1PartitionType.None); int startX = (baseX + 4) * x; int startY = (baseY + 4) * y; bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs index 9f0dbc2844..b3346e48a0 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs @@ -2,23 +2,14 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1; -using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; internal class Av1TileDecoderStub : IAv1TileDecoder { - public bool SequenceHeaderDone { get; set; } - - public bool ShowExistingFrame { get; set; } - - public bool SeenFrameHeader { get; set; } - - public ObuFrameHeader FrameInfo { get; } = new ObuFrameHeader(); - - public ObuSequenceHeader SequenceHeader { get; } = new ObuSequenceHeader(); - - public ObuTileInfo TileInfo { get; } = new ObuTileInfo(); + public void StartDecodeTiles() + { + } public void DecodeTile(Span tileData, int tileNum) { diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index 89b42535f4..c24eac5555 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -5,7 +5,6 @@ using System.Reflection; using System.Text; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; @@ -22,14 +21,17 @@ public class ObuFrameHeaderTests string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); byte[] content = File.ReadAllBytes(filePath); Span span = content.AsSpan(fileOffset, blockSize); - Av1Decoder decoder = new(); + Av1BitStreamReader reader = new(span); + IAv1TileDecoder decoder = new Av1TileDecoderStub(); + ObuReader obuReader = new(); // Act - decoder.Decode(span); + obuReader.Read(ref reader, blockSize, decoder); // Assert - Assert.True(decoder.SequenceHeaderDone); - Assert.False(decoder.SeenFrameHeader); + Assert.NotNull(obuReader.SequenceHeader); + Assert.NotNull(obuReader.FrameHeader); + Assert.NotNull(obuReader.TileGroupHeader); } /* [Theory] @@ -67,29 +69,31 @@ public class ObuFrameHeaderTests Span span = content.AsSpan(fileOffset, blockSize); IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); Av1BitStreamReader reader = new(span); + ObuReader obuReader1 = new(); // Act 1 - ObuReader.Read(ref reader, blockSize, tileDecoder); + obuReader1.Read(ref reader, blockSize, tileDecoder); // Assign 2 MemoryStream encoded = new(); // Act 2 - ObuWriter.Write(encoded, tileDecoder); + ObuWriter.Write(encoded, obuReader1.SequenceHeader, obuReader1.FrameHeader, obuReader1.TileGroupHeader); // Assign 2 Span encodedBuffer = encoded.ToArray(); IAv1TileDecoder tileDecoder2 = new Av1TileDecoderStub(); Av1BitStreamReader reader2 = new(span); + ObuReader obuReader2 = new(); // Act 2 - ObuReader.Read(ref reader2, encodedBuffer.Length, tileDecoder2); + obuReader2.Read(ref reader2, encodedBuffer.Length, tileDecoder2); // Assert - Assert.Equal(PrettyPrintProperties(tileDecoder.SequenceHeader.ColorConfig), PrettyPrintProperties(tileDecoder2.SequenceHeader.ColorConfig)); - Assert.Equal(PrettyPrintProperties(tileDecoder.SequenceHeader), PrettyPrintProperties(tileDecoder2.SequenceHeader)); - Assert.Equal(PrettyPrintProperties(tileDecoder.FrameInfo), PrettyPrintProperties(tileDecoder2.FrameInfo)); - Assert.Equal(PrettyPrintProperties(tileDecoder.TileInfo), PrettyPrintProperties(tileDecoder2.TileInfo)); + Assert.Equal(PrettyPrintProperties(obuReader1.SequenceHeader.ColorConfig), PrettyPrintProperties(obuReader2.SequenceHeader.ColorConfig)); + Assert.Equal(PrettyPrintProperties(obuReader1.SequenceHeader), PrettyPrintProperties(obuReader2.SequenceHeader)); + Assert.Equal(PrettyPrintProperties(obuReader1.FrameHeader), PrettyPrintProperties(obuReader2.FrameHeader)); + Assert.Equal(PrettyPrintProperties(obuReader1.TileGroupHeader), PrettyPrintProperties(obuReader2.TileGroupHeader)); } private static string PrettyPrintProperties(object obj)