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); }
/// <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)); }
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)); }
/// <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)); }
/// <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("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); } }
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)); }