Browse Source

Add support for decoding tiff's encoded with LeastSignificantBitFirst compression

pull/1725/head
Brian Popow 5 years ago
parent
commit
4eb4e54012
  1. 7
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs
  2. 29
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs
  3. 13
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs
  4. 7
      src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
  5. 18
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  6. 5
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  7. 6
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  8. 3
      tests/ImageSharp.Tests/TestImages.cs
  9. 3
      tests/Images/Input/Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff
  10. 3
      tests/Images/Input/Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff
  11. 3
      tests/Images/Input/Tiff/g3test.tiff

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

@ -22,11 +22,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// Initializes a new instance of the <see cref="ModifiedHuffmanTiffCompression" /> 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 ModifiedHuffmanTiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation)
: base(allocator, width, bitsPerPixel, FaxCompressionOptions.None, photometricInterpretation)
public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, TiffFillOrder fillOrder, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation)
: base(allocator, fillOrder, width, bitsPerPixel, FaxCompressionOptions.None, photometricInterpretation)
{
bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
this.whiteValue = (byte)(isWhiteZero ? 0 : 1);
@ -36,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
{
using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding: false, isModifiedHuffman: true);
using var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, this.Allocator, eolPadding: false, isModifiedHuffman: true);
buffer.Clear();
uint bitsWritten = 0;

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

@ -5,7 +5,8 @@ using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
@ -20,6 +21,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary>
private int bitsRead;
/// <summary>
/// The logical order of bits within a byte.
/// </summary>
private readonly TiffFillOrder fillOrder;
/// <summary>
/// Current value.
/// </summary>
@ -221,12 +227,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// Initializes a new instance of the <see cref="T4BitReader" /> 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>
/// <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, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false, bool isModifiedHuffman = false)
public T4BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false, bool isModifiedHuffman = false)
{
this.fillOrder = fillOrder;
this.Data = allocator.Allocate<byte>(bytesToRead);
this.ReadImageDataFromStream(input, bytesToRead);
@ -375,7 +383,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
break;
}
var currBit = this.ReadValue(1);
uint currBit = this.ReadValue(1);
this.value = (this.value << 1) | currBit;
if (this.IsEndOfScanLine)
@ -816,7 +824,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
Span<byte> dataSpan = this.Data.GetSpan();
int shift = 8 - this.bitsRead - 1;
var bit = (uint)((dataSpan[(int)this.position] & (1 << shift)) != 0 ? 1 : 0);
uint bit = (uint)((dataSpan[(int)this.position] & (1 << shift)) != 0 ? 1 : 0);
this.bitsRead++;
return bit;
@ -837,6 +845,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
Span<byte> dataSpan = this.Data.GetSpan();
input.Read(dataSpan, 0, bytesToRead);
if (this.fillOrder == TiffFillOrder.LeastSignificantBitFirst)
{
for (int i = 0; i < dataSpan.Length; i++)
{
dataSpan[i] = ReverseBits(dataSpan[i]);
}
}
}
// http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte ReverseBits(byte b) =>
(byte)((((b * 0x80200802UL) & 0x0884422110UL) * 0x0101010101UL) >> 32);
}
}

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

@ -24,20 +24,27 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// Initializes a new instance of the <see cref="T4TiffCompression" /> 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="faxOptions">Fax compression options.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param>
public T4TiffCompression(MemoryAllocator allocator, 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;
this.FillOrder = fillOrder;
bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
this.whiteValue = (byte)(isWhiteZero ? 0 : 1);
this.blackValue = (byte)(isWhiteZero ? 1 : 0);
}
/// <summary>
/// Gets the logical order of bits within a byte.
/// </summary>
protected TiffFillOrder FillOrder { get; }
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
{
@ -46,8 +53,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
TiffThrowHelper.ThrowNotSupported("TIFF CCITT 2D compression is not yet supported");
}
var eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding);
using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding);
bool eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding);
using var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, this.Allocator, eolPadding);
buffer.Clear();
uint bitsWritten = 0;

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

@ -16,7 +16,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
int width,
int bitsPerPixel,
TiffPredictor predictor,
FaxCompressionOptions faxOptions)
FaxCompressionOptions faxOptions,
TiffFillOrder fillOrder)
{
switch (method)
{
@ -40,11 +41,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
case TiffDecoderCompressionType.T4:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new T4TiffCompression(allocator, width, bitsPerPixel, faxOptions, photometricInterpretation);
return new T4TiffCompression(allocator, fillOrder, width, bitsPerPixel, faxOptions, photometricInterpretation);
case TiffDecoderCompressionType.HuffmanRle:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new ModifiedHuffmanTiffCompression(allocator, width, bitsPerPixel, photometricInterpretation);
return new ModifiedHuffmanTiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation);
default:
throw TiffThrowHelper.NotSupportedDecompressor(nameof(method));

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

@ -85,6 +85,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
public FaxCompressionOptions FaxCompressionOptions { get; set; }
/// <summary>
/// Gets or sets the the logical order of bits within a byte.
/// </summary>
public TiffFillOrder FillOrder { get; set; }
/// <summary>
/// Gets or sets the planar configuration type to use when decoding the image.
/// </summary>
@ -264,7 +269,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff
stripBuffers[stripIndex] = this.memoryAllocator.Allocate<byte>(uncompressedStripSize);
}
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions);
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(
this.CompressionType,
this.memoryAllocator,
this.PhotometricInterpretation,
frame.Width,
bitsPerPixel,
this.Predictor,
this.FaxCompressionOptions,
this.FillOrder);
TiffBasePlanarColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder);
@ -314,7 +327,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
frame.Width,
bitsPerPixel,
this.Predictor,
this.FaxCompressionOptions);
this.FaxCompressionOptions,
this.FillOrder);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder);

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

@ -35,9 +35,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
TiffFillOrder fillOrder = (TiffFillOrder?)exifProfile.GetValue(ExifTag.FillOrder)?.Value ?? TiffFillOrder.MostSignificantBitFirst;
if (fillOrder != TiffFillOrder.MostSignificantBitFirst)
if (fillOrder == TiffFillOrder.LeastSignificantBitFirst && frameMetadata.BitsPerPixel != TiffBitsPerPixel.Bit1)
{
TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is not supported.");
TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is only supported in combination with 1bit per pixel bicolor tiff's.");
}
if (frameMetadata.Predictor == TiffPredictor.FloatingPoint)
@ -69,6 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb;
options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24;
options.BitsPerSample = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0);
options.FillOrder = fillOrder;
options.ParseColorType(exifProfile);
options.ParseCompression(frameMetadata.Compression, exifProfile);

6
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -221,6 +221,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_Fax3Compressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(CcittFax3LowerOrderBitsFirst01, PixelTypes.Rgba32)]
[WithFile(CcittFax3LowerOrderBitsFirst02, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Compressed_LowerOrderBitsFirst<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Calliphora_RgbPackbits, PixelTypes.Rgba32)]
[WithFile(RgbPackbits, PixelTypes.Rgba32)]

3
tests/ImageSharp.Tests/TestImages.cs

@ -539,6 +539,9 @@ namespace SixLabors.ImageSharp.Tests
public const string CcittFax3AllMakeupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tiff";
public const string HuffmanRleAllTermCodes = "Tiff/huffman_rle_all_terminating_codes.tiff";
public const string HuffmanRleAllMakeupCodes = "Tiff/huffman_rle_all_makeup_codes.tiff";
public const string CcittFax3LowerOrderBitsFirst01 = "Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff";
public const string CcittFax3LowerOrderBitsFirst02 = "Tiff/g3test.tiff";
public const string HuffmanRleLowerOrderBitsFirst = "Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff";
// Test case for an issue, that the last bits in a row got ignored.
public const string HuffmanRle_basi3p02 = "Tiff/basi3p02_huffman_rle.tiff";

3
tests/Images/Input/Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5ac3e56a93996464a579ae19cf5f8d9531e2f08db36879aaba176731c24951a5
size 352

3
tests/Images/Input/Tiff/f8179f8f5e566349cf3583a1ff3ea95c.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cf75c4b679d2449e239f228cdee6a25adc7d7b16dde3fb9061a07b2fb0699db1
size 735412

3
tests/Images/Input/Tiff/g3test.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d5b2e1a17338133aa95cb8a16d82a171f5b50f7b9ae1a51ab06227dc3daa81d5
size 50401
Loading…
Cancel
Save