private static void Decode1DScanline(ref BitReader bitReader, bool whiteIsZero, Span <byte> scanline) { int width = scanline.Length; // 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) { int runLength = currentTable.DecodeRun(ref bitReader); if ((uint)runLength > (uint)scanline.Length) { throw new InvalidOperationException(); } scanline.Slice(0, runLength).Fill(fillValue); scanline = scanline.Slice(runLength); unpacked += runLength; // Switch to the other color. fillValue = (byte)~fillValue; CcittHelper.SwapTable(ref currentTable, ref otherTable); if (unpacked == width) { // This line is fully unpacked. Should exit and process next line. break; } } }
/// <inheritdoc /> public void Compress(TiffCompressionContext context, ReadOnlyMemory <byte> input, IBufferWriter <byte> outputWriter) { 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] != 8) { throw new NotSupportedException("Unsupported bits per sample."); } context.BitsPerSample = TiffValueCollection.Single <ushort>(1); ReadOnlySpan <byte> inputSpan = input.Span; int width = context.ImageSize.Width; int height = context.ImageSize.Height; var bitWriter = new BitWriter2(outputWriter, 4096); // Process every scanline for (int row = 0; row < height; row++) { ReadOnlySpan <byte> rowSpan = inputSpan.Slice(0, width); inputSpan = inputSpan.Slice(width); CcittEncodingTable currentTable = CcittEncodingTable.WhiteInstance; CcittEncodingTable otherTable = CcittEncodingTable.BlackInstance; // ModifiedHuffman compression assumes WhiteIsZero photometric interpretation is used. // Since the first run is white run, we look for black pixel in the first iteration. byte nextRunPixel = 255; while (!rowSpan.IsEmpty) { // Get the length of the current run int runLength = rowSpan.IndexOf(nextRunPixel); if (runLength < 0) { runLength = rowSpan.Length; } currentTable.EncodeRun(ref bitWriter, runLength); rowSpan = rowSpan.Slice(runLength); // Switch to the other color CcittHelper.SwapTable(ref currentTable, ref otherTable); nextRunPixel = (byte)~nextRunPixel; } bitWriter.AdvanceAlignByte(); } bitWriter.Flush(); }
/// <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; } } } } }
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(); } } }
private static void Encode2DScanline(ref BitWriter2 bitWriter, ReferenceScanline referenceScanline, ReadOnlySpan <byte> scanline) { int width = scanline.Length; CcittEncodingTable currentTable = CcittEncodingTable.WhiteInstance; CcittEncodingTable otherTable = CcittEncodingTable.BlackInstance; CodingScanline codingScanline = new CodingScanline(whiteIsZero: true, scanline); byte a0Byte = 0; int a0 = -1; while (true) { int a1 = codingScanline.FindA1(a0, a0Byte); int b1 = referenceScanline.FindB1(a0, a0Byte); int b2 = referenceScanline.FindB2(b1); if (b2 < a1) { EncodePassCode(ref bitWriter); a0 = b2; } else { int a1b1 = b1 - a1; if (a1b1 >= -3 && a1b1 <= 3) { // Vertical mode coding switch (a1b1) { case 3: EncodeVerticalL3Code(ref bitWriter); break; case 2: EncodeVerticalL2Code(ref bitWriter); break; case 1: EncodeVerticalL1Code(ref bitWriter); break; case 0: EncodeVertical0Code(ref bitWriter); break; case -1: EncodeVerticalR1Code(ref bitWriter); break; case -2: EncodeVerticalR2Code(ref bitWriter); break; case -3: EncodeVerticalR3Code(ref bitWriter); break; default: break; } CcittHelper.SwapTable(ref currentTable, ref otherTable); a0 = a1; } else { int a2 = codingScanline.FindA2(a1); EncodeHorizontalCode(ref bitWriter); currentTable.EncodeRun(ref bitWriter, a1 - a0); otherTable.EncodeRun(ref bitWriter, a2 - a1); a0 = a2; } } if (a0 >= width) { break; } a0Byte = scanline[a0]; } }
/// <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(); } }