Browse Source

Add support for decompressing huffman encoded tiffs

pull/1570/head
Brian Popow 5 years ago
parent
commit
8122bed91b
  1. 47
      src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs
  2. 9
      src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs
  3. 7
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs
  4. 6
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs
  5. 5
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
  6. 72
      src/ImageSharp/Formats/Tiff/Compression/TiffModifiedHuffmanCompression.cs
  7. 13
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  8. 6
      src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs
  9. 3
      tests/ImageSharp.Tests/TestImages.cs
  10. 3
      tests/Images/Input/Tiff/Calliphora_huffman_rle.tif

47
src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs

@ -61,6 +61,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// </summary>
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
/// <param name="input">The compressed input stream.</param>
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param>
public T4BitReader(Stream input, int bytesToRead, MemoryAllocator allocator)
/// <param name="isModifiedHuffman">Indicates, if its the modified huffman code variation.</param>
public T4BitReader(Stream input, int bytesToRead, MemoryAllocator allocator, bool isModifiedHuffman = false)
{
this.Data = allocator.Allocate<byte>(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;
}
}
}
/// <inheritdoc/>
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);
}

9
src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs

@ -17,8 +17,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// </summary>
/// <param name="allocator">The memory allocator.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param>
public T4TiffCompression(MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation)
: base(allocator, photometricInterpretation)
/// <param name="width">The image width.</param>
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<byte> buffer, int pos, uint count, int value)
protected void WriteBits(Span<byte> 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<byte> buffer, int bufferPos, int bitPos, int value)
protected void WriteBit(Span<byte> buffer, int bufferPos, int bitPos, int value)
{
buffer[bufferPos] |= (byte)(value << (7 - bitPos));
}

7
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;
/// <summary>
/// Decompresses image data into the supplied buffer.
/// </summary>

6
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));
}

5
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.
/// </summary>
T4 = 4,
/// <summary>
/// Image data is compressed using modified huffman compression.
/// </summary>
HuffmanRle = 5,
}
}

72
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
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed using Modified Huffman Compression.
/// </summary>
internal class TiffModifiedHuffmanCompression : T4TiffCompression
{
/// <summary>
/// Initializes a new instance of the <see cref="TiffModifiedHuffmanCompression" /> class.
/// </summary>
/// <param name="allocator">The memory allocator.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param>
/// <param name="width">The image width.</param>
public TiffModifiedHuffmanCompression(MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation, int width)
: base(allocator, photometricInterpretation, width)
{
}
/// <inheritdoc/>
public override void Decompress(Stream stream, int byteCount, Span<byte> 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;
}
}
}
}
}
}

13
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
/// <param name="rowsPerStrip">The number of rows per strip of data.</param>
/// <param name="stripOffsets">An array of byte offsets to each strip in the image.</param>
/// <param name="stripByteCounts">An array of the size of each strip (in bytes).</param>
private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts)
/// <param name="width">The image width.</param>
private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap);
@ -304,7 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
}
private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts)
private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width)
where TPixel : unmanaged, IPixel<TPixel>
{
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip);
@ -313,7 +314,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Buffer2D<TPixel> 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<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample, this.ColorMap);

6
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);

3
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,*/ };

3
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
Loading…
Cancel
Save