Browse Source

Add support for decoding tiff's with T.6 fax compression

pull/1747/head
Brian Popow 5 years ago
parent
commit
a11752e98c
  1. 154
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs
  2. 27
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs
  3. 32
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs
  4. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs
  5. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs
  6. 73
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs
  7. 19
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs
  8. 3
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs
  9. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs
  10. 375
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs
  11. 10
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs
  12. 160
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs
  13. 253
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs
  14. 19
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs
  15. 7
      src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs
  16. 4
      src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
  17. 2
      src/ImageSharp/Formats/Tiff/README.md
  18. 9
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  19. 8
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  20. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
  21. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
  22. 8
      tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs
  23. 9
      tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs

154
src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs

@ -0,0 +1,154 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Represents a reference scan line for CCITT 2D decoding.
/// </summary>
internal readonly ref struct CcittReferenceScanline
{
private readonly ReadOnlySpan<byte> scanLine;
private readonly int width;
private readonly byte whiteByte;
/// <summary>
/// Initializes a new instance of the <see cref="CcittReferenceScanline"/> struct.
/// </summary>
/// <param name="whiteIsZero">Indicates, if white is zero, otherwise black is zero.</param>
/// <param name="scanLine">The scan line.</param>
public CcittReferenceScanline(bool whiteIsZero, ReadOnlySpan<byte> scanLine)
{
this.scanLine = scanLine;
this.width = scanLine.Length;
this.whiteByte = whiteIsZero ? (byte)0 : (byte)255;
}
/// <summary>
/// Initializes a new instance of the <see cref="CcittReferenceScanline"/> struct.
/// </summary>
/// <param name="whiteIsZero">Indicates, if white is zero, otherwise black is zero.</param>
/// <param name="width">The width of the scanline.</param>
public CcittReferenceScanline(bool whiteIsZero, int width)
{
this.scanLine = default;
this.width = width;
this.whiteByte = whiteIsZero ? (byte)0 : (byte)255;
}
public bool IsEmpty => this.scanLine.IsEmpty;
/// <summary>
/// Finds b1: The first changing element on the reference line to the right of a0 and of opposite color to a0.
/// </summary>
/// <param name="a0">The reference or starting element om the coding line.</param>
/// <param name="a0Byte">Fill byte.</param>
/// <returns>Position of b1.</returns>
public int FindB1(int a0, byte a0Byte)
{
if (this.scanLine.IsEmpty)
{
return this.FindB1ForImaginaryWhiteLine(a0, a0Byte);
}
return this.FindB1ForNormalLine(a0, a0Byte);
}
/// <summary>
/// Finds b2: The next changing element to the right of b1 on the reference line.
/// </summary>
/// <param name="b1">The first changing element on the reference line to the right of a0 and opposite of color to a0.</param>
/// <returns>Position of b1.</returns>
public int FindB2(int b1)
{
if (this.scanLine.IsEmpty)
{
return this.FindB2ForImaginaryWhiteLine();
}
return this.FindB2ForNormalLine(b1);
}
private int FindB1ForImaginaryWhiteLine(int a0, byte a0Byte)
{
if (a0 < 0)
{
if (a0Byte != this.whiteByte)
{
return 0;
}
}
return this.width;
}
private int FindB1ForNormalLine(int a0, byte a0Byte)
{
int offset = 0;
if (a0 < 0)
{
if (a0Byte != this.scanLine[0])
{
return 0;
}
}
else
{
offset = a0;
}
ReadOnlySpan<byte> searchSpace = this.scanLine.Slice(offset);
byte searchByte = (byte)~a0Byte;
int index = searchSpace.IndexOf(searchByte);
if (index < 0)
{
return this.scanLine.Length;
}
if (index != 0)
{
return offset + index;
}
searchByte = (byte)~searchSpace[0];
index = searchSpace.IndexOf(searchByte);
if (index < 0)
{
return this.scanLine.Length;
}
searchSpace = searchSpace.Slice(index);
offset += index;
index = searchSpace.IndexOf((byte)~searchByte);
if (index < 0)
{
return this.scanLine.Length;
}
return index + offset;
}
private int FindB2ForImaginaryWhiteLine() => this.width;
private int FindB2ForNormalLine(int b1)
{
if (b1 >= this.scanLine.Length)
{
return this.scanLine.Length;
}
byte searchByte = (byte)~this.scanLine[b1];
int offset = b1 + 1;
ReadOnlySpan<byte> searchSpace = this.scanLine.Slice(offset);
int index = searchSpace.IndexOf(searchByte);
if (index == -1)
{
return this.scanLine.Length;
}
return offset + index;
}
}
}

27
src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs

@ -0,0 +1,27 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Diagnostics;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
[DebuggerDisplay("Type = {Type}")]
internal readonly struct CcittTwoDimensionalCode
{
private readonly ushort value;
/// <summary>
/// Initializes a new instance of the <see cref="CcittTwoDimensionalCode"/> struct.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="bitsRequired">The bits required.</param>
/// <param name="extensionBits">The extension bits.</param>
public CcittTwoDimensionalCode(CcittTwoDimensionalCodeType type, int bitsRequired, int extensionBits = 0)
=> this.value = (ushort)((byte)type | ((bitsRequired & 0b1111) << 8) | ((extensionBits & 0b111) << 11));
/// <summary>
/// Gets the code type.
/// </summary>
public CcittTwoDimensionalCodeType Type => (CcittTwoDimensionalCodeType)(this.value & 0b11111111);
}
}

32
src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs

@ -0,0 +1,32 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
internal enum CcittTwoDimensionalCodeType
{
None = 0,
Pass = 1,
Horizontal = 2,
Vertical0 = 3,
VerticalR1 = 4,
VerticalR2 = 5,
VerticalR3 = 6,
VerticalL1 = 7,
VerticalL2 = 8,
VerticalL3 = 9,
Extensions1D = 10,
Extensions2D = 11,
}
}

2
src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{
long pos = stream.Position;
using (var deframeStream = new ZlibInflateStream(

2
src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{
var decoder = new TiffLzwDecoder(stream);
decoder.DecodePixels(buffer);

73
src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs

@ -0,0 +1,73 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Bit reader for data encoded with the modified huffman rle method.
/// See TIFF 6.0 specification, section 10.
/// </summary>
internal class ModifiedHuffmanBitReader : T4BitReader
{
/// <summary>
/// Initializes a new instance of the <see cref="ModifiedHuffmanBitReader"/> class.
/// </summary>
/// <param name="input">The compressed input stream.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param>
public ModifiedHuffmanBitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator)
: base(input, fillOrder, bytesToRead, allocator)
{
}
/// <inheritdoc/>
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (this.BitsRead > 0 && this.BitsRead < 7);
/// <inheritdoc/>
public override bool IsEndOfScanLine
{
get
{
if (this.IsWhiteRun && this.CurValueBitsRead == 12 && this.Value == 1)
{
return true;
}
if (this.CurValueBitsRead == 11 && this.Value == 0)
{
// black run.
return true;
}
return false;
}
}
/// <inheritdoc/>
public override void StartNewRow()
{
base.StartNewRow();
int pad = 8 - (this.BitsRead % 8);
if (pad != 8)
{
// Skip padding bits, move to next byte.
this.Position++;
this.ResetBitsRead();
}
}
/// <summary>
/// No EOL is expected at the start of a run for the modified huffman encoding.
/// </summary>
protected override void ReadEolBeforeFirstData()
{
// Nothing to do here.
}
}
}

19
src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs

@ -35,9 +35,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{
using var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, this.Allocator, eolPadding: false, isModifiedHuffman: true);
using var bitReader = new ModifiedHuffmanBitReader(stream, this.FillOrder, byteCount, this.Allocator);
buffer.Clear();
uint bitsWritten = 0;
@ -51,20 +51,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
if (bitReader.IsWhiteRun)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue);
bitsWritten += bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
}
else
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue);
bitsWritten += bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
}
bitsWritten += bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
}
if (pixelsWritten % this.Width == 0)
if (pixelsWritten == this.Width)
{
bitReader.StartNewRow();
pixelsWritten = 0;
// Write padding bits, if necessary.
uint pad = 8 - (bitsWritten % 8);
@ -74,6 +74,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
bitsWritten += pad;
}
}
if (pixelsWritten > this.Width)
{
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, decoded more pixels then image width");
}
}
}
}

3
src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs

@ -25,7 +25,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) => _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount));
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
=> _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount));
/// <inheritdoc/>
protected override void Dispose(bool disposing)

2
src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{
if (this.compressedDataMemory == null)
{

375
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs

@ -16,31 +16,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary>
internal class T4BitReader : IDisposable
{
/// <summary>
/// Number of bits read.
/// </summary>
private int bitsRead;
/// <summary>
/// The logical order of bits within a byte.
/// </summary>
private readonly TiffFillOrder fillOrder;
/// <summary>
/// Current value.
/// </summary>
private uint value;
/// <summary>
/// Number of bits read for the current run value.
/// </summary>
private int curValueBitsRead;
/// <summary>
/// Byte position in the buffer.
/// </summary>
private ulong position;
/// <summary>
/// Indicates whether its the first line of data which is read from the image.
/// </summary>
@ -57,20 +37,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary>
private bool isStartOfRow;
/// <summary>
/// Indicates whether the modified huffman compression, as specified in the TIFF spec in section 10, is used.
/// </summary>
private readonly bool isModifiedHuffmanRle;
/// <summary>
/// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false.
/// </summary>
private readonly bool eolPadding;
private readonly int dataLength;
/// <summary>
/// The minimum code length in bits.
/// </summary>
private const int MinCodeLength = 2;
/// <summary>
/// The maximum code length in bits.
/// </summary>
private readonly int maxCodeLength = 13;
private static readonly Dictionary<uint, uint> WhiteLen4TermCodes = new Dictionary<uint, uint>()
@ -231,19 +210,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param>
/// <param name="eolPadding">Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false.</param>
/// <param name="isModifiedHuffman">Indicates, if its the modified huffman code variation. Defaults to false.</param>
public T4BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false, bool isModifiedHuffman = false)
public T4BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false)
{
this.fillOrder = fillOrder;
this.Data = allocator.Allocate<byte>(bytesToRead);
this.ReadImageDataFromStream(input, bytesToRead);
this.isModifiedHuffmanRle = isModifiedHuffman;
this.dataLength = bytesToRead;
this.bitsRead = 0;
this.value = 0;
this.curValueBitsRead = 0;
this.position = 0;
this.DataLength = bytesToRead;
this.BitsRead = 0;
this.Value = 0;
this.CurValueBitsRead = 0;
this.Position = 0;
this.IsWhiteRun = true;
this.isFirstScanLine = true;
this.isStartOfRow = true;
@ -257,6 +234,31 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
}
/// <summary>
/// Gets the current value.
/// </summary>
protected uint Value { get; private set; }
/// <summary>
/// Gets the number of bits read for the current run value.
/// </summary>
protected int CurValueBitsRead { get; private set; }
/// <summary>
/// Gets the number of bits read.
/// </summary>
protected int BitsRead { get; private set; }
/// <summary>
/// Gets the available data in bytes.
/// </summary>
protected int DataLength { get; }
/// <summary>
/// Gets or sets the byte position in the buffer.
/// </summary>
protected ulong Position { get; set; }
/// <summary>
/// Gets the compressed image data.
/// </summary>
@ -265,23 +267,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary>
/// Gets a value indicating whether there is more data to read left.
/// </summary>
public bool HasMoreData
{
get
{
if (this.isModifiedHuffmanRle)
{
return this.position < (ulong)this.dataLength - 1 || (this.bitsRead > 0 && this.bitsRead < 7);
}
return this.position < (ulong)this.dataLength - 1;
}
}
public virtual bool HasMoreData => this.Position < (ulong)this.DataLength - 1;
/// <summary>
/// Gets a value indicating whether the current run is a white pixel run, otherwise its a black pixel run.
/// Gets or sets a value indicating whether the current run is a white pixel run, otherwise its a black pixel run.
/// </summary>
public bool IsWhiteRun { get; private set; }
public bool IsWhiteRun { get; protected set; }
/// <summary>
/// Gets the number of pixels in the current run.
@ -291,16 +282,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary>
/// Gets a value indicating whether the end of a pixel row has been reached.
/// </summary>
public bool IsEndOfScanLine
public virtual bool IsEndOfScanLine
{
get
{
if (this.eolPadding)
{
return this.curValueBitsRead >= 12 && this.value == 1;
return this.CurValueBitsRead >= 12 && this.Value == 1;
}
return this.curValueBitsRead == 12 && this.value == 1;
return this.CurValueBitsRead == 12 && this.Value == 1;
}
}
@ -315,29 +306,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
this.terminationCodeFound = false;
}
// Initialize for next run.
this.Reset();
if (this.isFirstScanLine && !this.isModifiedHuffmanRle)
{
// We expect an EOL before the first data.
this.value = this.ReadValue(this.eolPadding ? 16 : 12);
if (!this.IsEndOfScanLine)
{
TiffThrowHelper.ThrowImageFormatException("t4 parsing error: expected start of data marker not found");
}
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);
this.Value = this.ReadValue(MinCodeLength);
do
{
if (this.curValueBitsRead > this.maxCodeLength)
if (this.CurValueBitsRead > this.maxCodeLength)
{
TiffThrowHelper.ThrowImageFormatException("t4 parsing error: invalid code length read");
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: invalid code length read");
}
bool isMakeupCode = this.IsMakeupCode();
@ -363,10 +345,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
// 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.IsWhiteRun = !this.IsWhiteRun;
this.Reset();
this.isStartOfRow = false;
continue;
this.terminationCodeFound = true;
this.RunLength = 0;
break;
}
if (this.IsWhiteRun)
@ -384,7 +367,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
uint currBit = this.ReadValue(1);
this.value = (this.value << 1) | currBit;
this.Value = (this.Value << 1) | currBit;
if (this.IsEndOfScanLine)
{
@ -396,55 +379,106 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
this.isFirstScanLine = false;
}
public void StartNewRow()
/// <summary>
/// Initialization for a new row.
/// </summary>
public virtual void StartNewRow()
{
// Each new row starts with a white run.
this.IsWhiteRun = true;
this.isStartOfRow = true;
this.terminationCodeFound = false;
}
if (this.isModifiedHuffmanRle)
/// <inheritdoc/>
public void Dispose() => this.Data.Dispose();
/// <summary>
/// An EOL is expected before the first data.
/// </summary>
protected virtual void ReadEolBeforeFirstData()
{
if (this.isFirstScanLine)
{
int pad = 8 - (this.bitsRead % 8);
if (pad != 8)
this.Value = this.ReadValue(this.eolPadding ? 16 : 12);
if (!this.IsEndOfScanLine)
{
// Skip padding bits, move to next byte.
this.position++;
this.bitsRead = 0;
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: expected start of data marker not found");
}
this.Reset();
}
}
/// <inheritdoc/>
public void Dispose() => this.Data.Dispose();
/// <summary>
/// Resets the current value read and the number of bits read.
/// </summary>
/// <param name="resetRunLength">if set to true resets also the run length.</param>
protected void Reset(bool resetRunLength = true)
{
this.Value = 0;
this.CurValueBitsRead = 0;
if (resetRunLength)
{
this.RunLength = 0;
}
}
/// <summary>
/// Resets the bits read to 0.
/// </summary>
protected void ResetBitsRead() => this.BitsRead = 0;
/// <summary>
/// Reads the next value.
/// </summary>
/// <param name="nBits">The number of bits to read.</param>
/// <returns>The value read.</returns>
protected uint ReadValue(int nBits)
{
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
uint v = 0;
int shift = nBits;
while (shift-- > 0)
{
uint bit = this.GetBit();
v |= bit << shift;
this.CurValueBitsRead++;
}
return v;
}
private uint WhiteTerminatingCodeRunLength()
{
switch (this.curValueBitsRead)
switch (this.CurValueBitsRead)
{
case 4:
{
return WhiteLen4TermCodes[this.value];
return WhiteLen4TermCodes[this.Value];
}
case 5:
{
return WhiteLen5TermCodes[this.value];
return WhiteLen5TermCodes[this.Value];
}
case 6:
{
return WhiteLen6TermCodes[this.value];
return WhiteLen6TermCodes[this.Value];
}
case 7:
{
return WhiteLen7TermCodes[this.value];
return WhiteLen7TermCodes[this.Value];
}
case 8:
{
return WhiteLen8TermCodes[this.value];
return WhiteLen8TermCodes[this.Value];
}
}
@ -453,61 +487,61 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private uint BlackTerminatingCodeRunLength()
{
switch (this.curValueBitsRead)
switch (this.CurValueBitsRead)
{
case 2:
{
return BlackLen2TermCodes[this.value];
return BlackLen2TermCodes[this.Value];
}
case 3:
{
return BlackLen3TermCodes[this.value];
return BlackLen3TermCodes[this.Value];
}
case 4:
{
return BlackLen4TermCodes[this.value];
return BlackLen4TermCodes[this.Value];
}
case 5:
{
return BlackLen5TermCodes[this.value];
return BlackLen5TermCodes[this.Value];
}
case 6:
{
return BlackLen6TermCodes[this.value];
return BlackLen6TermCodes[this.Value];
}
case 7:
{
return BlackLen7TermCodes[this.value];
return BlackLen7TermCodes[this.Value];
}
case 8:
{
return BlackLen8TermCodes[this.value];
return BlackLen8TermCodes[this.Value];
}
case 9:
{
return BlackLen9TermCodes[this.value];
return BlackLen9TermCodes[this.Value];
}
case 10:
{
return BlackLen10TermCodes[this.value];
return BlackLen10TermCodes[this.Value];
}
case 11:
{
return BlackLen11TermCodes[this.value];
return BlackLen11TermCodes[this.Value];
}
case 12:
{
return BlackLen12TermCodes[this.value];
return BlackLen12TermCodes[this.Value];
}
}
@ -516,41 +550,41 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private uint WhiteMakeupCodeRunLength()
{
switch (this.curValueBitsRead)
switch (this.CurValueBitsRead)
{
case 5:
{
return WhiteLen5MakeupCodes[this.value];
return WhiteLen5MakeupCodes[this.Value];
}
case 6:
{
return WhiteLen6MakeupCodes[this.value];
return WhiteLen6MakeupCodes[this.Value];
}
case 7:
{
return WhiteLen7MakeupCodes[this.value];
return WhiteLen7MakeupCodes[this.Value];
}
case 8:
{
return WhiteLen8MakeupCodes[this.value];
return WhiteLen8MakeupCodes[this.Value];
}
case 9:
{
return WhiteLen9MakeupCodes[this.value];
return WhiteLen9MakeupCodes[this.Value];
}
case 11:
{
return WhiteLen11MakeupCodes[this.value];
return WhiteLen11MakeupCodes[this.Value];
}
case 12:
{
return WhiteLen12MakeupCodes[this.value];
return WhiteLen12MakeupCodes[this.Value];
}
}
@ -559,26 +593,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private uint BlackMakeupCodeRunLength()
{
switch (this.curValueBitsRead)
switch (this.CurValueBitsRead)
{
case 10:
{
return BlackLen10MakeupCodes[this.value];
return BlackLen10MakeupCodes[this.Value];
}
case 11:
{
return BlackLen11MakeupCodes[this.value];
return BlackLen11MakeupCodes[this.Value];
}
case 12:
{
return BlackLen12MakeupCodes[this.value];
return BlackLen12MakeupCodes[this.Value];
}
case 13:
{
return BlackLen13MakeupCodes[this.value];
return BlackLen13MakeupCodes[this.Value];
}
}
@ -597,49 +631,41 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private bool IsWhiteMakeupCode()
{
switch (this.curValueBitsRead)
switch (this.CurValueBitsRead)
{
case 5:
{
return WhiteLen5MakeupCodes.ContainsKey(this.value);
return WhiteLen5MakeupCodes.ContainsKey(this.Value);
}
case 6:
{
return WhiteLen6MakeupCodes.ContainsKey(this.value);
return WhiteLen6MakeupCodes.ContainsKey(this.Value);
}
case 7:
{
return WhiteLen7MakeupCodes.ContainsKey(this.value);
return WhiteLen7MakeupCodes.ContainsKey(this.Value);
}
case 8:
{
return WhiteLen8MakeupCodes.ContainsKey(this.value);
return WhiteLen8MakeupCodes.ContainsKey(this.Value);
}
case 9:
{
return WhiteLen9MakeupCodes.ContainsKey(this.value);
return WhiteLen9MakeupCodes.ContainsKey(this.Value);
}
case 11:
{
return WhiteLen11MakeupCodes.ContainsKey(this.value);
return WhiteLen11MakeupCodes.ContainsKey(this.Value);
}
case 12:
{
if (this.isModifiedHuffmanRle)
{
if (this.value == 1)
{
return true;
}
}
return WhiteLen12MakeupCodes.ContainsKey(this.value);
return WhiteLen12MakeupCodes.ContainsKey(this.Value);
}
}
@ -648,34 +674,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private bool IsBlackMakeupCode()
{
switch (this.curValueBitsRead)
switch (this.CurValueBitsRead)
{
case 10:
{
return BlackLen10MakeupCodes.ContainsKey(this.value);
return BlackLen10MakeupCodes.ContainsKey(this.Value);
}
case 11:
{
if (this.isModifiedHuffmanRle)
{
if (this.value == 0)
{
return true;
}
}
return BlackLen11MakeupCodes.ContainsKey(this.value);
return BlackLen11MakeupCodes.ContainsKey(this.Value);
}
case 12:
{
return BlackLen12MakeupCodes.ContainsKey(this.value);
return BlackLen12MakeupCodes.ContainsKey(this.Value);
}
case 13:
{
return BlackLen13MakeupCodes.ContainsKey(this.value);
return BlackLen13MakeupCodes.ContainsKey(this.Value);
}
}
@ -694,31 +712,31 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private bool IsWhiteTerminatingCode()
{
switch (this.curValueBitsRead)
switch (this.CurValueBitsRead)
{
case 4:
{
return WhiteLen4TermCodes.ContainsKey(this.value);
return WhiteLen4TermCodes.ContainsKey(this.Value);
}
case 5:
{
return WhiteLen5TermCodes.ContainsKey(this.value);
return WhiteLen5TermCodes.ContainsKey(this.Value);
}
case 6:
{
return WhiteLen6TermCodes.ContainsKey(this.value);
return WhiteLen6TermCodes.ContainsKey(this.Value);
}
case 7:
{
return WhiteLen7TermCodes.ContainsKey(this.value);
return WhiteLen7TermCodes.ContainsKey(this.Value);
}
case 8:
{
return WhiteLen8TermCodes.ContainsKey(this.value);
return WhiteLen8TermCodes.ContainsKey(this.Value);
}
}
@ -727,117 +745,90 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private bool IsBlackTerminatingCode()
{
switch (this.curValueBitsRead)
switch (this.CurValueBitsRead)
{
case 2:
{
return BlackLen2TermCodes.ContainsKey(this.value);
return BlackLen2TermCodes.ContainsKey(this.Value);
}
case 3:
{
return BlackLen3TermCodes.ContainsKey(this.value);
return BlackLen3TermCodes.ContainsKey(this.Value);
}
case 4:
{
return BlackLen4TermCodes.ContainsKey(this.value);
return BlackLen4TermCodes.ContainsKey(this.Value);
}
case 5:
{
return BlackLen5TermCodes.ContainsKey(this.value);
return BlackLen5TermCodes.ContainsKey(this.Value);
}
case 6:
{
return BlackLen6TermCodes.ContainsKey(this.value);
return BlackLen6TermCodes.ContainsKey(this.Value);
}
case 7:
{
return BlackLen7TermCodes.ContainsKey(this.value);
return BlackLen7TermCodes.ContainsKey(this.Value);
}
case 8:
{
return BlackLen8TermCodes.ContainsKey(this.value);
return BlackLen8TermCodes.ContainsKey(this.Value);
}
case 9:
{
return BlackLen9TermCodes.ContainsKey(this.value);
return BlackLen9TermCodes.ContainsKey(this.Value);
}
case 10:
{
return BlackLen10TermCodes.ContainsKey(this.value);
return BlackLen10TermCodes.ContainsKey(this.Value);
}
case 11:
{
return BlackLen11TermCodes.ContainsKey(this.value);
return BlackLen11TermCodes.ContainsKey(this.Value);
}
case 12:
{
return BlackLen12TermCodes.ContainsKey(this.value);
return BlackLen12TermCodes.ContainsKey(this.Value);
}
}
return false;
}
private void Reset(bool resetRunLength = true)
{
this.value = 0;
this.curValueBitsRead = 0;
if (resetRunLength)
{
this.RunLength = 0;
}
}
private uint ReadValue(int nBits)
{
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
uint v = 0;
int shift = nBits;
while (shift-- > 0)
{
uint bit = this.GetBit();
v |= bit << shift;
this.curValueBitsRead++;
}
return v;
}
private uint GetBit()
{
if (this.bitsRead >= 8)
if (this.BitsRead >= 8)
{
this.LoadNewByte();
}
Span<byte> dataSpan = this.Data.GetSpan();
int shift = 8 - this.bitsRead - 1;
uint bit = (uint)((dataSpan[(int)this.position] & (1 << shift)) != 0 ? 1 : 0);
this.bitsRead++;
int shift = 8 - this.BitsRead - 1;
uint bit = (uint)((dataSpan[(int)this.Position] & (1 << shift)) != 0 ? 1 : 0);
this.BitsRead++;
return bit;
}
private void LoadNewByte()
{
this.position++;
this.bitsRead = 0;
this.Position++;
this.ResetBitsRead();
if (this.position >= (ulong)this.dataLength)
if (this.Position >= (ulong)this.DataLength)
{
TiffThrowHelper.ThrowImageFormatException("tiff image has invalid t4 compressed data");
TiffThrowHelper.ThrowImageFormatException("tiff image has invalid ccitt compressed data");
}
}

10
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs

@ -31,7 +31,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <param name="bitsPerPixel">The number of bits per pixel.</param>
/// <param name="faxOptions">Fax compression options.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param>
public T4TiffCompression(MemoryAllocator allocator, TiffFillOrder fillOrder, int width, int bitsPerPixel, FaxCompressionOptions faxOptions, TiffPhotometricInterpretation photometricInterpretation)
public T4TiffCompression(
MemoryAllocator allocator,
TiffFillOrder fillOrder,
int width,
int bitsPerPixel,
FaxCompressionOptions faxOptions,
TiffPhotometricInterpretation photometricInterpretation)
: base(allocator, width, bitsPerPixel)
{
this.faxCompressionOptions = faxOptions;
@ -48,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
protected TiffFillOrder FillOrder { get; }
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{
if (this.faxCompressionOptions.HasFlag(FaxCompressionOptions.TwoDimensionalCoding))
{

160
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs

@ -0,0 +1,160 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Bit reader for reading CCITT T6 compressed fax data.
/// See: Facsimile Coding Schemes and Coding Control Functions for Group 4 Facsimile Apparatus, itu-t recommendation t.6
/// </summary>
internal class T6BitReader : T4BitReader
{
private readonly int maxCodeLength = 12;
private static readonly CcittTwoDimensionalCode None = new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.None, 0);
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len1Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b1, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Vertical0, 1) }
};
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len3Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Horizontal, 3) },
{ 0b010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL1, 3) },
{ 0b011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR1, 3) }
};
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len4Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b0001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Pass, 4) }
};
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len6Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b000011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR2, 6) },
{ 0b000010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL2, 6) }
};
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len7Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b0000011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR3, 7) },
{ 0b0000010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL3, 7) },
{ 0b0000001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Extensions2D, 7) },
{ 0b0000000, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Extensions1D, 7) }
};
/// <summary>
/// Initializes a new instance of the <see cref="T6BitReader"/> class.
/// </summary>
/// <param name="input">The compressed input stream.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param>
public T6BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator)
: base(input, fillOrder, bytesToRead, allocator)
{
}
/// <inheritdoc/>
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (this.BitsRead > 0 && this.BitsRead < 7);
/// <summary>
/// Gets or sets the two dimensional code.
/// </summary>
public CcittTwoDimensionalCode Code { get; internal set; }
public bool ReadNextCodeWord()
{
this.Code = None;
this.Reset();
uint value = this.ReadValue(1);
do
{
if (this.CurValueBitsRead > this.maxCodeLength)
{
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: invalid code length read");
}
switch (this.CurValueBitsRead)
{
case 1:
if (Len1Codes.ContainsKey(value))
{
this.Code = Len1Codes[value];
return false;
}
break;
case 3:
if (Len3Codes.ContainsKey(value))
{
this.Code = Len3Codes[value];
return false;
}
break;
case 4:
if (Len4Codes.ContainsKey(value))
{
this.Code = Len4Codes[value];
return false;
}
break;
case 6:
if (Len6Codes.ContainsKey(value))
{
this.Code = Len6Codes[value];
return false;
}
break;
case 7:
if (Len7Codes.ContainsKey(value))
{
this.Code = Len7Codes[value];
return false;
}
break;
}
uint currBit = this.ReadValue(1);
value = (value << 1) | currBit;
}
while (!this.IsEndOfScanLine);
if (this.IsEndOfScanLine)
{
return true;
}
return false;
}
/// <summary>
/// No EOL is expected at the start of a run.
/// </summary>
protected override void ReadEolBeforeFirstData()
{
// Nothing to do here.
}
/// <summary>
/// Swaps the white run to black run an vise versa.
/// </summary>
public void SwapColor() => this.IsWhiteRun = !this.IsWhiteRun;
}
}

253
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs

@ -0,0 +1,253 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed using CCITT T6 compression.
/// </summary>
internal class T6TiffCompression : TiffBaseDecompressor
{
private readonly bool isWhiteZero;
private readonly byte whiteValue;
private readonly byte blackValue;
private readonly int width;
/// <summary>
/// Initializes a new instance of the <see cref="T6TiffCompression" /> class.
/// </summary>
/// <param name="allocator">The memory allocator.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The number of bits per pixel.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param>
public T6TiffCompression(
MemoryAllocator allocator,
TiffFillOrder fillOrder,
int width,
int bitsPerPixel,
TiffPhotometricInterpretation photometricInterpretation)
: base(allocator, width, bitsPerPixel)
{
this.FillOrder = fillOrder;
this.width = width;
this.isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
this.whiteValue = (byte)(this.isWhiteZero ? 0 : 1);
this.blackValue = (byte)(this.isWhiteZero ? 1 : 0);
}
/// <summary>
/// Gets the logical order of bits within a byte.
/// </summary>
private TiffFillOrder FillOrder { get; }
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{
int height = stripHeight;
using System.Buffers.IMemoryOwner<byte> scanLineBuffer = this.Allocator.Allocate<byte>(this.width * 2);
Span<byte> scanLine = scanLineBuffer.GetSpan().Slice(0, this.width);
Span<byte> referenceScanLineSpan = scanLineBuffer.GetSpan().Slice(this.width, this.width);
using var bitReader = new T6BitReader(stream, this.FillOrder, byteCount, this.Allocator);
var referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, this.width);
uint bitsWritten = 0;
for (int y = 0; y < height; y++)
{
scanLine.Fill(0);
Decode2DScanline(bitReader, this.isWhiteZero, referenceScanLine, scanLine);
bitsWritten = this.WriteScanLine(buffer, scanLine, bitsWritten);
scanLine.CopyTo(referenceScanLineSpan);
referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, referenceScanLineSpan);
}
}
private uint WriteScanLine(Span<byte> buffer, Span<byte> scanLine, uint bitsWritten)
{
byte white = (byte)(this.isWhiteZero ? 0 : 255);
for (int i = 0; i < scanLine.Length; i++)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, 1, scanLine[i] == white ? this.whiteValue : this.blackValue);
bitsWritten++;
}
// Write padding bytes, if necessary.
uint pad = 8 - (bitsWritten % 8);
if (pad != 8)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0);
bitsWritten += pad;
}
return bitsWritten;
}
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");
}
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

19
src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs

@ -15,8 +15,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// </summary>
internal abstract class TiffBaseDecompressor : TiffBaseCompression
{
protected TiffBaseDecompressor(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None)
: base(allocator, width, bitsPerPixel, predictor)
/// <summary>
/// Initializes a new instance of the <see cref="TiffBaseDecompressor"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="width">The width of the image.</param>
/// <param name="bitsPerPixel">The bits per pixel.</param>
/// <param name="predictor">The predictor.</param>
protected TiffBaseDecompressor(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None)
: base(memoryAllocator, width, bitsPerPixel, predictor)
{
}
@ -26,8 +33,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// <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, Span<byte> buffer)
public void Decompress(BufferedReadStream stream, uint stripOffset, uint stripByteCount, int stripHeight, Span<byte> buffer)
{
if (stripByteCount > int.MaxValue)
{
@ -35,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
}
stream.Seek(stripOffset, SeekOrigin.Begin);
this.Decompress(stream, (int)stripByteCount, buffer);
this.Decompress(stream, (int)stripByteCount, stripHeight, buffer);
if (stripOffset + stripByteCount < stream.Position)
{
@ -48,7 +56,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// </summary>
/// <param name="stream">The <see cref="Stream" /> to read image data from.</param>
/// <param name="byteCount">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>
protected abstract void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer);
protected abstract void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer);
}
}

7
src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs

@ -29,10 +29,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
Lzw = 3,
/// <summary>
/// Image data is compressed using T4-encoding: CCITT T.4.
/// Image data is compressed using CCITT T.4 fax compression.
/// </summary>
T4 = 4,
/// <summary>
/// Image data is compressed using CCITT T.6 fax compression.
/// </summary>
T6 = 6,
/// <summary>
/// Image data is compressed using modified huffman compression.
/// </summary>

4
src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs

@ -46,6 +46,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new T4TiffCompression(allocator, fillOrder, width, bitsPerPixel, faxOptions, photometricInterpretation);
case TiffDecoderCompressionType.T6:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new T6TiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation);
case TiffDecoderCompressionType.HuffmanRle:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new ModifiedHuffmanTiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation);

2
src/ImageSharp/Formats/Tiff/README.md

@ -45,7 +45,7 @@
|Ccitt1D | Y | Y | |
|PackBits | Y | Y | |
|CcittGroup3Fax | Y | Y | |
|CcittGroup4Fax | | | |
|CcittGroup4Fax | | Y | |
|Lzw | Y | Y | Based on ImageSharp GIF LZW implementation - this code could be modified to be (i) shared, or (ii) optimised for each case |
|Old Jpeg | | | We should not even try to support this |
|Jpeg (Technote 2) | | | |

9
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -317,7 +317,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff
int stripIndex = i;
for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++)
{
decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripBuffers[planeIndex].GetSpan());
decompressor.Decompress(
this.inputStream,
(uint)stripOffsets[stripIndex],
(uint)stripByteCounts[stripIndex],
stripHeight,
stripBuffers[planeIndex].GetSpan());
stripIndex += stripsPerPlane;
}
@ -385,7 +390,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break;
}
decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripBufferSpan);
decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripHeight, stripBufferSpan);
colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight);
}

8
src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

@ -418,6 +418,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break;
}
case TiffCompression.CcittGroup4Fax:
{
options.CompressionType = TiffDecoderCompressionType.T6;
options.FaxCompressionOptions = exifProfile.GetValue(ExifTag.T4Options) != null ? (FaxCompressionOptions)exifProfile.GetValue(ExifTag.T4Options).Value : FaxCompressionOptions.None;
break;
}
case TiffCompression.Ccitt1D:
{
options.CompressionType = TiffDecoderCompressionType.HuffmanRle;

2
tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false);
decompressor.Decompress(stream, 0, (uint)stream.Length, buffer);
decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer);
Assert.Equal(data, buffer);
}

2
tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs

@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
var buffer = new byte[data.Length];
using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false);
decompressor.Decompress(stream, 0, (uint)stream.Length, buffer);
decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer);
Assert.Equal(data, buffer);
}

8
tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs

@ -17,10 +17,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
[InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 5, new byte[] { 10, 15, 20, 25, 30 })]
public void Decompress_ReadsData(byte[] inputData, uint byteCount, byte[] expectedResult)
{
var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData));
var buffer = new byte[expectedResult.Length];
using var memoryStream = new MemoryStream(inputData);
using var stream = new BufferedReadStream(Configuration.Default, memoryStream);
byte[] buffer = new byte[expectedResult.Length];
new NoneTiffCompression(default, default, default).Decompress(stream, 0, byteCount, buffer);
using var decompressor = new NoneTiffCompression(default, default, default);
decompressor.Decompress(stream, 0, byteCount, 1, buffer);
Assert.Equal(expectedResult, buffer);
}

9
tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs

@ -26,11 +26,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
[InlineData(new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA }, new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA })] // Apple PackBits sample
public void Decompress_ReadsData(byte[] inputData, byte[] expectedResult)
{
var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData));
var buffer = new byte[expectedResult.Length];
using var memoryStream = new MemoryStream(inputData);
using var stream = new BufferedReadStream(Configuration.Default, memoryStream);
byte[] buffer = new byte[expectedResult.Length];
using var decompressor = new PackBitsTiffCompression(new ArrayPoolMemoryAllocator(), default, default);
decompressor.Decompress(stream, 0, (uint)inputData.Length, buffer);
decompressor.Decompress(stream, 0, (uint)inputData.Length, 1, buffer);
Assert.Equal(expectedResult, buffer);
}
@ -41,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
{
// arrange
Span<byte> input = inputData.AsSpan();
var compressed = new byte[expectedResult.Length];
byte[] compressed = new byte[expectedResult.Length];
// act
PackBitsWriter.PackBits(input, compressed);

Loading…
Cancel
Save