/// <inheritdoc /> public void Decompress(TiffDecompressionContext context, ReadOnlyMemory <byte> input, Memory <byte> output) { var inflater = new Inflater(noHeader: false); Span <byte> destination = output.Span; while (!destination.IsEmpty) { int bytesWritten = inflater.Inflate(destination); if (inflater.IsFinished) { break; } if (inflater.IsNeedingInput) { if (input.IsEmpty) { throw new InvalidDataException(); } inflater.SetInput(input); input = default; } else if (bytesWritten == 0) { throw new InvalidDataException(); } destination = destination.Slice(bytesWritten); } }
/// <inheritdoc /> public void Decompress(TiffDecompressionContext context, ReadOnlyMemory <byte> input, Memory <byte> output) { ReadOnlySpan <byte> inputSpan = input.Span; Span <byte> scanlinesBufferSpan = output.Span; if (inputSpan.IsEmpty) { return; } byte first = inputSpan[0]; if (first == 0) { DecompressLeastSignificantBitFirst(inputSpan, scanlinesBufferSpan); } else if (first == 128) { DecompressMostSignificantBitFirst(inputSpan, scanlinesBufferSpan); } else { throw new InvalidDataException(); } }
public void Decompress(TiffDecompressionContext context, ReadOnlyMemory <byte> input, Memory <byte> output) { // Copy frame header JpegFrameHeader frameHeader = _frameHeader; frameHeader = new JpegFrameHeader(frameHeader.SamplePrecision, (ushort)context.ImageSize.Height, (ushort)context.ImageSize.Width, frameHeader.NumberOfComponents, frameHeader.Components); var decoder = new JpegDecoder(); decoder.StartOfFrame = JpegMarker.StartOfFrame0; decoder.MemoryPool = context.MemoryPool; decoder.SetFrameHeader(frameHeader); decoder.SetRestartInterval(_restartInterval); foreach (ComponentInfo componentInfo in _components) { decoder.SetQuantizationTable(componentInfo.QuantizationTable); decoder.SetHuffmanTable(componentInfo.DcTable); decoder.SetHuffmanTable(componentInfo.AcTable); } var outputWriter = new JpegBuffer8BitOutputWriter(context.ImageSize.Width, context.SkippedScanlines, context.SkippedScanlines + context.RequestedScanlines, decoder.NumberOfComponents, output); decoder.SetOutputWriter(outputWriter); var reader = new JpegReader(input); decoder.ProcessScan(ref reader, _scanHeader); }
/// <inheritdoc /> public void Decompress(TiffDecompressionContext context, ReadOnlyMemory <byte> input, Memory <byte> output) { if (context is null) { throw new ArgumentNullException(nameof(context)); } int bytesPerScanline = context.BytesPerScanline; ReadOnlySpan <byte> inputSpan = input.Span; Span <byte> scanlinesBufferSpan = output.Span; int totalScanlines = context.SkippedScanlines + context.RequestedScanlines; for (int i = 0; i < totalScanlines; i++) { if (scanlinesBufferSpan.Length < bytesPerScanline) { throw new ArgumentException("destination too short.", nameof(output)); } Span <byte> scanline = scanlinesBufferSpan.Slice(0, bytesPerScanline); scanlinesBufferSpan = scanlinesBufferSpan.Slice(bytesPerScanline); int unpacked = 0; while (unpacked < bytesPerScanline) { sbyte n = (sbyte)inputSpan[0]; inputSpan = inputSpan.Slice(1); if (n >= 0) { inputSpan.Slice(0, n + 1).CopyTo(scanline); inputSpan = inputSpan.Slice(n + 1); scanline = scanline.Slice(n + 1); unpacked += n + 1; } else if (n != -128) { byte v = inputSpan[0]; inputSpan = inputSpan.Slice(1); scanline.Slice(0, 1 - n).Fill(v); scanline = scanline.Slice(1 - n); unpacked += 1 - n; } } } }
/// <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("T6 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); ReferenceScanline referenceScanline = new ReferenceScanline(whiteIsZero, width); // Process every scanline for (int i = 0; i < height; i++) { Span <byte> scanline = scanlinesBufferSpan.Slice(0, width); scanlinesBufferSpan = scanlinesBufferSpan.Slice(width); Decode2DScanline(ref bitReader, whiteIsZero, referenceScanline, scanline); referenceScanline = new ReferenceScanline(whiteIsZero, scanline); } }
/// <inheritdoc /> public void Decompress(TiffDecompressionContext context, ReadOnlyMemory <byte> input, Memory <byte> output) { input.CopyTo(output); }
/// <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("ThunderScan compression does not support this photometric interpretation."); } if (context.BitsPerSample.Count != 1 || context.BitsPerSample[0] != 4) { throw new NotSupportedException("Unsupported bits per sample."); } ReadOnlySpan <sbyte> twoBitDiffs = MemoryMarshal.Cast <byte, sbyte>(TwoBitDiffDecodeTable); ReadOnlySpan <sbyte> threeBitDiffs = MemoryMarshal.Cast <byte, sbyte>(ThreeBitDiffDecodeTable); ReadOnlySpan <byte> inputSpan = input.Span; OutputWriter writer = new OutputWriter(output.Span, context.ImageSize.Width, context.ImageSize.Height); int delta; uint lastPixel = 0; ulong buffer = 0; uint bufferCount = 0; for (int i = 0; i < inputSpan.Length; i++) { uint inputByte = inputSpan[i]; switch (inputByte & 0b11_000000) { case 0b00_000000: for (uint j = 0; j < inputByte; j++) { if (bufferCount == 16) { writer.Write(buffer, 16); bufferCount = 0; } buffer = (buffer << 4) | lastPixel; bufferCount++; } break; case 0b01_000000: if (bufferCount > (16 - 3)) { writer.Write(buffer, bufferCount); bufferCount = 0; } if ((delta = (int)((inputByte >> 4) & 0b11)) != 0b10) { lastPixel = (uint)((int)lastPixel + twoBitDiffs[delta]) & 0b1111; buffer = (buffer << 4) | lastPixel; bufferCount++; } if ((delta = (int)((inputByte >> 2) & 0b11)) != 0b10) { lastPixel = (uint)((int)lastPixel + twoBitDiffs[delta]) & 0b1111; buffer = (buffer << 4) | lastPixel; bufferCount++; } if ((delta = (int)(inputByte & 0b11)) != 0b10) { lastPixel = (uint)((int)lastPixel + twoBitDiffs[delta]) & 0b1111; buffer = (buffer << 4) | lastPixel; bufferCount++; } break; case 0b10_000000: if (bufferCount > (16 - 2)) { writer.Write(buffer, bufferCount); bufferCount = 0; } if ((delta = (int)((inputByte >> 3) & 0b111)) != 0b100) { lastPixel = (uint)((int)lastPixel + threeBitDiffs[delta]) & 0b1111; buffer = (buffer << 4) | lastPixel; bufferCount++; } if ((delta = (int)(inputByte & 0b111)) != 0b100) { lastPixel = (uint)((int)lastPixel + threeBitDiffs[delta]) & 0b1111; buffer = (buffer << 4) | lastPixel; bufferCount++; } break; case 0b11_000000: if (bufferCount == 16) { writer.Write(buffer, 16); bufferCount = 0; } lastPixel = inputByte & 0b1111; buffer = (buffer << 4) | lastPixel; bufferCount++; break; } } if (bufferCount != 0) { writer.Write(buffer, bufferCount); } if (!writer.RemainingSpan.IsEmpty) { writer.RemainingSpan.Clear(); } }
/// <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("ThunderScan compression does not support this photometric interpretation."); } if (context.BitsPerSample.Count != 1 || context.BitsPerSample[0] != 2) { throw new NotSupportedException("Unsupported bits per sample."); } int width = context.ImageSize.Width; int height = context.SkippedScanlines + context.RequestedScanlines; int bytesPerScanline = context.BytesPerScanline; ReadOnlySpan <byte> inputSpan = input.Span; Span <byte> outputSpan = output.Span; for (int row = 0; row < height; row++) { if (inputSpan.IsEmpty) { ThrowMoreDataIsExpected(); } Span <byte> scanline = outputSpan.Slice(0, bytesPerScanline); outputSpan = outputSpan.Slice(bytesPerScanline); int leadingByte = inputSpan[0]; if (leadingByte == 0x00) { // Literal row inputSpan = inputSpan.Slice(1); if (inputSpan.Length < scanline.Length) { ThrowMoreDataIsExpected(); } inputSpan.Slice(0, scanline.Length).CopyTo(scanline); inputSpan = inputSpan.Slice(scanline.Length); } else if (leadingByte == 0x40) { // Literal span if (inputSpan.Length < 5) { ThrowMoreDataIsExpected(); } int offset = BinaryPrimitives.ReadUInt16BigEndian(inputSpan.Slice(1)); int count = BinaryPrimitives.ReadUInt16BigEndian(inputSpan.Slice(3)); inputSpan = inputSpan.Slice(5); if (inputSpan.Length < count) { ThrowMoreDataIsExpected(); } if (offset > scanline.Length || (offset + count) > scanline.Length) { throw new InvalidDataException("Incorrect offset and count for literal span."); } scanline.Slice(0, offset).Fill(0xff); inputSpan.Slice(0, count).CopyTo(scanline.Slice(offset, count)); scanline.Slice(offset + count).Fill(0xff); inputSpan = inputSpan.Slice(count); } else { // RLE var writer = new RunLengthWriter(scanline, width); while (!writer.IsEndOfLine) { if (inputSpan.IsEmpty) { ThrowMoreDataIsExpected(); } writer.WriteRunLength(inputSpan[0]); inputSpan = inputSpan.Slice(1); } } } }
/// <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); } }
/// <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 void Decompress(TiffDecompressionContext context, ReadOnlyMemory <byte> input, Memory <byte> output) { if (context is null) { throw new ArgumentNullException(nameof(context)); } // Identify this block JpegDecoder decoder = Interlocked.Exchange(ref _decoder, null) ?? LoadJpegDecoder(); decoder.MemoryPool = context.MemoryPool; decoder.SetInput(input); decoder.Identify(); // Validate we are capable of decoding this. TiffSize outputBufferSize = context.ImageSize; if (decoder.Width < outputBufferSize.Width || decoder.Height < outputBufferSize.Height) { throw new InvalidDataException("Image dimension is too small."); } // Check number of components if (decoder.NumberOfComponents != _numberOfComponents) { throw new InvalidDataException($"Expect {_numberOfComponents} components, but got {decoder.NumberOfComponents} components in the JPEG stream."); } JpegBlockOutputWriter outputWriter; if (decoder.Precision == 8) { if (context.BitsPerSample.GetFirstOrDefault() != 8) { throw new InvalidDataException("Precision of 8 bit is not expected."); } outputWriter = new JpegBuffer8BitOutputWriter(outputBufferSize.Width, context.SkippedScanlines, context.SkippedScanlines + context.RequestedScanlines, decoder.NumberOfComponents, output); } else if (decoder.Precision < 8) { if (context.BitsPerSample.GetFirstOrDefault() != 8) { throw new InvalidDataException($"Precision of {decoder.Precision} bit is not expected."); } outputWriter = new JpegBufferAny8BitOutputWriter(outputBufferSize.Width, context.SkippedScanlines, context.SkippedScanlines + context.RequestedScanlines, decoder.NumberOfComponents, decoder.Precision, output); } else if (decoder.Precision <= 16) { if (context.BitsPerSample.GetFirstOrDefault() != 16) { throw new InvalidDataException($"Precision of {decoder.Precision} bit is not expected."); } outputWriter = new JpegBufferAny16BitOutputWriter(outputBufferSize.Width, context.SkippedScanlines, context.SkippedScanlines + context.RequestedScanlines, decoder.NumberOfComponents, decoder.Precision, output); } else { throw new InvalidDataException($"Precision of {decoder.Precision} bit is not expected."); } // Decode decoder.SetOutputWriter(outputWriter); decoder.Decode(); // Reset state decoder.ResetInput(); decoder.ResetHeader(); decoder.ResetOutputWriter(); // Cache the instances Interlocked.CompareExchange(ref _decoder, decoder, null); }