Browse Source

Merge pull request #1537 from IldarKhayrutdinov/tiff-format

#12 Support multi strip encoding. Improve performance and memory usage.
pull/1570/head
Brian Popow 5 years ago
committed by GitHub
parent
commit
2ec4787e7b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  2. 38
      src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs
  3. 63
      src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs
  4. 40
      src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs
  5. 32
      src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs
  6. 47
      src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs
  7. 88
      src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs
  8. 52
      src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs
  9. 8
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs
  10. 8
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs
  11. 15
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs
  12. 13
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs
  13. 21
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs
  14. 28
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs
  15. 2
      src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs
  16. 56
      src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
  17. 62
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs
  18. 48
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs
  19. 32
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompresor.cs
  20. 58
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs
  21. 2
      src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs
  22. 25
      src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
  23. 5
      src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
  24. 14
      src/ImageSharp/Formats/Tiff/README.md
  25. 6
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  26. 7
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  27. 3
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  28. 135
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  29. 5
      src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs
  30. 104
      src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter.cs
  31. 227
      src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter.cs
  32. 19
      src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs
  33. 46
      src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter.cs
  34. 165
      src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter.cs
  35. 215
      src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter.cs
  36. 168
      src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter.cs
  37. 1
      src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs
  38. 12
      tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs
  39. 18
      tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs
  40. 2
      tests/ImageSharp.Benchmarks/Config.cs
  41. 4
      tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
  42. 13
      tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
  43. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs
  44. 3
      tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
  45. 1
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs
  46. 149
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  47. 2
      tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs
  48. 87
      tests/Images/Input/Tiff/Benchmarks/SixLabors.ImageSharp.Benchmarks.Codecs.DecodeTiffBig-report-github.md
  49. 81
      tests/Images/Input/Tiff/Benchmarks/SixLabors.ImageSharp.Benchmarks.Codecs.DecodeTiffBig-report.html

1
src/ImageSharp/Formats/ImageExtensions.Save.cs

@ -13,7 +13,6 @@ using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
namespace SixLabors.ImageSharp
{

38
src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs

@ -14,34 +14,38 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
int startIdx = bufferPos + bitPos;
int endIdx = (int)(startIdx + count);
for (int i = startIdx; i < endIdx; i++)
if (value == 1)
{
if (value == 1)
for (int i = startIdx; i < endIdx; i++)
{
WriteBit(buffer, bufferPos, bitPos);
bitPos++;
if (bitPos >= 8)
{
bitPos = 0;
bufferPos++;
}
}
else
}
else
{
for (int i = startIdx; i < endIdx; i++)
{
WriteZeroBit(buffer, bufferPos, bitPos);
}
bitPos++;
if (bitPos >= 8)
{
bitPos = 0;
bufferPos++;
bitPos++;
if (bitPos >= 8)
{
bitPos = 0;
bufferPos++;
}
}
}
}
public static void WriteBit(Span<byte> buffer, int bufferPos, int bitPos)
{
buffer[bufferPos] |= (byte)(1 << (7 - bitPos));
}
public static void WriteBit(Span<byte> buffer, int bufferPos, int bitPos) => buffer[bufferPos] |= (byte)(1 << (7 - bitPos));
public static void WriteZeroBit(Span<byte> buffer, int bufferPos, int bitPos)
{
buffer[bufferPos] = (byte)(buffer[bufferPos] & ~(1 << (7 - bitPos)));
}
public static void WriteZeroBit(Span<byte> buffer, int bufferPos, int bitPos) => buffer[bufferPos] = (byte)(buffer[bufferPos] & ~(1 << (7 - bitPos)));
}
}

63
src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs

@ -0,0 +1,63 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors
{
internal class DeflateCompressor : TiffBaseCompressor
{
private readonly DeflateCompressionLevel compressionLevel;
private readonly MemoryStream memoryStream = new MemoryStream();
public DeflateCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor, DeflateCompressionLevel compressionLevel)
: base(output, allocator, width, bitsPerPixel, predictor)
=> this.compressionLevel = compressionLevel;
/// <inheritdoc/>
public override TiffEncoderCompression Method => TiffEncoderCompression.Deflate;
/// <inheritdoc/>
public override void Initialize(int rowsPerStrip)
{
}
/// <inheritdoc/>
public override void CompressStrip(Span<byte> rows, int height)
{
this.memoryStream.Seek(0, SeekOrigin.Begin);
using var stream = new ZlibDeflateStream(this.Allocator, this.memoryStream, this.compressionLevel);
if (this.Predictor == TiffPredictor.Horizontal)
{
HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel);
}
stream.Write(rows);
stream.Flush();
stream.Dispose();
int size = (int)this.memoryStream.Position;
#if !NETSTANDARD1_3
byte[] buffer = this.memoryStream.GetBuffer();
this.Output.Write(buffer, 0, size);
#else
this.memoryStream.SetLength(size);
this.memoryStream.Position = 0;
this.memoryStream.CopyTo(this.Output);
#endif
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

40
src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs

@ -0,0 +1,40 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors
{
internal class LzwCompressor : TiffBaseCompressor
{
private TiffLzwEncoder lzwEncoder;
public LzwCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor)
: base(output, allocator, width, bitsPerPixel, predictor)
{
}
/// <inheritdoc/>
public override TiffEncoderCompression Method => TiffEncoderCompression.Lzw;
/// <inheritdoc/>
public override void Initialize(int rowsPerStrip) => this.lzwEncoder = new TiffLzwEncoder(this.Allocator);
/// <inheritdoc/>
public override void CompressStrip(Span<byte> rows, int height)
{
if (this.Predictor == TiffPredictor.Horizontal)
{
HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel);
}
this.lzwEncoder.Encode(rows, this.Output);
}
/// <inheritdoc/>
protected override void Dispose(bool disposing) => this.lzwEncoder?.Dispose();
}
}

32
src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs

@ -0,0 +1,32 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors
{
internal class NoCompressor : TiffBaseCompressor
{
public NoCompressor(Stream output)
: base(output, default, default, default)
{
}
/// <inheritdoc/>
public override TiffEncoderCompression Method => TiffEncoderCompression.None;
/// <inheritdoc/>
public override void Initialize(int rowsPerStrip)
{
}
/// <inheritdoc/>
public override void CompressStrip(Span<byte> rows, int height) => this.Output.Write(rows);
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

47
src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs

@ -0,0 +1,47 @@
// 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.Experimental.Tiff.Compression.Compressors
{
internal class PackBitsCompressor : TiffBaseCompressor
{
private IManagedByteBuffer pixelData;
public PackBitsCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel)
: base(output, allocator, width, bitsPerPixel)
{
}
/// <inheritdoc/>
public override TiffEncoderCompression Method => TiffEncoderCompression.PackBits;
/// <inheritdoc/>
public override void Initialize(int rowsPerStrip)
{
int additionalBytes = ((this.BytesPerRow + 126) / 127) + 1;
this.pixelData = this.Allocator.AllocateManagedByteBuffer(this.BytesPerRow + additionalBytes);
}
/// <inheritdoc/>
public override void CompressStrip(Span<byte> rows, int height)
{
DebugGuard.IsTrue(rows.Length % height == 0, "Invalid height");
DebugGuard.IsTrue(this.BytesPerRow == rows.Length / height, "The widths must match");
Span<byte> span = this.pixelData.GetSpan();
for (int i = 0; i < height; i++)
{
Span<byte> row = rows.Slice(i * this.BytesPerRow, this.BytesPerRow);
int size = PackBitsWriter.PackBits(row, span);
this.Output.Write(span.Slice(0, size));
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing) => this.pixelData?.Dispose();
}
}

88
src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitWriter.cs → src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs

@ -5,16 +5,14 @@ using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors
{
/// <summary>
/// Bitwriter for writing compressed CCITT T4 1D data.
/// </summary>
internal class T4BitWriter
internal class T4BitCompressor : TiffBaseCompressor
{
private const uint WhiteZeroRunTermCode = 0x35;
@ -176,49 +174,52 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors
{ 1408, 0x54 }, { 1472, 0x55 }, { 1536, 0x5A }, { 1600, 0x5B }, { 1664, 0x64 }, { 1728, 0x65 }
};
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The modified huffman is basically the same as CCITT T4, but without EOL markers and padding at the end of the rows.
/// </summary>
private readonly bool useModifiedHuffman;
private readonly Configuration configuration;
private IMemoryOwner<byte> compressedDataBuffer;
private int bytePosition;
private byte bitPosition;
/// <summary>
/// The modified huffman is basically the same as CCITT T4, but without EOL markers and padding at the end of the rows.
/// </summary>
private readonly bool useModifiedHuffman;
/// <summary>
/// Initializes a new instance of the <see cref="T4BitWriter" /> class.
/// Initializes a new instance of the <see cref="T4BitCompressor" /> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="output">The output.</param>
/// <param name="allocator">The allocator.</param>
/// <param name="width">The width.</param>
/// <param name="bitsPerPixel">The bits per pixel.</param>
/// <param name="useModifiedHuffman">Indicates if the modified huffman RLE should be used.</param>
public T4BitWriter(MemoryAllocator memoryAllocator, Configuration configuration, bool useModifiedHuffman = false)
public T4BitCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, bool useModifiedHuffman = false)
: base(output, allocator, width, bitsPerPixel)
{
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
this.bytePosition = 0;
this.bitPosition = 0;
this.useModifiedHuffman = useModifiedHuffman;
}
/// <summary>
/// Writes a image compressed with CCITT T4 to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream. This has to be a bi-color image.</param>
/// <param name="pixelRowAsGray">A span for converting a pixel row to gray.</param>
/// <param name="stream">The stream to write to.</param>
/// <returns>The number of bytes written to the stream.</returns>
public int CompressImage<TPixel>(Image<TPixel> image, Span<L8> pixelRowAsGray, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
public override TiffEncoderCompression Method => this.useModifiedHuffman ? TiffEncoderCompression.ModifiedHuffman : TiffEncoderCompression.CcittGroup3Fax;
public override void Initialize(int rowsPerStrip)
{
// This is too much memory allocated, but just 1 bit per pixel will not do, if the compression rate is not good.
int maxNeededBytes = image.Width * image.Height;
IMemoryOwner<byte> compressedDataBuffer = this.memoryAllocator.Allocate<byte>(maxNeededBytes, AllocationOptions.Clean);
Span<byte> compressedData = compressedDataBuffer.GetSpan();
int maxNeededBytes = this.Width * rowsPerStrip;
this.compressedDataBuffer = this.Allocator.Allocate<byte>(maxNeededBytes);
}
/// <summary>Writes a image compressed with CCITT T4 to the stream.</summary>
/// <param name="pixelsAsGray">The pixels as 8-bit gray array.</param>
/// <param name="height">The strip height.</param>
public override void CompressStrip(Span<byte> pixelsAsGray, int height)
{
DebugGuard.IsTrue(pixelsAsGray.Length / height == this.Width, "Values must be equals");
DebugGuard.IsTrue(pixelsAsGray.Length % height == 0, "Values must be equals");
this.compressedDataBuffer.Clear();
Span<byte> compressedData = this.compressedDataBuffer.GetSpan();
this.bytePosition = 0;
this.bitPosition = 0;
@ -229,36 +230,35 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors
this.WriteCode(12, 1, compressedData);
}
uint pixelsWritten = 0;
for (int y = 0; y < image.Height; y++)
for (int y = 0; y < height; y++)
{
bool isWhiteRun = true;
bool isStartOrRow = true;
Span<TPixel> pixelRow = image.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8(this.configuration, pixelRow, pixelRowAsGray);
int x = 0;
while (x < image.Width)
Span<byte> row = pixelsAsGray.Slice(y * this.Width, this.Width);
while (x < this.Width)
{
uint runLength = 0;
for (int i = x; i < image.Width; i++)
for (int i = x; i < this.Width; i++)
{
if (isWhiteRun && pixelRowAsGray[i].PackedValue != 255)
if (isWhiteRun && row[i] != 255)
{
break;
}
if (isWhiteRun && pixelRowAsGray[i].PackedValue == 255)
if (isWhiteRun && row[i] == 255)
{
runLength++;
continue;
}
if (!isWhiteRun && pixelRowAsGray[i].PackedValue != 0)
if (!isWhiteRun && row[i] != 0)
{
break;
}
if (!isWhiteRun && pixelRowAsGray[i].PackedValue == 0)
if (!isWhiteRun && row[i] == 0)
{
runLength++;
}
@ -280,7 +280,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors
code = this.GetTermCode(runLength, out codeLength, isWhiteRun);
this.WriteCode(codeLength, code, compressedData);
x += (int)runLength;
pixelsWritten += runLength;
}
else
{
@ -288,10 +287,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors
code = this.GetMakeupCode(runLength, out codeLength, isWhiteRun);
this.WriteCode(codeLength, code, compressedData);
x += (int)runLength;
pixelsWritten += runLength;
// If we are at the end of the line with a makeup code, we need to write a final term code with a length of zero.
if (x == image.Width)
if (x == this.Width)
{
if (isWhiteRun)
{
@ -315,11 +313,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors
// Write the compressed data to the stream.
int bytesToWrite = this.bitPosition != 0 ? this.bytePosition + 1 : this.bytePosition;
stream.Write(compressedData.Slice(0, bytesToWrite));
return bytesToWrite;
this.Output.Write(compressedData.Slice(0, bytesToWrite));
}
protected override void Dispose(bool disposing) => this.compressedDataBuffer?.Dispose();
private void WriteEndOfLine(Span<byte> compressedData)
{
if (this.useModifiedHuffman)

52
src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs

@ -7,7 +7,7 @@ using System.IO;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors
{
/*
This implementation is a port of a java tiff encoder by Harald Kuhr: https://github.com/haraldk/TwelveMonkeys
@ -71,8 +71,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
private static readonly int TableSize = 1 << MaxBits;
private readonly IMemoryOwner<byte> data;
// A child is made up of a parent (or prefix) code plus a suffix byte
// and siblings are strings with a common parent(or prefix) and different suffix bytes.
private readonly IMemoryOwner<int> children;
@ -95,31 +93,27 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// <summary>
/// Initializes a new instance of the <see cref="TiffLzwEncoder"/> class.
/// </summary>
/// <param name="data">The data to compress.</param>
/// <param name="memoryAllocator">The memory allocator.</param>
public TiffLzwEncoder(MemoryAllocator memoryAllocator, IMemoryOwner<byte> data)
public TiffLzwEncoder(MemoryAllocator memoryAllocator)
{
this.data = data;
this.children = memoryAllocator.Allocate<int>(TableSize, AllocationOptions.Clean);
this.siblings = memoryAllocator.Allocate<int>(TableSize, AllocationOptions.Clean);
this.suffixes = memoryAllocator.Allocate<int>(TableSize, AllocationOptions.Clean);
this.parent = -1;
this.bitsPerCode = MinBits;
this.nextValidCode = EoiCode + 1;
this.maxCode = (1 << this.bitsPerCode) - 1;
this.children = memoryAllocator.Allocate<int>(TableSize);
this.siblings = memoryAllocator.Allocate<int>(TableSize);
this.suffixes = memoryAllocator.Allocate<int>(TableSize);
}
/// <summary>
/// Encodes and compresses the indexed pixels to the stream.
/// </summary>
/// <param name="data">The data to compress.</param>
/// <param name="stream">The stream to write to.</param>
public void Encode(Stream stream)
public void Encode(Span<byte> data, Stream stream)
{
this.Reset();
Span<int> childrenSpan = this.children.GetSpan();
Span<int> suffixesSpan = this.suffixes.GetSpan();
Span<int> siblingsSpan = this.siblings.GetSpan();
int length = this.data.Length();
int length = data.Length;
if (length == 0)
{
@ -130,12 +124,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
{
// Init stream.
this.WriteCode(stream, ClearCode);
this.parent = this.ReadNextByte() & 0xff;
this.parent = this.ReadNextByte(data);
}
while (this.bufferPosition < this.data.Length())
while (this.bufferPosition < data.Length)
{
int value = this.ReadNextByte() & 0xff;
int value = this.ReadNextByte(data);
int child = childrenSpan[this.parent];
if (child > 0)
@ -206,14 +200,24 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
this.suffixes.Dispose();
}
private byte ReadNextByte()
private void Reset()
{
Span<byte> dataSpan = this.data.GetSpan();
var nextByte = dataSpan[this.bufferPosition];
this.bufferPosition++;
return nextByte;
this.children.Clear();
this.siblings.Clear();
this.suffixes.Clear();
this.parent = -1;
this.bitsPerCode = MinBits;
this.nextValidCode = EoiCode + 1;
this.maxCode = (1 << this.bitsPerCode) - 1;
this.bits = 0;
this.bitPos = 0;
this.bufferPosition = 0;
}
private byte ReadNextByte(Span<byte> data) => data[this.bufferPosition++];
private void IncreaseCodeSizeOrResetIfNeeded(Stream stream)
{
if (this.nextValidCode > this.maxCode)

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

@ -6,7 +6,6 @@ using System.IO.Compression;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -18,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
/// <remarks>
/// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type.
/// </remarks>
internal class DeflateTiffCompression : TiffBaseCompression
internal class DeflateTiffCompression : TiffBaseDecompresor
{
/// <summary>
/// Initializes a new instance of the <see cref="DeflateTiffCompression" /> class.
@ -54,5 +53,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel);
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

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

@ -4,7 +4,6 @@
using System;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -13,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
/// <summary>
/// Class to handle cases where TIFF image data is compressed using LZW compression.
/// </summary>
internal class LzwTiffCompression : TiffBaseCompression
internal class LzwTiffCompression : TiffBaseDecompresor
{
/// <summary>
/// Initializes a new instance of the <see cref="LzwTiffCompression" /> class.
@ -38,5 +37,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel);
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

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

@ -14,6 +14,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
/// </summary>
internal class ModifiedHuffmanTiffCompression : T4TiffCompression
{
private readonly byte whiteValue;
private readonly byte blackValue;
/// <summary>
/// Initializes a new instance of the <see cref="ModifiedHuffmanTiffCompression" /> class.
/// </summary>
@ -23,15 +27,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation, int width)
: base(allocator, FaxCompressionOptions.None, photometricInterpretation, width)
{
bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
this.whiteValue = (byte)(isWhiteZero ? 0 : 1);
this.blackValue = (byte)(isWhiteZero ? 1 : 0);
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
{
bool isWhiteZero = this.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
byte whiteValue = (byte)(isWhiteZero ? 0 : 1);
byte blackValue = (byte)(isWhiteZero ? 1 : 0);
using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding: false, isModifiedHuffman: true);
buffer.Clear();
@ -45,13 +48,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
{
if (bitReader.IsWhiteRun)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, whiteValue);
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue);
bitsWritten += bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
}
else
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, blackValue);
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue);
bitsWritten += bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
}

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

@ -4,25 +4,28 @@
using System;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors
{
/// <summary>
/// Class to handle cases where TIFF image data is not compressed.
/// </summary>
internal class NoneTiffCompression : TiffBaseCompression
internal class NoneTiffCompression : TiffBaseDecompresor
{
/// <summary>
/// Initializes a new instance of the <see cref="NoneTiffCompression" /> class.
/// </summary>
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
public NoneTiffCompression(MemoryAllocator memoryAllocator)
: base(memoryAllocator)
public NoneTiffCompression()
: base(default, default, default)
{
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) => _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount));
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

21
src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs

@ -12,23 +12,33 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
/// <summary>
/// Class to handle cases where TIFF image data is compressed using PackBits compression.
/// </summary>
internal class PackBitsTiffCompression : TiffBaseCompression
internal class PackBitsTiffCompression : TiffBaseDecompresor
{
private IMemoryOwner<byte> compressedDataMemory;
/// <summary>
/// Initializes a new instance of the <see cref="PackBitsTiffCompression" /> class.
/// </summary>
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
public PackBitsTiffCompression(MemoryAllocator memoryAllocator)
: base(memoryAllocator)
: base(memoryAllocator, default, default)
{
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
{
using IMemoryOwner<byte> compressedDataMemory = this.Allocator.Allocate<byte>(byteCount);
if (this.compressedDataMemory == null)
{
this.compressedDataMemory = this.Allocator.Allocate<byte>(byteCount);
}
else if (this.compressedDataMemory.Length() < byteCount)
{
this.compressedDataMemory.Dispose();
this.compressedDataMemory = this.Allocator.Allocate<byte>(byteCount);
}
Span<byte> compressedData = compressedDataMemory.GetSpan();
Span<byte> compressedData = this.compressedDataMemory.GetSpan();
stream.Read(compressedData, 0, byteCount);
int compressedOffset = 0;
@ -77,5 +87,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
destinationArray[i + destinationIndex] = value;
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing) => this.compressedDataMemory?.Dispose();
}
}

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

@ -12,10 +12,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
/// <summary>
/// Class to handle cases where TIFF image data is compressed using CCITT T4 compression.
/// </summary>
internal class T4TiffCompression : TiffBaseCompression
internal class T4TiffCompression : TiffBaseDecompresor
{
private readonly FaxCompressionOptions faxCompressionOptions;
private readonly byte whiteValue;
private readonly byte blackValue;
/// <summary>
/// Initializes a new instance of the <see cref="T4TiffCompression" /> class.
/// </summary>
@ -24,7 +28,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
/// <param name="photometricInterpretation">The photometric interpretation.</param>
/// <param name="width">The image width.</param>
public T4TiffCompression(MemoryAllocator allocator, FaxCompressionOptions faxOptions, TiffPhotometricInterpretation photometricInterpretation, int width)
: base(allocator, photometricInterpretation, width) => this.faxCompressionOptions = faxOptions;
: base(allocator, width, default)
{
this.faxCompressionOptions = faxOptions;
bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
this.whiteValue = (byte)(isWhiteZero ? 0 : 1);
this.blackValue = (byte)(isWhiteZero ? 1 : 0);
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
@ -34,10 +45,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
TiffThrowHelper.ThrowNotSupported("TIFF CCITT 2D compression is not yet supported");
}
bool isWhiteZero = this.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
byte whiteValue = (byte)(isWhiteZero ? 0 : 1);
byte blackValue = (byte)(isWhiteZero ? 1 : 0);
var eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding);
using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding);
@ -51,12 +58,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
{
if (bitReader.IsWhiteRun)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, whiteValue);
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue);
bitsWritten += bitReader.RunLength;
}
else
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, blackValue);
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue);
bitsWritten += bitReader.RunLength;
}
}
@ -73,5 +80,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
}
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

2
src/ImageSharp/Formats/Tiff/Compression/Decompressors/FaxCompressionOptions.cs → src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs

@ -3,7 +3,7 @@
using System;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
{
/// <summary>
/// Fax compression options, see TIFF spec page 51f (T4Options).

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

@ -7,7 +7,7 @@ using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
{
/// <summary>
/// Methods for undoing the horizontal prediction used in combination with deflate and LZW compressed TIFF images.
@ -32,38 +32,64 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
}
}
public static void ApplyHorizontalPrediction(Span<byte> rows, int width, int bitsPerPixel)
{
if (bitsPerPixel == 8)
{
ApplyHorizontalPrediction8Bit(rows, width);
}
else if (bitsPerPixel == 24)
{
ApplyHorizontalPrediction24Bit(rows, width);
}
}
/// <summary>
/// Applies a horizontal predictor to the rgb row.
/// Make use of the fact that many continuous-tone images rarely vary much in pixel value from one pixel to the next.
/// In such images, if we replace the pixel values by differences between consecutive pixels, many of the differences should be 0, plus
/// or minus 1, and so on.This reduces the apparent information content and allows LZW to encode the data more compactly.
/// </summary>
/// <param name="rowSpan">The rgb pixel row.</param>
/// <param name="rows">The rgb pixel rows.</param>
/// <param name="width">The width.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ApplyHorizontalPrediction24Bit(Span<byte> rowSpan)
private static void ApplyHorizontalPrediction24Bit(Span<byte> rows, int width)
{
Span<Rgb24> rowRgb = MemoryMarshal.Cast<byte, Rgb24>(rowSpan);
for (int x = rowRgb.Length - 1; x >= 1; x--)
DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals");
int height = rows.Length / width;
for (int y = 0; y < height; y++)
{
byte r = (byte)(rowRgb[x].R - rowRgb[x - 1].R);
byte g = (byte)(rowRgb[x].G - rowRgb[x - 1].G);
byte b = (byte)(rowRgb[x].B - rowRgb[x - 1].B);
var rgb = new Rgb24(r, g, b);
rowRgb[x].FromRgb24(rgb);
Span<byte> rowSpan = rows.Slice(y * width, width);
Span<Rgb24> rowRgb = MemoryMarshal.Cast<byte, Rgb24>(rowSpan);
for (int x = rowRgb.Length - 1; x >= 1; x--)
{
byte r = (byte)(rowRgb[x].R - rowRgb[x - 1].R);
byte g = (byte)(rowRgb[x].G - rowRgb[x - 1].G);
byte b = (byte)(rowRgb[x].B - rowRgb[x - 1].B);
var rgb = new Rgb24(r, g, b);
rowRgb[x].FromRgb24(rgb);
}
}
}
/// <summary>
/// Applies a horizontal predictor to a gray pixel row.
/// </summary>
/// <param name="rowSpan">The gray pixel row.</param>
/// <param name="rows">The gray pixel rows.</param>
/// <param name="width">The width.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void ApplyHorizontalPrediction8Bit(Span<byte> rowSpan)
private static void ApplyHorizontalPrediction8Bit(Span<byte> rows, int width)
{
for (int x = rowSpan.Length - 1; x >= 1; x--)
DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals");
int height = rows.Length / width;
for (int y = 0; y < height; y++)
{
rowSpan[x] -= rowSpan[x - 1];
Span<byte> rowSpan = rows.Slice(y * width, width);
for (int x = rowSpan.Length - 1; x >= 1; x--)
{
rowSpan[x] -= rowSpan[x - 1];
}
}
}

62
src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs

@ -0,0 +1,62 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
{
internal abstract class TiffBaseCompression : IDisposable
{
private bool isDisposed;
protected TiffBaseCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None)
{
this.Allocator = allocator;
this.Width = width;
this.BitsPerPixel = bitsPerPixel;
this.Predictor = predictor;
this.BytesPerRow = ((width * bitsPerPixel) + 7) / 8;
}
/// <summary>
/// Gets the image width.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the bits per pixel.
/// </summary>
public int BitsPerPixel { get; }
/// <summary>
/// Gets the bytes per row.
/// </summary>
public int BytesPerRow { get; }
/// <summary>
/// Gets the predictor to use. Should only be used with deflate or lzw compression.
/// </summary>
public TiffPredictor Predictor { get; }
/// <summary>
/// Gets the memory allocator.
/// </summary>
protected MemoryAllocator Allocator { get; }
/// <inheritdoc />
public void Dispose()
{
if (this.isDisposed)
{
return;
}
this.isDisposed = true;
this.Dispose(true);
}
protected abstract void Dispose(bool disposing);
}
}

48
src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs

@ -0,0 +1,48 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
{
internal abstract class TiffBaseCompressor : TiffBaseCompression
{
/// <summary>
/// Initializes a new instance of the <see cref="TiffBaseCompressor"/> class.
/// </summary>
/// <param name="output">The output stream to write the compressed image to.</param>
/// <param name="allocator">The memory allocator.</param>
/// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">Bits per pixel.</param>
/// <param name="predictor">The predictor to use (should only be used with deflate or lzw compression). Defaults to none.</param>
protected TiffBaseCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None)
: base(allocator, width, bitsPerPixel, predictor)
=> this.Output = output;
/// <summary>
/// Gets the compression method to use.
/// </summary>
public abstract TiffEncoderCompression Method { get; }
/// <summary>
/// Gets the output stream to write the compressed image to.
/// </summary>
public Stream Output { get; }
/// <summary>
/// Does any initialization required for the compression.
/// </summary>
/// <param name="rowsPerStrip">The number of rows per strip.</param>
public abstract void Initialize(int rowsPerStrip);
/// <summary>
/// Compresses a strip of the image.
/// </summary>
/// <param name="rows">Image rows to compress.</param>
/// <param name="height">Image height.</param>
public abstract void CompressStrip(Span<byte> rows, int height);
}
}

32
src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffBaseCompression.cs → src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompresor.cs

@ -8,40 +8,18 @@ using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
{
/// <summary>
/// Base tiff decompressor class.
/// The base tiff decompressor class.
/// </summary>
internal abstract class TiffBaseCompression
internal abstract class TiffBaseDecompresor : TiffBaseCompression
{
protected TiffBaseCompression(MemoryAllocator allocator) => this.Allocator = allocator;
protected TiffBaseCompression(MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation, int width)
: this(allocator)
protected TiffBaseDecompresor(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None)
: base(allocator, width, bitsPerPixel, predictor)
{
this.PhotometricInterpretation = photometricInterpretation;
this.Width = width;
}
protected TiffBaseCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor)
: this(allocator)
{
this.Width = width;
this.BitsPerPixel = bitsPerPixel;
this.Predictor = predictor;
}
protected MemoryAllocator Allocator { get; }
protected TiffPhotometricInterpretation PhotometricInterpretation { get; }
protected int Width { get; }
protected int BitsPerPixel { get; }
protected TiffPredictor Predictor { get; }
/// <summary>
/// Decompresses image data into the supplied buffer.
/// </summary>

58
src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs

@ -0,0 +1,58 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
{
internal static class TiffCompressorFactory
{
public static TiffBaseCompressor Create(
TiffEncoderCompression method,
Stream output,
MemoryAllocator allocator,
int width,
int bitsPerPixel,
DeflateCompressionLevel compressionLevel,
TiffPredictor predictor)
{
switch (method)
{
case TiffEncoderCompression.None:
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set");
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new NoCompressor(output);
case TiffEncoderCompression.PackBits:
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set");
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new PackBitsCompressor(output, allocator, width, bitsPerPixel);
case TiffEncoderCompression.Deflate:
return new DeflateCompressor(output, allocator, width, bitsPerPixel, predictor, compressionLevel);
case TiffEncoderCompression.Lzw:
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set");
return new LzwCompressor(output, allocator, width, bitsPerPixel, predictor);
case TiffEncoderCompression.CcittGroup3Fax:
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set");
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new T4BitCompressor(output, allocator, width, bitsPerPixel, false);
case TiffEncoderCompression.ModifiedHuffman:
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set");
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new T4BitCompressor(output, allocator, width, bitsPerPixel, true);
default:
throw TiffThrowHelper.NotSupportedCompressor(nameof(method));
}
}
}
}

2
src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffDecoderCompressionType.cs → src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
{
/// <summary>
/// Provides enumeration of the various TIFF compression types the decoder can handle.

25
src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffDecompressorsFactory.cs → src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs

@ -1,15 +1,16 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
{
internal static class TiffDecompressorsFactory
{
public static TiffBaseCompression Create(
TiffDecoderCompressionType compressionType,
public static TiffBaseDecompresor Create(
TiffDecoderCompressionType method,
MemoryAllocator allocator,
TiffPhotometricInterpretation photometricInterpretation,
int width,
@ -17,32 +18,36 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
TiffPredictor predictor,
FaxCompressionOptions faxOptions)
{
switch (compressionType)
switch (method)
{
case TiffDecoderCompressionType.None:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "predictor");
return new NoneTiffCompression(allocator);
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
return new NoneTiffCompression();
case TiffDecoderCompressionType.PackBits:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "predictor");
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
return new PackBitsTiffCompression(allocator);
case TiffDecoderCompressionType.Deflate:
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
return new DeflateTiffCompression(allocator, width, bitsPerPixel, predictor);
case TiffDecoderCompressionType.Lzw:
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
return new LzwTiffCompression(allocator, width, bitsPerPixel, predictor);
case TiffDecoderCompressionType.T4:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "predictor");
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new T4TiffCompression(allocator, faxOptions, photometricInterpretation, width);
case TiffDecoderCompressionType.HuffmanRle:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "predictor");
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new ModifiedHuffmanTiffCompression(allocator, photometricInterpretation, width);
default:
throw TiffThrowHelper.NotSupportedCompression(nameof(compressionType));
throw TiffThrowHelper.NotSupportedDecompressor(nameof(method));
}
}
}

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

@ -37,5 +37,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// Gets the quantizer for creating a color palette image.
/// </summary>
IQuantizer Quantizer { get; }
/// <summary>
/// Gets the maximum size of strip (bytes).
/// </summary>
int MaxStripBytes { get; }
}
}

14
src/ImageSharp/Formats/Tiff/README.md

@ -79,19 +79,19 @@
|CellWidth | | | |
|CellLength | | | |
|FillOrder | | - | Ignore. In practice is very uncommon, and is not recommended. |
|ImageDescription | | Y | |
|Make | | Y | |
|Model | | Y | |
|ImageDescription | Y | Y | |
|Make | Y | Y | |
|Model | Y | Y | |
|StripOffsets | Y | Y | |
|Orientation | | - | Ignore. Many readers ignore this tag. |
|SamplesPerPixel | Y | - | Currently ignored, as can be inferred from count of BitsPerSample |
|RowsPerStrip | | Y | |
|RowsPerStrip | Y | Y | |
|StripByteCounts | Y | Y | |
|MinSampleValue | | | |
|MaxSampleValue | | | |
|XResolution | Y | Y | |
|YResolution | Y | Y | |
|PlanarConfiguration | | Y | |
|PlanarConfiguration | | Y | Encoding support only chunky. |
|FreeOffsets | | | |
|FreeByteCounts | | | |
|GrayResponseUnit | | | |
@ -110,7 +110,7 @@
| |Encoder|Decoder|Comments |
|---------------------------|:-----:|:-----:|--------------------------|
|NewSubfileType | | | |
|DocumentName | | | |
|DocumentName | Y | Y | |
|PageName | | | |
|XPosition | | | |
|YPosition | | | |
@ -166,7 +166,7 @@
|YCbCrSubSampling | | | |
|YCbCrPositioning | | | |
|ReferenceBlackWhite | | | |
|StripRowCounts | | - | |
|StripRowCounts | - | - | See RFC 2301 (File Format for Internet Fax). |
|XMP | Y | Y | |
|ImageID | | | |
|ImageLayer | | | |

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

@ -3,7 +3,7 @@
using System.Collections.Generic;
using System.Threading;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -249,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
stripBuffers[stripIndex] = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize);
}
TiffBaseCompression decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions);
using TiffBaseDecompresor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions);
RgbPlanarTiffColor<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap);
@ -292,7 +292,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
Buffer2D<TPixel> pixels = frame.PixelBuffer;
TiffBaseCompression decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions);
using TiffBaseDecompresor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample, this.ColorMap);

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

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -50,6 +50,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
}
if (entries.ExifProfile.GetValue(ExifTag.StripRowCounts) != null)
{
TiffThrowHelper.ThrowNotSupported("Variable-sized strips are not supported.");
}
options.PlanarConfiguration = entries.PlanarConfiguration;
options.Predictor = entries.Predictor;
options.PhotometricInterpretation = entries.PhotometricInterpretation;

3
src/ImageSharp/Formats/Tiff/TiffEncoder.cs

@ -32,6 +32,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <inheritdoc/>
public IQuantizer Quantizer { get; set; }
/// <inheritdoc/>
public int MaxStripBytes { get; set; } = TiffEncoderCore.DefaultStripSize;
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>

135
src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

@ -8,10 +8,10 @@ using System.Threading;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@ -24,6 +24,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
internal sealed class TiffEncoderCore : IImageEncoderInternals
{
public const int DefaultStripSize = 8 * 1024;
public static readonly ByteOrder ByteOrder = BitConverter.IsLittleEndian ? ByteOrder.LittleEndian : ByteOrder.BigEndian;
private static readonly ushort ByteOrderMarker = BitConverter.IsLittleEndian
@ -43,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <summary>
/// The color depth, in number of bits per pixel.
/// </summary>
private TiffBitsPerPixel? bitsPerPixel;
private TiffBitsPerPixel bitsPerPixel;
/// <summary>
/// The quantizer for creating color palette image.
@ -55,6 +57,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
private readonly DeflateCompressionLevel compressionLevel;
/// <summary>
/// The maximum number of bytes for a strip.
/// </summary>
private readonly int maxStripBytes;
/// <summary>
/// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
/// </summary>
@ -68,6 +75,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
this.UseHorizontalPredictor = options.UseHorizontalPredictor;
this.compressionLevel = options.CompressionLevel;
this.maxStripBytes = options.MaxStripBytes;
}
/// <summary>
@ -105,26 +113,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
Guard.NotNull(stream, nameof(stream));
this.configuration = image.GetConfiguration();
ImageMetadata metadata = image.Metadata;
TiffMetadata tiffMetadata = metadata.GetTiffMetadata();
this.bitsPerPixel ??= tiffMetadata.BitsPerPixel;
if (this.Mode == TiffEncodingMode.Default)
{
// Preserve input bits per pixel, if no mode was specified.
if (this.bitsPerPixel == TiffBitsPerPixel.Pixel8)
{
this.Mode = TiffEncodingMode.Gray;
}
else if (this.bitsPerPixel == TiffBitsPerPixel.Pixel1)
{
this.Mode = TiffEncodingMode.BiColor;
}
else
{
this.Mode = TiffEncodingMode.Rgb;
}
}
this.SetMode(image);
this.SetPhotometricInterpretation();
using (var writer = new TiffStreamWriter(stream))
@ -132,7 +122,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
long firstIfdMarker = this.WriteHeader(writer);
// TODO: multiframing is not support
long nextIfdMarker = this.WriteImage(writer, image, firstIfdMarker);
this.WriteImage(writer, image, firstIfdMarker);
}
}
@ -159,8 +149,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <param name="writer">The <see cref="BinaryWriter"/> to write data to.</param>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="ifdOffset">The marker to write this IFD offset.</param>
/// <returns>The marker to write the next IFD offset (if present).</returns>
public long WriteImage<TPixel>(TiffStreamWriter writer, Image<TPixel> image, long ifdOffset)
private void WriteImage<TPixel>(TiffStreamWriter writer, Image<TPixel> image, long ifdOffset)
where TPixel : unmanaged, IPixel<TPixel>
{
var entriesCollector = new TiffEncoderEntriesCollector();
@ -168,18 +157,34 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
// Write the image bytes to the steam.
var imageDataStart = (uint)writer.Position;
TiffBaseColorWriter colorWriter = TiffColorWriterFactory.Create(this.Mode, writer, this.memoryAllocator, this.configuration, entriesCollector);
using TiffBaseCompressor compressor = TiffCompressorFactory.Create(
this.CompressionType,
writer.BaseStream,
this.memoryAllocator,
image.Width,
(int)this.bitsPerPixel,
this.compressionLevel,
this.UseHorizontalPredictor ? TiffPredictor.Horizontal : TiffPredictor.None);
using TiffBaseColorWriter<TPixel> colorWriter = TiffColorWriterFactory.Create(this.Mode, image.Frames.RootFrame, this.quantizer, this.memoryAllocator, this.configuration, entriesCollector);
int rowsPerStrip = this.CalcRowsPerStrip(image.Frames.RootFrame, colorWriter.BytesPerRow);
int imageDataBytes = colorWriter.Write(image, this.quantizer, this.CompressionType, this.compressionLevel, this.UseHorizontalPredictor);
colorWriter.Write(compressor, rowsPerStrip);
this.AddStripTags(image, entriesCollector, imageDataStart, imageDataBytes);
entriesCollector.ProcessImageFormat(this);
entriesCollector.ProcessGeneral(image);
writer.WriteMarker(ifdOffset, (uint)writer.Position);
long nextIfdMarker = this.WriteIfd(writer, entriesCollector.Entries);
}
private int CalcRowsPerStrip(ImageFrame image, int bytesPerRow)
{
int sz = this.maxStripBytes > 0 ? this.maxStripBytes : DefaultStripSize;
int height = sz / bytesPerRow;
return nextIfdMarker + imageDataBytes;
return height > 0 ? (height < image.Height ? height : image.Height) : 1;
}
/// <summary>
@ -188,7 +193,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <param name="writer">The <see cref="BinaryWriter"/> to write data to.</param>
/// <param name="entries">The IFD entries to write to the file.</param>
/// <returns>The marker to write the next IFD offset (if present).</returns>
public long WriteIfd(TiffStreamWriter writer, List<IExifValue> entries)
private long WriteIfd(TiffStreamWriter writer, List<IExifValue> entries)
{
if (entries.Count == 0)
{
@ -239,37 +244,59 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
return nextIfdMarker;
}
/// <summary>
/// Adds image format information to the specified IFD.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}" /> to encode from.</param>
/// <param name="entriesCollector">The entries collector.</param>
/// <param name="imageDataStartOffset">The start of the image data in the stream.</param>
/// <param name="imageDataBytes">The image data in bytes to write.</param>
public void AddStripTags<TPixel>(Image<TPixel> image, TiffEncoderEntriesCollector entriesCollector, uint imageDataStartOffset, int imageDataBytes)
where TPixel : unmanaged, IPixel<TPixel>
private void SetMode(Image image)
{
var stripOffsets = new ExifLongArray(ExifTagValue.StripOffsets)
if (this.CompressionType == TiffEncoderCompression.CcittGroup3Fax || this.CompressionType == TiffEncoderCompression.ModifiedHuffman)
{
// TODO: we only write one image strip for the start.
Value = new[] { imageDataStartOffset }
};
if (this.Mode == TiffEncodingMode.Default)
{
this.Mode = TiffEncodingMode.BiColor;
this.bitsPerPixel = TiffBitsPerPixel.Pixel1;
return;
}
var rowsPerStrip = new ExifLong(ExifTagValue.RowsPerStrip)
{
// All rows in one strip.
Value = (uint)image.Height
};
if (this.Mode != TiffEncodingMode.BiColor)
{
TiffThrowHelper.ThrowImageFormatException($"The {this.CompressionType} compression and {this.Mode} aren't compatible. Please use {this.CompressionType} only with {TiffEncodingMode.BiColor} or {TiffEncodingMode.Default} mode.");
}
}
var stripByteCounts = new ExifLongArray(ExifTagValue.StripByteCounts)
if (this.Mode == TiffEncodingMode.Default)
{
Value = new[] { (uint)imageDataBytes }
};
// Preserve input bits per pixel, if no mode was specified.
TiffMetadata tiffMetadata = image.Metadata.GetTiffMetadata();
switch (tiffMetadata.BitsPerPixel)
{
case TiffBitsPerPixel.Pixel1:
this.Mode = TiffEncodingMode.BiColor;
break;
case TiffBitsPerPixel.Pixel8:
// todo: can gray or palette
this.Mode = TiffEncodingMode.Gray;
break;
default:
this.Mode = TiffEncodingMode.Rgb;
break;
}
}
entriesCollector.Add(stripOffsets);
entriesCollector.Add(rowsPerStrip);
entriesCollector.Add(stripByteCounts);
switch (this.Mode)
{
case TiffEncodingMode.BiColor:
this.bitsPerPixel = TiffBitsPerPixel.Pixel1;
break;
case TiffEncodingMode.ColorPalette:
case TiffEncodingMode.Gray:
this.bitsPerPixel = TiffBitsPerPixel.Pixel8;
break;
case TiffEncodingMode.Rgb:
this.bitsPerPixel = TiffBitsPerPixel.Pixel24;
break;
default:
this.Mode = TiffEncodingMode.Rgb;
this.bitsPerPixel = TiffBitsPerPixel.Pixel24;
break;
}
}
private void SetPhotometricInterpretation()

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

@ -19,7 +19,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage);
[MethodImpl(InliningOptions.ColdPath)]
public static Exception NotSupportedCompression(string compressionType) => throw new NotSupportedException($"Not supported compression: {compressionType}");
public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}");
[MethodImpl(InliningOptions.ColdPath)]
public static Exception NotSupportedCompressor(string compressionType) => throw new NotSupportedException($"Not supported encoder compression method: {compressionType}");
[MethodImpl(InliningOptions.ColdPath)]
public static Exception InvalidColorType(string colorType) => throw new NotSupportedException($"Invalid color type: {colorType}");

104
src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter.cs

@ -2,35 +2,33 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers
{
/// <summary>
/// Utility class for writing TIFF data to a <see cref="Stream"/>.
/// </summary>
internal abstract class TiffBaseColorWriter
internal abstract class TiffBaseColorWriter<TPixel> : IDisposable
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="TiffBaseColorWriter" /> class.
/// </summary>
/// <param name="output">The output stream.</param>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="entriesCollector">The entries collector.</param>
protected TiffBaseColorWriter(TiffStreamWriter output, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector)
private bool isDisposed;
protected TiffBaseColorWriter(ImageFrame<TPixel> image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector)
{
this.Output = output;
this.Image = image;
this.MemoryAllocator = memoryAllocator;
this.Configuration = configuration;
this.EntriesCollector = entriesCollector;
this.BytesPerRow = ((image.Width * this.BitsPerPixel) + 7) / 8;
}
protected TiffStreamWriter Output { get; }
public abstract int BitsPerPixel { get; }
public int BytesPerRow { get; }
protected ImageFrame<TPixel> Image { get; }
protected MemoryAllocator MemoryAllocator { get; }
@ -38,7 +36,75 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers
protected TiffEncoderEntriesCollector EntriesCollector { get; }
public abstract int Write<TPixel>(Image<TPixel> image, IQuantizer quantizer, TiffEncoderCompression compression, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel<TPixel>;
public virtual void Write(TiffBaseCompressor compressor, int rowsPerStrip)
{
DebugGuard.IsTrue(this.BytesPerRow == compressor.BytesPerRow || compressor.BytesPerRow == 0, "Values must be equals");
int stripsCount = (this.Image.Height + rowsPerStrip - 1) / rowsPerStrip;
uint[] stripOffsets = new uint[stripsCount];
uint[] stripByteCounts = new uint[stripsCount];
int stripIndex = 0;
compressor.Initialize(rowsPerStrip);
for (int y = 0; y < this.Image.Height; y += rowsPerStrip)
{
long offset = compressor.Output.Position;
int height = Math.Min(rowsPerStrip, this.Image.Height - y);
this.EncodeStrip(y, height, compressor);
long endOffset = compressor.Output.Position;
stripOffsets[stripIndex] = (uint)offset;
stripByteCounts[stripIndex] = (uint)(endOffset - offset);
stripIndex++;
}
DebugGuard.IsTrue(stripIndex == stripsCount, "Values must be equals");
this.AddStripTags(rowsPerStrip, stripOffsets, stripByteCounts);
}
/// <inheritdoc/>
public void Dispose()
{
if (this.isDisposed)
{
return;
}
this.isDisposed = true;
this.Dispose(true);
}
protected static Span<T> GetStripPixels<T>(Buffer2D<T> buffer2D, int y, int height)
where T : struct
=> buffer2D.GetSingleSpan().Slice(y * buffer2D.Width, height * buffer2D.Width);
protected abstract void EncodeStrip(int y, int height, TiffBaseCompressor compressor);
/// <summary>
/// Adds image format information to the specified IFD.
/// </summary>
/// <param name="rowsPerStrip">The rows per strip.</param>
/// <param name="stripOffsets">The strip offsets.</param>
/// <param name="stripByteCounts">The strip byte counts.</param>
private void AddStripTags(int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts)
{
this.EntriesCollector.Add(new ExifLong(ExifTagValue.RowsPerStrip)
{
Value = (uint)rowsPerStrip
});
this.EntriesCollector.Add(new ExifLongArray(ExifTagValue.StripOffsets)
{
Value = stripOffsets
});
this.EntriesCollector.Add(new ExifLongArray(ExifTagValue.StripByteCounts)
{
Value = stripByteCounts
});
}
protected abstract void Dispose(bool disposing);
}
}

227
src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter.cs

@ -3,208 +3,99 @@
using System;
using System.Buffers;
using System.IO;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers
{
/// <summary>
/// Utility class for writing TIFF data to a <see cref="Stream"/>.
/// </summary>
internal class TiffBiColorWriter : TiffBaseColorWriter
internal class TiffBiColorWriter<TPixel> : TiffBaseColorWriter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public TiffBiColorWriter(TiffStreamWriter output, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector)
: base(output, memoryAllocator, configuration, entriesCollector)
{
}
private readonly Image<TPixel> imageBlackWhite;
/// <summary>
/// Writes the image data as 1 bit black and white to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="quantizer">The quantizer.</param>
/// <param name="compression">The compression to use.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
/// <param name="useHorizontalPredictor">if set to <c>true</c> [use horizontal predictor].</param>
/// <returns>
/// The number of bytes written.
/// </returns>
public override int Write<TPixel>(Image<TPixel> image, IQuantizer quantizer, TiffEncoderCompression compression, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor)
{
int padding = image.Width % 8 == 0 ? 0 : 1;
int bytesPerRow = (image.Width / 8) + padding;
using IMemoryOwner<L8> pixelRowAsGray = this.MemoryAllocator.Allocate<L8>(image.Width);
using IManagedByteBuffer row = this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerRow, AllocationOptions.Clean);
Span<byte> outputRow = row.GetSpan();
Span<L8> pixelRowAsGraySpan = pixelRowAsGray.GetSpan();
private IMemoryOwner<byte> pixelsAsGray;
private IMemoryOwner<byte> bitStrip;
public TiffBiColorWriter(ImageFrame<TPixel> image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector)
: base(image, memoryAllocator, configuration, entriesCollector)
{
// Convert image to black and white.
// TODO: Should we allow to skip this by the user, if its known to be black and white already?
using Image<TPixel> imageBlackWhite = image.Clone();
imageBlackWhite.Mutate(img => img.BinaryDither(default(ErrorDither)));
this.imageBlackWhite = new Image<TPixel>(configuration, new ImageMetadata(), new[] { image.Clone() });
this.imageBlackWhite.Mutate(img => img.BinaryDither(KnownDitherings.FloydSteinberg));
}
if (compression == TiffEncoderCompression.Deflate)
{
return this.WriteBiColorDeflate(imageBlackWhite, pixelRowAsGraySpan, outputRow, compressionLevel);
}
/// <inheritdoc/>
public override int BitsPerPixel => 1;
if (compression == TiffEncoderCompression.PackBits)
/// <inheritdoc/>
protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor)
{
if (this.pixelsAsGray == null)
{
return this.WriteBiColorPackBits(imageBlackWhite, pixelRowAsGraySpan, outputRow);
this.pixelsAsGray = this.MemoryAllocator.Allocate<byte>(height * this.Image.Width);
}
if (compression == TiffEncoderCompression.CcittGroup3Fax)
{
var bitWriter = new T4BitWriter(this.MemoryAllocator, this.Configuration);
return bitWriter.CompressImage(imageBlackWhite, pixelRowAsGraySpan, this.Output.BaseStream);
}
Span<byte> pixelAsGraySpan = this.pixelsAsGray.Slice(0, height * this.Image.Width);
if (compression == TiffEncoderCompression.ModifiedHuffman)
Span<TPixel> pixels = GetStripPixels(this.imageBlackWhite.GetRootFramePixelBuffer(), y, height);
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixels, pixelAsGraySpan, pixels.Length);
if (compressor.Method == TiffEncoderCompression.CcittGroup3Fax || compressor.Method == TiffEncoderCompression.ModifiedHuffman)
{
var bitWriter = new T4BitWriter(this.MemoryAllocator, this.Configuration, useModifiedHuffman: true);
return bitWriter.CompressImage(imageBlackWhite, pixelRowAsGraySpan, this.Output.BaseStream);
// Special case for T4BitCompressor.
compressor.CompressStrip(pixelAsGraySpan, height);
}
// Write image uncompressed.
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
else
{
int bitIndex = 0;
int byteIndex = 0;
Span<TPixel> pixelRow = imageBlackWhite.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8(this.Configuration, pixelRow, pixelRowAsGraySpan);
for (int x = 0; x < pixelRow.Length; x++)
int bytesPerStrip = this.BytesPerRow * height;
if (this.bitStrip == null)
{
int shift = 7 - bitIndex;
if (pixelRowAsGraySpan[x].PackedValue == 255)
{
outputRow[byteIndex] |= (byte)(1 << shift);
}
bitIndex++;
if (bitIndex == 8)
{
byteIndex++;
bitIndex = 0;
}
this.bitStrip = this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerStrip);
}
this.Output.Write(outputRow);
bytesWritten += outputRow.Length;
outputRow.Clear();
}
return bytesWritten;
}
/// <summary>
/// Writes the image data as 1 bit black and white with deflate compression to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="pixelRowAsGraySpan">A span for converting a pixel row to gray.</param>
/// <param name="outputRow">A span which will be used to store the output pixels.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
/// <returns>The number of bytes written.</returns>
public int WriteBiColorDeflate<TPixel>(Image<TPixel> image, Span<L8> pixelRowAsGraySpan, Span<byte> outputRow, DeflateCompressionLevel compressionLevel)
where TPixel : unmanaged, IPixel<TPixel>
{
using var memoryStream = new MemoryStream();
using var deflateStream = new ZlibDeflateStream(this.MemoryAllocator, memoryStream, compressionLevel);
Span<byte> rows = this.bitStrip.Slice(0, bytesPerStrip);
rows.Clear();
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
{
int bitIndex = 0;
int byteIndex = 0;
Span<TPixel> pixelRow = image.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8(this.Configuration, pixelRow, pixelRowAsGraySpan);
for (int x = 0; x < pixelRow.Length; x++)
int grayPixelIndex = 0;
for (int s = 0; s < height; s++)
{
int shift = 7 - bitIndex;
if (pixelRowAsGraySpan[x].PackedValue == 255)
int bitIndex = 0;
int byteIndex = 0;
Span<byte> outputRow = rows.Slice(s * this.BytesPerRow);
for (int x = 0; x < this.Image.Width; x++)
{
outputRow[byteIndex] |= (byte)(1 << shift);
}
bitIndex++;
if (bitIndex == 8)
{
byteIndex++;
bitIndex = 0;
int shift = 7 - bitIndex;
if (pixelAsGraySpan[grayPixelIndex++] == 255)
{
outputRow[byteIndex] |= (byte)(1 << shift);
}
bitIndex++;
if (bitIndex == 8)
{
byteIndex++;
bitIndex = 0;
}
}
}
deflateStream.Write(outputRow);
outputRow.Clear();
compressor.CompressStrip(rows, height);
}
deflateStream.Flush();
byte[] buffer = memoryStream.ToArray();
this.Output.Write(buffer);
bytesWritten += buffer.Length;
return bytesWritten;
}
/// <summary>
/// Writes the image data as 1 bit black and white with pack bits compression to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="pixelRowAsGraySpan">A span for converting a pixel row to gray.</param>
/// <param name="outputRow">A span which will be used to store the output pixels.</param>
/// <returns>The number of bytes written.</returns>
public int WriteBiColorPackBits<TPixel>(Image<TPixel> image, Span<L8> pixelRowAsGraySpan, Span<byte> outputRow)
where TPixel : unmanaged, IPixel<TPixel>
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
// Worst case is that the actual compressed data is larger then the input data. In this case we need 1 additional byte per 127 bits.
int additionalBytes = (image.Width / 127) + 2;
int compressedRowBytes = (image.Width / 8) + additionalBytes;
using IManagedByteBuffer compressedRow = this.MemoryAllocator.AllocateManagedByteBuffer(compressedRowBytes, AllocationOptions.Clean);
Span<byte> compressedRowSpan = compressedRow.GetSpan();
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
{
int bitIndex = 0;
int byteIndex = 0;
Span<TPixel> pixelRow = image.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8(this.Configuration, pixelRow, pixelRowAsGraySpan);
for (int x = 0; x < pixelRow.Length; x++)
{
int shift = 7 - bitIndex;
if (pixelRowAsGraySpan[x].PackedValue == 255)
{
outputRow[byteIndex] |= (byte)(1 << shift);
}
bitIndex++;
if (bitIndex == 8)
{
byteIndex++;
bitIndex = 0;
}
}
var size = PackBitsWriter.PackBits(outputRow, compressedRowSpan);
this.Output.Write(compressedRowSpan.Slice(0, size));
bytesWritten += size;
outputRow.Clear();
}
return bytesWritten;
this.imageBlackWhite?.Dispose();
this.pixelsAsGray?.Dispose();
this.bitStrip?.Dispose();
}
}
}

19
src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs

@ -2,23 +2,32 @@
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers
{
internal static class TiffColorWriterFactory
{
public static TiffBaseColorWriter Create(TiffEncodingMode mode, TiffStreamWriter output, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector)
public static TiffBaseColorWriter<TPixel> Create<TPixel>(
TiffEncodingMode mode,
ImageFrame<TPixel> image,
IQuantizer quantizer,
MemoryAllocator memoryAllocator,
Configuration configuration,
TiffEncoderEntriesCollector entriesCollector)
where TPixel : unmanaged, IPixel<TPixel>
{
switch (mode)
{
case TiffEncodingMode.ColorPalette:
return new TiffPaletteWriter(output, memoryAllocator, configuration, entriesCollector);
return new TiffPaletteWriter<TPixel>(image, quantizer, memoryAllocator, configuration, entriesCollector);
case TiffEncodingMode.Gray:
return new TiffGrayWriter(output, memoryAllocator, configuration, entriesCollector);
return new TiffGrayWriter<TPixel>(image, memoryAllocator, configuration, entriesCollector);
case TiffEncodingMode.BiColor:
return new TiffBiColorWriter(output, memoryAllocator, configuration, entriesCollector);
return new TiffBiColorWriter<TPixel>(image, memoryAllocator, configuration, entriesCollector);
default:
return new TiffRgbWriter(output, memoryAllocator, configuration, entriesCollector);
return new TiffRgbWriter<TPixel>(image, memoryAllocator, configuration, entriesCollector);
}
}
}

46
src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter.cs

@ -0,0 +1,46 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers
{
/// <summary>
/// The base class for composite color types: 8-bit gray, 24-bit RGB (4-bit gray, 16-bit (565/555) RGB, 32-bit RGB, CMYK, YCbCr).
/// </summary>
internal abstract class TiffCompositeColorWriter<TPixel> : TiffBaseColorWriter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private IManagedByteBuffer rowBuffer;
protected TiffCompositeColorWriter(ImageFrame<TPixel> image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector)
: base(image, memoryAllocator, configuration, entriesCollector)
{
}
protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor)
{
if (this.rowBuffer == null)
{
this.rowBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(this.BytesPerRow * height);
}
this.rowBuffer.Clear();
Span<byte> rowSpan = this.rowBuffer.GetSpan().Slice(0, this.BytesPerRow * height);
Span<TPixel> pixels = GetStripPixels(this.Image.PixelBuffer, y, height);
this.EncodePixels(pixels, rowSpan);
compressor.CompressStrip(rowSpan, height);
}
protected abstract void EncodePixels(Span<TPixel> pixels, Span<byte> buffer);
/// <inheritdoc />
protected override void Dispose(bool disposing) => this.rowBuffer?.Dispose();
}
}

165
src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter.cs

@ -2,172 +2,23 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.IO;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers
{
/// <summary>
/// Utility class for writing TIFF data to a <see cref="Stream"/>.
/// </summary>
internal class TiffGrayWriter : TiffBaseColorWriter
internal class TiffGrayWriter<TPixel> : TiffCompositeColorWriter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public TiffGrayWriter(TiffStreamWriter output, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector)
: base(output, memoryAllocator, configuration, entriesCollector)
{
}
/// <summary>
/// Writes the image data as 8 bit gray to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="quantizer">The quantizer.</param>
/// <param name="compression">The compression to use.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used. Should only be used with deflate or lzw compression.</param>
/// <returns>
/// The number of bytes written.
/// </returns>
public override int Write<TPixel>(Image<TPixel> image, IQuantizer quantizer, TiffEncoderCompression compression, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor)
public TiffGrayWriter(ImageFrame<TPixel> image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector)
: base(image, memoryAllocator, configuration, entriesCollector)
{
using IManagedByteBuffer row = this.MemoryAllocator.AllocateManagedByteBuffer(image.Width);
Span<byte> rowSpan = row.GetSpan();
if (compression == TiffEncoderCompression.Deflate)
{
return this.WriteGrayDeflateCompressed(image, rowSpan, compressionLevel, useHorizontalPredictor);
}
if (compression == TiffEncoderCompression.Lzw)
{
return this.WriteGrayLzwCompressed(image, rowSpan, useHorizontalPredictor);
}
if (compression == TiffEncoderCompression.PackBits)
{
return this.WriteGrayPackBitsCompressed(image, rowSpan);
}
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> pixelRow = image.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixelRow, rowSpan, pixelRow.Length);
this.Output.Write(rowSpan);
bytesWritten += rowSpan.Length;
}
return bytesWritten;
}
/// <summary>
/// Writes the image data as 8 bit gray with deflate compression to the stream.
/// </summary>
/// <param name="image">The image to write to the stream.</param>
/// <param name="rowSpan">A span of a row of pixels.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used.</param>
/// <returns>The number of bytes written.</returns>
private int WriteGrayDeflateCompressed<TPixel>(Image<TPixel> image, Span<byte> rowSpan, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesWritten = 0;
using var memoryStream = new MemoryStream();
using var deflateStream = new ZlibDeflateStream(this.MemoryAllocator, memoryStream, compressionLevel);
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> pixelRow = image.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixelRow, rowSpan, pixelRow.Length);
if (useHorizontalPredictor)
{
HorizontalPredictor.ApplyHorizontalPrediction8Bit(rowSpan);
}
deflateStream.Write(rowSpan);
}
deflateStream.Flush();
/// <inheritdoc />
public override int BitsPerPixel => 8;
byte[] buffer = memoryStream.ToArray();
this.Output.Write(buffer);
bytesWritten += buffer.Length;
return bytesWritten;
}
/// <summary>
/// Writes the image data as 8 bit gray with lzw compression to the stream.
/// </summary>
/// <param name="image">The image to write to the stream.</param>
/// <param name="rowSpan">A span of a row of pixels.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used.</param>
/// <returns>The number of bytes written.</returns>
private int WriteGrayLzwCompressed<TPixel>(Image<TPixel> image, Span<byte> rowSpan, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesWritten = 0;
using var memoryStream = new MemoryStream();
IMemoryOwner<byte> pixelData = this.MemoryAllocator.Allocate<byte>(image.Width * image.Height);
Span<byte> pixels = pixelData.GetSpan();
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> pixelRow = image.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixelRow, rowSpan, pixelRow.Length);
if (useHorizontalPredictor)
{
HorizontalPredictor.ApplyHorizontalPrediction8Bit(rowSpan);
}
rowSpan.CopyTo(pixels.Slice(y * image.Width));
}
using var lzwEncoder = new TiffLzwEncoder(this.MemoryAllocator, pixelData);
lzwEncoder.Encode(memoryStream);
byte[] buffer = memoryStream.ToArray();
this.Output.Write(buffer);
bytesWritten += buffer.Length;
return bytesWritten;
}
/// <summary>
/// Writes the image data as 8 bit gray to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="rowSpan">A span of a row of pixels.</param>
/// <returns>The number of bytes written.</returns>
private int WriteGrayPackBitsCompressed<TPixel>(Image<TPixel> image, Span<byte> rowSpan)
where TPixel : unmanaged, IPixel<TPixel>
{
// Worst case is that the actual compressed data is larger then the input data. In this case we need 1 additional byte per 127 bytes.
int additionalBytes = (image.Width / 127) + 1;
using IManagedByteBuffer compressedRow = this.MemoryAllocator.AllocateManagedByteBuffer(image.Width + additionalBytes, AllocationOptions.Clean);
Span<byte> compressedRowSpan = compressedRow.GetSpan();
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> pixelRow = image.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixelRow, rowSpan, pixelRow.Length);
int size = PackBitsWriter.PackBits(rowSpan, compressedRowSpan);
this.Output.Write(compressedRow.Slice(0, size));
bytesWritten += size;
compressedRowSpan.Clear();
}
return bytesWritten;
}
/// <inheritdoc />
protected override void EncodePixels(Span<TPixel> pixels, Span<byte> buffer) => PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixels, buffer, pixels.Length);
}
}

215
src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter.cs

@ -3,13 +3,9 @@
using System;
using System.Buffers;
using System.IO;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
@ -17,44 +13,40 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers
{
/// <summary>
/// Utility class for writing TIFF data to a <see cref="Stream"/>.
/// </summary>
internal class TiffPaletteWriter : TiffBaseColorWriter
internal class TiffPaletteWriter<TPixel> : TiffBaseColorWriter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="TiffPaletteWriter" /> class.
/// </summary>
/// <param name="output">The output stream.</param>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="entriesCollector">The entries collector.</param>
public TiffPaletteWriter(TiffStreamWriter output, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector)
: base(output, memoryAllocator, configuration, entriesCollector)
private const int ColorsPerChannel = 256;
private const int ColorPaletteSize = ColorsPerChannel * 3;
private const int ColorPaletteBytes = ColorPaletteSize * 2;
private readonly IndexedImageFrame<TPixel> quantized;
public TiffPaletteWriter(ImageFrame<TPixel> image, IQuantizer quantizer, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector)
: base(image, memoryAllocator, configuration, entriesCollector)
{
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.Configuration);
this.quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
this.AddTag(this.quantized);
}
/// <summary>
/// Writes the image data as indices into a color map to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="quantizer">The quantizer to use.</param>
/// <param name="compression">The compression to use.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used. Should only be used in combination with deflate or LZW compression.</param>
/// <returns>
/// The number of bytes written.
/// </returns>
public override int Write<TPixel>(Image<TPixel> image, IQuantizer quantizer, TiffEncoderCompression compression, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor)
/// <inheritdoc />
public override int BitsPerPixel => 8;
/// <inheritdoc />
protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor)
{
int colorsPerChannel = 256;
int colorPaletteSize = colorsPerChannel * 3;
int colorPaletteBytes = colorPaletteSize * 2;
using IManagedByteBuffer row = this.MemoryAllocator.AllocateManagedByteBuffer(image.Width);
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.Configuration);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image.Frames.RootFrame, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(colorPaletteBytes);
Span<byte> pixels = GetStripPixels(((IPixelSource)this.quantized).PixelBuffer, y, height);
compressor.CompressStrip(pixels, height);
}
/// <inheritdoc />
protected override void Dispose(bool disposing) => this.quantized?.Dispose();
private void AddTag(IndexedImageFrame<TPixel> quantized)
{
using IMemoryOwner<byte> colorPaletteBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(ColorPaletteBytes);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span;
@ -65,11 +57,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers
PixelOperations<TPixel>.Instance.ToRgb48(this.Configuration, quantizedColors, quantizedColorRgb48);
// It can happen that the quantized colors are less than the expected 256 per channel.
var diffToMaxColors = colorsPerChannel - quantizedColors.Length;
var diffToMaxColors = ColorsPerChannel - quantizedColors.Length;
// In a TIFF ColorMap, all the Red values come first, followed by the Green values,
// then the Blue values. Convert the quantized palette to this format.
var palette = new ushort[colorPaletteSize];
var palette = new ushort[ColorPaletteSize];
int paletteIdx = 0;
for (int i = 0; i < quantizedColors.Length; i++)
{
@ -96,147 +88,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers
};
this.EntriesCollector.Add(colorMap);
if (compression == TiffEncoderCompression.Deflate)
{
return this.WriteDeflateCompressedPalettedRgb(image, quantized, compressionLevel, useHorizontalPredictor);
}
if (compression == TiffEncoderCompression.Lzw)
{
return this.WriteLzwCompressedPalettedRgb(image, quantized, useHorizontalPredictor);
}
if (compression == TiffEncoderCompression.PackBits)
{
return this.WritePackBitsCompressedPalettedRgb(image, quantized);
}
// No compression.
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
{
ReadOnlySpan<byte> pixelSpan = quantized.GetPixelRowSpan(y);
this.Output.Write(pixelSpan);
bytesWritten += pixelSpan.Length;
}
return bytesWritten;
}
/// <summary>
/// Writes the image data as indices into a color map compressed with deflate compression to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="quantized">The quantized frame.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used.</param>
/// <returns>The number of bytes written.</returns>
private int WriteDeflateCompressedPalettedRgb<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel<TPixel>
{
using IManagedByteBuffer tmpBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(image.Width);
using var memoryStream = new MemoryStream();
using var deflateStream = new ZlibDeflateStream(this.MemoryAllocator, memoryStream, compressionLevel);
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
{
ReadOnlySpan<byte> pixelRow = quantized.GetPixelRowSpan(y);
if (useHorizontalPredictor)
{
// We need a writable Span here.
Span<byte> pixelRowCopy = tmpBuffer.GetSpan();
pixelRow.CopyTo(pixelRowCopy);
HorizontalPredictor.ApplyHorizontalPrediction8Bit(pixelRowCopy);
deflateStream.Write(pixelRowCopy);
}
else
{
deflateStream.Write(pixelRow);
}
}
deflateStream.Flush();
byte[] buffer = memoryStream.ToArray();
this.Output.Write(buffer);
bytesWritten += buffer.Length;
return bytesWritten;
}
/// <summary>
/// Writes the image data as indices into a color map compressed with lzw compression to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="quantized">The quantized frame.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used.</param>
/// <returns>The number of bytes written.</returns>
private int WriteLzwCompressedPalettedRgb<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel<TPixel>
{
IMemoryOwner<byte> pixelData = this.MemoryAllocator.Allocate<byte>(image.Width * image.Height);
using IManagedByteBuffer tmpBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(image.Width);
using var memoryStream = new MemoryStream();
int bytesWritten = 0;
Span<byte> pixels = pixelData.GetSpan();
for (int y = 0; y < image.Height; y++)
{
ReadOnlySpan<byte> indexedPixelRow = quantized.GetPixelRowSpan(y);
if (useHorizontalPredictor)
{
// We need a writable Span here.
Span<byte> pixelRowCopy = tmpBuffer.GetSpan();
indexedPixelRow.CopyTo(pixelRowCopy);
HorizontalPredictor.ApplyHorizontalPrediction8Bit(pixelRowCopy);
pixelRowCopy.CopyTo(pixels.Slice(y * image.Width));
}
else
{
indexedPixelRow.CopyTo(pixels.Slice(y * image.Width));
}
}
using var lzwEncoder = new TiffLzwEncoder(this.MemoryAllocator, pixelData);
lzwEncoder.Encode(memoryStream);
byte[] buffer = memoryStream.ToArray();
this.Output.Write(buffer);
bytesWritten += buffer.Length;
return bytesWritten;
}
/// <summary>
/// Writes the image data as indices into a color map compressed with deflate compression to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="quantized">The quantized frame.</param>
/// <returns>The number of bytes written.</returns>
private int WritePackBitsCompressedPalettedRgb<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized)
where TPixel : unmanaged, IPixel<TPixel>
{
// Worst case is that the actual compressed data is larger then the input data. In this case we need 1 additional byte per 127 bytes.
int additionalBytes = (image.Width * 3 / 127) + 1;
using IManagedByteBuffer compressedRow = this.MemoryAllocator.AllocateManagedByteBuffer((image.Width * 3) + additionalBytes, AllocationOptions.Clean);
Span<byte> compressedRowSpan = compressedRow.GetSpan();
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
{
ReadOnlySpan<byte> pixelSpan = quantized.GetPixelRowSpan(y);
int size = PackBitsWriter.PackBits(pixelSpan, compressedRowSpan);
this.Output.Write(compressedRowSpan.Slice(0, size));
bytesWritten += size;
}
return bytesWritten;
}
}
}

168
src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter.cs

@ -2,175 +2,23 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.IO;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers
{
/// <summary>
/// Utility class for writing TIFF data to a <see cref="Stream"/>.
/// </summary>
internal class TiffRgbWriter : TiffBaseColorWriter
internal class TiffRgbWriter<TPixel> : TiffCompositeColorWriter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public TiffRgbWriter(TiffStreamWriter output, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector)
: base(output, memoryAllocator, configuration, entriesCollector)
{
}
/// <summary>
/// Writes the image data as RGB to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="quantizer">The quantizer.</param>
/// <param name="compression">The compression to use.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used. Should only be used with deflate compression.</param>
/// <returns>
/// The number of bytes written.
/// </returns>
public override int Write<TPixel>(Image<TPixel> image, IQuantizer quantizer, TiffEncoderCompression compression, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor)
public TiffRgbWriter(ImageFrame<TPixel> image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector)
: base(image, memoryAllocator, configuration, entriesCollector)
{
using IManagedByteBuffer row = this.MemoryAllocator.AllocateManagedByteBuffer(image.Width * 3);
Span<byte> rowSpan = row.GetSpan();
if (compression == TiffEncoderCompression.Deflate)
{
return this.WriteDeflateCompressedRgb(image, rowSpan, compressionLevel, useHorizontalPredictor);
}
if (compression == TiffEncoderCompression.Lzw)
{
return this.WriteLzwCompressedRgb(image, rowSpan, useHorizontalPredictor);
}
if (compression == TiffEncoderCompression.PackBits)
{
return this.WriteRgbPackBitsCompressed(image, rowSpan);
}
// No compression.
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> pixelRow = image.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgb24Bytes(this.Configuration, pixelRow, rowSpan, pixelRow.Length);
this.Output.Write(rowSpan);
bytesWritten += rowSpan.Length;
}
return bytesWritten;
}
/// <summary>
/// Writes the image data as RGB compressed with zlib to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="rowSpan">A Span for a pixel row.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used. Should only be used with deflate compression.</param>
/// <returns>The number of bytes written.</returns>
private int WriteDeflateCompressedRgb<TPixel>(Image<TPixel> image, Span<byte> rowSpan, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesWritten = 0;
using var memoryStream = new MemoryStream();
using var deflateStream = new ZlibDeflateStream(this.MemoryAllocator, memoryStream, compressionLevel);
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> pixelRow = image.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgb24Bytes(this.Configuration, pixelRow, rowSpan, pixelRow.Length);
if (useHorizontalPredictor)
{
HorizontalPredictor.ApplyHorizontalPrediction24Bit(rowSpan);
}
/// <inheritdoc />
public override int BitsPerPixel => 24;
deflateStream.Write(rowSpan);
}
deflateStream.Flush();
byte[] buffer = memoryStream.ToArray();
this.Output.Write(buffer);
bytesWritten += buffer.Length;
return bytesWritten;
}
/// <summary>
/// Writes the image data as RGB compressed with lzw to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="rowSpan">A Span for a pixel row.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used.</param>
/// <returns>The number of bytes written.</returns>
private int WriteLzwCompressedRgb<TPixel>(Image<TPixel> image, Span<byte> rowSpan, bool useHorizontalPredictor)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesWritten = 0;
using var memoryStream = new MemoryStream();
IMemoryOwner<byte> pixelData = this.MemoryAllocator.Allocate<byte>(image.Width * image.Height * 3);
Span<byte> pixels = pixelData.GetSpan();
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> pixelRow = image.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgb24Bytes(this.Configuration, pixelRow, rowSpan, pixelRow.Length);
if (useHorizontalPredictor)
{
HorizontalPredictor.ApplyHorizontalPrediction24Bit(rowSpan);
}
rowSpan.CopyTo(pixels.Slice(y * image.Width * 3));
}
using var lzwEncoder = new TiffLzwEncoder(this.MemoryAllocator, pixelData);
lzwEncoder.Encode(memoryStream);
byte[] buffer = memoryStream.ToArray();
this.Output.Write(buffer);
bytesWritten += buffer.Length;
return bytesWritten;
}
/// <summary>
/// Writes the image data as RGB with packed bits compression to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel data.</typeparam>
/// <param name="image">The image to write to the stream.</param>
/// <param name="rowSpan">A Span for a pixel row.</param>
/// <returns>The number of bytes written.</returns>
private int WriteRgbPackBitsCompressed<TPixel>(Image<TPixel> image, Span<byte> rowSpan)
where TPixel : unmanaged, IPixel<TPixel>
{
// Worst case is that the actual compressed data is larger then the input data. In this case we need 1 additional byte per 127 bytes.
int additionalBytes = (image.Width * 3 / 127) + 1;
using IManagedByteBuffer compressedRow = this.MemoryAllocator.AllocateManagedByteBuffer((image.Width * 3) + additionalBytes, AllocationOptions.Clean);
Span<byte> compressedRowSpan = compressedRow.GetSpan();
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> pixelRow = image.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgb24Bytes(this.Configuration, pixelRow, rowSpan, pixelRow.Length);
int size = PackBitsWriter.PackBits(rowSpan, compressedRowSpan);
this.Output.Write(compressedRow.Slice(0, size));
bytesWritten += size;
compressedRowSpan.Clear();
}
return bytesWritten;
}
/// <inheritdoc />
protected override void EncodePixels(Span<TPixel> pixels, Span<byte> buffer) => PixelOperations<TPixel>.Instance.ToRgb24Bytes(this.Configuration, pixels, buffer, pixels.Length);
}
}

1
src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs

@ -3,7 +3,6 @@
using System;
using System.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers
{

12
tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs

@ -53,12 +53,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Params(
TestImages.Tiff.CcittFax3AllTermCodes,
TestImages.Tiff.HuffmanRleAllMakeupCodes,
TestImages.Tiff.GrayscaleUncompressed,
TestImages.Tiff.PaletteUncompressed,
TestImages.Tiff.RgbDeflate,
TestImages.Tiff.RgbLzwPredictor,
TestImages.Tiff.RgbPackbits,
TestImages.Tiff.RgbUncompressed)]
TestImages.Tiff.Calliphora_GrayscaleUncompressed,
TestImages.Tiff.Calliphora_RgbPaletteLzw_Predictor,
TestImages.Tiff.Calliphora_RgbDeflate_Predictor,
TestImages.Tiff.Calliphora_RgbLzwPredictor,
TestImages.Tiff.Calliphora_RgbPackbits,
TestImages.Tiff.Calliphora_RgbUncompressed)]
public string TestImage { get; set; }
#endif

18
tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs

@ -24,14 +24,14 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[Params(TestImages.Tiff.RgbUncompressed)]
[Params(TestImages.Tiff.Calliphora_RgbUncompressed)]
public string TestImage { get; set; }
[Params(
TiffEncoderCompression.None,
////TiffEncoderCompression.Deflate,
TiffEncoderCompression.Deflate,
TiffEncoderCompression.Lzw,
////TiffEncoderCompression.PackBits,
TiffEncoderCompression.PackBits,
TiffEncoderCompression.CcittGroup3Fax,
TiffEncoderCompression.ModifiedHuffman)]
public TiffEncoderCompression Compression { get; set; }
@ -70,7 +70,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Description = "ImageSharp Tiff")]
public void TiffCore()
{
var encoder = new TiffEncoder() { Compression = this.Compression };
TiffEncodingMode mode = TiffEncodingMode.Default;
// workaround for 1-bit bug
if (this.Compression == TiffEncoderCompression.CcittGroup3Fax || this.Compression == TiffEncoderCompression.ModifiedHuffman)
{
mode = TiffEncodingMode.BiColor;
}
var encoder = new TiffEncoder() { Compression = this.Compression, Mode = mode };
using var memoryStream = new MemoryStream();
this.core.SaveAsTiff(memoryStream, encoder);
}
@ -107,7 +115,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
return EncoderValue.CompressionLZW;
default:
throw new System.ArgumentOutOfRangeException(nameof(compression));
throw new System.NotSupportedException(compression.ToString());
}
}
}

2
tests/ImageSharp.Benchmarks/Config.cs

@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Benchmarks
}
#endif
this.SummaryStyle = SummaryStyle.Default.WithMaxParameterColumnWidth(40);
this.SummaryStyle = SummaryStyle.Default.WithMaxParameterColumnWidth(50);
}
public class MultiFramework : Config

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

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

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

@ -1,13 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
@ -40,7 +38,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
using BufferedReadStream stream = CreateCompressedStream(data);
var buffer = new byte[data.Length];
new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None).Decompress(stream, 0, (uint)stream.Length, buffer);
using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None);
decompressor.Decompress(stream, 0, (uint)stream.Length, buffer);
Assert.Equal(data, buffer);
}
@ -48,12 +47,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
private static BufferedReadStream CreateCompressedStream(byte[] inputData)
{
Stream compressedStream = new MemoryStream();
using System.Buffers.IMemoryOwner<byte> data = Configuration.Default.MemoryAllocator.Allocate<byte>(inputData.Length);
inputData.AsSpan().CopyTo(data.GetSpan());
using (var encoder = new TiffLzwEncoder(Configuration.Default.MemoryAllocator, data))
using (var encoder = new TiffLzwEncoder(Configuration.Default.MemoryAllocator))
{
encoder.Encode(compressedStream);
encoder.Encode(inputData, compressedStream);
}
compressedStream.Seek(0, SeekOrigin.Begin);

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

@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData));
var buffer = new byte[expectedResult.Length];
new NoneTiffCompression(null).Decompress(stream, 0, byteCount, buffer);
new NoneTiffCompression().Decompress(stream, 0, byteCount, buffer);
Assert.Equal(expectedResult, buffer);
}

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

@ -29,7 +29,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData));
var buffer = new byte[expectedResult.Length];
new PackBitsTiffCompression(new ArrayPoolMemoryAllocator()).Decompress(stream, 0, (uint)inputData.Length, buffer);
using var decompressor = new PackBitsTiffCompression(new ArrayPoolMemoryAllocator());
decompressor.Decompress(stream, 0, (uint)inputData.Length, buffer);
Assert.Equal(expectedResult, buffer);
}

1
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs

@ -4,7 +4,6 @@
using System.IO;
using SixLabors.ImageSharp.Formats.Experimental.Tiff;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers;
using SixLabors.ImageSharp.Memory;

149
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Formats;
@ -21,12 +22,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
private static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder();
private readonly Configuration configuration;
private static readonly Configuration Configuration;
public TiffEncoderTests()
static TiffEncoderTests()
{
this.configuration = new Configuration();
this.configuration.AddTiff();
Configuration = new Configuration();
Configuration.AddTiff();
}
[Theory]
@ -62,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(this.configuration, memStream);
using var output = Image.Load<Rgba32>(Configuration, memStream);
TiffMetadata meta = output.Metadata.GetTiffMetadata();
Assert.Equal(expectedBitsPerPixel, meta.BitsPerPixel);
Assert.Equal(expectedCompression, meta.Compression);
@ -85,11 +86,52 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(this.configuration, memStream);
using var output = Image.Load<Rgba32>(Configuration, memStream);
TiffMetadata meta = output.Metadata.GetTiffMetadata();
Assert.Equal(expectedBitsPerPixel, meta.BitsPerPixel);
}
[Theory]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncoderCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncoderCompression.ModifiedHuffman, TiffCompression.Ccitt1D)]
[WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffEncoderCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)]
[WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffEncoderCompression.ModifiedHuffman, TiffCompression.Ccitt1D)]
public void TiffEncoder_CorrectBiMode<TPixel>(TestImageProvider<TPixel> provider, TiffEncoderCompression compression, TiffCompression expectedCompression)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
var encoder = new TiffEncoder() { Compression = compression };
using Image<TPixel> input = provider.GetImage();
using var memStream = new MemoryStream();
// act
input.Save(memStream, encoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(Configuration, memStream);
TiffMetadata meta = output.Metadata.GetTiffMetadata();
Assert.Equal(TiffBitsPerPixel.Pixel1, meta.BitsPerPixel);
Assert.Equal(expectedCompression, meta.Compression);
}
[Theory]
[InlineData(TiffEncodingMode.ColorPalette, TiffEncoderCompression.CcittGroup3Fax)]
[InlineData(TiffEncodingMode.ColorPalette, TiffEncoderCompression.ModifiedHuffman)]
[InlineData(TiffEncodingMode.Gray, TiffEncoderCompression.ModifiedHuffman)]
[InlineData(TiffEncodingMode.Rgb, TiffEncoderCompression.ModifiedHuffman)]
public void TiffEncoder_IncompatibilityOptions(TiffEncodingMode mode, TiffEncoderCompression compression)
{
// arrange
using var input = new Image<Rgb24>(10, 10);
var encoder = new TiffEncoder() { Mode = mode, Compression = compression };
using var memStream = new MemoryStream();
// act
Assert.Throws<ImageFormatException>(() => input.Save(memStream, encoder));
}
[Theory]
[WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeRgb_Works<TPixel>(TestImageProvider<TPixel> provider)
@ -211,6 +253,72 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffEncoder_EncodeBiColor_WithModifiedHuffmanCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel1, TiffEncodingMode.BiColor, TiffEncoderCompression.ModifiedHuffman);
[Theory]
[WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffEncodingMode.Gray, TiffEncoderCompression.PackBits, 16 * 1024)]
[WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffEncodingMode.ColorPalette, TiffEncoderCompression.Lzw, 32 * 1024)]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncodingMode.Rgb, TiffEncoderCompression.Deflate, 64 * 1024)]
[WithFile(RgbUncompressed, PixelTypes.Rgb24, TiffEncodingMode.Rgb, TiffEncoderCompression.None, 10 * 1024)]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncodingMode.Rgb, TiffEncoderCompression.None, 30 * 1024)]
[WithFile(RgbUncompressed, PixelTypes.Rgb48, TiffEncodingMode.Rgb, TiffEncoderCompression.None, 70 * 1024)]
public void TiffEncoder_StripLength<TPixel>(TestImageProvider<TPixel> provider, TiffEncodingMode mode, TiffEncoderCompression compression, int maxSize)
where TPixel : unmanaged, IPixel<TPixel> =>
TestStripLength(provider, mode, compression, maxSize);
[Theory]
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.L8, TiffEncodingMode.BiColor, TiffEncoderCompression.CcittGroup3Fax, 9 * 1024)]
public void TiffEncoder_StripLength_OutOfBounds<TPixel>(TestImageProvider<TPixel> provider, TiffEncodingMode mode, TiffEncoderCompression compression, int maxSize)
where TPixel : unmanaged, IPixel<TPixel> =>
//// CcittGroup3Fax compressed data length can be larger than the original length
Assert.Throws<Xunit.Sdk.TrueException>(() => TestStripLength(provider, mode, compression, maxSize));
private static void TestStripLength<TPixel>(TestImageProvider<TPixel> provider, TiffEncodingMode mode, TiffEncoderCompression compression, int maxSize)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
var tiffEncoder = new TiffEncoder() { Mode = mode, Compression = compression, MaxStripBytes = maxSize };
Image<TPixel> input = provider.GetImage();
using var memStream = new MemoryStream();
TiffFrameMetadata inputMeta = input.Frames.RootFrame.Metadata.GetTiffMetadata();
// act
input.Save(memStream, tiffEncoder);
// assert
memStream.Position = 0;
var output = Image.Load<Rgba32>(Configuration, memStream);
TiffFrameMetadata meta = output.Frames.RootFrame.Metadata.GetTiffMetadata();
Assert.True(output.Height > (int)meta.RowsPerStrip);
Assert.True(meta.StripOffsets.Length > 1);
Assert.True(meta.StripByteCounts.Length > 1);
foreach (Number sz in meta.StripByteCounts)
{
Assert.True((uint)sz <= maxSize);
}
// for uncompressed more accurate test
if (compression == TiffEncoderCompression.None)
{
for (int i = 0; i < meta.StripByteCounts.Length - 1; i++)
{
// the difference must be less than one row
int stripBytes = (int)meta.StripByteCounts[i];
var widthBytes = (meta.BitsPerPixel + 7) / 8 * (int)meta.Width;
Assert.True((maxSize - stripBytes) < widthBytes);
}
}
// compare with reference
TestTiffEncoderCore(
provider,
(TiffBitsPerPixel)inputMeta.BitsPerPixel,
mode,
Convert(inputMeta.Compression),
maxStripSize: maxSize);
}
private static void TestTiffEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
TiffBitsPerPixel bitsPerPixel,
@ -218,14 +326,41 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
TiffEncoderCompression compression = TiffEncoderCompression.None,
bool usePredictor = false,
bool useExactComparer = true,
int maxStripSize = 0,
float compareTolerance = 0.01f)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
var encoder = new TiffEncoder { Mode = mode, Compression = compression, UseHorizontalPredictor = usePredictor };
var encoder = new TiffEncoder
{
Mode = mode,
Compression = compression,
UseHorizontalPredictor = usePredictor,
MaxStripBytes = maxStripSize
};
// Does DebugSave & load reference CompareToReferenceInput():
image.VerifyEncoder(provider, "tiff", bitsPerPixel, encoder, useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), referenceDecoder: ReferenceDecoder);
}
private static TiffEncoderCompression Convert(TiffCompression compression)
{
switch (compression)
{
default:
case TiffCompression.None:
return TiffEncoderCompression.None;
case TiffCompression.Deflate:
return TiffEncoderCompression.Deflate;
case TiffCompression.Lzw:
return TiffEncoderCompression.Lzw;
case TiffCompression.PackBits:
return TiffEncoderCompression.PackBits;
case TiffCompression.Ccitt1D:
return TiffEncoderCompression.ModifiedHuffman;
case TiffCompression.CcittGroup3Fax:
return TiffEncoderCompression.CcittGroup3Fax;
}
}
}
}

2
tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs

@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers;
using SixLabors.ImageSharp.Memory;

87
tests/Images/Input/Tiff/Benchmarks/SixLabors.ImageSharp.Benchmarks.Codecs.DecodeTiffBig-report-github.md

@ -1,87 +0,0 @@
``` ini
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-3610QM CPU 2.30GHz (Ivy Bridge), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=5.0.100
[Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
Job-KSIANY : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT
Job-VMCLSF : .NET Core 2.1.23 (CoreCLR 4.6.29321.03, CoreFX 4.6.29321.01), X64 RyuJIT
Job-UHENIY : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
InvocationCount=1 IterationCount=5 LaunchCount=1
UnrollFactor=1 WarmupCount=3
```
| Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---------------------- |----------- |-------------- |----------------------------------- |-----------:|----------:|----------:|------:|--------:|-----------:|----------:|----------:|------------:|
| **&#39;System.Drawing Tiff&#39;** | **Job-KSIANY** | **.NET 4.7.2** | **medium_bw_Fax3.tiff** | **491.6 ms** | **20.40 ms** | **5.30 ms** | **1.00** | **0.00** | **1000.0000** | **-** | **-** | **5768128 B** |
| &#39;ImageSharp Tiff&#39; | Job-KSIANY | .NET 4.7.2 | medium_bw_Fax3.tiff | 6,970.2 ms | 70.64 ms | 10.93 ms | 14.23 | 0.12 | 1000.0000 | 1000.0000 | 1000.0000 | 241518600 B |
| | | | | | | | | | | | | |
| &#39;System.Drawing Tiff&#39; | Job-VMCLSF | .NET Core 2.1 | medium_bw_Fax3.tiff | 486.2 ms | 23.15 ms | 3.58 ms | 1.00 | 0.00 | 1000.0000 | - | - | 5751016 B |
| &#39;ImageSharp Tiff&#39; | Job-VMCLSF | .NET Core 2.1 | medium_bw_Fax3.tiff | 4,150.2 ms | 322.16 ms | 83.66 ms | 8.47 | 0.16 | - | - | - | 235961088 B |
| | | | | | | | | | | | | |
| &#39;System.Drawing Tiff&#39; | Job-UHENIY | .NET Core 3.1 | medium_bw_Fax3.tiff | 490.1 ms | 12.76 ms | 3.31 ms | 1.00 | 0.00 | - | - | - | 176 B |
| &#39;ImageSharp Tiff&#39; | Job-UHENIY | .NET Core 3.1 | medium_bw_Fax3.tiff | 3,582.9 ms | 61.89 ms | 16.07 ms | 7.31 | 0.06 | - | - | - | 235961496 B |
| | | | | | | | | | | | | |
| **&#39;System.Drawing Tiff&#39;** | **Job-KSIANY** | **.NET 4.7.2** | **medium_bw_Rle.tiff** | **499.1 ms** | **26.71 ms** | **6.94 ms** | **1.00** | **0.00** | **1000.0000** | **-** | **-** | **8494472 B** |
| &#39;ImageSharp Tiff&#39; | Job-KSIANY | .NET 4.7.2 | medium_bw_Rle.tiff | 7,290.4 ms | 938.28 ms | 243.67 ms | 14.61 | 0.33 | 1000.0000 | 1000.0000 | 1000.0000 | 237020384 B |
| | | | | | | | | | | | | |
| &#39;System.Drawing Tiff&#39; | Job-VMCLSF | .NET Core 2.1 | medium_bw_Rle.tiff | 490.6 ms | 30.19 ms | 4.67 ms | 1.00 | 0.00 | 1000.0000 | - | - | 8475688 B |
| &#39;ImageSharp Tiff&#39; | Job-VMCLSF | .NET Core 2.1 | medium_bw_Rle.tiff | 4,230.2 ms | 35.59 ms | 5.51 ms | 8.62 | 0.08 | - | - | - | 235961944 B |
| | | | | | | | | | | | | |
| &#39;System.Drawing Tiff&#39; | Job-UHENIY | .NET Core 3.1 | medium_bw_Rle.tiff | 487.6 ms | 12.07 ms | 1.87 ms | 1.00 | 0.00 | - | - | - | 176 B |
| &#39;ImageSharp Tiff&#39; | Job-UHENIY | .NET Core 3.1 | medium_bw_Rle.tiff | 3,647.4 ms | 42.62 ms | 11.07 ms | 7.48 | 0.04 | - | - | - | 235962184 B |
| | | | | | | | | | | | | |
| **&#39;System.Drawing Tiff&#39;** | **Job-KSIANY** | **.NET 4.7.2** | **medium_grayscale_uncompressed.tiff** | **606.7 ms** | **20.45 ms** | **5.31 ms** | **1.00** | **0.00** | **18000.0000** | **-** | **-** | **90301696 B** |
| &#39;ImageSharp Tiff&#39; | Job-KSIANY | .NET 4.7.2 | medium_grayscale_uncompressed.tiff | 1,852.9 ms | 6.74 ms | 1.75 ms | 3.05 | 0.03 | - | - | - | 235970584 B |
| | | | | | | | | | | | | |
| &#39;System.Drawing Tiff&#39; | Job-VMCLSF | .NET Core 2.1 | medium_grayscale_uncompressed.tiff | 606.6 ms | 36.58 ms | 9.50 ms | 1.00 | 0.00 | 18000.0000 | - | - | 90104048 B |
| &#39;ImageSharp Tiff&#39; | Job-VMCLSF | .NET Core 2.1 | medium_grayscale_uncompressed.tiff | 764.3 ms | 15.69 ms | 4.08 ms | 1.26 | 0.02 | - | - | - | 235965376 B |
| | | | | | | | | | | | | |
| &#39;System.Drawing Tiff&#39; | Job-UHENIY | .NET Core 3.1 | medium_grayscale_uncompressed.tiff | 569.6 ms | 17.44 ms | 4.53 ms | 1.00 | 0.00 | - | - | - | 176 B |
| &#39;ImageSharp Tiff&#39; | Job-UHENIY | .NET Core 3.1 | medium_grayscale_uncompressed.tiff | 655.2 ms | 17.48 ms | 4.54 ms | 1.15 | 0.01 | - | - | - | 235965488 B |
| | | | | | | | | | | | | |
| **&#39;System.Drawing Tiff&#39;** | **Job-KSIANY** | **.NET 4.7.2** | **medium_palette_uncompressed.tiff** | **578.0 ms** | **22.32 ms** | **5.80 ms** | **1.00** | **0.00** | **18000.0000** | **-** | **-** | **90301696 B** |
| &#39;ImageSharp Tiff&#39; | Job-KSIANY | .NET 4.7.2 | medium_palette_uncompressed.tiff | 3,336.9 ms | 21.42 ms | 5.56 ms | 5.77 | 0.07 | - | - | - | 236003608 B |
| | | | | | | | | | | | | |
| &#39;System.Drawing Tiff&#39; | Job-VMCLSF | .NET Core 2.1 | medium_palette_uncompressed.tiff | 601.9 ms | 40.85 ms | 6.32 ms | 1.00 | 0.00 | 18000.0000 | - | - | 90107368 B |
| &#39;ImageSharp Tiff&#39; | Job-VMCLSF | .NET Core 2.1 | medium_palette_uncompressed.tiff | 1,971.9 ms | 15.69 ms | 4.07 ms | 3.28 | 0.04 | - | - | - | 235996096 B |
| | | | | | | | | | | | | |
| &#39;System.Drawing Tiff&#39; | Job-UHENIY | .NET Core 3.1 | medium_palette_uncompressed.tiff | 566.1 ms | 28.06 ms | 4.34 ms | 1.00 | 0.00 | - | - | - | 176 B |
| &#39;ImageSharp Tiff&#39; | Job-UHENIY | .NET Core 3.1 | medium_palette_uncompressed.tiff | 1,664.1 ms | 11.59 ms | 1.79 ms | 2.94 | 0.02 | - | - | - | 235996208 B |
| | | | | | | | | | | | | |
| **&#39;System.Drawing Tiff&#39;** | **Job-KSIANY** | **.NET 4.7.2** | **medium_rgb_deflate.tiff** | **357.4 ms** | **15.54 ms** | **2.40 ms** | **1.00** | **0.00** | **3000.0000** | **-** | **-** | **9662560 B** |
| &#39;ImageSharp Tiff&#39; | Job-KSIANY | .NET 4.7.2 | medium_rgb_deflate.tiff | 776.1 ms | 14.51 ms | 3.77 ms | 2.17 | 0.01 | 22000.0000 | 1000.0000 | 1000.0000 | 303476856 B |
| | | | | | | | | | | | | |
| &#39;System.Drawing Tiff&#39; | Job-VMCLSF | .NET Core 2.1 | medium_rgb_deflate.tiff | 359.7 ms | 12.29 ms | 3.19 ms | 1.00 | 0.00 | 3000.0000 | - | - | 9629400 B |
| &#39;ImageSharp Tiff&#39; | Job-VMCLSF | .NET Core 2.1 | medium_rgb_deflate.tiff | 554.5 ms | 16.78 ms | 4.36 ms | 1.54 | 0.02 | 2000.0000 | 1000.0000 | 1000.0000 | 239716144 B |
| | | | | | | | | | | | | |
| &#39;System.Drawing Tiff&#39; | Job-UHENIY | .NET Core 3.1 | medium_rgb_deflate.tiff | 353.2 ms | 7.22 ms | 1.12 ms | 1.00 | 0.00 | - | - | - | 176 B |
| &#39;ImageSharp Tiff&#39; | Job-UHENIY | .NET Core 3.1 | medium_rgb_deflate.tiff | 557.1 ms | 10.79 ms | 2.80 ms | 1.58 | 0.00 | 2000.0000 | 1000.0000 | 1000.0000 | 239470552 B |
| | | | | | | | | | | | | |
| **&#39;System.Drawing Tiff&#39;** | **Job-KSIANY** | **.NET 4.7.2** | **medium_rgb_lzw.tiff** | **511.0 ms** | **6.43 ms** | **1.67 ms** | **1.00** | **0.00** | **3000.0000** | **-** | **-** | **11600840 B** |
| &#39;ImageSharp Tiff&#39; | Job-KSIANY | .NET 4.7.2 | medium_rgb_lzw.tiff | 2,691.6 ms | 16.81 ms | 2.60 ms | 5.27 | 0.02 | - | - | - | 236044312 B |
| | | | | | | | | | | | | |
| &#39;System.Drawing Tiff&#39; | Job-VMCLSF | .NET Core 2.1 | medium_rgb_lzw.tiff | 511.4 ms | 11.44 ms | 1.77 ms | 1.00 | 0.00 | 3000.0000 | - | - | 11569776 B |
| &#39;ImageSharp Tiff&#39; | Job-VMCLSF | .NET Core 2.1 | medium_rgb_lzw.tiff | 1,654.1 ms | 12.42 ms | 1.92 ms | 3.23 | 0.01 | - | - | - | 236041592 B |
| | | | | | | | | | | | | |
| &#39;System.Drawing Tiff&#39; | Job-UHENIY | .NET Core 3.1 | medium_rgb_lzw.tiff | 507.7 ms | 8.89 ms | 2.31 ms | 1.00 | 0.00 | - | - | - | 176 B |
| &#39;ImageSharp Tiff&#39; | Job-UHENIY | .NET Core 3.1 | medium_rgb_lzw.tiff | 1,689.5 ms | 40.41 ms | 6.25 ms | 3.33 | 0.03 | - | - | - | 236041656 B |
| | | | | | | | | | | | | |
| **&#39;System.Drawing Tiff&#39;** | **Job-KSIANY** | **.NET 4.7.2** | **medium_rgb_packbits.tiff** | **776.8 ms** | **31.69 ms** | **8.23 ms** | **1.00** | **0.00** | **56000.0000** | **-** | **-** | **304057016 B** |
| &#39;ImageSharp Tiff&#39; | Job-KSIANY | .NET 4.7.2 | medium_rgb_packbits.tiff | 531.2 ms | 23.17 ms | 6.02 ms | 0.68 | 0.01 | - | - | - | 236003352 B |
| | | | | | | | | | | | | |
| &#39;System.Drawing Tiff&#39; | Job-VMCLSF | .NET Core 2.1 | medium_rgb_packbits.tiff | 764.2 ms | 41.43 ms | 6.41 ms | 1.00 | 0.00 | 56000.0000 | - | - | 303861120 B |
| &#39;ImageSharp Tiff&#39; | Job-VMCLSF | .NET Core 2.1 | medium_rgb_packbits.tiff | 300.0 ms | 4.39 ms | 0.68 ms | 0.39 | 0.00 | - | - | - | 235998408 B |
| | | | | | | | | | | | | |
| &#39;System.Drawing Tiff&#39; | Job-UHENIY | .NET Core 3.1 | medium_rgb_packbits.tiff | 659.1 ms | 34.59 ms | 8.98 ms | 1.00 | 0.00 | - | - | - | 176 B |
| &#39;ImageSharp Tiff&#39; | Job-UHENIY | .NET Core 3.1 | medium_rgb_packbits.tiff | 297.5 ms | 21.13 ms | 5.49 ms | 0.45 | 0.00 | - | - | - | 235998520 B |
| | | | | | | | | | | | | |
| **&#39;System.Drawing Tiff&#39;** | **Job-KSIANY** | **.NET 4.7.2** | **medium_rgb_uncompressed.tiff** | **742.5 ms** | **50.45 ms** | **13.10 ms** | **1.00** | **0.00** | **55000.0000** | **-** | **-** | **302644272 B** |
| &#39;ImageSharp Tiff&#39; | Job-KSIANY | .NET 4.7.2 | medium_rgb_uncompressed.tiff | 414.3 ms | 15.37 ms | 3.99 ms | 0.56 | 0.01 | - | - | - | 235986968 B |
| | | | | | | | | | | | | |
| &#39;System.Drawing Tiff&#39; | Job-VMCLSF | .NET Core 2.1 | medium_rgb_uncompressed.tiff | 750.2 ms | 74.13 ms | 19.25 ms | 1.00 | 0.00 | 55000.0000 | - | - | 302448096 B |
| &#39;ImageSharp Tiff&#39; | Job-VMCLSF | .NET Core 2.1 | medium_rgb_uncompressed.tiff | 283.6 ms | 21.56 ms | 5.60 ms | 0.38 | 0.01 | - | - | - | 235981128 B |
| | | | | | | | | | | | | |
| &#39;System.Drawing Tiff&#39; | Job-UHENIY | .NET Core 3.1 | medium_rgb_uncompressed.tiff | 662.6 ms | 49.79 ms | 12.93 ms | 1.00 | 0.00 | - | - | - | 176 B |
| &#39;ImageSharp Tiff&#39; | Job-UHENIY | .NET Core 3.1 | medium_rgb_uncompressed.tiff | 278.6 ms | 9.48 ms | 2.46 ms | 0.42 | 0.01 | - | - | - | 235981352 B |

81
tests/Images/Input/Tiff/Benchmarks/SixLabors.ImageSharp.Benchmarks.Codecs.DecodeTiffBig-report.html

@ -1,81 +0,0 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8' />
<title>SixLabors.ImageSharp.Benchmarks.Codecs.DecodeTiffBig-20201209-175548</title>
<style type="text/css">
table { border-collapse: collapse; display: block; width: 100%; overflow: auto; }
td, th { padding: 6px 13px; border: 1px solid #ddd; text-align: right; }
tr { background-color: #fff; border-top: 1px solid #ccc; }
tr:nth-child(even) { background: #f8f8f8; }
</style>
</head>
<body>
<pre><code>
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-3610QM CPU 2.30GHz (Ivy Bridge), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=5.0.100
[Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
Job-KSIANY : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT
Job-VMCLSF : .NET Core 2.1.23 (CoreCLR 4.6.29321.03, CoreFX 4.6.29321.01), X64 RyuJIT
Job-UHENIY : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
</code></pre>
<pre><code>InvocationCount=1 IterationCount=5 LaunchCount=1
UnrollFactor=1 WarmupCount=3
</code></pre>
<table>
<thead><tr><th> Method</th><th> Job</th><th>Runtime</th><th> TestImage</th><th>Mean</th><th>Error</th><th>StdDev</th><th>Ratio</th><th>RatioSD</th><th>Gen 0</th><th>Gen 1</th><th>Gen 2</th><th>Allocated</th>
</tr>
</thead><tbody><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-KSIANY</td><td>.NET 4.7.2</td><td>medium_bw_Fax3.tiff</td><td>491.6 ms</td><td>20.40 ms</td><td>5.30 ms</td><td>1.00</td><td>0.00</td><td>1000.0000</td><td>-</td><td>-</td><td>5768128 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-KSIANY</td><td>.NET 4.7.2</td><td>medium_bw_Fax3.tiff</td><td>6,970.2 ms</td><td>70.64 ms</td><td>10.93 ms</td><td>14.23</td><td>0.12</td><td>1000.0000</td><td>1000.0000</td><td>1000.0000</td><td>241518600 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-VMCLSF</td><td>.NET Core 2.1</td><td>medium_bw_Fax3.tiff</td><td>486.2 ms</td><td>23.15 ms</td><td>3.58 ms</td><td>1.00</td><td>0.00</td><td>1000.0000</td><td>-</td><td>-</td><td>5751016 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-VMCLSF</td><td>.NET Core 2.1</td><td>medium_bw_Fax3.tiff</td><td>4,150.2 ms</td><td>322.16 ms</td><td>83.66 ms</td><td>8.47</td><td>0.16</td><td>-</td><td>-</td><td>-</td><td>235961088 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-UHENIY</td><td>.NET Core 3.1</td><td>medium_bw_Fax3.tiff</td><td>490.1 ms</td><td>12.76 ms</td><td>3.31 ms</td><td>1.00</td><td>0.00</td><td>-</td><td>-</td><td>-</td><td>176 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-UHENIY</td><td>.NET Core 3.1</td><td>medium_bw_Fax3.tiff</td><td>3,582.9 ms</td><td>61.89 ms</td><td>16.07 ms</td><td>7.31</td><td>0.06</td><td>-</td><td>-</td><td>-</td><td>235961496 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-KSIANY</td><td>.NET 4.7.2</td><td>medium_bw_Rle.tiff</td><td>499.1 ms</td><td>26.71 ms</td><td>6.94 ms</td><td>1.00</td><td>0.00</td><td>1000.0000</td><td>-</td><td>-</td><td>8494472 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-KSIANY</td><td>.NET 4.7.2</td><td>medium_bw_Rle.tiff</td><td>7,290.4 ms</td><td>938.28 ms</td><td>243.67 ms</td><td>14.61</td><td>0.33</td><td>1000.0000</td><td>1000.0000</td><td>1000.0000</td><td>237020384 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-VMCLSF</td><td>.NET Core 2.1</td><td>medium_bw_Rle.tiff</td><td>490.6 ms</td><td>30.19 ms</td><td>4.67 ms</td><td>1.00</td><td>0.00</td><td>1000.0000</td><td>-</td><td>-</td><td>8475688 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-VMCLSF</td><td>.NET Core 2.1</td><td>medium_bw_Rle.tiff</td><td>4,230.2 ms</td><td>35.59 ms</td><td>5.51 ms</td><td>8.62</td><td>0.08</td><td>-</td><td>-</td><td>-</td><td>235961944 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-UHENIY</td><td>.NET Core 3.1</td><td>medium_bw_Rle.tiff</td><td>487.6 ms</td><td>12.07 ms</td><td>1.87 ms</td><td>1.00</td><td>0.00</td><td>-</td><td>-</td><td>-</td><td>176 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-UHENIY</td><td>.NET Core 3.1</td><td>medium_bw_Rle.tiff</td><td>3,647.4 ms</td><td>42.62 ms</td><td>11.07 ms</td><td>7.48</td><td>0.04</td><td>-</td><td>-</td><td>-</td><td>235962184 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-KSIANY</td><td>.NET 4.7.2</td><td>medium_grayscale_uncompressed.tiff</td><td>606.7 ms</td><td>20.45 ms</td><td>5.31 ms</td><td>1.00</td><td>0.00</td><td>18000.0000</td><td>-</td><td>-</td><td>90301696 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-KSIANY</td><td>.NET 4.7.2</td><td>medium_grayscale_uncompressed.tiff</td><td>1,852.9 ms</td><td>6.74 ms</td><td>1.75 ms</td><td>3.05</td><td>0.03</td><td>-</td><td>-</td><td>-</td><td>235970584 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-VMCLSF</td><td>.NET Core 2.1</td><td>medium_grayscale_uncompressed.tiff</td><td>606.6 ms</td><td>36.58 ms</td><td>9.50 ms</td><td>1.00</td><td>0.00</td><td>18000.0000</td><td>-</td><td>-</td><td>90104048 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-VMCLSF</td><td>.NET Core 2.1</td><td>medium_grayscale_uncompressed.tiff</td><td>764.3 ms</td><td>15.69 ms</td><td>4.08 ms</td><td>1.26</td><td>0.02</td><td>-</td><td>-</td><td>-</td><td>235965376 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-UHENIY</td><td>.NET Core 3.1</td><td>medium_grayscale_uncompressed.tiff</td><td>569.6 ms</td><td>17.44 ms</td><td>4.53 ms</td><td>1.00</td><td>0.00</td><td>-</td><td>-</td><td>-</td><td>176 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-UHENIY</td><td>.NET Core 3.1</td><td>medium_grayscale_uncompressed.tiff</td><td>655.2 ms</td><td>17.48 ms</td><td>4.54 ms</td><td>1.15</td><td>0.01</td><td>-</td><td>-</td><td>-</td><td>235965488 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-KSIANY</td><td>.NET 4.7.2</td><td>medium_palette_uncompressed.tiff</td><td>578.0 ms</td><td>22.32 ms</td><td>5.80 ms</td><td>1.00</td><td>0.00</td><td>18000.0000</td><td>-</td><td>-</td><td>90301696 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-KSIANY</td><td>.NET 4.7.2</td><td>medium_palette_uncompressed.tiff</td><td>3,336.9 ms</td><td>21.42 ms</td><td>5.56 ms</td><td>5.77</td><td>0.07</td><td>-</td><td>-</td><td>-</td><td>236003608 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-VMCLSF</td><td>.NET Core 2.1</td><td>medium_palette_uncompressed.tiff</td><td>601.9 ms</td><td>40.85 ms</td><td>6.32 ms</td><td>1.00</td><td>0.00</td><td>18000.0000</td><td>-</td><td>-</td><td>90107368 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-VMCLSF</td><td>.NET Core 2.1</td><td>medium_palette_uncompressed.tiff</td><td>1,971.9 ms</td><td>15.69 ms</td><td>4.07 ms</td><td>3.28</td><td>0.04</td><td>-</td><td>-</td><td>-</td><td>235996096 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-UHENIY</td><td>.NET Core 3.1</td><td>medium_palette_uncompressed.tiff</td><td>566.1 ms</td><td>28.06 ms</td><td>4.34 ms</td><td>1.00</td><td>0.00</td><td>-</td><td>-</td><td>-</td><td>176 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-UHENIY</td><td>.NET Core 3.1</td><td>medium_palette_uncompressed.tiff</td><td>1,664.1 ms</td><td>11.59 ms</td><td>1.79 ms</td><td>2.94</td><td>0.02</td><td>-</td><td>-</td><td>-</td><td>235996208 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-KSIANY</td><td>.NET 4.7.2</td><td>medium_rgb_deflate.tiff</td><td>357.4 ms</td><td>15.54 ms</td><td>2.40 ms</td><td>1.00</td><td>0.00</td><td>3000.0000</td><td>-</td><td>-</td><td>9662560 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-KSIANY</td><td>.NET 4.7.2</td><td>medium_rgb_deflate.tiff</td><td>776.1 ms</td><td>14.51 ms</td><td>3.77 ms</td><td>2.17</td><td>0.01</td><td>22000.0000</td><td>1000.0000</td><td>1000.0000</td><td>303476856 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-VMCLSF</td><td>.NET Core 2.1</td><td>medium_rgb_deflate.tiff</td><td>359.7 ms</td><td>12.29 ms</td><td>3.19 ms</td><td>1.00</td><td>0.00</td><td>3000.0000</td><td>-</td><td>-</td><td>9629400 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-VMCLSF</td><td>.NET Core 2.1</td><td>medium_rgb_deflate.tiff</td><td>554.5 ms</td><td>16.78 ms</td><td>4.36 ms</td><td>1.54</td><td>0.02</td><td>2000.0000</td><td>1000.0000</td><td>1000.0000</td><td>239716144 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-UHENIY</td><td>.NET Core 3.1</td><td>medium_rgb_deflate.tiff</td><td>353.2 ms</td><td>7.22 ms</td><td>1.12 ms</td><td>1.00</td><td>0.00</td><td>-</td><td>-</td><td>-</td><td>176 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-UHENIY</td><td>.NET Core 3.1</td><td>medium_rgb_deflate.tiff</td><td>557.1 ms</td><td>10.79 ms</td><td>2.80 ms</td><td>1.58</td><td>0.00</td><td>2000.0000</td><td>1000.0000</td><td>1000.0000</td><td>239470552 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-KSIANY</td><td>.NET 4.7.2</td><td>medium_rgb_lzw.tiff</td><td>511.0 ms</td><td>6.43 ms</td><td>1.67 ms</td><td>1.00</td><td>0.00</td><td>3000.0000</td><td>-</td><td>-</td><td>11600840 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-KSIANY</td><td>.NET 4.7.2</td><td>medium_rgb_lzw.tiff</td><td>2,691.6 ms</td><td>16.81 ms</td><td>2.60 ms</td><td>5.27</td><td>0.02</td><td>-</td><td>-</td><td>-</td><td>236044312 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-VMCLSF</td><td>.NET Core 2.1</td><td>medium_rgb_lzw.tiff</td><td>511.4 ms</td><td>11.44 ms</td><td>1.77 ms</td><td>1.00</td><td>0.00</td><td>3000.0000</td><td>-</td><td>-</td><td>11569776 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-VMCLSF</td><td>.NET Core 2.1</td><td>medium_rgb_lzw.tiff</td><td>1,654.1 ms</td><td>12.42 ms</td><td>1.92 ms</td><td>3.23</td><td>0.01</td><td>-</td><td>-</td><td>-</td><td>236041592 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-UHENIY</td><td>.NET Core 3.1</td><td>medium_rgb_lzw.tiff</td><td>507.7 ms</td><td>8.89 ms</td><td>2.31 ms</td><td>1.00</td><td>0.00</td><td>-</td><td>-</td><td>-</td><td>176 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-UHENIY</td><td>.NET Core 3.1</td><td>medium_rgb_lzw.tiff</td><td>1,689.5 ms</td><td>40.41 ms</td><td>6.25 ms</td><td>3.33</td><td>0.03</td><td>-</td><td>-</td><td>-</td><td>236041656 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-KSIANY</td><td>.NET 4.7.2</td><td>medium_rgb_packbits.tiff</td><td>776.8 ms</td><td>31.69 ms</td><td>8.23 ms</td><td>1.00</td><td>0.00</td><td>56000.0000</td><td>-</td><td>-</td><td>304057016 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-KSIANY</td><td>.NET 4.7.2</td><td>medium_rgb_packbits.tiff</td><td>531.2 ms</td><td>23.17 ms</td><td>6.02 ms</td><td>0.68</td><td>0.01</td><td>-</td><td>-</td><td>-</td><td>236003352 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-VMCLSF</td><td>.NET Core 2.1</td><td>medium_rgb_packbits.tiff</td><td>764.2 ms</td><td>41.43 ms</td><td>6.41 ms</td><td>1.00</td><td>0.00</td><td>56000.0000</td><td>-</td><td>-</td><td>303861120 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-VMCLSF</td><td>.NET Core 2.1</td><td>medium_rgb_packbits.tiff</td><td>300.0 ms</td><td>4.39 ms</td><td>0.68 ms</td><td>0.39</td><td>0.00</td><td>-</td><td>-</td><td>-</td><td>235998408 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-UHENIY</td><td>.NET Core 3.1</td><td>medium_rgb_packbits.tiff</td><td>659.1 ms</td><td>34.59 ms</td><td>8.98 ms</td><td>1.00</td><td>0.00</td><td>-</td><td>-</td><td>-</td><td>176 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-UHENIY</td><td>.NET Core 3.1</td><td>medium_rgb_packbits.tiff</td><td>297.5 ms</td><td>21.13 ms</td><td>5.49 ms</td><td>0.45</td><td>0.00</td><td>-</td><td>-</td><td>-</td><td>235998520 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-KSIANY</td><td>.NET 4.7.2</td><td>medium_rgb_uncompressed.tiff</td><td>742.5 ms</td><td>50.45 ms</td><td>13.10 ms</td><td>1.00</td><td>0.00</td><td>55000.0000</td><td>-</td><td>-</td><td>302644272 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-KSIANY</td><td>.NET 4.7.2</td><td>medium_rgb_uncompressed.tiff</td><td>414.3 ms</td><td>15.37 ms</td><td>3.99 ms</td><td>0.56</td><td>0.01</td><td>-</td><td>-</td><td>-</td><td>235986968 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-VMCLSF</td><td>.NET Core 2.1</td><td>medium_rgb_uncompressed.tiff</td><td>750.2 ms</td><td>74.13 ms</td><td>19.25 ms</td><td>1.00</td><td>0.00</td><td>55000.0000</td><td>-</td><td>-</td><td>302448096 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-VMCLSF</td><td>.NET Core 2.1</td><td>medium_rgb_uncompressed.tiff</td><td>283.6 ms</td><td>21.56 ms</td><td>5.60 ms</td><td>0.38</td><td>0.01</td><td>-</td><td>-</td><td>-</td><td>235981128 B</td>
</tr><tr><td>&#39;System.Drawing Tiff&#39;</td><td>Job-UHENIY</td><td>.NET Core 3.1</td><td>medium_rgb_uncompressed.tiff</td><td>662.6 ms</td><td>49.79 ms</td><td>12.93 ms</td><td>1.00</td><td>0.00</td><td>-</td><td>-</td><td>-</td><td>176 B</td>
</tr><tr><td>&#39;ImageSharp Tiff&#39;</td><td>Job-UHENIY</td><td>.NET Core 3.1</td><td>medium_rgb_uncompressed.tiff</td><td>278.6 ms</td><td>9.48 ms</td><td>2.46 ms</td><td>0.42</td><td>0.01</td><td>-</td><td>-</td><td>-</td><td>235981352 B</td>
</tr></tbody></table>
</body>
</html>
Loading…
Cancel
Save