diff --git a/src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs
index 043e5b313..ed2fad7ed 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs
@@ -61,6 +61,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
///
private bool isStartOfRow;
+ private readonly bool isModifiedHuffmanRle;
+
private readonly int dataLength;
private const int MinCodeLength = 2;
@@ -223,11 +225,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// The compressed input stream.
/// The number of bytes to read from the stream.
/// The memory allocator.
- public T4BitReader(Stream input, int bytesToRead, MemoryAllocator allocator)
+ /// Indicates, if its the modified huffman code variation.
+ public T4BitReader(Stream input, int bytesToRead, MemoryAllocator allocator, bool isModifiedHuffman = false)
{
this.Data = allocator.Allocate(bytesToRead);
this.ReadImageDataFromStream(input, bytesToRead, allocator);
+ this.isModifiedHuffmanRle = isModifiedHuffman;
this.dataLength = bytesToRead;
this.bitsRead = 0;
this.value = 0;
@@ -302,7 +306,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
this.Reset();
- if (this.isFirstScanLine)
+ if (this.isFirstScanLine && !this.isModifiedHuffmanRle)
{
// We expect an EOL before the first data.
this.value = this.ReadValue(12);
@@ -372,9 +376,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
if (this.IsEndOfScanLine)
{
- // Each new row starts with a white run.
- this.isWhiteRun = true;
- this.isStartOfRow = true;
+ this.StartNewRow();
}
}
while (!this.IsEndOfScanLine);
@@ -382,6 +384,25 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
this.isFirstScanLine = false;
}
+ public void StartNewRow()
+ {
+ // Each new row starts with a white run.
+ this.isWhiteRun = true;
+ this.isStartOfRow = true;
+ this.terminationCodeFound = false;
+
+ if (this.isModifiedHuffmanRle)
+ {
+ int pad = 8 - (this.bitsRead % 8);
+ if (pad != 8)
+ {
+ // Skip padding bits, move to next byte.
+ this.position++;
+ this.bitsRead = 0;
+ }
+ }
+ }
+
///
public void Dispose()
{
@@ -601,6 +622,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
case 12:
{
+ if (this.isModifiedHuffmanRle)
+ {
+ if (this.value == 1)
+ {
+ return true;
+ }
+ }
+
return WhiteLen12MakeupCodes.ContainsKey(this.value);
}
}
@@ -619,6 +648,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
case 11:
{
+ if (this.isModifiedHuffmanRle)
+ {
+ if (this.value == 0)
+ {
+ return true;
+ }
+ }
+
return BlackLen11MakeupCodes.ContainsKey(this.value);
}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs
index 8c16cde68..6aeb5af81 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs
@@ -17,8 +17,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
///
/// The memory allocator.
/// The photometric interpretation.
- public T4TiffCompression(MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation)
- : base(allocator, photometricInterpretation)
+ /// The image width.
+ public T4TiffCompression(MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation, int width)
+ : base(allocator, photometricInterpretation, width)
{
}
@@ -63,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
}
}
- private void WriteBits(Span buffer, int pos, uint count, int value)
+ protected void WriteBits(Span buffer, int pos, uint count, int value)
{
int bitPos = pos % 8;
int bufferPos = pos / 8;
@@ -83,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
}
}
- private void WriteBit(Span buffer, int bufferPos, int bitPos, int value)
+ protected void WriteBit(Span buffer, int bufferPos, int bitPos, int value)
{
buffer[bufferPos] |= (byte)(value << (7 - bitPos));
}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs
index 1a2b814fe..fb05a9f25 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs
@@ -16,18 +16,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff
private TiffPhotometricInterpretation photometricInterpretation;
+ private int width;
+
public TiffBaseCompression(MemoryAllocator allocator) => this.allocator = allocator;
- public TiffBaseCompression(MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation)
+ public TiffBaseCompression(MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation, int width)
{
this.allocator = allocator;
this.photometricInterpretation = photometricInterpretation;
+ this.width = width;
}
protected MemoryAllocator Allocator => this.allocator;
protected TiffPhotometricInterpretation PhotometricInterpretation => this.photometricInterpretation;
+ protected int Width => this.width;
+
///
/// Decompresses image data into the supplied buffer.
///
diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs
index e15cc451f..3a0e5e6da 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs
@@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
internal static class TiffCompressionFactory
{
- public static TiffBaseCompression Create(TiffCompressionType compressionType, MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation)
+ public static TiffBaseCompression Create(TiffCompressionType compressionType, MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation, int width)
{
switch (compressionType)
{
@@ -21,7 +21,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case TiffCompressionType.Lzw:
return new LzwTiffCompression(allocator);
case TiffCompressionType.T4:
- return new T4TiffCompression(allocator, photometricInterpretation);
+ return new T4TiffCompression(allocator, photometricInterpretation, width);
+ case TiffCompressionType.HuffmanRle:
+ return new TiffModifiedHuffmanCompression(allocator, photometricInterpretation, width);
default:
throw TiffThrowHelper.NotSupportedCompression(nameof(compressionType));
}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
index 665e4aca2..8a33948f9 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
@@ -32,5 +32,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// Image data is compressed using T4-encoding: CCITT T.4.
///
T4 = 4,
+
+ ///
+ /// Image data is compressed using modified huffman compression.
+ ///
+ HuffmanRle = 5,
}
}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffModifiedHuffmanCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffModifiedHuffmanCompression.cs
new file mode 100644
index 000000000..1201ab66a
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/TiffModifiedHuffmanCompression.cs
@@ -0,0 +1,72 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.IO;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Tiff.Compression
+{
+ ///
+ /// Class to handle cases where TIFF image data is compressed using Modified Huffman Compression.
+ ///
+ internal class TiffModifiedHuffmanCompression : T4TiffCompression
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The memory allocator.
+ /// The photometric interpretation.
+ /// The image width.
+ public TiffModifiedHuffmanCompression(MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation, int width)
+ : base(allocator, photometricInterpretation, width)
+ {
+ }
+
+ ///
+ public override void Decompress(Stream stream, int byteCount, Span buffer)
+ {
+ bool isWhiteZero = this.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
+ int whiteValue = isWhiteZero ? 0 : 1;
+ int blackValue = isWhiteZero ? 1 : 0;
+
+ using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, isModifiedHuffman: true);
+
+ uint bitsWritten = 0;
+ uint pixelsWritten = 0;
+ while (bitReader.HasMoreData)
+ {
+ bitReader.ReadNextRun();
+
+ if (bitReader.RunLength > 0)
+ {
+ if (bitReader.IsWhiteRun)
+ {
+ this.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, whiteValue);
+ bitsWritten += bitReader.RunLength;
+ pixelsWritten += bitReader.RunLength;
+ }
+ else
+ {
+ this.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, blackValue);
+ bitsWritten += bitReader.RunLength;
+ pixelsWritten += bitReader.RunLength;
+ }
+ }
+
+ if (pixelsWritten % this.Width == 0)
+ {
+ bitReader.StartNewRow();
+
+ // Write padding bytes, if necessary.
+ uint pad = 8 - (bitsWritten % 8);
+ if (pad != 8)
+ {
+ this.WriteBits(buffer, (int)bitsWritten, pad, 0);
+ bitsWritten += pad;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
index 5c253d4c8..bbf361e0d 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -210,11 +210,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar)
{
- this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts);
+ this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts, width);
}
else
{
- this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts);
+ this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts, width);
}
return frame;
@@ -258,7 +258,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// The number of rows per strip of data.
/// An array of byte offsets to each strip in the image.
/// An array of the size of each strip (in bytes).
- private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts)
+ /// The image width.
+ private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width)
where TPixel : unmanaged, IPixel
{
int stripsPerPixel = this.BitsPerSample.Length;
@@ -276,7 +277,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
stripBuffers[stripIndex] = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize);
}
- TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation);
+ TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, width);
RgbPlanarTiffColor colorDecoder = TiffColorDecoderFactory.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap);
@@ -304,7 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
}
- private void DecodeStripsChunky(ImageFrame frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts)
+ private void DecodeStripsChunky(ImageFrame frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width)
where TPixel : unmanaged, IPixel
{
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip);
@@ -313,7 +314,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Buffer2D pixels = frame.PixelBuffer;
- TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation);
+ TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, width);
TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create(this.ColorType, this.BitsPerSample, this.ColorMap);
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs
index 5348be8ce..6db776039 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs
@@ -352,6 +352,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break;
}
+ case TiffCompression.Ccitt1D:
+ {
+ options.CompressionType = TiffCompressionType.HuffmanRle;
+ break;
+ }
+
default:
{
TiffThrowHelper.ThrowNotSupported("The specified TIFF compression format is not supported: " + compression);
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index fa9e4e290..7f16f4aa7 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -512,6 +512,7 @@ namespace SixLabors.ImageSharp.Tests
public const string Calliphora_RgbPackbits = "Tiff/Calliphora_rgb_packbits.tiff";
public const string Calliphora_RgbUncompressed = "Tiff/Calliphora_rgb_uncompressed.tiff";
public const string Calliphora_Fax3Compressed = "Tiff/Calliphora_ccitt_fax3.tif";
+ public const string Calliphora_HuffmanCompressed = "Tiff/Calliphora_huffman_rle.tif";
public const string CcittFax3AllTermCodes = "Tiff/ccitt_fax3_all_terminating_codes.tif";
public const string CcittFax3AllMakupCodes = "Tiff/ccitt_fax3_all_makeupcodes_codes.tif";
@@ -545,7 +546,7 @@ namespace SixLabors.ImageSharp.Tests
public const string SampleMetadata = "Tiff/metadata_sample.tiff";
- public static readonly string[] All = { Calliphora_GrayscaleUncompressed, Calliphora_PaletteUncompressed, /*Calliphora_RgbDeflate_Predictor, Calliphora_RgbLzwe_Predictor, */ Calliphora_RgbPackbits, Calliphora_RgbUncompressed, Calliphora_Fax3Compressed, CcittFax3AllTermCodes, CcittFax3AllMakupCodes, GrayscaleDeflateMultistrip, GrayscaleUncompressed, PaletteDeflateMultistrip, PaletteUncompressed, /*RgbDeflate_Predictor,*/ RgbDeflateMultistrip, /*RgbJpeg,*/ /*RgbLzw_Predictor, RgbLzwMultistrip_Predictor,*/ RgbLzw_NoPredictor_Multistrip, RgbLzw_NoPredictor_Multistrip_Motorola, RgbLzw_NoPredictor_Singlestrip_Motorola, RgbPackbits, RgbPackbitsMultistrip, RgbUncompressed, /* MultiframeLzw_Predictor, MultiFrameDifferentVariants, SampleMetadata,*/ SmallRgbDeflate, SmallRgbLzw, };
+ public static readonly string[] All = { Calliphora_GrayscaleUncompressed, Calliphora_PaletteUncompressed, /*Calliphora_RgbDeflate_Predictor, Calliphora_RgbLzwe_Predictor, */ Calliphora_RgbPackbits, Calliphora_RgbUncompressed, Calliphora_HuffmanCompressed, Calliphora_Fax3Compressed, CcittFax3AllTermCodes, CcittFax3AllMakupCodes, GrayscaleDeflateMultistrip, GrayscaleUncompressed, PaletteDeflateMultistrip, PaletteUncompressed, /*RgbDeflate_Predictor,*/ RgbDeflateMultistrip, /*RgbJpeg,*/ /*RgbLzw_Predictor, RgbLzwMultistrip_Predictor,*/ RgbLzw_NoPredictor_Multistrip, RgbLzw_NoPredictor_Multistrip_Motorola, RgbLzw_NoPredictor_Singlestrip_Motorola, RgbPackbits, RgbPackbitsMultistrip, RgbUncompressed, /* MultiframeLzw_Predictor, MultiFrameDifferentVariants, SampleMetadata,*/ SmallRgbDeflate, SmallRgbLzw, };
public static readonly string[] Multiframes = { MultiframeDeflateWithPreview /*MultiframeLzw_Predictor, MultiFrameDifferentSize, MultiframeDifferentSizeTiled, MultiFrameDifferentVariants,*/ };
diff --git a/tests/Images/Input/Tiff/Calliphora_huffman_rle.tif b/tests/Images/Input/Tiff/Calliphora_huffman_rle.tif
new file mode 100644
index 000000000..e0a39d248
--- /dev/null
+++ b/tests/Images/Input/Tiff/Calliphora_huffman_rle.tif
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1a4f687de9925863b1c9f32f53b6c05fb121f9d7b02ff5869113c4745433f10d
+size 124644