/// <summary> /// Writes decoded pixel to buffer at a given position. /// </summary> /// <param name="buffer">The buffer to write to.</param> /// <param name="offset">The position to write to.</param> /// <returns>The number of bytes written.</returns> public int WriteTo(Span <byte> buffer, int offset) { if (this.Length == 0) { return(0); } if (this.Length == 1) { buffer[offset] = this.value; return(1); } LzwString e = this; int endIdx = this.Length - 1; if (endIdx >= buffer.Length) { TiffThrowHelper.ThrowImageFormatException("Error reading lzw compressed stream. Either pixel buffer to write to is to small or code length is invalid!"); } for (int i = endIdx; i >= 0; i--) { buffer[offset + i] = e.value; e = e.previous; } return(this.Length); }
private void AddStringToTable(LzwString lzwString) { if (this.tableLength > this.table.Length) { TiffThrowHelper.ThrowImageFormatException($"TIFF LZW with more than {MaxBits} bits per code encountered (table overflow)"); } this.table[this.tableLength++] = lzwString; if (this.tableLength > this.maxCode) { this.bitsPerCode++; if (this.bitsPerCode > MaxBits) { // Continue reading MaxBits (12 bit) length codes. this.bitsPerCode = MaxBits; } this.bitMask = BitmaskFor(this.bitsPerCode); this.maxCode = this.MaxCode(); } if (lzwString.Length > this.maxString) { this.maxString = lzwString.Length; } }
/// <summary> /// Decodes and decompresses all pixel indices from the stream. /// </summary> /// <param name="pixels">The pixel array to decode to.</param> public void DecodePixels(Span <byte> pixels) { // Adapted from the pseudo-code example found in the TIFF 6.0 Specification, 1992. // See Section 13: "LZW Compression"/"LZW Decoding", page 61+ int code; int offset = 0; while ((code = this.GetNextCode()) != EoiCode) { if (code == ClearCode) { this.Init(); code = this.GetNextCode(); if (code == EoiCode) { break; } if (this.table[code] == null) { TiffThrowHelper.ThrowImageFormatException($"Corrupted TIFF LZW: code {code} (table size: {this.tableLength})"); } offset += this.table[code].WriteTo(pixels, offset); } else { if (this.table[this.oldCode] == null) { TiffThrowHelper.ThrowImageFormatException($"Corrupted TIFF LZW: code {this.oldCode} (table size: {this.tableLength})"); } if (this.IsInTable(code)) { offset += this.table[code].WriteTo(pixels, offset); this.AddStringToTable(this.table[this.oldCode].Concatenate(this.table[code].FirstChar)); } else { LzwString outString = this.table[this.oldCode].Concatenate(this.table[this.oldCode].FirstChar); offset += outString.WriteTo(pixels, offset); this.AddStringToTable(outString); } } this.oldCode = code; if (offset >= pixels.Length) { break; } } }
private void LoadNewByte() { this.Position++; this.ResetBitsRead(); if (this.Position >= (ulong)this.DataLength) { TiffThrowHelper.ThrowImageFormatException("tiff image has invalid ccitt compressed data"); } }
/// <inheritdoc/> protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span <byte> buffer) { if (this.compressedDataMemory == null) { this.compressedDataMemory = this.Allocator.Allocate <byte>(byteCount); } else if (this.compressedDataMemory.Length() < byteCount) { this.compressedDataMemory.Dispose(); this.compressedDataMemory = this.Allocator.Allocate <byte>(byteCount); } Span <byte> compressedData = this.compressedDataMemory.GetSpan(); stream.Read(compressedData, 0, byteCount); int compressedOffset = 0; int decompressedOffset = 0; while (compressedOffset < byteCount) { byte headerByte = compressedData[compressedOffset]; if (headerByte <= 127) { int literalOffset = compressedOffset + 1; int literalLength = compressedData[compressedOffset] + 1; if ((literalOffset + literalLength) > compressedData.Length) { TiffThrowHelper.ThrowImageFormatException("Tiff packbits compression error: not enough data."); } compressedData.Slice(literalOffset, literalLength).CopyTo(buffer.Slice(decompressedOffset)); compressedOffset += literalLength + 1; decompressedOffset += literalLength; } else if (headerByte == 0x80) { compressedOffset += 1; } else { byte repeatData = compressedData[compressedOffset + 1]; int repeatLength = 257 - headerByte; ArrayCopyRepeat(repeatData, buffer, decompressedOffset, repeatLength); compressedOffset += 2; decompressedOffset += repeatLength; } } }
/// <summary> /// Decompresses image data into the supplied buffer. /// </summary> /// <param name="stream">The <see cref="Stream" /> to read image data from.</param> /// <param name="stripOffset">The strip offset of stream.</param> /// <param name="stripByteCount">The number of bytes to read from the input stream.</param> /// <param name="stripHeight">The height of the strip.</param> /// <param name="buffer">The output buffer for uncompressed data.</param> public void Decompress(BufferedReadStream stream, ulong stripOffset, ulong stripByteCount, int stripHeight, Span <byte> buffer) { DebugGuard.MustBeLessThanOrEqualTo(stripOffset, (ulong)long.MaxValue, nameof(stripOffset)); DebugGuard.MustBeLessThanOrEqualTo(stripByteCount, (ulong)long.MaxValue, nameof(stripByteCount)); stream.Seek((long)stripOffset, SeekOrigin.Begin); this.Decompress(stream, (int)stripByteCount, stripHeight, buffer); if ((long)stripOffset + (long)stripByteCount < stream.Position) { TiffThrowHelper.ThrowImageFormatException("Out of range when reading a strip."); } }
/// <summary> /// An EOL is expected before the first data. /// </summary> protected virtual void ReadEolBeforeFirstData() { if (this.isFirstScanLine) { this.Value = this.ReadValue(this.eolPadding ? 16 : 12); if (!this.IsEndOfScanLine) { TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: expected start of data marker not found"); } this.Reset(); } }
/// <summary> /// Decompresses image data into the supplied buffer. /// </summary> /// <param name="stream">The <see cref="Stream" /> to read image data from.</param> /// <param name="stripOffset">The strip offset of stream.</param> /// <param name="stripByteCount">The number of bytes to read from the input stream.</param> /// <param name="stripHeight">The height of the strip.</param> /// <param name="buffer">The output buffer for uncompressed data.</param> public void Decompress(BufferedReadStream stream, uint stripOffset, uint stripByteCount, int stripHeight, Span <byte> buffer) { if (stripByteCount > int.MaxValue) { TiffThrowHelper.ThrowImageFormatException("The StripByteCount value is too big."); } stream.Seek(stripOffset, SeekOrigin.Begin); this.Decompress(stream, (int)stripByteCount, stripHeight, buffer); if (stripOffset + stripByteCount < stream.Position) { TiffThrowHelper.ThrowImageFormatException("Out of range when reading a strip."); } }
/// <inheritdoc/> protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span <byte> buffer) { using var bitReader = new ModifiedHuffmanBitReader(stream, this.FillOrder, byteCount, this.Allocator); buffer.Clear(); uint bitsWritten = 0; uint pixelsWritten = 0; while (bitReader.HasMoreData) { bitReader.ReadNextRun(); if (bitReader.RunLength > 0) { if (bitReader.IsWhiteRun) { BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue); } else { BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue); } bitsWritten += bitReader.RunLength; pixelsWritten += bitReader.RunLength; } if (pixelsWritten == this.Width) { bitReader.StartNewRow(); pixelsWritten = 0; // Write padding bits, if necessary. uint pad = 8 - (bitsWritten % 8); if (pad != 8) { BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0); bitsWritten += pad; } } if (pixelsWritten > this.Width) { TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, decoded more pixels then image width"); } } }
public YCbCrConverter(Rational[] referenceBlackAndWhite, Rational[] coefficients) { referenceBlackAndWhite ??= DefaultReferenceBlackWhite; coefficients ??= DefaultLuma; if (referenceBlackAndWhite.Length != 6) { TiffThrowHelper.ThrowImageFormatException("reference black and white array should have 6 entry's"); } if (coefficients.Length != 3) { TiffThrowHelper.ThrowImageFormatException("luma coefficients array should have 6 entry's"); } this.yExpander = new CodingRangeExpander(referenceBlackAndWhite[0], referenceBlackAndWhite[1], 255); this.cbExpander = new CodingRangeExpander(referenceBlackAndWhite[2], referenceBlackAndWhite[3], 127); this.crExpander = new CodingRangeExpander(referenceBlackAndWhite[4], referenceBlackAndWhite[5], 127); this.converter = new YCbCrToRgbConverter(coefficients[0], coefficients[1], coefficients[2]); }
private static void Decode2DScanline(T6BitReader bitReader, bool whiteIsZero, CcittReferenceScanline referenceScanline, Span <byte> scanline) { int width = scanline.Length; bitReader.StartNewRow(); // 2D Encoding variables. int a0 = -1; 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. bool isEol = bitReader.ReadNextCodeWord(); // Special case handling for EOL. if (isEol) { // If a TIFF reader encounters EOFB before the expected number of lines has been extracted, // it is appropriate to assume that the missing rows consist entirely of white pixels. scanline.Fill(whiteIsZero ? (byte)0 : (byte)255); break; } // Update 2D Encoding variables. int b1 = referenceScanline.FindB1(a0, fillByte); // Switch on the code word. int a1; switch (bitReader.Code.Type) { case CcittTwoDimensionalCodeType.None: TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, could not read a valid code word."); break; 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) bitReader.ReadNextRun(); int runLength = (int)bitReader.RunLength; if (runLength > (uint)(scanline.Length - unpacked)) { TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error"); } scanline.Slice(unpacked, runLength).Fill(fillByte); unpacked += runLength; fillByte = (byte)~fillByte; // Decode M(a1a2) bitReader.ReadNextRun(); runLength = (int)bitReader.RunLength; if (runLength > (uint)(scanline.Length - unpacked)) { TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error"); } scanline.Slice(unpacked, runLength).Fill(fillByte); unpacked += runLength; fillByte = (byte)~fillByte; // 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; bitReader.SwapColor(); break; case CcittTwoDimensionalCodeType.VerticalR1: a1 = b1 + 1; scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); unpacked = a1; a0 = a1; fillByte = (byte)~fillByte; bitReader.SwapColor(); break; case CcittTwoDimensionalCodeType.VerticalR2: a1 = b1 + 2; scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); unpacked = a1; a0 = a1; fillByte = (byte)~fillByte; bitReader.SwapColor(); break; case CcittTwoDimensionalCodeType.VerticalR3: a1 = b1 + 3; scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); unpacked = a1; a0 = a1; fillByte = (byte)~fillByte; bitReader.SwapColor(); break; case CcittTwoDimensionalCodeType.VerticalL1: a1 = b1 - 1; scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); unpacked = a1; a0 = a1; fillByte = (byte)~fillByte; bitReader.SwapColor(); break; case CcittTwoDimensionalCodeType.VerticalL2: a1 = b1 - 2; scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); unpacked = a1; a0 = a1; fillByte = (byte)~fillByte; bitReader.SwapColor(); break; case CcittTwoDimensionalCodeType.VerticalL3: a1 = b1 - 3; scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte); unpacked = a1; a0 = a1; fillByte = (byte)~fillByte; bitReader.SwapColor(); break; default: throw new NotSupportedException("ccitt extensions are not supported."); } // This line is fully unpacked. Should exit and process next line. if (unpacked == width) { break; } if (unpacked > width) { TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, unpacked data > width"); } } }
/// <summary> /// Read the next run of pixels. /// </summary> public void ReadNextRun() { if (this.terminationCodeFound) { this.IsWhiteRun = !this.IsWhiteRun; this.terminationCodeFound = false; } // Initialize for next run. this.Reset(); // We expect an EOL before the first data. this.ReadEolBeforeFirstData(); // A code word must have at least 2 bits. this.Value = this.ReadValue(MinCodeLength); do { if (this.CurValueBitsRead > this.maxCodeLength) { TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: invalid code length read"); } bool isMakeupCode = this.IsMakeupCode(); if (isMakeupCode) { if (this.IsWhiteRun) { this.RunLength += this.WhiteMakeupCodeRunLength(); } else { this.RunLength += this.BlackMakeupCodeRunLength(); } this.isStartOfRow = false; this.Reset(resetRunLength: false); continue; } bool isTerminatingCode = this.IsTerminatingCode(); if (isTerminatingCode) { // Each line starts with a white run. If the image starts with black, a white run with length zero is written. if (this.isStartOfRow && this.IsWhiteRun && this.WhiteTerminatingCodeRunLength() == 0) { this.Reset(); this.isStartOfRow = false; this.terminationCodeFound = true; this.RunLength = 0; break; } if (this.IsWhiteRun) { this.RunLength += this.WhiteTerminatingCodeRunLength(); } else { this.RunLength += this.BlackTerminatingCodeRunLength(); } this.terminationCodeFound = true; this.isStartOfRow = false; break; } uint currBit = this.ReadValue(1); this.Value = (this.Value << 1) | currBit; if (this.IsEndOfScanLine) { this.StartNewRow(); } }while (!this.IsEndOfScanLine); this.isFirstScanLine = false; }