diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs index 07c6b37b84..6673803a48 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.cs +++ b/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 { diff --git a/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs b/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs index 9c857eccde..597a91b68c 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs +++ b/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 buffer, int bufferPos, int bitPos) - { - buffer[bufferPos] |= (byte)(1 << (7 - bitPos)); - } + public static void WriteBit(Span buffer, int bufferPos, int bitPos) => buffer[bufferPos] |= (byte)(1 << (7 - bitPos)); - public static void WriteZeroBit(Span buffer, int bufferPos, int bitPos) - { - buffer[bufferPos] = (byte)(buffer[bufferPos] & ~(1 << (7 - bitPos))); - } + public static void WriteZeroBit(Span buffer, int bufferPos, int bitPos) => buffer[bufferPos] = (byte)(buffer[bufferPos] & ~(1 << (7 - bitPos))); } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs new file mode 100644 index 0000000000..46cb63a86e --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors +{ + internal class DeflateCompressor : TiffBaseCompressor + { + private readonly DeflateCompressionLevel compressionLevel; + + private readonly MemoryStream memoryStream = new MemoryStream(); + + public DeflateCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor, DeflateCompressionLevel compressionLevel) + : base(output, allocator, width, bitsPerPixel, predictor) + => this.compressionLevel = compressionLevel; + + /// + public override TiffEncoderCompression Method => TiffEncoderCompression.Deflate; + + /// + public override void Initialize(int rowsPerStrip) + { + } + + /// + public override void CompressStrip(Span rows, int height) + { + this.memoryStream.Seek(0, SeekOrigin.Begin); + using var stream = new ZlibDeflateStream(this.Allocator, this.memoryStream, this.compressionLevel); + + if (this.Predictor == TiffPredictor.Horizontal) + { + HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel); + } + + stream.Write(rows); + + stream.Flush(); + stream.Dispose(); + + int size = (int)this.memoryStream.Position; + +#if !NETSTANDARD1_3 + byte[] buffer = this.memoryStream.GetBuffer(); + this.Output.Write(buffer, 0, size); +#else + this.memoryStream.SetLength(size); + this.memoryStream.Position = 0; + this.memoryStream.CopyTo(this.Output); +#endif + } + + /// + protected override void Dispose(bool disposing) + { + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs new file mode 100644 index 0000000000..d29cf61573 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors +{ + internal class LzwCompressor : TiffBaseCompressor + { + private TiffLzwEncoder lzwEncoder; + + public LzwCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor) + : base(output, allocator, width, bitsPerPixel, predictor) + { + } + + /// + public override TiffEncoderCompression Method => TiffEncoderCompression.Lzw; + + /// + public override void Initialize(int rowsPerStrip) => this.lzwEncoder = new TiffLzwEncoder(this.Allocator); + + /// + public override void CompressStrip(Span 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(); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs new file mode 100644 index 0000000000..f4a902b46a --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; + +namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors +{ + internal class NoCompressor : TiffBaseCompressor + { + public NoCompressor(Stream output) + : base(output, default, default, default) + { + } + + /// + public override TiffEncoderCompression Method => TiffEncoderCompression.None; + + /// + public override void Initialize(int rowsPerStrip) + { + } + + /// + public override void CompressStrip(Span rows, int height) => this.Output.Write(rows); + + /// + protected override void Dispose(bool disposing) + { + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs new file mode 100644 index 0000000000..5353b17c3c --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors +{ + internal class PackBitsCompressor : TiffBaseCompressor + { + private IManagedByteBuffer pixelData; + + public PackBitsCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel) + : base(output, allocator, width, bitsPerPixel) + { + } + + /// + public override TiffEncoderCompression Method => TiffEncoderCompression.PackBits; + + /// + public override void Initialize(int rowsPerStrip) + { + int additionalBytes = ((this.BytesPerRow + 126) / 127) + 1; + this.pixelData = this.Allocator.AllocateManagedByteBuffer(this.BytesPerRow + additionalBytes); + } + + /// + public override void CompressStrip(Span rows, int height) + { + DebugGuard.IsTrue(rows.Length % height == 0, "Invalid height"); + DebugGuard.IsTrue(this.BytesPerRow == rows.Length / height, "The widths must match"); + + Span span = this.pixelData.GetSpan(); + for (int i = 0; i < height; i++) + { + Span row = rows.Slice(i * this.BytesPerRow, this.BytesPerRow); + int size = PackBitsWriter.PackBits(row, span); + this.Output.Write(span.Slice(0, size)); + } + } + + /// + protected override void Dispose(bool disposing) => this.pixelData?.Dispose(); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitWriter.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs similarity index 88% rename from src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitWriter.cs rename to src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs index 99e2a148c8..13d19f7ff8 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitWriter.cs +++ b/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 { /// /// Bitwriter for writing compressed CCITT T4 1D data. /// - 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; + /// + /// The modified huffman is basically the same as CCITT T4, but without EOL markers and padding at the end of the rows. + /// + private readonly bool useModifiedHuffman; - private readonly Configuration configuration; + private IMemoryOwner compressedDataBuffer; private int bytePosition; private byte bitPosition; /// - /// The modified huffman is basically the same as CCITT T4, but without EOL markers and padding at the end of the rows. - /// - private readonly bool useModifiedHuffman; - - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The memory allocator. - /// The configuration. + /// The output. + /// The allocator. + /// The width. + /// The bits per pixel. /// Indicates if the modified huffman RLE should be used. - 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; } - /// - /// Writes a image compressed with CCITT T4 to the stream. - /// - /// The pixel data. - /// The image to write to the stream. This has to be a bi-color image. - /// A span for converting a pixel row to gray. - /// The stream to write to. - /// The number of bytes written to the stream. - public int CompressImage(Image image, Span pixelRowAsGray, Stream stream) - where TPixel : unmanaged, IPixel + 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 compressedDataBuffer = this.memoryAllocator.Allocate(maxNeededBytes, AllocationOptions.Clean); - Span compressedData = compressedDataBuffer.GetSpan(); + int maxNeededBytes = this.Width * rowsPerStrip; + this.compressedDataBuffer = this.Allocator.Allocate(maxNeededBytes); + } + + /// Writes a image compressed with CCITT T4 to the stream. + /// The pixels as 8-bit gray array. + /// The strip height. + public override void CompressStrip(Span pixelsAsGray, int height) + { + DebugGuard.IsTrue(pixelsAsGray.Length / height == this.Width, "Values must be equals"); + DebugGuard.IsTrue(pixelsAsGray.Length % height == 0, "Values must be equals"); + + this.compressedDataBuffer.Clear(); + Span 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 pixelRow = image.GetPixelRowSpan(y); - PixelOperations.Instance.ToL8(this.configuration, pixelRow, pixelRowAsGray); int x = 0; - while (x < image.Width) + + Span 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 compressedData) { if (this.useModifiedHuffman) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs index 93876c0719..0e73b9fb46 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs +++ b/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 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 children; @@ -95,31 +93,27 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils /// /// Initializes a new instance of the class. /// - /// The data to compress. /// The memory allocator. - public TiffLzwEncoder(MemoryAllocator memoryAllocator, IMemoryOwner data) + public TiffLzwEncoder(MemoryAllocator memoryAllocator) { - this.data = data; - this.children = memoryAllocator.Allocate(TableSize, AllocationOptions.Clean); - this.siblings = memoryAllocator.Allocate(TableSize, AllocationOptions.Clean); - this.suffixes = memoryAllocator.Allocate(TableSize, AllocationOptions.Clean); - - this.parent = -1; - this.bitsPerCode = MinBits; - this.nextValidCode = EoiCode + 1; - this.maxCode = (1 << this.bitsPerCode) - 1; + this.children = memoryAllocator.Allocate(TableSize); + this.siblings = memoryAllocator.Allocate(TableSize); + this.suffixes = memoryAllocator.Allocate(TableSize); } /// /// Encodes and compresses the indexed pixels to the stream. /// + /// The data to compress. /// The stream to write to. - public void Encode(Stream stream) + public void Encode(Span data, Stream stream) { + this.Reset(); + Span childrenSpan = this.children.GetSpan(); Span suffixesSpan = this.suffixes.GetSpan(); Span 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 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 data) => data[this.bufferPosition++]; + private void IncreaseCodeSizeOrResetIfNeeded(Stream stream) { if (this.nextValidCode > this.maxCode) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs index 8c0dbee95e..be63bdad86 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs +++ b/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 /// /// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type. /// - internal class DeflateTiffCompression : TiffBaseCompression + internal class DeflateTiffCompression : TiffBaseDecompresor { /// /// Initializes a new instance of the class. @@ -54,5 +53,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel); } } + + /// + protected override void Dispose(bool disposing) + { + } } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs index f0439fb7e4..a14738e44a 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs +++ b/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 /// /// Class to handle cases where TIFF image data is compressed using LZW compression. /// - internal class LzwTiffCompression : TiffBaseCompression + internal class LzwTiffCompression : TiffBaseDecompresor { /// /// Initializes a new instance of the class. @@ -38,5 +37,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel); } } + + /// + protected override void Dispose(bool disposing) + { + } } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs index 1664cbebb0..7a7cd20f78 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs @@ -14,6 +14,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso /// internal class ModifiedHuffmanTiffCompression : T4TiffCompression { + private readonly byte whiteValue; + + private readonly byte blackValue; + /// /// Initializes a new instance of the class. /// @@ -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); } /// protected override void Decompress(BufferedReadStream stream, int byteCount, Span 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; } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs index f7271fd032..f041a00e9e 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs @@ -4,25 +4,28 @@ using System; using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors { /// /// Class to handle cases where TIFF image data is not compressed. /// - internal class NoneTiffCompression : TiffBaseCompression + internal class NoneTiffCompression : TiffBaseDecompresor { /// /// Initializes a new instance of the class. /// - /// The memoryAllocator to use for buffer allocations. - public NoneTiffCompression(MemoryAllocator memoryAllocator) - : base(memoryAllocator) + public NoneTiffCompression() + : base(default, default, default) { } /// protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) => _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount)); + + /// + protected override void Dispose(bool disposing) + { + } } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs index 9786ef5ff9..c7461a8073 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs @@ -12,23 +12,33 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso /// /// Class to handle cases where TIFF image data is compressed using PackBits compression. /// - internal class PackBitsTiffCompression : TiffBaseCompression + internal class PackBitsTiffCompression : TiffBaseDecompresor { + private IMemoryOwner compressedDataMemory; + /// /// Initializes a new instance of the class. /// /// The memoryAllocator to use for buffer allocations. public PackBitsTiffCompression(MemoryAllocator memoryAllocator) - : base(memoryAllocator) + : base(memoryAllocator, default, default) { } /// protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) { - using IMemoryOwner compressedDataMemory = this.Allocator.Allocate(byteCount); + if (this.compressedDataMemory == null) + { + this.compressedDataMemory = this.Allocator.Allocate(byteCount); + } + else if (this.compressedDataMemory.Length() < byteCount) + { + this.compressedDataMemory.Dispose(); + this.compressedDataMemory = this.Allocator.Allocate(byteCount); + } - Span compressedData = compressedDataMemory.GetSpan(); + Span compressedData = this.compressedDataMemory.GetSpan(); stream.Read(compressedData, 0, byteCount); int compressedOffset = 0; @@ -77,5 +87,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso destinationArray[i + destinationIndex] = value; } } + + /// + protected override void Dispose(bool disposing) => this.compressedDataMemory?.Dispose(); } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs index 005b5132a5..275e3d598d 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs @@ -12,10 +12,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso /// /// Class to handle cases where TIFF image data is compressed using CCITT T4 compression. /// - internal class T4TiffCompression : TiffBaseCompression + internal class T4TiffCompression : TiffBaseDecompresor { private readonly FaxCompressionOptions faxCompressionOptions; + private readonly byte whiteValue; + + private readonly byte blackValue; + /// /// Initializes a new instance of the class. /// @@ -24,7 +28,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso /// The photometric interpretation. /// The image width. 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); + } /// protected override void Decompress(BufferedReadStream stream, int byteCount, Span buffer) @@ -34,10 +45,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso TiffThrowHelper.ThrowNotSupported("TIFF CCITT 2D compression is not yet supported"); } - bool isWhiteZero = this.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; - byte whiteValue = (byte)(isWhiteZero ? 0 : 1); - byte blackValue = (byte)(isWhiteZero ? 1 : 0); - var eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding); using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding); @@ -51,12 +58,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso { if (bitReader.IsWhiteRun) { - BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, whiteValue); + BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue); bitsWritten += bitReader.RunLength; } else { - BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, blackValue); + BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue); bitsWritten += bitReader.RunLength; } } @@ -73,5 +80,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso } } } + + /// + protected override void Dispose(bool disposing) + { + } } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/FaxCompressionOptions.cs b/src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs similarity index 98% rename from src/ImageSharp/Formats/Tiff/Compression/Decompressors/FaxCompressionOptions.cs rename to src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs index c98ad0387c..d5171db657 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/FaxCompressionOptions.cs +++ b/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 { /// /// Fax compression options, see TIFF spec page 51f (T4Options). diff --git a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs index 10ac39747d..f3062c2a0c 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs +++ b/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 { /// /// 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 rows, int width, int bitsPerPixel) + { + if (bitsPerPixel == 8) + { + ApplyHorizontalPrediction8Bit(rows, width); + } + else if (bitsPerPixel == 24) + { + ApplyHorizontalPrediction24Bit(rows, width); + } + } + /// /// 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. /// - /// The rgb pixel row. + /// The rgb pixel rows. + /// The width. [MethodImpl(InliningOptions.ShortMethod)] - public static void ApplyHorizontalPrediction24Bit(Span rowSpan) + private static void ApplyHorizontalPrediction24Bit(Span rows, int width) { - Span rowRgb = MemoryMarshal.Cast(rowSpan); - - for (int x = rowRgb.Length - 1; x >= 1; x--) + DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals"); + int height = rows.Length / width; + for (int y = 0; y < height; y++) { - byte r = (byte)(rowRgb[x].R - rowRgb[x - 1].R); - byte g = (byte)(rowRgb[x].G - rowRgb[x - 1].G); - byte b = (byte)(rowRgb[x].B - rowRgb[x - 1].B); - var rgb = new Rgb24(r, g, b); - rowRgb[x].FromRgb24(rgb); + Span rowSpan = rows.Slice(y * width, width); + Span rowRgb = MemoryMarshal.Cast(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); + } } } /// /// Applies a horizontal predictor to a gray pixel row. /// - /// The gray pixel row. + /// The gray pixel rows. + /// The width. [MethodImpl(InliningOptions.ShortMethod)] - public static void ApplyHorizontalPrediction8Bit(Span rowSpan) + private static void ApplyHorizontalPrediction8Bit(Span rows, int width) { - for (int x = rowSpan.Length - 1; x >= 1; x--) + DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals"); + int height = rows.Length / width; + for (int y = 0; y < height; y++) { - rowSpan[x] -= rowSpan[x - 1]; + Span rowSpan = rows.Slice(y * width, width); + for (int x = rowSpan.Length - 1; x >= 1; x--) + { + rowSpan[x] -= rowSpan[x - 1]; + } } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs new file mode 100644 index 0000000000..a403ca0649 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression +{ + internal abstract class TiffBaseCompression : IDisposable + { + private bool isDisposed; + + protected TiffBaseCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) + { + this.Allocator = allocator; + this.Width = width; + this.BitsPerPixel = bitsPerPixel; + this.Predictor = predictor; + this.BytesPerRow = ((width * bitsPerPixel) + 7) / 8; + } + + /// + /// Gets the image width. + /// + public int Width { get; } + + /// + /// Gets the bits per pixel. + /// + public int BitsPerPixel { get; } + + /// + /// Gets the bytes per row. + /// + public int BytesPerRow { get; } + + /// + /// Gets the predictor to use. Should only be used with deflate or lzw compression. + /// + public TiffPredictor Predictor { get; } + + /// + /// Gets the memory allocator. + /// + protected MemoryAllocator Allocator { get; } + + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + this.isDisposed = true; + this.Dispose(true); + } + + protected abstract void Dispose(bool disposing); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs new file mode 100644 index 0000000000..6514fbe834 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression +{ + internal abstract class TiffBaseCompressor : TiffBaseCompression + { + /// + /// Initializes a new instance of the class. + /// + /// The output stream to write the compressed image to. + /// The memory allocator. + /// The image width. + /// Bits per pixel. + /// The predictor to use (should only be used with deflate or lzw compression). Defaults to none. + protected TiffBaseCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) + : base(allocator, width, bitsPerPixel, predictor) + => this.Output = output; + + /// + /// Gets the compression method to use. + /// + public abstract TiffEncoderCompression Method { get; } + + /// + /// Gets the output stream to write the compressed image to. + /// + public Stream Output { get; } + + /// + /// Does any initialization required for the compression. + /// + /// The number of rows per strip. + public abstract void Initialize(int rowsPerStrip); + + /// + /// Compresses a strip of the image. + /// + /// Image rows to compress. + /// Image height. + public abstract void CompressStrip(Span rows, int height); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffBaseCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompresor.cs similarity index 65% rename from src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffBaseCompression.cs rename to src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompresor.cs index 7262b483b0..5f981911df 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffBaseCompression.cs +++ b/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 { /// - /// Base tiff decompressor class. + /// The base tiff decompressor class. /// - 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; } - /// /// Decompresses image data into the supplied buffer. /// diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs new file mode 100644 index 0000000000..eeb14fb749 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors; +using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression +{ + internal static class TiffCompressorFactory + { + public static TiffBaseCompressor Create( + TiffEncoderCompression method, + Stream output, + MemoryAllocator allocator, + int width, + int bitsPerPixel, + DeflateCompressionLevel compressionLevel, + TiffPredictor predictor) + { + switch (method) + { + case TiffEncoderCompression.None: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + + return new NoCompressor(output); + + case TiffEncoderCompression.PackBits: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new PackBitsCompressor(output, allocator, width, bitsPerPixel); + + case TiffEncoderCompression.Deflate: + return new DeflateCompressor(output, allocator, width, bitsPerPixel, predictor, compressionLevel); + + case TiffEncoderCompression.Lzw: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + return new LzwCompressor(output, allocator, width, bitsPerPixel, predictor); + + case TiffEncoderCompression.CcittGroup3Fax: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new T4BitCompressor(output, allocator, width, bitsPerPixel, false); + + case TiffEncoderCompression.ModifiedHuffman: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new T4BitCompressor(output, allocator, width, bitsPerPixel, true); + + default: + throw TiffThrowHelper.NotSupportedCompressor(nameof(method)); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffDecoderCompressionType.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs similarity index 98% rename from src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffDecoderCompressionType.cs rename to src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs index 8ec11c3605..247d91e63e 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffDecoderCompressionType.cs +++ b/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 { /// /// Provides enumeration of the various TIFF compression types the decoder can handle. diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffDecompressorsFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs similarity index 59% rename from src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffDecompressorsFactory.cs rename to src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs index 2f78405bb7..130205b5b2 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffDecompressorsFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs @@ -1,15 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression { internal static class TiffDecompressorsFactory { - public static TiffBaseCompression Create( - TiffDecoderCompressionType compressionType, + public static TiffBaseDecompresor Create( + TiffDecoderCompressionType method, MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation, int width, @@ -17,32 +18,36 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompresso TiffPredictor predictor, FaxCompressionOptions faxOptions) { - switch (compressionType) + switch (method) { case TiffDecoderCompressionType.None: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "predictor"); - return new NoneTiffCompression(allocator); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); + return new NoneTiffCompression(); case TiffDecoderCompressionType.PackBits: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "predictor"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); return new PackBitsTiffCompression(allocator); case TiffDecoderCompressionType.Deflate: + DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); return new DeflateTiffCompression(allocator, width, bitsPerPixel, predictor); case TiffDecoderCompressionType.Lzw: + DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); return new LzwTiffCompression(allocator, width, bitsPerPixel, predictor); case TiffDecoderCompressionType.T4: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "predictor"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); return new T4TiffCompression(allocator, faxOptions, photometricInterpretation, width); case TiffDecoderCompressionType.HuffmanRle: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "predictor"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); return new ModifiedHuffmanTiffCompression(allocator, photometricInterpretation, width); default: - throw TiffThrowHelper.NotSupportedCompression(nameof(compressionType)); + throw TiffThrowHelper.NotSupportedDecompressor(nameof(method)); } } } diff --git a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs index d9ede337ad..03a8328eca 100644 --- a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs +++ b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs @@ -37,5 +37,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// Gets the quantizer for creating a color palette image. /// IQuantizer Quantizer { get; } + + /// + /// Gets the maximum size of strip (bytes). + /// + int MaxStripBytes { get; } } } diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md index 32c2b1d400..565ecb6cd8 100644 --- a/src/ImageSharp/Formats/Tiff/README.md +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -79,19 +79,19 @@ |CellWidth | | | | |CellLength | | | | |FillOrder | | - | Ignore. In practice is very uncommon, and is not recommended. | -|ImageDescription | | Y | | -|Make | | Y | | -|Model | | Y | | +|ImageDescription | Y | Y | | +|Make | Y | Y | | +|Model | Y | Y | | |StripOffsets | Y | Y | | |Orientation | | - | Ignore. Many readers ignore this tag. | |SamplesPerPixel | Y | - | Currently ignored, as can be inferred from count of BitsPerSample | -|RowsPerStrip | | Y | | +|RowsPerStrip | Y | Y | | |StripByteCounts | Y | Y | | |MinSampleValue | | | | |MaxSampleValue | | | | |XResolution | Y | Y | | |YResolution | Y | Y | | -|PlanarConfiguration | | Y | | +|PlanarConfiguration | | Y | Encoding support only chunky. | |FreeOffsets | | | | |FreeByteCounts | | | | |GrayResponseUnit | | | | @@ -110,7 +110,7 @@ | |Encoder|Decoder|Comments | |---------------------------|:-----:|:-----:|--------------------------| |NewSubfileType | | | | -|DocumentName | | | | +|DocumentName | Y | Y | | |PageName | | | | |XPosition | | | | |YPosition | | | | @@ -166,7 +166,7 @@ |YCbCrSubSampling | | | | |YCbCrPositioning | | | | |ReferenceBlackWhite | | | | -|StripRowCounts | | - | | +|StripRowCounts | - | - | See RFC 2301 (File Format for Internet Fax). | |XMP | Y | Y | | |ImageID | | | | |ImageLayer | | | | diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 26c4d0038c..fe81d2edb3 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/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 colorDecoder = TiffColorDecoderFactory.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap); @@ -292,7 +292,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff Buffer2D 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 colorDecoder = TiffColorDecoderFactory.Create(this.ColorType, this.BitsPerSample, this.ColorMap); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index 625af123df..123b8494eb 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors; +using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; using SixLabors.ImageSharp.Metadata.Profiles.Exif; @@ -50,6 +50,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff } } + if (entries.ExifProfile.GetValue(ExifTag.StripRowCounts) != null) + { + TiffThrowHelper.ThrowNotSupported("Variable-sized strips are not supported."); + } + options.PlanarConfiguration = entries.PlanarConfiguration; options.Predictor = entries.Predictor; options.PhotometricInterpretation = entries.PhotometricInterpretation; diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs index 0f333679e9..5f747a6851 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs @@ -32,6 +32,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// public IQuantizer Quantizer { get; set; } + /// + public int MaxStripBytes { get; set; } = TiffEncoderCore.DefaultStripSize; + /// public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 063da629fc..d9997a28d6 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -8,10 +8,10 @@ using System.Threading; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Compression.Zlib; +using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -24,6 +24,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// internal sealed class TiffEncoderCore : IImageEncoderInternals { + public const int DefaultStripSize = 8 * 1024; + public static readonly ByteOrder ByteOrder = BitConverter.IsLittleEndian ? ByteOrder.LittleEndian : ByteOrder.BigEndian; private static readonly ushort ByteOrderMarker = BitConverter.IsLittleEndian @@ -43,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// /// The color depth, in number of bits per pixel. /// - private TiffBitsPerPixel? bitsPerPixel; + private TiffBitsPerPixel bitsPerPixel; /// /// The quantizer for creating color palette image. @@ -55,6 +57,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// private readonly DeflateCompressionLevel compressionLevel; + /// + /// The maximum number of bytes for a strip. + /// + private readonly int maxStripBytes; + /// /// Initializes a new instance of the class. /// @@ -68,6 +75,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; this.UseHorizontalPredictor = options.UseHorizontalPredictor; this.compressionLevel = options.CompressionLevel; + this.maxStripBytes = options.MaxStripBytes; } /// @@ -105,26 +113,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff Guard.NotNull(stream, nameof(stream)); this.configuration = image.GetConfiguration(); - ImageMetadata metadata = image.Metadata; - TiffMetadata tiffMetadata = metadata.GetTiffMetadata(); - this.bitsPerPixel ??= tiffMetadata.BitsPerPixel; - if (this.Mode == TiffEncodingMode.Default) - { - // Preserve input bits per pixel, if no mode was specified. - if (this.bitsPerPixel == TiffBitsPerPixel.Pixel8) - { - this.Mode = TiffEncodingMode.Gray; - } - else if (this.bitsPerPixel == TiffBitsPerPixel.Pixel1) - { - this.Mode = TiffEncodingMode.BiColor; - } - else - { - this.Mode = TiffEncodingMode.Rgb; - } - } + this.SetMode(image); this.SetPhotometricInterpretation(); using (var writer = new TiffStreamWriter(stream)) @@ -132,7 +122,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff long firstIfdMarker = this.WriteHeader(writer); // TODO: multiframing is not support - long nextIfdMarker = this.WriteImage(writer, image, firstIfdMarker); + this.WriteImage(writer, image, firstIfdMarker); } } @@ -159,8 +149,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// The to write data to. /// The to encode from. /// The marker to write this IFD offset. - /// The marker to write the next IFD offset (if present). - public long WriteImage(TiffStreamWriter writer, Image image, long ifdOffset) + private void WriteImage(TiffStreamWriter writer, Image image, long ifdOffset) where TPixel : unmanaged, IPixel { var entriesCollector = new TiffEncoderEntriesCollector(); @@ -168,18 +157,34 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff // Write the image bytes to the steam. var imageDataStart = (uint)writer.Position; - TiffBaseColorWriter colorWriter = TiffColorWriterFactory.Create(this.Mode, writer, this.memoryAllocator, this.configuration, entriesCollector); + using TiffBaseCompressor compressor = TiffCompressorFactory.Create( + this.CompressionType, + writer.BaseStream, + this.memoryAllocator, + image.Width, + (int)this.bitsPerPixel, + this.compressionLevel, + this.UseHorizontalPredictor ? TiffPredictor.Horizontal : TiffPredictor.None); + + using TiffBaseColorWriter colorWriter = TiffColorWriterFactory.Create(this.Mode, image.Frames.RootFrame, this.quantizer, this.memoryAllocator, this.configuration, entriesCollector); + + int rowsPerStrip = this.CalcRowsPerStrip(image.Frames.RootFrame, colorWriter.BytesPerRow); - int imageDataBytes = colorWriter.Write(image, this.quantizer, this.CompressionType, this.compressionLevel, this.UseHorizontalPredictor); + colorWriter.Write(compressor, rowsPerStrip); - this.AddStripTags(image, entriesCollector, imageDataStart, imageDataBytes); entriesCollector.ProcessImageFormat(this); entriesCollector.ProcessGeneral(image); writer.WriteMarker(ifdOffset, (uint)writer.Position); long nextIfdMarker = this.WriteIfd(writer, entriesCollector.Entries); + } + + private int CalcRowsPerStrip(ImageFrame image, int bytesPerRow) + { + int sz = this.maxStripBytes > 0 ? this.maxStripBytes : DefaultStripSize; + int height = sz / bytesPerRow; - return nextIfdMarker + imageDataBytes; + return height > 0 ? (height < image.Height ? height : image.Height) : 1; } /// @@ -188,7 +193,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff /// The to write data to. /// The IFD entries to write to the file. /// The marker to write the next IFD offset (if present). - public long WriteIfd(TiffStreamWriter writer, List entries) + private long WriteIfd(TiffStreamWriter writer, List entries) { if (entries.Count == 0) { @@ -239,37 +244,59 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff return nextIfdMarker; } - /// - /// Adds image format information to the specified IFD. - /// - /// The pixel format. - /// The to encode from. - /// The entries collector. - /// The start of the image data in the stream. - /// The image data in bytes to write. - public void AddStripTags(Image image, TiffEncoderEntriesCollector entriesCollector, uint imageDataStartOffset, int imageDataBytes) - where TPixel : unmanaged, IPixel + private void SetMode(Image image) { - var stripOffsets = new ExifLongArray(ExifTagValue.StripOffsets) + if (this.CompressionType == TiffEncoderCompression.CcittGroup3Fax || this.CompressionType == TiffEncoderCompression.ModifiedHuffman) { - // TODO: we only write one image strip for the start. - Value = new[] { imageDataStartOffset } - }; + if (this.Mode == TiffEncodingMode.Default) + { + this.Mode = TiffEncodingMode.BiColor; + this.bitsPerPixel = TiffBitsPerPixel.Pixel1; + return; + } - var rowsPerStrip = new ExifLong(ExifTagValue.RowsPerStrip) - { - // All rows in one strip. - Value = (uint)image.Height - }; + if (this.Mode != TiffEncodingMode.BiColor) + { + TiffThrowHelper.ThrowImageFormatException($"The {this.CompressionType} compression and {this.Mode} aren't compatible. Please use {this.CompressionType} only with {TiffEncodingMode.BiColor} or {TiffEncodingMode.Default} mode."); + } + } - var stripByteCounts = new ExifLongArray(ExifTagValue.StripByteCounts) + if (this.Mode == TiffEncodingMode.Default) { - Value = new[] { (uint)imageDataBytes } - }; + // Preserve input bits per pixel, if no mode was specified. + TiffMetadata tiffMetadata = image.Metadata.GetTiffMetadata(); + switch (tiffMetadata.BitsPerPixel) + { + case TiffBitsPerPixel.Pixel1: + this.Mode = TiffEncodingMode.BiColor; + break; + case TiffBitsPerPixel.Pixel8: + // todo: can gray or palette + this.Mode = TiffEncodingMode.Gray; + break; + default: + this.Mode = TiffEncodingMode.Rgb; + break; + } + } - entriesCollector.Add(stripOffsets); - entriesCollector.Add(rowsPerStrip); - entriesCollector.Add(stripByteCounts); + switch (this.Mode) + { + case TiffEncodingMode.BiColor: + this.bitsPerPixel = TiffBitsPerPixel.Pixel1; + break; + case TiffEncodingMode.ColorPalette: + case TiffEncodingMode.Gray: + this.bitsPerPixel = TiffBitsPerPixel.Pixel8; + break; + case TiffEncodingMode.Rgb: + this.bitsPerPixel = TiffBitsPerPixel.Pixel24; + break; + default: + this.Mode = TiffEncodingMode.Rgb; + this.bitsPerPixel = TiffBitsPerPixel.Pixel24; + break; + } } private void SetPhotometricInterpretation() diff --git a/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs b/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs index d853836b55..db91fcbcb4 100644 --- a/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs +++ b/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}"); diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter.cs index 23b4e329df..f61b294364 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter.cs +++ b/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 { - /// - /// Utility class for writing TIFF data to a . - /// - internal abstract class TiffBaseColorWriter + internal abstract class TiffBaseColorWriter : IDisposable + where TPixel : unmanaged, IPixel { - /// - /// Initializes a new instance of the class. - /// - /// The output stream. - /// The memory allocator. - /// The configuration. - /// The entries collector. - protected TiffBaseColorWriter(TiffStreamWriter output, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) + private bool isDisposed; + + protected TiffBaseColorWriter(ImageFrame 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 Image { get; } protected MemoryAllocator MemoryAllocator { get; } @@ -38,7 +36,75 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers protected TiffEncoderEntriesCollector EntriesCollector { get; } - public abstract int Write(Image image, IQuantizer quantizer, TiffEncoderCompression compression, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor) - where TPixel : unmanaged, IPixel; + public virtual void Write(TiffBaseCompressor compressor, int rowsPerStrip) + { + DebugGuard.IsTrue(this.BytesPerRow == compressor.BytesPerRow || compressor.BytesPerRow == 0, "Values must be equals"); + int stripsCount = (this.Image.Height + rowsPerStrip - 1) / rowsPerStrip; + + uint[] stripOffsets = new uint[stripsCount]; + uint[] stripByteCounts = new uint[stripsCount]; + + int stripIndex = 0; + compressor.Initialize(rowsPerStrip); + for (int y = 0; y < this.Image.Height; y += rowsPerStrip) + { + long offset = compressor.Output.Position; + + int height = Math.Min(rowsPerStrip, this.Image.Height - y); + this.EncodeStrip(y, height, compressor); + + long endOffset = compressor.Output.Position; + stripOffsets[stripIndex] = (uint)offset; + stripByteCounts[stripIndex] = (uint)(endOffset - offset); + stripIndex++; + } + + DebugGuard.IsTrue(stripIndex == stripsCount, "Values must be equals"); + this.AddStripTags(rowsPerStrip, stripOffsets, stripByteCounts); + } + + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + this.isDisposed = true; + this.Dispose(true); + } + + protected static Span GetStripPixels(Buffer2D 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); + + /// + /// Adds image format information to the specified IFD. + /// + /// The rows per strip. + /// The strip offsets. + /// The strip byte counts. + 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); } } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter.cs index 24c2f08daf..ca366f5150 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter.cs @@ -3,208 +3,99 @@ using System; using System.Buffers; -using System.IO; -using SixLabors.ImageSharp.Compression.Zlib; -using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors; +using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Dithering; -using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers { - /// - /// Utility class for writing TIFF data to a . - /// - internal class TiffBiColorWriter : TiffBaseColorWriter + internal class TiffBiColorWriter : TiffBaseColorWriter + where TPixel : unmanaged, IPixel { - public TiffBiColorWriter(TiffStreamWriter output, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(output, memoryAllocator, configuration, entriesCollector) - { - } + private readonly Image imageBlackWhite; - /// - /// Writes the image data as 1 bit black and white to the stream. - /// - /// The pixel data. - /// The image to write to the stream. - /// The quantizer. - /// The compression to use. - /// The compression level for deflate compression. - /// if set to true [use horizontal predictor]. - /// - /// The number of bytes written. - /// - public override int Write(Image 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 pixelRowAsGray = this.MemoryAllocator.Allocate(image.Width); - using IManagedByteBuffer row = this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerRow, AllocationOptions.Clean); - Span outputRow = row.GetSpan(); - Span pixelRowAsGraySpan = pixelRowAsGray.GetSpan(); + private IMemoryOwner pixelsAsGray; + private IMemoryOwner bitStrip; + + public TiffBiColorWriter(ImageFrame 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 imageBlackWhite = image.Clone(); - imageBlackWhite.Mutate(img => img.BinaryDither(default(ErrorDither))); + this.imageBlackWhite = new Image(configuration, new ImageMetadata(), new[] { image.Clone() }); + this.imageBlackWhite.Mutate(img => img.BinaryDither(KnownDitherings.FloydSteinberg)); + } - if (compression == TiffEncoderCompression.Deflate) - { - return this.WriteBiColorDeflate(imageBlackWhite, pixelRowAsGraySpan, outputRow, compressionLevel); - } + /// + public override int BitsPerPixel => 1; - if (compression == TiffEncoderCompression.PackBits) + /// + protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) + { + if (this.pixelsAsGray == null) { - return this.WriteBiColorPackBits(imageBlackWhite, pixelRowAsGraySpan, outputRow); + this.pixelsAsGray = this.MemoryAllocator.Allocate(height * this.Image.Width); } - if (compression == TiffEncoderCompression.CcittGroup3Fax) - { - var bitWriter = new T4BitWriter(this.MemoryAllocator, this.Configuration); - return bitWriter.CompressImage(imageBlackWhite, pixelRowAsGraySpan, this.Output.BaseStream); - } + Span pixelAsGraySpan = this.pixelsAsGray.Slice(0, height * this.Image.Width); - if (compression == TiffEncoderCompression.ModifiedHuffman) + Span pixels = GetStripPixels(this.imageBlackWhite.GetRootFramePixelBuffer(), y, height); + + PixelOperations.Instance.ToL8Bytes(this.Configuration, pixels, pixelAsGraySpan, pixels.Length); + + if (compressor.Method == TiffEncoderCompression.CcittGroup3Fax || compressor.Method == TiffEncoderCompression.ModifiedHuffman) { - var bitWriter = new T4BitWriter(this.MemoryAllocator, this.Configuration, useModifiedHuffman: true); - return bitWriter.CompressImage(imageBlackWhite, pixelRowAsGraySpan, this.Output.BaseStream); + // Special case for T4BitCompressor. + compressor.CompressStrip(pixelAsGraySpan, height); } - - // Write image uncompressed. - int bytesWritten = 0; - for (int y = 0; y < image.Height; y++) + else { - int bitIndex = 0; - int byteIndex = 0; - Span pixelRow = imageBlackWhite.GetPixelRowSpan(y); - PixelOperations.Instance.ToL8(this.Configuration, pixelRow, pixelRowAsGraySpan); - for (int x = 0; x < pixelRow.Length; x++) + int bytesPerStrip = this.BytesPerRow * height; + if (this.bitStrip == null) { - int shift = 7 - bitIndex; - if (pixelRowAsGraySpan[x].PackedValue == 255) - { - outputRow[byteIndex] |= (byte)(1 << shift); - } - - bitIndex++; - if (bitIndex == 8) - { - byteIndex++; - bitIndex = 0; - } + this.bitStrip = this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerStrip); } - this.Output.Write(outputRow); - bytesWritten += outputRow.Length; - - outputRow.Clear(); - } - - return bytesWritten; - } - - /// - /// Writes the image data as 1 bit black and white with deflate compression to the stream. - /// - /// The pixel data. - /// The image to write to the stream. - /// A span for converting a pixel row to gray. - /// A span which will be used to store the output pixels. - /// The compression level for deflate compression. - /// The number of bytes written. - public int WriteBiColorDeflate(Image image, Span pixelRowAsGraySpan, Span outputRow, DeflateCompressionLevel compressionLevel) - where TPixel : unmanaged, IPixel - { - using var memoryStream = new MemoryStream(); - using var deflateStream = new ZlibDeflateStream(this.MemoryAllocator, memoryStream, compressionLevel); + Span rows = this.bitStrip.Slice(0, bytesPerStrip); + rows.Clear(); - int bytesWritten = 0; - for (int y = 0; y < image.Height; y++) - { - int bitIndex = 0; - int byteIndex = 0; - Span pixelRow = image.GetPixelRowSpan(y); - PixelOperations.Instance.ToL8(this.Configuration, pixelRow, pixelRowAsGraySpan); - for (int x = 0; x < pixelRow.Length; x++) + int grayPixelIndex = 0; + for (int s = 0; s < height; s++) { - int shift = 7 - bitIndex; - if (pixelRowAsGraySpan[x].PackedValue == 255) + int bitIndex = 0; + int byteIndex = 0; + Span outputRow = rows.Slice(s * this.BytesPerRow); + for (int x = 0; x < this.Image.Width; x++) { - outputRow[byteIndex] |= (byte)(1 << shift); - } - - bitIndex++; - if (bitIndex == 8) - { - byteIndex++; - bitIndex = 0; + int shift = 7 - bitIndex; + if (pixelAsGraySpan[grayPixelIndex++] == 255) + { + outputRow[byteIndex] |= (byte)(1 << shift); + } + + bitIndex++; + if (bitIndex == 8) + { + byteIndex++; + bitIndex = 0; + } } } - deflateStream.Write(outputRow); - - outputRow.Clear(); + compressor.CompressStrip(rows, height); } - - deflateStream.Flush(); - byte[] buffer = memoryStream.ToArray(); - this.Output.Write(buffer); - bytesWritten += buffer.Length; - - return bytesWritten; } - /// - /// Writes the image data as 1 bit black and white with pack bits compression to the stream. - /// - /// The pixel data. - /// The image to write to the stream. - /// A span for converting a pixel row to gray. - /// A span which will be used to store the output pixels. - /// The number of bytes written. - public int WriteBiColorPackBits(Image image, Span pixelRowAsGraySpan, Span outputRow) - where TPixel : unmanaged, IPixel + /// + protected override void Dispose(bool disposing) { - // Worst case is that the actual compressed data is larger then the input data. In this case we need 1 additional byte per 127 bits. - int additionalBytes = (image.Width / 127) + 2; - int compressedRowBytes = (image.Width / 8) + additionalBytes; - using IManagedByteBuffer compressedRow = this.MemoryAllocator.AllocateManagedByteBuffer(compressedRowBytes, AllocationOptions.Clean); - Span compressedRowSpan = compressedRow.GetSpan(); - - int bytesWritten = 0; - for (int y = 0; y < image.Height; y++) - { - int bitIndex = 0; - int byteIndex = 0; - Span pixelRow = image.GetPixelRowSpan(y); - PixelOperations.Instance.ToL8(this.Configuration, pixelRow, pixelRowAsGraySpan); - for (int x = 0; x < pixelRow.Length; x++) - { - int shift = 7 - bitIndex; - if (pixelRowAsGraySpan[x].PackedValue == 255) - { - outputRow[byteIndex] |= (byte)(1 << shift); - } - - bitIndex++; - if (bitIndex == 8) - { - byteIndex++; - bitIndex = 0; - } - } - - var size = PackBitsWriter.PackBits(outputRow, compressedRowSpan); - this.Output.Write(compressedRowSpan.Slice(0, size)); - bytesWritten += size; - - outputRow.Clear(); - } - - return bytesWritten; + this.imageBlackWhite?.Dispose(); + this.pixelsAsGray?.Dispose(); + this.bitStrip?.Dispose(); } } } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs index 6c378f28dc..10bb9b96ed 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs +++ b/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 Create( + TiffEncodingMode mode, + ImageFrame image, + IQuantizer quantizer, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector) + where TPixel : unmanaged, IPixel { switch (mode) { case TiffEncodingMode.ColorPalette: - return new TiffPaletteWriter(output, memoryAllocator, configuration, entriesCollector); + return new TiffPaletteWriter(image, quantizer, memoryAllocator, configuration, entriesCollector); case TiffEncodingMode.Gray: - return new TiffGrayWriter(output, memoryAllocator, configuration, entriesCollector); + return new TiffGrayWriter(image, memoryAllocator, configuration, entriesCollector); case TiffEncodingMode.BiColor: - return new TiffBiColorWriter(output, memoryAllocator, configuration, entriesCollector); + return new TiffBiColorWriter(image, memoryAllocator, configuration, entriesCollector); default: - return new TiffRgbWriter(output, memoryAllocator, configuration, entriesCollector); + return new TiffRgbWriter(image, memoryAllocator, configuration, entriesCollector); } } } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter.cs new file mode 100644 index 0000000000..018ac6fa05 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers +{ + /// + /// 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). + /// + internal abstract class TiffCompositeColorWriter : TiffBaseColorWriter + where TPixel : unmanaged, IPixel + { + private IManagedByteBuffer rowBuffer; + + protected TiffCompositeColorWriter(ImageFrame 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 rowSpan = this.rowBuffer.GetSpan().Slice(0, this.BytesPerRow * height); + + Span pixels = GetStripPixels(this.Image.PixelBuffer, y, height); + + this.EncodePixels(pixels, rowSpan); + compressor.CompressStrip(rowSpan, height); + } + + protected abstract void EncodePixels(Span pixels, Span buffer); + + /// + protected override void Dispose(bool disposing) => this.rowBuffer?.Dispose(); + } +} diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter.cs index 7161254f83..7e2e4e304e 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter.cs @@ -2,172 +2,23 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; -using System.IO; - -using SixLabors.ImageSharp.Compression.Zlib; -using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors; -using SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils; -using SixLabors.ImageSharp.Formats.Tiff.Compression; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers { - /// - /// Utility class for writing TIFF data to a . - /// - internal class TiffGrayWriter : TiffBaseColorWriter + internal class TiffGrayWriter : TiffCompositeColorWriter + where TPixel : unmanaged, IPixel { - public TiffGrayWriter(TiffStreamWriter output, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(output, memoryAllocator, configuration, entriesCollector) - { - } - - /// - /// Writes the image data as 8 bit gray to the stream. - /// - /// The pixel data. - /// The image to write to the stream. - /// The quantizer. - /// The compression to use. - /// The compression level for deflate compression. - /// Indicates if horizontal prediction should be used. Should only be used with deflate or lzw compression. - /// - /// The number of bytes written. - /// - public override int Write(Image image, IQuantizer quantizer, TiffEncoderCompression compression, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor) + public TiffGrayWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) + : base(image, memoryAllocator, configuration, entriesCollector) { - using IManagedByteBuffer row = this.MemoryAllocator.AllocateManagedByteBuffer(image.Width); - Span 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 pixelRow = image.GetPixelRowSpan(y); - PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelRow, rowSpan, pixelRow.Length); - this.Output.Write(rowSpan); - bytesWritten += rowSpan.Length; - } - - return bytesWritten; } - /// - /// Writes the image data as 8 bit gray with deflate compression to the stream. - /// - /// The image to write to the stream. - /// A span of a row of pixels. - /// The compression level for deflate compression. - /// Indicates if horizontal prediction should be used. - /// The number of bytes written. - private int WriteGrayDeflateCompressed(Image image, Span rowSpan, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor) - where TPixel : unmanaged, IPixel - { - 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 pixelRow = image.GetPixelRowSpan(y); - PixelOperations.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; - } - - /// - /// Writes the image data as 8 bit gray with lzw compression to the stream. - /// - /// The image to write to the stream. - /// A span of a row of pixels. - /// Indicates if horizontal prediction should be used. - /// The number of bytes written. - private int WriteGrayLzwCompressed(Image image, Span rowSpan, bool useHorizontalPredictor) - where TPixel : unmanaged, IPixel - { - int bytesWritten = 0; - using var memoryStream = new MemoryStream(); - - IMemoryOwner pixelData = this.MemoryAllocator.Allocate(image.Width * image.Height); - Span pixels = pixelData.GetSpan(); - for (int y = 0; y < image.Height; y++) - { - Span pixelRow = image.GetPixelRowSpan(y); - PixelOperations.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; - } - - /// - /// Writes the image data as 8 bit gray to the stream. - /// - /// The pixel data. - /// The image to write to the stream. - /// A span of a row of pixels. - /// The number of bytes written. - private int WriteGrayPackBitsCompressed(Image image, Span rowSpan) - where TPixel : unmanaged, IPixel - { - // 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 compressedRowSpan = compressedRow.GetSpan(); - - int bytesWritten = 0; - for (int y = 0; y < image.Height; y++) - { - Span pixelRow = image.GetPixelRowSpan(y); - PixelOperations.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 pixels, Span buffer) => PixelOperations.Instance.ToL8Bytes(this.Configuration, pixels, buffer, pixels.Length); } } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter.cs index 55a2efc9a9..dc7dcf5891 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter.cs @@ -3,13 +3,9 @@ using System; using System.Buffers; -using System.IO; using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.Compression.Zlib; -using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors; -using SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils; -using SixLabors.ImageSharp.Formats.Tiff.Compression; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; @@ -17,44 +13,40 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers { - /// - /// Utility class for writing TIFF data to a . - /// - internal class TiffPaletteWriter : TiffBaseColorWriter + internal class TiffPaletteWriter : TiffBaseColorWriter + where TPixel : unmanaged, IPixel { - /// - /// Initializes a new instance of the class. - /// - /// The output stream. - /// The memory allocator. - /// The configuration. - /// The entries collector. - 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 quantized; + + public TiffPaletteWriter(ImageFrame image, IQuantizer quantizer, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) + : base(image, memoryAllocator, configuration, entriesCollector) { + using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration); + this.quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); + + this.AddTag(this.quantized); } - /// - /// Writes the image data as indices into a color map to the stream. - /// - /// The pixel data. - /// The image to write to the stream. - /// The quantizer to use. - /// The compression to use. - /// The compression level for deflate compression. - /// Indicates if horizontal prediction should be used. Should only be used in combination with deflate or LZW compression. - /// - /// The number of bytes written. - /// - public override int Write(Image 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 frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration); - using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image.Frames.RootFrame, image.Bounds()); - using IMemoryOwner colorPaletteBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(colorPaletteBytes); + Span 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 quantized) + { + using IMemoryOwner colorPaletteBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(ColorPaletteBytes); Span colorPalette = colorPaletteBuffer.GetSpan(); ReadOnlySpan quantizedColors = quantized.Palette.Span; @@ -65,11 +57,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers PixelOperations.Instance.ToRgb48(this.Configuration, quantizedColors, quantizedColorRgb48); // It can happen that the quantized colors are less than the expected 256 per channel. - var diffToMaxColors = colorsPerChannel - quantizedColors.Length; + var diffToMaxColors = ColorsPerChannel - quantizedColors.Length; // In a TIFF ColorMap, all the Red values come first, followed by the Green values, // then the Blue values. Convert the quantized palette to this format. - var palette = new ushort[colorPaletteSize]; + var palette = new ushort[ColorPaletteSize]; int paletteIdx = 0; for (int i = 0; i < quantizedColors.Length; i++) { @@ -96,147 +88,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers }; this.EntriesCollector.Add(colorMap); - - if (compression == TiffEncoderCompression.Deflate) - { - return this.WriteDeflateCompressedPalettedRgb(image, quantized, compressionLevel, useHorizontalPredictor); - } - - if (compression == TiffEncoderCompression.Lzw) - { - return this.WriteLzwCompressedPalettedRgb(image, quantized, useHorizontalPredictor); - } - - if (compression == TiffEncoderCompression.PackBits) - { - return this.WritePackBitsCompressedPalettedRgb(image, quantized); - } - - // No compression. - int bytesWritten = 0; - for (int y = 0; y < image.Height; y++) - { - ReadOnlySpan pixelSpan = quantized.GetPixelRowSpan(y); - this.Output.Write(pixelSpan); - bytesWritten += pixelSpan.Length; - } - - return bytesWritten; - } - - /// - /// Writes the image data as indices into a color map compressed with deflate compression to the stream. - /// - /// The pixel data. - /// The image to write to the stream. - /// The quantized frame. - /// The compression level for deflate compression. - /// Indicates if horizontal prediction should be used. - /// The number of bytes written. - private int WriteDeflateCompressedPalettedRgb(Image image, IndexedImageFrame quantized, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor) - where TPixel : unmanaged, IPixel - { - 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 pixelRow = quantized.GetPixelRowSpan(y); - if (useHorizontalPredictor) - { - // We need a writable Span here. - Span 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; - } - - /// - /// Writes the image data as indices into a color map compressed with lzw compression to the stream. - /// - /// The pixel data. - /// The image to write to the stream. - /// The quantized frame. - /// Indicates if horizontal prediction should be used. - /// The number of bytes written. - private int WriteLzwCompressedPalettedRgb(Image image, IndexedImageFrame quantized, bool useHorizontalPredictor) - where TPixel : unmanaged, IPixel - { - IMemoryOwner pixelData = this.MemoryAllocator.Allocate(image.Width * image.Height); - using IManagedByteBuffer tmpBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(image.Width); - using var memoryStream = new MemoryStream(); - - int bytesWritten = 0; - Span pixels = pixelData.GetSpan(); - for (int y = 0; y < image.Height; y++) - { - ReadOnlySpan indexedPixelRow = quantized.GetPixelRowSpan(y); - - if (useHorizontalPredictor) - { - // We need a writable Span here. - Span 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; - } - - /// - /// Writes the image data as indices into a color map compressed with deflate compression to the stream. - /// - /// The pixel data. - /// The image to write to the stream. - /// The quantized frame. - /// The number of bytes written. - private int WritePackBitsCompressedPalettedRgb(Image image, IndexedImageFrame quantized) - where TPixel : unmanaged, IPixel - { - // 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 compressedRowSpan = compressedRow.GetSpan(); - - int bytesWritten = 0; - for (int y = 0; y < image.Height; y++) - { - ReadOnlySpan pixelSpan = quantized.GetPixelRowSpan(y); - - int size = PackBitsWriter.PackBits(pixelSpan, compressedRowSpan); - this.Output.Write(compressedRowSpan.Slice(0, size)); - bytesWritten += size; - } - - return bytesWritten; } } } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter.cs index 26f2d82d86..b32b5ed90a 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter.cs @@ -2,175 +2,23 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; -using System.IO; - -using SixLabors.ImageSharp.Compression.Zlib; -using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors; -using SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils; -using SixLabors.ImageSharp.Formats.Tiff.Compression; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Writers { - /// - /// Utility class for writing TIFF data to a . - /// - internal class TiffRgbWriter : TiffBaseColorWriter + internal class TiffRgbWriter : TiffCompositeColorWriter + where TPixel : unmanaged, IPixel { - public TiffRgbWriter(TiffStreamWriter output, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(output, memoryAllocator, configuration, entriesCollector) - { - } - - /// - /// Writes the image data as RGB to the stream. - /// - /// The pixel data. - /// The image to write to the stream. - /// The quantizer. - /// The compression to use. - /// The compression level for deflate compression. - /// Indicates if horizontal prediction should be used. Should only be used with deflate compression. - /// - /// The number of bytes written. - /// - public override int Write(Image image, IQuantizer quantizer, TiffEncoderCompression compression, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor) + public TiffRgbWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) + : base(image, memoryAllocator, configuration, entriesCollector) { - using IManagedByteBuffer row = this.MemoryAllocator.AllocateManagedByteBuffer(image.Width * 3); - Span 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 pixelRow = image.GetPixelRowSpan(y); - PixelOperations.Instance.ToRgb24Bytes(this.Configuration, pixelRow, rowSpan, pixelRow.Length); - this.Output.Write(rowSpan); - bytesWritten += rowSpan.Length; - } - - return bytesWritten; } - /// - /// Writes the image data as RGB compressed with zlib to the stream. - /// - /// The pixel data. - /// The image to write to the stream. - /// A Span for a pixel row. - /// The compression level for deflate compression. - /// Indicates if horizontal prediction should be used. Should only be used with deflate compression. - /// The number of bytes written. - private int WriteDeflateCompressedRgb(Image image, Span rowSpan, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor) - where TPixel : unmanaged, IPixel - { - 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 pixelRow = image.GetPixelRowSpan(y); - PixelOperations.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; - } - - /// - /// Writes the image data as RGB compressed with lzw to the stream. - /// - /// The pixel data. - /// The image to write to the stream. - /// A Span for a pixel row. - /// Indicates if horizontal prediction should be used. - /// The number of bytes written. - private int WriteLzwCompressedRgb(Image image, Span rowSpan, bool useHorizontalPredictor) - where TPixel : unmanaged, IPixel - { - int bytesWritten = 0; - using var memoryStream = new MemoryStream(); - - IMemoryOwner pixelData = this.MemoryAllocator.Allocate(image.Width * image.Height * 3); - Span pixels = pixelData.GetSpan(); - for (int y = 0; y < image.Height; y++) - { - Span pixelRow = image.GetPixelRowSpan(y); - PixelOperations.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; - } - - /// - /// Writes the image data as RGB with packed bits compression to the stream. - /// - /// The pixel data. - /// The image to write to the stream. - /// A Span for a pixel row. - /// The number of bytes written. - private int WriteRgbPackBitsCompressed(Image image, Span rowSpan) - where TPixel : unmanaged, IPixel - { - // 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 compressedRowSpan = compressedRow.GetSpan(); - int bytesWritten = 0; - - for (int y = 0; y < image.Height; y++) - { - Span pixelRow = image.GetPixelRowSpan(y); - PixelOperations.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 pixels, Span buffer) => PixelOperations.Instance.ToRgb24Bytes(this.Configuration, pixels, buffer, pixels.Length); } } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs index 5b971962a4..b7749e0f6f 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs +++ b/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 { diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs index fb506528f9..44ffae1d98 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs @@ -53,12 +53,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Params( TestImages.Tiff.CcittFax3AllTermCodes, TestImages.Tiff.HuffmanRleAllMakeupCodes, - TestImages.Tiff.GrayscaleUncompressed, - TestImages.Tiff.PaletteUncompressed, - TestImages.Tiff.RgbDeflate, - TestImages.Tiff.RgbLzwPredictor, - TestImages.Tiff.RgbPackbits, - TestImages.Tiff.RgbUncompressed)] + TestImages.Tiff.Calliphora_GrayscaleUncompressed, + TestImages.Tiff.Calliphora_RgbPaletteLzw_Predictor, + TestImages.Tiff.Calliphora_RgbDeflate_Predictor, + TestImages.Tiff.Calliphora_RgbLzwPredictor, + TestImages.Tiff.Calliphora_RgbPackbits, + TestImages.Tiff.Calliphora_RgbUncompressed)] public string TestImage { get; set; } #endif diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs index 8080b16073..e40907c8ed 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs @@ -24,14 +24,14 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - [Params(TestImages.Tiff.RgbUncompressed)] + [Params(TestImages.Tiff.Calliphora_RgbUncompressed)] public string TestImage { get; set; } [Params( TiffEncoderCompression.None, - ////TiffEncoderCompression.Deflate, + TiffEncoderCompression.Deflate, TiffEncoderCompression.Lzw, - ////TiffEncoderCompression.PackBits, + TiffEncoderCompression.PackBits, TiffEncoderCompression.CcittGroup3Fax, TiffEncoderCompression.ModifiedHuffman)] public TiffEncoderCompression Compression { get; set; } @@ -70,7 +70,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Description = "ImageSharp Tiff")] public void TiffCore() { - var encoder = new TiffEncoder() { Compression = this.Compression }; + TiffEncodingMode mode = TiffEncodingMode.Default; + + // workaround for 1-bit bug + if (this.Compression == TiffEncoderCompression.CcittGroup3Fax || this.Compression == TiffEncoderCompression.ModifiedHuffman) + { + mode = TiffEncodingMode.BiColor; + } + + var encoder = new TiffEncoder() { Compression = this.Compression, Mode = mode }; using var memoryStream = new MemoryStream(); this.core.SaveAsTiff(memoryStream, encoder); } @@ -107,7 +115,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs return EncoderValue.CompressionLZW; default: - throw new System.ArgumentOutOfRangeException(nameof(compression)); + throw new System.NotSupportedException(compression.ToString()); } } } diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index 16999ea7df..63064eec5b 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Benchmarks } #endif - this.SummaryStyle = SummaryStyle.Default.WithMaxParameterColumnWidth(40); + this.SummaryStyle = SummaryStyle.Default.WithMaxParameterColumnWidth(50); } public class MultiFramework : Config diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs index f1de0c9715..cdf0f68f64 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs +++ b/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); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs index 410ead84d2..9108cdaf8a 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs @@ -1,13 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.IO; +using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Compressors; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression.Decompressors; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; -using SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils; using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression @@ -40,7 +38,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression using BufferedReadStream stream = CreateCompressedStream(data); var buffer = new byte[data.Length]; - new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None).Decompress(stream, 0, (uint)stream.Length, buffer); + using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffPredictor.None); + decompressor.Decompress(stream, 0, (uint)stream.Length, buffer); Assert.Equal(data, buffer); } @@ -48,12 +47,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression private static BufferedReadStream CreateCompressedStream(byte[] inputData) { Stream compressedStream = new MemoryStream(); - using System.Buffers.IMemoryOwner data = Configuration.Default.MemoryAllocator.Allocate(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); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs index b366694578..466027bee4 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs +++ b/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); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs index 6fcaa24d26..a211bde535 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs +++ b/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); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs index d7c066ec2f..107fd6079a 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs +++ b/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; diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index 3af0608df1..0041d05ce4 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; using SixLabors.ImageSharp.Formats; @@ -21,12 +22,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff { private static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder(); - private readonly Configuration configuration; + private static readonly Configuration Configuration; - public TiffEncoderTests() + static TiffEncoderTests() { - this.configuration = new Configuration(); - this.configuration.AddTiff(); + Configuration = new Configuration(); + Configuration.AddTiff(); } [Theory] @@ -62,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff // assert memStream.Position = 0; - using var output = Image.Load(this.configuration, memStream); + using var output = Image.Load(Configuration, memStream); TiffMetadata meta = output.Metadata.GetTiffMetadata(); Assert.Equal(expectedBitsPerPixel, meta.BitsPerPixel); Assert.Equal(expectedCompression, meta.Compression); @@ -85,11 +86,52 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff // assert memStream.Position = 0; - using var output = Image.Load(this.configuration, memStream); + using var output = Image.Load(Configuration, memStream); TiffMetadata meta = output.Metadata.GetTiffMetadata(); Assert.Equal(expectedBitsPerPixel, meta.BitsPerPixel); } + [Theory] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncoderCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncoderCompression.ModifiedHuffman, TiffCompression.Ccitt1D)] + [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffEncoderCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)] + [WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffEncoderCompression.ModifiedHuffman, TiffCompression.Ccitt1D)] + public void TiffEncoder_CorrectBiMode(TestImageProvider provider, TiffEncoderCompression compression, TiffCompression expectedCompression) + where TPixel : unmanaged, IPixel + { + // arrange + var encoder = new TiffEncoder() { Compression = compression }; + using Image input = provider.GetImage(); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, encoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(Configuration, memStream); + TiffMetadata meta = output.Metadata.GetTiffMetadata(); + + Assert.Equal(TiffBitsPerPixel.Pixel1, meta.BitsPerPixel); + Assert.Equal(expectedCompression, meta.Compression); + } + + [Theory] + [InlineData(TiffEncodingMode.ColorPalette, TiffEncoderCompression.CcittGroup3Fax)] + [InlineData(TiffEncodingMode.ColorPalette, TiffEncoderCompression.ModifiedHuffman)] + [InlineData(TiffEncodingMode.Gray, TiffEncoderCompression.ModifiedHuffman)] + [InlineData(TiffEncodingMode.Rgb, TiffEncoderCompression.ModifiedHuffman)] + public void TiffEncoder_IncompatibilityOptions(TiffEncodingMode mode, TiffEncoderCompression compression) + { + // arrange + using var input = new Image(10, 10); + var encoder = new TiffEncoder() { Mode = mode, Compression = compression }; + using var memStream = new MemoryStream(); + + // act + Assert.Throws(() => input.Save(memStream, encoder)); + } + [Theory] [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] public void TiffEncoder_EncodeRgb_Works(TestImageProvider provider) @@ -211,6 +253,72 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffEncoder_EncodeBiColor_WithModifiedHuffmanCompression_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Pixel1, TiffEncodingMode.BiColor, TiffEncoderCompression.ModifiedHuffman); + [Theory] + [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffEncodingMode.Gray, TiffEncoderCompression.PackBits, 16 * 1024)] + [WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffEncodingMode.ColorPalette, TiffEncoderCompression.Lzw, 32 * 1024)] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncodingMode.Rgb, TiffEncoderCompression.Deflate, 64 * 1024)] + [WithFile(RgbUncompressed, PixelTypes.Rgb24, TiffEncodingMode.Rgb, TiffEncoderCompression.None, 10 * 1024)] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffEncodingMode.Rgb, TiffEncoderCompression.None, 30 * 1024)] + [WithFile(RgbUncompressed, PixelTypes.Rgb48, TiffEncodingMode.Rgb, TiffEncoderCompression.None, 70 * 1024)] + public void TiffEncoder_StripLength(TestImageProvider provider, TiffEncodingMode mode, TiffEncoderCompression compression, int maxSize) + where TPixel : unmanaged, IPixel => + TestStripLength(provider, mode, compression, maxSize); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.L8, TiffEncodingMode.BiColor, TiffEncoderCompression.CcittGroup3Fax, 9 * 1024)] + public void TiffEncoder_StripLength_OutOfBounds(TestImageProvider provider, TiffEncodingMode mode, TiffEncoderCompression compression, int maxSize) + where TPixel : unmanaged, IPixel => + //// CcittGroup3Fax compressed data length can be larger than the original length + Assert.Throws(() => TestStripLength(provider, mode, compression, maxSize)); + + private static void TestStripLength(TestImageProvider provider, TiffEncodingMode mode, TiffEncoderCompression compression, int maxSize) + where TPixel : unmanaged, IPixel + { + // arrange + var tiffEncoder = new TiffEncoder() { Mode = mode, Compression = compression, MaxStripBytes = maxSize }; + Image input = provider.GetImage(); + using var memStream = new MemoryStream(); + TiffFrameMetadata inputMeta = input.Frames.RootFrame.Metadata.GetTiffMetadata(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + var output = Image.Load(Configuration, memStream); + TiffFrameMetadata meta = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + + Assert.True(output.Height > (int)meta.RowsPerStrip); + Assert.True(meta.StripOffsets.Length > 1); + Assert.True(meta.StripByteCounts.Length > 1); + + foreach (Number sz in meta.StripByteCounts) + { + Assert.True((uint)sz <= maxSize); + } + + // for uncompressed more accurate test + if (compression == TiffEncoderCompression.None) + { + for (int i = 0; i < meta.StripByteCounts.Length - 1; i++) + { + // the difference must be less than one row + int stripBytes = (int)meta.StripByteCounts[i]; + var widthBytes = (meta.BitsPerPixel + 7) / 8 * (int)meta.Width; + + Assert.True((maxSize - stripBytes) < widthBytes); + } + } + + // compare with reference + TestTiffEncoderCore( + provider, + (TiffBitsPerPixel)inputMeta.BitsPerPixel, + mode, + Convert(inputMeta.Compression), + maxStripSize: maxSize); + } + private static void TestTiffEncoderCore( TestImageProvider provider, TiffBitsPerPixel bitsPerPixel, @@ -218,14 +326,41 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff TiffEncoderCompression compression = TiffEncoderCompression.None, bool usePredictor = false, bool useExactComparer = true, + int maxStripSize = 0, float compareTolerance = 0.01f) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); - var encoder = new TiffEncoder { Mode = mode, Compression = compression, UseHorizontalPredictor = usePredictor }; + var encoder = new TiffEncoder + { + Mode = mode, + Compression = compression, + UseHorizontalPredictor = usePredictor, + MaxStripBytes = maxStripSize + }; // Does DebugSave & load reference CompareToReferenceInput(): image.VerifyEncoder(provider, "tiff", bitsPerPixel, encoder, useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), referenceDecoder: ReferenceDecoder); } + + private static TiffEncoderCompression Convert(TiffCompression compression) + { + switch (compression) + { + default: + case TiffCompression.None: + return TiffEncoderCompression.None; + case TiffCompression.Deflate: + return TiffEncoderCompression.Deflate; + case TiffCompression.Lzw: + return TiffEncoderCompression.Lzw; + case TiffCompression.PackBits: + return TiffEncoderCompression.PackBits; + case TiffCompression.Ccitt1D: + return TiffEncoderCompression.ModifiedHuffman; + case TiffCompression.CcittGroup3Fax: + return TiffEncoderCompression.CcittGroup3Fax; + } + } } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs index 8d86482ecc..7ea2e4cc4a 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs +++ b/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; diff --git a/tests/Images/Input/Tiff/Benchmarks/SixLabors.ImageSharp.Benchmarks.Codecs.DecodeTiffBig-report-github.md b/tests/Images/Input/Tiff/Benchmarks/SixLabors.ImageSharp.Benchmarks.Codecs.DecodeTiffBig-report-github.md deleted file mode 100644 index 366c1480ab..0000000000 --- a/tests/Images/Input/Tiff/Benchmarks/SixLabors.ImageSharp.Benchmarks.Codecs.DecodeTiffBig-report-github.md +++ /dev/null @@ -1,87 +0,0 @@ -``` ini - -BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042 -Intel Core i7-3610QM CPU 2.30GHz (Ivy Bridge), 1 CPU, 8 logical and 4 physical cores -.NET Core SDK=5.0.100 - [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - Job-KSIANY : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT - Job-VMCLSF : .NET Core 2.1.23 (CoreCLR 4.6.29321.03, CoreFX 4.6.29321.01), X64 RyuJIT - Job-UHENIY : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - -InvocationCount=1 IterationCount=5 LaunchCount=1 -UnrollFactor=1 WarmupCount=3 - -``` -| Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | -|---------------------- |----------- |-------------- |----------------------------------- |-----------:|----------:|----------:|------:|--------:|-----------:|----------:|----------:|------------:| -| **'System.Drawing Tiff'** | **Job-KSIANY** | **.NET 4.7.2** | **medium_bw_Fax3.tiff** | **491.6 ms** | **20.40 ms** | **5.30 ms** | **1.00** | **0.00** | **1000.0000** | **-** | **-** | **5768128 B** | -| 'ImageSharp Tiff' | Job-KSIANY | .NET 4.7.2 | medium_bw_Fax3.tiff | 6,970.2 ms | 70.64 ms | 10.93 ms | 14.23 | 0.12 | 1000.0000 | 1000.0000 | 1000.0000 | 241518600 B | -| | | | | | | | | | | | | | -| 'System.Drawing Tiff' | Job-VMCLSF | .NET Core 2.1 | medium_bw_Fax3.tiff | 486.2 ms | 23.15 ms | 3.58 ms | 1.00 | 0.00 | 1000.0000 | - | - | 5751016 B | -| 'ImageSharp Tiff' | Job-VMCLSF | .NET Core 2.1 | medium_bw_Fax3.tiff | 4,150.2 ms | 322.16 ms | 83.66 ms | 8.47 | 0.16 | - | - | - | 235961088 B | -| | | | | | | | | | | | | | -| 'System.Drawing Tiff' | Job-UHENIY | .NET Core 3.1 | medium_bw_Fax3.tiff | 490.1 ms | 12.76 ms | 3.31 ms | 1.00 | 0.00 | - | - | - | 176 B | -| 'ImageSharp Tiff' | Job-UHENIY | .NET Core 3.1 | medium_bw_Fax3.tiff | 3,582.9 ms | 61.89 ms | 16.07 ms | 7.31 | 0.06 | - | - | - | 235961496 B | -| | | | | | | | | | | | | | -| **'System.Drawing Tiff'** | **Job-KSIANY** | **.NET 4.7.2** | **medium_bw_Rle.tiff** | **499.1 ms** | **26.71 ms** | **6.94 ms** | **1.00** | **0.00** | **1000.0000** | **-** | **-** | **8494472 B** | -| 'ImageSharp Tiff' | Job-KSIANY | .NET 4.7.2 | medium_bw_Rle.tiff | 7,290.4 ms | 938.28 ms | 243.67 ms | 14.61 | 0.33 | 1000.0000 | 1000.0000 | 1000.0000 | 237020384 B | -| | | | | | | | | | | | | | -| 'System.Drawing Tiff' | Job-VMCLSF | .NET Core 2.1 | medium_bw_Rle.tiff | 490.6 ms | 30.19 ms | 4.67 ms | 1.00 | 0.00 | 1000.0000 | - | - | 8475688 B | -| 'ImageSharp Tiff' | Job-VMCLSF | .NET Core 2.1 | medium_bw_Rle.tiff | 4,230.2 ms | 35.59 ms | 5.51 ms | 8.62 | 0.08 | - | - | - | 235961944 B | -| | | | | | | | | | | | | | -| 'System.Drawing Tiff' | Job-UHENIY | .NET Core 3.1 | medium_bw_Rle.tiff | 487.6 ms | 12.07 ms | 1.87 ms | 1.00 | 0.00 | - | - | - | 176 B | -| 'ImageSharp Tiff' | Job-UHENIY | .NET Core 3.1 | medium_bw_Rle.tiff | 3,647.4 ms | 42.62 ms | 11.07 ms | 7.48 | 0.04 | - | - | - | 235962184 B | -| | | | | | | | | | | | | | -| **'System.Drawing Tiff'** | **Job-KSIANY** | **.NET 4.7.2** | **medium_grayscale_uncompressed.tiff** | **606.7 ms** | **20.45 ms** | **5.31 ms** | **1.00** | **0.00** | **18000.0000** | **-** | **-** | **90301696 B** | -| 'ImageSharp Tiff' | Job-KSIANY | .NET 4.7.2 | medium_grayscale_uncompressed.tiff | 1,852.9 ms | 6.74 ms | 1.75 ms | 3.05 | 0.03 | - | - | - | 235970584 B | -| | | | | | | | | | | | | | -| 'System.Drawing Tiff' | Job-VMCLSF | .NET Core 2.1 | medium_grayscale_uncompressed.tiff | 606.6 ms | 36.58 ms | 9.50 ms | 1.00 | 0.00 | 18000.0000 | - | - | 90104048 B | -| 'ImageSharp Tiff' | Job-VMCLSF | .NET Core 2.1 | medium_grayscale_uncompressed.tiff | 764.3 ms | 15.69 ms | 4.08 ms | 1.26 | 0.02 | - | - | - | 235965376 B | -| | | | | | | | | | | | | | -| 'System.Drawing Tiff' | Job-UHENIY | .NET Core 3.1 | medium_grayscale_uncompressed.tiff | 569.6 ms | 17.44 ms | 4.53 ms | 1.00 | 0.00 | - | - | - | 176 B | -| 'ImageSharp Tiff' | Job-UHENIY | .NET Core 3.1 | medium_grayscale_uncompressed.tiff | 655.2 ms | 17.48 ms | 4.54 ms | 1.15 | 0.01 | - | - | - | 235965488 B | -| | | | | | | | | | | | | | -| **'System.Drawing Tiff'** | **Job-KSIANY** | **.NET 4.7.2** | **medium_palette_uncompressed.tiff** | **578.0 ms** | **22.32 ms** | **5.80 ms** | **1.00** | **0.00** | **18000.0000** | **-** | **-** | **90301696 B** | -| 'ImageSharp Tiff' | Job-KSIANY | .NET 4.7.2 | medium_palette_uncompressed.tiff | 3,336.9 ms | 21.42 ms | 5.56 ms | 5.77 | 0.07 | - | - | - | 236003608 B | -| | | | | | | | | | | | | | -| 'System.Drawing Tiff' | Job-VMCLSF | .NET Core 2.1 | medium_palette_uncompressed.tiff | 601.9 ms | 40.85 ms | 6.32 ms | 1.00 | 0.00 | 18000.0000 | - | - | 90107368 B | -| 'ImageSharp Tiff' | Job-VMCLSF | .NET Core 2.1 | medium_palette_uncompressed.tiff | 1,971.9 ms | 15.69 ms | 4.07 ms | 3.28 | 0.04 | - | - | - | 235996096 B | -| | | | | | | | | | | | | | -| 'System.Drawing Tiff' | Job-UHENIY | .NET Core 3.1 | medium_palette_uncompressed.tiff | 566.1 ms | 28.06 ms | 4.34 ms | 1.00 | 0.00 | - | - | - | 176 B | -| 'ImageSharp Tiff' | Job-UHENIY | .NET Core 3.1 | medium_palette_uncompressed.tiff | 1,664.1 ms | 11.59 ms | 1.79 ms | 2.94 | 0.02 | - | - | - | 235996208 B | -| | | | | | | | | | | | | | -| **'System.Drawing Tiff'** | **Job-KSIANY** | **.NET 4.7.2** | **medium_rgb_deflate.tiff** | **357.4 ms** | **15.54 ms** | **2.40 ms** | **1.00** | **0.00** | **3000.0000** | **-** | **-** | **9662560 B** | -| 'ImageSharp Tiff' | Job-KSIANY | .NET 4.7.2 | medium_rgb_deflate.tiff | 776.1 ms | 14.51 ms | 3.77 ms | 2.17 | 0.01 | 22000.0000 | 1000.0000 | 1000.0000 | 303476856 B | -| | | | | | | | | | | | | | -| 'System.Drawing Tiff' | Job-VMCLSF | .NET Core 2.1 | medium_rgb_deflate.tiff | 359.7 ms | 12.29 ms | 3.19 ms | 1.00 | 0.00 | 3000.0000 | - | - | 9629400 B | -| 'ImageSharp Tiff' | Job-VMCLSF | .NET Core 2.1 | medium_rgb_deflate.tiff | 554.5 ms | 16.78 ms | 4.36 ms | 1.54 | 0.02 | 2000.0000 | 1000.0000 | 1000.0000 | 239716144 B | -| | | | | | | | | | | | | | -| 'System.Drawing Tiff' | Job-UHENIY | .NET Core 3.1 | medium_rgb_deflate.tiff | 353.2 ms | 7.22 ms | 1.12 ms | 1.00 | 0.00 | - | - | - | 176 B | -| 'ImageSharp Tiff' | Job-UHENIY | .NET Core 3.1 | medium_rgb_deflate.tiff | 557.1 ms | 10.79 ms | 2.80 ms | 1.58 | 0.00 | 2000.0000 | 1000.0000 | 1000.0000 | 239470552 B | -| | | | | | | | | | | | | | -| **'System.Drawing Tiff'** | **Job-KSIANY** | **.NET 4.7.2** | **medium_rgb_lzw.tiff** | **511.0 ms** | **6.43 ms** | **1.67 ms** | **1.00** | **0.00** | **3000.0000** | **-** | **-** | **11600840 B** | -| 'ImageSharp Tiff' | Job-KSIANY | .NET 4.7.2 | medium_rgb_lzw.tiff | 2,691.6 ms | 16.81 ms | 2.60 ms | 5.27 | 0.02 | - | - | - | 236044312 B | -| | | | | | | | | | | | | | -| 'System.Drawing Tiff' | Job-VMCLSF | .NET Core 2.1 | medium_rgb_lzw.tiff | 511.4 ms | 11.44 ms | 1.77 ms | 1.00 | 0.00 | 3000.0000 | - | - | 11569776 B | -| 'ImageSharp Tiff' | Job-VMCLSF | .NET Core 2.1 | medium_rgb_lzw.tiff | 1,654.1 ms | 12.42 ms | 1.92 ms | 3.23 | 0.01 | - | - | - | 236041592 B | -| | | | | | | | | | | | | | -| 'System.Drawing Tiff' | Job-UHENIY | .NET Core 3.1 | medium_rgb_lzw.tiff | 507.7 ms | 8.89 ms | 2.31 ms | 1.00 | 0.00 | - | - | - | 176 B | -| 'ImageSharp Tiff' | Job-UHENIY | .NET Core 3.1 | medium_rgb_lzw.tiff | 1,689.5 ms | 40.41 ms | 6.25 ms | 3.33 | 0.03 | - | - | - | 236041656 B | -| | | | | | | | | | | | | | -| **'System.Drawing Tiff'** | **Job-KSIANY** | **.NET 4.7.2** | **medium_rgb_packbits.tiff** | **776.8 ms** | **31.69 ms** | **8.23 ms** | **1.00** | **0.00** | **56000.0000** | **-** | **-** | **304057016 B** | -| 'ImageSharp Tiff' | Job-KSIANY | .NET 4.7.2 | medium_rgb_packbits.tiff | 531.2 ms | 23.17 ms | 6.02 ms | 0.68 | 0.01 | - | - | - | 236003352 B | -| | | | | | | | | | | | | | -| 'System.Drawing Tiff' | Job-VMCLSF | .NET Core 2.1 | medium_rgb_packbits.tiff | 764.2 ms | 41.43 ms | 6.41 ms | 1.00 | 0.00 | 56000.0000 | - | - | 303861120 B | -| 'ImageSharp Tiff' | Job-VMCLSF | .NET Core 2.1 | medium_rgb_packbits.tiff | 300.0 ms | 4.39 ms | 0.68 ms | 0.39 | 0.00 | - | - | - | 235998408 B | -| | | | | | | | | | | | | | -| 'System.Drawing Tiff' | Job-UHENIY | .NET Core 3.1 | medium_rgb_packbits.tiff | 659.1 ms | 34.59 ms | 8.98 ms | 1.00 | 0.00 | - | - | - | 176 B | -| 'ImageSharp Tiff' | Job-UHENIY | .NET Core 3.1 | medium_rgb_packbits.tiff | 297.5 ms | 21.13 ms | 5.49 ms | 0.45 | 0.00 | - | - | - | 235998520 B | -| | | | | | | | | | | | | | -| **'System.Drawing Tiff'** | **Job-KSIANY** | **.NET 4.7.2** | **medium_rgb_uncompressed.tiff** | **742.5 ms** | **50.45 ms** | **13.10 ms** | **1.00** | **0.00** | **55000.0000** | **-** | **-** | **302644272 B** | -| 'ImageSharp Tiff' | Job-KSIANY | .NET 4.7.2 | medium_rgb_uncompressed.tiff | 414.3 ms | 15.37 ms | 3.99 ms | 0.56 | 0.01 | - | - | - | 235986968 B | -| | | | | | | | | | | | | | -| 'System.Drawing Tiff' | Job-VMCLSF | .NET Core 2.1 | medium_rgb_uncompressed.tiff | 750.2 ms | 74.13 ms | 19.25 ms | 1.00 | 0.00 | 55000.0000 | - | - | 302448096 B | -| 'ImageSharp Tiff' | Job-VMCLSF | .NET Core 2.1 | medium_rgb_uncompressed.tiff | 283.6 ms | 21.56 ms | 5.60 ms | 0.38 | 0.01 | - | - | - | 235981128 B | -| | | | | | | | | | | | | | -| 'System.Drawing Tiff' | Job-UHENIY | .NET Core 3.1 | medium_rgb_uncompressed.tiff | 662.6 ms | 49.79 ms | 12.93 ms | 1.00 | 0.00 | - | - | - | 176 B | -| 'ImageSharp Tiff' | Job-UHENIY | .NET Core 3.1 | medium_rgb_uncompressed.tiff | 278.6 ms | 9.48 ms | 2.46 ms | 0.42 | 0.01 | - | - | - | 235981352 B | diff --git a/tests/Images/Input/Tiff/Benchmarks/SixLabors.ImageSharp.Benchmarks.Codecs.DecodeTiffBig-report.html b/tests/Images/Input/Tiff/Benchmarks/SixLabors.ImageSharp.Benchmarks.Codecs.DecodeTiffBig-report.html deleted file mode 100644 index 10fd01fd45..0000000000 --- a/tests/Images/Input/Tiff/Benchmarks/SixLabors.ImageSharp.Benchmarks.Codecs.DecodeTiffBig-report.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - -SixLabors.ImageSharp.Benchmarks.Codecs.DecodeTiffBig-20201209-175548 - - - - -

-BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
-Intel Core i7-3610QM CPU 2.30GHz (Ivy Bridge), 1 CPU, 8 logical and 4 physical cores
-.NET Core SDK=5.0.100
-  [Host]     : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
-  Job-KSIANY : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT
-  Job-VMCLSF : .NET Core 2.1.23 (CoreCLR 4.6.29321.03, CoreFX 4.6.29321.01), X64 RyuJIT
-  Job-UHENIY : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
-
-
InvocationCount=1  IterationCount=5  LaunchCount=1  
-UnrollFactor=1  WarmupCount=3  
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Method JobRuntime TestImageMeanErrorStdDevRatioRatioSDGen 0Gen 1Gen 2Allocated
'System.Drawing Tiff'Job-KSIANY.NET 4.7.2medium_bw_Fax3.tiff491.6 ms20.40 ms5.30 ms1.000.001000.0000--5768128 B
'ImageSharp Tiff'Job-KSIANY.NET 4.7.2medium_bw_Fax3.tiff6,970.2 ms70.64 ms10.93 ms14.230.121000.00001000.00001000.0000241518600 B
'System.Drawing Tiff'Job-VMCLSF.NET Core 2.1medium_bw_Fax3.tiff486.2 ms23.15 ms3.58 ms1.000.001000.0000--5751016 B
'ImageSharp Tiff'Job-VMCLSF.NET Core 2.1medium_bw_Fax3.tiff4,150.2 ms322.16 ms83.66 ms8.470.16---235961088 B
'System.Drawing Tiff'Job-UHENIY.NET Core 3.1medium_bw_Fax3.tiff490.1 ms12.76 ms3.31 ms1.000.00---176 B
'ImageSharp Tiff'Job-UHENIY.NET Core 3.1medium_bw_Fax3.tiff3,582.9 ms61.89 ms16.07 ms7.310.06---235961496 B
'System.Drawing Tiff'Job-KSIANY.NET 4.7.2medium_bw_Rle.tiff499.1 ms26.71 ms6.94 ms1.000.001000.0000--8494472 B
'ImageSharp Tiff'Job-KSIANY.NET 4.7.2medium_bw_Rle.tiff7,290.4 ms938.28 ms243.67 ms14.610.331000.00001000.00001000.0000237020384 B
'System.Drawing Tiff'Job-VMCLSF.NET Core 2.1medium_bw_Rle.tiff490.6 ms30.19 ms4.67 ms1.000.001000.0000--8475688 B
'ImageSharp Tiff'Job-VMCLSF.NET Core 2.1medium_bw_Rle.tiff4,230.2 ms35.59 ms5.51 ms8.620.08---235961944 B
'System.Drawing Tiff'Job-UHENIY.NET Core 3.1medium_bw_Rle.tiff487.6 ms12.07 ms1.87 ms1.000.00---176 B
'ImageSharp Tiff'Job-UHENIY.NET Core 3.1medium_bw_Rle.tiff3,647.4 ms42.62 ms11.07 ms7.480.04---235962184 B
'System.Drawing Tiff'Job-KSIANY.NET 4.7.2medium_grayscale_uncompressed.tiff606.7 ms20.45 ms5.31 ms1.000.0018000.0000--90301696 B
'ImageSharp Tiff'Job-KSIANY.NET 4.7.2medium_grayscale_uncompressed.tiff1,852.9 ms6.74 ms1.75 ms3.050.03---235970584 B
'System.Drawing Tiff'Job-VMCLSF.NET Core 2.1medium_grayscale_uncompressed.tiff606.6 ms36.58 ms9.50 ms1.000.0018000.0000--90104048 B
'ImageSharp Tiff'Job-VMCLSF.NET Core 2.1medium_grayscale_uncompressed.tiff764.3 ms15.69 ms4.08 ms1.260.02---235965376 B
'System.Drawing Tiff'Job-UHENIY.NET Core 3.1medium_grayscale_uncompressed.tiff569.6 ms17.44 ms4.53 ms1.000.00---176 B
'ImageSharp Tiff'Job-UHENIY.NET Core 3.1medium_grayscale_uncompressed.tiff655.2 ms17.48 ms4.54 ms1.150.01---235965488 B
'System.Drawing Tiff'Job-KSIANY.NET 4.7.2medium_palette_uncompressed.tiff578.0 ms22.32 ms5.80 ms1.000.0018000.0000--90301696 B
'ImageSharp Tiff'Job-KSIANY.NET 4.7.2medium_palette_uncompressed.tiff3,336.9 ms21.42 ms5.56 ms5.770.07---236003608 B
'System.Drawing Tiff'Job-VMCLSF.NET Core 2.1medium_palette_uncompressed.tiff601.9 ms40.85 ms6.32 ms1.000.0018000.0000--90107368 B
'ImageSharp Tiff'Job-VMCLSF.NET Core 2.1medium_palette_uncompressed.tiff1,971.9 ms15.69 ms4.07 ms3.280.04---235996096 B
'System.Drawing Tiff'Job-UHENIY.NET Core 3.1medium_palette_uncompressed.tiff566.1 ms28.06 ms4.34 ms1.000.00---176 B
'ImageSharp Tiff'Job-UHENIY.NET Core 3.1medium_palette_uncompressed.tiff1,664.1 ms11.59 ms1.79 ms2.940.02---235996208 B
'System.Drawing Tiff'Job-KSIANY.NET 4.7.2medium_rgb_deflate.tiff357.4 ms15.54 ms2.40 ms1.000.003000.0000--9662560 B
'ImageSharp Tiff'Job-KSIANY.NET 4.7.2medium_rgb_deflate.tiff776.1 ms14.51 ms3.77 ms2.170.0122000.00001000.00001000.0000303476856 B
'System.Drawing Tiff'Job-VMCLSF.NET Core 2.1medium_rgb_deflate.tiff359.7 ms12.29 ms3.19 ms1.000.003000.0000--9629400 B
'ImageSharp Tiff'Job-VMCLSF.NET Core 2.1medium_rgb_deflate.tiff554.5 ms16.78 ms4.36 ms1.540.022000.00001000.00001000.0000239716144 B
'System.Drawing Tiff'Job-UHENIY.NET Core 3.1medium_rgb_deflate.tiff353.2 ms7.22 ms1.12 ms1.000.00---176 B
'ImageSharp Tiff'Job-UHENIY.NET Core 3.1medium_rgb_deflate.tiff557.1 ms10.79 ms2.80 ms1.580.002000.00001000.00001000.0000239470552 B
'System.Drawing Tiff'Job-KSIANY.NET 4.7.2medium_rgb_lzw.tiff511.0 ms6.43 ms1.67 ms1.000.003000.0000--11600840 B
'ImageSharp Tiff'Job-KSIANY.NET 4.7.2medium_rgb_lzw.tiff2,691.6 ms16.81 ms2.60 ms5.270.02---236044312 B
'System.Drawing Tiff'Job-VMCLSF.NET Core 2.1medium_rgb_lzw.tiff511.4 ms11.44 ms1.77 ms1.000.003000.0000--11569776 B
'ImageSharp Tiff'Job-VMCLSF.NET Core 2.1medium_rgb_lzw.tiff1,654.1 ms12.42 ms1.92 ms3.230.01---236041592 B
'System.Drawing Tiff'Job-UHENIY.NET Core 3.1medium_rgb_lzw.tiff507.7 ms8.89 ms2.31 ms1.000.00---176 B
'ImageSharp Tiff'Job-UHENIY.NET Core 3.1medium_rgb_lzw.tiff1,689.5 ms40.41 ms6.25 ms3.330.03---236041656 B
'System.Drawing Tiff'Job-KSIANY.NET 4.7.2medium_rgb_packbits.tiff776.8 ms31.69 ms8.23 ms1.000.0056000.0000--304057016 B
'ImageSharp Tiff'Job-KSIANY.NET 4.7.2medium_rgb_packbits.tiff531.2 ms23.17 ms6.02 ms0.680.01---236003352 B
'System.Drawing Tiff'Job-VMCLSF.NET Core 2.1medium_rgb_packbits.tiff764.2 ms41.43 ms6.41 ms1.000.0056000.0000--303861120 B
'ImageSharp Tiff'Job-VMCLSF.NET Core 2.1medium_rgb_packbits.tiff300.0 ms4.39 ms0.68 ms0.390.00---235998408 B
'System.Drawing Tiff'Job-UHENIY.NET Core 3.1medium_rgb_packbits.tiff659.1 ms34.59 ms8.98 ms1.000.00---176 B
'ImageSharp Tiff'Job-UHENIY.NET Core 3.1medium_rgb_packbits.tiff297.5 ms21.13 ms5.49 ms0.450.00---235998520 B
'System.Drawing Tiff'Job-KSIANY.NET 4.7.2medium_rgb_uncompressed.tiff742.5 ms50.45 ms13.10 ms1.000.0055000.0000--302644272 B
'ImageSharp Tiff'Job-KSIANY.NET 4.7.2medium_rgb_uncompressed.tiff414.3 ms15.37 ms3.99 ms0.560.01---235986968 B
'System.Drawing Tiff'Job-VMCLSF.NET Core 2.1medium_rgb_uncompressed.tiff750.2 ms74.13 ms19.25 ms1.000.0055000.0000--302448096 B
'ImageSharp Tiff'Job-VMCLSF.NET Core 2.1medium_rgb_uncompressed.tiff283.6 ms21.56 ms5.60 ms0.380.01---235981128 B
'System.Drawing Tiff'Job-UHENIY.NET Core 3.1medium_rgb_uncompressed.tiff662.6 ms49.79 ms12.93 ms1.000.00---176 B
'ImageSharp Tiff'Job-UHENIY.NET Core 3.1medium_rgb_uncompressed.tiff278.6 ms9.48 ms2.46 ms0.420.01---235981352 B
- -