Browse Source

Add support for horizontal predictor for 16 bit gray tiff's

pull/1729/head
Brian Popow 5 years ago
parent
commit
1aea0061eb
  1. 12
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs
  2. 11
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs
  3. 54
      src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
  4. 7
      src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
  5. 13
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  6. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
  7. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
  8. 6
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  9. 2
      tests/ImageSharp.Tests/TestImages.cs
  10. 3
      tests/Images/Input/Tiff/flower-minisblack-16_lsb_lzw_predictor.tiff
  11. 3
      tests/Images/Input/Tiff/flower-minisblack-16_msb_lzw_predictor.tiff

12
src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs

@ -5,7 +5,6 @@ using System;
using System.IO.Compression; using System.IO.Compression;
using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -20,6 +19,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </remarks> /// </remarks>
internal class DeflateTiffCompression : TiffBaseDecompressor internal class DeflateTiffCompression : TiffBaseDecompressor
{ {
private readonly bool isBigEndian;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DeflateTiffCompression" /> class. /// Initializes a new instance of the <see cref="DeflateTiffCompression" /> class.
/// </summary> /// </summary>
@ -27,10 +28,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <param name="width">The image width.</param> /// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The bits used per pixel.</param> /// <param name="bitsPerPixel">The bits used per pixel.</param>
/// <param name="predictor">The tiff predictor used.</param> /// <param name="predictor">The tiff predictor used.</param>
public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor) /// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
: base(memoryAllocator, width, bitsPerPixel, predictor) public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor, bool isBigEndian)
{ : base(memoryAllocator, width, bitsPerPixel, predictor) => this.isBigEndian = isBigEndian;
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
if (this.Predictor == TiffPredictor.Horizontal) if (this.Predictor == TiffPredictor.Horizontal)
{ {
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel); HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel, this.isBigEndian);
} }
} }

11
src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs

@ -13,6 +13,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary> /// </summary>
internal class LzwTiffCompression : TiffBaseDecompressor internal class LzwTiffCompression : TiffBaseDecompressor
{ {
private readonly bool isBigEndian;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LzwTiffCompression" /> class. /// Initializes a new instance of the <see cref="LzwTiffCompression" /> class.
/// </summary> /// </summary>
@ -20,10 +22,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <param name="width">The image width.</param> /// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The bits used per pixel.</param> /// <param name="bitsPerPixel">The bits used per pixel.</param>
/// <param name="predictor">The tiff predictor used.</param> /// <param name="predictor">The tiff predictor used.</param>
public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor) /// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
: base(memoryAllocator, width, bitsPerPixel, predictor) public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor, bool isBigEndian)
{ : base(memoryAllocator, width, bitsPerPixel, predictor) => this.isBigEndian = isBigEndian;
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
if (this.Predictor == TiffPredictor.Horizontal) if (this.Predictor == TiffPredictor.Horizontal)
{ {
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel); HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel, this.isBigEndian);
} }
} }

54
src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs

@ -2,9 +2,10 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers.Binary;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression namespace SixLabors.ImageSharp.Formats.Tiff.Compression
@ -20,12 +21,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// <param name="pixelBytes">Buffer with decompressed pixel data.</param> /// <param name="pixelBytes">Buffer with decompressed pixel data.</param>
/// <param name="width">The width of the image or strip.</param> /// <param name="width">The width of the image or strip.</param>
/// <param name="bitsPerPixel">Bits per pixel.</param> /// <param name="bitsPerPixel">Bits per pixel.</param>
public static void Undo(Span<byte> pixelBytes, int width, int bitsPerPixel) /// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public static void Undo(Span<byte> pixelBytes, int width, int bitsPerPixel, bool isBigEndian)
{ {
if (bitsPerPixel == 8) if (bitsPerPixel == 8)
{ {
Undo8Bit(pixelBytes, width); Undo8Bit(pixelBytes, width);
} }
else if (bitsPerPixel == 16)
{
Undo16Bit(pixelBytes, width, isBigEndian);
}
else if (bitsPerPixel == 24) else if (bitsPerPixel == 24)
{ {
Undo24Bit(pixelBytes, width); Undo24Bit(pixelBytes, width);
@ -110,6 +116,50 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
} }
} }
private static void Undo16Bit(Span<byte> pixelBytes, int width, bool isBigEndian)
{
int rowBytesCount = width * 2;
int height = pixelBytes.Length / rowBytesCount;
if (isBigEndian)
{
for (int y = 0; y < height; y++)
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
ushort pixelValue = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 2);
ushort diff = TiffUtils.ConvertToUShortBigEndian(rowSpan);
pixelValue += diff;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, pixelValue);
offset += 2;
}
}
}
else
{
for (int y = 0; y < height; y++)
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
ushort pixelValue = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 2);
ushort diff = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
pixelValue += diff;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, pixelValue);
offset += 2;
}
}
}
}
private static void Undo24Bit(Span<byte> pixelBytes, int width) private static void Undo24Bit(Span<byte> pixelBytes, int width)
{ {
int rowBytesCount = width * 3; int rowBytesCount = width * 3;

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

@ -16,7 +16,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
int width, int width,
int bitsPerPixel, int bitsPerPixel,
TiffPredictor predictor, TiffPredictor predictor,
FaxCompressionOptions faxOptions) FaxCompressionOptions faxOptions,
ByteOrder byteOrder)
{ {
switch (method) switch (method)
{ {
@ -32,11 +33,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
case TiffDecoderCompressionType.Deflate: case TiffDecoderCompressionType.Deflate:
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
return new DeflateTiffCompression(allocator, width, bitsPerPixel, predictor); return new DeflateTiffCompression(allocator, width, bitsPerPixel, predictor, byteOrder == ByteOrder.BigEndian);
case TiffDecoderCompressionType.Lzw: case TiffDecoderCompressionType.Lzw:
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
return new LzwTiffCompression(allocator, width, bitsPerPixel, predictor); return new LzwTiffCompression(allocator, width, bitsPerPixel, predictor, byteOrder == ByteOrder.BigEndian);
case TiffDecoderCompressionType.T4: case TiffDecoderCompressionType.T4:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");

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

@ -264,7 +264,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff
stripBuffers[stripIndex] = this.memoryAllocator.Allocate<byte>(uncompressedStripSize); 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.byteOrder);
TiffBasePlanarColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder); TiffBasePlanarColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder);
@ -314,7 +322,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
frame.Width, frame.Width,
bitsPerPixel, bitsPerPixel,
this.Predictor, this.Predictor,
this.FaxCompressionOptions); this.FaxCompressionOptions,
this.byteOrder);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.Configuration, this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder); TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.Configuration, this.ColorType, this.BitsPerSample, this.ColorMap, this.byteOrder);

2
tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs

@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
{ {
var buffer = new byte[data.Length]; var buffer = new byte[data.Length];
using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None); using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None, false);
decompressor.Decompress(stream, 0, (uint)stream.Length, buffer); decompressor.Decompress(stream, 0, (uint)stream.Length, buffer);

2
tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs

@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
using BufferedReadStream stream = CreateCompressedStream(data); using BufferedReadStream stream = CreateCompressedStream(data);
var buffer = new byte[data.Length]; var buffer = new byte[data.Length];
using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None); using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None, false);
decompressor.Decompress(stream, 0, (uint)stream.Length, buffer); decompressor.Decompress(stream, 0, (uint)stream.Length, buffer);
Assert.Equal(data, buffer); Assert.Equal(data, buffer);

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

@ -161,6 +161,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_16Bit_Gray<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_16Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower16BitGrayPredictorBigEndian, PixelTypes.Rgba32)]
[WithFile(Flower16BitGrayPredictorLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_16Bit_Gray_WithPredictor<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory] [Theory]
[WithFile(Flower24BitGray, PixelTypes.Rgba32)] [WithFile(Flower24BitGray, PixelTypes.Rgba32)]
[WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)] [WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)]

2
tests/ImageSharp.Tests/TestImages.cs

@ -599,6 +599,8 @@ namespace SixLabors.ImageSharp.Tests
public const string Flower16BitGrayLittleEndian = "Tiff/flower-minisblack-16_lsb.tiff"; public const string Flower16BitGrayLittleEndian = "Tiff/flower-minisblack-16_lsb.tiff";
public const string Flower16BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-16_lsb.tiff"; public const string Flower16BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-16_lsb.tiff";
public const string Flower16BitGrayMinIsWhiteBigEndian = "Tiff/flower-miniswhite-16.tiff"; public const string Flower16BitGrayMinIsWhiteBigEndian = "Tiff/flower-miniswhite-16.tiff";
public const string Flower16BitGrayPredictorBigEndian = "Tiff/flower-minisblack-16_msb_lzw_predictor.tiff";
public const string Flower16BitGrayPredictorLittleEndian = "Tiff/flower-minisblack-16_lsb_lzw_predictor.tiff";
public const string Flower24BitGray = "Tiff/flower-minisblack-24.tiff"; public const string Flower24BitGray = "Tiff/flower-minisblack-24.tiff";
public const string Flower24BitGrayLittleEndian = "Tiff/flower-minisblack-24_lsb.tiff"; public const string Flower24BitGrayLittleEndian = "Tiff/flower-minisblack-24_lsb.tiff";
public const string Flower32BitGray = "Tiff/flower-minisblack-32.tiff"; public const string Flower32BitGray = "Tiff/flower-minisblack-32.tiff";

3
tests/Images/Input/Tiff/flower-minisblack-16_lsb_lzw_predictor.tiff

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

3
tests/Images/Input/Tiff/flower-minisblack-16_msb_lzw_predictor.tiff

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