diff --git a/src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs
new file mode 100644
index 0000000000..0ac30e8f17
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs
@@ -0,0 +1,79 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Tiff
+{
+ using System;
+ using System.Buffers;
+ using System.IO;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Class to handle cases where TIFF image data is compressed using PackBits compression.
+ ///
+ internal static class PackBitsTiffCompression
+ {
+ ///
+ /// Decompresses image data into the supplied buffer.
+ ///
+ /// The to read image data from.
+ /// The number of bytes to read from the input stream.
+ /// The output buffer for uncompressed data.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decompress(Stream stream, int byteCount, byte[] buffer)
+ {
+ byte[] compressedData = ArrayPool.Shared.Rent(byteCount);
+
+ try
+ {
+ stream.ReadFull(compressedData, byteCount);
+ int compressedOffset = 0;
+ int decompressedOffset = 0;
+
+ while (compressedOffset < byteCount)
+ {
+ byte headerByte = compressedData[compressedOffset];
+
+ if (headerByte <= (byte)127)
+ {
+ int literalOffset = compressedOffset + 1;
+ int literalLength = compressedData[compressedOffset] + 1;
+
+ Array.Copy(compressedData, literalOffset, buffer, decompressedOffset, literalLength);
+
+ compressedOffset += literalLength + 1;
+ decompressedOffset += literalLength;
+ }
+ else if (headerByte == (byte)0x80)
+ {
+ compressedOffset += 1;
+ }
+ else
+ {
+ byte repeatData = compressedData[compressedOffset + 1];
+ int repeatLength = 257 - headerByte;
+
+ ArrayCopyRepeat(repeatData, buffer, decompressedOffset, repeatLength);
+
+ compressedOffset += 2;
+ decompressedOffset += repeatLength;
+ }
+ }
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(compressedData);
+ }
+ }
+
+ private static void ArrayCopyRepeat(byte value, byte[] destinationArray, int destinationIndex, int length)
+ {
+ for (int i = 0; i < length; i++)
+ {
+ destinationArray[i + destinationIndex] = value;
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
index c661ea8947..6f9ce8f870 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
@@ -13,6 +13,11 @@ namespace ImageSharp.Formats.Tiff
///
/// Image data is stored uncompressed in the TIFF file.
///
- None = 0
+ None = 0,
+
+ ///
+ /// Image data is compressed using PackBits compression.
+ ///
+ PackBits = 1,
}
}
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
index cc2d0f8b7d..7c8efad0f4 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -267,6 +267,12 @@ namespace ImageSharp.Formats
break;
}
+ case TiffCompression.PackBits:
+ {
+ this.CompressionType = TiffCompressionType.PackBits;
+ break;
+ }
+
default:
{
throw new NotSupportedException("The specified TIFF compression format is not supported.");
@@ -377,6 +383,9 @@ namespace ImageSharp.Formats
case TiffCompressionType.None:
NoneTiffCompression.Decompress(this.InputStream, (int)byteCount, buffer);
break;
+ case TiffCompressionType.PackBits:
+ PackBitsTiffCompression.Decompress(this.InputStream, (int)byteCount, buffer);
+ break;
default:
throw new InvalidOperationException();
}
diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
new file mode 100644
index 0000000000..85a7bd729f
--- /dev/null
+++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
@@ -0,0 +1,35 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Tests
+{
+ using System.IO;
+ using Xunit;
+
+ using ImageSharp.Formats.Tiff;
+
+ public class PackBitsTiffCompressionTests
+ {
+ [Theory]
+ [InlineData(new byte[] { }, new byte[] { })]
+ [InlineData(new byte[] { 0x00, 0x2A }, new byte[] { 0x2A })] // Read one byte
+ [InlineData(new byte[] { 0x01, 0x15, 0x32 }, new byte[] { 0x15, 0x32 })] // Read two bytes
+ [InlineData(new byte[] { 0xFF, 0x2A }, new byte[] { 0x2A, 0x2A })] // Repeat two bytes
+ [InlineData(new byte[] { 0xFE, 0x2A }, new byte[] { 0x2A, 0x2A, 0x2A })] // Repeat three bytes
+ [InlineData(new byte[] { 0x80 }, new byte[] { })] // Read a 'No operation' byte
+ [InlineData(new byte[] { 0x01, 0x15, 0x32, 0x80, 0xFF, 0xA2 }, new byte[] { 0x15, 0x32, 0xA2, 0xA2 })] // Read two bytes, nop, repeat two bytes
+ [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)
+ {
+ Stream stream = new MemoryStream(inputData);
+ byte[] buffer = new byte[expectedResult.Length];
+
+ PackBitsTiffCompression.Decompress(stream, inputData.Length, buffer);
+
+ Assert.Equal(expectedResult, buffer);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs
index f302c17b30..642cc2c0ed 100644
--- a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs
+++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs
@@ -123,6 +123,8 @@ namespace ImageSharp.Tests
[Theory]
[InlineData(false, TiffCompression.None, TiffCompressionType.None)]
[InlineData(true, TiffCompression.None, TiffCompressionType.None)]
+ [InlineData(false, TiffCompression.PackBits, TiffCompressionType.PackBits)]
+ [InlineData(true, TiffCompression.PackBits, TiffCompressionType.PackBits)]
public void ReadImageFormat_DeterminesCorrectCompressionImplementation(bool isLittleEndian, ushort compression, int compressionType)
{
Stream stream = CreateTiffGenIfd()
@@ -147,7 +149,6 @@ namespace ImageSharp.Tests
[InlineData(false, TiffCompression.Lzw)]
[InlineData(false, TiffCompression.OldDeflate)]
[InlineData(false, TiffCompression.OldJpeg)]
- [InlineData(false, TiffCompression.PackBits)]
[InlineData(false, 999)]
[InlineData(true, TiffCompression.Ccitt1D)]
[InlineData(true, TiffCompression.CcittGroup3Fax)]
@@ -159,7 +160,6 @@ namespace ImageSharp.Tests
[InlineData(true, TiffCompression.Lzw)]
[InlineData(true, TiffCompression.OldDeflate)]
[InlineData(true, TiffCompression.OldJpeg)]
- [InlineData(true, TiffCompression.PackBits)]
[InlineData(true, 999)]
public void ReadImageFormat_ThrowsExceptionForUnsupportedCompression(bool isLittleEndian, ushort compression)
{