Browse Source

Add support for PackBits compression

pull/1570/head
Andrew Wilkinson 9 years ago
parent
commit
c25ba595af
  1. 79
      src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs
  2. 7
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
  3. 9
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  4. 35
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
  5. 4
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderImageTests.cs

79
src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs

@ -0,0 +1,79 @@
// <copyright file="PackBitsTiffCompression.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Tiff
{
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
/// <summary>
/// Class to handle cases where TIFF image data is compressed using PackBits compression.
/// </summary>
internal static class PackBitsTiffCompression
{
/// <summary>
/// Decompresses image data into the supplied buffer.
/// </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="buffer">The output buffer for uncompressed data.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decompress(Stream stream, int byteCount, byte[] buffer)
{
byte[] compressedData = ArrayPool<byte>.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<byte>.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;
}
}
}
}

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

@ -13,6 +13,11 @@ namespace ImageSharp.Formats.Tiff
/// <summary>
/// Image data is stored uncompressed in the TIFF file.
/// </summary>
None = 0
None = 0,
/// <summary>
/// Image data is compressed using PackBits compression.
/// </summary>
PackBits = 1,
}
}

9
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();
}

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

@ -0,0 +1,35 @@
// <copyright file="PackBitsTiffCompressionTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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);
}
}
}

4
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)
{

Loading…
Cancel
Save