public void TestUnalignedReadHigherOrderBitsFirst() { byte[] data = new byte[] { 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x9a, 0xab, 0xbc }; var bitReader = new BitReader(data); Assert.Equal((uint)0, bitReader.Peek(1)); Assert.Equal(0, bitReader.ConsumedBits); Assert.Equal(0, bitReader.ConsumedBytes); Assert.Equal((uint)0, bitReader.Read(1)); Assert.Equal(1, bitReader.ConsumedBits); Assert.Equal(1, bitReader.ConsumedBytes); Assert.Equal((uint)0, bitReader.Read(2)); Assert.Equal(3, bitReader.ConsumedBits); Assert.Equal(1, bitReader.ConsumedBytes); Assert.Equal((uint)0b100, bitReader.Peek(3)); Assert.Equal((uint)0b100, bitReader.Read(3)); Assert.Equal(6, bitReader.ConsumedBits); Assert.Equal(1, bitReader.ConsumedBytes); Assert.Equal((uint)0b10001, bitReader.Peek(5)); bitReader.AdvanceAlignByte(); Assert.Equal(8, bitReader.ConsumedBits); Assert.Equal(1, bitReader.ConsumedBytes); Assert.Equal((uint)0b001000, bitReader.Read(6)); Assert.Equal((uint)0b1100110, bitReader.Read(7)); Assert.Equal((uint)0b10001000, bitReader.Read(8)); Assert.Equal((uint)0b101, bitReader.Peek(3)); Assert.Equal(29, bitReader.ConsumedBits); Assert.Equal((uint)0b1010101011, bitReader.Peek(10)); Assert.Equal((uint)0b101010, bitReader.Read(6)); Assert.Equal((uint)0b10110011, bitReader.Peek(8)); Assert.Equal(35, bitReader.ConsumedBits); bitReader.AdvanceAlignByte(); Assert.Equal(40, bitReader.ConsumedBits); Assert.Equal(5, bitReader.ConsumedBytes); Assert.Equal((uint)0x6778899a, bitReader.Peek(32)); Assert.Equal((uint)0b01100, bitReader.Read(5)); Assert.Equal(0b111_01111000_10001001_10011010_10101, (uint)bitReader.Read(32)); bitReader.AdvanceAlignByte(); Assert.Equal(80, bitReader.ConsumedBits); Assert.Equal(10, bitReader.ConsumedBytes); Assert.Equal((uint)0xbc, bitReader.Peek(8)); Assert.Equal((uint)0xbc00, bitReader.Peek(16)); }
public void TestAlignedAdvanceByte() { byte[] data = new byte[] { 0xab, 0xcd, 0xef }; var bitReader = new BitReader(data); bitReader.Read(8); bitReader.AdvanceAlignByte(); Assert.Equal(1, bitReader.ConsumedBytes); Assert.Equal(8, bitReader.ConsumedBits); Assert.Equal((uint)0xcd, bitReader.Read(8)); }
/// <inheritdoc /> public void Decompress(TiffDecompressionContext context, ReadOnlyMemory <byte> input, Memory <byte> output) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.PhotometricInterpretation != TiffPhotometricInterpretation.WhiteIsZero && context.PhotometricInterpretation != TiffPhotometricInterpretation.BlackIsZero) { throw new NotSupportedException("T4 compression does not support this photometric interpretation."); } if (context.BitsPerSample.Count != 1 || context.BitsPerSample[0] != 1) { throw new NotSupportedException("Unsupported bits per sample."); } ReadOnlySpan <byte> inputSpan = input.Span; Span <byte> scanlinesBufferSpan = output.Span; bool whiteIsZero = context.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; int width = context.ImageSize.Width; int height = context.SkippedScanlines + context.RequestedScanlines; var bitReader = new BitReader(inputSpan, higherOrderBitsFirst: _higherOrderBitsFirst); // Skip the first EOL code if (bitReader.Peek(12) == 0b000000000001) // EOL code is 000000000001 (12bits). { bitReader.Advance(12); } else if (bitReader.Peek(16) == 1) // Or EOL code is zero filled. { bitReader.Advance(16); } // Process every scanline for (int i = 0; i < height; i++) { if (scanlinesBufferSpan.Length < width) { throw new InvalidDataException(); } Span <byte> scanline = scanlinesBufferSpan.Slice(0, width); scanlinesBufferSpan = scanlinesBufferSpan.Slice(width); // Process every code word in this scanline byte fillValue = whiteIsZero ? (byte)0 : (byte)255; CcittDecodingTable currentTable = CcittDecodingTable.WhiteInstance; CcittDecodingTable otherTable = CcittDecodingTable.BlackInstance; int unpacked = 0; CcittCodeValue tableEntry; while (true) { uint value = bitReader.Peek(16); if (!currentTable.TryLookup(value, out tableEntry)) { throw new InvalidDataException(); } if (tableEntry.IsEndOfLine) { // fill the rest of scanline scanline.Fill(fillValue); bitReader.Advance(tableEntry.BitsRequired); break; } // Check to see whether we are encountering a "filled" EOL ? if ((value & 0b1111111111110000) == 0) { // Align to 8 bits int filledBits = 8 - (bitReader.ConsumedBits + 12) % 8; if (bitReader.Peek(filledBits) != 0) { throw new InvalidDataException(); } // Confirm it is indeed an EOL code. value = bitReader.Read(filledBits + 12); if (value == 0b000000000001) { // fill the rest of scanline scanline.Fill(fillValue); break; } throw new InvalidDataException(); } // Process normal code. int runLength = tableEntry.RunLength; scanline.Slice(0, runLength).Fill(fillValue); scanline = scanline.Slice(runLength); unpacked += runLength; bitReader.Advance(tableEntry.BitsRequired); // Terminating code is met. Switch to the other color. if (tableEntry.IsTerminatingCode) { fillValue = (byte)~fillValue; CcittHelper.SwapTable(ref currentTable, ref otherTable); // This line is fully unpacked. Should exit and process next line. if (unpacked == width) { // If this is the last line of the image, we don't process any code after this. if (i == height - 1) { break; } // Is the next code word EOL ? value = bitReader.Peek(16); if ((value >> 4) == 0b000000000001) { // Skip the EOL code bitReader.Advance(12); break; } // Maybe the EOL is zero filled? First align to 8 bits int filledBits = 8 - (bitReader.ConsumedBits + 12) % 8; if (bitReader.Peek(filledBits) != 0) { bitReader.AdvanceAlignByte(); break; } // Confirm it is indeed an EOL code. value = bitReader.Peek(filledBits + 12); if (value == 0b000000000001) { bitReader.Advance(filledBits + 12); } bitReader.AdvanceAlignByte(); break; } } } } }
/// <inheritdoc /> public void Decompress(TiffDecompressionContext context, ReadOnlyMemory <byte> input, Memory <byte> output) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.PhotometricInterpretation != TiffPhotometricInterpretation.WhiteIsZero && context.PhotometricInterpretation != TiffPhotometricInterpretation.BlackIsZero) { throw new NotSupportedException("Modified Huffman compression does not support this photometric interpretation."); } if (context.BitsPerSample.Count != 1 || context.BitsPerSample[0] != 1) { throw new NotSupportedException("Unsupported bits per sample."); } ReadOnlySpan <byte> inputSpan = input.Span; Span <byte> scanlinesBufferSpan = output.Span; bool whiteIsZero = context.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; int width = context.ImageSize.Width; int height = context.SkippedScanlines + context.RequestedScanlines; var bitReader = new BitReader(inputSpan, higherOrderBitsFirst: _higherOrderBitsFirst); // Process every scanline for (int i = 0; i < height; i++) { if (scanlinesBufferSpan.Length < width) { throw new InvalidDataException(); } Span <byte> scanline = scanlinesBufferSpan.Slice(0, width); scanlinesBufferSpan = scanlinesBufferSpan.Slice(width); // Process every code word in this scanline byte fillValue = whiteIsZero ? (byte)0 : (byte)255; CcittDecodingTable currentTable = CcittDecodingTable.WhiteInstance; CcittDecodingTable otherTable = CcittDecodingTable.BlackInstance; int unpacked = 0; while (true) { if (!currentTable.TryLookup(bitReader.Peek(16), out CcittCodeValue tableEntry)) { throw new InvalidDataException(); } if (tableEntry.IsEndOfLine) { // EOL code is not used in modified huffman algorithm throw new InvalidDataException(); } // Process normal code. int runLength = tableEntry.RunLength; scanline.Slice(0, runLength).Fill(fillValue); scanline = scanline.Slice(runLength); unpacked += runLength; bitReader.Advance(tableEntry.BitsRequired); // Terminating code is met. Switch to the other color. if (tableEntry.IsTerminatingCode) { fillValue = (byte)~fillValue; CcittHelper.SwapTable(ref currentTable, ref otherTable); // This line is fully unpacked. Should exit and process next line. if (unpacked == width) { break; } } } bitReader.AdvanceAlignByte(); } }