From 4bf3d16789e11f1a6c72825d96d68065d96db0ce Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 8 Dec 2020 13:50:01 +0100 Subject: [PATCH] Reworked lzw encoder with a tree based approach based on a java implementation --- .../Formats/Tiff/TiffEncoderCore.cs | 2 +- .../Formats/Tiff/Utils/TiffLzwEncoder.cs | 571 ++++++------------ .../Formats/Tiff/Utils/TiffWriter.cs | 6 +- .../DeflateTiffCompressionTests.cs | 2 +- .../Compression/LzwTiffCompressionTests.cs | 18 +- 5 files changed, 194 insertions(+), 405 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 6afe5f9336..2e8fdfc365 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -297,7 +297,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff var rowsPerStrip = new ExifLong(ExifTagValue.RowsPerStrip) { - // TODO: all rows in one strip for the start + // All rows in one strip. Value = (uint)image.Height }; diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs index 96db8e110d..072b548a73 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs @@ -9,28 +9,46 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils { + /* + This implementation is a port of a java tiff encoder by Harald Kuhr: https://github.com/haraldk/TwelveMonkeys + + Original licence: + + BSD 3-Clause License + + * Copyright (c) 2015, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + ** Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /// /// Encodes and compresses the image data using dynamic Lempel-Ziv compression. /// /// - /// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00 - /// - /// GIFCOMPR.C - GIF Image compression routines - /// - /// - /// Lempel-Ziv compression based on 'compress'. GIF modifications by - /// David Rowley (mgardi@watdcsu.waterloo.edu) - /// - /// GIF Image compression - modified 'compress' - /// - /// Based on: compress.c - File compression ala IEEE Computer, June 1984. - /// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) - /// Jim McKie (decvax!mcvax!jim) - /// Steve Davies (decvax!vax135!petsd!peora!srd) - /// Ken Turkowski (decvax!decwrl!turtlevax!ken) - /// James A. Woods (decvax!ihnp4!ames!jaw) - /// Joe Orost (decvax!vax135!petsd!joe) - /// /// /// This code is based on the used for GIF encoding. There is potential /// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW @@ -42,173 +60,54 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils /// internal sealed class TiffLzwEncoder : IDisposable { - /// - /// The end-of-file marker - /// - private const int Eof = -1; - - /// - /// The maximum number of bits. - /// - private const int Bits = 12; - - /// - /// 80% occupancy - /// - private const int HashSize = 5003; - - /// - /// Mask used when shifting pixel values - /// - private static readonly int[] Masks = - { - 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, - 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF - }; - - /// - /// The working pixel array. - /// - private readonly IMemoryOwner pixelArray; + // Clear: Re-initialize tables. + private static readonly int ClearCode = 256; - /// - /// The initial code size. - /// - private readonly int initialCodeSize; + // End of Information. + private static readonly int EoiCode = 257; - /// - /// The hash table. - /// - private readonly IMemoryOwner hashTable; + private static readonly int MinBits = 9; + private static readonly int MaxBits = 12; - /// - /// The code table. - /// - private readonly IMemoryOwner codeTable; + private static readonly int TableSize = 1 << MaxBits; - /// - /// Define the storage for the packet accumulator. - /// - private readonly byte[] accumulators = new byte[256]; - - /// - /// A value indicating whether this instance of the given entity has been disposed. - /// - /// if this instance has been disposed; otherwise, . - /// - /// If the entity is disposed, it must not be disposed a second - /// time. The isDisposed field is set the first time the entity - /// is disposed. If the isDisposed field is true, then the Dispose() - /// method will not dispose again. This help not to prolong the entity's - /// life in the Garbage Collector. - /// - private bool isDisposed; + private readonly IMemoryOwner data; - /// - /// The current pixel - /// - private int currentPixel; + // 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; - /// - /// Number of bits/code - /// - private int bitCount; + private readonly IMemoryOwner siblings; - /// - /// User settable max # bits/code - /// - private int maxbits = Bits; + private readonly IMemoryOwner suffixes; - /// - /// maximum code, given bitCount - /// - private int maxcode; + // Initial setup + private int parent; + private int bitsPerCode; + private int nextValidCode; + private int maxCode; - /// - /// should NEVER generate this code - /// - private int maxmaxcode = 1 << Bits; - - /// - /// For dynamic table sizing - /// - private int hsize = HashSize; - - /// - /// First unused entry - /// - private int freeEntry; - - /// - /// Block compression parameters -- after all codes are used up, - /// and compression rate changes, start over. - /// - private bool clearFlag; - - /// - /// Algorithm: use open addressing double hashing (no chaining) on the - /// prefix code / next character combination. We do a variant of Knuth's - /// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime - /// secondary probe. Here, the modular division first probe is gives way - /// to a faster exclusive-or manipulation. Also do block compression with - /// an adaptive reset, whereby the code table is cleared when the compression - /// ratio decreases, but after the table fills. The variable-length output - /// codes are re-sized at this point, and a special CLEAR code is generated - /// for the decompressor. Late addition: construct the table according to - /// file size for noticeable speed improvement on small files. Please direct - /// questions about this implementation to ames!jaw. - /// - private int globalInitialBits; - - /// - /// The clear code. - /// - private int clearCode; - - /// - /// The end-of-file code. - /// - private int eofCode; - - /// - /// Output the given code. - /// Inputs: - /// code: A bitCount-bit integer. If == -1, then EOF. This assumes - /// that bitCount =< wordsize - 1. - /// Outputs: - /// Outputs code to the file. - /// Assumptions: - /// Chars are 8 bits long. - /// Algorithm: - /// Maintain a BITS character long buffer (so that 8 codes will - /// fit in it exactly). Use the VAX insv instruction to insert each - /// code in turn. When the buffer fills up empty it and start over. - /// - private int currentAccumulator; - - /// - /// The current bits. - /// - private int currentBits; - - /// - /// Number of characters so far in this 'packet' - /// - private int accumulatorCount; + // Buffer for partial codes + private int bits; + private int bitPos; + private int bufferPosition; /// /// Initializes a new instance of the class. /// - /// The array of indexed pixels. - /// The color depth in bits. + /// The data to compress. /// The memory allocator. - public TiffLzwEncoder(MemoryAllocator memoryAllocator, IMemoryOwner indexedPixels, int colorDepth) + public TiffLzwEncoder(MemoryAllocator memoryAllocator, IMemoryOwner data) { - this.pixelArray = indexedPixels; - this.initialCodeSize = Math.Max(2, colorDepth); - - this.hashTable = memoryAllocator.Allocate(HashSize, AllocationOptions.Clean); - this.codeTable = memoryAllocator.Allocate(HashSize, AllocationOptions.Clean); + 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; } /// @@ -217,282 +116,158 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils /// The stream to write to. public void Encode(Stream stream) { - this.currentPixel = 0; - - // Compress and write the pixel data - this.Compress(this.initialCodeSize + 1, stream); - } - - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - this.Dispose(true); - } - - /// - /// Gets the maximum code value - /// - /// The number of bits - /// See - private static int GetMaxcode(int bitCount) - { - return (1 << bitCount) - 1; - } - - /// - /// Add a character to the end of the current packet, and if it is 254 characters, - /// flush the packet to disk. - /// - /// The character to add. - /// The stream to write to. - private void AddCharacter(byte c, Stream stream) - { - this.accumulators[this.accumulatorCount++] = c; - if (this.accumulatorCount >= 254) - { - this.FlushPacket(stream); - } - } + Span childrenSpan = this.children.GetSpan(); + Span suffixesSpan = this.suffixes.GetSpan(); + Span siblingsSpan = this.siblings.GetSpan(); + int length = this.data.Length(); - /// - /// Table clear for block compress - /// - /// The output stream. - private void ClearBlock(Stream stream) - { - this.ResetCodeTable(this.hsize); - this.freeEntry = this.clearCode + 2; - this.clearFlag = true; - - this.Output(this.clearCode, stream); - } - - /// - /// Reset the code table. - /// - /// The hash size. - private void ResetCodeTable(int size) - { - Span hashTableSpan = this.hashTable.GetSpan(); - for (int i = 0; i < size; ++i) + if (length == 0) { - hashTableSpan[i] = -1; + return; } - } - - /// - /// Compress the packets to the stream. - /// - /// The initial bits. - /// The stream to write to. - private void Compress(int intialBits, Stream stream) - { - int fcode; - int c; - int ent; - int hsizeReg; - int hshift; - - // Set up the globals: globalInitialBits - initial number of bits - this.globalInitialBits = intialBits; - - // Set up the necessary values - this.clearFlag = false; - this.bitCount = this.globalInitialBits; - this.maxcode = GetMaxcode(this.bitCount); - this.clearCode = 1 << (intialBits - 1); - this.eofCode = this.clearCode + 1; - this.freeEntry = this.clearCode + 2; - - this.accumulatorCount = 0; // clear packet - - ent = this.NextPixel(); - - hshift = 0; - for (fcode = this.hsize; fcode < 65536; fcode *= 2) + if (this.parent == -1) { - ++hshift; + // Init stream. + this.WriteCode(stream, ClearCode); + this.parent = this.ReadNextByte() & 0xff; } - hshift = 8 - hshift; // set hash code range bound - - hsizeReg = this.hsize; - - this.ResetCodeTable(hsizeReg); // clear hash table - - this.Output(this.clearCode, stream); - - Span hashTableSpan = this.hashTable.GetSpan(); - Span codeTableSpan = this.codeTable.GetSpan(); - while ((c = this.NextPixel()) != Eof) + while (this.bufferPosition < this.data.Length()) { - fcode = (c << this.maxbits) + ent; - int i = (c << hshift) ^ ent /* = 0 */; + int value = this.ReadNextByte() & 0xff; + int child = childrenSpan[this.parent]; - if (hashTableSpan[i] == fcode) + if (child > 0) { - ent = codeTableSpan[i]; - continue; - } - - // Non-empty slot - if (hashTableSpan[i] >= 0) - { - int disp = hsizeReg - i; - if (i == 0) + if (suffixesSpan[child] == value) { - disp = 1; + this.parent = child; } - - do + else { - if ((i -= disp) < 0) - { - i += hsizeReg; - } + int sibling = child; - if (hashTableSpan[i] == fcode) + while (true) { - ent = codeTableSpan[i]; - break; + if (siblingsSpan[sibling] > 0) + { + sibling = siblingsSpan[sibling]; + + if (suffixesSpan[sibling] == value) + { + this.parent = sibling; + break; + } + } + else + { + siblingsSpan[sibling] = (short)this.nextValidCode; + suffixesSpan[this.nextValidCode] = (short)value; + this.WriteCode(stream, this.parent); + this.parent = value; + this.nextValidCode++; + + this.IncreaseCodeSizeOrResetIfNeeded(stream); + + break; + } } } - while (hashTableSpan[i] >= 0); - - if (hashTableSpan[i] == fcode) - { - continue; - } - } - - this.Output(ent, stream); - ent = c; - if (this.freeEntry < this.maxmaxcode) - { - codeTableSpan[i] = this.freeEntry++; // code -> hashtable - hashTableSpan[i] = fcode; } else { - this.ClearBlock(stream); + childrenSpan[this.parent] = (short)this.nextValidCode; + suffixesSpan[this.nextValidCode] = (short)value; + this.WriteCode(stream, this.parent); + this.parent = value; + this.nextValidCode++; + + this.IncreaseCodeSizeOrResetIfNeeded(stream); } } - // Put out the final code. - this.Output(ent, stream); - - this.Output(this.eofCode, stream); - } + // Write EOI when we are done. + this.WriteCode(stream, this.parent); + this.WriteCode(stream, EoiCode); - /// - /// Flush the packet to disk, and reset the accumulator. - /// - /// The output stream. - private void FlushPacket(Stream outStream) - { - if (this.accumulatorCount > 0) + // Flush partial codes by writing 0 pad. + if (this.bitPos > 0) { - outStream.Write(this.accumulators, 0, this.accumulatorCount); - this.accumulatorCount = 0; + this.WriteCode(stream, 0); } } - /// - /// Return the next pixel from the image - /// - /// - /// The - /// - private int NextPixel() + /// + public void Dispose() { - if (this.currentPixel == this.pixelArray.Length()) - { - return Eof; - } - - this.currentPixel++; - return this.pixelArray.GetSpan()[this.currentPixel - 1] & 0xff; + this.children.Dispose(); + this.siblings.Dispose(); + this.suffixes.Dispose(); } - /// - /// Output the current code to the stream. - /// - /// The code. - /// The stream to write to. - private void Output(int code, Stream outs) + private byte ReadNextByte() { - this.currentAccumulator &= Masks[this.currentBits]; - - if (this.currentBits > 0) - { - this.currentAccumulator |= code << this.currentBits; - } - else - { - this.currentAccumulator = code; - } - - this.currentBits += this.bitCount; - - while (this.currentBits >= 8) - { - this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); - this.currentAccumulator >>= 8; - this.currentBits -= 8; - } + Span dataSpan = this.data.GetSpan(); + var nextByte = dataSpan[this.bufferPosition]; + this.bufferPosition++; + return nextByte; + } - // If the next entry is going to be too big for the code size, - // then increase it, if possible. - if (this.freeEntry > this.maxcode || this.clearFlag) + private void IncreaseCodeSizeOrResetIfNeeded(Stream stream) + { + if (this.nextValidCode > this.maxCode) { - if (this.clearFlag) + if (this.bitsPerCode == MaxBits) { - this.maxcode = GetMaxcode(this.bitCount = this.globalInitialBits); - this.clearFlag = false; + // Reset stream by writing Clear code. + this.WriteCode(stream, ClearCode); + + // Reset tables. + this.ResetTables(); } else { - ++this.bitCount; - this.maxcode = this.bitCount == this.maxbits - ? this.maxmaxcode - : GetMaxcode(this.bitCount); + // Increase code size. + this.bitsPerCode++; + this.maxCode = MaxValue(this.bitsPerCode); } } + } - if (code == this.eofCode) - { - // At EOF, write the rest of the buffer. - while (this.currentBits > 0) - { - this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); - this.currentAccumulator >>= 8; - this.currentBits -= 8; - } + private void WriteCode(Stream stream, int code) + { + this.bits = (this.bits << this.bitsPerCode) | (code & this.maxCode); + this.bitPos += this.bitsPerCode; - this.FlushPacket(outs); + while (this.bitPos >= 8) + { + int b = (this.bits >> (this.bitPos - 8)) & 0xff; + stream.WriteByte((byte)b); + this.bitPos -= 8; } + + this.bits &= BitmaskFor(this.bitPos); } - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// If true, the object gets disposed. - private void Dispose(bool disposing) + private void ResetTables() { - if (this.isDisposed) - { - return; - } + this.children.GetSpan().Fill(0); + this.siblings.GetSpan().Fill(0); - if (disposing) - { - this.hashTable.Dispose(); - this.codeTable.Dispose(); - } + this.bitsPerCode = MinBits; + this.maxCode = MaxValue(this.bitsPerCode); + this.nextValidCode = EoiCode + 1; + } - this.isDisposed = true; + private static int MaxValue(int codeLen) + { + return (1 << codeLen) - 1; + } + + private static int BitmaskFor(int bits) + { + return MaxValue(bits); } } } diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs index 6deeb0a637..09db488903 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs @@ -238,7 +238,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils rowSpan.CopyTo(pixels.Slice(y * image.Width * 3)); } - using var lzwEncoder = new TiffLzwEncoder(this.memoryAllocator, pixelData, 8); + using var lzwEncoder = new TiffLzwEncoder(this.memoryAllocator, pixelData); lzwEncoder.Encode(memoryStream); byte[] buffer = memoryStream.ToArray(); @@ -441,7 +441,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils } } - using var lzwEncoder = new TiffLzwEncoder(this.memoryAllocator, pixelData, 8); + using var lzwEncoder = new TiffLzwEncoder(this.memoryAllocator, pixelData); lzwEncoder.Encode(memoryStream); byte[] buffer = memoryStream.ToArray(); @@ -584,7 +584,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils rowSpan.CopyTo(pixels.Slice(y * image.Width)); } - using var lzwEncoder = new TiffLzwEncoder(this.memoryAllocator, pixelData, 8); + using var lzwEncoder = new TiffLzwEncoder(this.memoryAllocator, pixelData); lzwEncoder.Encode(memoryStream); byte[] buffer = memoryStream.ToArray(); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs index 17a18a3466..fbac89e9ac 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression [InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes [InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence - public void Decompress_ReadsData(byte[] data) + public void Compress_Decompress_Roundtrip_Works(byte[] data) { using (Stream stream = CreateCompressedStream(data)) { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs index 1837fe98ef..79cc1b1a88 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs @@ -15,13 +15,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression [Trait("Format", "Tiff")] public class LzwTiffCompressionTests { + [Theory] + [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 }, new byte[] { 128, 0, 64, 66, 168, 36, 22, 12, 3, 2, 64, 64, 0, 0 })] // Repeated bytes + + public void Compress_Works(byte[] inputData, byte[] expectedCompressedData) + { + var compressedData = new byte[expectedCompressedData.Length]; + Stream streamData = CreateCompressedStream(inputData); + streamData.Read(compressedData, 0, expectedCompressedData.Length); + + Assert.Equal(expectedCompressedData, compressedData); + } + [Theory] [InlineData(new byte[] { })] [InlineData(new byte[] { 42 })] // One byte [InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes [InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence - public void Decompress_ReadsData(byte[] data) + + public void Compress_Decompress_Roundtrip_Works(byte[] data) { using Stream stream = CreateCompressedStream(data); var buffer = new byte[data.Length]; @@ -37,12 +50,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression 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, 8)) + using (var encoder = new TiffLzwEncoder(Configuration.Default.MemoryAllocator, data)) { encoder.Encode(compressedStream); } compressedStream.Seek(0, SeekOrigin.Begin); + return compressedStream; } }