Browse Source

Support multi strip encoding for tiff. Improve performance and memory usage of decoders and encoders.

# Conflicts:
#	tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
pull/1570/head
Ildar Khayrutdinov 5 years ago
parent
commit
9e139882c4
  1. 1
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  2. 38
      src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs
  3. 59
      src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs
  4. 36
      src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs
  5. 28
      src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs
  6. 39
      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. 7
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs
  10. 7
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs
  11. 15
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs
  12. 12
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs
  13. 20
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs
  14. 27
      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. 47
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs
  18. 25
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs
  19. 32
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompresor.cs
  20. 61
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs
  21. 2
      src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs
  22. 27
      src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
  23. 10
      src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
  24. 6
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  25. 2
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  26. 6
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  27. 135
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  28. 26
      src/ImageSharp/Formats/Tiff/TiffEncoderPixelStorageMethod.cs
  29. 5
      src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs
  30. 103
      src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter.cs
  31. 225
      src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter.cs
  32. 19
      src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs
  33. 45
      src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter.cs
  34. 162
      src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter.cs
  35. 212
      src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter.cs
  36. 165
      src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter.cs
  37. 1
      src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs
  38. 4
      tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
  39. 13
      tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
  40. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs
  41. 3
      tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
  42. 1
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs
  43. 102
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  44. 2
      tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs

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

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

@ -0,0 +1,59 @@
// 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;
public override TiffEncoderCompression Method => TiffEncoderCompression.Deflate;
public override void Initialize(int rowsPerStrip)
{
}
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(); // todo: dispose write crc
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
}
protected override void Dispose(bool disposing)
{
}
}
}

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

@ -0,0 +1,36 @@
// 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)
{
}
public override TiffEncoderCompression Method => TiffEncoderCompression.Lzw;
public override void Initialize(int rowsPerStrip) => this.lzwEncoder = new TiffLzwEncoder(this.Allocator);
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);
}
protected override void Dispose(bool disposing) => this.lzwEncoder?.Dispose();
}
}

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

@ -0,0 +1,28 @@
// 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)
{
}
public override TiffEncoderCompression Method => TiffEncoderCompression.None;
public override void Initialize(int rowsPerStrip)
{
}
public override void CompressStrip(Span<byte> rows, int height) => this.Output.Write(rows);
protected override void Dispose(bool disposing)
{
}
}
}

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

@ -0,0 +1,39 @@
// 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.Constants;
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)
{
}
public override TiffEncoderCompression Method => TiffEncoderCompression.PackBits;
public override void Initialize(int rowsPerStrip)
{
int additionalBytes = (this.BytesPerRow / 127) + 1;
this.pixelData = this.Allocator.AllocateManagedByteBuffer((this.BytesPerRow + additionalBytes) * rowsPerStrip);
}
public override void CompressStrip(Span<byte> rows, int height)
{
this.pixelData.Clear();
Span<byte> span = this.pixelData.GetSpan();
int size = PackBitsWriter.PackBits(rows, span);
this.Output.Write(span.Slice(0, size));
}
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.Equals(pixelsAsGray.Length / height, this.Width);
DebugGuard.Equals(pixelsAsGray.Length % height, 0);
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)

7
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,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel);
}
}
protected override void Dispose(bool disposing)
{
}
}
}

7
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,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel);
}
}
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;
}

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

@ -4,25 +4,27 @@
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));
protected override void Dispose(bool disposing)
{
}
}
}

20
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,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
destinationArray[i + destinationIndex] = value;
}
}
protected override void Dispose(bool disposing) => this.compressedDataMemory?.Dispose();
}
}

27
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,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso
}
}
}
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.Equals(rows.Length % width, 0);
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.Equals(rows.Length % width, 0);
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];
}
}
}

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

@ -0,0 +1,47 @@
// 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;
}
public int Width { get; }
public int BitsPerPixel { get; }
public int BytesPerRow { get; }
public TiffPredictor Predictor { get; }
protected MemoryAllocator Allocator { get; }
public void Dispose()
{
if (this.isDisposed)
{
return;
}
this.isDisposed = true;
this.Dispose(true);
}
protected abstract void Dispose(bool disposing);
}
}

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

@ -0,0 +1,25 @@
// 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
{
protected TiffBaseCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None)
: base(allocator, width, bitsPerPixel, predictor)
=> this.Output = output;
public abstract TiffEncoderCompression Method { get; }
public Stream Output { get; }
public abstract void Initialize(int rowsPerStrip);
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>

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

@ -0,0 +1,61 @@
// Copyright (c) Six Labors.
// 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;
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.Equals(compressionLevel, default(DeflateCompressionLevel));
DebugGuard.Equals(predictor, TiffPredictor.None);
return new NoCompressor(output);
case TiffEncoderCompression.PackBits:
DebugGuard.Equals(compressionLevel, default(DeflateCompressionLevel));
DebugGuard.Equals(predictor, TiffPredictor.None);
return new PackBitsCompressor(output, allocator, width, bitsPerPixel);
case TiffEncoderCompression.Deflate:
return new DeflateCompressor(output, allocator, width, bitsPerPixel, predictor, compressionLevel);
case TiffEncoderCompression.Lzw:
DebugGuard.Equals(compressionLevel, default(DeflateCompressionLevel));
return new LzwCompressor(output, allocator, width, bitsPerPixel, predictor);
case TiffEncoderCompression.CcittGroup3Fax:
DebugGuard.Equals(compressionLevel, default(DeflateCompressionLevel));
DebugGuard.Equals(predictor, TiffPredictor.None);
return new T4BitCompressor(output, allocator, width, bitsPerPixel, false);
case TiffEncoderCompression.ModifiedHuffman:
DebugGuard.Equals(compressionLevel, default(DeflateCompressionLevel));
DebugGuard.Equals(predictor, TiffPredictor.None);
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.

27
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,38 @@ 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.Equals(predictor, TiffPredictor.None);
DebugGuard.Equals(faxOptions, FaxCompressionOptions.None);
return new NoneTiffCompression();
case TiffDecoderCompressionType.PackBits:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "predictor");
DebugGuard.Equals(predictor, TiffPredictor.None);
DebugGuard.Equals(faxOptions, FaxCompressionOptions.None);
return new PackBitsTiffCompression(allocator);
case TiffDecoderCompressionType.Deflate:
DebugGuard.Equals(faxOptions, FaxCompressionOptions.None);
return new DeflateTiffCompression(allocator, width, bitsPerPixel, predictor);
case TiffDecoderCompressionType.Lzw:
DebugGuard.Equals(faxOptions, FaxCompressionOptions.None);
return new LzwTiffCompression(allocator, width, bitsPerPixel, predictor);
case TiffDecoderCompressionType.T4:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "predictor");
DebugGuard.Equals(predictor, TiffPredictor.None);
DebugGuard.Equals(faxOptions, FaxCompressionOptions.None);
return new T4TiffCompression(allocator, faxOptions, photometricInterpretation, width);
case TiffDecoderCompressionType.HuffmanRle:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "predictor");
DebugGuard.Equals(predictor, TiffPredictor.None);
DebugGuard.Equals(faxOptions, FaxCompressionOptions.None);
return new ModifiedHuffmanTiffCompression(allocator, photometricInterpretation, width);
default:
throw TiffThrowHelper.NotSupportedCompression(nameof(compressionType));
throw TiffThrowHelper.NotSupportedDecompressor(nameof(method));
}
}
}

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

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

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

2
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;

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

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

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

@ -8,6 +8,7 @@ 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;
@ -30,6 +31,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
? TiffConstants.ByteOrderLittleEndianShort
: TiffConstants.ByteOrderBigEndianShort;
private const int DefaultStripSize = 8 * 1024;
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
@ -43,7 +46,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 +58,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
private readonly DeflateCompressionLevel compressionLevel;
private readonly TiffEncoderPixelStorageMethod storageMode;
private readonly int maxStripBytes;
/// <summary>
/// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
/// </summary>
@ -68,6 +75,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
this.UseHorizontalPredictor = options.UseHorizontalPredictor;
this.compressionLevel = options.CompressionLevel;
this.storageMode = options.PixelStorageMethod;
this.maxStripBytes = options.MaxStripBytes;
}
/// <summary>
@ -105,26 +114,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 +123,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 +150,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 +158,43 @@ 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)
{
switch (this.storageMode)
{
default:
case TiffEncoderPixelStorageMethod.Auto:
case TiffEncoderPixelStorageMethod.MultiStrip:
int sz = this.maxStripBytes > 0 ? this.maxStripBytes : DefaultStripSize;
int height = sz / bytesPerRow;
return height > 0 ? (height < image.Height ? height : image.Height) : 1;
return nextIfdMarker + imageDataBytes;
case TiffEncoderPixelStorageMethod.SingleStrip:
return image.Height;
}
}
/// <summary>
@ -188,7 +203,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 +254,51 @@ 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 }
};
this.Mode = TiffEncodingMode.BiColor;
this.bitsPerPixel = TiffBitsPerPixel.Pixel1;
return;
}
var rowsPerStrip = new ExifLong(ExifTagValue.RowsPerStrip)
if (this.Mode == TiffEncodingMode.Default)
{
// All rows in one strip.
Value = (uint)image.Height
};
// 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;
}
}
var stripByteCounts = new ExifLongArray(ExifTagValue.StripByteCounts)
switch (this.Mode)
{
Value = new[] { (uint)imageDataBytes }
};
entriesCollector.Add(stripOffsets);
entriesCollector.Add(rowsPerStrip);
entriesCollector.Add(stripByteCounts);
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()

26
src/ImageSharp/Formats/Tiff/TiffEncoderPixelStorageMethod.cs

@ -0,0 +1,26 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{
/// <summary>
/// The tiff encoder pixel storage method.
/// </summary>
public enum TiffEncoderPixelStorageMethod
{
/// <summary>
/// The auto mode.
/// </summary>
Auto,
/// <summary>
/// The single strip mode.
/// </summary>
SingleStrip,
/// <summary>
/// The multi strip mode.
/// </summary>
MultiStrip,
}
}

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

103
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,74 @@ 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.Equals(this.BytesPerRow, compressor.BytesPerRow);
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.Equals(stripIndex, stripsCount);
this.AddStripTags(rowsPerStrip, stripOffsets, stripByteCounts);
}
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);
}
}

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

@ -3,208 +3,97 @@
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)));
if (compression == TiffEncoderCompression.Deflate)
{
return this.WriteBiColorDeflate(imageBlackWhite, pixelRowAsGraySpan, outputRow, compressionLevel);
}
this.imageBlackWhite = new Image<TPixel>(configuration, new ImageMetadata(), new[] { image.Clone() });
this.imageBlackWhite.Mutate(img => img.BinaryDither(default(ErrorDither)));
}
if (compression == TiffEncoderCompression.PackBits)
{
return this.WriteBiColorPackBits(imageBlackWhite, pixelRowAsGraySpan, outputRow);
}
public override int BitsPerPixel => 1;
if (compression == TiffEncoderCompression.CcittGroup3Fax)
protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor)
{
if (this.pixelsAsGray == null)
{
var bitWriter = new T4BitWriter(this.MemoryAllocator, this.Configuration);
return bitWriter.CompressImage(imageBlackWhite, pixelRowAsGraySpan, this.Output.BaseStream);
this.pixelsAsGray = this.MemoryAllocator.Allocate<byte>(height * this.Image.Width);
}
if (compression == TiffEncoderCompression.ModifiedHuffman)
{
var bitWriter = new T4BitWriter(this.MemoryAllocator, this.Configuration, useModifiedHuffman: true);
return bitWriter.CompressImage(imageBlackWhite, pixelRowAsGraySpan, this.Output.BaseStream);
}
this.pixelsAsGray.Clear();
// Write image uncompressed.
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
{
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 shift = 7 - bitIndex;
if (pixelRowAsGraySpan[x].PackedValue == 255)
{
outputRow[byteIndex] |= (byte)(1 << shift);
}
Span<byte> pixelAsGraySpan = this.pixelsAsGray.Slice(0, height * this.Image.Width);
bitIndex++;
if (bitIndex == 8)
{
byteIndex++;
bitIndex = 0;
}
}
Span<TPixel> pixels = GetStripPixels(this.imageBlackWhite.GetRootFramePixelBuffer(), y, height);
this.Output.Write(outputRow);
bytesWritten += outputRow.Length;
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixels, pixelAsGraySpan, pixels.Length);
outputRow.Clear();
if (compressor.Method == TiffEncoderCompression.CcittGroup3Fax || compressor.Method == TiffEncoderCompression.ModifiedHuffman)
{
compressor.CompressStrip(pixelAsGraySpan, height);
}
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);
int bytesWritten = 0;
for (int y = 0; y < image.Height; y++)
else
{
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++)
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;
}
int bytesPerRow = this.BytesPerRow * height;
this.bitStrip = this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerRow);
}
deflateStream.Write(outputRow);
outputRow.Clear();
}
this.bitStrip.Clear();
Span<byte> rows = this.bitStrip.GetSpan();
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>
{
// 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 xx = 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[xx++] == 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();
compressor.CompressStrip(rows, height);
}
}
return bytesWritten;
protected override void Dispose(bool disposing)
{
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);
}
}
}

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

@ -0,0 +1,45 @@
// 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;
public 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);
protected override void Dispose(bool disposing) => this.rowBuffer?.Dispose();
}
}

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

@ -2,172 +2,22 @@
// 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();
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;
}
protected override void EncodePixels(Span<TPixel> pixels, Span<byte> buffer) => PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixels, buffer, pixels.Length);
}
}

212
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,37 @@ 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)
public override int BitsPerPixel => 8;
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);
}
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 +54,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 +85,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;
}
}
}

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

@ -2,175 +2,22 @@
// 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);
}
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;
}
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
{

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
@ -39,7 +37,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);
}
@ -47,12 +46,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;

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

@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[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_Bug<TPixel>(TestImageProvider<TPixel> provider, TiffEncoderCompression compression, TiffCompression expectedCompression)
public void TiffEncoder_CorrectBiMode<TPixel>(TestImageProvider<TPixel> provider, TiffEncoderCompression compression, TiffCompression expectedCompression)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
@ -111,17 +111,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
using var output = Image.Load<Rgba32>(this.configuration, memStream);
TiffMetadata meta = output.Metadata.GetTiffMetadata();
// This is bug!
// BitsPerPixel must be 1, and compression must be eqals which was setted in encoder
Assert.NotEqual(TiffBitsPerPixel.Pixel1, meta.BitsPerPixel);
Assert.NotEqual(expectedCompression, meta.Compression);
Assert.Equal(input.Metadata.GetTiffMetadata().BitsPerPixel, meta.BitsPerPixel);
Assert.Equal(TiffCompression.None, meta.Compression);
// expected values
//// Assert.Equal(TiffBitsPerPixel.Pixel1, meta.BitsPerPixel);
//// Assert.Equal(expectedCompression, meta.Compression);
Assert.Equal(TiffBitsPerPixel.Pixel1, meta.BitsPerPixel);
Assert.Equal(expectedCompression, meta.Compression);
}
[Theory]
@ -286,6 +277,62 @@ 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(Calliphora_BiColorUncompressed, PixelTypes.L8, TiffEncodingMode.BiColor, TiffEncoderPixelStorageMethod.SingleStrip)]
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.L8, TiffEncodingMode.BiColor, TiffEncoderPixelStorageMethod.MultiStrip, 9 * 1024)]
[WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffEncodingMode.Gray, TiffEncoderPixelStorageMethod.SingleStrip)]
[WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffEncodingMode.Gray, TiffEncoderPixelStorageMethod.MultiStrip, 16 * 1024)]
[WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffEncodingMode.ColorPalette, TiffEncoderPixelStorageMethod.SingleStrip)]
[WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffEncodingMode.ColorPalette, TiffEncoderPixelStorageMethod.MultiStrip, 32 * 1024)]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncodingMode.Rgb, TiffEncoderPixelStorageMethod.SingleStrip)]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncodingMode.Rgb, TiffEncoderPixelStorageMethod.MultiStrip, 64 * 1024)]
public void TiffEncoder_StorageMethods<TPixel>(TestImageProvider<TPixel> provider, TiffEncodingMode mode, TiffEncoderPixelStorageMethod storageMethod, int maxSize = 0)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
var tiffEncoder = new TiffEncoder() { PixelStorageMethod = storageMethod, MaxStripBytes = maxSize };
using 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;
using var output = Image.Load<Rgba32>(this.configuration, memStream);
TiffFrameMetadata meta = output.Frames.RootFrame.Metadata.GetTiffMetadata();
if (storageMethod == TiffEncoderPixelStorageMethod.SingleStrip)
{
Assert.Equal(output.Height, (int)meta.RowsPerStrip);
Assert.Equal(1, meta.StripOffsets.Length);
Assert.Equal(1, meta.StripByteCounts.Length);
}
else
{
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((int)sz <= maxSize);
}
}
// compare with reference
TestTiffEncoderCore(
provider,
(TiffBitsPerPixel)inputMeta.BitsPerPixel,
mode,
Convert(inputMeta.Compression),
maxStripSize: maxSize,
storageMethod: storageMethod
);
}
private static void TestTiffEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
TiffBitsPerPixel bitsPerPixel,
@ -293,14 +340,43 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
TiffEncoderCompression compression = TiffEncoderCompression.None,
bool usePredictor = false,
bool useExactComparer = true,
int maxStripSize = 0,
TiffEncoderPixelStorageMethod? storageMethod = null,
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,
PixelStorageMethod = storageMethod ?? TiffEncoderPixelStorageMethod.Auto,
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.CcittGroup3Fax:
return TiffEncoderCompression.CcittGroup3Fax;
case TiffCompression.CcittGroup4Fax:
return TiffEncoderCompression.ModifiedHuffman;
}
}
}
}

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;

Loading…
Cancel
Save