Beispiel #1
0
        public void TestLargeAdvance()
        {
            byte[] data   = new byte[24];
            var    reader = new BitReader(data);

            reader.Advance(32);
            Assert.Equal(32, reader.ConsumedBits);
            reader.Advance(128);
            Assert.Equal(32 + 128, reader.ConsumedBits);
        }
Beispiel #2
0
        /// <inheritdoc />
        public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (next is null)
            {
                throw new ArgumentNullException(nameof(next));
            }

            int  bitCount = _bitCount;
            bool isHigherOrderBitsFirst = _fillOrder != TiffFillOrder.LowerOrderBitsFirst;

            int                 bytesPerScanline = (context.SourceImageSize.Width * bitCount + 7) / 8;
            Memory <byte>       source           = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline);
            ReadOnlySpan <byte> sourceSpan       = source.Span;

            using TiffPixelBufferWriter <TiffGray16> writer = context.GetWriter <TiffGray16>();

            int rows = context.ReadSize.Height;
            int cols = context.ReadSize.Width;

            // BitReader.Read reads bytes in big-endian way, we only need to reverse the endianness if the source is little-endian.
            bool reverseEndianness = context.IsLittleEndian && bitCount % 8 == 0;
            bool canDoFastPath     = bitCount >= 16 && !reverseEndianness;

            for (int row = 0; row < rows; row++)
            {
                using TiffPixelSpanHandle <TiffGray16> pixelSpanHandle = writer.GetRowSpan(row);
                Span <TiffGray16> pixelSpan = pixelSpanHandle.GetSpan();
                var bitReader = new BitReader(sourceSpan.Slice(0, bytesPerScanline), isHigherOrderBitsFirst);
                bitReader.Advance(context.SourceReadOffset.X * bitCount);

                if (canDoFastPath)
                {
                    // Fast path for bits >= 16
                    for (int col = 0; col < cols; col++)
                    {
                        uint value = bitReader.Read(bitCount);
                        value          = FastExpandBits(value, bitCount, 32);
                        pixelSpan[col] = new TiffGray16((ushort)(~value >> 16));
                    }
                }
                else
                {
                    // Slow path
                    for (int col = 0; col < cols; col++)
                    {
                        uint value = bitReader.Read(bitCount);
                        value          = (uint)ExpandBits(value, bitCount, 32, reverseEndianness);
                        pixelSpan[col] = new TiffGray16((ushort)(~value >> 16));
                    }
                }

                sourceSpan = sourceSpan.Slice(bytesPerScanline);
            }

            return(next.RunAsync(context));
        }
Beispiel #3
0
        public void TestAlignedRead(int byteCount, bool testAdvance)
        {
            var rand = new Random(42);

            byte[] data = new byte[byteCount];
            rand.NextBytes(data);

            var bitReader = new BitReader(data);

            for (int i = 0; i < byteCount; i++)
            {
                Assert.Equal(i, bitReader.ConsumedBytes);
                Assert.Equal(i * 8, bitReader.ConsumedBits);
                uint value = bitReader.Peek(8);
                Assert.Equal(i, bitReader.ConsumedBytes);
                Assert.Equal(i * 8, bitReader.ConsumedBits);
                Assert.Equal(data[i], value);
                if (testAdvance)
                {
                    bitReader.Advance(8);
                }
                else
                {
                    value = bitReader.Read(8);
                    Assert.Equal(data[i], value);
                }
                Assert.Equal(i + 1, bitReader.ConsumedBytes);
                Assert.Equal((i + 1) * 8, bitReader.ConsumedBits);
            }
        }
        /// <inheritdoc />
        public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (next is null)
            {
                throw new ArgumentNullException(nameof(next));
            }

            int  bitCount = _bitCount;
            bool isHigherOrderBitsFirst = _fillOrder != TiffFillOrder.LowerOrderBitsFirst;

            int                 bytesPerScanline = (context.SourceImageSize.Width * bitCount + 7) / 8;
            Memory <byte>       source           = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline);
            ReadOnlySpan <byte> sourceSpan       = source.Span;

            using TiffPixelBufferWriter <TiffGray8> writer = context.GetWriter <TiffGray8>();

            int rows = context.ReadSize.Height;
            int cols = context.ReadSize.Width;

            for (int row = 0; row < rows; row++)
            {
                using TiffPixelSpanHandle <TiffGray8> pixelSpanHandle = writer.GetRowSpan(row);
                Span <TiffGray8> pixelSpan = pixelSpanHandle.GetSpan();
                var bitReader = new BitReader(sourceSpan.Slice(0, bytesPerScanline), isHigherOrderBitsFirst);
                bitReader.Advance(context.SourceReadOffset.X * bitCount);

                if (bitCount * 2 >= 8)
                {
                    // Fast path for bits >= 4
                    for (int col = 0; col < cols; col++)
                    {
                        uint value = bitReader.Read(bitCount);
                        value          = FastExpandBits(value, bitCount, 8);
                        pixelSpan[col] = new TiffGray8((byte)value);
                    }
                }
                else
                {
                    // Slow path
                    for (int col = 0; col < cols; col++)
                    {
                        uint value = bitReader.Read(bitCount);
                        value          = ExpandBits(value, bitCount, 8);
                        pixelSpan[col] = new TiffGray8((byte)value);
                    }
                }

                sourceSpan = sourceSpan.Slice(bytesPerScanline);
            }

            return(next.RunAsync(context));
        }
Beispiel #5
0
        /// <inheritdoc />
        public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (next is null)
            {
                throw new ArgumentNullException(nameof(next));
            }

            // Colormap array is read using TiffTagReader, its elements should be in machine-endian.
            ushort[] colorMap = _colorMap;

            int  bitCount = _bitCount;
            bool isHigherOrderBitsFirst = _fillOrder != TiffFillOrder.LowerOrderBitsFirst;

            int                 bytesPerScanline = (context.SourceImageSize.Width * bitCount + 7) / 8;
            Memory <byte>       source           = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline);
            ReadOnlySpan <byte> sourceSpan       = source.Span;

            using TiffPixelBufferWriter <TiffRgba64> writer = context.GetWriter <TiffRgba64>();

            int rows = context.ReadSize.Height;
            int cols = context.ReadSize.Width;

            TiffRgba64 pixel = default;

            pixel.A = ushort.MaxValue;

            for (int row = 0; row < rows; row++)
            {
                using TiffPixelSpanHandle <TiffRgba64> pixelSpanHandle = writer.GetRowSpan(row);
                Span <TiffRgba64> pixelSpan = pixelSpanHandle.GetSpan();
                var bitReader = new BitReader(sourceSpan.Slice(0, bytesPerScanline), isHigherOrderBitsFirst);
                bitReader.Advance(context.SourceReadOffset.X * bitCount);

                for (int col = 0; col < cols; col++)
                {
                    uint offset = bitReader.Read(bitCount);
                    pixel.R = colorMap[offset];
                    pixel.G = colorMap[SingleColorCount + offset];
                    pixel.B = colorMap[2 * SingleColorCount + offset];

                    pixelSpan[col] = pixel;
                }

                sourceSpan = sourceSpan.Slice(bytesPerScanline);
            }

            return(next.RunAsync(context));
        }
        /// <inheritdoc />
        public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (next is null)
            {
                throw new ArgumentNullException(nameof(next));
            }

            Span <ushort> bitsPerSample = stackalloc ushort[3];

            _bitsPerSample.CopyTo(bitsPerSample);
            bool isHigherOrderBitsFirst = _fillOrder != TiffFillOrder.LowerOrderBitsFirst;
            bool canDoFastPath          = bitsPerSample[0] >= 4 && bitsPerSample[1] >= 4 && bitsPerSample[2] >= 4;
            int  totalBitsPerSample     = bitsPerSample[0] + bitsPerSample[1] + bitsPerSample[2];

            int                 bytesPerScanline = (context.SourceImageSize.Width * totalBitsPerSample + 7) / 8;
            Memory <byte>       source           = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline);
            ReadOnlySpan <byte> sourceSpan       = source.Span;

            using TiffPixelBufferWriter <TiffRgb24> writer = context.GetWriter <TiffRgb24>();

            int rows = context.ReadSize.Height;
            int cols = context.ReadSize.Width;

            TiffRgb24 pixel = default;

            for (int row = 0; row < rows; row++)
            {
                using TiffPixelSpanHandle <TiffRgb24> pixelSpanHandle = writer.GetRowSpan(row);
                Span <TiffRgb24> pixelSpan = pixelSpanHandle.GetSpan();
                var bitReader = new BitReader(sourceSpan.Slice(0, bytesPerScanline), isHigherOrderBitsFirst);
                bitReader.Advance(context.SourceReadOffset.X * totalBitsPerSample);

                if (canDoFastPath)
                {
                    // Fast path for bits >= 8
                    for (int col = 0; col < cols; col++)
                    {
                        pixel.R        = (byte)FastExpandBits(bitReader.Read(bitsPerSample[0]), bitsPerSample[0], 8);
                        pixel.G        = (byte)FastExpandBits(bitReader.Read(bitsPerSample[1]), bitsPerSample[1], 8);
                        pixel.B        = (byte)FastExpandBits(bitReader.Read(bitsPerSample[2]), bitsPerSample[2], 8);
                        pixelSpan[col] = pixel;
                    }
                }
                else
                {
                    // Slow path
                    for (int col = 0; col < cols; col++)
                    {
                        pixel.R        = (byte)ExpandBits(bitReader.Read(bitsPerSample[0]), bitsPerSample[0], 8);
                        pixel.G        = (byte)ExpandBits(bitReader.Read(bitsPerSample[1]), bitsPerSample[1], 8);
                        pixel.B        = (byte)ExpandBits(bitReader.Read(bitsPerSample[2]), bitsPerSample[2], 8);
                        pixelSpan[col] = pixel;
                    }
                }

                sourceSpan = sourceSpan.Slice(bytesPerScanline);
            }

            return(next.RunAsync(context));
        }
        /// <inheritdoc />
        public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (next is null)
            {
                throw new ArgumentNullException(nameof(next));
            }

            Span <ushort> bitsPerSample = stackalloc ushort[3];

            _bitsPerSample.CopyTo(bitsPerSample);
            bool isHigherOrderBitsFirst = _fillOrder != TiffFillOrder.LowerOrderBitsFirst;
            int  totalBitsPerSample     = bitsPerSample[0] + bitsPerSample[1] + bitsPerSample[2];

            int                 bytesPerScanline = (context.SourceImageSize.Width * totalBitsPerSample + 7) / 8;
            Memory <byte>       source           = context.UncompressedData.Slice(context.SourceReadOffset.Y * bytesPerScanline);
            ReadOnlySpan <byte> sourceSpan       = source.Span;

            using TiffPixelBufferWriter <TiffRgba64> writer = context.GetWriter <TiffRgba64>();

            int rows = context.ReadSize.Height;
            int cols = context.ReadSize.Width;

            // BitReader.Read reads bytes in big-endian way, we only need to reverse the endianness if the source is little-endian.
            bool isLittleEndian     = context.IsLittleEndian;
            bool reverseEndiannessR = isLittleEndian && bitsPerSample[0] % 8 == 0;
            bool reverseEndiannessG = isLittleEndian && bitsPerSample[1] % 8 == 0;
            bool reverseEndiannessB = isLittleEndian && bitsPerSample[2] % 8 == 0;
            bool canDoFastPath      = bitsPerSample[0] >= 16 && bitsPerSample[1] >= 16 && bitsPerSample[2] >= 16 &&
                                      !reverseEndiannessR & !reverseEndiannessG & !reverseEndiannessB;

            TiffRgba64 pixel = default;

            pixel.A = ushort.MaxValue;

            for (int row = 0; row < rows; row++)
            {
                using TiffPixelSpanHandle <TiffRgba64> pixelSpanHandle = writer.GetRowSpan(row);
                Span <TiffRgba64> pixelSpan = pixelSpanHandle.GetSpan();
                var bitReader = new BitReader(sourceSpan.Slice(0, bytesPerScanline), isHigherOrderBitsFirst);
                bitReader.Advance(context.SourceReadOffset.X * totalBitsPerSample);

                if (canDoFastPath)
                {
                    // Fast path for bits >= 8
                    for (int col = 0; col < cols; col++)
                    {
                        pixel.R        = (ushort)(FastExpandBits(bitReader.Read(bitsPerSample[0]), bitsPerSample[0], 32) >> 16);
                        pixel.G        = (ushort)(FastExpandBits(bitReader.Read(bitsPerSample[1]), bitsPerSample[1], 32) >> 16);
                        pixel.B        = (ushort)(FastExpandBits(bitReader.Read(bitsPerSample[2]), bitsPerSample[2], 32) >> 16);
                        pixelSpan[col] = pixel;
                    }
                }
                else
                {
                    // Slow path
                    for (int col = 0; col < cols; col++)
                    {
                        pixel.R        = (ushort)(ExpandBits(bitReader.Read(bitsPerSample[0]), bitsPerSample[0], 32, reverseEndiannessR) >> 16);
                        pixel.G        = (ushort)(ExpandBits(bitReader.Read(bitsPerSample[1]), bitsPerSample[1], 32, reverseEndiannessG) >> 16);
                        pixel.B        = (ushort)(ExpandBits(bitReader.Read(bitsPerSample[2]), bitsPerSample[2], 32, reverseEndiannessB) >> 16);
                        pixelSpan[col] = pixel;
                    }
                }

                sourceSpan = sourceSpan.Slice(bytesPerScanline);
            }

            return(next.RunAsync(context));
        }
Beispiel #8
0
        /// <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;
                        }
                    }
                }
            }
        }
Beispiel #9
0
        /// <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);

            ReferenceScanline referenceScanline = default;

            // 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);

                // Read the first EOL.
                if (bitReader.Peek(12) == 0b000000000001) // EOL code is 000000000001 (12bits).
                {
                    bitReader.Advance(12);
                }
                else // Or EOL code is zero filled.
                {
                    // 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.
                    int value = (int)bitReader.Read(filledBits + 12);
                    if (value != 0b000000000001)
                    {
                        throw new InvalidDataException();
                    }
                }

                // Determine the type of this line (1d vs 2d)
                bool is1D = bitReader.Read(1) != 0;

                if (is1D)
                {
                    Decode1DScanline(ref bitReader, whiteIsZero, scanline);
                }
                else
                {
                    if (referenceScanline.IsEmpty)
                    {
                        throw new InvalidDataException();
                    }
                    Decode2DScanline(ref bitReader, whiteIsZero, referenceScanline, scanline);
                }
                referenceScanline = new ReferenceScanline(whiteIsZero, scanline);
            }
        }
Beispiel #10
0
        private static void Decode2DScanline(ref BitReader bitReader, bool whiteIsZero, ReferenceScanline referenceScanline, Span <byte> scanline)
        {
            int width = scanline.Length;
            CcittDecodingTable currentTable = CcittDecodingTable.WhiteInstance;
            CcittDecodingTable otherTable   = CcittDecodingTable.BlackInstance;
            CcittTwoDimensionalDecodingTable decodingTable = CcittTwoDimensionalDecodingTable.Instance;
            CcittTwoDimensionalCodeValue     tableEntry;
            const int PeekCount = CcittTwoDimensionalDecodingTable.PeekCount;

            // 2D Encoding variables.
            int  a0 = -1, a1, b1;
            byte fillByte = whiteIsZero ? (byte)0 : (byte)255;

            // Process every code word in this scanline
            int unpacked = 0;

            while (true)
            {
                // Read next code word and advance pass it.
                int value = (int)bitReader.Peek(PeekCount); // PeekCount = 12

                // Special case handling for EOL.
                // Lucky we don't need to peek again for EOL, because PeekCount(12) is excatly the length of EOL code.
                if (value == 0b000000000001)
                {
                    scanline.Fill(fillByte);
                    break;
                }

                // Look up in the table and advance past this code.
                if (!decodingTable.TryLookup(value, out tableEntry))
                {
                    throw new InvalidDataException();
                }
                bitReader.Advance(tableEntry.BitsRequired);

                // Update 2D Encoding variables.
                b1 = referenceScanline.FindB1(a0, fillByte);

                // Switch on the code word.
                switch (tableEntry.Type)
                {
                case CcittTwoDimensionalCodeType.Pass:
                    int b2 = referenceScanline.FindB2(b1);
                    scanline.Slice(unpacked, b2 - unpacked).Fill(fillByte);
                    unpacked = b2;
                    a0       = b2;
                    break;

                case CcittTwoDimensionalCodeType.Horizontal:
                    // Decode M(a0a1)
                    int runLength = currentTable.DecodeRun(ref bitReader);
                    if ((uint)runLength > (uint)(scanline.Length - unpacked))
                    {
                        throw new InvalidOperationException();
                    }
                    scanline.Slice(unpacked, runLength).Fill(fillByte);
                    unpacked += runLength;
                    fillByte  = (byte)~fillByte;
                    CcittHelper.SwapTable(ref currentTable, ref otherTable);
                    // Decode M(a1a2)
                    runLength = currentTable.DecodeRun(ref bitReader);
                    if ((uint)runLength > (uint)(scanline.Length - unpacked))
                    {
                        throw new InvalidOperationException();
                    }
                    scanline.Slice(unpacked, runLength).Fill(fillByte);
                    unpacked += runLength;
                    fillByte  = (byte)~fillByte;
                    CcittHelper.SwapTable(ref currentTable, ref otherTable);
                    // Prepare next a0
                    a0 = unpacked;
                    break;

                case CcittTwoDimensionalCodeType.Vertical0:
                    a1 = b1;
                    scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte);
                    unpacked = a1;
                    a0       = a1;
                    fillByte = (byte)~fillByte;
                    CcittHelper.SwapTable(ref currentTable, ref otherTable);
                    break;

                case CcittTwoDimensionalCodeType.VerticalR1:
                    a1 = b1 + 1;
                    scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte);
                    unpacked = a1;
                    a0       = a1;
                    fillByte = (byte)~fillByte;
                    CcittHelper.SwapTable(ref currentTable, ref otherTable);
                    break;

                case CcittTwoDimensionalCodeType.VerticalR2:
                    a1 = b1 + 2;
                    scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte);
                    unpacked = a1;
                    a0       = a1;
                    fillByte = (byte)~fillByte;
                    CcittHelper.SwapTable(ref currentTable, ref otherTable);
                    break;

                case CcittTwoDimensionalCodeType.VerticalR3:
                    a1 = b1 + 3;
                    scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte);
                    unpacked = a1;
                    a0       = a1;
                    fillByte = (byte)~fillByte;
                    CcittHelper.SwapTable(ref currentTable, ref otherTable);
                    break;

                case CcittTwoDimensionalCodeType.VerticalL1:
                    a1 = b1 - 1;
                    scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte);
                    unpacked = a1;
                    a0       = a1;
                    fillByte = (byte)~fillByte;
                    CcittHelper.SwapTable(ref currentTable, ref otherTable);
                    break;

                case CcittTwoDimensionalCodeType.VerticalL2:
                    a1 = b1 - 2;
                    scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte);
                    unpacked = a1;
                    a0       = a1;
                    fillByte = (byte)~fillByte;
                    CcittHelper.SwapTable(ref currentTable, ref otherTable);
                    break;

                case CcittTwoDimensionalCodeType.VerticalL3:
                    a1 = b1 - 3;
                    scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte);
                    unpacked = a1;
                    a0       = a1;
                    fillByte = (byte)~fillByte;
                    CcittHelper.SwapTable(ref currentTable, ref otherTable);
                    break;

                default:
                    throw new NotSupportedException("1D and 2D Extensions not supportted.");
                }

                // This line is fully unpacked. Should exit and process next line.
                if (unpacked == width)
                {
                    break;
                }
                else if (unpacked > width)
                {
                    throw new InvalidDataException();
                }
            }
        }
        /// <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();
            }
        }
        /// <inheritdoc />
        public ValueTask InvokeAsync(TiffImageDecoderContext context, ITiffImageDecoderPipelineNode next)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (next is null)
            {
                throw new ArgumentNullException(nameof(next));
            }

            Span <ushort> bitsPerSample = stackalloc ushort[4];

            _bitsPerSample.CopyTo(bitsPerSample);
            bool isHigherOrderBitsFirst = _fillOrder != TiffFillOrder.LowerOrderBitsFirst;

            int bytesPerScanlineR = (context.SourceImageSize.Width * bitsPerSample[0] + 7) / 8;
            int bytesPerScanlineG = (context.SourceImageSize.Width * bitsPerSample[1] + 7) / 8;
            int bytesPerScanlineB = (context.SourceImageSize.Width * bitsPerSample[2] + 7) / 8;
            int bytesPerScanlineA = (context.SourceImageSize.Width * bitsPerSample[3] + 7) / 8;

            ReadOnlySpan <byte> sourceSpan = context.UncompressedData.Span;
            ReadOnlySpan <byte> sourceR    = sourceSpan.Slice(0, context.SourceImageSize.Height * bytesPerScanlineR);
            ReadOnlySpan <byte> sourceG    = sourceSpan.Slice(sourceR.Length, context.SourceImageSize.Height * bytesPerScanlineG);
            ReadOnlySpan <byte> sourceB    = sourceSpan.Slice(sourceR.Length + sourceG.Length, context.SourceImageSize.Height * bytesPerScanlineB);
            ReadOnlySpan <byte> sourceA    = sourceSpan.Slice(sourceR.Length + sourceG.Length + sourceB.Length, context.SourceImageSize.Height * bytesPerScanlineA);

            sourceR = sourceR.Slice(context.SourceReadOffset.Y * bytesPerScanlineR);
            sourceG = sourceG.Slice(context.SourceReadOffset.Y * bytesPerScanlineG);
            sourceB = sourceB.Slice(context.SourceReadOffset.Y * bytesPerScanlineB);
            sourceA = sourceB.Slice(context.SourceReadOffset.Y * bytesPerScanlineA);

            using TiffPixelBufferWriter <TiffRgba64> writer = context.GetWriter <TiffRgba64>();

            int rows = context.ReadSize.Height;
            int cols = context.ReadSize.Width;

            // BitReader.Read reads bytes in big-endian way, we only need to reverse the endianness if the source is little-endian.
            bool isLittleEndian     = context.IsLittleEndian;
            bool reverseEndiannessR = isLittleEndian && bitsPerSample[0] % 8 == 0;
            bool reverseEndiannessG = isLittleEndian && bitsPerSample[1] % 8 == 0;
            bool reverseEndiannessB = isLittleEndian && bitsPerSample[2] % 8 == 0;
            bool reverseEndiannessA = isLittleEndian && bitsPerSample[3] % 8 == 0;
            bool canDoFastPath      = bitsPerSample[0] >= 16 && bitsPerSample[1] >= 16 && bitsPerSample[2] >= 16 && bitsPerSample[3] >= 16 &&
                                      !reverseEndiannessR & !reverseEndiannessG & !reverseEndiannessB & !reverseEndiannessA;

            TiffRgba64 pixel = default;

            for (int row = 0; row < rows; row++)
            {
                using TiffPixelSpanHandle <TiffRgba64> pixelSpanHandle = writer.GetRowSpan(row);
                Span <TiffRgba64> pixelSpan = pixelSpanHandle.GetSpan();
                var bitReaderR = new BitReader(sourceR.Slice(0, bytesPerScanlineR), isHigherOrderBitsFirst);
                var bitReaderG = new BitReader(sourceG.Slice(0, bytesPerScanlineG), isHigherOrderBitsFirst);
                var bitReaderB = new BitReader(sourceB.Slice(0, bytesPerScanlineB), isHigherOrderBitsFirst);
                var bitReaderA = new BitReader(sourceA.Slice(0, bytesPerScanlineA), isHigherOrderBitsFirst);
                bitReaderR.Advance(context.SourceReadOffset.X * bitsPerSample[0]);
                bitReaderG.Advance(context.SourceReadOffset.X * bitsPerSample[1]);
                bitReaderB.Advance(context.SourceReadOffset.X * bitsPerSample[2]);
                bitReaderA.Advance(context.SourceReadOffset.X * bitsPerSample[3]);

                if (canDoFastPath)
                {
                    // Fast path for bits >= 8
                    for (int col = 0; col < cols; col++)
                    {
                        pixel.R        = (ushort)(FastExpandBits(bitReaderR.Read(bitsPerSample[0]), bitsPerSample[0], 32) >> 16);
                        pixel.G        = (ushort)(FastExpandBits(bitReaderG.Read(bitsPerSample[1]), bitsPerSample[1], 32) >> 16);
                        pixel.B        = (ushort)(FastExpandBits(bitReaderB.Read(bitsPerSample[2]), bitsPerSample[2], 32) >> 16);
                        pixel.A        = (ushort)(FastExpandBits(bitReaderA.Read(bitsPerSample[3]), bitsPerSample[3], 32) >> 16);
                        pixelSpan[col] = pixel;
                    }
                }
                else
                {
                    // Slow path
                    for (int col = 0; col < cols; col++)
                    {
                        pixel.R        = (ushort)(ExpandBits(bitReaderR.Read(bitsPerSample[0]), bitsPerSample[0], 32, reverseEndiannessR) >> 16);
                        pixel.G        = (ushort)(ExpandBits(bitReaderG.Read(bitsPerSample[1]), bitsPerSample[1], 32, reverseEndiannessG) >> 16);
                        pixel.B        = (ushort)(ExpandBits(bitReaderB.Read(bitsPerSample[2]), bitsPerSample[2], 32, reverseEndiannessB) >> 16);
                        pixel.A        = (ushort)(ExpandBits(bitReaderA.Read(bitsPerSample[3]), bitsPerSample[3], 32, reverseEndiannessA) >> 16);
                        pixelSpan[col] = pixel;
                    }
                }

                if (_isAlphaAssociated)
                {
                    if (_undoColorPreMultiplying)
                    {
                        UndoColorPreMultiplying(pixelSpan);
                    }
                    else
                    {
                        WipeAlphaChanel(pixelSpan);
                    }
                }

                sourceR = sourceR.Slice(bytesPerScanlineR);
                sourceG = sourceG.Slice(bytesPerScanlineG);
                sourceB = sourceB.Slice(bytesPerScanlineB);
                sourceA = sourceA.Slice(bytesPerScanlineA);
            }

            return(next.RunAsync(context));
        }